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->getSnapPoints();
259 if (_snap_points.size() > 100) {
260 /* Snapping a huge number of nodes will take way too long, so limit the number of snappable nodes
261 An average user would rarely ever try to snap such a large number of nodes anyway, because
262 (s)he could hardly discern which node would be snapping */
263 _snap_points = selection->getSnapPointsConvexHull();
264 // Unfortunately, by now we will have lost the font-baseline snappoints :-(
265 }
266 _box = selection->bounds();
267 _bbox_points.clear();
268 if (_box) {
269 for ( unsigned i = 0 ; i < 4 ; i++ ) {
270 _bbox_points.push_back(_box->corner(i));
271 }
272 }
274 gchar const *scale_origin = prefs_get_string_attribute("tools.select", "scale_origin");
275 bool const origin_on_bbox = (scale_origin == NULL || !strcmp(scale_origin, "bbox"));
278 /*Snapping will be to either nodes or to boundingbox-cornes; each will require its own origin, which
279 is only slightly different from the other. When we would use an origin at one of the nodes while
280 trying to snap the boundingbox, all four points of the boundingbox would be moving (e.g. during stretching),
281 and would therefore also be snapping (which is bad). This leads to bugs similar to #1540195, in which
282 a box is caught between to guides. To solve this, we need two different points: _opposite_for_snappoints and
283 _opposite_for_boundingbox
284 */
286 if (_box) {
287 _opposite_for_bboxpoints = _box->min() + _box->dimensions() * NR::scale(1-x, 1-y);
289 NR::Rect op_box = *_box;
290 // FIXME: should be using ConvexHull here
291 if ( _snap_points.empty() == false) {
292 std::vector<NR::Point>::iterator i = _snap_points.begin();
293 op_box = NR::Rect(*i, *i);
294 i++;
295 while (i != _snap_points.end()) {
296 op_box.expandTo(*i);
297 i++;
298 }
299 }
300 _opposite_for_snappoints = ( op_box.min() + ( op_box.dimensions() * NR::scale(1-x, 1-y) ) );
301 //until we can kick out the old _opposite, and use _opposite_for_bboxpoints or _opposite_for_snappoints everywhere
302 //keep the old behavior for _opposite:
303 _opposite = origin_on_bbox ? _opposite_for_bboxpoints : _opposite_for_snappoints;
304 //FIXME: get rid of _opposite. Requires different handling of preferences, see Bulia Byak's comment for bug #1540195
305 }
307 if ((x != -1) && (y != -1)) {
308 sp_canvas_item_show(_norm);
309 sp_canvas_item_show(_grip);
310 }
312 if (_show == SHOW_OUTLINE) {
313 for (int i = 0; i < 4; i++)
314 sp_canvas_item_show(_l[i]);
315 }
317 _updateHandles();
318 g_return_if_fail(_stamp_cache == NULL);
319 }
321 void Inkscape::SelTrans::transform(NR::Matrix const &rel_affine, NR::Point const &norm)
322 {
323 g_return_if_fail(_grabbed);
324 g_return_if_fail(!_empty);
326 NR::Matrix const affine( NR::translate(-norm) * rel_affine * NR::translate(norm) );
328 if (_show == SHOW_CONTENT) {
329 // update the content
330 for (unsigned i = 0; i < _items.size(); i++) {
331 SPItem &item = *_items[i].first;
332 NR::Matrix const &prev_transform = _items[i].second;
333 sp_item_set_i2d_affine(&item, prev_transform * affine);
334 }
335 } else {
336 if (_box) {
337 NR::Point p[4];
338 /* update the outline */
339 for (unsigned i = 0 ; i < 4 ; i++) {
340 p[i] = _box->corner(i) * affine;
341 }
342 for (unsigned i = 0 ; i < 4 ; i++) {
343 sp_ctrlline_set_coords(SP_CTRLLINE(_l[i]), p[i], p[(i+1)%4]);
344 }
345 }
346 }
348 _current = affine;
349 _changed = true;
350 _updateHandles();
351 }
353 void Inkscape::SelTrans::ungrab()
354 {
355 g_return_if_fail(_grabbed);
356 _grabbed = false;
357 _show_handles = true;
359 Inkscape::Selection *selection = sp_desktop_selection(_desktop);
360 _updateVolatileState();
362 for (unsigned i = 0; i < _items.size(); i++) {
363 sp_object_unref(SP_OBJECT(_items[i].first), NULL);
364 }
366 sp_canvas_item_hide(_norm);
367 sp_canvas_item_hide(_grip);
369 if (_show == SHOW_OUTLINE) {
370 for (int i = 0; i < 4; i++)
371 sp_canvas_item_hide(_l[i]);
372 }
374 if (_stamp_cache) {
375 g_slist_free(_stamp_cache);
376 _stamp_cache = NULL;
377 }
379 _message_context.clear();
381 if (!_empty && _changed) {
382 sp_selection_apply_affine(selection, _current, (_show == SHOW_OUTLINE)? true : false);
383 if (_center) {
384 *_center *= _current;
385 _center_is_set = true;
386 }
388 // If dragging showed content live, sp_selection_apply_affine cannot change the centers
389 // appropriately - it does not know the original positions of the centers (all objects already have
390 // the new bboxes). So we need to reset the centers from our saved array.
391 if (_show != SHOW_OUTLINE && !_current.is_translation()) {
392 for (unsigned i = 0; i < _items_centers.size(); i++) {
393 SPItem *currentItem = _items_centers[i].first;
394 if (currentItem->isCenterSet()) { // only if it's already set
395 currentItem->setCenter (_items_centers[i].second * _current);
396 SP_OBJECT(currentItem)->updateRepr();
397 }
398 }
399 }
401 _items.clear();
402 _items_centers.clear();
404 if (_current.is_translation()) {
405 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
406 _("Move"));
407 } else if (_current.is_scale()) {
408 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
409 _("Scale"));
410 } else if (_current.is_rotation()) {
411 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
412 _("Rotate"));
413 } else {
414 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
415 _("Skew"));
416 }
418 } else {
420 if (_center_is_set) {
421 // we were dragging center; update reprs and commit undoable action
422 for (GSList const *l = _desktop->selection->itemList(); l; l = l->next) {
423 SPItem *it = (SPItem*)SP_OBJECT(l->data);
424 SP_OBJECT(it)->updateRepr();
425 }
426 sp_document_done (sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
427 _("Set center"));
428 }
430 _items.clear();
431 _items_centers.clear();
432 _updateHandles();
433 }
434 }
436 /* fixme: This is really bad, as we compare positions for each stamp (Lauris) */
437 /* fixme: IMHO the best way to keep sort cache would be to implement timestamping at last */
439 void Inkscape::SelTrans::stamp()
440 {
441 Inkscape::Selection *selection = sp_desktop_selection(_desktop);
443 bool fixup = !_grabbed;
444 if ( fixup && _stamp_cache ) {
445 // TODO - give a proper fix. Simple temproary work-around for the grab() issue
446 g_slist_free(_stamp_cache);
447 _stamp_cache = NULL;
448 }
450 /* stamping mode */
451 if (!_empty) {
452 GSList *l;
453 if (_stamp_cache) {
454 l = _stamp_cache;
455 } else {
456 /* Build cache */
457 l = g_slist_copy((GSList *) selection->itemList());
458 l = g_slist_sort(l, (GCompareFunc) sp_object_compare_position);
459 _stamp_cache = l;
460 }
462 while (l) {
463 SPItem *original_item = SP_ITEM(l->data);
464 Inkscape::XML::Node *original_repr = SP_OBJECT_REPR(original_item);
466 // remember the position of the item
467 gint pos = original_repr->position();
468 // remember parent
469 Inkscape::XML::Node *parent = sp_repr_parent(original_repr);
471 Inkscape::XML::Node *copy_repr = original_repr->duplicate(parent->document());
473 // add the new repr to the parent
474 parent->appendChild(copy_repr);
475 // move to the saved position
476 copy_repr->setPosition(pos > 0 ? pos : 0);
478 SPItem *copy_item = (SPItem *) sp_desktop_document(_desktop)->getObjectByRepr(copy_repr);
480 NR::Matrix const *new_affine;
481 if (_show == SHOW_OUTLINE) {
482 NR::Matrix const i2d(sp_item_i2d_affine(original_item));
483 NR::Matrix const i2dnew( i2d * _current );
484 sp_item_set_i2d_affine(copy_item, i2dnew);
485 new_affine = ©_item->transform;
486 } else {
487 new_affine = &original_item->transform;
488 }
490 sp_item_write_transform(copy_item, copy_repr, *new_affine);
492 if ( copy_item->isCenterSet() && _center ) {
493 copy_item->setCenter(*_center * _current);
494 }
496 Inkscape::GC::release(copy_repr);
497 l = l->next;
498 }
499 sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
500 _("Stamp"));
501 }
503 if ( fixup && _stamp_cache ) {
504 // TODO - give a proper fix. Simple temproary work-around for the grab() issue
505 g_slist_free(_stamp_cache);
506 _stamp_cache = NULL;
507 }
508 }
510 void Inkscape::SelTrans::_updateHandles()
511 {
512 if ( !_show_handles || _empty )
513 {
514 sp_remove_handles(_shandle, 8);
515 sp_remove_handles(_rhandle, 8);
516 sp_remove_handles(&_chandle, 1);
517 return;
518 }
520 // center handle
521 if ( _chandle == NULL ) {
522 _chandle = sp_knot_new(_desktop, _("<b>Center</b> of rotation and skewing: drag to reposition; scaling with Shift also uses this center"));
524 _chandle->setShape (SP_CTRL_SHAPE_BITMAP);
525 _chandle->setSize (13);
526 _chandle->setAnchor (handle_center.anchor);
527 _chandle->setMode (SP_CTRL_MODE_XOR);
528 _chandle->setFill(0x00000000, 0x00000000, 0x00000000);
529 _chandle->setStroke(0x000000ff, 0xff0000b0, 0xff0000b0);
530 _chandle->setPixbuf(handles[handle_center.control]);
531 sp_knot_update_ctrl(_chandle);
533 g_signal_connect(G_OBJECT(_chandle), "request",
534 G_CALLBACK(sp_sel_trans_handle_request), (gpointer) &handle_center);
535 g_signal_connect(G_OBJECT(_chandle), "moved",
536 G_CALLBACK(sp_sel_trans_handle_new_event), (gpointer) &handle_center);
537 g_signal_connect(G_OBJECT(_chandle), "grabbed",
538 G_CALLBACK(sp_sel_trans_handle_grab), (gpointer) &handle_center);
539 g_signal_connect(G_OBJECT(_chandle), "ungrabbed",
540 G_CALLBACK(sp_sel_trans_handle_ungrab), (gpointer) &handle_center);
541 g_signal_connect(G_OBJECT(_chandle), "clicked",
542 G_CALLBACK(sp_sel_trans_handle_click), (gpointer) &handle_center);
543 }
545 sp_remove_handles(&_chandle, 1);
546 if ( _state == STATE_SCALE ) {
547 sp_remove_handles(_rhandle, 8);
548 _showHandles(_shandle, handles_scale, 8,
549 _("<b>Squeeze or stretch</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"),
550 _("<b>Scale</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"));
551 } else {
552 sp_remove_handles(_shandle, 8);
553 _showHandles(_rhandle, handles_rotate, 8,
554 _("<b>Skew</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to skew around the opposite side"),
555 _("<b>Rotate</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to rotate around the opposite corner"));
556 }
558 if (!_center_is_set) {
559 _center = _desktop->selection->center();
560 _center_is_set = true;
561 }
563 if ( _state == STATE_SCALE || !_center ) {
564 sp_knot_hide(_chandle);
565 } else {
566 sp_knot_show(_chandle);
567 sp_knot_moveto(_chandle, &*_center);
568 }
569 }
571 void Inkscape::SelTrans::_updateVolatileState()
572 {
573 Inkscape::Selection *selection = sp_desktop_selection(_desktop);
574 _empty = selection->isEmpty();
576 if (_empty) {
577 return;
578 }
580 _box = selection->bounds();
581 if (!_box) {
582 _empty = true;
583 return;
584 }
586 _strokewidth = stroke_average_width (selection->itemList());
587 }
589 static void sp_remove_handles(SPKnot *knot[], gint num)
590 {
591 for (int i = 0; i < num; i++) {
592 if (knot[i] != NULL) {
593 sp_knot_hide(knot[i]);
594 }
595 }
596 }
598 void Inkscape::SelTrans::_showHandles(SPKnot *knot[], SPSelTransHandle const handle[], gint num,
599 gchar const *even_tip, gchar const *odd_tip)
600 {
601 g_return_if_fail( !_empty );
603 for (int i = 0; i < num; i++) {
604 if (knot[i] == NULL) {
605 knot[i] = sp_knot_new(_desktop, i % 2 ? even_tip : odd_tip);
607 knot[i]->setShape (SP_CTRL_SHAPE_BITMAP);
608 knot[i]->setSize (13);
609 knot[i]->setAnchor (handle[i].anchor);
610 knot[i]->setMode (SP_CTRL_MODE_XOR);
611 knot[i]->setFill(0x000000ff, 0x00ff6600, 0x00ff6600); // inversion, green, green
612 knot[i]->setStroke(0x000000ff, 0x000000ff, 0x000000ff); // inversion
613 knot[i]->setPixbuf(handles[handle[i].control]);
614 sp_knot_update_ctrl(knot[i]);
616 g_signal_connect(G_OBJECT(knot[i]), "request",
617 G_CALLBACK(sp_sel_trans_handle_request), (gpointer) &handle[i]);
618 g_signal_connect(G_OBJECT(knot[i]), "moved",
619 G_CALLBACK(sp_sel_trans_handle_new_event), (gpointer) &handle[i]);
620 g_signal_connect(G_OBJECT(knot[i]), "grabbed",
621 G_CALLBACK(sp_sel_trans_handle_grab), (gpointer) &handle[i]);
622 g_signal_connect(G_OBJECT(knot[i]), "ungrabbed",
623 G_CALLBACK(sp_sel_trans_handle_ungrab), (gpointer) &handle[i]);
624 g_signal_connect(G_OBJECT(knot[i]), "event", G_CALLBACK(sp_seltrans_handle_event), (gpointer) &handle[i]);
625 }
626 sp_knot_show(knot[i]);
628 NR::Point const handle_pt(handle[i].x, handle[i].y);
629 // shouldn't have nullary bbox, but knots
630 g_assert(_box);
631 NR::Point p( _box->min()
632 + ( _box->dimensions()
633 * NR::scale(handle_pt) ) );
635 sp_knot_moveto(knot[i], &p);
636 }
637 }
639 static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data)
640 {
641 SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleGrab(
642 knot, state, *(SPSelTransHandle const *) data
643 );
644 }
646 static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, gpointer data)
647 {
648 SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->ungrab();
649 }
651 static void sp_sel_trans_handle_new_event(SPKnot *knot, NR::Point *position, guint state, gpointer data)
652 {
653 SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleNewEvent(
654 knot, position, state, *(SPSelTransHandle const *) data
655 );
656 }
658 static gboolean sp_sel_trans_handle_request(SPKnot *knot, NR::Point *position, guint state, gboolean *data)
659 {
660 return SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleRequest(
661 knot, position, state, *(SPSelTransHandle const *) data
662 );
663 }
665 static void sp_sel_trans_handle_click(SPKnot *knot, guint state, gpointer data)
666 {
667 SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleClick(
668 knot, state, *(SPSelTransHandle const *) data
669 );
670 }
672 void Inkscape::SelTrans::handleClick(SPKnot *knot, guint state, SPSelTransHandle const &handle)
673 {
674 switch (handle.anchor) {
675 case GTK_ANCHOR_CENTER:
676 if (state & GDK_SHIFT_MASK) {
677 // Unset the center position for all selected items
678 for (GSList const *l = _desktop->selection->itemList(); l; l = l->next) {
679 SPItem *it = (SPItem*)(SP_OBJECT(l->data));
680 it->unsetCenter();
681 SP_OBJECT(it)->updateRepr();
682 _center_is_set = false; // center has changed
683 _updateHandles();
684 }
685 sp_document_done (sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
686 _("Reset center"));
687 }
688 break;
689 default:
690 break;
691 }
692 }
694 void Inkscape::SelTrans::handleGrab(SPKnot *knot, guint state, SPSelTransHandle const &handle)
695 {
696 switch (handle.anchor) {
697 case GTK_ANCHOR_CENTER:
698 g_object_set(G_OBJECT(_grip),
699 "shape", SP_CTRL_SHAPE_BITMAP,
700 "size", 13.0,
701 NULL);
702 sp_canvas_item_show(_grip);
703 break;
704 default:
705 g_object_set(G_OBJECT(_grip),
706 "shape", SP_CTRL_SHAPE_CROSS,
707 "size", 7.0,
708 NULL);
709 sp_canvas_item_show(_norm);
710 sp_canvas_item_show(_grip);
712 break;
713 }
715 grab(sp_knot_position(knot), handle.x, handle.y, FALSE);
716 }
719 void Inkscape::SelTrans::handleNewEvent(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
720 {
721 if (!SP_KNOT_IS_GRABBED(knot)) {
722 return;
723 }
725 // in case items have been unhooked from the document, don't
726 // try to continue processing events for them.
727 for (unsigned int i = 0; i < _items.size(); i++) {
728 if (!SP_OBJECT_DOCUMENT(SP_OBJECT(_items[i].first)) ) {
729 return;
730 }
731 }
733 handle.action(this, handle, *position, state);
734 }
737 gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
738 {
739 if (!SP_KNOT_IS_GRABBED(knot)) {
740 return TRUE;
741 }
743 knot->desktop->setPosition(*position);
745 if (state & GDK_MOD1_MASK) {
746 *position = _point + ( *position - _point ) / 10;
747 }
749 if ((!(state & GDK_SHIFT_MASK) == !(_state == STATE_ROTATE)) && (&handle != &handle_center)) {
750 _origin = _opposite;
751 _origin_for_bboxpoints = _opposite_for_bboxpoints;
752 _origin_for_snappoints = _opposite_for_snappoints;
753 } else if (_center) {
754 _origin = *_center;
755 _origin_for_bboxpoints = *_center;
756 _origin_for_snappoints = *_center;
757 } else {
758 // FIXME
759 return TRUE;
760 }
761 if (handle.request(this, handle, *position, state)) {
762 sp_knot_set_position(knot, position, state);
763 SP_CTRL(_grip)->moveto(*position);
764 SP_CTRL(_norm)->moveto(_origin);
765 }
767 return TRUE;
768 }
771 void Inkscape::SelTrans::_selChanged(Inkscape::Selection *selection)
772 {
773 if (!_grabbed) {
774 _updateVolatileState();
775 _current.set_identity();
776 _center_is_set = false; // center(s) may have changed
777 _updateHandles();
778 }
779 }
781 void Inkscape::SelTrans::_selModified(Inkscape::Selection *selection, guint flags)
782 {
783 if (!_grabbed) {
784 _updateVolatileState();
785 _current.set_identity();
787 // reset internal flag
788 _changed = false;
790 _center_is_set = false; // center(s) may have changed
792 _updateHandles();
793 }
794 }
796 /*
797 * handlers for handle move-request
798 */
800 /** Returns -1 or 1 according to the sign of x. Returns 1 for 0 and NaN. */
801 static double sign(double const x)
802 {
803 return ( x < 0
804 ? -1
805 : 1 );
806 }
808 gboolean sp_sel_trans_scale_request(Inkscape::SelTrans *seltrans,
809 SPSelTransHandle const &, NR::Point &pt, guint state)
810 {
811 return seltrans->scaleRequest(pt, state);
812 }
814 gboolean sp_sel_trans_stretch_request(Inkscape::SelTrans *seltrans,
815 SPSelTransHandle const &handle, NR::Point &pt, guint state)
816 {
817 return seltrans->stretchRequest(handle, pt, state);
818 }
820 gboolean sp_sel_trans_skew_request(Inkscape::SelTrans *seltrans,
821 SPSelTransHandle const &handle, NR::Point &pt, guint state)
822 {
823 return seltrans->skewRequest(handle, pt, state);
824 }
826 gboolean sp_sel_trans_rotate_request(Inkscape::SelTrans *seltrans,
827 SPSelTransHandle const &, NR::Point &pt, guint state)
828 {
829 return seltrans->rotateRequest(pt, state);
830 }
832 gboolean sp_sel_trans_center_request(Inkscape::SelTrans *seltrans,
833 SPSelTransHandle const &, NR::Point &pt, guint state)
834 {
835 return seltrans->centerRequest(pt, state);
836 }
838 gboolean Inkscape::SelTrans::scaleRequest(NR::Point &pt, guint state)
839 {
840 using NR::X;
841 using NR::Y;
843 NR::Point d = _point - _origin;
844 NR::scale s(0, 0);
846 /* Work out the new scale factors `s' */
847 for ( unsigned int i = 0 ; i < 2 ; i++ ) {
848 if ( fabs(d[i]) > 0.001 ) {
849 s[i] = ( pt[i] - _origin[i] ) / d[i];
850 if ( fabs(s[i]) < 1e-9 ) {
851 s[i] = 1e-9;
852 }
853 }
854 }
856 SnapManager const &m = _desktop->namedview->snap_manager;
858 /* Get a STL list of the selected items.
859 ** FIXME: this should probably be done by Inkscape::Selection.
860 */
861 std::list<SPItem const*> it;
862 for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) {
863 it.push_back(reinterpret_cast<SPItem*>(i->data));
864 }
866 if ((state & GDK_CONTROL_MASK) || _desktop->isToolboxButtonActive ("lock")) {
867 // Scale is locked to a 1:1 aspect ratio, so that s[X] must be made to equal s[Y].
868 //
869 // The aspect-ratio must be locked before snapping
870 if (fabs(s[NR::X]) > fabs(s[NR::Y])) {
871 s[NR::X] = fabs(s[NR::Y]) * sign(s[NR::X]);
872 } else {
873 s[NR::Y] = fabs(s[NR::X]) * sign(s[NR::Y]);
874 }
876 // Snap along a suitable constraint vector from the origin.
878 // The inclination of the constraint vector is calculated from the aspect ratio
879 NR::Point bbox_dim = _box->dimensions();
880 double const aspect_ratio = bbox_dim[1] / bbox_dim[0]; // = height / width
882 // Determine direction of the constraint vector
883 NR::Point const cv = NR::Point(
884 pt[NR::X] > _origin[NR::X] ? 1 : -1,
885 pt[NR::Y] > _origin[NR::Y] ? aspect_ratio : -aspect_ratio
886 );
888 std::pair<NR::scale, bool> bb = m.constrainedSnapScale(Snapper::BBOX_POINT,
889 _bbox_points,
890 it,
891 Snapper::ConstraintLine(_origin_for_bboxpoints, cv),
892 s,
893 _origin_for_bboxpoints);
895 std::pair<NR::scale, bool> sn = m.constrainedSnapScale(Snapper::SNAP_POINT,
896 _snap_points,
897 it,
898 Snapper::ConstraintLine(_origin_for_snappoints, cv),
899 s,
900 _origin_for_snappoints);
902 if (bb.second == false && sn.second == false) {
903 /* We didn't snap, so just keep the locked aspect ratio */
904 } else {
905 /* Choose the smaller difference in scale. Since s[X] == s[Y] we can
906 ** just compare difference in s[X].
907 */
908 double const bd = bb.second ? fabs(bb.first[NR::X] - s[NR::X]) : NR_HUGE;
909 double const sd = sn.second ? fabs(sn.first[NR::X] - s[NR::X]) : NR_HUGE;
910 s = (bd < sd) ? bb.first : sn.first;
911 }
913 } else {
914 /* Scale aspect ratio is unlocked */
916 std::pair<NR::scale, bool> bb = m.freeSnapScale(Snapper::BBOX_POINT,
917 _bbox_points,
918 it,
919 s,
920 _origin_for_bboxpoints);
921 std::pair<NR::scale, bool> sn = m.freeSnapScale(Snapper::SNAP_POINT,
922 _snap_points,
923 it,
924 s,
925 _origin_for_snappoints);
927 /* Pick the snap that puts us closest to the original scale */
928 NR::Coord bd = bb.second ?
929 fabs(NR::L2(NR::Point(bb.first[NR::X], bb.first[NR::Y])) -
930 NR::L2(NR::Point(s[NR::X], s[NR::Y])))
931 : NR_HUGE;
932 NR::Coord sd = sn.second ?
933 fabs(NR::L2(NR::Point(sn.first[NR::X], sn.first[NR::Y])) -
934 NR::L2(NR::Point(s[NR::X], s[NR::Y])))
935 : NR_HUGE;
936 s = (bd < sd) ? bb.first : sn.first;
937 }
939 /* Update the knot position */
940 pt = ( _point - _origin ) * s + _origin;
942 /* Status text */
943 _message_context.setF(Inkscape::NORMAL_MESSAGE,
944 _("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>Ctrl</b> to lock ratio"),
945 100 * s[NR::X], 100 * s[NR::Y]);
947 return TRUE;
948 }
950 gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
951 {
952 using NR::X;
953 using NR::Y;
955 NR::Dim2 axis, perp;
957 switch (handle.cursor) {
958 case GDK_TOP_SIDE:
959 case GDK_BOTTOM_SIDE:
960 axis = NR::Y;
961 perp = NR::X;
962 break;
963 case GDK_LEFT_SIDE:
964 case GDK_RIGHT_SIDE:
965 axis = NR::X;
966 perp = NR::Y;
967 break;
968 default:
969 g_assert_not_reached();
970 return TRUE;
971 };
973 if ( fabs( _point[axis] - _origin[axis] ) < 1e-15 ) {
974 return FALSE;
975 }
977 NR::scale s(1, 1);
978 s[axis] = ( ( pt[axis] - _origin[axis] )
979 / ( _point[axis] - _origin[axis] ) );
980 if ( fabs(s[axis]) < 1e-15 ) {
981 s[axis] = 1e-15;
982 }
984 /* Get a STL list of the selected items.
985 ** FIXME: this should probably be done by Inkscape::Selection.
986 */
987 std::list<SPItem const*> it;
988 for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) {
989 it.push_back(reinterpret_cast<SPItem*>(i->data));
990 }
992 SnapManager const &m = _desktop->namedview->snap_manager;
994 if ( state & GDK_CONTROL_MASK ) {
995 // on ctrl, apply symmetrical scaling instead of stretching
996 s[perp] = fabs(s[axis]);
998 std::pair<NR::Coord, bool> const bb = m.freeSnapStretch(
999 Snapper::BBOX_POINT,
1000 _bbox_points,
1001 it,
1002 s[axis],
1003 _origin_for_bboxpoints,
1004 axis,
1005 true);
1007 std::pair<NR::Coord, bool> const sn = m.freeSnapStretch(
1008 Snapper::SNAP_POINT,
1009 _snap_points,
1010 it,
1011 s[axis],
1012 _origin_for_snappoints,
1013 axis,
1014 true);
1016 NR::Coord const bd = bb.second ? fabs(bb.first - s[axis]) : NR_HUGE;
1017 NR::Coord const sd = sn.second ? fabs(sn.first - s[axis]) : NR_HUGE;
1018 NR::Coord const ratio = (bd < sd) ? bb.first : sn.first;
1020 s[axis] = fabs(ratio) * sign(s[axis]);
1021 s[perp] = fabs(s[axis]);
1022 } else {
1024 std::pair<NR::Coord, bool> const bb = m.freeSnapStretch(
1025 Snapper::BBOX_POINT,
1026 _bbox_points,
1027 it,
1028 s[axis],
1029 _origin_for_bboxpoints,
1030 axis,
1031 false);
1033 std::pair<NR::Coord, bool> const sn = m.freeSnapStretch(
1034 Snapper::SNAP_POINT,
1035 _snap_points,
1036 it,
1037 s[axis],
1038 _origin_for_snappoints,
1039 axis,
1040 false);
1042 /* Choose the smaller difference in scale */
1043 NR::Coord const bd = bb.second ? fabs(bb.first - s[axis]) : NR_HUGE;
1044 NR::Coord const sd = sn.second ? fabs(sn.first - s[axis]) : NR_HUGE;
1045 s[axis] = (bd < sd) ? bb.first : sn.first;
1046 s[perp] = 1;
1047 }
1049 pt = ( _point - _origin ) * NR::scale(s) + _origin;
1050 if (isNaN(pt[X] + pt[Y])) {
1051 g_warning("point=(%g, %g), norm=(%g, %g), s=(%g, %g)\n",
1052 _point[X], _point[Y], _origin[X], _origin[Y], s[X], s[Y]);
1053 }
1055 // status text
1056 _message_context.setF(Inkscape::NORMAL_MESSAGE,
1057 _("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>Ctrl</b> to lock ratio"),
1058 100 * s[NR::X], 100 * s[NR::Y]);
1060 return TRUE;
1061 }
1063 gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
1064 {
1065 using NR::X;
1066 using NR::Y;
1068 if (handle.cursor != GDK_SB_V_DOUBLE_ARROW && handle.cursor != GDK_SB_H_DOUBLE_ARROW) {
1069 return FALSE;
1070 }
1072 NR::Dim2 dim_a;
1073 NR::Dim2 dim_b;
1074 if (handle.cursor == GDK_SB_V_DOUBLE_ARROW) {
1075 dim_a = X;
1076 dim_b = Y;
1077 } else {
1078 dim_a = Y;
1079 dim_b = X;
1080 }
1082 double skew[2];
1083 double s[2] = { 1.0, 1.0 };
1085 if (fabs(_point[dim_a] - _origin[dim_a]) < NR_EPSILON) {
1086 return FALSE;
1087 }
1089 skew[dim_a] = ( pt[dim_b] - _point[dim_b] ) / ( _point[dim_a] - _origin[dim_a] );
1091 s[dim_a] = ( pt[dim_a] - _origin[dim_a] ) / ( _point[dim_a] - _origin[dim_a] );
1093 if ( fabs(s[dim_a]) < 1 ) {
1094 s[dim_a] = sign(s[dim_a]);
1095 } else {
1096 s[dim_a] = floor( s[dim_a] + 0.5 );
1097 }
1099 double radians = atan(skew[dim_a] / s[dim_a]);
1101 if (state & GDK_CONTROL_MASK) {
1103 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1105 if (snaps) {
1106 double sections = floor( radians * snaps / M_PI + .5 );
1107 if (fabs(sections) >= snaps / 2) sections = sign(sections) * (snaps / 2 - 1);
1108 radians = ( M_PI / snaps ) * sections;
1109 }
1110 skew[dim_a] = tan(radians) * s[dim_a];
1111 } else {
1112 SnapManager const &m = _desktop->namedview->snap_manager;
1114 std::pair<NR::Coord, bool> bb = m.freeSnapSkew(Inkscape::Snapper::BBOX_POINT,
1115 _bbox_points,
1116 std::list<SPItem const *>(),
1117 skew[dim_a],
1118 _origin_for_bboxpoints,
1119 dim_b);
1121 std::pair<NR::Coord, bool> sn = m.freeSnapSkew(Inkscape::Snapper::SNAP_POINT,
1122 _snap_points,
1123 std::list<SPItem const *>(),
1124 skew[dim_a],
1125 _origin_for_snappoints,
1126 dim_b);
1128 if (bb.second || sn.second) {
1129 /* We snapped something, so change the skew to reflect it */
1130 NR::Coord const bd = bb.second ? bb.first : NR_HUGE;
1131 NR::Coord const sd = sn.second ? sn.first : NR_HUGE;
1132 skew[dim_a] = std::min(bd, sd);
1133 }
1134 }
1136 pt[dim_b] = ( _point[dim_a] - _origin[dim_a] ) * skew[dim_a] + _point[dim_b];
1137 pt[dim_a] = ( _point[dim_a] - _origin[dim_a] ) * s[dim_a] + _origin[dim_a];
1139 /* status text */
1140 double degrees = 180 / M_PI * radians;
1141 if (degrees > 180) degrees -= 360;
1142 if (degrees < -180) degrees += 360;
1144 _message_context.setF(Inkscape::NORMAL_MESSAGE,
1145 // TRANSLATORS: don't modify the first ";"
1146 // (it will NOT be displayed as ";" - only the second one will be)
1147 _("<b>Skew</b>: %0.2f°; with <b>Ctrl</b> to snap angle"),
1148 degrees);
1150 return TRUE;
1151 }
1153 gboolean Inkscape::SelTrans::rotateRequest(NR::Point &pt, guint state)
1154 {
1155 int snaps = prefs_get_int_attribute("options.rotationsnapsperpi", "value", 12);
1157 // rotate affine in rotate
1158 NR::Point const d1 = _point - _origin;
1159 NR::Point const d2 = pt - _origin;
1161 NR::Coord const h1 = NR::L2(d1);
1162 if (h1 < 1e-15) return FALSE;
1163 NR::Point q1 = d1 / h1;
1164 NR::Coord const h2 = NR::L2(d2);
1165 if (fabs(h2) < 1e-15) return FALSE;
1166 NR::Point q2 = d2 / h2;
1168 double radians;
1169 if (state & GDK_CONTROL_MASK) {
1170 /* Have to restrict movement. */
1171 double cos_t = NR::dot(q1, q2);
1172 double sin_t = NR::dot(NR::rot90(q1), q2);
1173 radians = atan2(sin_t, cos_t);
1174 if (snaps) {
1175 radians = ( M_PI / snaps ) * floor( radians * snaps / M_PI + .5 );
1176 }
1177 q1 = NR::Point(1, 0);
1178 q2 = NR::Point(cos(radians), sin(radians));
1179 } else {
1180 radians = atan2(NR::dot(NR::rot90(d1), d2),
1181 NR::dot(d1, d2));
1182 }
1184 NR::rotate const r1(q1);
1185 NR::rotate const r2(q2);
1186 pt = _point * NR::translate(-_origin) * ( r2 / r1 ) * NR::translate(_origin);
1188 /* status text */
1189 double degrees = 180 / M_PI * radians;
1190 if (degrees > 180) degrees -= 360;
1191 if (degrees < -180) degrees += 360;
1193 _message_context.setF(Inkscape::NORMAL_MESSAGE,
1194 // TRANSLATORS: don't modify the first ";"
1195 // (it will NOT be displayed as ";" - only the second one will be)
1196 _("<b>Rotate</b>: %0.2f°; with <b>Ctrl</b> to snap angle"), degrees);
1198 return TRUE;
1199 }
1201 gboolean Inkscape::SelTrans::centerRequest(NR::Point &pt, guint state)
1202 {
1203 using NR::X;
1204 using NR::Y;
1206 SnapManager const &m = _desktop->namedview->snap_manager;
1207 pt = m.freeSnap(Snapper::SNAP_POINT, pt, NULL).getPoint();
1209 if (state & GDK_CONTROL_MASK) {
1210 if ( fabs(_point[X] - pt[X]) > fabs(_point[Y] - pt[Y]) ) {
1211 pt[Y] = _point[Y];
1212 } else {
1213 pt[X] = _point[X];
1214 }
1215 }
1217 if ( !(state & GDK_SHIFT_MASK) && _box ) {
1218 // screen pixels to snap center to bbox
1219 #define SNAP_DIST 5
1220 // FIXME: take from prefs
1221 double snap_dist = SNAP_DIST / _desktop->current_zoom();
1223 for (int i = 0; i < 2; i++) {
1224 if (fabs(pt[i] - _box->min()[i]) < snap_dist) {
1225 pt[i] = _box->min()[i];
1226 }
1227 if (fabs(pt[i] - _box->midpoint()[i]) < snap_dist) {
1228 pt[i] = _box->midpoint()[i];
1229 }
1230 if (fabs(pt[i] - _box->max()[i]) < snap_dist) {
1231 pt[i] = _box->max()[i];
1232 }
1233 }
1234 }
1236 // status text
1237 GString *xs = SP_PX_TO_METRIC_STRING(pt[X], _desktop->namedview->getDefaultMetric());
1238 GString *ys = SP_PX_TO_METRIC_STRING(pt[Y], _desktop->namedview->getDefaultMetric());
1239 _message_context.setF(Inkscape::NORMAL_MESSAGE, _("Move <b>center</b> to %s, %s"), xs->str, ys->str);
1240 g_string_free(xs, FALSE);
1241 g_string_free(ys, FALSE);
1243 return TRUE;
1244 }
1246 /*
1247 * handlers for handle movement
1248 *
1249 */
1251 void sp_sel_trans_stretch(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state)
1252 {
1253 seltrans->stretch(handle, pt, state);
1254 }
1256 void sp_sel_trans_scale(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1257 {
1258 seltrans->scale(pt, state);
1259 }
1261 void sp_sel_trans_skew(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state)
1262 {
1263 seltrans->skew(handle, pt, state);
1264 }
1266 void sp_sel_trans_rotate(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1267 {
1268 seltrans->rotate(pt, state);
1269 }
1271 void Inkscape::SelTrans::stretch(SPSelTransHandle const &handle, NR::Point &pt, guint state)
1272 {
1273 using NR::X;
1274 using NR::Y;
1276 NR::Dim2 dim;
1277 switch (handle.cursor) {
1278 case GDK_LEFT_SIDE:
1279 case GDK_RIGHT_SIDE:
1280 dim = X;
1281 break;
1282 case GDK_TOP_SIDE:
1283 case GDK_BOTTOM_SIDE:
1284 dim = Y;
1285 break;
1286 default:
1287 g_assert_not_reached();
1288 abort();
1289 break;
1290 }
1292 NR::Point const scale_origin(_origin);
1293 double const offset = _point[dim] - scale_origin[dim];
1294 if (!( fabs(offset) >= 1e-15 )) {
1295 return;
1296 }
1297 NR::scale s(1, 1);
1298 s[dim] = ( pt[dim] - scale_origin[dim] ) / offset;
1299 if (isNaN(s[dim])) {
1300 g_warning("s[dim]=%g, pt[dim]=%g, scale_origin[dim]=%g, point[dim]=%g\n",
1301 s[dim], pt[dim], scale_origin[dim], _point[dim]);
1302 }
1303 if (!( fabs(s[dim]) >= 1e-15 )) {
1304 s[dim] = 1e-15;
1305 }
1306 if (state & GDK_CONTROL_MASK) {
1307 /* Preserve aspect ratio, but never flip in the dimension not being edited. */
1308 s[!dim] = fabs(s[dim]);
1309 }
1311 if (!_box) {
1312 return;
1313 }
1315 NR::Point new_bbox_min = _box->min() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin));
1316 NR::Point new_bbox_max = _box->max() * (NR::translate(-scale_origin) * NR::Matrix(s) * NR::translate(scale_origin));
1318 int transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1);
1319 NR::Matrix scaler = get_scale_transform_with_stroke (*_box, _strokewidth, transform_stroke,
1320 new_bbox_min[NR::X], new_bbox_min[NR::Y], new_bbox_max[NR::X], new_bbox_max[NR::Y]);
1322 transform(scaler, NR::Point(0, 0)); // we have already accounted for origin, so pass 0,0
1323 }
1325 void Inkscape::SelTrans::scale(NR::Point &pt, guint state)
1326 {
1327 if (!_box) {
1328 return;
1329 }
1331 NR::Point const offset = _point - _origin;
1333 NR::scale s (1, 1);
1334 for (int i = NR::X; i <= NR::Y; i++) {
1335 if (fabs(offset[i]) > 1e-9)
1336 s[i] = (pt[i] - _origin[i]) / offset[i];
1337 if (fabs(s[i]) < 1e-9)
1338 s[i] = 1e-9;
1339 }
1340 NR::Point new_bbox_min = _box->min() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin));
1341 NR::Point new_bbox_max = _box->max() * (NR::translate(-_origin) * NR::Matrix(s) * NR::translate(_origin));
1343 int transform_stroke = prefs_get_int_attribute ("options.transform", "stroke", 1);
1344 NR::Matrix scaler = get_scale_transform_with_stroke (*_box, _strokewidth, transform_stroke,
1345 new_bbox_min[NR::X], new_bbox_min[NR::Y], new_bbox_max[NR::X], new_bbox_max[NR::Y]);
1347 transform(scaler, NR::Point(0, 0)); // we have already accounted for origin, so pass 0,0
1348 }
1350 void Inkscape::SelTrans::skew(SPSelTransHandle const &handle, NR::Point &pt, guint state)
1351 {
1352 NR::Point const offset = _point - _origin;
1354 unsigned dim;
1355 switch (handle.cursor) {
1356 case GDK_SB_H_DOUBLE_ARROW:
1357 dim = NR::Y;
1358 break;
1359 case GDK_SB_V_DOUBLE_ARROW:
1360 dim = NR::X;
1361 break;
1362 default:
1363 g_assert_not_reached();
1364 abort();
1365 break;
1366 }
1367 if (fabs(offset[dim]) < 1e-15) {
1368 return;
1369 }
1370 NR::Matrix skew = NR::identity();
1371 skew[2*dim + dim] = (pt[dim] - _origin[dim]) / offset[dim];
1372 skew[2*dim + (1-dim)] = (pt[1-dim] - _point[1-dim]) / offset[dim];
1373 skew[2*(1-dim) + (dim)] = 0;
1374 skew[2*(1-dim) + (1-dim)] = 1;
1376 for (int i = 0; i < 2; i++) {
1377 if (fabs(skew[3*i]) < 1e-15) {
1378 skew[3*i] = 1e-15;
1379 }
1380 }
1381 transform(skew, _origin);
1382 }
1384 void Inkscape::SelTrans::rotate(NR::Point &pt, guint state)
1385 {
1386 NR::Point const offset = _point - _origin;
1388 NR::Coord const h1 = NR::L2(offset);
1389 if (h1 < 1e-15) {
1390 return;
1391 }
1392 NR::Point const q1 = offset / h1;
1393 NR::Coord const h2 = NR::L2( pt - _origin );
1394 if (h2 < 1e-15) {
1395 return;
1396 }
1397 NR::Point const q2 = (pt - _origin) / h2;
1398 NR::rotate const r1(q1);
1399 NR::rotate const r2(q2);
1401 NR::Matrix rotate( r2 / r1 );
1402 transform(rotate, _origin);
1403 }
1405 void sp_sel_trans_center(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1406 {
1407 seltrans->setCenter(pt);
1408 }
1411 void Inkscape::SelTrans::moveTo(NR::Point const &xy, guint state)
1412 {
1413 SnapManager const &m = _desktop->namedview->snap_manager;
1415 /* The amount that we've moved by during this drag */
1416 NR::Point dxy = xy - _point;
1418 /* Get a STL list of the selected items.
1419 ** FIXME: this should probably be done by Inkscape::Selection.
1420 */
1421 std::list<SPItem const*> it;
1422 for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) {
1423 it.push_back(reinterpret_cast<SPItem*>(i->data));
1424 }
1426 bool const alt = (state & GDK_MOD1_MASK);
1427 bool const control = (state & GDK_CONTROL_MASK);
1428 bool const shift = (state & GDK_SHIFT_MASK);
1430 if (alt) {
1432 /* Alt pressed means keep offset: snap the moved distance to the grid.
1433 ** FIXME: this will snap to more than just the grid, nowadays.
1434 */
1436 dxy = m.freeSnap(Snapper::SNAP_POINT, dxy, NULL).getPoint();
1438 } else if (!shift) {
1440 /* We're snapping to things, possibly with a constraint to horizontal or
1441 ** vertical movement. Obtain a list of possible translations and then
1442 ** pick the smallest.
1443 */
1445 /* This will be our list of possible translations */
1446 std::list<std::pair<NR::Point, bool> > s;
1448 if (control) {
1450 /* Snap to things, and also constrain to horizontal or vertical movement */
1452 for (unsigned int dim = 0; dim < 2; dim++) {
1453 s.push_back(m.constrainedSnapTranslation(Inkscape::Snapper::BBOX_POINT,
1454 _bbox_points,
1455 it,
1456 Inkscape::Snapper::ConstraintLine(component_vectors[dim]),
1457 dxy));
1459 s.push_back(m.constrainedSnapTranslation(Inkscape::Snapper::SNAP_POINT,
1460 _snap_points,
1461 it,
1462 Inkscape::Snapper::ConstraintLine(component_vectors[dim]),
1463 dxy));
1464 }
1466 } else {
1468 /* Snap to things with no constraint */
1470 s.push_back(m.freeSnapTranslation(Inkscape::Snapper::BBOX_POINT,
1471 _bbox_points, it, dxy));
1472 s.push_back(m.freeSnapTranslation(Inkscape::Snapper::SNAP_POINT,
1473 _snap_points, it, dxy));
1474 }
1476 /* Pick one */
1477 NR::Coord best = NR_HUGE;
1478 for (std::list<std::pair<NR::Point, bool> >::const_iterator i = s.begin(); i != s.end(); i++) {
1479 if (i->second) {
1480 NR::Coord const m = NR::L2(i->first);
1481 if (m < best) {
1482 best = m;
1483 dxy = i->first;
1484 }
1485 }
1486 }
1487 }
1489 if (control) {
1490 /* Ensure that the horizontal and vertical constraint has been applied */
1491 if (fabs(dxy[NR::X]) > fabs(dxy[NR::Y])) {
1492 dxy[NR::Y] = 0;
1493 } else {
1494 dxy[NR::X] = 0;
1495 }
1496 }
1498 NR::Matrix const move((NR::translate(dxy)));
1499 NR::Point const norm(0, 0);
1500 transform(move, norm);
1502 // status text
1503 GString *xs = SP_PX_TO_METRIC_STRING(dxy[NR::X], _desktop->namedview->getDefaultMetric());
1504 GString *ys = SP_PX_TO_METRIC_STRING(dxy[NR::Y], _desktop->namedview->getDefaultMetric());
1505 _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);
1506 g_string_free(xs, TRUE);
1507 g_string_free(ys, TRUE);
1508 }
1511 /*
1512 Local Variables:
1513 mode:c++
1514 c-file-style:"stroustrup"
1515 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1516 indent-tabs-mode:nil
1517 fill-column:99
1518 End:
1519 */
1520 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :