1 #define __SELTRANS_C__
3 /*
4 * Helper object for transforming selected items
5 *
6 * Author:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 * Carl Hetherington <inkscape@carlh.net>
10 *
11 * Copyright (C) 1999-2002 Lauris Kaplinski
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <libnr/nr-matrix-ops.h>
21 #include <libnr/nr-matrix-translate-ops.h>
22 #include <libnr/nr-rotate-ops.h>
23 #include <libnr/nr-scale-ops.h>
24 #include <libnr/nr-translate-matrix-ops.h>
25 #include <libnr/nr-translate-ops.h>
26 #include <gdk/gdkkeysyms.h>
27 #include "document.h"
28 #include "sp-namedview.h"
29 #include "desktop.h"
30 #include "desktop-handles.h"
31 #include "desktop-style.h"
32 #include "knot.h"
33 #include "snap.h"
34 #include "selection.h"
35 #include "select-context.h"
36 #include "sp-item.h"
37 #include "sp-item-transform.h"
38 #include "seltrans-handles.h"
39 #include "seltrans.h"
40 #include "selection-chemistry.h"
41 #include "sp-metrics.h"
42 #include "verbs.h"
43 #include <glibmm/i18n.h>
44 #include "display/sp-ctrlline.h"
45 #include "prefs-utils.h"
46 #include "xml/repr.h"
48 #include "isnan.h" //temp fix. make sure included last
50 static void sp_remove_handles(SPKnot *knot[], gint num);
52 static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data);
53 static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, gpointer data);
54 static void sp_sel_trans_handle_click(SPKnot *knot, guint state, gpointer data);
55 static void sp_sel_trans_handle_new_event(SPKnot *knot, NR::Point *position, guint32 state, gpointer data);
56 static gboolean sp_sel_trans_handle_request(SPKnot *knot, NR::Point *p, guint state, gboolean *data);
58 extern GdkPixbuf *handles[];
60 static gboolean sp_seltrans_handle_event(SPKnot *knot, GdkEvent *event, gpointer)
61 {
62 switch (event->type) {
63 case GDK_MOTION_NOTIFY:
64 break;
65 case GDK_KEY_PRESS:
66 if (get_group0_keyval (&event->key) == GDK_space) {
67 /* stamping mode: both mode(show content and outline) operation with knot */
68 if (!SP_KNOT_IS_GRABBED(knot)) {
69 return FALSE;
70 }
71 SPDesktop *desktop = knot->desktop;
72 Inkscape::SelTrans *seltrans = SP_SELECT_CONTEXT(desktop->event_context)->_seltrans;
73 seltrans->stamp();
74 return TRUE;
75 }
76 break;
77 default:
78 break;
79 }
81 return FALSE;
82 }
84 Inkscape::SelTrans::SelTrans(SPDesktop *desktop) :
85 _desktop(desktop),
86 _selcue(desktop),
87 _state(STATE_SCALE),
88 _show(SHOW_CONTENT),
89 _grabbed(false),
90 _show_handles(true),
91 _box(NR::Nothing()),
92 _chandle(NULL),
93 _stamp_cache(NULL),
94 _message_context(desktop->messageStack())
95 {
96 g_return_if_fail(desktop != NULL);
98 for (int i = 0; i < 8; i++) {
99 _shandle[i] = NULL;
100 _rhandle[i] = NULL;
101 }
103 _updateVolatileState();
104 _current.set_identity();
106 _center_is_set = false; // reread _center from items, or set to bbox midpoint
108 _updateHandles();
110 _selection = sp_desktop_selection(desktop);
112 _norm = sp_canvas_item_new(sp_desktop_controls(desktop),
113 SP_TYPE_CTRL,
114 "anchor", GTK_ANCHOR_CENTER,
115 "mode", SP_CTRL_MODE_COLOR,
116 "shape", SP_CTRL_SHAPE_BITMAP,
117 "size", 13.0,
118 "filled", TRUE,
119 "fill_color", 0x00000000,
120 "stroked", TRUE,
121 "stroke_color", 0x000000a0,
122 "pixbuf", handles[12],
123 NULL);
125 _grip = sp_canvas_item_new(sp_desktop_controls(desktop),
126 SP_TYPE_CTRL,
127 "anchor", GTK_ANCHOR_CENTER,
128 "mode", SP_CTRL_MODE_XOR,
129 "shape", SP_CTRL_SHAPE_CROSS,
130 "size", 7.0,
131 "filled", TRUE,
132 "fill_color", 0xffffff7f,
133 "stroked", TRUE,
134 "stroke_color", 0xffffffff,
135 "pixbuf", handles[12],
136 NULL);
138 sp_canvas_item_hide(_grip);
139 sp_canvas_item_hide(_norm);
141 for (int i = 0; i < 4; i++) {
142 _l[i] = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
143 sp_canvas_item_hide(_l[i]);
144 }
146 _sel_changed_connection = _selection->connectChanged(
147 sigc::mem_fun(*this, &Inkscape::SelTrans::_selChanged)
148 );
150 _sel_modified_connection = _selection->connectModified(
151 sigc::mem_fun(*this, &Inkscape::SelTrans::_selModified)
152 );
153 }
155 Inkscape::SelTrans::~SelTrans()
156 {
157 _sel_changed_connection.disconnect();
158 _sel_modified_connection.disconnect();
160 for (unsigned int i = 0; i < 8; i++) {
161 if (_shandle[i]) {
162 g_object_unref(G_OBJECT(_shandle[i]));
163 _shandle[i] = NULL;
164 }
165 if (_rhandle[i]) {
166 g_object_unref(G_OBJECT(_rhandle[i]));
167 _rhandle[i] = NULL;
168 }
169 }
170 if (_chandle) {
171 g_object_unref(G_OBJECT(_chandle));
172 _chandle = NULL;
173 }
175 if (_norm) {
176 gtk_object_destroy(GTK_OBJECT(_norm));
177 _norm = NULL;
178 }
179 if (_grip) {
180 gtk_object_destroy(GTK_OBJECT(_grip));
181 _grip = NULL;
182 }
183 for (int i = 0; i < 4; i++) {
184 if (_l[i]) {
185 gtk_object_destroy(GTK_OBJECT(_l[i]));
186 _l[i] = NULL;
187 }
188 }
190 for (unsigned i = 0; i < _items.size(); i++) {
191 sp_object_unref(SP_OBJECT(_items[i].first), NULL);
192 }
194 _items.clear();
195 _items_centers.clear();
196 }
198 void Inkscape::SelTrans::resetState()
199 {
200 _state = STATE_SCALE;
201 }
203 void Inkscape::SelTrans::increaseState()
204 {
205 if (_state == STATE_SCALE) {
206 _state = STATE_ROTATE;
207 } else {
208 _state = STATE_SCALE;
209 }
211 _center_is_set = true; // no need to reread center
213 _updateHandles();
214 }
216 void Inkscape::SelTrans::setCenter(NR::Point const &p)
217 {
218 _center = p;
219 _center_is_set = true;
221 // Write the new center position into all selected items
222 for (GSList const *l = _desktop->selection->itemList(); l; l = l->next) {
223 SPItem *it = (SPItem*)SP_OBJECT(l->data);
224 it->setCenter(p);
225 // only set the value; updating repr and document_done will be done once, on ungrab
226 }
228 _updateHandles();
229 }
231 void Inkscape::SelTrans::grab(NR::Point const &p, gdouble x, gdouble y, bool show_handles)
232 {
233 Inkscape::Selection *selection = sp_desktop_selection(_desktop);
235 g_return_if_fail(!_grabbed);
237 _grabbed = true;
238 _show_handles = show_handles;
239 _updateVolatileState();
240 _current.set_identity();
242 _changed = false;
244 if (_empty) {
245 return;
246 }
248 for (GSList const *l = selection->itemList(); l; l = l->next) {
249 SPItem *it = (SPItem*)sp_object_ref(SP_OBJECT(l->data), NULL);
250 _items.push_back(std::pair<SPItem *, NR::Matrix>(it, sp_item_i2d_affine(it)));
251 _items_centers.push_back(std::pair<SPItem *, NR::Point>(it, it->getCenter())); // for content-dragging, we need to remember original centers
252 }
254 _current.set_identity();
256 _point = p;
258 _snap_points = selection->getSnapPointsConvexHull();
259 _box = selection->bounds();
260 _bbox_points.clear();
261 if (_box) {
262 for ( unsigned i = 0 ; i < 4 ; i++ ) {
263 _bbox_points.push_back(_box->corner(i));
264 }
265 }
267 gchar const *scale_origin = prefs_get_string_attribute("tools.select", "scale_origin");
268 bool const origin_on_bbox = (scale_origin == NULL || !strcmp(scale_origin, "bbox"));
271 /*Snapping will be to either nodes or to boundingbox-cornes; each will require its own origin, which
272 is only slightly different from the other. When we would use an origin at one of the nodes while
273 trying to snap the boundingbox, all four points of the boundingbox would be moving (e.g. during stretching),
274 and would therefore also be snapping (which is bad). This leads to bugs similar to #1540195, in which
275 a box is caught between to guides. To solve this, we need two different points: _opposite_for_snappoints and
276 _opposite_for_boundingbox
277 */
279 if (_box) {
280 _opposite_for_bboxpoints = _box->min() + _box->dimensions() * NR::scale(1-x, 1-y);
282 NR::Rect op_box = *_box;
283 // FIXME: should be using ConvexHull here
284 if ( _snap_points.empty() == false) {
285 std::vector<NR::Point>::iterator i = _snap_points.begin();
286 op_box = NR::Rect(*i, *i);
287 i++;
288 while (i != _snap_points.end()) {
289 op_box.expandTo(*i);
290 i++;
291 }
292 }
293 _opposite_for_snappoints = ( op_box.min() + ( op_box.dimensions() * NR::scale(1-x, 1-y) ) );
294 //until we can kick out the old _opposite, and use _opposite_for_bboxpoints or _opposite_for_snappoints everywhere
295 //keep the old behavior for _opposite:
296 _opposite = origin_on_bbox ? _opposite_for_bboxpoints : _opposite_for_snappoints;
297 //FIXME: get rid of _opposite. Requires different handling of preferences, see Bulia Byak's comment for bug #1540195
298 }
300 if ((x != -1) && (y != -1)) {
301 sp_canvas_item_show(_norm);
302 sp_canvas_item_show(_grip);
303 }
305 if (_show == SHOW_OUTLINE) {
306 for (int i = 0; i < 4; i++)
307 sp_canvas_item_show(_l[i]);
308 }
310 _updateHandles();
311 g_return_if_fail(_stamp_cache == NULL);
312 }
314 void Inkscape::SelTrans::transform(NR::Matrix const &rel_affine, NR::Point const &norm)
315 {
316 g_return_if_fail(_grabbed);
317 g_return_if_fail(!_empty);
319 NR::Matrix const affine( NR::translate(-norm) * rel_affine * NR::translate(norm) );
321 if (_show == SHOW_CONTENT) {
322 // update the content
323 for (unsigned i = 0; i < _items.size(); i++) {
324 SPItem &item = *_items[i].first;
325 NR::Matrix const &prev_transform = _items[i].second;
326 sp_item_set_i2d_affine(&item, prev_transform * affine);
327 }
328 } else {
329 if (_box) {
330 NR::Point p[4];
331 /* update the outline */
332 for (unsigned i = 0 ; i < 4 ; i++) {
333 p[i] = _box->corner(i) * affine;
334 }
335 for (unsigned i = 0 ; i < 4 ; i++) {
336 sp_ctrlline_set_coords(SP_CTRLLINE(_l[i]), p[i], p[(i+1)%4]);
337 }
338 }
339 }
341 _current = affine;
342 _changed = true;
343 _updateHandles();
344 }
346 void Inkscape::SelTrans::ungrab()
347 {
348 g_return_if_fail(_grabbed);
349 _grabbed = false;
350 _show_handles = true;
352 Inkscape::Selection *selection = sp_desktop_selection(_desktop);
353 _updateVolatileState();
355 for (unsigned i = 0; i < _items.size(); i++) {
356 sp_object_unref(SP_OBJECT(_items[i].first), NULL);
357 }
359 sp_canvas_item_hide(_norm);
360 sp_canvas_item_hide(_grip);
362 if (_show == SHOW_OUTLINE) {
363 for (int i = 0; i < 4; i++)
364 sp_canvas_item_hide(_l[i]);
365 }
367 if (_stamp_cache) {
368 g_slist_free(_stamp_cache);
369 _stamp_cache = NULL;
370 }
372 _message_context.clear();
374 if (!_empty && _changed) {
375 sp_selection_apply_affine(selection, _current, (_show == SHOW_OUTLINE)? true : false);
376 if (_center) {
377 *_center *= _current;
378 _center_is_set = true;
379 }
381 // If dragging showed content live, sp_selection_apply_affine cannot change the centers
382 // appropriately - it does not know the original positions of the centers (all objects already have
383 // the new bboxes). So we need to reset the centers from our saved array.
384 if (_show != SHOW_OUTLINE && !_current.is_translation()) {
385 for (unsigned i = 0; i < _items_centers.size(); i++) {
386 SPItem *currentItem = _items_centers[i].first;
387 if (currentItem->isCenterSet()) { // only if it's already set
388 currentItem->setCenter (_items_centers[i].second * _current);
389 SP_OBJECT(currentItem)->updateRepr();
390 }
391 }
392 }
394 _items.clear();
395 _items_centers.clear();
397 if (_current.is_translation()) {
398 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
399 _("Move"));
400 } else if (_current.is_scale()) {
401 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
402 _("Scale"));
403 } else if (_current.is_rotation()) {
404 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
405 _("Rotate"));
406 } else {
407 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
408 _("Skew"));
409 }
411 } else {
413 if (_center_is_set) {
414 // we were dragging center; update reprs and commit undoable action
415 for (GSList const *l = _desktop->selection->itemList(); l; l = l->next) {
416 SPItem *it = (SPItem*)SP_OBJECT(l->data);
417 SP_OBJECT(it)->updateRepr();
418 }
419 sp_document_done (sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
420 _("Set center"));
421 }
423 _items.clear();
424 _items_centers.clear();
425 _updateHandles();
426 }
427 }
429 /* fixme: This is really bad, as we compare positions for each stamp (Lauris) */
430 /* fixme: IMHO the best way to keep sort cache would be to implement timestamping at last */
432 void Inkscape::SelTrans::stamp()
433 {
434 Inkscape::Selection *selection = sp_desktop_selection(_desktop);
436 bool fixup = !_grabbed;
437 if ( fixup && _stamp_cache ) {
438 // TODO - give a proper fix. Simple temproary work-around for the grab() issue
439 g_slist_free(_stamp_cache);
440 _stamp_cache = NULL;
441 }
443 /* stamping mode */
444 if (!_empty) {
445 GSList *l;
446 if (_stamp_cache) {
447 l = _stamp_cache;
448 } else {
449 /* Build cache */
450 l = g_slist_copy((GSList *) selection->itemList());
451 l = g_slist_sort(l, (GCompareFunc) sp_object_compare_position);
452 _stamp_cache = l;
453 }
455 while (l) {
456 SPItem *original_item = SP_ITEM(l->data);
457 Inkscape::XML::Node *original_repr = SP_OBJECT_REPR(original_item);
459 // remember the position of the item
460 gint pos = original_repr->position();
461 // remember parent
462 Inkscape::XML::Node *parent = sp_repr_parent(original_repr);
464 Inkscape::XML::Node *copy_repr = original_repr->duplicate(parent->document());
466 // add the new repr to the parent
467 parent->appendChild(copy_repr);
468 // move to the saved position
469 copy_repr->setPosition(pos > 0 ? pos : 0);
471 SPItem *copy_item = (SPItem *) sp_desktop_document(_desktop)->getObjectByRepr(copy_repr);
473 NR::Matrix const *new_affine;
474 if (_show == SHOW_OUTLINE) {
475 NR::Matrix const i2d(sp_item_i2d_affine(original_item));
476 NR::Matrix const i2dnew( i2d * _current );
477 sp_item_set_i2d_affine(copy_item, i2dnew);
478 new_affine = ©_item->transform;
479 } else {
480 new_affine = &original_item->transform;
481 }
483 sp_item_write_transform(copy_item, copy_repr, *new_affine);
485 if ( copy_item->isCenterSet() && _center ) {
486 copy_item->setCenter(*_center * _current);
487 }
489 Inkscape::GC::release(copy_repr);
490 l = l->next;
491 }
492 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
493 _("Stamp"));
494 }
496 if ( fixup && _stamp_cache ) {
497 // TODO - give a proper fix. Simple temproary work-around for the grab() issue
498 g_slist_free(_stamp_cache);
499 _stamp_cache = NULL;
500 }
501 }
503 void Inkscape::SelTrans::_updateHandles()
504 {
505 if ( !_show_handles || _empty )
506 {
507 sp_remove_handles(_shandle, 8);
508 sp_remove_handles(_rhandle, 8);
509 sp_remove_handles(&_chandle, 1);
510 return;
511 }
513 // center handle
514 if ( _chandle == NULL ) {
515 _chandle = sp_knot_new(_desktop, _("<b>Center</b> of rotation and skewing: drag to reposition; scaling with Shift also uses this center"));
517 _chandle->setShape (SP_CTRL_SHAPE_BITMAP);
518 _chandle->setSize (13);
519 _chandle->setAnchor (handle_center.anchor);
520 _chandle->setMode (SP_CTRL_MODE_XOR);
521 _chandle->setFill(0x00000000, 0x00000000, 0x00000000);
522 _chandle->setStroke(0x000000ff, 0xff0000b0, 0xff0000b0);
523 _chandle->setPixbuf(handles[handle_center.control]);
524 sp_knot_update_ctrl(_chandle);
526 g_signal_connect(G_OBJECT(_chandle), "request",
527 G_CALLBACK(sp_sel_trans_handle_request), (gpointer) &handle_center);
528 g_signal_connect(G_OBJECT(_chandle), "moved",
529 G_CALLBACK(sp_sel_trans_handle_new_event), (gpointer) &handle_center);
530 g_signal_connect(G_OBJECT(_chandle), "grabbed",
531 G_CALLBACK(sp_sel_trans_handle_grab), (gpointer) &handle_center);
532 g_signal_connect(G_OBJECT(_chandle), "ungrabbed",
533 G_CALLBACK(sp_sel_trans_handle_ungrab), (gpointer) &handle_center);
534 g_signal_connect(G_OBJECT(_chandle), "clicked",
535 G_CALLBACK(sp_sel_trans_handle_click), (gpointer) &handle_center);
536 }
538 sp_remove_handles(&_chandle, 1);
539 if ( _state == STATE_SCALE ) {
540 sp_remove_handles(_rhandle, 8);
541 _showHandles(_shandle, handles_scale, 8,
542 _("<b>Squeeze or stretch</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"),
543 _("<b>Scale</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"));
544 } else {
545 sp_remove_handles(_shandle, 8);
546 _showHandles(_rhandle, handles_rotate, 8,
547 _("<b>Skew</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to skew around the opposite side"),
548 _("<b>Rotate</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to rotate around the opposite corner"));
549 }
551 if (!_center_is_set) {
552 _center = _desktop->selection->center();
553 _center_is_set = true;
554 }
556 if ( _state == STATE_SCALE || !_center ) {
557 sp_knot_hide(_chandle);
558 } else {
559 sp_knot_show(_chandle);
560 sp_knot_moveto(_chandle, &*_center);
561 }
562 }
564 void Inkscape::SelTrans::_updateVolatileState()
565 {
566 Inkscape::Selection *selection = sp_desktop_selection(_desktop);
567 _empty = selection->isEmpty();
569 if (_empty) {
570 return;
571 }
573 _box = selection->bounds();
574 if (!_box) {
575 _empty = true;
576 return;
577 }
579 _strokewidth = stroke_average_width (selection->itemList());
580 }
582 static void sp_remove_handles(SPKnot *knot[], gint num)
583 {
584 for (int i = 0; i < num; i++) {
585 if (knot[i] != NULL) {
586 sp_knot_hide(knot[i]);
587 }
588 }
589 }
591 void Inkscape::SelTrans::_showHandles(SPKnot *knot[], SPSelTransHandle const handle[], gint num,
592 gchar const *even_tip, gchar const *odd_tip)
593 {
594 g_return_if_fail( !_empty );
596 for (int i = 0; i < num; i++) {
597 if (knot[i] == NULL) {
598 knot[i] = sp_knot_new(_desktop, i % 2 ? even_tip : odd_tip);
600 knot[i]->setShape (SP_CTRL_SHAPE_BITMAP);
601 knot[i]->setSize (13);
602 knot[i]->setAnchor (handle[i].anchor);
603 knot[i]->setMode (SP_CTRL_MODE_XOR);
604 knot[i]->setFill(0x000000ff, 0x00ff6600, 0x00ff6600); // inversion, green, green
605 knot[i]->setStroke(0x000000ff, 0x000000ff, 0x000000ff); // inversion
606 knot[i]->setPixbuf(handles[handle[i].control]);
607 sp_knot_update_ctrl(knot[i]);
609 g_signal_connect(G_OBJECT(knot[i]), "request",
610 G_CALLBACK(sp_sel_trans_handle_request), (gpointer) &handle[i]);
611 g_signal_connect(G_OBJECT(knot[i]), "moved",
612 G_CALLBACK(sp_sel_trans_handle_new_event), (gpointer) &handle[i]);
613 g_signal_connect(G_OBJECT(knot[i]), "grabbed",
614 G_CALLBACK(sp_sel_trans_handle_grab), (gpointer) &handle[i]);
615 g_signal_connect(G_OBJECT(knot[i]), "ungrabbed",
616 G_CALLBACK(sp_sel_trans_handle_ungrab), (gpointer) &handle[i]);
617 g_signal_connect(G_OBJECT(knot[i]), "event", G_CALLBACK(sp_seltrans_handle_event), (gpointer) &handle[i]);
618 }
619 sp_knot_show(knot[i]);
621 NR::Point const handle_pt(handle[i].x, handle[i].y);
622 // shouldn't have nullary bbox, but knots
623 g_assert(_box);
624 NR::Point p( _box->min()
625 + ( _box->dimensions()
626 * NR::scale(handle_pt) ) );
628 sp_knot_moveto(knot[i], &p);
629 }
630 }
632 static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data)
633 {
634 SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleGrab(
635 knot, state, *(SPSelTransHandle const *) data
636 );
637 }
639 static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, gpointer data)
640 {
641 SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->ungrab();
642 }
644 static void sp_sel_trans_handle_new_event(SPKnot *knot, NR::Point *position, guint state, gpointer data)
645 {
646 SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleNewEvent(
647 knot, position, state, *(SPSelTransHandle const *) data
648 );
649 }
651 static gboolean sp_sel_trans_handle_request(SPKnot *knot, NR::Point *position, guint state, gboolean *data)
652 {
653 return SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleRequest(
654 knot, position, state, *(SPSelTransHandle const *) data
655 );
656 }
658 static void sp_sel_trans_handle_click(SPKnot *knot, guint state, gpointer data)
659 {
660 SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleClick(
661 knot, state, *(SPSelTransHandle const *) data
662 );
663 }
665 void Inkscape::SelTrans::handleClick(SPKnot *knot, guint state, SPSelTransHandle const &handle)
666 {
667 switch (handle.anchor) {
668 case GTK_ANCHOR_CENTER:
669 if (state & GDK_SHIFT_MASK) {
670 // Unset the center position for all selected items
671 for (GSList const *l = _desktop->selection->itemList(); l; l = l->next) {
672 SPItem *it = (SPItem*)(SP_OBJECT(l->data));
673 it->unsetCenter();
674 SP_OBJECT(it)->updateRepr();
675 _center_is_set = false; // center has changed
676 _updateHandles();
677 }
678 sp_document_done (sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
679 _("Reset center"));
680 }
681 break;
682 default:
683 break;
684 }
685 }
687 void Inkscape::SelTrans::handleGrab(SPKnot *knot, guint state, SPSelTransHandle const &handle)
688 {
689 switch (handle.anchor) {
690 case GTK_ANCHOR_CENTER:
691 g_object_set(G_OBJECT(_grip),
692 "shape", SP_CTRL_SHAPE_BITMAP,
693 "size", 13.0,
694 NULL);
695 sp_canvas_item_show(_grip);
696 break;
697 default:
698 g_object_set(G_OBJECT(_grip),
699 "shape", SP_CTRL_SHAPE_CROSS,
700 "size", 7.0,
701 NULL);
702 sp_canvas_item_show(_norm);
703 sp_canvas_item_show(_grip);
705 break;
706 }
708 grab(sp_knot_position(knot), handle.x, handle.y, FALSE);
709 }
712 void Inkscape::SelTrans::handleNewEvent(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
713 {
714 if (!SP_KNOT_IS_GRABBED(knot)) {
715 return;
716 }
718 // in case items have been unhooked from the document, don't
719 // try to continue processing events for them.
720 for (unsigned int i = 0; i < _items.size(); i++) {
721 if (!SP_OBJECT_DOCUMENT(SP_OBJECT(_items[i].first)) ) {
722 return;
723 }
724 }
726 handle.action(this, handle, *position, state);
727 }
730 gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
731 {
732 if (!SP_KNOT_IS_GRABBED(knot)) {
733 return TRUE;
734 }
736 knot->desktop->setPosition(*position);
738 if (state & GDK_MOD1_MASK) {
739 *position = _point + ( *position - _point ) / 10;
740 }
742 if ((!(state & GDK_SHIFT_MASK) == !(_state == STATE_ROTATE)) && (&handle != &handle_center)) {
743 _origin = _opposite;
744 _origin_for_bboxpoints = _opposite_for_bboxpoints;
745 _origin_for_snappoints = _opposite_for_snappoints;
746 } else if (_center) {
747 _origin = *_center;
748 _origin_for_bboxpoints = *_center;
749 _origin_for_snappoints = *_center;
750 } else {
751 // FIXME
752 return TRUE;
753 }
754 if (handle.request(this, handle, *position, state)) {
755 sp_knot_set_position(knot, position, state);
756 SP_CTRL(_grip)->moveto(*position);
757 SP_CTRL(_norm)->moveto(_origin);
758 }
760 return TRUE;
761 }
764 void Inkscape::SelTrans::_selChanged(Inkscape::Selection *selection)
765 {
766 if (!_grabbed) {
767 _updateVolatileState();
768 _current.set_identity();
769 _center_is_set = false; // center(s) may have changed
770 _updateHandles();
771 }
772 }
774 void Inkscape::SelTrans::_selModified(Inkscape::Selection *selection, guint flags)
775 {
776 if (!_grabbed) {
777 _updateVolatileState();
778 _current.set_identity();
780 // reset internal flag
781 _changed = false;
783 _center_is_set = false; // center(s) may have changed
785 _updateHandles();
786 }
787 }
789 /*
790 * handlers for handle move-request
791 */
793 /** Returns -1 or 1 according to the sign of x. Returns 1 for 0 and NaN. */
794 static double sign(double const x)
795 {
796 return ( x < 0
797 ? -1
798 : 1 );
799 }
801 gboolean sp_sel_trans_scale_request(Inkscape::SelTrans *seltrans,
802 SPSelTransHandle const &, NR::Point &pt, guint state)
803 {
804 return seltrans->scaleRequest(pt, state);
805 }
807 gboolean sp_sel_trans_stretch_request(Inkscape::SelTrans *seltrans,
808 SPSelTransHandle const &handle, NR::Point &pt, guint state)
809 {
810 return seltrans->stretchRequest(handle, pt, state);
811 }
813 gboolean sp_sel_trans_skew_request(Inkscape::SelTrans *seltrans,
814 SPSelTransHandle const &handle, NR::Point &pt, guint state)
815 {
816 return seltrans->skewRequest(handle, pt, state);
817 }
819 gboolean sp_sel_trans_rotate_request(Inkscape::SelTrans *seltrans,
820 SPSelTransHandle const &, NR::Point &pt, guint state)
821 {
822 return seltrans->rotateRequest(pt, state);
823 }
825 gboolean sp_sel_trans_center_request(Inkscape::SelTrans *seltrans,
826 SPSelTransHandle const &, NR::Point &pt, guint state)
827 {
828 return seltrans->centerRequest(pt, state);
829 }
831 gboolean Inkscape::SelTrans::scaleRequest(NR::Point &pt, guint state)
832 {
833 using NR::X;
834 using NR::Y;
836 NR::Point d = _point - _origin;
837 NR::scale s(0, 0);
839 /* Work out the new scale factors `s' */
840 for ( unsigned int i = 0 ; i < 2 ; i++ ) {
841 if ( fabs(d[i]) > 0.001 ) {
842 s[i] = ( pt[i] - _origin[i] ) / d[i];
843 if ( fabs(s[i]) < 1e-9 ) {
844 s[i] = 1e-9;
845 }
846 }
847 }
849 SnapManager const &m = _desktop->namedview->snap_manager;
851 /* Get a STL list of the selected items.
852 ** FIXME: this should probably be done by Inkscape::Selection.
853 */
854 std::list<SPItem const*> it;
855 for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) {
856 it.push_back(reinterpret_cast<SPItem*>(i->data));
857 }
859 if ((state & GDK_CONTROL_MASK) || _desktop->isToolboxButtonActive ("lock")) {
860 /* Scale is locked to a 1:1 aspect ratio, so that s[X] must be made to equal s[Y].
861 ** To do this, we snap along a suitable constraint vector from the origin.
862 */
864 // The inclination of the constraint vector is calculated from the aspect ratio
865 NR::Point bbox_dim = _box->dimensions();
866 double const aspect_ratio = bbox_dim[1] / bbox_dim[0]; // = height / width
868 // Determine direction of the constraint vector
869 NR::Point const cv = NR::Point(
870 pt[NR::X] > _origin[NR::X] ? 1 : -1,
871 pt[NR::Y] > _origin[NR::Y] ? aspect_ratio : -aspect_ratio
872 );
874 std::pair<NR::scale, bool> bb = m.constrainedSnapScale(Snapper::BBOX_POINT,
875 _bbox_points,
876 it,
877 Snapper::ConstraintLine(_origin_for_bboxpoints, cv),
878 s,
879 _origin_for_bboxpoints);
881 std::pair<NR::scale, bool> sn = m.constrainedSnapScale(Snapper::SNAP_POINT,
882 _snap_points,
883 it,
884 Snapper::ConstraintLine(_origin_for_snappoints, cv),
885 s,
886 _origin_for_snappoints);
888 if (bb.second == false && sn.second == false) {
890 /* We didn't snap, so just lock aspect ratio */
891 if (fabs(s[NR::X]) > fabs(s[NR::Y])) {
892 s[NR::X] = fabs(s[NR::Y]) * sign(s[NR::X]);
893 } else {
894 s[NR::Y] = fabs(s[NR::X]) * sign(s[NR::Y]);
895 }
897 } else {
899 /* Choose the smaller difference in scale. Since s[X] == s[Y] we can
900 ** just compare difference in s[X].
901 */
902 double const bd = bb.second ? fabs(bb.first[NR::X] - s[NR::X]) : NR_HUGE;
903 double const sd = sn.second ? fabs(sn.first[NR::X] - s[NR::X]) : NR_HUGE;
904 s = (bd < sd) ? bb.first : sn.first;
905 }
907 } else {
908 /* Scale aspect ratio is unlocked */
910 std::pair<NR::scale, bool> bb = m.freeSnapScale(Snapper::BBOX_POINT,
911 _bbox_points,
912 it,
913 s,
914 _origin_for_bboxpoints);
915 std::pair<NR::scale, bool> sn = m.freeSnapScale(Snapper::SNAP_POINT,
916 _snap_points,
917 it,
918 s,
919 _origin_for_snappoints);
921 /* Pick the snap that puts us closest to the original scale */
922 NR::Coord bd = bb.second ?
923 fabs(NR::L2(NR::Point(bb.first[NR::X], bb.first[NR::Y])) -
924 NR::L2(NR::Point(s[NR::X], s[NR::Y])))
925 : NR_HUGE;
926 NR::Coord sd = sn.second ?
927 fabs(NR::L2(NR::Point(sn.first[NR::X], sn.first[NR::Y])) -
928 NR::L2(NR::Point(s[NR::X], s[NR::Y])))
929 : NR_HUGE;
930 s = (bd < sd) ? bb.first : sn.first;
931 }
933 /* Update the knot position */
934 pt = ( _point - _origin ) * s + _origin;
936 /* Status text */
937 _message_context.setF(Inkscape::NORMAL_MESSAGE,
938 _("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>Ctrl</b> to lock ratio"),
939 100 * s[NR::X], 100 * s[NR::Y]);
941 return TRUE;
942 }
944 gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
945 {
946 using NR::X;
947 using NR::Y;
949 NR::Dim2 axis, perp;
951 switch (handle.cursor) {
952 case GDK_TOP_SIDE:
953 case GDK_BOTTOM_SIDE:
954 axis = NR::Y;
955 perp = NR::X;
956 break;
957 case GDK_LEFT_SIDE:
958 case GDK_RIGHT_SIDE:
959 axis = NR::X;
960 perp = NR::Y;
961 break;
962 default:
963 g_assert_not_reached();
964 return TRUE;
965 };
967 if ( fabs( _point[axis] - _origin[axis] ) < 1e-15 ) {
968 return FALSE;
969 }
971 NR::scale s(1, 1);
972 s[axis] = ( ( pt[axis] - _origin[axis] )
973 / ( _point[axis] - _origin[axis] ) );
974 if ( fabs(s[axis]) < 1e-15 ) {
975 s[axis] = 1e-15;
976 }
978 /* Get a STL list of the selected items.
979 ** FIXME: this should probably be done by Inkscape::Selection.
980 */
981 std::list<SPItem const*> it;
982 for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) {
983 it.push_back(reinterpret_cast<SPItem*>(i->data));
984 }
986 SnapManager const &m = _desktop->namedview->snap_manager;
988 if ( state & GDK_CONTROL_MASK ) {
989 s[perp] = fabs(s[axis]);
991 std::pair<NR::Coord, bool> const bb = m.freeSnapStretch(
992 Snapper::BBOX_POINT,
993 _bbox_points,
994 it,
995 s[axis],
996 _origin_for_bboxpoints,
997 axis,
998 true);
1000 std::pair<NR::Coord, bool> const sn = m.freeSnapStretch(
1001 Snapper::SNAP_POINT,
1002 _snap_points,
1003 it,
1004 s[axis],
1005 _origin_for_snappoints,
1006 axis,
1007 true);
1009 NR::Coord const bd = bb.second ? fabs(bb.first - s[axis]) : NR_HUGE;
1010 NR::Coord const sd = sn.second ? fabs(sn.first - s[axis]) : NR_HUGE;
1011 NR::Coord const ratio = (bd < sd) ? bb.first : sn.first;
1013 s[axis] = fabs(ratio) * sign(s[axis]);
1014 s[perp] = fabs(s[axis]);
1015 } else {
1017 std::pair<NR::Coord, bool> const bb = m.freeSnapStretch(
1018 Snapper::BBOX_POINT,
1019 _bbox_points,
1020 it,
1021 s[axis],
1022 _origin_for_bboxpoints,
1023 axis,
1024 false);
1026 std::pair<NR::Coord, bool> const sn = m.freeSnapStretch(
1027 Snapper::SNAP_POINT,
1028 _snap_points,
1029 it,
1030 s[axis],
1031 _origin_for_snappoints,
1032 axis,
1033 false);
1035 /* Choose the smaller difference in scale */
1036 NR::Coord const bd = bb.second ? fabs(bb.first - s[axis]) : NR_HUGE;
1037 NR::Coord const sd = sn.second ? fabs(sn.first - s[axis]) : NR_HUGE;
1038 s[axis] = (bd < sd) ? bb.first : sn.first;
1039 s[perp] = 1;
1040 }
1042 pt = ( _point - _origin ) * NR::scale(s) + _origin;
1043 if (isNaN(pt[X] + pt[Y])) {
1044 g_warning("point=(%g, %g), norm=(%g, %g), s=(%g, %g)\n",
1045 _point[X], _point[Y], _origin[X], _origin[Y], s[X], s[Y]);
1046 }
1048 // status text
1049 _message_context.setF(Inkscape::NORMAL_MESSAGE,
1050 _("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>Ctrl</b> to lock ratio"),
1051 100 * s[NR::X], 100 * s[NR::Y]);
1053 return TRUE;
1054 }
1056 gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
1057 {
1058 using NR::X;
1059 using NR::Y;
1061 if (handle.cursor != GDK_SB_V_DOUBLE_ARROW && handle.cursor != GDK_SB_H_DOUBLE_ARROW) {
1062 return FALSE;
1063 }
1065 NR::Dim2 dim_a;
1066 NR::Dim2 dim_b;
1067 if (handle.cursor == GDK_SB_V_DOUBLE_ARROW) {
1068 dim_a = X;
1069 dim_b = Y;
1070 } else {
1071 dim_a = Y;
1072 dim_b = X;
1073 }
1075 double skew[2];
1076 double s[2] = { 1.0, 1.0 };
1078 if (fabs(_point[dim_a] - _origin[dim_a]) < NR_EPSILON) {
1079 return FALSE;
1080 }
1082 skew[dim_a] = ( pt[dim_b] - _point[dim_b] ) / ( _point[dim_a] - _origin[dim_a] );
1084 s[dim_a] = ( pt[dim_a] - _origin[dim_a] ) / ( _point[dim_a] - _origin[dim_a] );
1086 if ( fabs(s[dim_a]) < 1 ) {
1087 s[dim_a] = sign(s[dim_a]);
1088 } else {
1089 s[dim_a] = floor( s[dim_a] + 0.5 );
1090 }
1092 double radians = atan(skew[dim_a] / s[dim_a]);
1094 if (state & GDK_CONTROL_MASK) {
1096 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1098 if (snaps) {
1099 double sections = floor( radians * snaps / M_PI + .5 );
1100 if (fabs(sections) >= snaps / 2) sections = sign(sections) * (snaps / 2 - 1);
1101 radians = ( M_PI / snaps ) * sections;
1102 }
1103 skew[dim_a] = tan(radians) * s[dim_a];
1104 } else {
1105 SnapManager const &m = _desktop->namedview->snap_manager;
1107 std::pair<NR::Coord, bool> bb = m.freeSnapSkew(Inkscape::Snapper::BBOX_POINT,
1108 _bbox_points,
1109 std::list<SPItem const *>(),
1110 skew[dim_a],
1111 _origin_for_bboxpoints,
1112 dim_b);
1114 std::pair<NR::Coord, bool> sn = m.freeSnapSkew(Inkscape::Snapper::SNAP_POINT,
1115 _snap_points,
1116 std::list<SPItem const *>(),
1117 skew[dim_a],
1118 _origin_for_snappoints,
1119 dim_b);
1121 if (bb.second || sn.second) {
1122 /* We snapped something, so change the skew to reflect it */
1123 NR::Coord const bd = bb.second ? bb.first : NR_HUGE;
1124 NR::Coord const sd = sn.second ? sn.first : NR_HUGE;
1125 skew[dim_a] = std::min(bd, sd);
1126 }
1127 }
1129 pt[dim_b] = ( _point[dim_a] - _origin[dim_a] ) * skew[dim_a] + _point[dim_b];
1130 pt[dim_a] = ( _point[dim_a] - _origin[dim_a] ) * s[dim_a] + _origin[dim_a];
1132 /* status text */
1133 double degrees = 180 / M_PI * radians;
1134 if (degrees > 180) degrees -= 360;
1135 if (degrees < -180) degrees += 360;
1137 _message_context.setF(Inkscape::NORMAL_MESSAGE,
1138 // TRANSLATORS: don't modify the first ";"
1139 // (it will NOT be displayed as ";" - only the second one will be)
1140 _("<b>Skew</b>: %0.2f°; with <b>Ctrl</b> to snap angle"),
1141 degrees);
1143 return TRUE;
1144 }
1146 gboolean Inkscape::SelTrans::rotateRequest(NR::Point &pt, guint state)
1147 {
1148 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1150 // rotate affine in rotate
1151 NR::Point const d1 = _point - _origin;
1152 NR::Point const d2 = pt - _origin;
1154 NR::Coord const h1 = NR::L2(d1);
1155 if (h1 < 1e-15) return FALSE;
1156 NR::Point q1 = d1 / h1;
1157 NR::Coord const h2 = NR::L2(d2);
1158 if (fabs(h2) < 1e-15) return FALSE;
1159 NR::Point q2 = d2 / h2;
1161 double radians;
1162 if (state & GDK_CONTROL_MASK) {
1163 /* Have to restrict movement. */
1164 double cos_t = NR::dot(q1, q2);
1165 double sin_t = NR::dot(NR::rot90(q1), q2);
1166 radians = atan2(sin_t, cos_t);
1167 if (snaps) {
1168 radians = ( M_PI / snaps ) * floor( radians * snaps / M_PI + .5 );
1169 }
1170 q1 = NR::Point(1, 0);
1171 q2 = NR::Point(cos(radians), sin(radians));
1172 } else {
1173 radians = atan2(NR::dot(NR::rot90(d1), d2),
1174 NR::dot(d1, d2));
1175 }
1177 NR::rotate const r1(q1);
1178 NR::rotate const r2(q2);
1179 pt = _point * NR::translate(-_origin) * ( r2 / r1 ) * NR::translate(_origin);
1181 /* status text */
1182 double degrees = 180 / M_PI * radians;
1183 if (degrees > 180) degrees -= 360;
1184 if (degrees < -180) degrees += 360;
1186 _message_context.setF(Inkscape::NORMAL_MESSAGE,
1187 // TRANSLATORS: don't modify the first ";"
1188 // (it will NOT be displayed as ";" - only the second one will be)
1189 _("<b>Rotate</b>: %0.2f°; with <b>Ctrl</b> to snap angle"), degrees);
1191 return TRUE;
1192 }
1194 gboolean Inkscape::SelTrans::centerRequest(NR::Point &pt, guint state)
1195 {
1196 using NR::X;
1197 using NR::Y;
1199 SnapManager const &m = _desktop->namedview->snap_manager;
1200 pt = m.freeSnap(Snapper::SNAP_POINT, pt, NULL).getPoint();
1202 if (state & GDK_CONTROL_MASK) {
1203 if ( fabs(_point[X] - pt[X]) > fabs(_point[Y] - pt[Y]) ) {
1204 pt[Y] = _point[Y];
1205 } else {
1206 pt[X] = _point[X];
1207 }
1208 }
1210 if ( !(state & GDK_SHIFT_MASK) && _box ) {
1211 // screen pixels to snap center to bbox
1212 #define SNAP_DIST 5
1213 // FIXME: take from prefs
1214 double snap_dist = SNAP_DIST / _desktop->current_zoom();
1216 for (int i = 0; i < 2; i++) {
1217 if (fabs(pt[i] - _box->min()[i]) < snap_dist) {
1218 pt[i] = _box->min()[i];
1219 }
1220 if (fabs(pt[i] - _box->midpoint()[i]) < snap_dist) {
1221 pt[i] = _box->midpoint()[i];
1222 }
1223 if (fabs(pt[i] - _box->max()[i]) < snap_dist) {
1224 pt[i] = _box->max()[i];
1225 }
1226 }
1227 }
1229 // status text
1230 GString *xs = SP_PX_TO_METRIC_STRING(pt[X], _desktop->namedview->getDefaultMetric());
1231 GString *ys = SP_PX_TO_METRIC_STRING(pt[Y], _desktop->namedview->getDefaultMetric());
1232 _message_context.setF(Inkscape::NORMAL_MESSAGE, _("Move <b>center</b> to %s, %s"), xs->str, ys->str);
1233 g_string_free(xs, FALSE);
1234 g_string_free(ys, FALSE);
1236 return TRUE;
1237 }
1239 /*
1240 * handlers for handle movement
1241 *
1242 */
1244 void sp_sel_trans_stretch(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state)
1245 {
1246 seltrans->stretch(handle, pt, state);
1247 }
1249 void sp_sel_trans_scale(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1250 {
1251 seltrans->scale(pt, state);
1252 }
1254 void sp_sel_trans_skew(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state)
1255 {
1256 seltrans->skew(handle, pt, state);
1257 }
1259 void sp_sel_trans_rotate(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1260 {
1261 seltrans->rotate(pt, state);
1262 }
1264 void Inkscape::SelTrans::stretch(SPSelTransHandle const &handle, NR::Point &pt, guint state)
1265 {
1266 using NR::X;
1267 using NR::Y;
1269 NR::Dim2 dim;
1270 switch (handle.cursor) {
1271 case GDK_LEFT_SIDE:
1272 case GDK_RIGHT_SIDE:
1273 dim = X;
1274 break;
1275 case GDK_TOP_SIDE:
1276 case GDK_BOTTOM_SIDE:
1277 dim = Y;
1278 break;
1279 default:
1280 g_assert_not_reached();
1281 abort();
1282 break;
1283 }
1285 NR::Point const scale_origin(_origin);
1286 double const offset = _point[dim] - scale_origin[dim];
1287 if (!( fabs(offset) >= 1e-15 )) {
1288 return;
1289 }
1290 NR::scale s(1, 1);
1291 s[dim] = ( pt[dim] - scale_origin[dim] ) / offset;
1292 if (isNaN(s[dim])) {
1293 g_warning("s[dim]=%g, pt[dim]=%g, scale_origin[dim]=%g, point[dim]=%g\n",
1294 s[dim], pt[dim], scale_origin[dim], _point[dim]);
1295 }
1296 if (!( fabs(s[dim]) >= 1e-15 )) {
1297 s[dim] = 1e-15;
1298 }
1299 if (state & GDK_CONTROL_MASK) {
1300 /* Preserve aspect ratio, but never flip in the dimension not being edited. */
1301 s[!dim] = fabs(s[dim]);
1302 }
1304 if (!_box) {
1305 return;
1306 }
1308 NR::Point new_bbox_min = _box->min() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin));
1309 NR::Point new_bbox_max = _box->max() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin));
1311 int transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1);
1312 NR::Matrix scaler = get_scale_transform_with_stroke (*_box, _strokewidth, transform_stroke,
1313 new_bbox_min[NR::X], new_bbox_min[NR::Y], new_bbox_max[NR::X], new_bbox_max[NR::Y]);
1315 transform(scaler, NR::Point(0, 0)); // we have already accounted for origin, so pass 0,0
1316 }
1318 void Inkscape::SelTrans::scale(NR::Point &pt, guint state)
1319 {
1320 if (!_box) {
1321 return;
1322 }
1324 NR::Point const offset = _point - _origin;
1326 NR::scale s (1, 1);
1327 for (int i = NR::X; i <= NR::Y; i++) {
1328 if (fabs(offset[i]) > 1e-9)
1329 s[i] = (pt[i] - _origin[i]) / offset[i];
1330 if (fabs(s[i]) < 1e-9)
1331 s[i] = 1e-9;
1332 }
1333 NR::Point new_bbox_min = _box->min() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin));
1334 NR::Point new_bbox_max = _box->max() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin));
1336 int transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1);
1337 NR::Matrix scaler = get_scale_transform_with_stroke (*_box, _strokewidth, transform_stroke,
1338 new_bbox_min[NR::X], new_bbox_min[NR::Y], new_bbox_max[NR::X], new_bbox_max[NR::Y]);
1340 transform(scaler, NR::Point(0, 0)); // we have already accounted for origin, so pass 0,0
1341 }
1343 void Inkscape::SelTrans::skew(SPSelTransHandle const &handle, NR::Point &pt, guint state)
1344 {
1345 NR::Point const offset = _point - _origin;
1347 unsigned dim;
1348 switch (handle.cursor) {
1349 case GDK_SB_H_DOUBLE_ARROW:
1350 dim = NR::Y;
1351 break;
1352 case GDK_SB_V_DOUBLE_ARROW:
1353 dim = NR::X;
1354 break;
1355 default:
1356 g_assert_not_reached();
1357 abort();
1358 break;
1359 }
1360 if (fabs(offset[dim]) < 1e-15) {
1361 return;
1362 }
1363 NR::Matrix skew = NR::identity();
1364 skew[2*dim + dim] = (pt[dim] - _origin[dim]) / offset[dim];
1365 skew[2*dim + (1-dim)] = (pt[1-dim] - _point[1-dim]) / offset[dim];
1366 skew[2*(1-dim) + (dim)] = 0;
1367 skew[2*(1-dim) + (1-dim)] = 1;
1369 for (int i = 0; i < 2; i++) {
1370 if (fabs(skew[3*i]) < 1e-15) {
1371 skew[3*i] = 1e-15;
1372 }
1373 }
1374 transform(skew, _origin);
1375 }
1377 void Inkscape::SelTrans::rotate(NR::Point &pt, guint state)
1378 {
1379 NR::Point const offset = _point - _origin;
1381 NR::Coord const h1 = NR::L2(offset);
1382 if (h1 < 1e-15) {
1383 return;
1384 }
1385 NR::Point const q1 = offset / h1;
1386 NR::Coord const h2 = NR::L2( pt - _origin );
1387 if (h2 < 1e-15) {
1388 return;
1389 }
1390 NR::Point const q2 = (pt - _origin) / h2;
1391 NR::rotate const r1(q1);
1392 NR::rotate const r2(q2);
1394 NR::Matrix rotate( r2 / r1 );
1395 transform(rotate, _origin);
1396 }
1398 void sp_sel_trans_center(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1399 {
1400 seltrans->setCenter(pt);
1401 }
1404 void Inkscape::SelTrans::moveTo(NR::Point const &xy, guint state)
1405 {
1406 SnapManager const &m = _desktop->namedview->snap_manager;
1408 /* The amount that we've moved by during this drag */
1409 NR::Point dxy = xy - _point;
1411 /* Get a STL list of the selected items.
1412 ** FIXME: this should probably be done by Inkscape::Selection.
1413 */
1414 std::list<SPItem const*> it;
1415 for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) {
1416 it.push_back(reinterpret_cast<SPItem*>(i->data));
1417 }
1419 bool const alt = (state & GDK_MOD1_MASK);
1420 bool const control = (state & GDK_CONTROL_MASK);
1421 bool const shift = (state & GDK_SHIFT_MASK);
1423 if (alt) {
1425 /* Alt pressed means keep offset: snap the moved distance to the grid.
1426 ** FIXME: this will snap to more than just the grid, nowadays.
1427 */
1429 dxy = m.freeSnap(Snapper::SNAP_POINT, dxy, NULL).getPoint();
1431 } else if (!shift) {
1433 /* We're snapping to things, possibly with a constraint to horizontal or
1434 ** vertical movement. Obtain a list of possible translations and then
1435 ** pick the smallest.
1436 */
1438 /* This will be our list of possible translations */
1439 std::list<std::pair<NR::Point, bool> > s;
1441 if (control) {
1443 /* Snap to things, and also constrain to horizontal or vertical movement */
1445 for (unsigned int dim = 0; dim < 2; dim++) {
1446 s.push_back(m.constrainedSnapTranslation(Inkscape::Snapper::BBOX_POINT,
1447 _bbox_points,
1448 it,
1449 Inkscape::Snapper::ConstraintLine(component_vectors[dim]),
1450 dxy));
1452 s.push_back(m.constrainedSnapTranslation(Inkscape::Snapper::SNAP_POINT,
1453 _snap_points,
1454 it,
1455 Inkscape::Snapper::ConstraintLine(component_vectors[dim]),
1456 dxy));
1457 }
1459 } else {
1461 /* Snap to things with no constraint */
1463 s.push_back(m.freeSnapTranslation(Inkscape::Snapper::BBOX_POINT,
1464 _bbox_points, it, dxy));
1465 s.push_back(m.freeSnapTranslation(Inkscape::Snapper::SNAP_POINT,
1466 _snap_points, it, dxy));
1467 }
1469 /* Pick one */
1470 NR::Coord best = NR_HUGE;
1471 for (std::list<std::pair<NR::Point, bool> >::const_iterator i = s.begin(); i != s.end(); i++) {
1472 if (i->second) {
1473 NR::Coord const m = NR::L2(i->first);
1474 if (m < best) {
1475 best = m;
1476 dxy = i->first;
1477 }
1478 }
1479 }
1480 }
1482 if (control) {
1483 /* Ensure that the horizontal and vertical constraint has been applied */
1484 if (fabs(dxy[NR::X]) > fabs(dxy[NR::Y])) {
1485 dxy[NR::Y] = 0;
1486 } else {
1487 dxy[NR::X] = 0;
1488 }
1489 }
1491 NR::Matrix const move((NR::translate(dxy)));
1492 NR::Point const norm(0, 0);
1493 transform(move, norm);
1495 // status text
1496 GString *xs = SP_PX_TO_METRIC_STRING(dxy[NR::X], _desktop->namedview->getDefaultMetric());
1497 GString *ys = SP_PX_TO_METRIC_STRING(dxy[NR::Y], _desktop->namedview->getDefaultMetric());
1498 _message_context.setF(Inkscape::NORMAL_MESSAGE, _("<b>Move</b> by %s, %s; with <b>Ctrl</b> to restrict to horizontal/vertical; with <b>Shift</b> to disable snapping"), xs->str, ys->str);
1499 g_string_free(xs, TRUE);
1500 g_string_free(ys, TRUE);
1501 }
1504 /*
1505 Local Variables:
1506 mode:c++
1507 c-file-style:"stroustrup"
1508 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1509 indent-tabs-mode:nil
1510 fill-column:99
1511 End:
1512 */
1513 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :