1 /** @file
2 * \brief Transform dialog - implementation
3 */
4 /* Authors:
5 * Bryce W. Harrington <bryce@bryceharrington.org>
6 * buliabyak@gmail.com
7 *
8 * Copyright (C) 2004, 2005 Authors
9 * Released under GNU GPL. Read the file 'COPYING' for more information.
10 */
12 #ifdef HAVE_CONFIG_H
13 # include <config.h>
14 #endif
16 #include <gtkmm/stock.h>
17 #include <gtkmm/dialog.h>
19 #include "document.h"
20 #include "desktop-handles.h"
21 #include "transformation.h"
22 #include "align-and-distribute.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 "preferences.h"
29 #include "sp-namedview.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 "", "transform-move-horizontal", &_units_move),
85 _scalar_move_vertical (_("_Vertical:"), _("Vertical displacement (relative) or position (absolute)"), UNIT_TYPE_LINEAR,
86 "", "transform-move-vertical", &_units_move),
87 _scalar_scale_horizontal(_("_Width:"), _("Horizontal size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
88 "", "transform-scale-horizontal", &_units_scale),
89 _scalar_scale_vertical (_("_Height:"), _("Vertical size (absolute or percentage of current)"), UNIT_TYPE_DIMENSIONLESS,
90 "", "transform-scale-vertical", &_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-skew-horizontal", &_units_skew),
95 _scalar_skew_vertical (_("_Vertical:"), _("Vertical skew angle (positive = counterclockwise), or absolute displacement, or percentage displacement"), UNIT_TYPE_LINEAR,
96 "", "transform-skew-vertical", &_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 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
138 _check_apply_separately.set_active(prefs->getBool("/dialogs/transformation/applyseparately"));
139 _check_apply_separately.signal_toggled().connect(sigc::mem_fun(*this, &Transformation::onApplySeparatelyToggled));
141 // make sure all spinbuttons activate Apply on pressing Enter
142 ((Gtk::Entry *) (_scalar_move_horizontal.getWidget()))->set_activates_default(true);
143 ((Gtk::Entry *) (_scalar_move_vertical.getWidget()))->set_activates_default(true);
144 ((Gtk::Entry *) (_scalar_scale_horizontal.getWidget()))->set_activates_default(true);
145 ((Gtk::Entry *) (_scalar_scale_vertical.getWidget()))->set_activates_default(true);
146 ((Gtk::Entry *) (_scalar_rotate.getWidget()))->set_activates_default(true);
147 ((Gtk::Entry *) (_scalar_skew_horizontal.getWidget()))->set_activates_default(true);
148 ((Gtk::Entry *) (_scalar_skew_vertical.getWidget()))->set_activates_default(true);
150 updateSelection(PAGE_MOVE, _getSelection());
152 resetButton = addResponseButton(Gtk::Stock::CLEAR, 0);
153 if (resetButton) {
154 _tooltips.set_tip((*resetButton), _("Reset the values on the current tab to defaults"));
155 resetButton->set_sensitive(true);
156 resetButton->signal_clicked().connect(sigc::mem_fun(*this, &Transformation::onClear));
157 }
159 applyButton = addResponseButton(Gtk::Stock::APPLY, Gtk::RESPONSE_APPLY);
160 if (applyButton) {
161 _tooltips.set_tip((*applyButton), _("Apply transformation to selection"));
162 applyButton->set_sensitive(false);
163 }
165 // Connect to the global selection changed & modified signals
166 g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
167 g_signal_connect (G_OBJECT (INKSCAPE), "modify_selection", G_CALLBACK (on_selection_modified), this);
169 show_all_children();
170 }
172 Transformation::~Transformation()
173 {
174 sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
175 }
178 /*########################################################################
179 # U T I L I T Y
180 ########################################################################*/
182 void
183 Transformation::presentPage(Transformation::PageType page)
184 {
185 _notebook.set_current_page(page);
186 show();
187 present();
188 }
193 /*########################################################################
194 # S E T U P L A Y O U T
195 ########################################################################*/
198 void
199 Transformation::layoutPageMove()
200 {
201 _units_move.setUnitType(UNIT_TYPE_LINEAR);
203 // Setting default unit to document unit
204 SPDesktop *dt = getDesktop();
205 SPNamedView *nv = sp_desktop_namedview(dt);
206 if (nv->doc_units) {
207 _units_move.setUnit(nv->doc_units->abbr);
208 }
210 _scalar_move_horizontal.initScalar(-1e6, 1e6);
211 _scalar_move_horizontal.setDigits(3);
212 _scalar_move_horizontal.setIncrements(0.1, 1.0);
214 _scalar_move_vertical.initScalar(-1e6, 1e6);
215 _scalar_move_vertical.setDigits(3);
216 _scalar_move_vertical.setIncrements(0.1, 1.0);
218 //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_HOR );
219 _page_move.table()
220 .attach(_scalar_move_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
222 _page_move.table()
223 .attach(_units_move, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
225 _scalar_move_horizontal.signal_value_changed()
226 .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged));
228 //_scalar_move_vertical.set_label_image( INKSCAPE_STOCK_ARROWS_VER );
229 _page_move.table()
230 .attach(_scalar_move_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
232 _scalar_move_vertical.signal_value_changed()
233 .connect(sigc::mem_fun(*this, &Transformation::onMoveValueChanged));
235 // Relative moves
236 _page_move.table()
237 .attach(_check_move_relative, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
238 _check_move_relative.set_active(true);
239 _check_move_relative.signal_toggled()
240 .connect(sigc::mem_fun(*this, &Transformation::onMoveRelativeToggled));
241 }
243 void
244 Transformation::layoutPageScale()
245 {
246 _units_scale.setUnitType(UNIT_TYPE_DIMENSIONLESS);
247 _units_scale.setUnitType(UNIT_TYPE_LINEAR);
249 _scalar_scale_horizontal.initScalar(-1e6, 1e6);
250 _scalar_scale_horizontal.setValue(100.0, "%");
251 _scalar_scale_horizontal.setDigits(3);
252 _scalar_scale_horizontal.setIncrements(0.1, 1.0);
253 _scalar_scale_horizontal.setAbsoluteIsIncrement(true);
254 _scalar_scale_horizontal.setPercentageIsIncrement(true);
256 _scalar_scale_vertical.initScalar(-1e6, 1e6);
257 _scalar_scale_vertical.setValue(100.0, "%");
258 _scalar_scale_vertical.setDigits(3);
259 _scalar_scale_vertical.setIncrements(0.1, 1.0);
260 _scalar_scale_vertical.setAbsoluteIsIncrement(true);
261 _scalar_scale_vertical.setPercentageIsIncrement(true);
263 _page_scale.table()
264 .attach(_scalar_scale_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
265 _scalar_scale_horizontal.signal_value_changed()
266 .connect(sigc::mem_fun(*this, &Transformation::onScaleXValueChanged));
268 _page_scale.table()
269 .attach(_units_scale, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
271 _page_scale.table()
272 .attach(_scalar_scale_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
273 _scalar_scale_vertical.signal_value_changed()
274 .connect(sigc::mem_fun(*this, &Transformation::onScaleYValueChanged));
276 _page_scale.table()
277 .attach(_check_scale_proportional, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
278 _check_scale_proportional.set_active(false);
279 _check_scale_proportional.signal_toggled()
280 .connect(sigc::mem_fun(*this, &Transformation::onScaleProportionalToggled));
282 //TODO: add a widget for selecting the fixed point in scaling, or honour rotation center?
283 }
285 void
286 Transformation::layoutPageRotate()
287 {
288 _units_rotate.setUnitType(UNIT_TYPE_RADIAL);
290 _scalar_rotate.initScalar(-360.0, 360.0);
291 _scalar_rotate.setDigits(3);
292 _scalar_rotate.setIncrements(0.1, 1.0);
294 _page_rotate.table()
295 .attach(_scalar_rotate, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
297 _page_rotate.table()
298 .attach(_units_rotate, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
300 _scalar_rotate.signal_value_changed()
301 .connect(sigc::mem_fun(*this, &Transformation::onRotateValueChanged));
303 //TODO: honour rotation center?
304 }
306 void
307 Transformation::layoutPageSkew()
308 {
309 _units_skew.setUnitType(UNIT_TYPE_LINEAR);
310 _units_skew.setUnitType(UNIT_TYPE_DIMENSIONLESS);
311 _units_skew.setUnitType(UNIT_TYPE_RADIAL);
313 _scalar_skew_horizontal.initScalar(-1e6, 1e6);
314 _scalar_skew_horizontal.setDigits(3);
315 _scalar_skew_horizontal.setIncrements(0.1, 1.0);
317 _scalar_skew_vertical.initScalar(-1e6, 1e6);
318 _scalar_skew_vertical.setDigits(3);
319 _scalar_skew_vertical.setIncrements(0.1, 1.0);
321 _page_skew.table()
322 .attach(_scalar_skew_horizontal, 0, 2, 0, 1, Gtk::FILL, Gtk::SHRINK);
323 _scalar_skew_horizontal.signal_value_changed()
324 .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged));
326 _page_skew.table()
327 .attach(_units_skew, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
329 _page_skew.table()
330 .attach(_scalar_skew_vertical, 0, 2, 1, 2, Gtk::FILL, Gtk::SHRINK);
331 _scalar_skew_vertical.signal_value_changed()
332 .connect(sigc::mem_fun(*this, &Transformation::onSkewValueChanged));
334 //TODO: honour rotation center?
335 }
339 void
340 Transformation::layoutPageTransform()
341 {
342 _scalar_transform_a.setWidgetSizeRequest(65, -1);
343 _scalar_transform_a.setRange(-1e10, 1e10);
344 _scalar_transform_a.setDigits(3);
345 _scalar_transform_a.setIncrements(0.1, 1.0);
346 _scalar_transform_a.setValue(1.0);
347 _page_transform.table()
348 .attach(_scalar_transform_a, 0, 1, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
349 _scalar_transform_a.signal_value_changed()
350 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
353 _scalar_transform_b.setWidgetSizeRequest(65, -1);
354 _scalar_transform_b.setRange(-1e10, 1e10);
355 _scalar_transform_b.setDigits(3);
356 _scalar_transform_b.setIncrements(0.1, 1.0);
357 _scalar_transform_b.setValue(0.0);
358 _page_transform.table()
359 .attach(_scalar_transform_b, 0, 1, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
360 _scalar_transform_b.signal_value_changed()
361 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
364 _scalar_transform_c.setWidgetSizeRequest(65, -1);
365 _scalar_transform_c.setRange(-1e10, 1e10);
366 _scalar_transform_c.setDigits(3);
367 _scalar_transform_c.setIncrements(0.1, 1.0);
368 _scalar_transform_c.setValue(0.0);
369 _page_transform.table()
370 .attach(_scalar_transform_c, 1, 2, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
371 _scalar_transform_c.signal_value_changed()
372 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
375 _scalar_transform_d.setWidgetSizeRequest(65, -1);
376 _scalar_transform_d.setRange(-1e10, 1e10);
377 _scalar_transform_d.setDigits(3);
378 _scalar_transform_d.setIncrements(0.1, 1.0);
379 _scalar_transform_d.setValue(1.0);
380 _page_transform.table()
381 .attach(_scalar_transform_d, 1, 2, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
382 _scalar_transform_d.signal_value_changed()
383 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
386 _scalar_transform_e.setWidgetSizeRequest(65, -1);
387 _scalar_transform_e.setRange(-1e10, 1e10);
388 _scalar_transform_e.setDigits(3);
389 _scalar_transform_e.setIncrements(0.1, 1.0);
390 _scalar_transform_e.setValue(0.0);
391 _page_transform.table()
392 .attach(_scalar_transform_e, 2, 3, 0, 1, Gtk::SHRINK, Gtk::SHRINK);
393 _scalar_transform_e.signal_value_changed()
394 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
397 _scalar_transform_f.setWidgetSizeRequest(65, -1);
398 _scalar_transform_f.setRange(-1e10, 1e10);
399 _scalar_transform_f.setDigits(3);
400 _scalar_transform_f.setIncrements(0.1, 1.0);
401 _scalar_transform_f.setValue(0.0);
402 _page_transform.table()
403 .attach(_scalar_transform_f, 2, 3, 1, 2, Gtk::SHRINK, Gtk::SHRINK);
404 _scalar_transform_f.signal_value_changed()
405 .connect(sigc::mem_fun(*this, &Transformation::onTransformValueChanged));
407 // Edit existing matrix
408 _page_transform.table()
409 .attach(_check_replace_matrix, 0, 2, 2, 3, Gtk::FILL, Gtk::SHRINK);
410 _check_replace_matrix.set_active(false);
411 _check_replace_matrix.signal_toggled()
412 .connect(sigc::mem_fun(*this, &Transformation::onReplaceMatrixToggled));
413 }
416 /*########################################################################
417 # U P D A T E
418 ########################################################################*/
420 void
421 Transformation::updateSelection(PageType page, Inkscape::Selection *selection)
422 {
423 if (!selection || selection->isEmpty())
424 return;
426 switch (page) {
427 case PAGE_MOVE: {
428 updatePageMove(selection);
429 break;
430 }
431 case PAGE_SCALE: {
432 updatePageScale(selection);
433 break;
434 }
435 case PAGE_ROTATE: {
436 updatePageRotate(selection);
437 break;
438 }
439 case PAGE_SKEW: {
440 updatePageSkew(selection);
441 break;
442 }
443 case PAGE_TRANSFORM: {
444 updatePageTransform(selection);
445 break;
446 }
447 case PAGE_QTY: {
448 break;
449 }
450 }
452 setResponseSensitive(Gtk::RESPONSE_APPLY,
453 selection && !selection->isEmpty());
454 }
456 void
457 Transformation::onSwitchPage(GtkNotebookPage */*page*/,
458 guint pagenum)
459 {
460 updateSelection((PageType)pagenum, sp_desktop_selection(getDesktop()));
461 }
464 void
465 Transformation::updatePageMove(Inkscape::Selection *selection)
466 {
467 if (selection && !selection->isEmpty()) {
468 if (!_check_move_relative.get_active()) {
469 Geom::OptRect bbox = selection->bounds();
470 if (bbox) {
471 double x = bbox->min()[Geom::X];
472 double y = bbox->min()[Geom::Y];
474 double conversion = _units_move.getConversion("px");
475 _scalar_move_horizontal.setValue(x / conversion);
476 _scalar_move_vertical.setValue(y / conversion);
477 }
478 } else {
479 // do nothing, so you can apply the same relative move to many objects in turn
480 }
481 _page_move.set_sensitive(true);
482 } else {
483 _page_move.set_sensitive(false);
484 }
485 }
487 void
488 Transformation::updatePageScale(Inkscape::Selection *selection)
489 {
490 if (selection && !selection->isEmpty()) {
491 Geom::OptRect bbox = selection->bounds();
492 if (bbox) {
493 double w = bbox->dimensions()[Geom::X];
494 double h = bbox->dimensions()[Geom::Y];
495 _scalar_scale_horizontal.setHundredPercent(w);
496 _scalar_scale_vertical.setHundredPercent(h);
497 onScaleXValueChanged(); // to update x/y proportionality if switch is on
498 _page_scale.set_sensitive(true);
499 } else {
500 _page_scale.set_sensitive(false);
501 }
502 } else {
503 _page_scale.set_sensitive(false);
504 }
505 }
507 void
508 Transformation::updatePageRotate(Inkscape::Selection *selection)
509 {
510 if (selection && !selection->isEmpty()) {
511 _page_rotate.set_sensitive(true);
512 } else {
513 _page_rotate.set_sensitive(false);
514 }
515 }
517 void
518 Transformation::updatePageSkew(Inkscape::Selection *selection)
519 {
520 if (selection && !selection->isEmpty()) {
521 Geom::OptRect bbox = selection->bounds();
522 if (bbox) {
523 double w = bbox->dimensions()[Geom::X];
524 double h = bbox->dimensions()[Geom::Y];
525 _scalar_skew_vertical.setHundredPercent(w);
526 _scalar_skew_horizontal.setHundredPercent(h);
527 _page_skew.set_sensitive(true);
528 } else {
529 _page_skew.set_sensitive(false);
530 }
531 } else {
532 _page_skew.set_sensitive(false);
533 }
534 }
536 void
537 Transformation::updatePageTransform(Inkscape::Selection *selection)
538 {
539 if (selection && !selection->isEmpty()) {
540 if (_check_replace_matrix.get_active()) {
541 Geom::Matrix current (SP_ITEM(selection->itemList()->data)->transform); // take from the first item in selection
543 Geom::Matrix new_displayed = current;
545 _scalar_transform_a.setValue(new_displayed[0]);
546 _scalar_transform_b.setValue(new_displayed[1]);
547 _scalar_transform_c.setValue(new_displayed[2]);
548 _scalar_transform_d.setValue(new_displayed[3]);
549 _scalar_transform_e.setValue(new_displayed[4]);
550 _scalar_transform_f.setValue(new_displayed[5]);
551 } else {
552 // do nothing, so you can apply the same matrix to many objects in turn
553 }
554 _page_transform.set_sensitive(true);
555 } else {
556 _page_transform.set_sensitive(false);
557 }
558 }
564 /*########################################################################
565 # A P P L Y
566 ########################################################################*/
570 void
571 Transformation::_apply()
572 {
573 Inkscape::Selection * const selection = _getSelection();
574 if (!selection || selection->isEmpty())
575 return;
577 int const page = _notebook.get_current_page();
579 switch (page) {
580 case PAGE_MOVE: {
581 applyPageMove(selection);
582 break;
583 }
584 case PAGE_ROTATE: {
585 applyPageRotate(selection);
586 break;
587 }
588 case PAGE_SCALE: {
589 applyPageScale(selection);
590 break;
591 }
592 case PAGE_SKEW: {
593 applyPageSkew(selection);
594 break;
595 }
596 case PAGE_TRANSFORM: {
597 applyPageTransform(selection);
598 break;
599 }
600 }
602 //Let's play with never turning this off
603 //setResponseSensitive(Gtk::RESPONSE_APPLY, false);
604 }
606 void
607 Transformation::applyPageMove(Inkscape::Selection *selection)
608 {
609 double x = _scalar_move_horizontal.getValue("px");
610 double y = _scalar_move_vertical.getValue("px");
612 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
613 if (!prefs->getBool("/dialogs/transformation/applyseparately")) {
614 // move selection as a whole
615 if (_check_move_relative.get_active()) {
616 sp_selection_move_relative(selection, x, y);
617 } else {
618 Geom::OptRect bbox = selection->bounds();
619 if (bbox) {
620 sp_selection_move_relative(selection,
621 x - bbox->min()[Geom::X], y - bbox->min()[Geom::Y]);
622 }
623 }
624 } else {
626 if (_check_move_relative.get_active()) {
627 // shift each object relatively to the previous one
628 using Inkscape::Util::GSListConstIterator;
629 std::list<SPItem *> selected;
630 selected.insert<GSListConstIterator<SPItem *> >(selected.end(), selection->itemList(), NULL);
631 if (selected.empty()) return;
633 if (fabs(x) > 1e-6) {
634 std::vector< BBoxSort > sorted;
635 for (std::list<SPItem *>::iterator it(selected.begin());
636 it != selected.end();
637 ++it)
638 {
639 Geom::OptRect bbox = sp_item_bbox_desktop(*it);
640 if (bbox) {
641 sorted.push_back(BBoxSort(*it, *bbox, Geom::X, x > 0? 1. : 0., x > 0? 0. : 1.));
642 }
643 }
644 //sort bbox by anchors
645 std::sort(sorted.begin(), sorted.end());
647 double move = x;
648 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
649 it < sorted.end();
650 it ++ )
651 {
652 sp_item_move_rel(it->item, Geom::Translate(move, 0));
653 // move each next object by x relative to previous
654 move += x;
655 }
656 }
657 if (fabs(y) > 1e-6) {
658 std::vector< BBoxSort > sorted;
659 for (std::list<SPItem *>::iterator it(selected.begin());
660 it != selected.end();
661 ++it)
662 {
663 Geom::OptRect bbox = sp_item_bbox_desktop(*it);
664 if (bbox) {
665 sorted.push_back(BBoxSort(*it, *bbox, Geom::Y, y > 0? 1. : 0., y > 0? 0. : 1.));
666 }
667 }
668 //sort bbox by anchors
669 std::sort(sorted.begin(), sorted.end());
671 double move = y;
672 for ( std::vector<BBoxSort> ::iterator it (sorted.begin());
673 it < sorted.end();
674 it ++ )
675 {
676 sp_item_move_rel(it->item, Geom::Translate(0, move));
677 // move each next object by x relative to previous
678 move += y;
679 }
680 }
681 } else {
682 Geom::OptRect bbox = selection->bounds();
683 if (bbox) {
684 sp_selection_move_relative(selection,
685 x - bbox->min()[Geom::X], y - bbox->min()[Geom::Y]);
686 }
687 }
688 }
690 sp_document_done ( sp_desktop_document (selection->desktop()) , SP_VERB_DIALOG_TRANSFORM,
691 _("Move"));
692 }
694 void
695 Transformation::applyPageScale(Inkscape::Selection *selection)
696 {
697 double scaleX = _scalar_scale_horizontal.getValue("px");
698 double scaleY = _scalar_scale_vertical.getValue("px");
700 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
701 if (prefs->getBool("/dialogs/transformation/applyseparately")) {
702 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
703 SPItem *item = SP_ITEM(l->data);
704 Geom::Scale scale (0,0);
705 // the values are increments!
706 if (_units_scale.isAbsolute()) {
707 Geom::OptRect bbox(sp_item_bbox_desktop(item));
708 if (bbox) {
709 double new_width = scaleX;
710 if (fabs(new_width) < 1e-6) new_width = 1e-6; // not 0, as this would result in a nasty no-bbox object
711 double new_height = scaleY;
712 if (fabs(new_height) < 1e-6) new_height = 1e-6;
713 scale = Geom::Scale(new_width / bbox->dimensions()[Geom::X], new_height / bbox->dimensions()[Geom::Y]);
714 }
715 } else {
716 double new_width = scaleX;
717 if (fabs(new_width) < 1e-6) new_width = 1e-6;
718 double new_height = scaleY;
719 if (fabs(new_height) < 1e-6) new_height = 1e-6;
720 scale = Geom::Scale(new_width / 100.0, new_height / 100.0);
721 }
722 sp_item_scale_rel (item, scale);
723 }
724 } else {
725 Geom::OptRect bbox(selection->bounds());
726 if (bbox) {
727 Geom::Point center(bbox->midpoint()); // use rotation center?
728 Geom::Scale scale (0,0);
729 // the values are increments!
730 if (_units_scale.isAbsolute()) {
731 double new_width = scaleX;
732 if (fabs(new_width) < 1e-6) new_width = 1e-6;
733 double new_height = scaleY;
734 if (fabs(new_height) < 1e-6) new_height = 1e-6;
735 scale = Geom::Scale(new_width / bbox->dimensions()[Geom::X], new_height / bbox->dimensions()[Geom::Y]);
736 } else {
737 double new_width = scaleX;
738 if (fabs(new_width) < 1e-6) new_width = 1e-6;
739 double new_height = scaleY;
740 if (fabs(new_height) < 1e-6) new_height = 1e-6;
741 scale = Geom::Scale(new_width / 100.0, new_height / 100.0);
742 }
743 sp_selection_scale_relative(selection, center, scale);
744 }
745 }
747 sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
748 _("Scale"));
749 }
751 void
752 Transformation::applyPageRotate(Inkscape::Selection *selection)
753 {
754 double angle = _scalar_rotate.getValue("deg");
756 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
757 if (prefs->getBool("/dialogs/transformation/applyseparately")) {
758 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
759 SPItem *item = SP_ITEM(l->data);
760 sp_item_rotate_rel(item, Geom::Rotate (angle*M_PI/180.0));
761 }
762 } else {
763 boost::optional<Geom::Point> center = selection->center();
764 if (center) {
765 sp_selection_rotate_relative(selection, *center, angle);
766 }
767 }
769 sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
770 _("Rotate"));
771 }
773 void
774 Transformation::applyPageSkew(Inkscape::Selection *selection)
775 {
776 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
777 if (prefs->getBool("/dialogs/transformation/applyseparately")) {
778 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
779 SPItem *item = SP_ITEM(l->data);
781 if (!_units_skew.isAbsolute()) { // percentage
782 double skewX = _scalar_skew_horizontal.getValue("%");
783 double skewY = _scalar_skew_vertical.getValue("%");
784 sp_item_skew_rel (item, 0.01*skewX, 0.01*skewY);
785 } else if (_units_skew.isRadial()) { //deg or rad
786 double angleX = _scalar_skew_horizontal.getValue("rad");
787 double angleY = _scalar_skew_vertical.getValue("rad");
788 double skewX = tan(-angleX);
789 double skewY = tan(angleY);
790 sp_item_skew_rel (item, skewX, skewY);
791 } else { // absolute displacement
792 double skewX = _scalar_skew_horizontal.getValue("px");
793 double skewY = _scalar_skew_vertical.getValue("px");
794 Geom::OptRect bbox(sp_item_bbox_desktop(item));
795 if (bbox) {
796 double width = bbox->dimensions()[Geom::X];
797 double height = bbox->dimensions()[Geom::Y];
798 sp_item_skew_rel (item, skewX/height, skewY/width);
799 }
800 }
801 }
802 } else { // transform whole selection
803 Geom::OptRect bbox = selection->bounds();
804 boost::optional<Geom::Point> center = selection->center();
806 if ( bbox && center ) {
807 double width = bbox->dimensions()[Geom::X];
808 double height = bbox->dimensions()[Geom::Y];
810 if (!_units_skew.isAbsolute()) { // percentage
811 double skewX = _scalar_skew_horizontal.getValue("%");
812 double skewY = _scalar_skew_vertical.getValue("%");
813 sp_selection_skew_relative(selection, *center, 0.01*skewX, 0.01*skewY);
814 } else if (_units_skew.isRadial()) { //deg or rad
815 double angleX = _scalar_skew_horizontal.getValue("rad");
816 double angleY = _scalar_skew_vertical.getValue("rad");
817 double skewX = tan(-angleX);
818 double skewY = tan(angleY);
819 sp_selection_skew_relative(selection, *center, skewX, skewY);
820 } else { // absolute displacement
821 double skewX = _scalar_skew_horizontal.getValue("px");
822 double skewY = _scalar_skew_vertical.getValue("px");
823 sp_selection_skew_relative(selection, *center, skewX/height, skewY/width);
824 }
825 }
826 }
828 sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
829 _("Skew"));
830 }
833 void
834 Transformation::applyPageTransform(Inkscape::Selection *selection)
835 {
836 double a = _scalar_transform_a.getValue();
837 double b = _scalar_transform_b.getValue();
838 double c = _scalar_transform_c.getValue();
839 double d = _scalar_transform_d.getValue();
840 double e = _scalar_transform_e.getValue();
841 double f = _scalar_transform_f.getValue();
843 Geom::Matrix displayed(a, b, c, d, e, f);
845 if (_check_replace_matrix.get_active()) {
846 for (GSList const *l = selection->itemList(); l != NULL; l = l->next) {
847 SPItem *item = SP_ITEM(l->data);
848 sp_item_set_item_transform(item, displayed);
849 SP_OBJECT(item)->updateRepr();
850 }
851 } else {
852 sp_selection_apply_affine(selection, displayed); // post-multiply each object's transform
853 }
855 sp_document_done(sp_desktop_document(selection->desktop()), SP_VERB_DIALOG_TRANSFORM,
856 _("Edit transformation matrix"));
857 }
863 /*########################################################################
864 # V A L U E - C H A N G E D C A L L B A C K S
865 ########################################################################*/
867 void
868 Transformation::onMoveValueChanged()
869 {
870 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
871 }
873 void
874 Transformation::onMoveRelativeToggled()
875 {
876 Inkscape::Selection *selection = _getSelection();
878 if (!selection || selection->isEmpty())
879 return;
881 double x = _scalar_move_horizontal.getValue("px");
882 double y = _scalar_move_vertical.getValue("px");
884 double conversion = _units_move.getConversion("px");
886 //g_message("onMoveRelativeToggled: %f, %f px\n", x, y);
888 Geom::OptRect bbox = selection->bounds();
890 if (bbox) {
891 if (_check_move_relative.get_active()) {
892 // From absolute to relative
893 _scalar_move_horizontal.setValue((x - bbox->min()[Geom::X]) / conversion);
894 _scalar_move_vertical.setValue(( y - bbox->min()[Geom::Y]) / conversion);
895 } else {
896 // From relative to absolute
897 _scalar_move_horizontal.setValue((bbox->min()[Geom::X] + x) / conversion);
898 _scalar_move_vertical.setValue(( bbox->min()[Geom::Y] + y) / conversion);
899 }
900 }
902 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
903 }
905 void
906 Transformation::onScaleXValueChanged()
907 {
908 if (_scalar_scale_horizontal.setProgrammatically) {
909 _scalar_scale_horizontal.setProgrammatically = false;
910 return;
911 }
913 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
915 if (_check_scale_proportional.get_active()) {
916 if (!_units_scale.isAbsolute()) { // percentage, just copy over
917 _scalar_scale_vertical.setValue(_scalar_scale_horizontal.getValue("%"));
918 } else {
919 double scaleXPercentage = _scalar_scale_horizontal.getAsPercentage();
920 _scalar_scale_vertical.setFromPercentage (scaleXPercentage);
921 }
922 }
923 }
925 void
926 Transformation::onScaleYValueChanged()
927 {
928 if (_scalar_scale_vertical.setProgrammatically) {
929 _scalar_scale_vertical.setProgrammatically = false;
930 return;
931 }
933 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
935 if (_check_scale_proportional.get_active()) {
936 if (!_units_scale.isAbsolute()) { // percentage, just copy over
937 _scalar_scale_horizontal.setValue(_scalar_scale_vertical.getValue("%"));
938 } else {
939 double scaleYPercentage = _scalar_scale_vertical.getAsPercentage();
940 _scalar_scale_horizontal.setFromPercentage (scaleYPercentage);
941 }
942 }
943 }
945 void
946 Transformation::onRotateValueChanged()
947 {
948 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
949 }
951 void
952 Transformation::onSkewValueChanged()
953 {
954 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
955 }
957 void
958 Transformation::onTransformValueChanged()
959 {
961 /*
962 double a = _scalar_transform_a.getValue();
963 double b = _scalar_transform_b.getValue();
964 double c = _scalar_transform_c.getValue();
965 double d = _scalar_transform_d.getValue();
966 double e = _scalar_transform_e.getValue();
967 double f = _scalar_transform_f.getValue();
969 //g_message("onTransformValueChanged: (%f, %f, %f, %f, %f, %f)\n",
970 // a, b, c, d, e ,f);
971 */
973 setResponseSensitive(Gtk::RESPONSE_APPLY, true);
974 }
976 void
977 Transformation::onReplaceMatrixToggled()
978 {
979 Inkscape::Selection *selection = _getSelection();
981 if (!selection || selection->isEmpty())
982 return;
984 double a = _scalar_transform_a.getValue();
985 double b = _scalar_transform_b.getValue();
986 double c = _scalar_transform_c.getValue();
987 double d = _scalar_transform_d.getValue();
988 double e = _scalar_transform_e.getValue();
989 double f = _scalar_transform_f.getValue();
991 Geom::Matrix displayed (a, b, c, d, e, f);
992 Geom::Matrix current = SP_ITEM(selection->itemList()->data)->transform; // take from the first item in selection
994 Geom::Matrix new_displayed;
995 if (_check_replace_matrix.get_active()) {
996 new_displayed = current;
997 } else {
998 new_displayed = current.inverse() * displayed;
999 }
1001 _scalar_transform_a.setValue(new_displayed[0]);
1002 _scalar_transform_b.setValue(new_displayed[1]);
1003 _scalar_transform_c.setValue(new_displayed[2]);
1004 _scalar_transform_d.setValue(new_displayed[3]);
1005 _scalar_transform_e.setValue(new_displayed[4]);
1006 _scalar_transform_f.setValue(new_displayed[5]);
1007 }
1009 void
1010 Transformation::onScaleProportionalToggled()
1011 {
1012 onScaleXValueChanged();
1013 }
1016 void
1017 Transformation::onClear()
1018 {
1019 int const page = _notebook.get_current_page();
1021 switch (page) {
1022 case PAGE_MOVE: {
1023 Inkscape::Selection *selection = _getSelection();
1024 if (!selection || selection->isEmpty() || _check_move_relative.get_active()) {
1025 _scalar_move_horizontal.setValue(0);
1026 _scalar_move_vertical.setValue(0);
1027 } else {
1028 Geom::OptRect bbox = selection->bounds();
1029 if (bbox) {
1030 _scalar_move_horizontal.setValue(bbox->min()[Geom::X], "px");
1031 _scalar_move_vertical.setValue(bbox->min()[Geom::Y], "px");
1032 }
1033 }
1034 break;
1035 }
1036 case PAGE_ROTATE: {
1037 _scalar_rotate.setValue(0);
1038 break;
1039 }
1040 case PAGE_SCALE: {
1041 _scalar_scale_horizontal.setValue(100, "%");
1042 _scalar_scale_vertical.setValue(100, "%");
1043 break;
1044 }
1045 case PAGE_SKEW: {
1046 _scalar_skew_horizontal.setValue(0);
1047 _scalar_skew_vertical.setValue(0);
1048 break;
1049 }
1050 case PAGE_TRANSFORM: {
1051 _scalar_transform_a.setValue(1);
1052 _scalar_transform_b.setValue(0);
1053 _scalar_transform_c.setValue(0);
1054 _scalar_transform_d.setValue(1);
1055 _scalar_transform_e.setValue(0);
1056 _scalar_transform_f.setValue(0);
1057 break;
1058 }
1059 }
1060 }
1062 void
1063 Transformation::onApplySeparatelyToggled()
1064 {
1065 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1066 prefs->setBool("/dialogs/transformation/applyseparately", _check_apply_separately.get_active());
1067 }
1070 } // namespace Dialog
1071 } // namespace UI
1072 } // namespace Inkscape
1074 /*
1075 Local Variables:
1076 mode:c++
1077 c-file-style:"stroustrup"
1078 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1079 indent-tabs-mode:nil
1080 fill-column:99
1081 End:
1082 */
1083 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :