Code

Fix for bug #1589436 - font baseline now correctly snaps in selector tool
[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->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"));
277     
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     */
285     
286     if (_box) {
287         _opposite_for_bboxpoints = _box->min() + _box->dimensions() * NR::scale(1-x, 1-y);
288         
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     }
306     
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);
321 void Inkscape::SelTrans::transform(NR::Matrix const &rel_affine, NR::Point const &norm)
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();
353 void Inkscape::SelTrans::ungrab()
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     }
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()
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 = &copy_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     }
510 void Inkscape::SelTrans::_updateHandles()
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     }
571 void Inkscape::SelTrans::_updateVolatileState()
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());
589 static void sp_remove_handles(SPKnot *knot[], gint num)
591     for (int i = 0; i < num; i++) {
592         if (knot[i] != NULL) {
593             sp_knot_hide(knot[i]);
594         }
595     }
598 void Inkscape::SelTrans::_showHandles(SPKnot *knot[], SPSelTransHandle const handle[], gint num,
599                              gchar const *even_tip, gchar const *odd_tip)
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     }
639 static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data)
641     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleGrab(
642         knot, state, *(SPSelTransHandle const *) data
643         );
646 static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, gpointer data)
648     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->ungrab();
651 static void sp_sel_trans_handle_new_event(SPKnot *knot, NR::Point *position, guint state, gpointer data)
653     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleNewEvent(
654         knot, position, state, *(SPSelTransHandle const *) data
655         );
658 static gboolean sp_sel_trans_handle_request(SPKnot *knot, NR::Point *position, guint state, gboolean *data)
660     return SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleRequest(
661         knot, position, state, *(SPSelTransHandle const *) data
662         );
665 static void sp_sel_trans_handle_click(SPKnot *knot, guint state, gpointer data)
667     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleClick(
668         knot, state, *(SPSelTransHandle const *) data
669         );
672 void Inkscape::SelTrans::handleClick(SPKnot *knot, guint state, SPSelTransHandle const &handle)
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     }
694 void Inkscape::SelTrans::handleGrab(SPKnot *knot, guint state, SPSelTransHandle const &handle)
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);
719 void Inkscape::SelTrans::handleNewEvent(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
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);
737 gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, NR::Point *position, guint state, SPSelTransHandle const &handle)
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;
771 void Inkscape::SelTrans::_selChanged(Inkscape::Selection *selection)
773     if (!_grabbed) {
774         _updateVolatileState();
775         _current.set_identity();
776         _center_is_set = false; // center(s) may have changed
777         _updateHandles();
778     }
781 void Inkscape::SelTrans::_selModified(Inkscape::Selection *selection, guint flags)
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     }
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)
803     return ( x < 0
804              ? -1
805              : 1 );
808 gboolean sp_sel_trans_scale_request(Inkscape::SelTrans *seltrans,
809                                     SPSelTransHandle const &, NR::Point &pt, guint state)
811     return seltrans->scaleRequest(pt, state);
814 gboolean sp_sel_trans_stretch_request(Inkscape::SelTrans *seltrans,
815                                       SPSelTransHandle const &handle, NR::Point &pt, guint state)
817     return seltrans->stretchRequest(handle, pt, state);
820 gboolean sp_sel_trans_skew_request(Inkscape::SelTrans *seltrans,
821                                    SPSelTransHandle const &handle, NR::Point &pt, guint state)
823     return seltrans->skewRequest(handle, pt, state);
826 gboolean sp_sel_trans_rotate_request(Inkscape::SelTrans *seltrans,
827                                      SPSelTransHandle const &, NR::Point &pt, guint state)
829     return seltrans->rotateRequest(pt, state);
832 gboolean sp_sel_trans_center_request(Inkscape::SelTrans *seltrans,
833                                      SPSelTransHandle const &, NR::Point &pt, guint state)
835     return seltrans->centerRequest(pt, state);
838 gboolean Inkscape::SelTrans::scaleRequest(NR::Point &pt, guint state)
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         ** To do this, we snap along a suitable constraint vector from the origin.
869         */
870         
871         // The inclination of the constraint vector is calculated from the aspect ratio
872         NR::Point bbox_dim = _box->dimensions();
873         double const aspect_ratio = bbox_dim[1] / bbox_dim[0]; // = height / width
875         // Determine direction of the constraint vector
876         NR::Point const cv = NR::Point(
877             pt[NR::X] > _origin[NR::X] ? 1 : -1,
878             pt[NR::Y] > _origin[NR::Y] ? aspect_ratio : -aspect_ratio
879             );
881         std::pair<NR::scale, bool> bb = m.constrainedSnapScale(Snapper::BBOX_POINT,
882                                                                _bbox_points,
883                                                                it,
884                                                                Snapper::ConstraintLine(_origin_for_bboxpoints, cv),
885                                                                s,
886                                                                _origin_for_bboxpoints);
888         std::pair<NR::scale, bool> sn = m.constrainedSnapScale(Snapper::SNAP_POINT,
889                                                                _snap_points,
890                                                                it,
891                                                                Snapper::ConstraintLine(_origin_for_snappoints, cv),
892                                                                s,
893                                                                _origin_for_snappoints);
895         if (bb.second == false && sn.second == false) {
897             /* We didn't snap, so just lock aspect ratio */
898             if (fabs(s[NR::X]) > fabs(s[NR::Y])) {
899                 s[NR::X] = fabs(s[NR::Y]) * sign(s[NR::X]);
900             } else {
901                 s[NR::Y] = fabs(s[NR::X]) * sign(s[NR::Y]);
902             }
904         } else {
906             /* Choose the smaller difference in scale.  Since s[X] == s[Y] we can
907             ** just compare difference in s[X].
908             */
909             double const bd = bb.second ? fabs(bb.first[NR::X] - s[NR::X]) : NR_HUGE;
910             double const sd = sn.second ? fabs(sn.first[NR::X] - s[NR::X]) : NR_HUGE;
911             s = (bd < sd) ? bb.first : sn.first;
912         }
914     } else {
915         /* Scale aspect ratio is unlocked */
916         
917         std::pair<NR::scale, bool> bb = m.freeSnapScale(Snapper::BBOX_POINT,
918                                                         _bbox_points,
919                                                         it,
920                                                         s,
921                                                         _origin_for_bboxpoints);
922         std::pair<NR::scale, bool> sn = m.freeSnapScale(Snapper::SNAP_POINT,
923                                                         _snap_points,
924                                                         it,
925                                                         s,
926                                                         _origin_for_snappoints);
928         /* Pick the snap that puts us closest to the original scale */
929         NR::Coord bd = bb.second ?
930             fabs(NR::L2(NR::Point(bb.first[NR::X], bb.first[NR::Y])) -
931                  NR::L2(NR::Point(s[NR::X], s[NR::Y])))
932             : NR_HUGE;
933         NR::Coord sd = sn.second ?
934             fabs(NR::L2(NR::Point(sn.first[NR::X], sn.first[NR::Y])) -
935                  NR::L2(NR::Point(s[NR::X], s[NR::Y])))
936             : NR_HUGE;
937         s = (bd < sd) ? bb.first : sn.first;
938     }
940     /* Update the knot position */
941     pt = ( _point - _origin ) * s + _origin;
943     /* Status text */
944     _message_context.setF(Inkscape::NORMAL_MESSAGE,
945                           _("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>Ctrl</b> to lock ratio"),
946                           100 * s[NR::X], 100 * s[NR::Y]);
948     return TRUE;
951 gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
953     using NR::X;
954     using NR::Y;
956     NR::Dim2 axis, perp;
958     switch (handle.cursor) {
959         case GDK_TOP_SIDE:
960         case GDK_BOTTOM_SIDE:
961            axis = NR::Y;
962            perp = NR::X;
963            break;
964         case GDK_LEFT_SIDE:
965         case GDK_RIGHT_SIDE:
966            axis = NR::X;
967            perp = NR::Y;
968            break;
969         default:
970             g_assert_not_reached();
971             return TRUE;
972     };
974     if ( fabs( _point[axis] - _origin[axis] ) < 1e-15 ) {
975         return FALSE;
976     }
978     NR::scale s(1, 1);
979     s[axis] = ( ( pt[axis] - _origin[axis] )
980                 / ( _point[axis] - _origin[axis] ) );
981     if ( fabs(s[axis]) < 1e-15 ) {
982         s[axis] = 1e-15;
983     }
985     /* Get a STL list of the selected items.
986     ** FIXME: this should probably be done by Inkscape::Selection.
987     */
988     std::list<SPItem const*> it;
989     for (GSList const *i = _selection->itemList(); i != NULL; i = i->next) {
990         it.push_back(reinterpret_cast<SPItem*>(i->data));
991     }
993     SnapManager const &m = _desktop->namedview->snap_manager;
995     if ( state & GDK_CONTROL_MASK ) {
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 {
1023         
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;
1063 gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, NR::Point &pt, guint state)
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);
1127         
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&#176;; with <b>Ctrl</b> to snap angle"),
1148                           degrees);
1150     return TRUE;
1153 gboolean Inkscape::SelTrans::rotateRequest(NR::Point &pt, guint state)
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&#176;; with <b>Ctrl</b> to snap angle"), degrees);
1198     return TRUE;
1201 gboolean Inkscape::SelTrans::centerRequest(NR::Point &pt, guint state)
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;
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)
1253     seltrans->stretch(handle, pt, state);
1256 void sp_sel_trans_scale(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1258     seltrans->scale(pt, state);
1261 void sp_sel_trans_skew(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, NR::Point &pt, guint state)
1263     seltrans->skew(handle, pt, state);
1266 void sp_sel_trans_rotate(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1268     seltrans->rotate(pt, state);
1271 void Inkscape::SelTrans::stretch(SPSelTransHandle const &handle, NR::Point &pt, guint state)
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
1325 void Inkscape::SelTrans::scale(NR::Point &pt, guint state)
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
1350 void Inkscape::SelTrans::skew(SPSelTransHandle const &handle, NR::Point &pt, guint state)
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);
1384 void Inkscape::SelTrans::rotate(NR::Point &pt, guint state)
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);
1405 void sp_sel_trans_center(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, NR::Point &pt, guint state)
1407     seltrans->setCenter(pt);
1411 void Inkscape::SelTrans::moveTo(NR::Point const &xy, guint state)
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));
1458                             
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);
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 :