Code

patch from Gustav Broberg: undo annotations and history dialog
[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, Inkscape::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_desktop_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_desktop_document(desktop)),
163                            a.my1 * sp_document_height(sp_desktop_document(desktop)));
164             break;
166         case AlignAndDistribute::DRAWING:
167         {
168             NR::Rect b = sp_item_bbox_desktop
169                 ( (SPItem *) sp_document_root (sp_desktop_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_desktop_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_desktop_document (desktop) , SP_VERB_DIALOG_ALIGN_DISTRIBUTE,
219                                /* TODO: annotate */ "align-and-distribute.cpp:219" );
220         }
223     }
224     guint _index;
225     AlignAndDistribute &_dialog;
227     static const Coeffs _allCoeffs[10];
229 };
230 ActionAlign::Coeffs const ActionAlign::_allCoeffs[10] = {
231     {1., 0., 0., 0., 0., 1., 0., 0.},
232     {1., 0., 0., 0., 1., 0., 0., 0.},
233     {.5, .5, 0., 0., .5, .5, 0., 0.},
234     {0., 1., 0., 0., 0., 1., 0., 0.},
235     {0., 1., 0., 0., 1., 0., 0., 0.},
236     {0., 0., 0., 1., 0., 0., 1., 0.},
237     {0., 0., 0., 1., 0., 0., 0., 1.},
238     {0., 0., .5, .5, 0., 0., .5, .5},
239     {0., 0., 1., 0., 0., 0., 1., 0.},
240     {0., 0., 1., 0., 0., 0., 0., 1.}
241 };
243 struct BBoxSort
245     SPItem *item;
246     float anchor;
247     NR::Rect bbox;
248     BBoxSort(SPItem *pItem, NR::Dim2 orientation, double kBegin, double kEnd) :
249         item(pItem),
250         bbox (sp_item_bbox_desktop (pItem))
251     {
252         anchor = kBegin * bbox.min()[orientation] + kEnd * bbox.max()[orientation];
253     }
254     BBoxSort(const BBoxSort &rhs):
255         //NOTE :  this copy ctor is called O(sort) when sorting the vector
256         //this is bad. The vector should be a vector of pointers.
257         //But I'll wait the bohem GC before doing that
258         item(rhs.item), anchor(rhs.anchor), bbox(rhs.bbox) {
259     }
260 };
261 bool operator< (const BBoxSort &a, const BBoxSort &b)
263     return (a.anchor < b.anchor);
266 class ActionDistribute : public Action {
267 public :
268     ActionDistribute(const Glib::ustring &id,
269                      const Glib::ustring &tiptext,
270                      guint row, guint column,
271                      AlignAndDistribute &dialog,
272                      bool onInterSpace,
273                      NR::Dim2 orientation,
274                      double kBegin, double kEnd
275         ):
276         Action(id, tiptext, row, column,
277                dialog.distribute_table(), dialog.tooltips(), dialog),
278         _dialog(dialog),
279         _onInterSpace(onInterSpace),
280         _orientation(orientation),
281         _kBegin(kBegin),
282         _kEnd( kEnd)
283     {}
285 private :
286     virtual void on_button_click() {
287         //Retreive selected objects
288         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
289         if (!desktop) return;
291         Inkscape::Selection *selection = sp_desktop_selection(desktop);
292         if (!selection) return;
294         using Inkscape::Util::GSListConstIterator;
295         std::list<SPItem *> selected;
296         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
297         if (selected.empty()) return;
299         //Check 2 or more selected objects
300         std::list<SPItem *>::iterator second(selected.begin());
301         ++second;
302         if (second == selected.end()) return;
305         std::vector< BBoxSort  > sorted;
306         for (std::list<SPItem *>::iterator it(selected.begin());
307             it != selected.end();
308             ++it)
309         {
310             BBoxSort b (*it, _orientation, _kBegin, _kEnd);
311             sorted.push_back(b);
312         }
313         //sort bbox by anchors
314         std::sort(sorted.begin(), sorted.end());
316         // see comment in ActionAlign above
317         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
318         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
320         unsigned int len = sorted.size();
321         bool changed = false;
322         if (_onInterSpace)
323         {
324             //overall bboxes span
325             float dist = (sorted.back().bbox.max()[_orientation] -
326                           sorted.front().bbox.min()[_orientation]);
327             //space eaten by bboxes
328             float span = 0;
329             for (unsigned int i = 0; i < len; i++)
330             {
331                 span += sorted[i].bbox.extent(_orientation);
332             }
333             //new distance between each bbox
334             float step = (dist - span) / (len - 1);
335             float pos = sorted.front().bbox.min()[_orientation];
336             for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
337                   it < sorted.end();
338                   it ++ )
339             {
340                 if (!NR_DF_TEST_CLOSE (pos, it->bbox.min()[_orientation], 1e-6)) {
341                     NR::Point t(0.0, 0.0);
342                     t[_orientation] = pos - it->bbox.min()[_orientation];
343                     sp_item_move_rel(it->item, NR::translate(t));
344                     changed = true;
345                 }
346                 pos += it->bbox.extent(_orientation);
347                 pos += step;
348             }
349         }
350         else
351         {
352             //overall anchor span
353             float dist = sorted.back().anchor - sorted.front().anchor;
354             //distance between anchors
355             float step = dist / (len - 1);
357             for ( unsigned int i = 0; i < len ; i ++ )
358             {
359                 BBoxSort & it(sorted[i]);
360                 //new anchor position
361                 float pos = sorted.front().anchor + i * step;
362                 //Don't move if we are really close
363                 if (!NR_DF_TEST_CLOSE (pos, it.anchor, 1e-6)) {
364                     //Compute translation
365                     NR::Point t(0.0, 0.0);
366                     t[_orientation] = pos - it.anchor;
367                     //translate
368                     sp_item_move_rel(it.item, NR::translate(t));
369                     changed = true;
370                 }
371             }
372         }
374         // restore compensation setting
375         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
377         if (changed) {
378             sp_document_done ( sp_desktop_document (desktop), SP_VERB_DIALOG_ALIGN_DISTRIBUTE, 
379                                /* TODO: annotate */ "align-and-distribute.cpp:379");
380         }
381     }
382     guint _index;
383     AlignAndDistribute &_dialog;
384     bool _onInterSpace;
385     NR::Dim2 _orientation;
387     double _kBegin;
388     double _kEnd;
390 };
393 class ActionNode : public Action {
394 public :
395     ActionNode(const Glib::ustring &id,
396                const Glib::ustring &tiptext,
397                guint column,
398                AlignAndDistribute &dialog,
399                NR::Dim2 orientation, bool distribute):
400         Action(id, tiptext, 0, column,
401                dialog.nodes_table(), dialog.tooltips(), dialog),
402         _orientation(orientation),
403         _distribute(distribute)
404     {}
406 private :
407     NR::Dim2 _orientation;
408     bool _distribute;
409     virtual void on_button_click()
410     {
412         if (!SP_ACTIVE_DESKTOP) return;
413         SPEventContext *event_context = sp_desktop_event_context(SP_ACTIVE_DESKTOP);
414         if (!SP_IS_NODE_CONTEXT (event_context)) return ;
416         Inkscape::NodePath::Path *nodepath = SP_NODE_CONTEXT (event_context)->nodepath;
417         if (!nodepath) return;
418         if (_distribute)
419             sp_nodepath_selected_distribute(nodepath, _orientation);
420         else
421             sp_nodepath_selected_align(nodepath, _orientation);
423     }
424 };
426 class ActionRemoveOverlaps : public Action {
427 private:
428     Gtk::Label removeOverlapXGapLabel;
429     Gtk::Label removeOverlapYGapLabel;
430     Gtk::SpinButton removeOverlapXGap;
431     Gtk::SpinButton removeOverlapYGap;
433 public:
434     ActionRemoveOverlaps(Glib::ustring const &id,
435                          Glib::ustring const &tiptext,
436                          guint row,
437                          guint column,
438                          AlignAndDistribute &dialog) :
439         Action(id, tiptext, row, column + 4,
440                dialog.removeOverlap_table(), dialog.tooltips(), dialog)
441     {
442         dialog.removeOverlap_table().set_col_spacings(3);
443     
444         removeOverlapXGap.set_digits(1);
445         removeOverlapXGap.set_size_request(60, -1);
446         removeOverlapXGap.set_increments(1.0, 5.0);
447         removeOverlapXGap.set_range(-1000.0, 1000.0);
448         removeOverlapXGap.set_value(0);
449         dialog.tooltips().set_tip(removeOverlapXGap,
450                                   _("Minimum horizontal gap (in px units) between bounding boxes"));
451         /* TRANSLATORS: Horizontal gap */
452         removeOverlapXGapLabel.set_label(_("H:"));
454         removeOverlapYGap.set_digits(1);
455         removeOverlapYGap.set_size_request(60, -1);
456         removeOverlapYGap.set_increments(1.0, 5.0);
457         removeOverlapYGap.set_range(-1000.0, 1000.0);
458         removeOverlapYGap.set_value(0);
459         dialog.tooltips().set_tip(removeOverlapYGap,
460                                   _("Minimum vertical gap (in px units) between bounding boxes"));
461         /* TRANSLATORS: Vertical gap */
462         removeOverlapYGapLabel.set_label(_("V:"));
464         dialog.removeOverlap_table().attach(removeOverlapXGapLabel, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
465         dialog.removeOverlap_table().attach(removeOverlapXGap, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
466         dialog.removeOverlap_table().attach(removeOverlapYGapLabel, column+2, column+3, row, row+1, Gtk::FILL, Gtk::FILL);
467         dialog.removeOverlap_table().attach(removeOverlapYGap, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
469     }
471 private :
472     virtual void on_button_click()
473     {
474         if (!SP_ACTIVE_DESKTOP) return;
476         // see comment in ActionAlign above
477         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
478         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
480         // xGap and yGap are the minimum space required between bounding rectangles.
481         double const xGap = removeOverlapXGap.get_value();
482         double const yGap = removeOverlapYGap.get_value();
483         removeoverlap(sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList(),
484                       xGap, yGap);
486         // restore compensation setting
487         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
489         sp_document_done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE, 
490                          /* TODO: annotate */ "align-and-distribute.cpp:490");
491     }
492 };
494 class ActionGraphLayout : public Action {
495 public:
496     ActionGraphLayout(Glib::ustring const &id,
497                          Glib::ustring const &tiptext,
498                          guint row,
499                          guint column,
500                          AlignAndDistribute &dialog) :
501         Action(id, tiptext, row, column + 4,
502                dialog.graphLayout_table(), dialog.tooltips(), dialog)
503     {}
505 private :
506     virtual void on_button_click()
507     {
508         if (!SP_ACTIVE_DESKTOP) return;
510         // see comment in ActionAlign above
511         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
512         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
514         graphlayout(sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList());
516         // restore compensation setting
517         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
519         sp_document_done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE, 
520                          /* TODO: annotate */ "align-and-distribute.cpp:520");
521     }
522 };
524 class ActionUnclump : public Action {
525 public :
526     ActionUnclump(const Glib::ustring &id,
527                const Glib::ustring &tiptext,
528                guint row,
529                guint column,
530                AlignAndDistribute &dialog):
531         Action(id, tiptext, row, column,
532                dialog.distribute_table(), dialog.tooltips(), dialog)
533     {}
535 private :
536     virtual void on_button_click()
537     {
538         if (!SP_ACTIVE_DESKTOP) return;
540         // see comment in ActionAlign above
541         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
542         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
544         unclump ((GSList *) sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList());
546         // restore compensation setting
547         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
549         sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE, 
550                           /* TODO: annotate */ "align-and-distribute.cpp:550");
551     }
552 };
554 class ActionRandomize : public Action {
555 public :
556     ActionRandomize(const Glib::ustring &id,
557                const Glib::ustring &tiptext,
558                guint row,
559                guint column,
560                AlignAndDistribute &dialog):
561         Action(id, tiptext, row, column,
562                dialog.distribute_table(), dialog.tooltips(), dialog)
563     {}
565 private :
566     virtual void on_button_click()
567     {
568         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
569         if (!desktop) return;
571         Inkscape::Selection *selection = sp_desktop_selection(desktop);
572         if (!selection) return;
574         using Inkscape::Util::GSListConstIterator;
575         std::list<SPItem *> selected;
576         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
577         if (selected.empty()) return;
579         //Check 2 or more selected objects
580         if (selected.size() < 2) return;
582         // This bbox is cached between calls to randomize, so that there's no growth nor shrink
583         // nor drift on sequential randomizations. Discard cache on global (or better active
584         // desktop's) selection_change signal.
585         if (!_dialog.randomize_bbox_set) {
586             _dialog.randomize_bbox = selection->bounds();
587             _dialog.randomize_bbox_set = true;
588         }
590         // see comment in ActionAlign above
591         int saved_compensation = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
592         prefs_set_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_UNMOVED);
594         for (std::list<SPItem *>::iterator it(selected.begin());
595             it != selected.end();
596             ++it)
597         {
598             sp_document_ensure_up_to_date(sp_desktop_document (desktop));
599             NR::Rect item_box = sp_item_bbox_desktop (*it);
600             // find new center, staying within bbox 
601             double x = _dialog.randomize_bbox.min()[NR::X] + item_box.extent(NR::X)/2 +
602                 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::X) - item_box.extent(NR::X));
603             double y = _dialog.randomize_bbox.min()[NR::Y] + item_box.extent(NR::Y)/2 +
604                 g_random_double_range (0, _dialog.randomize_bbox.extent(NR::Y) - item_box.extent(NR::Y));
605             // displacement is the new center minus old:
606             NR::Point t = NR::Point (x, y) - 0.5*(item_box.max() + item_box.min());
607             sp_item_move_rel(*it, NR::translate(t));
608         }
610         // restore compensation setting
611         prefs_set_int_attribute("options.clonecompensation", "value", saved_compensation);
613         sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE, 
614                           /* TODO: annotate */ "align-and-distribute.cpp:614");
615     }
616 };
618 struct Baselines
620     SPItem *_item;
621     NR::Point _base;
622     NR::Dim2 _orientation;
623     Baselines(SPItem *item, NR::Point base, NR::Dim2 orientation) :
624         _item (item),
625         _base (base),
626         _orientation (orientation)
627     {}
628 };
630 bool operator< (const Baselines &a, const Baselines &b)
632     return (a._base[a._orientation] < b._base[b._orientation]);
635 class ActionBaseline : public Action {
636 public :
637     ActionBaseline(const Glib::ustring &id,
638                const Glib::ustring &tiptext,
639                guint row,
640                guint column,
641                AlignAndDistribute &dialog,
642                Gtk::Table &table,
643                NR::Dim2 orientation, bool distribute):
644         Action(id, tiptext, row, column,
645                table, dialog.tooltips(), dialog),
646         _orientation(orientation),
647         _distribute(distribute)
648     {}
650 private :
651     NR::Dim2 _orientation;
652     bool _distribute;
653     virtual void on_button_click()
654     {
655         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
656         if (!desktop) return;
658         Inkscape::Selection *selection = sp_desktop_selection(desktop);
659         if (!selection) return;
661         using Inkscape::Util::GSListConstIterator;
662         std::list<SPItem *> selected;
663         selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
664         if (selected.empty()) return;
666         //Check 2 or more selected objects
667         if (selected.size() < 2) return;
669         NR::Point b_min = NR::Point (HUGE_VAL, HUGE_VAL);
670         NR::Point b_max = NR::Point (-HUGE_VAL, -HUGE_VAL);
672         std::vector<Baselines> sorted;
674         for (std::list<SPItem *>::iterator it(selected.begin());
675             it != selected.end();
676             ++it)
677         {
678             if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
679                 Inkscape::Text::Layout const *layout = te_get_layout(*it);
680                 NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
681                 if (base[NR::X] < b_min[NR::X]) b_min[NR::X] = base[NR::X];
682                 if (base[NR::Y] < b_min[NR::Y]) b_min[NR::Y] = base[NR::Y];
683                 if (base[NR::X] > b_max[NR::X]) b_max[NR::X] = base[NR::X];
684                 if (base[NR::Y] > b_max[NR::Y]) b_max[NR::Y] = base[NR::Y];
686                 Baselines b (*it, base, _orientation);
687                 sorted.push_back(b);
688             }
689         }
691         if (sorted.size() <= 1) return;
693         //sort baselines
694         std::sort(sorted.begin(), sorted.end());
696         bool changed = false;
698         if (_distribute) {
699             double step = (b_max[_orientation] - b_min[_orientation])/(sorted.size() - 1);
700             for (unsigned int i = 0; i < sorted.size(); i++) {
701                 SPItem *item = sorted[i]._item;
702                 NR::Point base = sorted[i]._base;
703                 NR::Point t(0.0, 0.0);
704                 t[_orientation] = b_min[_orientation] + step * i - base[_orientation];
705                 sp_item_move_rel(item, NR::translate(t));
706                 changed = true;
707             }
709         } else {
710             for (std::list<SPItem *>::iterator it(selected.begin());
711                  it != selected.end();
712                  ++it)
713             {
714                 if (SP_IS_TEXT (*it) || SP_IS_FLOWTEXT (*it)) {
715                     Inkscape::Text::Layout const *layout = te_get_layout(*it);
716                     NR::Point base = layout->characterAnchorPoint(layout->begin()) * sp_item_i2d_affine(*it);
717                     NR::Point t(0.0, 0.0);
718                     t[_orientation] = b_min[_orientation] - base[_orientation];
719                     sp_item_move_rel(*it, NR::translate(t));
720                     changed = true;
721                 }
722             }
723         }
725         if (changed) {
726             sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_ALIGN_DISTRIBUTE, 
727                               /* TODO: annotate */ "align-and-distribute.cpp:727");
728         }
729     }
730 };
734 void on_tool_changed(Inkscape::Application *inkscape, SPEventContext *context, AlignAndDistribute *daad)
736     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
737     if (desktop && sp_desktop_event_context(desktop))
738         daad->setMode(tools_active(desktop) == TOOLS_NODES);
741 void on_selection_changed(Inkscape::Application *inkscape, Inkscape::Selection *selection, AlignAndDistribute *daad)
743     daad->randomize_bbox_set = false;
746 /////////////////////////////////////////////////////////
751 AlignAndDistribute::AlignAndDistribute() 
752     : Dialog ("dialogs.align", SP_VERB_DIALOG_ALIGN_DISTRIBUTE),
753       randomize_bbox (NR::Point (0, 0), NR::Point (0, 0)),
754       _alignFrame(_("Align")),
755       _distributeFrame(_("Distribute")),
756       _removeOverlapFrame(_("Remove overlaps")),
757       _graphLayoutFrame(_("Connector network layout")),
758       _nodesFrame(_("Nodes")),
759       _alignTable(2, 6, true),
760       _distributeTable(3, 6, true),
761       _removeOverlapTable(1, 5, false),
762       _graphLayoutTable(1, 5, false),
763       _nodesTable(1, 4, true),
764       _anchorLabel(_("Relative to: "))
767     //Instanciate the align buttons
768     addAlignButton("al_left_out",
769                    _("Align right sides of objects to left side of anchor"),
770                    0, 0);
771     addAlignButton("al_left_in",
772                    _("Align left sides"),
773                    0, 1);
774     addAlignButton("al_center_hor",
775                    _("Center on vertical axis"),
776                    0, 2);
777     addAlignButton("al_right_in",
778                    _("Align right sides"),
779                    0, 3);
780     addAlignButton("al_right_out",
781                    _("Align left sides of objects to right side of anchor"),
782                    0, 4);
783     addAlignButton("al_top_out",
784                    _("Align bottoms of objects to top of anchor"),
785                    1, 0);
786     addAlignButton("al_top_in",
787                    _("Align tops"),
788                    1, 1);
789     addAlignButton("al_center_ver",
790                    _("Center on horizontal axis"),
791                    1, 2);
792     addAlignButton("al_bottom_in",
793                    _("Align bottoms"),
794                    1, 3);
795     addAlignButton("al_bottom_out",
796                    _("Align tops of objects to bottom of anchor"),
797                    1, 4);
799     //Baseline aligns
800     addBaselineButton("al_baselines_vert",
801                    _("Align baseline anchors of texts vertically"),
802                       0, 5, this->align_table(), NR::X, false);
803     addBaselineButton("al_baselines_hor",
804                    _("Align baseline anchors of texts horizontally"),
805                      1, 5, this->align_table(), NR::Y, false);
807     //The distribute buttons
808     addDistributeButton("distribute_hdist",
809                         _("Make horizontal gaps between objects equal"),
810                         0, 4, true, NR::X, .5, .5);
812     addDistributeButton("distribute_left",
813                         _("Distribute left sides equidistantly"),
814                         0, 1, false, NR::X, 1., 0.);
815     addDistributeButton("distribute_hcentre",
816                         _("Distribute centers equidistantly horizontally"),
817                         0, 2, false, NR::X, .5, .5);
818     addDistributeButton("distribute_right",
819                         _("Distribute right sides equidistantly"),
820                         0, 3, false, NR::X, 0., 1.);
822     addDistributeButton("distribute_vdist",
823                         _("Make vertical gaps between objects equal"),
824                         1, 4, true, NR::Y, .5, .5);
826     addDistributeButton("distribute_top",
827                         _("Distribute tops equidistantly"),
828                         1, 1, false, NR::Y, 0, 1);
829     addDistributeButton("distribute_vcentre",
830                         _("Distribute centers equidistantly vertically"),
831                         1, 2, false, NR::Y, .5, .5);
832     addDistributeButton("distribute_bottom",
833                         _("Distribute bottoms equidistantly"),
834                         1, 3, false, NR::Y, 1., 0.);
836     //Baseline distribs
837     addBaselineButton("distribute_baselines_hor",
838                    _("Distribute baseline anchors of texts horizontally"),
839                       0, 5, this->distribute_table(), NR::X, true);
840     addBaselineButton("distribute_baselines_vert",
841                    _("Distribute baseline anchors of texts vertically"),
842                      1, 5, this->distribute_table(), NR::Y, true);
844     //Randomize & Unclump
845     addRandomizeButton("distribute_randomize",
846                         _("Randomize centers in both dimensions"),
847                         2, 2);
848     addUnclumpButton("unclump",
849                         _("Unclump objects: try to equalize edge-to-edge distances"),
850                         2, 4);
852     //Remove overlaps
853     addRemoveOverlapsButton("remove_overlaps",
854                             _("Move objects as little as possible so that their bounding boxes do not overlap"),
855                             0, 0);
856     //Graph Layout
857     addGraphLayoutButton("graph_layout",
858                             _("Nicely arrange selected connector network"),
859                             0, 0);
861     //Node Mode buttons
862     addNodeButton("node_halign",
863                   _("Align selected nodes horizontally"),
864                   0, NR::X, false);
865     addNodeButton("node_valign",
866                   _("Align selected nodes vertically"),
867                   1, NR::Y, false);
868     addNodeButton("node_hdistribute",
869                   _("Distribute selected nodes horizontally"),
870                   2, NR::X, true);
871     addNodeButton("node_vdistribute",
872                   _("Distribute selected nodes vertically"),
873                   3, NR::Y, true);
875     //Rest of the widgetry
877     _combo.append_text(_("Last selected"));
878     _combo.append_text(_("First selected"));
879     _combo.append_text(_("Biggest item"));
880     _combo.append_text(_("Smallest item"));
881     _combo.append_text(_("Page"));
882     _combo.append_text(_("Drawing"));
883     _combo.append_text(_("Selection"));
885     _combo.set_active(6);
886     _combo.signal_changed().connect(sigc::mem_fun(*this, &AlignAndDistribute::on_ref_change));
888     _anchorBox.pack_start(_anchorLabel);
889     _anchorBox.pack_start(_combo);
891     _alignBox.pack_start(_anchorBox);
892     _alignBox.pack_start(_alignTable);
894     _alignFrame.add(_alignBox);
895     _distributeFrame.add(_distributeTable);
896     _removeOverlapFrame.add(_removeOverlapTable);
897     _graphLayoutFrame.add(_graphLayoutTable);
898     _nodesFrame.add(_nodesTable);
900     // Top level vbox
901     Gtk::VBox *vbox = get_vbox();
902     vbox->set_spacing(4);
904     // Notebook for individual transformations
906     vbox->pack_start(_alignFrame, true, true);
907     vbox->pack_start(_distributeFrame, true, true);
908     vbox->pack_start(_removeOverlapFrame, true, true);
909     vbox->pack_start(_graphLayoutFrame, true, true);
910     vbox->pack_start(_nodesFrame, true, true);
912     //Connect to the global tool change signal
913     g_signal_connect (G_OBJECT (INKSCAPE), "set_eventcontext", G_CALLBACK (on_tool_changed), this);
915     // Connect to the global selection change, to invalidate cached randomize_bbox
916     g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
917     randomize_bbox = NR::Rect (NR::Point (0, 0), NR::Point (0, 0));
918     randomize_bbox_set = false;
920     show_all_children();
922     on_tool_changed (NULL, NULL, this); // set current mode
925 AlignAndDistribute::~AlignAndDistribute() 
927     sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
929     for (std::list<Action *>::iterator it = _actionList.begin();
930          it != _actionList.end();
931          it ++)
932         delete *it;
935 void AlignAndDistribute::on_ref_change(){
936 //Make blink the master
942 void AlignAndDistribute::setMode(bool nodeEdit)
944     //Act on widgets used in node mode
945     void ( Gtk::Widget::*mNode) ()  = nodeEdit ?
946         &Gtk::Widget::show_all : &Gtk::Widget::hide_all;
948     //Act on widgets used in selection mode
949   void ( Gtk::Widget::*mSel) ()  = nodeEdit ?
950       &Gtk::Widget::hide_all : &Gtk::Widget::show_all;
953     ((_alignFrame).*(mSel))();
954     ((_distributeFrame).*(mSel))();
955     ((_removeOverlapFrame).*(mSel))();
956     ((_graphLayoutFrame).*(mSel))();
957     ((_nodesFrame).*(mNode))();
960 void AlignAndDistribute::addAlignButton(const Glib::ustring &id, const Glib::ustring tiptext,
961                                  guint row, guint col)
963     _actionList.push_back(
964         new ActionAlign(
965             id, tiptext, row, col,
966             *this , col + row * 5));
968 void AlignAndDistribute::addDistributeButton(const Glib::ustring &id, const Glib::ustring tiptext,
969                                       guint row, guint col, bool onInterSpace,
970                                       NR::Dim2 orientation, float kBegin, float kEnd)
972     _actionList.push_back(
973         new ActionDistribute(
974             id, tiptext, row, col, *this ,
975             onInterSpace, orientation,
976             kBegin, kEnd
977             )
978         );
981 void AlignAndDistribute::addNodeButton(const Glib::ustring &id, const Glib::ustring tiptext,
982                    guint col, NR::Dim2 orientation, bool distribute)
984     _actionList.push_back(
985         new ActionNode(
986             id, tiptext, col,
987             *this, orientation, distribute));
990 void AlignAndDistribute::addRemoveOverlapsButton(const Glib::ustring &id, const Glib::ustring tiptext,
991                                       guint row, guint col)
993     _actionList.push_back(
994         new ActionRemoveOverlaps(
995             id, tiptext, row, col, *this)
996         );
999 void AlignAndDistribute::addGraphLayoutButton(const Glib::ustring &id, const Glib::ustring tiptext,
1000                                       guint row, guint col)
1002     _actionList.push_back(
1003         new ActionGraphLayout(
1004             id, tiptext, row, col, *this)
1005         );
1008 void AlignAndDistribute::addUnclumpButton(const Glib::ustring &id, const Glib::ustring tiptext,
1009                                       guint row, guint col)
1011     _actionList.push_back(
1012         new ActionUnclump(
1013             id, tiptext, row, col, *this)
1014         );
1017 void AlignAndDistribute::addRandomizeButton(const Glib::ustring &id, const Glib::ustring tiptext,
1018                                       guint row, guint col)
1020     _actionList.push_back(
1021         new ActionRandomize(
1022             id, tiptext, row, col, *this)
1023         );
1026 void AlignAndDistribute::addBaselineButton(const Glib::ustring &id, const Glib::ustring tiptext,
1027                                     guint row, guint col, Gtk::Table &table, NR::Dim2 orientation, bool distribute)
1029     _actionList.push_back(
1030         new ActionBaseline(
1031             id, tiptext, row, col, 
1032             *this, table, orientation, distribute));
1038 std::list<SPItem *>::iterator AlignAndDistribute::find_master( std::list<SPItem *> &list, bool horizontal){
1039     std::list<SPItem *>::iterator master = list.end();
1040     switch (getAlignTarget()) {
1041     case LAST:
1042         return list.begin();
1043         break;
1045     case FIRST:
1046         return --(list.end());
1047         break;
1049     case BIGGEST:
1050     {
1051         gdouble max = -1e18;
1052         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1053             NR::Rect b = sp_item_bbox_desktop (*it);
1054             gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1055             if (dim > max) {
1056                 max = dim;
1057                 master = it;
1058             }
1059         }
1060         return master;
1061         break;
1062     }
1064     case SMALLEST:
1065     {
1066         gdouble max = 1e18;
1067         for (std::list<SPItem *>::iterator it = list.begin(); it != list.end(); it++) {
1068             NR::Rect b = sp_item_bbox_desktop (*it);
1069             gdouble dim = b.extent(horizontal ? NR::X : NR::Y);
1070             if (dim < max) {
1071                 max = dim;
1072                 master = it;
1073             }
1074         }
1075         return master;
1076         break;
1077     }
1079     default:
1080         g_assert_not_reached ();
1081         break;
1083     } // end of switch statement
1084     return master;
1087 AlignAndDistribute::AlignTarget AlignAndDistribute::getAlignTarget()const {
1088     return AlignTarget(_combo.get_active_row_number());
1093 } // namespace Dialog
1094 } // namespace UI
1095 } // namespace Inkscape
1097 /*
1098   Local Variables:
1099   mode:c++
1100   c-file-style:"stroustrup"
1101   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1102   indent-tabs-mode:nil
1103   fill-column:99
1104   End:
1105 */
1106 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :