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"))
110 {
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();
169 }
171 Transformation::~Transformation()
172 {
173 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
174 }
177 /*########################################################################
178 # U T I L I T Y
179 ########################################################################*/
181 void
182 Transformation::presentPage(Transformation::PageType page)
183 {
184 _notebook.set_current_page(page);
185 show();
186 present();
187 }
192 /*########################################################################
193 # S E T U P L A Y O U T
194 ########################################################################*/
197 void
198 Transformation::layoutPageMove()
199 {
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));
232 }
234 void
235 Transformation::layoutPageScale()
236 {
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?
274 }
276 void
277 Transformation::layoutPageRotate()
278 {
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?
295 }
297 void
298 Transformation::layoutPageSkew()
299 {
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?
326 }
330 void
331 Transformation::layoutPageTransform()
332 {
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));
404 }
407 /*########################################################################
408 # U P D A T E
409 ########################################################################*/
411 void
412 Transformation::updateSelection(PageType page, Inkscape::Selection *selection)
413 {
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());
445 }
447 void
448 Transformation::onSwitchPage(GtkNotebookPage */*page*/,
449 guint pagenum)
450 {
451 updateSelection((PageType)pagenum, sp_desktop_selection(getDesktop()));
452 }
455 void
456 Transformation::updatePageMove(Inkscape::Selection *selection)
457 {
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 }
475 }
477 void
478 Transformation::updatePageScale(Inkscape::Selection *selection)
479 {
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 }
495 }
497 void
498 Transformation::updatePageRotate(Inkscape::Selection *selection)
499 {
500 if (selection && !selection->isEmpty()) {
501 _page_rotate.set_sensitive(true);
502 } else {
503 _page_rotate.set_sensitive(false);
504 }
505 }
507 void
508 Transformation::updatePageSkew(Inkscape::Selection *selection)
509 {
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 }
524 }
526 void
527 Transformation::updatePageTransform(Inkscape::Selection *selection)
528 {
529 if (selection && !selection->isEmpty()) {
530 if (_check_replace_matrix.get_active()) {
531 Geom::Matrix current (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 }
548 }
554 /*########################################################################
555 # A P P L Y
556 ########################################################################*/
560 void
561 Transformation::_apply()
562 {
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);
594 }
596 void
597 Transformation::applyPageMove(Inkscape::Selection *selection)
598 {
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"));
681 }
683 void
684 Transformation::applyPageScale(Inkscape::Selection *selection)
685 {
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"));
737 }
739 void
740 Transformation::applyPageRotate(Inkscape::Selection *selection)
741 {
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"));
758 }
760 void
761 Transformation::applyPageSkew(Inkscape::Selection *selection)
762 {
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"));
816 }
819 void
820 Transformation::applyPageTransform(Inkscape::Selection *selection)
821 {
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"));
843 }
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()
855 {
856 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
857 }
859 void
860 Transformation::onMoveRelativeToggled()
861 {
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);
887 }
889 void
890 Transformation::onScaleXValueChanged()
891 {
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 }
907 }
909 void
910 Transformation::onScaleYValueChanged()
911 {
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 }
927 }
929 void
930 Transformation::onRotateValueChanged()
931 {
932 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
933 }
935 void
936 Transformation::onSkewValueChanged()
937 {
938 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
939 }
941 void
942 Transformation::onTransformValueChanged()
943 {
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);
958 }
960 void
961 Transformation::onReplaceMatrixToggled()
962 {
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 = 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]);
991 }
993 void
994 Transformation::onScaleProportionalToggled()
995 {
996 onScaleXValueChanged();
997 }
1000 void
1001 Transformation::onClear()
1002 {
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 }
1044 }
1046 void
1047 Transformation::onApplySeparatelyToggled()
1048 {
1049 prefs_set_int_attribute ("dialogs.transformation", "applyseparately", _check_apply_separately.get_active()? 1 : 0);
1050 }
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 :