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 "libnr/nr-matrix-ops.h"
24 #include "inkscape.h"
25 #include "selection.h"
26 #include "selection-chemistry.h"
27 #include "verbs.h"
28 #include "prefs-utils.h"
29 #include "sp-item-transform.h"
30 #include "macros.h"
31 #include "sp-item.h"
33 namespace Inkscape {
34 namespace UI {
35 namespace Dialog {
37 void on_selection_changed(Inkscape::Application */*inkscape*/, Inkscape::Selection *selection, Transformation *daad)
38 {
39 int page = daad->getCurrentPage();
40 daad->updateSelection((Inkscape::UI::Dialog::Transformation::PageType)page, selection);
41 }
43 void on_selection_modified( Inkscape::Application */*inkscape*/,
44 Inkscape::Selection *selection,
45 guint /*flags*/,
46 Transformation *daad )
47 {
48 int page = daad->getCurrentPage();
49 daad->updateSelection((Inkscape::UI::Dialog::Transformation::PageType)page, selection);
50 }
52 /*########################################################################
53 # C O N S T R U C T O R
54 ########################################################################*/
56 /**
57 * Constructor for Transformation. This does the initialization
58 * and layout of the dialog used for transforming SVG objects. It
59 * consists of 5 pages for the 5 operations it handles:
60 * 'Move' allows x,y translation of SVG objects
61 * 'Scale' allows linear resizing of SVG objects
62 * 'Rotate' allows rotating SVG objects by a degree
63 * 'Skew' allows skewing SVG objects
64 * 'Matrix' allows applying a generic affine transform on SVG objects,
65 * with the user specifying the 6 degrees of freedom manually.
66 *
67 * The dialog is implemented as a Gtk::Notebook with five pages.
68 * The pages are implemented using Inkscape's NotebookPage which
69 * is used to help make sure all of Inkscape's notebooks follow
70 * the same style. We then populate the pages with our widgets,
71 * we use the ScalarUnit class for this.
72 *
73 */
74 Transformation::Transformation()
75 : UI::Widget::Panel ("", "dialogs.transformation", SP_VERB_DIALOG_TRANSFORM),
76 _page_move (4, 2),
77 _page_scale (4, 2),
78 _page_rotate (4, 2),
79 _page_skew (4, 2),
80 _page_transform (3, 3),
81 _scalar_move_horizontal (_("_Horizontal"), _("Horizontal displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR,
82 "", "arrows_hor", &_units_move),
83 _scalar_move_vertical (_("_Vertical"), _("Vertical displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR,
84 "", "arrows_ver", &_units_move),
85 _scalar_scale_horizontal(_("_Width"), _("Horizontal size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
86 "", "transform_scale_hor", &_units_scale),
87 _scalar_scale_vertical (_("_Height"), _("Vertical size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
88 "", "transform_scale_ver", &_units_scale),
89 _scalar_rotate (_("A_ngle"), _("Rotation angle (positive = counterclockwise)"), UNIT_TYPE_RADIAL,
90 "", "transform_rotate", &_units_rotate),
91 _scalar_skew_horizontal (_("_Horizontal"), _("Horizontal skew angle (positive = counterclockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR,
92 "", "transform_scew_hor", &_units_skew),
93 _scalar_skew_vertical (_("_Vertical"), _("Vertical skew angle (positive = counterclockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR,
94 "", "transform_scew_ver", &_units_skew),
96 _scalar_transform_a ("_A", _("Transformation matrix element A")),
97 _scalar_transform_b ("_B", _("Transformation matrix element B")),
98 _scalar_transform_c ("_C", _("Transformation matrix element C")),
99 _scalar_transform_d ("_D", _("Transformation matrix element D")),
100 _scalar_transform_e ("_E", _("Transformation matrix element E")),
101 _scalar_transform_f ("_F", _("Transformation matrix element F")),
103 _check_move_relative (_("Rela_tive move"), _("Add the specified relative displacement to the current position; otherwise, edit the current absolute position directly")),
104 _check_scale_proportional (_("Scale proportionally"), _("Preserve the width/height ratio of the scaled objects")),
105 _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")),
106 _check_replace_matrix (_("Edit c_urrent matrix"), _("Edit the current transform= matrix; otherwise, post-multiply transform= by this matrix"))
108 {
109 Gtk::Box *contents = _getContents();
111 contents->set_spacing(0);
113 // Notebook for individual transformations
114 contents->pack_start(_notebook, true, true);
116 _notebook.append_page(_page_move, _("_Move"), true);
117 layoutPageMove();
119 _notebook.append_page(_page_scale, _("_Scale"), true);
120 layoutPageScale();
122 _notebook.append_page(_page_rotate, _("_Rotate"), true);
123 layoutPageRotate();
125 _notebook.append_page(_page_skew, _("Ske_w"), true);
126 layoutPageSkew();
128 _notebook.append_page(_page_transform, _("Matri_x"), true);
129 layoutPageTransform();
131 _notebook.signal_switch_page().connect(sigc::mem_fun(*this, &Transformation::onSwitchPage));
133 // Apply separately
134 contents->pack_start(_check_apply_separately, true, true);
135 _check_apply_separately.set_active(prefs_get_int_attribute_limited ("dialogs.transformation", "applyseparately", 0, 0, 1));
136 _check_apply_separately.signal_toggled().connect(sigc::mem_fun(*this, &Transformation::onApplySeparatelyToggled));
138 // make sure all spinbuttons activate Apply on pressing Enter
139 ((Gtk::Entry *) (_scalar_move_horizontal.getWidget()))->set_activates_default(true);
140 ((Gtk::Entry *) (_scalar_move_vertical.getWidget()))->set_activates_default(true);
141 ((Gtk::Entry *) (_scalar_scale_horizontal.getWidget()))->set_activates_default(true);
142 ((Gtk::Entry *) (_scalar_scale_vertical.getWidget()))->set_activates_default(true);
143 ((Gtk::Entry *) (_scalar_rotate.getWidget()))->set_activates_default(true);
144 ((Gtk::Entry *) (_scalar_skew_horizontal.getWidget()))->set_activates_default(true);
145 ((Gtk::Entry *) (_scalar_skew_vertical.getWidget()))->set_activates_default(true);
147 updateSelection(PAGE_MOVE, _getSelection());
149 resetButton = addResponseButton(Gtk::Stock::CLEAR, 0);
150 if (resetButton) {
151 _tooltips.set_tip((*resetButton), _("Reset the values on the current tab to defaults"));
152 resetButton->set_sensitive(true);
153 resetButton->signal_clicked().connect(sigc::mem_fun(*this, &Transformation::onClear));
154 }
156 applyButton = addResponseButton(Gtk::Stock::APPLY, Gtk::RESPONSE_APPLY);
157 if (applyButton) {
158 _tooltips.set_tip((*applyButton), _("Apply transformation to selection"));
159 applyButton->set_sensitive(false);
160 }
162 // Connect to the global selection changed & modified signals
163 g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
164 g_signal_connect (G_OBJECT (INKSCAPE), "modify_selection", G_CALLBACK (on_selection_modified), this);
166 show_all_children();
167 }
169 Transformation::~Transformation()
170 {
171 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
172 }
175 /*########################################################################
176 # U T I L I T Y
177 ########################################################################*/
179 void
180 Transformation::presentPage(Transformation::PageType page)
181 {
182 _notebook.set_current_page(page);
183 show();
184 present();
185 }
190 /*########################################################################
191 # S E T U P L A Y O U T
192 ########################################################################*/
195 void
196 Transformation::layoutPageMove()
197 {
198 _units_move.setUnitType(UNIT_TYPE_LINEAR);
199 _scalar_move_horizontal.initScalar(-1e6, 1e6);
200 _scalar_move_horizontal.setDigits(3);
201 _scalar_move_horizontal.setIncrements(0.1, 1.0);
203 _scalar_move_vertical.initScalar(-1e6, 1e6);
204 _scalar_move_vertical.setDigits(3);
205 _scalar_move_vertical.setIncrements(0.1, 1.0);
207 //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_HOR );
208 _page_move.table()
209 .attach(_scalar_move_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
211 _page_move.table()
212 .attach(_units_move, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
214 _scalar_move_horizontal.signal_value_changed()
215 .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged));
217 //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_VER );
218 _page_move.table()
219 .attach(_scalar_move_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
221 _scalar_move_vertical.signal_value_changed()
222 .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged));
224 // Relative moves
225 _page_move.table()
226 .attach(_check_move_relative, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
227 _check_move_relative.set_active(true);
228 _check_move_relative.signal_toggled()
229 .connect(sigc::mem_fun(*this, &Transformation::onMoveRelativeToggled));
230 }
232 void
233 Transformation::layoutPageScale()
234 {
235 _units_scale.setUnitType(UNIT_TYPE_DIMENSIONLESS);
236 _units_scale.setUnitType(UNIT_TYPE_LINEAR);
238 _scalar_scale_horizontal.initScalar(-1e6, 1e6);
239 _scalar_scale_horizontal.setValue(100.0, "%");
240 _scalar_scale_horizontal.setDigits(3);
241 _scalar_scale_horizontal.setIncrements(0.1, 1.0);
242 _scalar_scale_horizontal.setAbsoluteIsIncrement(true);
243 _scalar_scale_horizontal.setPercentageIsIncrement(true);
245 _scalar_scale_vertical.initScalar(-1e6, 1e6);
246 _scalar_scale_vertical.setValue(100.0, "%");
247 _scalar_scale_vertical.setDigits(3);
248 _scalar_scale_vertical.setIncrements(0.1, 1.0);
249 _scalar_scale_vertical.setAbsoluteIsIncrement(true);
250 _scalar_scale_vertical.setPercentageIsIncrement(true);
252 _page_scale.table()
253 .attach(_scalar_scale_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
254 _scalar_scale_horizontal.signal_value_changed()
255 .connect(sigc::mem_fun(*this, &Transformation::onScaleXValueChanged));
257 _page_scale.table()
258 .attach(_units_scale, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
260 _page_scale.table()
261 .attach(_scalar_scale_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
262 _scalar_scale_vertical.signal_value_changed()
263 .connect(sigc::mem_fun(*this, &Transformation::onScaleYValueChanged));
265 _page_scale.table()
266 .attach(_check_scale_proportional, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
267 _check_scale_proportional.set_active(false);
268 _check_scale_proportional.signal_toggled()
269 .connect(sigc::mem_fun(*this, &Transformation::onScaleProportionalToggled));
271 //TODO: add a widget for selecting the fixed point in scaling, or honour rotation center?
272 }
274 void
275 Transformation::layoutPageRotate()
276 {
277 _units_rotate.setUnitType(UNIT_TYPE_RADIAL);
279 _scalar_rotate.initScalar(-360.0, 360.0);
280 _scalar_rotate.setDigits(3);
281 _scalar_rotate.setIncrements(0.1, 1.0);
283 _page_rotate.table()
284 .attach(_scalar_rotate, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
286 _page_rotate.table()
287 .attach(_units_rotate, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
289 _scalar_rotate.signal_value_changed()
290 .connect(sigc::mem_fun(*this, &Transformation::onRotateValueChanged));
292 //TODO: honour rotation center?
293 }
295 void
296 Transformation::layoutPageSkew()
297 {
298 _units_skew.setUnitType(UNIT_TYPE_LINEAR);
299 _units_skew.setUnitType(UNIT_TYPE_DIMENSIONLESS);
300 _units_skew.setUnitType(UNIT_TYPE_RADIAL);
302 _scalar_skew_horizontal.initScalar(-1e6, 1e6);
303 _scalar_skew_horizontal.setDigits(3);
304 _scalar_skew_horizontal.setIncrements(0.1, 1.0);
306 _scalar_skew_vertical.initScalar(-1e6, 1e6);
307 _scalar_skew_vertical.setDigits(3);
308 _scalar_skew_vertical.setIncrements(0.1, 1.0);
310 _page_skew.table()
311 .attach(_scalar_skew_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
312 _scalar_skew_horizontal.signal_value_changed()
313 .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged));
315 _page_skew.table()
316 .attach(_units_skew, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
318 _page_skew.table()
319 .attach(_scalar_skew_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
320 _scalar_skew_vertical.signal_value_changed()
321 .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged));
323 //TODO: honour rotation center?
324 }
328 void
329 Transformation::layoutPageTransform()
330 {
331 _scalar_transform_a.setWidgetSizeRequest(65, -1);
332 _scalar_transform_a.setRange(-1e10, 1e10);
333 _scalar_transform_a.setDigits(3);
334 _scalar_transform_a.setIncrements(0.1, 1.0);
335 _scalar_transform_a.setValue(1.0);
336 _page_transform.table()
337 .attach(_scalar_transform_a, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
338 _scalar_transform_a.signal_value_changed()
339 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
342 _scalar_transform_b.setWidgetSizeRequest(65, -1);
343 _scalar_transform_b.setRange(-1e10, 1e10);
344 _scalar_transform_b.setDigits(3);
345 _scalar_transform_b.setIncrements(0.1, 1.0);
346 _scalar_transform_b.setValue(0.0);
347 _page_transform.table()
348 .attach(_scalar_transform_b, 0, 1, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
349 _scalar_transform_b.signal_value_changed()
350 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
353 _scalar_transform_c.setWidgetSizeRequest(65, -1);
354 _scalar_transform_c.setRange(-1e10, 1e10);
355 _scalar_transform_c.setDigits(3);
356 _scalar_transform_c.setIncrements(0.1, 1.0);
357 _scalar_transform_c.setValue(0.0);
358 _page_transform.table()
359 .attach(_scalar_transform_c, 1, 2, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
360 _scalar_transform_c.signal_value_changed()
361 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
364 _scalar_transform_d.setWidgetSizeRequest(65, -1);
365 _scalar_transform_d.setRange(-1e10, 1e10);
366 _scalar_transform_d.setDigits(3);
367 _scalar_transform_d.setIncrements(0.1, 1.0);
368 _scalar_transform_d.setValue(1.0);
369 _page_transform.table()
370 .attach(_scalar_transform_d, 1, 2, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
371 _scalar_transform_d.signal_value_changed()
372 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
375 _scalar_transform_e.setWidgetSizeRequest(65, -1);
376 _scalar_transform_e.setRange(-1e10, 1e10);
377 _scalar_transform_e.setDigits(3);
378 _scalar_transform_e.setIncrements(0.1, 1.0);
379 _scalar_transform_e.setValue(0.0);
380 _page_transform.table()
381 .attach(_scalar_transform_e, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
382 _scalar_transform_e.signal_value_changed()
383 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
386 _scalar_transform_f.setWidgetSizeRequest(65, -1);
387 _scalar_transform_f.setRange(-1e10, 1e10);
388 _scalar_transform_f.setDigits(3);
389 _scalar_transform_f.setIncrements(0.1, 1.0);
390 _scalar_transform_f.setValue(0.0);
391 _page_transform.table()
392 .attach(_scalar_transform_f, 2, 3, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
393 _scalar_transform_f.signal_value_changed()
394 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
396 // Edit existing matrix
397 _page_transform.table()
398 .attach(_check_replace_matrix, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
399 _check_replace_matrix.set_active(false);
400 _check_replace_matrix.signal_toggled()
401 .connect(sigc::mem_fun(*this, &Transformation::onReplaceMatrixToggled));
402 }
405 /*########################################################################
406 # U P D A T E
407 ########################################################################*/
409 void
410 Transformation::updateSelection(PageType page, Inkscape::Selection *selection)
411 {
412 if (!selection || selection->isEmpty())
413 return;
415 switch (page) {
416 case PAGE_MOVE: {
417 updatePageMove(selection);
418 break;
419 }
420 case PAGE_SCALE: {
421 updatePageScale(selection);
422 break;
423 }
424 case PAGE_ROTATE: {
425 updatePageRotate(selection);
426 break;
427 }
428 case PAGE_SKEW: {
429 updatePageSkew(selection);
430 break;
431 }
432 case PAGE_TRANSFORM: {
433 updatePageTransform(selection);
434 break;
435 }
436 case PAGE_QTY: {
437 break;
438 }
439 }
441 setResponseSensitive(Gtk::RESPONSE_APPLY,
442 selection && !selection->isEmpty());
443 }
445 void
446 Transformation::onSwitchPage(GtkNotebookPage */*page*/,
447 guint pagenum)
448 {
449 updateSelection((PageType)pagenum, sp_desktop_selection(getDesktop()));
450 }
452 void
453 Transformation::updatePageMove(Inkscape::Selection *selection)
454 {
455 if (selection && !selection->isEmpty()) {
456 if (!_check_move_relative.get_active()) {
457 NR::Maybe<NR::Rect> bbox = selection->bounds();
458 if (bbox) {
459 double x = bbox->min()[NR::X];
460 double y = bbox->min()[NR::Y];
462 _scalar_move_horizontal.setValue(x, "px");
463 _scalar_move_vertical.setValue(y, "px");
464 }
465 } else {
466 // do nothing, so you can apply the same relative move to many objects in turn
467 }
468 _page_move.set_sensitive(true);
469 } else {
470 _page_move.set_sensitive(false);
471 }
472 }
474 void
475 Transformation::updatePageScale(Inkscape::Selection *selection)
476 {
477 if (selection && !selection->isEmpty()) {
478 NR::Maybe<NR::Rect> bbox = selection->bounds();
479 if (bbox) {
480 double w = bbox->extent(NR::X);
481 double h = bbox->extent(NR::Y);
482 _scalar_scale_horizontal.setHundredPercent(w);
483 _scalar_scale_vertical.setHundredPercent(h);
484 onScaleXValueChanged(); // to update x/y proportionality if switch is on
485 _page_scale.set_sensitive(true);
486 } else {
487 _page_scale.set_sensitive(false);
488 }
489 } else {
490 _page_scale.set_sensitive(false);
491 }
492 }
494 void
495 Transformation::updatePageRotate(Inkscape::Selection *selection)
496 {
497 if (selection && !selection->isEmpty()) {
498 _page_rotate.set_sensitive(true);
499 } else {
500 _page_rotate.set_sensitive(false);
501 }
502 }
504 void
505 Transformation::updatePageSkew(Inkscape::Selection *selection)
506 {
507 if (selection && !selection->isEmpty()) {
508 NR::Maybe<NR::Rect> bbox = selection->bounds();
509 if (bbox) {
510 double w = bbox->extent(NR::X);
511 double h = bbox->extent(NR::Y);
512 _scalar_skew_vertical.setHundredPercent(w);
513 _scalar_skew_horizontal.setHundredPercent(h);
514 _page_skew.set_sensitive(true);
515 } else {
516 _page_skew.set_sensitive(false);
517 }
518 } else {
519 _page_skew.set_sensitive(false);
520 }
521 }
523 void
524 Transformation::updatePageTransform(Inkscape::Selection *selection)
525 {
526 if (selection && !selection->isEmpty()) {
527 if (_check_replace_matrix.get_active()) {
528 NR::Matrix current (SP_ITEM(selection->itemList()->data)->transform); // take from the first item in selection
530 NR::Matrix new_displayed = current;
532 _scalar_transform_a.setValue(new_displayed[0]);
533 _scalar_transform_b.setValue(new_displayed[1]);
534 _scalar_transform_c.setValue(new_displayed[2]);
535 _scalar_transform_d.setValue(new_displayed[3]);
536 _scalar_transform_e.setValue(new_displayed[4]);
537 _scalar_transform_f.setValue(new_displayed[5]);
538 } else {
539 // do nothing, so you can apply the same matrix to many objects in turn
540 }
541 _page_transform.set_sensitive(true);
542 } else {
543 _page_transform.set_sensitive(false);
544 }
545 }
551 /*########################################################################
552 # A P P L Y
553 ########################################################################*/
557 void
558 Transformation::_apply()
559 {
560 Inkscape::Selection * const selection = _getSelection();
561 if (!selection || selection->isEmpty())
562 return;
564 int const page = _notebook.get_current_page();
566 switch (page) {
567 case PAGE_MOVE: {
568 applyPageMove(selection);
569 break;
570 }
571 case PAGE_ROTATE: {
572 applyPageRotate(selection);
573 break;
574 }
575 case PAGE_SCALE: {
576 applyPageScale(selection);
577 break;
578 }
579 case PAGE_SKEW: {
580 applyPageSkew(selection);
581 break;
582 }
583 case PAGE_TRANSFORM: {
584 applyPageTransform(selection);
585 break;
586 }
587 }
589 //Let's play with never turning this off
590 //setResponseSensitive(Gtk::RESPONSE_APPLY, false);
591 }
593 void
594 Transformation::applyPageMove(Inkscape::Selection *selection)
595 {
596 double x = _scalar_move_horizontal.getValue("px");
597 double y = _scalar_move_vertical.getValue("px");
599 if (_check_move_relative.get_active()) {
600 sp_selection_move_relative(selection, x, y);
601 } else {
602 NR::Maybe<NR::Rect> bbox = selection->bounds();
603 if (bbox) {
604 sp_selection_move_relative(selection,
605 x - bbox->min()[NR::X], y - bbox->min()[NR::Y]);
606 }
607 }
609 sp_document_done ( sp_desktop_document (selection->desktop()) , SP_VERB_DIALOG_TRANSFORM,
610 _("Move"));
611 }
613 void
614 Transformation::applyPageScale(Inkscape::Selection *selection)
615 {
616 double scaleX = _scalar_scale_horizontal.getValue("px");
617 double scaleY = _scalar_scale_vertical.getValue("px");
619 if (prefs_get_int_attribute_limited ("dialogs.transformation", "applyseparately", 0, 0, 1) == 1) {
620 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
621 SPItem *item = SP_ITEM(l->data);
622 NR::scale scale (0,0);
623 // the values are increments!
624 if (_units_scale.isAbsolute()) {
625 NR::Maybe<NR::Rect> bbox(sp_item_bbox_desktop(item));
626 if (bbox) {
627 double new_width = scaleX;
628 if (fabs(new_width) < 1e-6) new_width = 1e-6; // not 0, as this would result in a nasty no-bbox object
629 double new_height = scaleY;
630 if (fabs(new_height) < 1e-6) new_height = 1e-6;
631 scale = NR::scale(new_width / bbox->extent(NR::X), new_height / bbox->extent(NR::Y));
632 }
633 } else {
634 double new_width = scaleX;
635 if (fabs(new_width) < 1e-6) new_width = 1e-6;
636 double new_height = scaleY;
637 if (fabs(new_height) < 1e-6) new_height = 1e-6;
638 scale = NR::scale(new_width / 100.0, new_height / 100.0);
639 }
640 sp_item_scale_rel (item, scale);
641 }
642 } else {
643 NR::Maybe<NR::Rect> bbox(selection->bounds());
644 if (bbox) {
645 NR::Point center(bbox->midpoint()); // use rotation center?
646 NR::scale scale (0,0);
647 // the values are increments!
648 if (_units_scale.isAbsolute()) {
649 double new_width = scaleX;
650 if (fabs(new_width) < 1e-6) new_width = 1e-6;
651 double new_height = scaleY;
652 if (fabs(new_height) < 1e-6) new_height = 1e-6;
653 scale = NR::scale(new_width / bbox->extent(NR::X), new_height / bbox->extent(NR::Y));
654 } else {
655 double new_width = scaleX;
656 if (fabs(new_width) < 1e-6) new_width = 1e-6;
657 double new_height = scaleY;
658 if (fabs(new_height) < 1e-6) new_height = 1e-6;
659 scale = NR::scale(new_width / 100.0, new_height / 100.0);
660 }
661 sp_selection_scale_relative(selection, center, scale);
662 }
663 }
665 sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
666 _("Scale"));
667 }
669 void
670 Transformation::applyPageRotate(Inkscape::Selection *selection)
671 {
672 double angle = _scalar_rotate.getValue("deg");
674 if (prefs_get_int_attribute_limited ("dialogs.transformation", "applyseparately", 0, 0, 1) == 1) {
675 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
676 SPItem *item = SP_ITEM(l->data);
677 sp_item_rotate_rel(item, NR::rotate (angle*M_PI/180.0));
678 }
679 } else {
680 NR::Maybe<NR::Point> center = selection->center();
681 if (center) {
682 sp_selection_rotate_relative(selection, *center, angle);
683 }
684 }
686 sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
687 _("Rotate"));
688 }
690 void
691 Transformation::applyPageSkew(Inkscape::Selection *selection)
692 {
693 if (prefs_get_int_attribute_limited ("dialogs.transformation", "applyseparately", 0, 0, 1) == 1) {
694 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
695 SPItem *item = SP_ITEM(l->data);
697 if (!_units_skew.isAbsolute()) { // percentage
698 double skewX = _scalar_skew_horizontal.getValue("%");
699 double skewY = _scalar_skew_vertical.getValue("%");
700 sp_item_skew_rel (item, 0.01*skewX, 0.01*skewY);
701 } else if (_units_skew.isRadial()) { //deg or rad
702 double angleX = _scalar_skew_horizontal.getValue("rad");
703 double angleY = _scalar_skew_vertical.getValue("rad");
704 double skewX = tan(-angleX);
705 double skewY = tan(angleY);
706 sp_item_skew_rel (item, skewX, skewY);
707 } else { // absolute displacement
708 double skewX = _scalar_skew_horizontal.getValue("px");
709 double skewY = _scalar_skew_vertical.getValue("px");
710 NR::Maybe<NR::Rect> bbox(sp_item_bbox_desktop(item));
711 if (bbox) {
712 double width = bbox->extent(NR::X);
713 double height = bbox->extent(NR::Y);
714 sp_item_skew_rel (item, skewX/height, skewY/width);
715 }
716 }
717 }
718 } else { // transform whole selection
719 NR::Maybe<NR::Rect> bbox = selection->bounds();
720 NR::Maybe<NR::Point> center = selection->center();
722 if ( bbox && center ) {
723 double width = bbox->extent(NR::X);
724 double height = bbox->extent(NR::Y);
726 if (!_units_skew.isAbsolute()) { // percentage
727 double skewX = _scalar_skew_horizontal.getValue("%");
728 double skewY = _scalar_skew_vertical.getValue("%");
729 sp_selection_skew_relative(selection, *center, 0.01*skewX, 0.01*skewY);
730 } else if (_units_skew.isRadial()) { //deg or rad
731 double angleX = _scalar_skew_horizontal.getValue("rad");
732 double angleY = _scalar_skew_vertical.getValue("rad");
733 double skewX = tan(-angleX);
734 double skewY = tan(angleY);
735 sp_selection_skew_relative(selection, *center, skewX, skewY);
736 } else { // absolute displacement
737 double skewX = _scalar_skew_horizontal.getValue("px");
738 double skewY = _scalar_skew_vertical.getValue("px");
739 sp_selection_skew_relative(selection, *center, skewX/height, skewY/width);
740 }
741 }
742 }
744 sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
745 _("Skew"));
746 }
749 void
750 Transformation::applyPageTransform(Inkscape::Selection *selection)
751 {
752 double a = _scalar_transform_a.getValue();
753 double b = _scalar_transform_b.getValue();
754 double c = _scalar_transform_c.getValue();
755 double d = _scalar_transform_d.getValue();
756 double e = _scalar_transform_e.getValue();
757 double f = _scalar_transform_f.getValue();
759 NR::Matrix displayed(a, b, c, d, e, f);
761 if (_check_replace_matrix.get_active()) {
762 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
763 SPItem *item = SP_ITEM(l->data);
764 sp_item_set_item_transform(item, displayed);
765 SP_OBJECT(item)->updateRepr();
766 }
767 } else {
768 sp_selection_apply_affine(selection, displayed); // post-multiply each object's transform
769 }
771 sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
772 _("Edit transformation matrix"));
773 }
779 /*########################################################################
780 # V A L U E - C H A N G E D C A L L B A C K S
781 ########################################################################*/
783 void
784 Transformation::onMoveValueChanged()
785 {
786 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
787 }
789 void
790 Transformation::onMoveRelativeToggled()
791 {
792 Inkscape::Selection *selection = _getSelection();
794 if (!selection || selection->isEmpty())
795 return;
797 double x = _scalar_move_horizontal.getValue("px");
798 double y = _scalar_move_vertical.getValue("px");
800 //g_message("onMoveRelativeToggled: %f, %f px\n", x, y);
802 NR::Maybe<NR::Rect> bbox = selection->bounds();
804 if (bbox) {
805 if (_check_move_relative.get_active()) {
806 // From absolute to relative
807 _scalar_move_horizontal.setValue(x - bbox->min()[NR::X], "px");
808 _scalar_move_vertical.setValue( y - bbox->min()[NR::Y], "px");
809 } else {
810 // From relative to absolute
811 _scalar_move_horizontal.setValue(bbox->min()[NR::X] + x, "px");
812 _scalar_move_vertical.setValue( bbox->min()[NR::Y] + y, "px");
813 }
814 }
816 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
817 }
819 void
820 Transformation::onScaleXValueChanged()
821 {
822 if (_scalar_scale_horizontal.setProgrammatically) {
823 _scalar_scale_horizontal.setProgrammatically = false;
824 return;
825 }
827 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
829 if (_check_scale_proportional.get_active()) {
830 if (!_units_scale.isAbsolute()) { // percentage, just copy over
831 _scalar_scale_vertical.setValue(_scalar_scale_horizontal.getValue("%"));
832 } else {
833 double scaleXPercentage = _scalar_scale_horizontal.getAsPercentage();
834 _scalar_scale_vertical.setFromPercentage (scaleXPercentage);
835 }
836 }
837 }
839 void
840 Transformation::onScaleYValueChanged()
841 {
842 if (_scalar_scale_vertical.setProgrammatically) {
843 _scalar_scale_vertical.setProgrammatically = false;
844 return;
845 }
847 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
849 if (_check_scale_proportional.get_active()) {
850 if (!_units_scale.isAbsolute()) { // percentage, just copy over
851 _scalar_scale_horizontal.setValue(_scalar_scale_vertical.getValue("%"));
852 } else {
853 double scaleYPercentage = _scalar_scale_vertical.getAsPercentage();
854 _scalar_scale_horizontal.setFromPercentage (scaleYPercentage);
855 }
856 }
857 }
859 void
860 Transformation::onRotateValueChanged()
861 {
862 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
863 }
865 void
866 Transformation::onSkewValueChanged()
867 {
868 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
869 }
871 void
872 Transformation::onTransformValueChanged()
873 {
875 /*
876 double a = _scalar_transform_a.getValue();
877 double b = _scalar_transform_b.getValue();
878 double c = _scalar_transform_c.getValue();
879 double d = _scalar_transform_d.getValue();
880 double e = _scalar_transform_e.getValue();
881 double f = _scalar_transform_f.getValue();
883 //g_message("onTransformValueChanged: (%f, %f, %f, %f, %f, %f)\n",
884 // a, b, c, d, e ,f);
885 */
887 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
888 }
890 void
891 Transformation::onReplaceMatrixToggled()
892 {
893 Inkscape::Selection *selection = _getSelection();
895 if (!selection || selection->isEmpty())
896 return;
898 double a = _scalar_transform_a.getValue();
899 double b = _scalar_transform_b.getValue();
900 double c = _scalar_transform_c.getValue();
901 double d = _scalar_transform_d.getValue();
902 double e = _scalar_transform_e.getValue();
903 double f = _scalar_transform_f.getValue();
905 NR::Matrix displayed (a, b, c, d, e, f);
906 NR::Matrix current (SP_ITEM(selection->itemList()->data)->transform); // take from the first item in selection
908 NR::Matrix new_displayed;
909 if (_check_replace_matrix.get_active()) {
910 new_displayed = current;
911 } else {
912 new_displayed = current.inverse() * displayed;
913 }
915 _scalar_transform_a.setValue(new_displayed[0]);
916 _scalar_transform_b.setValue(new_displayed[1]);
917 _scalar_transform_c.setValue(new_displayed[2]);
918 _scalar_transform_d.setValue(new_displayed[3]);
919 _scalar_transform_e.setValue(new_displayed[4]);
920 _scalar_transform_f.setValue(new_displayed[5]);
921 }
923 void
924 Transformation::onScaleProportionalToggled()
925 {
926 onScaleXValueChanged();
927 }
930 void
931 Transformation::onClear()
932 {
933 int const page = _notebook.get_current_page();
935 switch (page) {
936 case PAGE_MOVE: {
937 Inkscape::Selection *selection = _getSelection();
938 if (!selection || selection->isEmpty() || _check_move_relative.get_active()) {
939 _scalar_move_horizontal.setValue(0);
940 _scalar_move_vertical.setValue(0);
941 } else {
942 NR::Maybe<NR::Rect> bbox = selection->bounds();
943 if (bbox) {
944 _scalar_move_horizontal.setValue(bbox->min()[NR::X], "px");
945 _scalar_move_vertical.setValue(bbox->min()[NR::Y], "px");
946 }
947 }
948 break;
949 }
950 case PAGE_ROTATE: {
951 _scalar_rotate.setValue(0);
952 break;
953 }
954 case PAGE_SCALE: {
955 _scalar_scale_horizontal.setValue(100, "%");
956 _scalar_scale_vertical.setValue(100, "%");
957 break;
958 }
959 case PAGE_SKEW: {
960 _scalar_skew_horizontal.setValue(0);
961 _scalar_skew_vertical.setValue(0);
962 break;
963 }
964 case PAGE_TRANSFORM: {
965 _scalar_transform_a.setValue(1);
966 _scalar_transform_b.setValue(0);
967 _scalar_transform_c.setValue(0);
968 _scalar_transform_d.setValue(1);
969 _scalar_transform_e.setValue(0);
970 _scalar_transform_f.setValue(0);
971 break;
972 }
973 }
974 }
976 void
977 Transformation::onApplySeparatelyToggled()
978 {
979 prefs_set_int_attribute ("dialogs.transformation", "applyseparately", _check_apply_separately.get_active()? 1 : 0);
980 }
983 } // namespace Dialog
984 } // namespace UI
985 } // namespace Inkscape
989 /*
990 Local Variables:
991 mode:c++
992 c-file-style:"stroustrup"
993 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
994 indent-tabs-mode:nil
995 fill-column:99
996 End:
997 */
998 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :