Code

Implementing feature request #1673807: snapping of gradient handles
[inkscape.git] / src / seltrans.cpp
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         );
155 Inkscape::SelTrans::~SelTrans()
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();
198 void Inkscape::SelTrans::resetState()
200     _state = STATE_SCALE;
203 void Inkscape::SelTrans::increaseState()
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();
216 void Inkscape::SelTrans::setCenter(NR::Point const &p)
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();
231 void Inkscape::SelTrans::grab(NR::Point const &p, gdouble x, gdouble y, bool show_handles)
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"));
270     
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     */
278     
279     if (_box) {
280         _opposite_for_bboxpoints = _box->min() + _box->dimensions() * NR::scale(1-x, 1-y);
281         
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     }
299     
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);
314 void Inkscape::SelTrans::transform(NR::Matrix const &rel_affine, NR::Point const &norm)
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();
346 void Inkscape::SelTrans::ungrab()
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     }
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()
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();
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 = &copy_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     }
503 void Inkscape::SelTrans::_updateHandles()
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     }
564 void Inkscape::SelTrans::_updateVolatileState()
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());
582 static void sp_remove_handles(SPKnot *knot[], gint num)
584     for (int i = 0; i < num; i++) {
585         if (knot[i] != NULL) {
586             sp_knot_hide(knot[i]);
587         }
588     }
591 void Inkscape::SelTrans::_showHandles(SPKnot *knot[], SPSelTransHandle const handle[], gint num,
592                              gchar const *even_tip, gchar const *odd_tip)
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     }
632 static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data)
634     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleGrab(
635         knot, state, *(SPSelTransHandle const *) data
636         );
639 static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, gpointer data)
641     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->ungrab();
644 static void sp_sel_trans_handle_new_event(SPKnot *knot, NR::Point *position, guint state, gpointer data)
646     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleNewEvent(
647         knot, position, state, *(SPSelTransHandle const *) data
648         );
651 static gboolean sp_sel_trans_handle_request(SPKnot *knot, NR::Point *position, guint state, gboolean *data)
653     return SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleRequest(
654         knot, position, state, *(SPSelTransHandle const *) data
655         );
658 static void sp_sel_trans_handle_click(SPKnot *knot, guint state, gpointer data)
660     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleClick(
661         knot, state, *(SPSelTransHandle const *) data
662         );
665 void Inkscape::SelTrans::handleClick(SPKnot *knot, guint state, SPSelTransHandle const &handle)
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     }
687 void Inkscape::SelTrans::handleGrab(SPKnot *knot, guint state, SPSelTransHandle const &handle)
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);
712 void Inkscape::SelTrans::handleNewEvent(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
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);
730 gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
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;
764 void Inkscape::SelTrans::_selChanged(Inkscape::Selection *selection)
766     if (!_grabbed) {
767         _updateVolatileState();
768         _current.set_identity();
769         _center_is_set = false; // center(s) may have changed
770         _updateHandles();
771     }
774 void Inkscape::SelTrans::_selModified(Inkscape::Selection *selection, guint flags)
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     }
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)
796     return ( x < 0
797              ? -1
798              : 1 );
801 gboolean sp_sel_trans_scale_request(Inkscape::SelTrans *seltrans,
802                                     SPSelTransHandle const &, NR::Point &pt, guint state)
804     return seltrans->scaleRequest(pt, state);
807 gboolean sp_sel_trans_stretch_request(Inkscape::SelTrans *seltrans,
808                                       SPSelTransHandle const &handle, NR::Point &pt, guint state)
810     return seltrans->stretchRequest(handle, pt, state);
813 gboolean sp_sel_trans_skew_request(Inkscape::SelTrans *seltrans,
814                                    SPSelTransHandle const &handle, NR::Point &pt, guint state)
816     return seltrans->skewRequest(handle, pt, state);
819 gboolean sp_sel_trans_rotate_request(Inkscape::SelTrans *seltrans,
820                                      SPSelTransHandle const &, NR::Point &pt, guint state)
822     return seltrans->rotateRequest(pt, state);
825 gboolean sp_sel_trans_center_request(Inkscape::SelTrans *seltrans,
826                                      SPSelTransHandle const &, NR::Point &pt, guint state)
828     return seltrans->centerRequest(pt, state);
831 gboolean Inkscape::SelTrans::scaleRequest(NR::Point &pt, guint state)
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         */
863         
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 */
909         
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;
944 gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
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 {
1016         
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;
1056 gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
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);
1120         
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&#176;; with <b>Ctrl</b> to snap angle"),
1141                           degrees);
1143     return TRUE;
1146 gboolean Inkscape::SelTrans::rotateRequest(NR::Point &pt, guint state)
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&#176;; with <b>Ctrl</b> to snap angle"), degrees);
1191     return TRUE;
1194 gboolean Inkscape::SelTrans::centerRequest(NR::Point &pt, guint state)
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;
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)
1246     seltrans->stretch(handle, pt, state);
1249 void sp_sel_trans_scale(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1251     seltrans->scale(pt, state);
1254 void sp_sel_trans_skew(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state)
1256     seltrans->skew(handle, pt, state);
1259 void sp_sel_trans_rotate(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1261     seltrans->rotate(pt, state);
1264 void Inkscape::SelTrans::stretch(SPSelTransHandle const &handle, NR::Point &pt, guint state)
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
1318 void Inkscape::SelTrans::scale(NR::Point &pt, guint state)
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
1343 void Inkscape::SelTrans::skew(SPSelTransHandle const &handle, NR::Point &pt, guint state)
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);
1377 void Inkscape::SelTrans::rotate(NR::Point &pt, guint state)
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);
1398 void sp_sel_trans_center(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1400     seltrans->setCenter(pt);
1404 void Inkscape::SelTrans::moveTo(NR::Point const &xy, guint state)
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));
1451                             
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);
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 :