1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * A simple dialog for creating grid type arrangements of selected objects
4  *
5  * Authors:
6  *   Bob Jamison ( based off trace dialog)
7  *   John Cliff
8  *   Other dudes from The Inkscape Organization
9  *   Abhishek Sharma
10  *   Declara Denis
11  *
12  * Copyright (C) 2004 Bob Jamison
13  * Copyright (C) 2004 John Cliff
14  *
15  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16  */
17 //#define DEBUG_GRID_ARRANGE 1
18 
19 #include "ui/dialog/grid-arrange-tab.h"
20 #include <glibmm/i18n.h>
21 
22 #include <gtkmm/grid.h>
23 #include <gtkmm/sizegroup.h>
24 
25 #include <2geom/transforms.h>
26 
27 #include "verbs.h"
28 #include "preferences.h"
29 #include "inkscape.h"
30 
31 #include "document.h"
32 #include "document-undo.h"
33 #include "desktop.h"
34 //#include "sp-item-transform.h" FIXME
35 #include "ui/dialog/tile.h" // for Inkscape::UI::Dialog::ArrangeDialog
36 
37     /*
38      *    Sort items by their x co-ordinates, taking account of y (keeps rows intact)
39      *
40      *    <0 *elem1 goes before *elem2
41      *    0  *elem1 == *elem2
42      *    >0  *elem1 goes after *elem2
43      */
sp_compare_x_position(SPItem * first,SPItem * second)44     static bool sp_compare_x_position(SPItem *first, SPItem *second)
45     {
46         using Geom::X;
47         using Geom::Y;
48 
49         Geom::OptRect a = first->documentVisualBounds();
50         Geom::OptRect b = second->documentVisualBounds();
51 
52         if ( !a || !b ) {
53             // FIXME?
54             return false;
55         }
56 
57         double const a_height = a->dimensions()[Y];
58         double const b_height = b->dimensions()[Y];
59 
60         bool a_in_b_vert = false;
61         if ((a->min()[Y] < b->min()[Y] + 0.1) && (a->min()[Y] > b->min()[Y] - b_height)) {
62             a_in_b_vert = true;
63         } else if ((b->min()[Y] < a->min()[Y] + 0.1) && (b->min()[Y] > a->min()[Y] - a_height)) {
64             a_in_b_vert = true;
65         } else if (b->min()[Y] == a->min()[Y]) {
66             a_in_b_vert = true;
67         } else {
68             a_in_b_vert = false;
69         }
70 
71         if (!a_in_b_vert) { // a and b are not in the same row
72             return (a->min()[Y] < b->min()[Y]);
73         }
74         return (a->min()[X] < b->min()[X]);
75     }
76 
77     /*
78      *    Sort items by their y co-ordinates.
79      */
sp_compare_y_position(SPItem * first,SPItem * second)80     static bool sp_compare_y_position(SPItem *first, SPItem *second)
81     {
82         Geom::OptRect a = first->documentVisualBounds();
83         Geom::OptRect b = second->documentVisualBounds();
84 
85         if ( !a || !b ) {
86             // FIXME?
87             return false;
88         }
89 
90         if (a->min()[Geom::Y] > b->min()[Geom::Y]) {
91             return false;
92         }
93         if (a->min()[Geom::Y] < b->min()[Geom::Y]) {
94             return true;
95         }
96 
97         return false;
98     }
99 
100 
101     namespace Inkscape {
102     namespace UI {
103     namespace Dialog {
104 
105 
106     //#########################################################################
107     //## E V E N T S
108     //#########################################################################
109 
110     /*
111      *
112      * This arranges the selection in a grid pattern.
113      *
114      */
115 
arrange()116     void GridArrangeTab::arrange()
117     {
118 
119         int cnt,row_cnt,col_cnt,a,row,col;
120         double grid_left,grid_top,col_width,row_height,paddingx,paddingy,width, height, new_x, new_y;
121         double total_col_width,total_row_height;
122         col_width = 0;
123         row_height = 0;
124         total_col_width=0;
125         total_row_height=0;
126 
127         // check for correct numbers in the row- and col-spinners
128         on_col_spinbutton_changed();
129         on_row_spinbutton_changed();
130 
131         // set padding to manual values
132         paddingx = XPadding.getValue("px");
133         paddingy = YPadding.getValue("px");
134 
135         std::vector<double> row_heights;
136         std::vector<double> col_widths;
137         std::vector<double> row_ys;
138         std::vector<double> col_xs;
139 
140         int NoOfCols = NoOfColsSpinner.get_value_as_int();
141         int NoOfRows = NoOfRowsSpinner.get_value_as_int();
142 
143         width = 0;
144         for (a=0;a<NoOfCols; a++){
145             col_widths.push_back(width);
146         }
147 
148         height = 0;
149         for (a=0;a<NoOfRows; a++){
150             row_heights.push_back(height);
151         }
152         grid_left = 99999;
153         grid_top = 99999;
154 
155         SPDesktop *desktop = Parent->getDesktop();
156         desktop->getDocument()->ensureUpToDate();
157 
158         Inkscape::Selection *selection = desktop->getSelection();
159         std::vector<SPItem*> items;
160         if (selection) {
161             items.insert(items.end(), selection->items().begin(), selection->items().end());
162         }
163 
164         for(auto item : items){
165             Geom::OptRect b = item->documentVisualBounds();
166             if (!b) {
167                 continue;
168             }
169 
170             width = b->dimensions()[Geom::X];
171             height = b->dimensions()[Geom::Y];
172 
173             if (b->min()[Geom::X] < grid_left) {
174                 grid_left = b->min()[Geom::X];
175             }
176             if (b->min()[Geom::Y] < grid_top) {
177                 grid_top = b->min()[Geom::Y];
178             }
179             if (width > col_width) {
180                 col_width = width;
181             }
182             if (height > row_height) {
183                 row_height = height;
184             }
185         }
186 
187 
188         // require the sorting done before we can calculate row heights etc.
189 
190         g_return_if_fail(selection);
191         std::vector<SPItem*> sorted(selection->items().begin(), selection->items().end());
192         sort(sorted.begin(),sorted.end(),sp_compare_y_position);
193         sort(sorted.begin(),sorted.end(),sp_compare_x_position);
194 
195 
196         // Calculate individual Row and Column sizes if necessary
197 
198 
199             cnt=0;
200             const std::vector<SPItem*> sizes(sorted);
201             for (auto item : sizes) {
202                 Geom::OptRect b = item->documentVisualBounds();
203                 if (b) {
204                     width = b->dimensions()[Geom::X];
205                     height = b->dimensions()[Geom::Y];
206                     if (width > col_widths[(cnt % NoOfCols)]) {
207                         col_widths[(cnt % NoOfCols)] = width;
208                     }
209                     if (height > row_heights[(cnt / NoOfCols)]) {
210                         row_heights[(cnt / NoOfCols)] = height;
211                     }
212                 }
213 
214                 cnt++;
215             }
216 
217 
218         /// Make sure the top and left of the grid don't move by compensating for align values.
219     if (RowHeightButton.get_active()){
220         grid_top = grid_top - (((row_height - row_heights[0]) / 2)*(VertAlign));
221     }
222     if (ColumnWidthButton.get_active()){
223         grid_left = grid_left - (((col_width - col_widths[0]) /2)*(HorizAlign));
224     }
225 
226     #ifdef DEBUG_GRID_ARRANGE
227      g_print("\n cx = %f cy= %f gridleft=%f",cx,cy,grid_left);
228     #endif
229 
230     // Calculate total widths and heights, allowing for columns and rows non uniformly sized.
231 
232     if (ColumnWidthButton.get_active()){
233         total_col_width = col_width * NoOfCols;
234         col_widths.clear();
235         for (a=0;a<NoOfCols; a++){
236             col_widths.push_back(col_width);
237         }
238     } else {
239         for (a = 0; a < (int)col_widths.size(); a++)
240         {
241           total_col_width += col_widths[a] ;
242         }
243     }
244 
245     if (RowHeightButton.get_active()){
246         total_row_height = row_height * NoOfRows;
247         row_heights.clear();
248         for (a=0;a<NoOfRows; a++){
249             row_heights.push_back(row_height);
250         }
251     } else {
252         for (a = 0; a < (int)row_heights.size(); a++)
253         {
254           total_row_height += row_heights[a] ;
255         }
256     }
257 
258 
259     Geom::OptRect sel_bbox = selection->visualBounds();
260     // Fit to bbox, calculate padding between rows accordingly.
261     if ( sel_bbox && !SpaceManualRadioButton.get_active() ){
262 #ifdef DEBUG_GRID_ARRANGE
263 g_print("\n row = %f     col = %f selection x= %f selection y = %f", total_row_height,total_col_width, b.extent(Geom::X), b.extent(Geom::Y));
264 #endif
265         paddingx = (sel_bbox->width() - total_col_width) / (NoOfCols -1);
266         paddingy = (sel_bbox->height() - total_row_height) / (NoOfRows -1);
267     }
268 
269 /*
270     Horizontal align  - Left    = 0
271                         Centre  = 1
272                         Right   = 2
273 
274     Vertical align    - Top     = 0
275                         Middle  = 1
276                         Bottom  = 2
277 
278     X position is calculated by taking the grids left co-ord, adding the distance to the column,
279    then adding 1/2 the spacing multiplied by the align variable above,
280    Y position likewise, takes the top of the grid, adds the y to the current row then adds the padding in to align it.
281 
282 */
283 
284     // Calculate row and column x and y coords required to allow for columns and rows which are non uniformly sized.
285 
286     for (a=0;a<NoOfCols; a++){
287         if (a<1) col_xs.push_back(0);
288         else col_xs.push_back(col_widths[a-1]+paddingx+col_xs[a-1]);
289     }
290 
291 
292     for (a=0;a<NoOfRows; a++){
293         if (a<1) row_ys.push_back(0);
294         else row_ys.push_back(row_heights[a-1]+paddingy+row_ys[a-1]);
295     }
296 
297     cnt=0;
298     std::vector<SPItem*>::iterator it = sorted.begin();
299     for (row_cnt=0; ((it != sorted.end()) && (row_cnt<NoOfRows)); ++row_cnt) {
300 
301              std::vector<SPItem *> current_row;
302              col_cnt = 0;
303              for(;it!=sorted.end()&&col_cnt<NoOfCols;++it) {
304                  current_row.push_back(*it);
305                  col_cnt++;
306              }
307 
308              for (auto item:current_row) {
309                  Geom::OptRect b = item->documentVisualBounds();
310                  Geom::Point min;
311                  if (b) {
312                      width = b->dimensions()[Geom::X];
313                      height = b->dimensions()[Geom::Y];
314                      min = b->min();
315                  } else {
316                      width = height = 0;
317                      min = Geom::Point(0, 0);
318                  }
319 
320                  row = cnt / NoOfCols;
321                  col = cnt % NoOfCols;
322 
323                  new_x = grid_left + (((col_widths[col] - width)/2)*HorizAlign) + col_xs[col];
324                  new_y = grid_top + (((row_heights[row] - height)/2)*VertAlign) + row_ys[row];
325 
326                  Geom::Point move = Geom::Point(new_x, new_y) - min;
327                  Geom::Affine const affine = Geom::Affine(Geom::Translate(move));
328                  item->set_i2d_affine(item->i2doc_affine() * affine * item->document->doc2dt());
329                  item->doWriteTransform(item->transform);
330                  item->updateRepr();
331                  cnt +=1;
332              }
333     }
334 
335     DocumentUndo::done(desktop->getDocument(), SP_VERB_SELECTION_ARRANGE,
336                        _("Arrange in a grid"));
337 
338 }
339 
340 
341 //#########################################################################
342 //## E V E N T S
343 //#########################################################################
344 
345 /**
346  * changed value in # of columns spinbox.
347  */
on_row_spinbutton_changed()348 void GridArrangeTab::on_row_spinbutton_changed()
349 {
350     SPDesktop *desktop = Parent->getDesktop();
351     Inkscape::Selection *selection = desktop ? desktop->selection : nullptr;
352     if (!selection) return;
353 
354     int selcount = (int) boost::distance(selection->items());
355 
356     double NoOfRows = ceil(selcount / NoOfColsSpinner.get_value());
357     NoOfRowsSpinner.set_value(NoOfRows);
358 }
359 
360 /**
361  * changed value in # of rows spinbox.
362  */
on_col_spinbutton_changed()363 void GridArrangeTab::on_col_spinbutton_changed()
364 {
365     SPDesktop *desktop = Parent->getDesktop();
366     Inkscape::Selection *selection = desktop ? desktop->selection : nullptr;
367     if (!selection) return;
368 
369     int selcount = (int) boost::distance(selection->items());
370 
371     double NoOfCols = ceil(selcount / NoOfRowsSpinner.get_value());
372     NoOfColsSpinner.set_value(NoOfCols);
373 }
374 
375 /**
376  * changed value in x padding spinbox.
377  */
on_xpad_spinbutton_changed()378 void GridArrangeTab::on_xpad_spinbutton_changed()
379 {
380     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
381     prefs->setDouble("/dialogs/gridtiler/XPad", XPadding.getValue("px"));
382 
383 }
384 
385 /**
386  * changed value in y padding spinbox.
387  */
on_ypad_spinbutton_changed()388 void GridArrangeTab::on_ypad_spinbutton_changed()
389 {
390     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
391     prefs->setDouble("/dialogs/gridtiler/YPad", YPadding.getValue("px"));
392 }
393 
394 
395 /**
396  * checked/unchecked autosize Rows button.
397  */
on_RowSize_checkbutton_changed()398 void GridArrangeTab::on_RowSize_checkbutton_changed()
399 {
400     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
401     if (RowHeightButton.get_active()) {
402         prefs->setDouble("/dialogs/gridtiler/AutoRowSize", 20);
403     } else {
404         prefs->setDouble("/dialogs/gridtiler/AutoRowSize", -20);
405     }
406     RowHeightBox.set_sensitive ( !RowHeightButton.get_active());
407 }
408 
409 /**
410  * checked/unchecked autosize Rows button.
411  */
on_ColSize_checkbutton_changed()412 void GridArrangeTab::on_ColSize_checkbutton_changed()
413 {
414     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
415     if (ColumnWidthButton.get_active()) {
416         prefs->setDouble("/dialogs/gridtiler/AutoColSize", 20);
417     } else {
418         prefs->setDouble("/dialogs/gridtiler/AutoColSize", -20);
419     }
420     ColumnWidthBox.set_sensitive ( !ColumnWidthButton.get_active());
421 }
422 
423 /**
424  * changed value in columns spinbox.
425  */
on_rowSize_spinbutton_changed()426 void GridArrangeTab::on_rowSize_spinbutton_changed()
427 {
428     // quit if run by the attr_changed listener
429     if (updating) {
430             return;
431         }
432 
433     // in turn, prevent listener from responding
434     updating = true;
435     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
436     prefs->setDouble("/dialogs/gridtiler/RowHeight", RowHeightSpinner.get_value());
437     updating=false;
438 
439 }
440 
441 /**
442  * changed value in rows spinbox.
443  */
on_colSize_spinbutton_changed()444 void GridArrangeTab::on_colSize_spinbutton_changed()
445 {
446     // quit if run by the attr_changed listener
447     if (updating) {
448             return;
449         }
450 
451     // in turn, prevent listener from responding
452     updating = true;
453     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
454     prefs->setDouble("/dialogs/gridtiler/ColWidth", ColumnWidthSpinner.get_value());
455     updating=false;
456 
457 }
458 
459 /**
460  * changed Radio button in Spacing group.
461  */
Spacing_button_changed()462 void GridArrangeTab::Spacing_button_changed()
463 {
464     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
465     if (SpaceManualRadioButton.get_active()) {
466         prefs->setDouble("/dialogs/gridtiler/SpacingType", 20);
467     } else {
468         prefs->setDouble("/dialogs/gridtiler/SpacingType", -20);
469     }
470 
471     XPadding.set_sensitive ( SpaceManualRadioButton.get_active());
472     YPadding.set_sensitive ( SpaceManualRadioButton.get_active());
473 }
474 
475 /**
476  * changed Anchor selection widget.
477  */
Align_changed()478 void GridArrangeTab::Align_changed()
479 {
480     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
481     VertAlign = AlignmentSelector.getVerticalAlignment();
482     prefs->setInt("/dialogs/gridtiler/VertAlign", VertAlign);
483     HorizAlign = AlignmentSelector.getHorizontalAlignment();
484     prefs->setInt("/dialogs/gridtiler/HorizAlign", HorizAlign);
485 }
486 
487 /**
488  * Desktop selection changed
489  */
updateSelection()490 void GridArrangeTab::updateSelection()
491 {
492     // quit if run by the attr_changed listener
493     if (updating) {
494         return;
495     }
496 
497     // in turn, prevent listener from responding
498     updating = true;
499     SPDesktop *desktop = Parent->getDesktop();
500     Inkscape::Selection *selection = desktop ? desktop->selection : nullptr;
501     std::vector<SPItem*> items;
502     if (selection) {
503         items.insert(items.end(), selection->items().begin(), selection->items().end());
504     }
505 
506     if (!items.empty()) {
507         int selcount = items.size();
508 
509         if (NoOfColsSpinner.get_value() > 1 && NoOfRowsSpinner.get_value() > 1){
510             // Update the number of rows assuming number of columns wanted remains same.
511             double NoOfRows = ceil(selcount / NoOfColsSpinner.get_value());
512             NoOfRowsSpinner.set_value(NoOfRows);
513 
514             // if the selection has less than the number set for one row, reduce it appropriately
515             if (selcount < NoOfColsSpinner.get_value()) {
516                 double NoOfCols = ceil(selcount / NoOfRowsSpinner.get_value());
517                 NoOfColsSpinner.set_value(NoOfCols);
518             }
519         } else {
520             double PerRow = ceil(sqrt(selcount));
521             double PerCol = ceil(sqrt(selcount));
522             NoOfRowsSpinner.set_value(PerRow);
523             NoOfColsSpinner.set_value(PerCol);
524         }
525     }
526 
527     updating = false;
528 }
529 
setDesktop(SPDesktop * desktop)530 void GridArrangeTab::setDesktop(SPDesktop *desktop)
531 {
532     _selection_changed_connection.disconnect();
533 
534     if (desktop) {
535         updateSelection();
536 
537         _selection_changed_connection = INKSCAPE.signal_selection_changed.connect(
538             sigc::hide<0>(sigc::mem_fun(*this, &GridArrangeTab::updateSelection)));
539     }
540 }
541 
542 
543 //#########################################################################
544 //## C O N S T R U C T O R    /    D E S T R U C T O R
545 //#########################################################################
546 /**
547  * Constructor
548  */
GridArrangeTab(ArrangeDialog * parent)549 GridArrangeTab::GridArrangeTab(ArrangeDialog *parent)
550     : Parent(parent),
551       XPadding(_("X:"), _("Horizontal spacing between columns."), UNIT_TYPE_LINEAR, "", "object-columns", &PaddingUnitMenu),
552       YPadding(_("Y:"), _("Vertical spacing between rows."), XPadding, "", "object-rows"),
553       PaddingTable(Gtk::manage(new Gtk::Grid()))
554 {
555      // bool used by spin button callbacks to stop loops where they change each other.
556     updating = false;
557     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
558 
559     auto _col1 = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
560     auto _col2 = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
561     auto _col3 = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
562 
563     Gtk::Box *contents = this;
564 
565 #define MARGIN 2
566 
567     //##Set up the panel
568 
569     NoOfRowsLabel.set_text_with_mnemonic(_("_Rows:"));
570     NoOfRowsLabel.set_mnemonic_widget(NoOfRowsSpinner);
571     NoOfRowsBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
572     NoOfRowsBox.pack_start(NoOfRowsLabel, false, false, MARGIN);
573 
574     NoOfRowsSpinner.set_digits(0);
575     NoOfRowsSpinner.set_increments(1, 0);
576     NoOfRowsSpinner.set_range(1.0, 10000.0);
577     NoOfRowsSpinner.signal_changed().connect(sigc::mem_fun(*this, &GridArrangeTab::on_col_spinbutton_changed));
578     NoOfRowsSpinner.set_tooltip_text(_("Number of rows"));
579     NoOfRowsBox.pack_start(NoOfRowsSpinner, false, false, MARGIN);
580     _col1->add_widget(NoOfRowsBox);
581 
582     RowHeightButton.set_label(_("Equal _height"));
583     RowHeightButton.set_use_underline(true);
584     double AutoRow = prefs->getDouble("/dialogs/gridtiler/AutoRowSize", 15);
585     if (AutoRow>0)
586          AutoRowSize=true;
587     else
588          AutoRowSize=false;
589     RowHeightButton.set_active(AutoRowSize);
590 
591     NoOfRowsBox.pack_start(RowHeightButton, false, false, MARGIN);
592 
593     RowHeightButton.set_tooltip_text(_("If not set, each row has the height of the tallest object in it"));
594     RowHeightButton.signal_toggled().connect(sigc::mem_fun(*this, &GridArrangeTab::on_RowSize_checkbutton_changed));
595 
596     SpinsHBox.pack_start(NoOfRowsBox, false, false, MARGIN);
597 
598 
599     /*#### Label for X ####*/
600     padXByYLabel.set_label(" ");
601     XByYLabelVBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
602     XByYLabelVBox.pack_start(padXByYLabel, false, false, MARGIN);
603     XByYLabel.set_markup(" &#215; ");
604     XByYLabelVBox.pack_start(XByYLabel, false, false, MARGIN);
605     SpinsHBox.pack_start(XByYLabelVBox, false, false, MARGIN);
606     _col2->add_widget(XByYLabelVBox);
607 
608     /*#### Number of columns ####*/
609 
610     NoOfColsLabel.set_text_with_mnemonic(_("_Columns:"));
611     NoOfColsLabel.set_mnemonic_widget(NoOfColsSpinner);
612     NoOfColsBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
613     NoOfColsBox.pack_start(NoOfColsLabel, false, false, MARGIN);
614 
615     NoOfColsSpinner.set_digits(0);
616     NoOfColsSpinner.set_increments(1, 0);
617     NoOfColsSpinner.set_range(1.0, 10000.0);
618     NoOfColsSpinner.signal_changed().connect(sigc::mem_fun(*this, &GridArrangeTab::on_row_spinbutton_changed));
619     NoOfColsSpinner.set_tooltip_text(_("Number of columns"));
620     NoOfColsBox.pack_start(NoOfColsSpinner, false, false, MARGIN);
621     _col3->add_widget(NoOfColsBox);
622 
623     ColumnWidthButton.set_label(_("Equal _width"));
624     ColumnWidthButton.set_use_underline(true);
625     double AutoCol = prefs->getDouble("/dialogs/gridtiler/AutoColSize", 15);
626     if (AutoCol>0)
627          AutoColSize=true;
628     else
629          AutoColSize=false;
630     ColumnWidthButton.set_active(AutoColSize);
631     NoOfColsBox.pack_start(ColumnWidthButton, false, false, MARGIN);
632 
633     ColumnWidthButton.set_tooltip_text(_("If not set, each column has the width of the widest object in it"));
634     ColumnWidthButton.signal_toggled().connect(sigc::mem_fun(*this, &GridArrangeTab::on_ColSize_checkbutton_changed));
635 
636     SpinsHBox.pack_start(NoOfColsBox, false, false, MARGIN);
637 
638     TileBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
639     TileBox.pack_start(SpinsHBox, false, false, MARGIN);
640 
641     VertAlign = prefs->getInt("/dialogs/gridtiler/VertAlign", 1);
642     HorizAlign = prefs->getInt("/dialogs/gridtiler/HorizAlign", 1);
643 
644     // Anchor selection widget
645     AlignLabel.set_label(_("Alignment:"));
646     AlignLabel.set_halign(Gtk::ALIGN_START);
647     AlignLabel.set_valign(Gtk::ALIGN_CENTER);
648     AlignmentSelector.setAlignment(HorizAlign, VertAlign);
649     AlignmentSelector.on_selectionChanged().connect(sigc::mem_fun(*this, &GridArrangeTab::Align_changed));
650     TileBox.pack_start(AlignLabel, false, false, MARGIN);
651     TileBox.pack_start(AlignmentSelector, true, false, MARGIN);
652 
653     {
654         /*#### Radio buttons to control spacing manually or to fit selection bbox ####*/
655         SpaceByBBoxRadioButton.set_label(_("_Fit into selection box"));
656         SpaceByBBoxRadioButton.set_use_underline (true);
657         SpaceByBBoxRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &GridArrangeTab::Spacing_button_changed));
658         SpacingGroup = SpaceByBBoxRadioButton.get_group();
659 
660         SpacingVBox.pack_start(SpaceByBBoxRadioButton, false, false, MARGIN);
661 
662         SpaceManualRadioButton.set_label(_("_Set spacing:"));
663         SpaceManualRadioButton.set_use_underline (true);
664         SpaceManualRadioButton.set_group(SpacingGroup);
665         SpaceManualRadioButton.signal_toggled().connect(sigc::mem_fun(*this, &GridArrangeTab::Spacing_button_changed));
666         SpacingVBox.pack_start(SpaceManualRadioButton, false, false, MARGIN);
667 
668         TileBox.pack_start(SpacingVBox, false, false, MARGIN);
669     }
670 
671     {
672         /*#### Padding ####*/
673         PaddingUnitMenu.setUnitType(UNIT_TYPE_LINEAR);
674         PaddingUnitMenu.setUnit("px");
675 
676         YPadding.setDigits(5);
677         YPadding.setIncrements(0.2, 0);
678         YPadding.setRange(-10000, 10000);
679         double yPad = prefs->getDouble("/dialogs/gridtiler/YPad", 15);
680         YPadding.setValue(yPad, "px");
681         YPadding.signal_value_changed().connect(sigc::mem_fun(*this, &GridArrangeTab::on_ypad_spinbutton_changed));
682 
683         XPadding.setDigits(5);
684         XPadding.setIncrements(0.2, 0);
685         XPadding.setRange(-10000, 10000);
686         double xPad = prefs->getDouble("/dialogs/gridtiler/XPad", 15);
687         XPadding.setValue(xPad, "px");
688 
689         XPadding.signal_value_changed().connect(sigc::mem_fun(*this, &GridArrangeTab::on_xpad_spinbutton_changed));
690     }
691 
692     PaddingTable->set_border_width(MARGIN);
693     PaddingTable->set_row_spacing(MARGIN);
694     PaddingTable->set_column_spacing(MARGIN);
695     PaddingTable->attach(XPadding,        0, 0, 1, 1);
696     PaddingTable->attach(PaddingUnitMenu, 1, 0, 1, 1);
697     PaddingTable->attach(YPadding,        0, 1, 1, 1);
698 
699     TileBox.pack_start(*PaddingTable, false, false, MARGIN);
700 
701     contents->set_border_width(4);
702     contents->pack_start(TileBox);
703 
704     double SpacingType = prefs->getDouble("/dialogs/gridtiler/SpacingType", 15);
705     if (SpacingType>0) {
706         ManualSpacing=true;
707     } else {
708         ManualSpacing=false;
709     }
710     SpaceManualRadioButton.set_active(ManualSpacing);
711     SpaceByBBoxRadioButton.set_active(!ManualSpacing);
712     XPadding.set_sensitive (ManualSpacing);
713     YPadding.set_sensitive (ManualSpacing);
714 
715     show_all_children();
716 }
717 
718 } //namespace Dialog
719 } //namespace UI
720 } //namespace Inkscape
721 
722 /*
723   Local Variables:
724   mode:c++
725   c-file-style:"stroustrup"
726   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
727   indent-tabs-mode:nil
728   fill-column:99
729   End:
730 */
731 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
732