1 /** @file
2 * \brief Transform dialog - implementation
3 */
4 /* Authors:
5 * Bryce W. Harrington <bryce@bryceharrington.org>
6 * buliabyak@gmail.com
7 * Abhishek Sharma
8 *
9 * Copyright (C) 2004, 2005 Authors
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 "preferences.h"
30 #include "sp-namedview.h"
31 #include "sp-item-transform.h"
32 #include "macros.h"
33 #include "sp-item.h"
34 #include "util/glib-list-iterators.h"
36 namespace Inkscape {
37 namespace UI {
38 namespace Dialog {
40 void on_selection_changed(Inkscape::Application */*inkscape*/, Inkscape::Selection *selection, Transformation *daad)
41 {
42 int page = daad->getCurrentPage();
43 daad->updateSelection((Inkscape::UI::Dialog::Transformation::PageType)page, selection);
44 }
46 void on_selection_modified( Inkscape::Application */*inkscape*/,
47 Inkscape::Selection *selection,
48 guint /*flags*/,
49 Transformation *daad )
50 {
51 int page = daad->getCurrentPage();
52 daad->updateSelection((Inkscape::UI::Dialog::Transformation::PageType)page, selection);
53 }
55 /*########################################################################
56 # C O N S T R U C T O R
57 ########################################################################*/
59 /**
60 * Constructor for Transformation. This does the initialization
61 * and layout of the dialog used for transforming SVG objects. It
62 * consists of 5 pages for the 5 operations it handles:
63 * 'Move' allows x,y translation of SVG objects
64 * 'Scale' allows linear resizing of SVG objects
65 * 'Rotate' allows rotating SVG objects by a degree
66 * 'Skew' allows skewing SVG objects
67 * 'Matrix' allows applying a generic affine transform on SVG objects,
68 * with the user specifying the 6 degrees of freedom manually.
69 *
70 * The dialog is implemented as a Gtk::Notebook with five pages.
71 * The pages are implemented using Inkscape's NotebookPage which
72 * is used to help make sure all of Inkscape's notebooks follow
73 * the same style. We then populate the pages with our widgets,
74 * we use the ScalarUnit class for this.
75 *
76 */
77 Transformation::Transformation()
78 : UI::Widget::Panel ("", "/dialogs/transformation", SP_VERB_DIALOG_TRANSFORM),
79 _page_move (4, 2),
80 _page_scale (4, 2),
81 _page_rotate (4, 2),
82 _page_skew (4, 2),
83 _page_transform (3, 3),
84 _scalar_move_horizontal (_("_Horizontal:"), _("Horizontal displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR,
85 "", "transform-move-horizontal", &_units_move),
86 _scalar_move_vertical (_("_Vertical:"), _("Vertical displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR,
87 "", "transform-move-vertical", &_units_move),
88 _scalar_scale_horizontal(_("_Width:"), _("Horizontal size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
89 "", "transform-scale-horizontal", &_units_scale),
90 _scalar_scale_vertical (_("_Height:"), _("Vertical size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
91 "", "transform-scale-vertical", &_units_scale),
92 _scalar_rotate (_("A_ngle:"), _("Rotation angle (positive = counterclockwise)"), UNIT_TYPE_RADIAL,
93 "", "transform-rotate", &_units_rotate),
94 _scalar_skew_horizontal (_("_Horizontal:"), _("Horizontal skew angle (positive = counterclockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR,
95 "", "transform-skew-horizontal", &_units_skew),
96 _scalar_skew_vertical (_("_Vertical:"), _("Vertical skew angle (positive = counterclockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR,
97 "", "transform-skew-vertical", &_units_skew),
99 _scalar_transform_a ("_A:", _("Transformation matrix element A")),
100 _scalar_transform_b ("_B:", _("Transformation matrix element B")),
101 _scalar_transform_c ("_C:", _("Transformation matrix element C")),
102 _scalar_transform_d ("_D:", _("Transformation matrix element D")),
103 _scalar_transform_e ("_E:", _("Transformation matrix element E")),
104 _scalar_transform_f ("_F:", _("Transformation matrix element F")),
106 _check_move_relative (_("Rela_tive move"), _("Add the specified relative displacement to the current position; otherwise, edit the current absolute position directly")),
107 _check_scale_proportional (_("_Scale proportionally"), _("Preserve the width/height ratio of the scaled objects")),
108 _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")),
109 _check_replace_matrix (_("Edit c_urrent matrix"), _("Edit the current transform= matrix; otherwise, post-multiply transform= by this matrix"))
111 {
112 Gtk::Box *contents = _getContents();
114 contents->set_spacing(0);
116 // Notebook for individual transformations
117 contents->pack_start(_notebook, true, true);
119 _notebook.append_page(_page_move, _("_Move"), true);
120 layoutPageMove();
122 _notebook.append_page(_page_scale, _("_Scale"), true);
123 layoutPageScale();
125 _notebook.append_page(_page_rotate, _("_Rotate"), true);
126 layoutPageRotate();
128 _notebook.append_page(_page_skew, _("Ske_w"), true);
129 layoutPageSkew();
131 _notebook.append_page(_page_transform, _("Matri_x"), true);
132 layoutPageTransform();
134 _notebook.signal_switch_page().connect(sigc::mem_fun(*this, &Transformation::onSwitchPage));
136 // Apply separately
137 contents->pack_start(_check_apply_separately, true, true);
138 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
139 _check_apply_separately.set_active(prefs->getBool("/dialogs/transformation/applyseparately"));
140 _check_apply_separately.signal_toggled().connect(sigc::mem_fun(*this, &Transformation::onApplySeparatelyToggled));
142 // make sure all spinbuttons activate Apply on pressing Enter
143 ((Gtk::Entry *) (_scalar_move_horizontal.getWidget()))->set_activates_default(true);
144 ((Gtk::Entry *) (_scalar_move_vertical.getWidget()))->set_activates_default(true);
145 ((Gtk::Entry *) (_scalar_scale_horizontal.getWidget()))->set_activates_default(true);
146 ((Gtk::Entry *) (_scalar_scale_vertical.getWidget()))->set_activates_default(true);
147 ((Gtk::Entry *) (_scalar_rotate.getWidget()))->set_activates_default(true);
148 ((Gtk::Entry *) (_scalar_skew_horizontal.getWidget()))->set_activates_default(true);
149 ((Gtk::Entry *) (_scalar_skew_vertical.getWidget()))->set_activates_default(true);
151 updateSelection(PAGE_MOVE, _getSelection());
153 resetButton = addResponseButton(Gtk::Stock::CLEAR, 0);
154 if (resetButton) {
155 _tooltips.set_tip((*resetButton), _("Reset the values on the current tab to defaults"));
156 resetButton->set_sensitive(true);
157 resetButton->signal_clicked().connect(sigc::mem_fun(*this, &Transformation::onClear));
158 }
160 applyButton = addResponseButton(Gtk::Stock::APPLY, Gtk::RESPONSE_APPLY);
161 if (applyButton) {
162 _tooltips.set_tip((*applyButton), _("Apply transformation to selection"));
163 applyButton->set_sensitive(false);
164 }
166 // Connect to the global selection changed & modified signals
167 g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
168 g_signal_connect (G_OBJECT (INKSCAPE), "modify_selection", G_CALLBACK (on_selection_modified), this);
170 show_all_children();
171 }
173 Transformation::~Transformation()
174 {
175 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
176 }
179 /*########################################################################
180 # U T I L I T Y
181 ########################################################################*/
183 void
184 Transformation::presentPage(Transformation::PageType page)
185 {
186 _notebook.set_current_page(page);
187 show();
188 present();
189 }
194 /*########################################################################
195 # S E T U P L A Y O U T
196 ########################################################################*/
199 void
200 Transformation::layoutPageMove()
201 {
202 _units_move.setUnitType(UNIT_TYPE_LINEAR);
204 // Setting default unit to document unit
205 SPDesktop *dt = getDesktop();
206 SPNamedView *nv = sp_desktop_namedview(dt);
207 if (nv->doc_units) {
208 _units_move.setUnit(nv->doc_units->abbr);
209 }
211 _scalar_move_horizontal.initScalar(-1e6, 1e6);
212 _scalar_move_horizontal.setDigits(3);
213 _scalar_move_horizontal.setIncrements(0.1, 1.0);
215 _scalar_move_vertical.initScalar(-1e6, 1e6);
216 _scalar_move_vertical.setDigits(3);
217 _scalar_move_vertical.setIncrements(0.1, 1.0);
219 //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_HOR );
220 _page_move.table()
221 .attach(_scalar_move_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
223 _page_move.table()
224 .attach(_units_move, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
226 _scalar_move_horizontal.signal_value_changed()
227 .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged));
229 //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_VER );
230 _page_move.table()
231 .attach(_scalar_move_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
233 _scalar_move_vertical.signal_value_changed()
234 .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged));
236 // Relative moves
237 _page_move.table()
238 .attach(_check_move_relative, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
239 _check_move_relative.set_active(true);
240 _check_move_relative.signal_toggled()
241 .connect(sigc::mem_fun(*this, &Transformation::onMoveRelativeToggled));
242 }
244 void
245 Transformation::layoutPageScale()
246 {
247 _units_scale.setUnitType(UNIT_TYPE_DIMENSIONLESS);
248 _units_scale.setUnitType(UNIT_TYPE_LINEAR);
250 _scalar_scale_horizontal.initScalar(-1e6, 1e6);
251 _scalar_scale_horizontal.setValue(100.0, "%");
252 _scalar_scale_horizontal.setDigits(3);
253 _scalar_scale_horizontal.setIncrements(0.1, 1.0);
254 _scalar_scale_horizontal.setAbsoluteIsIncrement(true);
255 _scalar_scale_horizontal.setPercentageIsIncrement(true);
257 _scalar_scale_vertical.initScalar(-1e6, 1e6);
258 _scalar_scale_vertical.setValue(100.0, "%");
259 _scalar_scale_vertical.setDigits(3);
260 _scalar_scale_vertical.setIncrements(0.1, 1.0);
261 _scalar_scale_vertical.setAbsoluteIsIncrement(true);
262 _scalar_scale_vertical.setPercentageIsIncrement(true);
264 _page_scale.table()
265 .attach(_scalar_scale_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
266 _scalar_scale_horizontal.signal_value_changed()
267 .connect(sigc::mem_fun(*this, &Transformation::onScaleXValueChanged));
269 _page_scale.table()
270 .attach(_units_scale, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
272 _page_scale.table()
273 .attach(_scalar_scale_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
274 _scalar_scale_vertical.signal_value_changed()
275 .connect(sigc::mem_fun(*this, &Transformation::onScaleYValueChanged));
277 _page_scale.table()
278 .attach(_check_scale_proportional, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
279 _check_scale_proportional.set_active(false);
280 _check_scale_proportional.signal_toggled()
281 .connect(sigc::mem_fun(*this, &Transformation::onScaleProportionalToggled));
283 //TODO: add a widget for selecting the fixed point in scaling, or honour rotation center?
284 }
286 void
287 Transformation::layoutPageRotate()
288 {
289 _units_rotate.setUnitType(UNIT_TYPE_RADIAL);
291 _scalar_rotate.initScalar(-360.0, 360.0);
292 _scalar_rotate.setDigits(3);
293 _scalar_rotate.setIncrements(0.1, 1.0);
295 _page_rotate.table()
296 .attach(_scalar_rotate, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
298 _page_rotate.table()
299 .attach(_units_rotate, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
301 _scalar_rotate.signal_value_changed()
302 .connect(sigc::mem_fun(*this, &Transformation::onRotateValueChanged));
304 //TODO: honour rotation center?
305 }
307 void
308 Transformation::layoutPageSkew()
309 {
310 _units_skew.setUnitType(UNIT_TYPE_LINEAR);
311 _units_skew.setUnitType(UNIT_TYPE_DIMENSIONLESS);
312 _units_skew.setUnitType(UNIT_TYPE_RADIAL);
314 _scalar_skew_horizontal.initScalar(-1e6, 1e6);
315 _scalar_skew_horizontal.setDigits(3);
316 _scalar_skew_horizontal.setIncrements(0.1, 1.0);
318 _scalar_skew_vertical.initScalar(-1e6, 1e6);
319 _scalar_skew_vertical.setDigits(3);
320 _scalar_skew_vertical.setIncrements(0.1, 1.0);
322 _page_skew.table()
323 .attach(_scalar_skew_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
324 _scalar_skew_horizontal.signal_value_changed()
325 .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged));
327 _page_skew.table()
328 .attach(_units_skew, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
330 _page_skew.table()
331 .attach(_scalar_skew_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
332 _scalar_skew_vertical.signal_value_changed()
333 .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged));
335 //TODO: honour rotation center?
336 }
340 void
341 Transformation::layoutPageTransform()
342 {
343 _scalar_transform_a.setWidgetSizeRequest(65, -1);
344 _scalar_transform_a.setRange(-1e10, 1e10);
345 _scalar_transform_a.setDigits(3);
346 _scalar_transform_a.setIncrements(0.1, 1.0);
347 _scalar_transform_a.setValue(1.0);
348 _page_transform.table()
349 .attach(_scalar_transform_a, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
350 _scalar_transform_a.signal_value_changed()
351 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
354 _scalar_transform_b.setWidgetSizeRequest(65, -1);
355 _scalar_transform_b.setRange(-1e10, 1e10);
356 _scalar_transform_b.setDigits(3);
357 _scalar_transform_b.setIncrements(0.1, 1.0);
358 _scalar_transform_b.setValue(0.0);
359 _page_transform.table()
360 .attach(_scalar_transform_b, 0, 1, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
361 _scalar_transform_b.signal_value_changed()
362 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
365 _scalar_transform_c.setWidgetSizeRequest(65, -1);
366 _scalar_transform_c.setRange(-1e10, 1e10);
367 _scalar_transform_c.setDigits(3);
368 _scalar_transform_c.setIncrements(0.1, 1.0);
369 _scalar_transform_c.setValue(0.0);
370 _page_transform.table()
371 .attach(_scalar_transform_c, 1, 2, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
372 _scalar_transform_c.signal_value_changed()
373 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
376 _scalar_transform_d.setWidgetSizeRequest(65, -1);
377 _scalar_transform_d.setRange(-1e10, 1e10);
378 _scalar_transform_d.setDigits(3);
379 _scalar_transform_d.setIncrements(0.1, 1.0);
380 _scalar_transform_d.setValue(1.0);
381 _page_transform.table()
382 .attach(_scalar_transform_d, 1, 2, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
383 _scalar_transform_d.signal_value_changed()
384 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
387 _scalar_transform_e.setWidgetSizeRequest(65, -1);
388 _scalar_transform_e.setRange(-1e10, 1e10);
389 _scalar_transform_e.setDigits(3);
390 _scalar_transform_e.setIncrements(0.1, 1.0);
391 _scalar_transform_e.setValue(0.0);
392 _page_transform.table()
393 .attach(_scalar_transform_e, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
394 _scalar_transform_e.signal_value_changed()
395 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
398 _scalar_transform_f.setWidgetSizeRequest(65, -1);
399 _scalar_transform_f.setRange(-1e10, 1e10);
400 _scalar_transform_f.setDigits(3);
401 _scalar_transform_f.setIncrements(0.1, 1.0);
402 _scalar_transform_f.setValue(0.0);
403 _page_transform.table()
404 .attach(_scalar_transform_f, 2, 3, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
405 _scalar_transform_f.signal_value_changed()
406 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
408 // Edit existing matrix
409 _page_transform.table()
410 .attach(_check_replace_matrix, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
411 _check_replace_matrix.set_active(false);
412 _check_replace_matrix.signal_toggled()
413 .connect(sigc::mem_fun(*this, &Transformation::onReplaceMatrixToggled));
414 }
417 /*########################################################################
418 # U P D A T E
419 ########################################################################*/
421 void
422 Transformation::updateSelection(PageType page, Inkscape::Selection *selection)
423 {
424 if (!selection || selection->isEmpty())
425 return;
427 switch (page) {
428 case PAGE_MOVE: {
429 updatePageMove(selection);
430 break;
431 }
432 case PAGE_SCALE: {
433 updatePageScale(selection);
434 break;
435 }
436 case PAGE_ROTATE: {
437 updatePageRotate(selection);
438 break;
439 }
440 case PAGE_SKEW: {
441 updatePageSkew(selection);
442 break;
443 }
444 case PAGE_TRANSFORM: {
445 updatePageTransform(selection);
446 break;
447 }
448 case PAGE_QTY: {
449 break;
450 }
451 }
453 setResponseSensitive(Gtk::RESPONSE_APPLY,
454 selection && !selection->isEmpty());
455 }
457 void
458 Transformation::onSwitchPage(GtkNotebookPage */*page*/,
459 guint pagenum)
460 {
461 updateSelection((PageType)pagenum, sp_desktop_selection(getDesktop()));
462 }
465 void
466 Transformation::updatePageMove(Inkscape::Selection *selection)
467 {
468 if (selection && !selection->isEmpty()) {
469 if (!_check_move_relative.get_active()) {
470 Geom::OptRect bbox = selection->bounds();
471 if (bbox) {
472 double x = bbox->min()[Geom::X];
473 double y = bbox->min()[Geom::Y];
475 double conversion = _units_move.getConversion("px");
476 _scalar_move_horizontal.setValue(x / conversion);
477 _scalar_move_vertical.setValue(y / conversion);
478 }
479 } else {
480 // do nothing, so you can apply the same relative move to many objects in turn
481 }
482 _page_move.set_sensitive(true);
483 } else {
484 _page_move.set_sensitive(false);
485 }
486 }
488 void
489 Transformation::updatePageScale(Inkscape::Selection *selection)
490 {
491 if (selection && !selection->isEmpty()) {
492 Geom::OptRect bbox = selection->bounds();
493 if (bbox) {
494 double w = bbox->dimensions()[Geom::X];
495 double h = bbox->dimensions()[Geom::Y];
496 _scalar_scale_horizontal.setHundredPercent(w);
497 _scalar_scale_vertical.setHundredPercent(h);
498 onScaleXValueChanged(); // to update x/y proportionality if switch is on
499 _page_scale.set_sensitive(true);
500 } else {
501 _page_scale.set_sensitive(false);
502 }
503 } else {
504 _page_scale.set_sensitive(false);
505 }
506 }
508 void
509 Transformation::updatePageRotate(Inkscape::Selection *selection)
510 {
511 if (selection && !selection->isEmpty()) {
512 _page_rotate.set_sensitive(true);
513 } else {
514 _page_rotate.set_sensitive(false);
515 }
516 }
518 void
519 Transformation::updatePageSkew(Inkscape::Selection *selection)
520 {
521 if (selection && !selection->isEmpty()) {
522 Geom::OptRect bbox = selection->bounds();
523 if (bbox) {
524 double w = bbox->dimensions()[Geom::X];
525 double h = bbox->dimensions()[Geom::Y];
526 _scalar_skew_vertical.setHundredPercent(w);
527 _scalar_skew_horizontal.setHundredPercent(h);
528 _page_skew.set_sensitive(true);
529 } else {
530 _page_skew.set_sensitive(false);
531 }
532 } else {
533 _page_skew.set_sensitive(false);
534 }
535 }
537 void
538 Transformation::updatePageTransform(Inkscape::Selection *selection)
539 {
540 if (selection && !selection->isEmpty()) {
541 if (_check_replace_matrix.get_active()) {
542 Geom::Matrix current (SP_ITEM(selection->itemList()->data)->transform); // take from the first item in selection
544 Geom::Matrix new_displayed = current;
546 _scalar_transform_a.setValue(new_displayed[0]);
547 _scalar_transform_b.setValue(new_displayed[1]);
548 _scalar_transform_c.setValue(new_displayed[2]);
549 _scalar_transform_d.setValue(new_displayed[3]);
550 _scalar_transform_e.setValue(new_displayed[4]);
551 _scalar_transform_f.setValue(new_displayed[5]);
552 } else {
553 // do nothing, so you can apply the same matrix to many objects in turn
554 }
555 _page_transform.set_sensitive(true);
556 } else {
557 _page_transform.set_sensitive(false);
558 }
559 }
565 /*########################################################################
566 # A P P L Y
567 ########################################################################*/
571 void
572 Transformation::_apply()
573 {
574 Inkscape::Selection * const selection = _getSelection();
575 if (!selection || selection->isEmpty())
576 return;
578 int const page = _notebook.get_current_page();
580 switch (page) {
581 case PAGE_MOVE: {
582 applyPageMove(selection);
583 break;
584 }
585 case PAGE_ROTATE: {
586 applyPageRotate(selection);
587 break;
588 }
589 case PAGE_SCALE: {
590 applyPageScale(selection);
591 break;
592 }
593 case PAGE_SKEW: {
594 applyPageSkew(selection);
595 break;
596 }
597 case PAGE_TRANSFORM: {
598 applyPageTransform(selection);
599 break;
600 }
601 }
603 //Let's play with never turning this off
604 //setResponseSensitive(Gtk::RESPONSE_APPLY, false);
605 }
607 void
608 Transformation::applyPageMove(Inkscape::Selection *selection)
609 {
610 double x = _scalar_move_horizontal.getValue("px");
611 double y = _scalar_move_vertical.getValue("px");
613 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
614 if (!prefs->getBool("/dialogs/transformation/applyseparately")) {
615 // move selection as a whole
616 if (_check_move_relative.get_active()) {
617 sp_selection_move_relative(selection, x, y);
618 } else {
619 Geom::OptRect bbox = selection->bounds();
620 if (bbox) {
621 sp_selection_move_relative(selection,
622 x - bbox->min()[Geom::X], y - bbox->min()[Geom::Y]);
623 }
624 }
625 } else {
627 if (_check_move_relative.get_active()) {
628 // shift each object relatively to the previous one
629 using Inkscape::Util::GSListConstIterator;
630 std::list<SPItem *> selected;
631 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
632 if (selected.empty()) return;
634 if (fabs(x) > 1e-6) {
635 std::vector< BBoxSort > sorted;
636 for (std::list<SPItem *>::iterator it(selected.begin());
637 it != selected.end();
638 ++it)
639 {
640 Geom::OptRect bbox = (*it)->getBboxDesktop();
641 if (bbox) {
642 sorted.push_back(BBoxSort(*it, *bbox, Geom::X, x > 0? 1. : 0., x > 0? 0. : 1.));
643 }
644 }
645 //sort bbox by anchors
646 std::sort(sorted.begin(), sorted.end());
648 double move = x;
649 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
650 it < sorted.end();
651 it ++ )
652 {
653 sp_item_move_rel(it->item, Geom::Translate(move, 0));
654 // move each next object by x relative to previous
655 move += x;
656 }
657 }
658 if (fabs(y) > 1e-6) {
659 std::vector< BBoxSort > sorted;
660 for (std::list<SPItem *>::iterator it(selected.begin());
661 it != selected.end();
662 ++it)
663 {
664 Geom::OptRect bbox = (*it)->getBboxDesktop();
665 if (bbox) {
666 sorted.push_back(BBoxSort(*it, *bbox, Geom::Y, y > 0? 1. : 0., y > 0? 0. : 1.));
667 }
668 }
669 //sort bbox by anchors
670 std::sort(sorted.begin(), sorted.end());
672 double move = y;
673 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
674 it < sorted.end();
675 it ++ )
676 {
677 sp_item_move_rel(it->item, Geom::Translate(0, move));
678 // move each next object by x relative to previous
679 move += y;
680 }
681 }
682 } else {
683 Geom::OptRect bbox = selection->bounds();
684 if (bbox) {
685 sp_selection_move_relative(selection,
686 x - bbox->min()[Geom::X], y - bbox->min()[Geom::Y]);
687 }
688 }
689 }
691 DocumentUndo::done( sp_desktop_document(selection->desktop()) , SP_VERB_DIALOG_TRANSFORM,
692 _("Move"));
693 }
695 void
696 Transformation::applyPageScale(Inkscape::Selection *selection)
697 {
698 double scaleX = _scalar_scale_horizontal.getValue("px");
699 double scaleY = _scalar_scale_vertical.getValue("px");
701 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
702 if (prefs->getBool("/dialogs/transformation/applyseparately")) {
703 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
704 SPItem *item = SP_ITEM(l->data);
705 Geom::Scale scale (0,0);
706 // the values are increments!
707 if (_units_scale.isAbsolute()) {
708 Geom::OptRect bbox(item->getBboxDesktop());
709 if (bbox) {
710 double new_width = scaleX;
711 if (fabs(new_width) < 1e-6) new_width = 1e-6; // not 0, as this would result in a nasty no-bbox object
712 double new_height = scaleY;
713 if (fabs(new_height) < 1e-6) new_height = 1e-6;
714 scale = Geom::Scale(new_width / bbox->dimensions()[Geom::X], new_height / bbox->dimensions()[Geom::Y]);
715 }
716 } else {
717 double new_width = scaleX;
718 if (fabs(new_width) < 1e-6) new_width = 1e-6;
719 double new_height = scaleY;
720 if (fabs(new_height) < 1e-6) new_height = 1e-6;
721 scale = Geom::Scale(new_width / 100.0, new_height / 100.0);
722 }
723 sp_item_scale_rel (item, scale);
724 }
725 } else {
726 Geom::OptRect bbox(selection->bounds());
727 if (bbox) {
728 Geom::Point center(bbox->midpoint()); // use rotation center?
729 Geom::Scale scale (0,0);
730 // the values are increments!
731 if (_units_scale.isAbsolute()) {
732 double new_width = scaleX;
733 if (fabs(new_width) < 1e-6) new_width = 1e-6;
734 double new_height = scaleY;
735 if (fabs(new_height) < 1e-6) new_height = 1e-6;
736 scale = Geom::Scale(new_width / bbox->dimensions()[Geom::X], new_height / bbox->dimensions()[Geom::Y]);
737 } else {
738 double new_width = scaleX;
739 if (fabs(new_width) < 1e-6) new_width = 1e-6;
740 double new_height = scaleY;
741 if (fabs(new_height) < 1e-6) new_height = 1e-6;
742 scale = Geom::Scale(new_width / 100.0, new_height / 100.0);
743 }
744 sp_selection_scale_relative(selection, center, scale);
745 }
746 }
748 DocumentUndo::done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
749 _("Scale"));
750 }
752 void
753 Transformation::applyPageRotate(Inkscape::Selection *selection)
754 {
755 double angle = _scalar_rotate.getValue("deg");
757 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
758 if (prefs->getBool("/dialogs/transformation/applyseparately")) {
759 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
760 SPItem *item = SP_ITEM(l->data);
761 sp_item_rotate_rel(item, Geom::Rotate (angle*M_PI/180.0));
762 }
763 } else {
764 boost::optional<Geom::Point> center = selection->center();
765 if (center) {
766 sp_selection_rotate_relative(selection, *center, angle);
767 }
768 }
770 DocumentUndo::done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
771 _("Rotate"));
772 }
774 void
775 Transformation::applyPageSkew(Inkscape::Selection *selection)
776 {
777 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
778 if (prefs->getBool("/dialogs/transformation/applyseparately")) {
779 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
780 SPItem *item = SP_ITEM(l->data);
782 if (!_units_skew.isAbsolute()) { // percentage
783 double skewX = _scalar_skew_horizontal.getValue("%");
784 double skewY = _scalar_skew_vertical.getValue("%");
785 sp_item_skew_rel (item, 0.01*skewX, 0.01*skewY);
786 } else if (_units_skew.isRadial()) { //deg or rad
787 double angleX = _scalar_skew_horizontal.getValue("rad");
788 double angleY = _scalar_skew_vertical.getValue("rad");
789 double skewX = tan(-angleX);
790 double skewY = tan(angleY);
791 sp_item_skew_rel (item, skewX, skewY);
792 } else { // absolute displacement
793 double skewX = _scalar_skew_horizontal.getValue("px");
794 double skewY = _scalar_skew_vertical.getValue("px");
795 Geom::OptRect bbox(item->getBboxDesktop());
796 if (bbox) {
797 double width = bbox->dimensions()[Geom::X];
798 double height = bbox->dimensions()[Geom::Y];
799 sp_item_skew_rel (item, skewX/height, skewY/width);
800 }
801 }
802 }
803 } else { // transform whole selection
804 Geom::OptRect bbox = selection->bounds();
805 boost::optional<Geom::Point> center = selection->center();
807 if ( bbox && center ) {
808 double width = bbox->dimensions()[Geom::X];
809 double height = bbox->dimensions()[Geom::Y];
811 if (!_units_skew.isAbsolute()) { // percentage
812 double skewX = _scalar_skew_horizontal.getValue("%");
813 double skewY = _scalar_skew_vertical.getValue("%");
814 sp_selection_skew_relative(selection, *center, 0.01*skewX, 0.01*skewY);
815 } else if (_units_skew.isRadial()) { //deg or rad
816 double angleX = _scalar_skew_horizontal.getValue("rad");
817 double angleY = _scalar_skew_vertical.getValue("rad");
818 double skewX = tan(-angleX);
819 double skewY = tan(angleY);
820 sp_selection_skew_relative(selection, *center, skewX, skewY);
821 } else { // absolute displacement
822 double skewX = _scalar_skew_horizontal.getValue("px");
823 double skewY = _scalar_skew_vertical.getValue("px");
824 sp_selection_skew_relative(selection, *center, skewX/height, skewY/width);
825 }
826 }
827 }
829 DocumentUndo::done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
830 _("Skew"));
831 }
834 void
835 Transformation::applyPageTransform(Inkscape::Selection *selection)
836 {
837 double a = _scalar_transform_a.getValue();
838 double b = _scalar_transform_b.getValue();
839 double c = _scalar_transform_c.getValue();
840 double d = _scalar_transform_d.getValue();
841 double e = _scalar_transform_e.getValue();
842 double f = _scalar_transform_f.getValue();
844 Geom::Matrix displayed(a, b, c, d, e, f);
846 if (_check_replace_matrix.get_active()) {
847 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
848 SPItem *item = SP_ITEM(l->data);
849 item->set_item_transform(displayed);
850 SP_OBJECT(item)->updateRepr();
851 }
852 } else {
853 sp_selection_apply_affine(selection, displayed); // post-multiply each object's transform
854 }
856 DocumentUndo::done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
857 _("Edit transformation matrix"));
858 }
864 /*########################################################################
865 # V A L U E - C H A N G E D C A L L B A C K S
866 ########################################################################*/
868 void
869 Transformation::onMoveValueChanged()
870 {
871 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
872 }
874 void
875 Transformation::onMoveRelativeToggled()
876 {
877 Inkscape::Selection *selection = _getSelection();
879 if (!selection || selection->isEmpty())
880 return;
882 double x = _scalar_move_horizontal.getValue("px");
883 double y = _scalar_move_vertical.getValue("px");
885 double conversion = _units_move.getConversion("px");
887 //g_message("onMoveRelativeToggled: %f, %f px\n", x, y);
889 Geom::OptRect bbox = selection->bounds();
891 if (bbox) {
892 if (_check_move_relative.get_active()) {
893 // From absolute to relative
894 _scalar_move_horizontal.setValue((x - bbox->min()[Geom::X]) / conversion);
895 _scalar_move_vertical.setValue(( y - bbox->min()[Geom::Y]) / conversion);
896 } else {
897 // From relative to absolute
898 _scalar_move_horizontal.setValue((bbox->min()[Geom::X] + x) / conversion);
899 _scalar_move_vertical.setValue(( bbox->min()[Geom::Y] + y) / conversion);
900 }
901 }
903 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
904 }
906 void
907 Transformation::onScaleXValueChanged()
908 {
909 if (_scalar_scale_horizontal.setProgrammatically) {
910 _scalar_scale_horizontal.setProgrammatically = false;
911 return;
912 }
914 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
916 if (_check_scale_proportional.get_active()) {
917 if (!_units_scale.isAbsolute()) { // percentage, just copy over
918 _scalar_scale_vertical.setValue(_scalar_scale_horizontal.getValue("%"));
919 } else {
920 double scaleXPercentage = _scalar_scale_horizontal.getAsPercentage();
921 _scalar_scale_vertical.setFromPercentage (scaleXPercentage);
922 }
923 }
924 }
926 void
927 Transformation::onScaleYValueChanged()
928 {
929 if (_scalar_scale_vertical.setProgrammatically) {
930 _scalar_scale_vertical.setProgrammatically = false;
931 return;
932 }
934 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
936 if (_check_scale_proportional.get_active()) {
937 if (!_units_scale.isAbsolute()) { // percentage, just copy over
938 _scalar_scale_horizontal.setValue(_scalar_scale_vertical.getValue("%"));
939 } else {
940 double scaleYPercentage = _scalar_scale_vertical.getAsPercentage();
941 _scalar_scale_horizontal.setFromPercentage (scaleYPercentage);
942 }
943 }
944 }
946 void
947 Transformation::onRotateValueChanged()
948 {
949 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
950 }
952 void
953 Transformation::onSkewValueChanged()
954 {
955 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
956 }
958 void
959 Transformation::onTransformValueChanged()
960 {
962 /*
963 double a = _scalar_transform_a.getValue();
964 double b = _scalar_transform_b.getValue();
965 double c = _scalar_transform_c.getValue();
966 double d = _scalar_transform_d.getValue();
967 double e = _scalar_transform_e.getValue();
968 double f = _scalar_transform_f.getValue();
970 //g_message("onTransformValueChanged: (%f, %f, %f, %f, %f, %f)\n",
971 // a, b, c, d, e ,f);
972 */
974 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
975 }
977 void
978 Transformation::onReplaceMatrixToggled()
979 {
980 Inkscape::Selection *selection = _getSelection();
982 if (!selection || selection->isEmpty())
983 return;
985 double a = _scalar_transform_a.getValue();
986 double b = _scalar_transform_b.getValue();
987 double c = _scalar_transform_c.getValue();
988 double d = _scalar_transform_d.getValue();
989 double e = _scalar_transform_e.getValue();
990 double f = _scalar_transform_f.getValue();
992 Geom::Matrix displayed (a, b, c, d, e, f);
993 Geom::Matrix current = SP_ITEM(selection->itemList()->data)->transform; // take from the first item in selection
995 Geom::Matrix new_displayed;
996 if (_check_replace_matrix.get_active()) {
997 new_displayed = current;
998 } else {
999 new_displayed = current.inverse() * displayed;
1000 }
1002 _scalar_transform_a.setValue(new_displayed[0]);
1003 _scalar_transform_b.setValue(new_displayed[1]);
1004 _scalar_transform_c.setValue(new_displayed[2]);
1005 _scalar_transform_d.setValue(new_displayed[3]);
1006 _scalar_transform_e.setValue(new_displayed[4]);
1007 _scalar_transform_f.setValue(new_displayed[5]);
1008 }
1010 void
1011 Transformation::onScaleProportionalToggled()
1012 {
1013 onScaleXValueChanged();
1014 }
1017 void
1018 Transformation::onClear()
1019 {
1020 int const page = _notebook.get_current_page();
1022 switch (page) {
1023 case PAGE_MOVE: {
1024 Inkscape::Selection *selection = _getSelection();
1025 if (!selection || selection->isEmpty() || _check_move_relative.get_active()) {
1026 _scalar_move_horizontal.setValue(0);
1027 _scalar_move_vertical.setValue(0);
1028 } else {
1029 Geom::OptRect bbox = selection->bounds();
1030 if (bbox) {
1031 _scalar_move_horizontal.setValue(bbox->min()[Geom::X], "px");
1032 _scalar_move_vertical.setValue(bbox->min()[Geom::Y], "px");
1033 }
1034 }
1035 break;
1036 }
1037 case PAGE_ROTATE: {
1038 _scalar_rotate.setValue(0);
1039 break;
1040 }
1041 case PAGE_SCALE: {
1042 _scalar_scale_horizontal.setValue(100, "%");
1043 _scalar_scale_vertical.setValue(100, "%");
1044 break;
1045 }
1046 case PAGE_SKEW: {
1047 _scalar_skew_horizontal.setValue(0);
1048 _scalar_skew_vertical.setValue(0);
1049 break;
1050 }
1051 case PAGE_TRANSFORM: {
1052 _scalar_transform_a.setValue(1);
1053 _scalar_transform_b.setValue(0);
1054 _scalar_transform_c.setValue(0);
1055 _scalar_transform_d.setValue(1);
1056 _scalar_transform_e.setValue(0);
1057 _scalar_transform_f.setValue(0);
1058 break;
1059 }
1060 }
1061 }
1063 void
1064 Transformation::onApplySeparatelyToggled()
1065 {
1066 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1067 prefs->setBool("/dialogs/transformation/applyseparately", _check_apply_separately.get_active());
1068 }
1071 } // namespace Dialog
1072 } // namespace UI
1073 } // namespace Inkscape
1075 /*
1076 Local Variables:
1077 mode:c++
1078 c-file-style:"stroustrup"
1079 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1080 indent-tabs-mode:nil
1081 fill-column:99
1082 End:
1083 */
1084 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :