Code

NR::Maybe => boost::optional
[inkscape.git] / src / ui / dialog / transformation.cpp
1 /**
2  * \brief Object Transformation dialog
3  *
4  * Authors:
5  *   Bryce W. Harrington <bryce@bryceharrington.org>
6  *   buliabyak@gmail.com
7  *
8  * Copyright (C) 2004, 2005 Authors
9  *
10  * Released under GNU GPL.  Read the file 'COPYING' for more information.
11  */
13 #ifdef HAVE_CONFIG_H
14 # include <config.h>
15 #endif
17 #include <gtkmm/stock.h>
18 #include <gtkmm/dialog.h>
20 #include "document.h"
21 #include "desktop-handles.h"
22 #include "transformation.h"
23 #include "align-and-distribute.h"
24 #include "libnr/nr-matrix-ops.h"
25 #include "inkscape.h"
26 #include "selection.h"
27 #include "selection-chemistry.h"
28 #include "verbs.h"
29 #include "prefs-utils.h"
30 #include "sp-item-transform.h"
31 #include "macros.h"
32 #include "sp-item.h"
33 #include "util/glib-list-iterators.h"
35 namespace Inkscape {
36 namespace UI {
37 namespace Dialog {
39 void on_selection_changed(Inkscape::Application */*inkscape*/, Inkscape::Selection *selection, Transformation *daad)
40 {
41     int page = daad->getCurrentPage();
42     daad->updateSelection((Inkscape::UI::Dialog::Transformation::PageType)page, selection);
43 }
45 void on_selection_modified( Inkscape::Application */*inkscape*/,
46                             Inkscape::Selection *selection,
47                             guint /*flags*/,
48                             Transformation *daad )
49 {
50     int page = daad->getCurrentPage();
51     daad->updateSelection((Inkscape::UI::Dialog::Transformation::PageType)page, selection);
52 }
54 /*########################################################################
55 # C O N S T R U C T O R
56 ########################################################################*/
58 /**
59  * Constructor for Transformation.  This does the initialization
60  * and layout of the dialog used for transforming SVG objects.  It
61  * consists of 5 pages for the 5 operations it handles:
62  * 'Move' allows x,y translation of SVG objects
63  * 'Scale' allows linear resizing of SVG objects
64  * 'Rotate' allows rotating SVG objects by a degree
65  * 'Skew' allows skewing SVG objects
66  * 'Matrix' allows applying a generic affine transform on SVG objects,
67  *     with the user specifying the 6 degrees of freedom manually.
68  *
69  * The dialog is implemented as a Gtk::Notebook with five pages.
70  * The pages are implemented using Inkscape's NotebookPage which
71  * is used to help make sure all of Inkscape's notebooks follow
72  * the same style.  We then populate the pages with our widgets,
73  * we use the ScalarUnit class for this.
74  *
75  */
76 Transformation::Transformation()
77     : UI::Widget::Panel ("", "dialogs.transformation", SP_VERB_DIALOG_TRANSFORM),
78       _page_move              (4, 2),
79       _page_scale             (4, 2),
80       _page_rotate            (4, 2),
81       _page_skew              (4, 2),
82       _page_transform         (3, 3),
83       _scalar_move_horizontal (_("_Horizontal"), _("Horizontal displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR,
84                                "", "arrows_hor", &_units_move),
85       _scalar_move_vertical   (_("_Vertical"),  _("Vertical displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR,
86                                "", "arrows_ver", &_units_move),
87       _scalar_scale_horizontal(_("_Width"), _("Horizontal size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
88                                "", "transform_scale_hor", &_units_scale),
89       _scalar_scale_vertical  (_("_Height"),  _("Vertical size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
90                                "", "transform_scale_ver", &_units_scale),
91       _scalar_rotate          (_("A_ngle"), _("Rotation angle (positive = counterclockwise)"), UNIT_TYPE_RADIAL,
92                                "", "transform_rotate", &_units_rotate),
93       _scalar_skew_horizontal (_("_Horizontal"), _("Horizontal skew angle (positive = counterclockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR,
94                                "", "transform_scew_hor", &_units_skew),
95       _scalar_skew_vertical   (_("_Vertical"),  _("Vertical skew angle (positive = counterclockwise), or absolute displacement, or percentage displacement"),  UNIT_TYPE_LINEAR,
96                                "", "transform_scew_ver", &_units_skew),
98       _scalar_transform_a     ("_A", _("Transformation matrix element A")),
99       _scalar_transform_b     ("_B", _("Transformation matrix element B")),
100       _scalar_transform_c     ("_C", _("Transformation matrix element C")),
101       _scalar_transform_d     ("_D", _("Transformation matrix element D")),
102       _scalar_transform_e     ("_E", _("Transformation matrix element E")),
103       _scalar_transform_f     ("_F", _("Transformation matrix element F")),
105       _check_move_relative    (_("Rela_tive move"), _("Add the specified relative displacement to the current position; otherwise, edit the current absolute position directly")),
106       _check_scale_proportional (_("Scale proportionally"), _("Preserve the width/height ratio of the scaled objects")),
107       _check_apply_separately    (_("Apply to each _object separately"), _("Apply the scale/rotate/skew to each selected object separately; otherwise, transform the selection as a whole")),
108       _check_replace_matrix    (_("Edit c_urrent matrix"), _("Edit the current transform= matrix; otherwise, post-multiply transform= by this matrix"))
111     Gtk::Box *contents = _getContents();
113     contents->set_spacing(0);
115     // Notebook for individual transformations
116     contents->pack_start(_notebook, true, true);
118     _notebook.append_page(_page_move, _("_Move"), true);
119     layoutPageMove();
121     _notebook.append_page(_page_scale, _("_Scale"), true);
122     layoutPageScale();
124     _notebook.append_page(_page_rotate, _("_Rotate"), true);
125     layoutPageRotate();
127     _notebook.append_page(_page_skew, _("Ske_w"), true);
128     layoutPageSkew();
130     _notebook.append_page(_page_transform, _("Matri_x"), true);
131     layoutPageTransform();
133     _notebook.signal_switch_page().connect(sigc::mem_fun(*this, &Transformation::onSwitchPage));
135     // Apply separately
136     contents->pack_start(_check_apply_separately, true, true);
137     _check_apply_separately.set_active(prefs_get_int_attribute_limited ("dialogs.transformation", "applyseparately", 0, 0, 1));
138     _check_apply_separately.signal_toggled().connect(sigc::mem_fun(*this, &Transformation::onApplySeparatelyToggled));
140     // make sure all spinbuttons activate Apply on pressing Enter
141       ((Gtk::Entry *) (_scalar_move_horizontal.getWidget()))->set_activates_default(true);
142       ((Gtk::Entry *) (_scalar_move_vertical.getWidget()))->set_activates_default(true);
143       ((Gtk::Entry *) (_scalar_scale_horizontal.getWidget()))->set_activates_default(true);
144       ((Gtk::Entry *) (_scalar_scale_vertical.getWidget()))->set_activates_default(true);
145       ((Gtk::Entry *) (_scalar_rotate.getWidget()))->set_activates_default(true);
146       ((Gtk::Entry *) (_scalar_skew_horizontal.getWidget()))->set_activates_default(true);
147       ((Gtk::Entry *) (_scalar_skew_vertical.getWidget()))->set_activates_default(true);
149     updateSelection(PAGE_MOVE, _getSelection());
151     resetButton = addResponseButton(Gtk::Stock::CLEAR, 0);
152     if (resetButton) {
153         _tooltips.set_tip((*resetButton), _("Reset the values on the current tab to defaults"));
154         resetButton->set_sensitive(true);
155         resetButton->signal_clicked().connect(sigc::mem_fun(*this, &Transformation::onClear));
156     }
158     applyButton = addResponseButton(Gtk::Stock::APPLY, Gtk::RESPONSE_APPLY);
159     if (applyButton) {
160         _tooltips.set_tip((*applyButton), _("Apply transformation to selection"));
161         applyButton->set_sensitive(false);
162     }
164     // Connect to the global selection changed & modified signals
165     g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
166     g_signal_connect (G_OBJECT (INKSCAPE), "modify_selection", G_CALLBACK (on_selection_modified), this);
168     show_all_children();
171 Transformation::~Transformation()
173     sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
177 /*########################################################################
178 # U T I L I T Y
179 ########################################################################*/
181 void
182 Transformation::presentPage(Transformation::PageType page)
184     _notebook.set_current_page(page);
185     show();
186     present();
192 /*########################################################################
193 # S E T U P   L A Y O U T
194 ########################################################################*/
197 void
198 Transformation::layoutPageMove()
200     _units_move.setUnitType(UNIT_TYPE_LINEAR);
201     _scalar_move_horizontal.initScalar(-1e6, 1e6);
202     _scalar_move_horizontal.setDigits(3);
203     _scalar_move_horizontal.setIncrements(0.1, 1.0);
205     _scalar_move_vertical.initScalar(-1e6, 1e6);
206     _scalar_move_vertical.setDigits(3);
207     _scalar_move_vertical.setIncrements(0.1, 1.0);
209     //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_HOR );
210     _page_move.table()
211         .attach(_scalar_move_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
213     _page_move.table()
214         .attach(_units_move, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
216     _scalar_move_horizontal.signal_value_changed()
217         .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged));
219     //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_VER );
220     _page_move.table()
221         .attach(_scalar_move_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
223     _scalar_move_vertical.signal_value_changed()
224         .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged));
226     // Relative moves
227     _page_move.table()
228         .attach(_check_move_relative, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
229     _check_move_relative.set_active(true);
230     _check_move_relative.signal_toggled()
231         .connect(sigc::mem_fun(*this, &Transformation::onMoveRelativeToggled));
234 void
235 Transformation::layoutPageScale()
237     _units_scale.setUnitType(UNIT_TYPE_DIMENSIONLESS);
238     _units_scale.setUnitType(UNIT_TYPE_LINEAR);
240     _scalar_scale_horizontal.initScalar(-1e6, 1e6);
241     _scalar_scale_horizontal.setValue(100.0, "%");
242     _scalar_scale_horizontal.setDigits(3);
243     _scalar_scale_horizontal.setIncrements(0.1, 1.0);
244     _scalar_scale_horizontal.setAbsoluteIsIncrement(true);
245     _scalar_scale_horizontal.setPercentageIsIncrement(true);
247     _scalar_scale_vertical.initScalar(-1e6, 1e6);
248     _scalar_scale_vertical.setValue(100.0, "%");
249     _scalar_scale_vertical.setDigits(3);
250     _scalar_scale_vertical.setIncrements(0.1, 1.0);
251     _scalar_scale_vertical.setAbsoluteIsIncrement(true);
252     _scalar_scale_vertical.setPercentageIsIncrement(true);
254     _page_scale.table()
255         .attach(_scalar_scale_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
256     _scalar_scale_horizontal.signal_value_changed()
257         .connect(sigc::mem_fun(*this, &Transformation::onScaleXValueChanged));
259     _page_scale.table()
260         .attach(_units_scale, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
262     _page_scale.table()
263         .attach(_scalar_scale_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
264     _scalar_scale_vertical.signal_value_changed()
265         .connect(sigc::mem_fun(*this, &Transformation::onScaleYValueChanged));
267     _page_scale.table()
268         .attach(_check_scale_proportional, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
269     _check_scale_proportional.set_active(false);
270     _check_scale_proportional.signal_toggled()
271         .connect(sigc::mem_fun(*this, &Transformation::onScaleProportionalToggled));
273     //TODO: add a widget for selecting the fixed point in scaling, or honour rotation center?
276 void
277 Transformation::layoutPageRotate()
279     _units_rotate.setUnitType(UNIT_TYPE_RADIAL);
281     _scalar_rotate.initScalar(-360.0, 360.0);
282     _scalar_rotate.setDigits(3);
283     _scalar_rotate.setIncrements(0.1, 1.0);
285     _page_rotate.table()
286         .attach(_scalar_rotate, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
288     _page_rotate.table()
289         .attach(_units_rotate, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
291     _scalar_rotate.signal_value_changed()
292         .connect(sigc::mem_fun(*this, &Transformation::onRotateValueChanged));
294     //TODO: honour rotation center?
297 void
298 Transformation::layoutPageSkew()
300     _units_skew.setUnitType(UNIT_TYPE_LINEAR);
301     _units_skew.setUnitType(UNIT_TYPE_DIMENSIONLESS);
302     _units_skew.setUnitType(UNIT_TYPE_RADIAL);
304     _scalar_skew_horizontal.initScalar(-1e6, 1e6);
305     _scalar_skew_horizontal.setDigits(3);
306     _scalar_skew_horizontal.setIncrements(0.1, 1.0);
308     _scalar_skew_vertical.initScalar(-1e6, 1e6);
309     _scalar_skew_vertical.setDigits(3);
310     _scalar_skew_vertical.setIncrements(0.1, 1.0);
312     _page_skew.table()
313         .attach(_scalar_skew_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
314     _scalar_skew_horizontal.signal_value_changed()
315         .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged));
317     _page_skew.table()
318         .attach(_units_skew, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
320     _page_skew.table()
321         .attach(_scalar_skew_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
322     _scalar_skew_vertical.signal_value_changed()
323         .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged));
325     //TODO: honour rotation center?
330 void
331 Transformation::layoutPageTransform()
333     _scalar_transform_a.setWidgetSizeRequest(65, -1);
334     _scalar_transform_a.setRange(-1e10, 1e10);
335     _scalar_transform_a.setDigits(3);
336     _scalar_transform_a.setIncrements(0.1, 1.0);
337     _scalar_transform_a.setValue(1.0);
338     _page_transform.table()
339         .attach(_scalar_transform_a, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
340     _scalar_transform_a.signal_value_changed()
341         .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
344     _scalar_transform_b.setWidgetSizeRequest(65, -1);
345     _scalar_transform_b.setRange(-1e10, 1e10);
346     _scalar_transform_b.setDigits(3);
347     _scalar_transform_b.setIncrements(0.1, 1.0);
348     _scalar_transform_b.setValue(0.0);
349     _page_transform.table()
350         .attach(_scalar_transform_b, 0, 1, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
351     _scalar_transform_b.signal_value_changed()
352         .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
355     _scalar_transform_c.setWidgetSizeRequest(65, -1);
356     _scalar_transform_c.setRange(-1e10, 1e10);
357     _scalar_transform_c.setDigits(3);
358     _scalar_transform_c.setIncrements(0.1, 1.0);
359     _scalar_transform_c.setValue(0.0);
360     _page_transform.table()
361         .attach(_scalar_transform_c, 1, 2, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
362     _scalar_transform_c.signal_value_changed()
363         .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
366     _scalar_transform_d.setWidgetSizeRequest(65, -1);
367     _scalar_transform_d.setRange(-1e10, 1e10);
368     _scalar_transform_d.setDigits(3);
369     _scalar_transform_d.setIncrements(0.1, 1.0);
370     _scalar_transform_d.setValue(1.0);
371     _page_transform.table()
372         .attach(_scalar_transform_d, 1, 2, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
373     _scalar_transform_d.signal_value_changed()
374         .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
377     _scalar_transform_e.setWidgetSizeRequest(65, -1);
378     _scalar_transform_e.setRange(-1e10, 1e10);
379     _scalar_transform_e.setDigits(3);
380     _scalar_transform_e.setIncrements(0.1, 1.0);
381     _scalar_transform_e.setValue(0.0);
382     _page_transform.table()
383         .attach(_scalar_transform_e, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
384     _scalar_transform_e.signal_value_changed()
385         .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
388     _scalar_transform_f.setWidgetSizeRequest(65, -1);
389     _scalar_transform_f.setRange(-1e10, 1e10);
390     _scalar_transform_f.setDigits(3);
391     _scalar_transform_f.setIncrements(0.1, 1.0);
392     _scalar_transform_f.setValue(0.0);
393     _page_transform.table()
394         .attach(_scalar_transform_f, 2, 3, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
395     _scalar_transform_f.signal_value_changed()
396         .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
398     // Edit existing matrix
399     _page_transform.table()
400         .attach(_check_replace_matrix, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
401     _check_replace_matrix.set_active(false);
402     _check_replace_matrix.signal_toggled()
403         .connect(sigc::mem_fun(*this, &Transformation::onReplaceMatrixToggled));
407 /*########################################################################
408 # U P D A T E
409 ########################################################################*/
411 void
412 Transformation::updateSelection(PageType page, Inkscape::Selection *selection)
414     if (!selection || selection->isEmpty())
415         return;
417     switch (page) {
418         case PAGE_MOVE: {
419             updatePageMove(selection);
420             break;
421         }
422         case PAGE_SCALE: {
423             updatePageScale(selection);
424             break;
425         }
426         case PAGE_ROTATE: {
427             updatePageRotate(selection);
428             break;
429         }
430         case PAGE_SKEW: {
431             updatePageSkew(selection);
432             break;
433         }
434         case PAGE_TRANSFORM: {
435             updatePageTransform(selection);
436             break;
437         }
438         case PAGE_QTY: {
439             break;
440         }
441     }
443     setResponseSensitive(Gtk::RESPONSE_APPLY,
444                          selection && !selection->isEmpty());
447 void
448 Transformation::onSwitchPage(GtkNotebookPage */*page*/,
449                                    guint pagenum)
451     updateSelection((PageType)pagenum, sp_desktop_selection(getDesktop()));
455 void
456 Transformation::updatePageMove(Inkscape::Selection *selection)
458     if (selection && !selection->isEmpty()) {
459         if (!_check_move_relative.get_active()) {
460             boost::optional<NR::Rect> bbox = selection->bounds();
461             if (bbox) {
462                 double x = bbox->min()[Geom::X];
463                 double y = bbox->min()[Geom::Y];
465                 _scalar_move_horizontal.setValue(x, "px");
466                 _scalar_move_vertical.setValue(y, "px");
467             }
468         } else {
469             // do nothing, so you can apply the same relative move to many objects in turn
470         }
471         _page_move.set_sensitive(true);
472     } else {
473         _page_move.set_sensitive(false);
474     }
477 void
478 Transformation::updatePageScale(Inkscape::Selection *selection)
480     if (selection && !selection->isEmpty()) {
481         boost::optional<NR::Rect> bbox = selection->bounds();
482         if (bbox) {
483             double w = bbox->extent(Geom::X);
484             double h = bbox->extent(Geom::Y);
485             _scalar_scale_horizontal.setHundredPercent(w);
486             _scalar_scale_vertical.setHundredPercent(h);
487             onScaleXValueChanged(); // to update x/y proportionality if switch is on
488             _page_scale.set_sensitive(true);
489         } else {
490             _page_scale.set_sensitive(false);
491         }
492     } else {
493         _page_scale.set_sensitive(false);
494     }
497 void
498 Transformation::updatePageRotate(Inkscape::Selection *selection)
500     if (selection && !selection->isEmpty()) {
501         _page_rotate.set_sensitive(true);
502     } else {
503         _page_rotate.set_sensitive(false);
504     }
507 void
508 Transformation::updatePageSkew(Inkscape::Selection *selection)
510     if (selection && !selection->isEmpty()) {
511         boost::optional<NR::Rect> bbox = selection->bounds();
512         if (bbox) {
513             double w = bbox->extent(Geom::X);
514             double h = bbox->extent(Geom::Y);
515             _scalar_skew_vertical.setHundredPercent(w);
516             _scalar_skew_horizontal.setHundredPercent(h);
517             _page_skew.set_sensitive(true);
518         } else {
519             _page_skew.set_sensitive(false);
520         }
521     } else {
522         _page_skew.set_sensitive(false);
523     }
526 void
527 Transformation::updatePageTransform(Inkscape::Selection *selection)
529     if (selection && !selection->isEmpty()) {
530         if (_check_replace_matrix.get_active()) {
531             Geom::Matrix current (to_2geom(SP_ITEM(selection->itemList()->data)->transform)); // take from the first item in selection
533             Geom::Matrix new_displayed = current;
535             _scalar_transform_a.setValue(new_displayed[0]);
536             _scalar_transform_b.setValue(new_displayed[1]);
537             _scalar_transform_c.setValue(new_displayed[2]);
538             _scalar_transform_d.setValue(new_displayed[3]);
539             _scalar_transform_e.setValue(new_displayed[4]);
540             _scalar_transform_f.setValue(new_displayed[5]);
541         } else {
542             // do nothing, so you can apply the same matrix to many objects in turn
543         }
544         _page_transform.set_sensitive(true);
545     } else {
546         _page_transform.set_sensitive(false);
547     }
554 /*########################################################################
555 # A P P L Y
556 ########################################################################*/
560 void
561 Transformation::_apply()
563     Inkscape::Selection * const selection = _getSelection();
564     if (!selection || selection->isEmpty())
565         return;
567     int const page = _notebook.get_current_page();
569     switch (page) {
570         case PAGE_MOVE: {
571             applyPageMove(selection);
572             break;
573         }
574         case PAGE_ROTATE: {
575             applyPageRotate(selection);
576             break;
577         }
578         case PAGE_SCALE: {
579             applyPageScale(selection);
580             break;
581         }
582         case PAGE_SKEW: {
583             applyPageSkew(selection);
584             break;
585         }
586         case PAGE_TRANSFORM: {
587             applyPageTransform(selection);
588             break;
589         }
590     }
592     //Let's play with never turning this off
593     //setResponseSensitive(Gtk::RESPONSE_APPLY, false);
596 void
597 Transformation::applyPageMove(Inkscape::Selection *selection)
599     double x = _scalar_move_horizontal.getValue("px");
600     double y = _scalar_move_vertical.getValue("px");
602     if (prefs_get_int_attribute_limited ("dialogs.transformation", "applyseparately", 0, 0, 1) == 0) {
603         // move selection as a whole
604         if (_check_move_relative.get_active()) {
605             sp_selection_move_relative(selection, x, y);
606         } else {
607             boost::optional<NR::Rect> bbox = selection->bounds();
608             if (bbox) {
609                 sp_selection_move_relative(selection,
610                                            x - bbox->min()[Geom::X], y - bbox->min()[Geom::Y]);
611             }
612         }
613     } else {
615         if (_check_move_relative.get_active()) {
616             // shift each object relatively to the previous one
617             using Inkscape::Util::GSListConstIterator;
618             std::list<SPItem *> selected;
619             selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
620             if (selected.empty()) return;
622             if (fabs(x) > 1e-6) {
623                 std::vector< BBoxSort  > sorted;
624                 for (std::list<SPItem *>::iterator it(selected.begin());
625                      it != selected.end();
626                      ++it)
627                 {
628                     boost::optional<NR::Rect> bbox = sp_item_bbox_desktop(*it);
629                     if (bbox) {
630                         sorted.push_back(BBoxSort(*it, to_2geom(*bbox), Geom::X, x > 0? 1. : 0., x > 0? 0. : 1.));
631                     }
632                 }
633                 //sort bbox by anchors
634                 std::sort(sorted.begin(), sorted.end());
636                 double move = x;
637                 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
638                       it < sorted.end();
639                       it ++ )
640                 {
641                     sp_item_move_rel(it->item, NR::translate(move, 0));
642                     // move each next object by x relative to previous
643                     move += x;
644                 }
645             }
646             if (fabs(y) > 1e-6) {
647                 std::vector< BBoxSort  > sorted;
648                 for (std::list<SPItem *>::iterator it(selected.begin());
649                      it != selected.end();
650                      ++it)
651                 {
652                     boost::optional<NR::Rect> bbox = sp_item_bbox_desktop(*it);
653                     if (bbox) {
654                         sorted.push_back(BBoxSort(*it, to_2geom(*bbox), Geom::Y, y > 0? 1. : 0., y > 0? 0. : 1.));
655                     }
656                 }
657                 //sort bbox by anchors
658                 std::sort(sorted.begin(), sorted.end());
660                 double move = y;
661                 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
662                       it < sorted.end();
663                       it ++ )
664                 {
665                     sp_item_move_rel(it->item, NR::translate(0, move));
666                     // move each next object by x relative to previous
667                     move += y;
668                 }
669             }
670         } else {
671             boost::optional<NR::Rect> bbox = selection->bounds();
672             if (bbox) {
673                 sp_selection_move_relative(selection,
674                                            x - bbox->min()[Geom::X], y - bbox->min()[Geom::Y]);
675             }
676         }
677     }
679     sp_document_done ( sp_desktop_document (selection->desktop()) , SP_VERB_DIALOG_TRANSFORM,
680                        _("Move"));
683 void
684 Transformation::applyPageScale(Inkscape::Selection *selection)
686     double scaleX = _scalar_scale_horizontal.getValue("px");
687     double scaleY = _scalar_scale_vertical.getValue("px");
689     if (prefs_get_int_attribute_limited ("dialogs.transformation", "applyseparately", 0, 0, 1) == 1) {
690         for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
691             SPItem *item = SP_ITEM(l->data);
692             NR::scale scale (0,0);
693             // the values are increments!
694             if (_units_scale.isAbsolute()) {
695                 boost::optional<NR::Rect> bbox(sp_item_bbox_desktop(item));
696                 if (bbox) {
697                     double new_width = scaleX;
698                     if (fabs(new_width) < 1e-6) new_width = 1e-6; // not 0, as this would result in a nasty no-bbox object
699                     double new_height = scaleY;
700                     if (fabs(new_height) < 1e-6) new_height = 1e-6;
701                     scale = NR::scale(new_width / bbox->extent(Geom::X), new_height / bbox->extent(Geom::Y));
702                 }
703             } else {
704                 double new_width = scaleX;
705                 if (fabs(new_width) < 1e-6) new_width = 1e-6;
706                 double new_height = scaleY;
707                 if (fabs(new_height) < 1e-6) new_height = 1e-6;
708                 scale = NR::scale(new_width / 100.0, new_height / 100.0);
709             }
710             sp_item_scale_rel (item, scale);
711         }
712     } else {
713         boost::optional<NR::Rect> bbox(selection->bounds());
714         if (bbox) {
715             Geom::Point center(bbox->midpoint()); // use rotation center?
716             NR::scale scale (0,0);
717             // the values are increments!
718             if (_units_scale.isAbsolute()) {
719                 double new_width = scaleX;
720                 if (fabs(new_width) < 1e-6) new_width = 1e-6;
721                 double new_height = scaleY;
722                 if (fabs(new_height) < 1e-6) new_height = 1e-6;
723                 scale = NR::scale(new_width / bbox->extent(Geom::X), new_height / bbox->extent(Geom::Y));
724             } else {
725                 double new_width = scaleX;
726                 if (fabs(new_width) < 1e-6) new_width = 1e-6;
727                 double new_height = scaleY;
728                 if (fabs(new_height) < 1e-6) new_height = 1e-6;
729                 scale = NR::scale(new_width / 100.0, new_height / 100.0);
730             }
731             sp_selection_scale_relative(selection, center, scale);
732         }
733     }
735     sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
736                      _("Scale"));
739 void
740 Transformation::applyPageRotate(Inkscape::Selection *selection)
742     double angle = _scalar_rotate.getValue("deg");
744     if (prefs_get_int_attribute_limited ("dialogs.transformation", "applyseparately", 0, 0, 1) == 1) {
745         for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
746             SPItem *item = SP_ITEM(l->data);
747             sp_item_rotate_rel(item, NR::rotate (angle*M_PI/180.0));
748         }
749     } else {
750         boost::optional<NR::Point> center = selection->center();
751         if (center) {
752             sp_selection_rotate_relative(selection, *center, angle);
753         }
754     }
756     sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
757                      _("Rotate"));
760 void
761 Transformation::applyPageSkew(Inkscape::Selection *selection)
763     if (prefs_get_int_attribute_limited ("dialogs.transformation", "applyseparately", 0, 0, 1) == 1) {
764         for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
765             SPItem *item = SP_ITEM(l->data);
767             if (!_units_skew.isAbsolute()) { // percentage
768                 double skewX = _scalar_skew_horizontal.getValue("%");
769                 double skewY = _scalar_skew_vertical.getValue("%");
770                 sp_item_skew_rel (item, 0.01*skewX, 0.01*skewY);
771             } else if (_units_skew.isRadial()) { //deg or rad
772                 double angleX = _scalar_skew_horizontal.getValue("rad");
773                 double angleY = _scalar_skew_vertical.getValue("rad");
774                 double skewX = tan(-angleX);
775                 double skewY = tan(angleY);
776                 sp_item_skew_rel (item, skewX, skewY);
777             } else { // absolute displacement
778                 double skewX = _scalar_skew_horizontal.getValue("px");
779                 double skewY = _scalar_skew_vertical.getValue("px");
780                 boost::optional<NR::Rect> bbox(sp_item_bbox_desktop(item));
781                 if (bbox) {
782                     double width = bbox->extent(Geom::X);
783                     double height = bbox->extent(Geom::Y);
784                     sp_item_skew_rel (item, skewX/height, skewY/width);
785                 }
786             }
787         }
788     } else { // transform whole selection
789         boost::optional<NR::Rect> bbox = selection->bounds();
790         boost::optional<NR::Point> center = selection->center();
792         if ( bbox && center ) {
793             double width  = bbox->extent(Geom::X);
794             double height = bbox->extent(Geom::Y);
796             if (!_units_skew.isAbsolute()) { // percentage
797                 double skewX = _scalar_skew_horizontal.getValue("%");
798                 double skewY = _scalar_skew_vertical.getValue("%");
799                 sp_selection_skew_relative(selection, *center, 0.01*skewX, 0.01*skewY);
800             } else if (_units_skew.isRadial()) { //deg or rad
801                 double angleX = _scalar_skew_horizontal.getValue("rad");
802                 double angleY = _scalar_skew_vertical.getValue("rad");
803                 double skewX = tan(-angleX);
804                 double skewY = tan(angleY);
805                 sp_selection_skew_relative(selection, *center, skewX, skewY);
806             } else { // absolute displacement
807                 double skewX = _scalar_skew_horizontal.getValue("px");
808                 double skewY = _scalar_skew_vertical.getValue("px");
809                 sp_selection_skew_relative(selection, *center, skewX/height, skewY/width);
810             }
811         }
812     }
814     sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
815                      _("Skew"));
819 void
820 Transformation::applyPageTransform(Inkscape::Selection *selection)
822     double a = _scalar_transform_a.getValue();
823     double b = _scalar_transform_b.getValue();
824     double c = _scalar_transform_c.getValue();
825     double d = _scalar_transform_d.getValue();
826     double e = _scalar_transform_e.getValue();
827     double f = _scalar_transform_f.getValue();
829     NR::Matrix displayed(a, b, c, d, e, f);
831     if (_check_replace_matrix.get_active()) {
832         for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
833             SPItem *item = SP_ITEM(l->data);
834             sp_item_set_item_transform(item, displayed);
835             SP_OBJECT(item)->updateRepr();
836         }
837     } else {
838         sp_selection_apply_affine(selection, displayed); // post-multiply each object's transform
839     }
841     sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
842                      _("Edit transformation matrix"));
849 /*########################################################################
850 # V A L U E - C H A N G E D    C A L L B A C K S
851 ########################################################################*/
853 void
854 Transformation::onMoveValueChanged()
856     setResponseSensitive(Gtk::RESPONSE_APPLY, true);
859 void
860 Transformation::onMoveRelativeToggled()
862     Inkscape::Selection *selection = _getSelection();
864     if (!selection || selection->isEmpty())
865         return;
867     double x = _scalar_move_horizontal.getValue("px");
868     double y = _scalar_move_vertical.getValue("px");
870     //g_message("onMoveRelativeToggled: %f, %f px\n", x, y);
872     boost::optional<NR::Rect> bbox = selection->bounds();
874     if (bbox) {
875         if (_check_move_relative.get_active()) {
876             // From absolute to relative
877             _scalar_move_horizontal.setValue(x - bbox->min()[Geom::X], "px");
878             _scalar_move_vertical.setValue(  y - bbox->min()[Geom::Y], "px");
879         } else {
880             // From relative to absolute
881             _scalar_move_horizontal.setValue(bbox->min()[Geom::X] + x, "px");
882             _scalar_move_vertical.setValue(  bbox->min()[Geom::Y] + y, "px");
883         }
884     }
886     setResponseSensitive(Gtk::RESPONSE_APPLY, true);
889 void
890 Transformation::onScaleXValueChanged()
892     if (_scalar_scale_horizontal.setProgrammatically) {
893         _scalar_scale_horizontal.setProgrammatically = false;
894         return;
895     }
897     setResponseSensitive(Gtk::RESPONSE_APPLY, true);
899     if (_check_scale_proportional.get_active()) {
900         if (!_units_scale.isAbsolute()) { // percentage, just copy over
901             _scalar_scale_vertical.setValue(_scalar_scale_horizontal.getValue("%"));
902         } else {
903             double scaleXPercentage = _scalar_scale_horizontal.getAsPercentage();
904             _scalar_scale_vertical.setFromPercentage (scaleXPercentage);
905         }
906     }
909 void
910 Transformation::onScaleYValueChanged()
912     if (_scalar_scale_vertical.setProgrammatically) {
913         _scalar_scale_vertical.setProgrammatically = false;
914         return;
915     }
917     setResponseSensitive(Gtk::RESPONSE_APPLY, true);
919     if (_check_scale_proportional.get_active()) {
920         if (!_units_scale.isAbsolute()) { // percentage, just copy over
921             _scalar_scale_horizontal.setValue(_scalar_scale_vertical.getValue("%"));
922         } else {
923             double scaleYPercentage = _scalar_scale_vertical.getAsPercentage();
924             _scalar_scale_horizontal.setFromPercentage (scaleYPercentage);
925         }
926     }
929 void
930 Transformation::onRotateValueChanged()
932     setResponseSensitive(Gtk::RESPONSE_APPLY, true);
935 void
936 Transformation::onSkewValueChanged()
938     setResponseSensitive(Gtk::RESPONSE_APPLY, true);
941 void
942 Transformation::onTransformValueChanged()
945     /*
946     double a = _scalar_transform_a.getValue();
947     double b = _scalar_transform_b.getValue();
948     double c = _scalar_transform_c.getValue();
949     double d = _scalar_transform_d.getValue();
950     double e = _scalar_transform_e.getValue();
951     double f = _scalar_transform_f.getValue();
953     //g_message("onTransformValueChanged: (%f, %f, %f, %f, %f, %f)\n",
954     //          a, b, c, d, e ,f);
955     */
957     setResponseSensitive(Gtk::RESPONSE_APPLY, true);
960 void
961 Transformation::onReplaceMatrixToggled()
963     Inkscape::Selection *selection = _getSelection();
965     if (!selection || selection->isEmpty())
966         return;
968     double a = _scalar_transform_a.getValue();
969     double b = _scalar_transform_b.getValue();
970     double c = _scalar_transform_c.getValue();
971     double d = _scalar_transform_d.getValue();
972     double e = _scalar_transform_e.getValue();
973     double f = _scalar_transform_f.getValue();
975     Geom::Matrix displayed (a, b, c, d, e, f);
976     Geom::Matrix current = to_2geom(SP_ITEM(selection->itemList()->data)->transform); // take from the first item in selection
978     Geom::Matrix new_displayed;
979     if (_check_replace_matrix.get_active()) {
980         new_displayed = current;
981     } else {
982         new_displayed = current.inverse() * displayed;
983     }
985     _scalar_transform_a.setValue(new_displayed[0]);
986     _scalar_transform_b.setValue(new_displayed[1]);
987     _scalar_transform_c.setValue(new_displayed[2]);
988     _scalar_transform_d.setValue(new_displayed[3]);
989     _scalar_transform_e.setValue(new_displayed[4]);
990     _scalar_transform_f.setValue(new_displayed[5]);
993 void
994 Transformation::onScaleProportionalToggled()
996     onScaleXValueChanged();
1000 void
1001 Transformation::onClear()
1003     int const page = _notebook.get_current_page();
1005     switch (page) {
1006     case PAGE_MOVE: {
1007         Inkscape::Selection *selection = _getSelection();
1008         if (!selection || selection->isEmpty() || _check_move_relative.get_active()) {
1009             _scalar_move_horizontal.setValue(0);
1010             _scalar_move_vertical.setValue(0);
1011         } else {
1012             boost::optional<NR::Rect> bbox = selection->bounds();
1013             if (bbox) {
1014                 _scalar_move_horizontal.setValue(bbox->min()[Geom::X], "px");
1015                 _scalar_move_vertical.setValue(bbox->min()[Geom::Y], "px");
1016             }
1017         }
1018         break;
1019     }
1020     case PAGE_ROTATE: {
1021         _scalar_rotate.setValue(0);
1022         break;
1023     }
1024     case PAGE_SCALE: {
1025         _scalar_scale_horizontal.setValue(100, "%");
1026         _scalar_scale_vertical.setValue(100, "%");
1027         break;
1028     }
1029     case PAGE_SKEW: {
1030         _scalar_skew_horizontal.setValue(0);
1031         _scalar_skew_vertical.setValue(0);
1032         break;
1033     }
1034     case PAGE_TRANSFORM: {
1035         _scalar_transform_a.setValue(1);
1036         _scalar_transform_b.setValue(0);
1037         _scalar_transform_c.setValue(0);
1038         _scalar_transform_d.setValue(1);
1039         _scalar_transform_e.setValue(0);
1040         _scalar_transform_f.setValue(0);
1041         break;
1042     }
1043     }
1046 void
1047 Transformation::onApplySeparatelyToggled()
1049     prefs_set_int_attribute ("dialogs.transformation", "applyseparately", _check_apply_separately.get_active()? 1 : 0);
1053 } // namespace Dialog
1054 } // namespace UI
1055 } // namespace Inkscape
1059 /*
1060   Local Variables:
1061   mode:c++
1062   c-file-style:"stroustrup"
1063   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1064   indent-tabs-mode:nil
1065   fill-column:99
1066   End:
1067 */
1068 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :