Code

b0e19e60c5d9747835c2a9ea498c8621bded7c74
[inkscape.git] / src / seltrans.cpp
1 /** @file
2  * @brief Helper object for transforming selected items
3  */
4 /* Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   bulia byak <buliabyak@users.sf.net>
7  *   Carl Hetherington <inkscape@carlh.net>
8  *   Diederik van Lierop <mail@diedenrezi.nl>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 1999-2002 Lauris Kaplinski
12  * Copyright (C) 1999-2008 Authors
13  *
14  * Released under GNU GPL, read the file 'COPYING' for more information
15  */
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <cstring>
21 #include <string>
23 #include <2geom/transforms.h>
24 #include <gdk/gdkkeysyms.h>
25 #include "document.h"
26 #include "sp-namedview.h"
27 #include "desktop.h"
28 #include "desktop-handles.h"
29 #include "desktop-style.h"
30 #include "knot.h"
31 #include "snap.h"
32 #include "selection.h"
33 #include "select-context.h"
34 #include "sp-item.h"
35 #include "sp-item-transform.h"
36 #include "seltrans-handles.h"
37 #include "seltrans.h"
38 #include "selection-chemistry.h"
39 #include "sp-metrics.h"
40 #include "verbs.h"
41 #include <glibmm/i18n.h>
42 #include "display/sp-ctrlline.h"
43 #include "preferences.h"
44 #include "xml/repr.h"
45 #include "mod360.h"
46 #include <2geom/angle.h>
47 #include "display/snap-indicator.h"
49 using Inkscape::DocumentUndo;
51 static void sp_remove_handles(SPKnot *knot[], gint num);
53 static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data);
54 static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint state, gpointer data);
55 static void sp_sel_trans_handle_click(SPKnot *knot, guint state, gpointer data);
56 static void sp_sel_trans_handle_new_event(SPKnot *knot, Geom::Point *position, guint32 state, gpointer data);
57 static gboolean sp_sel_trans_handle_request(SPKnot *knot, Geom::Point *p, guint state, gboolean *data);
59 extern GdkPixbuf *handles[];
61 static gboolean sp_seltrans_handle_event(SPKnot *knot, GdkEvent *event, gpointer)
62 {
63     switch (event->type) {
64         case GDK_MOTION_NOTIFY:
65             break;
66         case GDK_KEY_PRESS:
67             if (get_group0_keyval (&event->key) == GDK_space) {
68                 /* stamping mode: both mode(show content and outline) operation with knot */
69                 if (!SP_KNOT_IS_GRABBED(knot)) {
70                     return FALSE;
71                 }
72                 SPDesktop *desktop = knot->desktop;
73                 Inkscape::SelTrans *seltrans = SP_SELECT_CONTEXT(desktop->event_context)->_seltrans;
74                 seltrans->stamp();
75                 return TRUE;
76             }
77             break;
78         default:
79             break;
80     }
82     return FALSE;
83 }
85 Inkscape::SelTrans::SelTrans(SPDesktop *desktop) :
86     _desktop(desktop),
87     _selcue(desktop),
88     _state(STATE_SCALE),
89     _show(SHOW_CONTENT),
90     _grabbed(false),
91     _show_handles(true),
92     _bbox(),
93     _approximate_bbox(),
94     _absolute_affine(Geom::Scale(1,1)),
95     _opposite(Geom::Point(0,0)),
96     _opposite_for_specpoints(Geom::Point(0,0)),
97     _opposite_for_bboxpoints(Geom::Point(0,0)),
98     _origin_for_specpoints(Geom::Point(0,0)),
99     _origin_for_bboxpoints(Geom::Point(0,0)),
100     _chandle(NULL),
101     _stamp_cache(NULL),
102     _message_context(desktop->messageStack())
104     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
105     int prefs_bbox = prefs->getBool("/tools/bounding_box");
106     _snap_bbox_type = !prefs_bbox ?
107         SPItem::APPROXIMATE_BBOX : SPItem::GEOMETRIC_BBOX;
109     g_return_if_fail(desktop != NULL);
111     for (int i = 0; i < 8; i++) {
112         _shandle[i] = NULL;
113         _rhandle[i] = NULL;
114     }
116     _updateVolatileState();
117     _current_relative_affine.setIdentity();
119     _center_is_set = false; // reread _center from items, or set to bbox midpoint
121     _updateHandles();
123     _selection = sp_desktop_selection(desktop);
125     _norm = sp_canvas_item_new(sp_desktop_controls(desktop),
126                                SP_TYPE_CTRL,
127                                "anchor", GTK_ANCHOR_CENTER,
128                                "mode", SP_CTRL_MODE_COLOR,
129                                "shape", SP_CTRL_SHAPE_BITMAP,
130                                "size", 13.0,
131                                "filled", TRUE,
132                                "fill_color", 0x00000000,
133                                "stroked", TRUE,
134                                "stroke_color", 0x000000a0,
135                                "pixbuf", handles[12],
136                                NULL);
138     _grip = sp_canvas_item_new(sp_desktop_controls(desktop),
139                                SP_TYPE_CTRL,
140                                "anchor", GTK_ANCHOR_CENTER,
141                                "mode", SP_CTRL_MODE_XOR,
142                                "shape", SP_CTRL_SHAPE_CROSS,
143                                "size", 7.0,
144                                "filled", TRUE,
145                                "fill_color", 0xffffff7f,
146                                "stroked", TRUE,
147                                "stroke_color", 0xffffffff,
148                                "pixbuf", handles[12],
149                                NULL);
151     sp_canvas_item_hide(_grip);
152     sp_canvas_item_hide(_norm);
154     for (int i = 0; i < 4; i++) {
155         _l[i] = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLLINE, NULL);
156         sp_canvas_item_hide(_l[i]);
157     }
159     _sel_changed_connection = _selection->connectChanged(
160         sigc::mem_fun(*this, &Inkscape::SelTrans::_selChanged)
161         );
163     _sel_modified_connection = _selection->connectModified(
164         sigc::mem_fun(*this, &Inkscape::SelTrans::_selModified)
165         );
168 Inkscape::SelTrans::~SelTrans()
170     _sel_changed_connection.disconnect();
171     _sel_modified_connection.disconnect();
173     for (unsigned int i = 0; i < 8; i++) {
174         if (_shandle[i]) {
175             g_object_unref(G_OBJECT(_shandle[i]));
176             _shandle[i] = NULL;
177         }
178         if (_rhandle[i]) {
179             g_object_unref(G_OBJECT(_rhandle[i]));
180             _rhandle[i] = NULL;
181         }
182     }
183     if (_chandle) {
184         g_object_unref(G_OBJECT(_chandle));
185         _chandle = NULL;
186     }
188     if (_norm) {
189         gtk_object_destroy(GTK_OBJECT(_norm));
190         _norm = NULL;
191     }
192     if (_grip) {
193         gtk_object_destroy(GTK_OBJECT(_grip));
194         _grip = NULL;
195     }
196     for (int i = 0; i < 4; i++) {
197         if (_l[i]) {
198             gtk_object_destroy(GTK_OBJECT(_l[i]));
199             _l[i] = NULL;
200         }
201     }
203     for (unsigned i = 0; i < _items.size(); i++) {
204         sp_object_unref(SP_OBJECT(_items[i]), NULL);
205     }
207     _items.clear();
208     _items_const.clear();
209     _items_affines.clear();
210     _items_centers.clear();
213 void Inkscape::SelTrans::resetState()
215     _state = STATE_SCALE;
218 void Inkscape::SelTrans::increaseState()
220     if (_state == STATE_SCALE) {
221         _state = STATE_ROTATE;
222     } else {
223         _state = STATE_SCALE;
224     }
226     _center_is_set = true; // no need to reread center
228     _updateHandles();
231 void Inkscape::SelTrans::setCenter(Geom::Point const &p)
233     _center = p;
234     _center_is_set = true;
236     // Write the new center position into all selected items
237     for (GSList const *l = _desktop->selection->itemList(); l; l = l->next) {
238         SPItem *it = (SPItem*)SP_OBJECT(l->data);
239         it->setCenter(p);
240         // only set the value; updating repr and document_done will be done once, on ungrab
241     }
243     _updateHandles();
246 void Inkscape::SelTrans::grab(Geom::Point const &p, gdouble x, gdouble y, bool show_handles, bool translating)
248     // While dragging a handle, we will either scale, skew, or rotate and the "translating" parameter will be false
249     // When dragging the selected item itself however, we will translate the selection and that parameter will be true
250     Inkscape::Selection *selection = sp_desktop_selection(_desktop);
251     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
253     g_return_if_fail(!_grabbed);
255     _grabbed = true;
256     _show_handles = show_handles;
257     _updateVolatileState();
258     _current_relative_affine.setIdentity();
260     _changed = false;
262     if (_empty) {
263         return;
264     }
266     for (GSList const *l = selection->itemList(); l; l = l->next) {
267         SPItem *it = (SPItem *)sp_object_ref(SP_OBJECT(l->data), NULL);
268         _items.push_back(it);
269         _items_const.push_back(it);
270         _items_affines.push_back(it->i2d_affine());
271         _items_centers.push_back(it->getCenter()); // for content-dragging, we need to remember original centers
272     }
274     _handle_x = x;
275     _handle_y = y;
277     // The selector tool should snap the bbox, special snappoints, and path nodes
278     // (The special points are the handles, center, rotation axis, font baseline, ends of spiral, etc.)
280     // First, determine the bounding box
281     _bbox = selection->bounds(_snap_bbox_type);
282     _approximate_bbox = selection->bounds(SPItem::APPROXIMATE_BBOX); // Used for correctly scaling the strokewidth
283     _geometric_bbox = selection->bounds(SPItem::GEOMETRIC_BBOX);
285     _point = p;
286     if (_geometric_bbox) {
287         _point_geom = _geometric_bbox->min() + _geometric_bbox->dimensions() * Geom::Scale(x, y);
288     } else {
289         _point_geom = p;
290     }
292     // Next, get all points to consider for snapping
293     SnapManager const &m = _desktop->namedview->snap_manager;
294     _snap_points.clear();
295     _snap_points = selection->getSnapPoints(&m.snapprefs);
296     std::vector<Inkscape::SnapCandidatePoint> snap_points_hull = selection->getSnapPointsConvexHull(&m.snapprefs);
297     if (_snap_points.size() > 200) {
298         /* Snapping a huge number of nodes will take way too long, so limit the number of snappable nodes
299         An average user would rarely ever try to snap such a large number of nodes anyway, because
300         (s)he could hardly discern which node would be snapping */
301         if (prefs->getBool("/options/snapclosestonly/value", false)) {
302             _keepClosestPointOnly(_snap_points, p);
303         } else {
304             _snap_points = snap_points_hull;
305         }
306         // Unfortunately, by now we will have lost the font-baseline snappoints :-(
307     }
309     // Find bbox hulling all special points, which excludes stroke width. Here we need to include the
310     // path nodes, for example because a rectangle which has been converted to a path doesn't have
311     // any other special points
312     Geom::Rect snap_points_bbox;
313     if ( snap_points_hull.empty() == false ) {
314         std::vector<Inkscape::SnapCandidatePoint>::iterator i = snap_points_hull.begin();
315         snap_points_bbox = Geom::Rect((*i).getPoint(), (*i).getPoint());
316         i++;
317         while (i != snap_points_hull.end()) {
318             snap_points_bbox.expandTo((*i).getPoint());
319             i++;
320         }
321     }
323     _bbox_points.clear();
324     _bbox_points_for_translating.clear();
325     // Collect the bounding box's corners and midpoints for each selected item
326     if (m.snapprefs.getSnapModeBBox()) {
327         bool mp = m.snapprefs.getSnapBBoxMidpoints();
328         bool emp = m.snapprefs.getSnapBBoxEdgeMidpoints();
329         // Preferably we'd use the bbox of each selected item, instead of the bbox of the selection as a whole; for translations
330         // this is easy to do, but when snapping the visual bbox while scaling we will have to compensate for the scaling of the
331         // stroke width. (see get_scale_transform_with_stroke()). This however is currently only implemented for a single bbox.
332         // That's why we have both _bbox_points_for_translating and _bbox_points.
333         getBBoxPoints(selection->bounds(_snap_bbox_type), &_bbox_points, false, true, emp, mp);
334         if (((_items.size() > 0) && (_items.size() < 50)) || prefs->getBool("/options/snapclosestonly/value", false)) {
335             // More than 50 items will produce at least 200 bbox points, which might make Inkscape crawl
336             // (see the comment a few lines above). In that case we will use the bbox of the selection as a whole
337             for (unsigned i = 0; i < _items.size(); i++) {
338                 getBBoxPoints(_items[i]->getBboxDesktop(_snap_bbox_type), &_bbox_points_for_translating, false, true, emp, mp);
339             }
340         } else {
341             _bbox_points_for_translating = _bbox_points; // use the bbox points of the selection as a whole
342         }
343     }
345     if (_bbox) {
346         // There are two separate "opposites" (i.e. opposite w.r.t. the handle being dragged):
347         //  - one for snapping the boundingbox, which can be either visual or geometric
348         //  - one for snapping the special points
349         // The "opposite" in case of a geometric boundingbox always coincides with the "opposite" for the special points
350         // These distinct "opposites" are needed in the snapmanager to avoid bugs such as #sf1540195 (in which
351         // a box is caught between two guides)
352         _opposite_for_bboxpoints = _bbox->min() + _bbox->dimensions() * Geom::Scale(1-x, 1-y);
353         _opposite_for_specpoints = snap_points_bbox.min() + snap_points_bbox.dimensions() * Geom::Scale(1-x, 1-y);
354         _opposite = _opposite_for_bboxpoints;
355     }
357     // When snapping the node closest to the mouse pointer is absolutely preferred over the closest snap
358     // (i.e. when weight == 1), then we will not even try to snap to other points and discard those other
359     // points immediately.
361     if (prefs->getBool("/options/snapclosestonly/value", false)) {
362         if (m.snapprefs.getSnapModeNode()) {
363             _keepClosestPointOnly(_snap_points, p);
364         } else {
365             _snap_points.clear(); // don't keep any point
366         }
368         if (m.snapprefs.getSnapModeBBox()) {
369             _keepClosestPointOnly(_bbox_points, p);
370             _keepClosestPointOnly(_bbox_points_for_translating, p);
371         } else {
372             _bbox_points.clear(); // don't keep any point
373             _bbox_points_for_translating.clear();
374         }
376         // Each of the three vectors of snappoints now contains either one snappoint or none at all.
377         if (_snap_points.size() > 1 || _bbox_points.size() > 1 || _bbox_points_for_translating.size() > 1) {
378             g_warning("Incorrect assumption encountered while finding the snap source; nothing serious, but please report to Diederik");
379         }
381         // Now let's reduce this to a single closest snappoint
382         Geom::Coord dsp    = _snap_points.size()                 == 1 ? Geom::L2((_snap_points.at(0)).getPoint() - p) : NR_HUGE;
383         Geom::Coord dbbp   = _bbox_points.size()                 == 1 ? Geom::L2((_bbox_points.at(0)).getPoint() - p) : NR_HUGE;
384         Geom::Coord dbbpft = _bbox_points_for_translating.size() == 1 ? Geom::L2((_bbox_points_for_translating.at(0)).getPoint() - p) : NR_HUGE;
386         if (translating) {
387             _bbox_points.clear();
388             if (dsp > dbbpft) {
389                 _snap_points.clear();
390             } else {
391                 _bbox_points_for_translating.clear();
392             }
393         } else {
394             _bbox_points_for_translating.clear();
395             if (dsp > dbbp) {
396                 _snap_points.clear();
397             } else {
398                 _bbox_points.clear();
399             }
400         }
402         if ((_snap_points.size() + _bbox_points.size() + _bbox_points_for_translating.size()) > 1) {
403             g_warning("Checking number of snap sources failed; nothing serious, but please report to Diederik");
404         }
406     }
408     if ((x != -1) && (y != -1)) {
409         sp_canvas_item_show(_norm);
410         sp_canvas_item_show(_grip);
411     }
413     if (_show == SHOW_OUTLINE) {
414         for (int i = 0; i < 4; i++)
415             sp_canvas_item_show(_l[i]);
416     }
418     _updateHandles();
419     g_return_if_fail(_stamp_cache == NULL);
422 void Inkscape::SelTrans::transform(Geom::Matrix const &rel_affine, Geom::Point const &norm)
424     g_return_if_fail(_grabbed);
425     g_return_if_fail(!_empty);
427     Geom::Matrix const affine( Geom::Translate(-norm) * rel_affine * Geom::Translate(norm) );
429     if (_show == SHOW_CONTENT) {
430         // update the content
431         for (unsigned i = 0; i < _items.size(); i++) {
432             SPItem &item = *_items[i];
433             Geom::Matrix const &prev_transform = _items_affines[i];
434             item.set_i2d_affine(prev_transform * affine);
435         }
436     } else {
437         if (_bbox) {
438             Geom::Point p[4];
439             /* update the outline */
440             for (unsigned i = 0 ; i < 4 ; i++) {
441                 p[i] = _bbox->corner(i) * affine;
442             }
443             for (unsigned i = 0 ; i < 4 ; i++) {
444                 sp_ctrlline_set_coords(SP_CTRLLINE(_l[i]), p[i], p[(i+1)%4]);
445             }
446         }
447     }
449     _current_relative_affine = affine;
450     _changed = true;
451     _updateHandles();
454 void Inkscape::SelTrans::ungrab()
456     g_return_if_fail(_grabbed);
457     _grabbed = false;
458     _show_handles = true;
460     _desktop->snapindicator->remove_snapsource();
462     Inkscape::Selection *selection = sp_desktop_selection(_desktop);
463     _updateVolatileState();
465     for (unsigned i = 0; i < _items.size(); i++) {
466         sp_object_unref(SP_OBJECT(_items[i]), NULL);
467     }
469     sp_canvas_item_hide(_norm);
470     sp_canvas_item_hide(_grip);
472     if (_show == SHOW_OUTLINE) {
473         for (int i = 0; i < 4; i++)
474             sp_canvas_item_hide(_l[i]);
475     }
477     if (_stamp_cache) {
478         g_slist_free(_stamp_cache);
479         _stamp_cache = NULL;
480     }
482     _message_context.clear();
484     if (!_empty && _changed) {
485         sp_selection_apply_affine(selection, _current_relative_affine, (_show == SHOW_OUTLINE)? true : false);
486         if (_center) {
487             *_center *= _current_relative_affine;
488             _center_is_set = true;
489         }
491 // If dragging showed content live, sp_selection_apply_affine cannot change the centers
492 // appropriately - it does not know the original positions of the centers (all objects already have
493 // the new bboxes). So we need to reset the centers from our saved array.
494         if (_show != SHOW_OUTLINE && !_current_relative_affine.isTranslation()) {
495             for (unsigned i = 0; i < _items_centers.size(); i++) {
496                 SPItem *currentItem = _items[i];
497                 if (currentItem->isCenterSet()) { // only if it's already set
498                     currentItem->setCenter (_items_centers[i] * _current_relative_affine);
499                     SP_OBJECT(currentItem)->updateRepr();
500                 }
501             }
502         }
504         _items.clear();
505         _items_const.clear();
506         _items_affines.clear();
507         _items_centers.clear();
509         if (_current_relative_affine.isTranslation()) {
510             DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
511                                _("Move"));
512         } else if (_current_relative_affine.without_translation().isScale()) {
513             DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
514                                _("Scale"));
515         } else if (_current_relative_affine.without_translation().isRotation()) {
516             DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
517                                _("Rotate"));
518         } else {
519             DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
520                                _("Skew"));
521         }
523     } else {
525         if (_center_is_set) {
526             // we were dragging center; update reprs and commit undoable action
527             for (GSList const *l = _desktop->selection->itemList(); l; l = l->next) {
528                 SPItem *it = (SPItem*)SP_OBJECT(l->data);
529                 SP_OBJECT(it)->updateRepr();
530             }
531             DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
532                                _("Set center"));
533         }
535         _items.clear();
536         _items_const.clear();
537         _items_affines.clear();
538         _items_centers.clear();
539         _updateHandles();
540     }
543 /* fixme: This is really bad, as we compare positions for each stamp (Lauris) */
544 /* fixme: IMHO the best way to keep sort cache would be to implement timestamping at last */
546 void Inkscape::SelTrans::stamp()
548     Inkscape::Selection *selection = sp_desktop_selection(_desktop);
550     bool fixup = !_grabbed;
551     if ( fixup && _stamp_cache ) {
552         // TODO - give a proper fix. Simple temproary work-around for the grab() issue
553         g_slist_free(_stamp_cache);
554         _stamp_cache = NULL;
555     }
557     /* stamping mode */
558     if (!_empty) {
559         GSList *l;
560         if (_stamp_cache) {
561             l = _stamp_cache;
562         } else {
563             /* Build cache */
564             l  = g_slist_copy((GSList *) selection->itemList());
565             l  = g_slist_sort(l, (GCompareFunc) sp_object_compare_position);
566             _stamp_cache = l;
567         }
569         while (l) {
570             SPItem *original_item = SP_ITEM(l->data);
571             Inkscape::XML::Node *original_repr = SP_OBJECT_REPR(original_item);
573             // remember the position of the item
574             gint pos = original_repr->position();
575             // remember parent
576             Inkscape::XML::Node *parent = sp_repr_parent(original_repr);
578             Inkscape::XML::Node *copy_repr = original_repr->duplicate(parent->document());
580             // add the new repr to the parent
581             parent->appendChild(copy_repr);
582             // move to the saved position
583             copy_repr->setPosition(pos > 0 ? pos : 0);
585             SPItem *copy_item = (SPItem *) sp_desktop_document(_desktop)->getObjectByRepr(copy_repr);
587             Geom::Matrix const *new_affine;
588             if (_show == SHOW_OUTLINE) {
589                 Geom::Matrix const i2d(original_item->i2d_affine());
590                 Geom::Matrix const i2dnew( i2d * _current_relative_affine );
591                 copy_item->set_i2d_affine(i2dnew);
592                 new_affine = &copy_item->transform;
593             } else {
594                 new_affine = &original_item->transform;
595             }
597             copy_item->doWriteTransform(copy_repr, *new_affine);
599             if ( copy_item->isCenterSet() && _center ) {
600                 copy_item->setCenter(*_center * _current_relative_affine);
601             }
603             Inkscape::GC::release(copy_repr);
604             l = l->next;
605         }
606         DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
607                            _("Stamp"));
608     }
610     if ( fixup && _stamp_cache ) {
611         // TODO - give a proper fix. Simple temproary work-around for the grab() issue
612         g_slist_free(_stamp_cache);
613         _stamp_cache = NULL;
614     }
617 void Inkscape::SelTrans::_updateHandles()
619     if ( !_show_handles || _empty )
620     {
621         sp_remove_handles(_shandle, 8);
622         sp_remove_handles(_rhandle, 8);
623         sp_remove_handles(&_chandle, 1);
624         return;
625     }
627     // center handle
628     if ( _chandle == NULL ) {
629         _chandle = sp_knot_new(_desktop, _("<b>Center</b> of rotation and skewing: drag to reposition; scaling with Shift also uses this center"));
631         _chandle->setShape (SP_CTRL_SHAPE_BITMAP);
632         _chandle->setSize (13);
633         _chandle->setAnchor (handle_center.anchor);
634         _chandle->setMode (SP_CTRL_MODE_XOR);
635         _chandle->setFill(0x00000000, 0x00000000, 0x00000000);
636         _chandle->setStroke(0x000000ff, 0xff0000b0, 0xff0000b0);
637         _chandle->setPixbuf(handles[handle_center.control]);
638         sp_knot_update_ctrl(_chandle);
640         g_signal_connect(G_OBJECT(_chandle), "request",
641                          G_CALLBACK(sp_sel_trans_handle_request), (gpointer) &handle_center);
642         g_signal_connect(G_OBJECT(_chandle), "moved",
643                          G_CALLBACK(sp_sel_trans_handle_new_event), (gpointer) &handle_center);
644         g_signal_connect(G_OBJECT(_chandle), "grabbed",
645                          G_CALLBACK(sp_sel_trans_handle_grab), (gpointer) &handle_center);
646         g_signal_connect(G_OBJECT(_chandle), "ungrabbed",
647                          G_CALLBACK(sp_sel_trans_handle_ungrab), (gpointer) &handle_center);
648         g_signal_connect(G_OBJECT(_chandle), "clicked",
649                          G_CALLBACK(sp_sel_trans_handle_click), (gpointer) &handle_center);
650     }
652     sp_remove_handles(&_chandle, 1);
653     if ( _state == STATE_SCALE ) {
654         sp_remove_handles(_rhandle, 8);
655         _showHandles(_shandle, handles_scale, 8,
656                     _("<b>Squeeze or stretch</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"),
657                     _("<b>Scale</b> selection; with <b>Ctrl</b> to scale uniformly; with <b>Shift</b> to scale around rotation center"));
658     } else {
659         sp_remove_handles(_shandle, 8);
660         _showHandles(_rhandle, handles_rotate, 8,
661                     _("<b>Skew</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to skew around the opposite side"),
662                     _("<b>Rotate</b> selection; with <b>Ctrl</b> to snap angle; with <b>Shift</b> to rotate around the opposite corner"));
663     }
665     if (!_center_is_set) {
666         _center = _desktop->selection->center();
667         _center_is_set = true;
668     }
670     if ( _state == STATE_SCALE || !_center ) {
671         sp_knot_hide(_chandle);
672     } else {
673         sp_knot_show(_chandle);
674         sp_knot_moveto(_chandle, *_center);
675     }
678 void Inkscape::SelTrans::_updateVolatileState()
680     Inkscape::Selection *selection = sp_desktop_selection(_desktop);
681     _empty = selection->isEmpty();
683     if (_empty) {
684         return;
685     }
687     //Update the bboxes
688     _bbox = selection->bounds(_snap_bbox_type);
689     _approximate_bbox = selection->bounds(SPItem::APPROXIMATE_BBOX);
691     if (!_bbox) {
692         _empty = true;
693         return;
694     }
696     _strokewidth = stroke_average_width (selection->itemList());
699 static void sp_remove_handles(SPKnot *knot[], gint num)
701     for (int i = 0; i < num; i++) {
702         if (knot[i] != NULL) {
703             sp_knot_hide(knot[i]);
704         }
705     }
708 void Inkscape::SelTrans::_showHandles(SPKnot *knot[], SPSelTransHandle const handle[], gint num,
709                              gchar const *even_tip, gchar const *odd_tip)
711     g_return_if_fail( !_empty );
713     for (int i = 0; i < num; i++) {
714         if (knot[i] == NULL) {
715             knot[i] = sp_knot_new(_desktop, i % 2 ? even_tip : odd_tip);
717             knot[i]->setShape (SP_CTRL_SHAPE_BITMAP);
718             knot[i]->setSize (13);
719             knot[i]->setAnchor (handle[i].anchor);
720             knot[i]->setMode (SP_CTRL_MODE_XOR);
721             knot[i]->setFill(0x000000ff, 0x00ff6600, 0x00ff6600); // inversion, green, green
722             knot[i]->setStroke(0x000000ff, 0x000000ff, 0x000000ff); // inversion
723             knot[i]->setPixbuf(handles[handle[i].control]);
724             sp_knot_update_ctrl(knot[i]);
726             g_signal_connect(G_OBJECT(knot[i]), "request",
727                              G_CALLBACK(sp_sel_trans_handle_request), (gpointer) &handle[i]);
728             g_signal_connect(G_OBJECT(knot[i]), "moved",
729                              G_CALLBACK(sp_sel_trans_handle_new_event), (gpointer) &handle[i]);
730             g_signal_connect(G_OBJECT(knot[i]), "grabbed",
731                              G_CALLBACK(sp_sel_trans_handle_grab), (gpointer) &handle[i]);
732             g_signal_connect(G_OBJECT(knot[i]), "ungrabbed",
733                              G_CALLBACK(sp_sel_trans_handle_ungrab), (gpointer) &handle[i]);
734             g_signal_connect(G_OBJECT(knot[i]), "event", G_CALLBACK(sp_seltrans_handle_event), (gpointer) &handle[i]);
735         }
736         sp_knot_show(knot[i]);
738         Geom::Point const handle_pt(handle[i].x, handle[i].y);
739         // shouldn't have nullary bbox, but knots
740         g_assert(_bbox);
741         Geom::Point p( _bbox->min()
742                      + ( _bbox->dimensions()
743                          * Geom::Scale(handle_pt) ) );
745         sp_knot_moveto(knot[i], p);
746     }
749 static void sp_sel_trans_handle_grab(SPKnot *knot, guint state, gpointer data)
751     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleGrab(
752         knot, state, *(SPSelTransHandle const *) data
753         );
756 static void sp_sel_trans_handle_ungrab(SPKnot *knot, guint /*state*/, gpointer /*data*/)
758     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->ungrab();
761 static void sp_sel_trans_handle_new_event(SPKnot *knot, Geom::Point *position, guint state, gpointer data)
763     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleNewEvent(
764         knot, position, state, *(SPSelTransHandle const *) data
765         );
768 static gboolean sp_sel_trans_handle_request(SPKnot *knot, Geom::Point *position, guint state, gboolean *data)
770     return SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleRequest(
771         knot, position, state, *(SPSelTransHandle const *) data
772         );
775 static void sp_sel_trans_handle_click(SPKnot *knot, guint state, gpointer data)
777     SP_SELECT_CONTEXT(knot->desktop->event_context)->_seltrans->handleClick(
778         knot, state, *(SPSelTransHandle const *) data
779         );
782 void Inkscape::SelTrans::handleClick(SPKnot */*knot*/, guint state, SPSelTransHandle const &handle)
784     switch (handle.anchor) {
785         case GTK_ANCHOR_CENTER:
786             if (state & GDK_SHIFT_MASK) {
787                 // Unset the  center position for all selected items
788                 for (GSList const *l = _desktop->selection->itemList(); l; l = l->next) {
789                     SPItem *it = (SPItem*)(SP_OBJECT(l->data));
790                     it->unsetCenter();
791                     SP_OBJECT(it)->updateRepr();
792                     _center_is_set = false;  // center has changed
793                     _updateHandles();
794                 }
795                 DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_SELECT,
796                                    _("Reset center"));
797             }
798             break;
799         default:
800             break;
801     }
804 void Inkscape::SelTrans::handleGrab(SPKnot *knot, guint /*state*/, SPSelTransHandle const &handle)
806     switch (handle.anchor) {
807         case GTK_ANCHOR_CENTER:
808             g_object_set(G_OBJECT(_grip),
809                          "shape", SP_CTRL_SHAPE_BITMAP,
810                          "size", 13.0,
811                          NULL);
812             sp_canvas_item_show(_grip);
813             break;
814         default:
815             g_object_set(G_OBJECT(_grip),
816                          "shape", SP_CTRL_SHAPE_CROSS,
817                          "size", 7.0,
818                          NULL);
819             sp_canvas_item_show(_norm);
820             sp_canvas_item_show(_grip);
822             break;
823     }
825     grab(sp_knot_position(knot), handle.x, handle.y, FALSE, FALSE);
829 void Inkscape::SelTrans::handleNewEvent(SPKnot *knot, Geom::Point *position, guint state, SPSelTransHandle const &handle)
831     if (!SP_KNOT_IS_GRABBED(knot)) {
832         return;
833     }
835     // in case items have been unhooked from the document, don't
836     // try to continue processing events for them.
837     for (unsigned int i = 0; i < _items.size(); i++) {
838         if (!SP_OBJECT_DOCUMENT(SP_OBJECT(_items[i])) ) {
839             return;
840         }
841     }
843     handle.action(this, handle, *position, state);
847 gboolean Inkscape::SelTrans::handleRequest(SPKnot *knot, Geom::Point *position, guint state, SPSelTransHandle const &handle)
849     if (!SP_KNOT_IS_GRABBED(knot)) {
850         return TRUE;
851     }
853     knot->desktop->setPosition(*position);
855     // When holding shift while rotating or skewing, the transformation will be
856     // relative to the point opposite of the handle; otherwise it will be relative
857     // to the center as set for the selection
858     if ((!(state & GDK_SHIFT_MASK) == !(_state == STATE_ROTATE)) && (&handle != &handle_center)) {
859         _origin = _opposite;
860         _origin_for_bboxpoints = _opposite_for_bboxpoints;
861         _origin_for_specpoints = _opposite_for_specpoints;
862     } else if (_center) {
863         _origin = *_center;
864         _origin_for_bboxpoints = *_center;
865         _origin_for_specpoints = *_center;
866     } else {
867         // FIXME
868         return TRUE;
869     }
870     if (handle.request(this, handle, *position, state)) {
871         sp_knot_set_position(knot, *position, state);
872         SP_CTRL(_grip)->moveto(*position);
873         if (&handle == &handle_center) {
874             SP_CTRL(_norm)->moveto(*position);
875         } else {
876             SP_CTRL(_norm)->moveto(_origin);
877         }
878     }
880     return TRUE;
884 void Inkscape::SelTrans::_selChanged(Inkscape::Selection */*selection*/)
886     if (!_grabbed) {
887         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
888         // reread in case it changed on the fly:
889         int prefs_bbox = prefs->getBool("/tools/bounding_box");
890          _snap_bbox_type = !prefs_bbox ?
891             SPItem::APPROXIMATE_BBOX : SPItem::GEOMETRIC_BBOX;
892         //SPItem::APPROXIMATE_BBOX will be replaced by SPItem::VISUAL_BBOX, as soon as the latter is implemented properly
894         _updateVolatileState();
895         _current_relative_affine.setIdentity();
896         _center_is_set = false; // center(s) may have changed
897         _updateHandles();
898     }
901 void Inkscape::SelTrans::_selModified(Inkscape::Selection */*selection*/, guint /*flags*/)
903     if (!_grabbed) {
904         _updateVolatileState();
905         _current_relative_affine.setIdentity();
907         // reset internal flag
908         _changed = false;
910         _center_is_set = false;  // center(s) may have changed
912         _updateHandles();
913     }
916 /*
917  * handlers for handle move-request
918  */
920 /** Returns -1 or 1 according to the sign of x.  Returns 1 for 0 and NaN. */
921 static double sign(double const x)
923     return ( x < 0
924              ? -1
925              : 1 );
928 gboolean sp_sel_trans_scale_request(Inkscape::SelTrans *seltrans,
929                                     SPSelTransHandle const &, Geom::Point &pt, guint state)
931     return seltrans->scaleRequest(pt, state);
934 gboolean sp_sel_trans_stretch_request(Inkscape::SelTrans *seltrans,
935                                       SPSelTransHandle const &handle, Geom::Point &pt, guint state)
937     return seltrans->stretchRequest(handle, pt, state);
940 gboolean sp_sel_trans_skew_request(Inkscape::SelTrans *seltrans,
941                                    SPSelTransHandle const &handle, Geom::Point &pt, guint state)
943     return seltrans->skewRequest(handle, pt, state);
946 gboolean sp_sel_trans_rotate_request(Inkscape::SelTrans *seltrans,
947                                      SPSelTransHandle const &, Geom::Point &pt, guint state)
949     return seltrans->rotateRequest(pt, state);
952 gboolean sp_sel_trans_center_request(Inkscape::SelTrans *seltrans,
953                                      SPSelTransHandle const &, Geom::Point &pt, guint state)
955     return seltrans->centerRequest(pt, state);
958 gboolean Inkscape::SelTrans::scaleRequest(Geom::Point &pt, guint state)
961     // Calculate the scale factors, which can be either visual or geometric
962     // depending on which type of bbox is currently being used (see preferences -> selector tool)
963     Geom::Scale default_scale = calcScaleFactors(_point, pt, _origin);
965     // Find the scale factors for the geometric bbox
966     Geom::Point pt_geom = _getGeomHandlePos(pt);
967     Geom::Scale geom_scale = calcScaleFactors(_point_geom, pt_geom, _origin_for_specpoints);
969     _absolute_affine = Geom::identity(); //Initialize the scaler
971     if (state & GDK_MOD1_MASK) { // scale by an integer multiplier/divider
972         // We're scaling either the visual or the geometric bbox here (see the comment above)
973         for ( unsigned int i = 0 ; i < 2 ; i++ ) {
974             if (fabs(default_scale[i]) > 1) {
975                 default_scale[i] = round(default_scale[i]);
976             } else if (default_scale[i] != 0) {
977                 default_scale[i] = 1/round(1/(MIN(default_scale[i], 10)));
978             }
979         }
980         // Update the knot position
981         pt = _calcAbsAffineDefault(default_scale);
982         // When scaling by an integer, snapping is not needed
983     } else {
984         // In all other cases we should try to snap now
985         SnapManager &m = _desktop->namedview->snap_manager;
986         m.setup(_desktop, false, _items_const);
988         Inkscape::SnappedPoint bb, sn;
990         if ((state & GDK_CONTROL_MASK) || _desktop->isToolboxButtonActive ("lock")) {
991             // Scale is locked to a 1:1 aspect ratio, so that s[X] must be made to equal s[Y].
992             //
993             // The aspect-ratio must be locked before snapping
994             if (fabs(default_scale[Geom::X]) > fabs(default_scale[Geom::Y])) {
995                 default_scale[Geom::X] = fabs(default_scale[Geom::Y]) * sign(default_scale[Geom::X]);
996                 geom_scale[Geom::X] = fabs(geom_scale[Geom::Y]) * sign(geom_scale[Geom::X]);
997             } else {
998                 default_scale[Geom::Y] = fabs(default_scale[Geom::X]) * sign(default_scale[Geom::Y]);
999                 geom_scale[Geom::Y] = fabs(geom_scale[Geom::X]) * sign(geom_scale[Geom::Y]);
1000             }
1002             // Snap along a suitable constraint vector from the origin.
1003             bb = m.constrainedSnapScale(_bbox_points, _point, default_scale, _origin_for_bboxpoints);
1004             sn = m.constrainedSnapScale(_snap_points, _point, geom_scale, _origin_for_specpoints);
1005         } else {
1006             /* Scale aspect ratio is unlocked */
1007             bb = m.freeSnapScale(_bbox_points, _point, default_scale, _origin_for_bboxpoints);
1008             sn = m.freeSnapScale(_snap_points, _point, geom_scale, _origin_for_specpoints);
1009         }
1011         if (!(bb.getSnapped() || sn.getSnapped())) {
1012             // We didn't snap at all! Don't update the handle position, just calculate the new transformation
1013             _calcAbsAffineDefault(default_scale);
1014             _desktop->snapindicator->remove_snaptarget();
1015         } else if (bb.getSnapped() && !bb.isOtherSnapBetter(sn, false)) {
1016             // We snapped the bbox (which is either visual or geometric)
1017             _desktop->snapindicator->set_new_snaptarget(bb);
1018             default_scale = Geom::Scale(bb.getTransformation());
1019             // Calculate the new transformation and update the handle position
1020             pt = _calcAbsAffineDefault(default_scale);
1021         } else {
1022             _desktop->snapindicator->set_new_snaptarget(sn);
1023             // We snapped the special points (e.g. nodes), which are not at the visual bbox
1024             // The handle location however (pt) might however be at the visual bbox, so we
1025             // will have to calculate pt taking the stroke width into account
1026             geom_scale = Geom::Scale(sn.getTransformation());
1027             pt = _calcAbsAffineGeom(geom_scale);
1028         }
1029         m.unSetup();
1030     }
1032     /* Status text */
1033     _message_context.setF(Inkscape::IMMEDIATE_MESSAGE,
1034                           _("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>Ctrl</b> to lock ratio"),
1035                           100 * _absolute_affine[0], 100 * _absolute_affine[3]);
1037     return TRUE;
1040 gboolean Inkscape::SelTrans::stretchRequest(SPSelTransHandle const &handle, Geom::Point &pt, guint state)
1042     Geom::Dim2 axis, perp;
1043     switch (handle.cursor) {
1044         case GDK_TOP_SIDE:
1045         case GDK_BOTTOM_SIDE:
1046             axis = Geom::Y;
1047             perp = Geom::X;
1048             break;
1049         case GDK_LEFT_SIDE:
1050         case GDK_RIGHT_SIDE:
1051             axis = Geom::X;
1052             perp = Geom::Y;
1053             break;
1054         default:
1055             g_assert_not_reached();
1056             return TRUE;
1057     };
1059     // Calculate the scale factors, which can be either visual or geometric
1060     // depending on which type of bbox is currently being used (see preferences -> selector tool)
1061     Geom::Scale default_scale = calcScaleFactors(_point, pt, _origin);
1062     default_scale[perp] = 1;
1064     // Find the scale factors for the geometric bbox
1065     Geom::Point pt_geom = _getGeomHandlePos(pt);
1066     Geom::Scale geom_scale = calcScaleFactors(_point_geom, pt_geom, _origin_for_specpoints);
1067     geom_scale[perp] = 1;
1069     _absolute_affine = Geom::identity(); //Initialize the scaler
1071     if (state & GDK_MOD1_MASK) { // stretch by an integer multiplier/divider
1072         if (fabs(default_scale[axis]) > 1) {
1073             default_scale[axis] = round(default_scale[axis]);
1074         } else if (default_scale[axis] != 0) {
1075             default_scale[axis] = 1/round(1/(MIN(default_scale[axis], 10)));
1076         }
1077         // Calculate the new transformation and update the handle position
1078         pt = _calcAbsAffineDefault(default_scale);
1079         // When stretching by an integer, snapping is not needed
1080     } else {
1081         // In all other cases we should try to snap now
1083         SnapManager &m = _desktop->namedview->snap_manager;
1084         m.setup(_desktop, false, _items_const);
1086         Inkscape::SnappedPoint bb, sn;
1087         g_assert(bb.getSnapped() == false); // Check initialization to catch any regression
1089         bool symmetrical = state & GDK_CONTROL_MASK;
1091         bb = m.constrainedSnapStretch(_bbox_points, _point, Geom::Coord(default_scale[axis]), _origin_for_bboxpoints, Geom::Dim2(axis), symmetrical);
1092         sn = m.constrainedSnapStretch(_snap_points, _point, Geom::Coord(geom_scale[axis]), _origin_for_specpoints, Geom::Dim2(axis), symmetrical);
1094         if (bb.getSnapped()) {
1095             // We snapped the bbox (which is either visual or geometric)
1096             default_scale[axis] = bb.getTransformation()[axis];
1097         }
1099         if (sn.getSnapped()) {
1100             geom_scale[axis] = sn.getTransformation()[axis];
1101         }
1103         if (symmetrical) {
1104             // on ctrl, apply symmetrical scaling instead of stretching
1105             // Preserve aspect ratio, but never flip in the dimension not being edited (by using fabs())
1106             default_scale[perp] = fabs(default_scale[axis]);
1107             geom_scale[perp] = fabs(geom_scale[axis]);
1108         }
1110         if (!(bb.getSnapped() || sn.getSnapped())) {
1111             // We didn't snap at all! Don't update the handle position, just calculate the new transformation
1112             _calcAbsAffineDefault(default_scale);
1113             _desktop->snapindicator->remove_snaptarget();
1114         } else if (bb.getSnapped() && !bb.isOtherSnapBetter(sn, false)) {
1115             _desktop->snapindicator->set_new_snaptarget(bb);
1116             // Calculate the new transformation and update the handle position
1117             pt = _calcAbsAffineDefault(default_scale);
1118         } else {
1119             _desktop->snapindicator->set_new_snaptarget(sn);
1120             // We snapped the special points (e.g. nodes), which are not at the visual bbox
1121             // The handle location however (pt) might however be at the visual bbox, so we
1122             // will have to calculate pt taking the stroke width into account
1123             pt = _calcAbsAffineGeom(geom_scale);
1124         }
1126         m.unSetup();
1127     }
1129     // status text
1130     _message_context.setF(Inkscape::IMMEDIATE_MESSAGE,
1131                           _("<b>Scale</b>: %0.2f%% x %0.2f%%; with <b>Ctrl</b> to lock ratio"),
1132                           100 * _absolute_affine[0], 100 * _absolute_affine[3]);
1134     return TRUE;
1137 gboolean Inkscape::SelTrans::skewRequest(SPSelTransHandle const &handle, Geom::Point &pt, guint state)
1139     /* When skewing (or rotating):
1140      * 1) the stroke width will not change. This makes life much easier because we don't have to
1141      *    account for that (like for scaling or stretching). As a consequence, all points will
1142      *    have the same origin for the transformation and for the snapping.
1143      * 2) When holding shift, the transformation will be relative to the point opposite of
1144      *    the handle; otherwise it will be relative to the center as set for the selection
1145      */
1147     Geom::Dim2 dim_a;
1148     Geom::Dim2 dim_b;
1150     switch (handle.cursor) {
1151         case GDK_SB_H_DOUBLE_ARROW:
1152             dim_a = Geom::Y;
1153             dim_b = Geom::X;
1154             break;
1155         case GDK_SB_V_DOUBLE_ARROW:
1156             dim_a = Geom::X;
1157             dim_b = Geom::Y;
1158             break;
1159         default:
1160             g_assert_not_reached();
1161             abort();
1162             break;
1163     }
1165     Geom::Point const initial_delta = _point - _origin;
1167     if (fabs(initial_delta[dim_a]) < 1e-15) {
1168         return false;
1169     }
1171     // Calculate the scale factors, which can be either visual or geometric
1172     // depending on which type of bbox is currently being used (see preferences -> selector tool)
1173     Geom::Scale scale = calcScaleFactors(_point, pt, _origin, false);
1174     Geom::Scale skew = calcScaleFactors(_point, pt, _origin, true);
1175     scale[dim_b] = 1;
1176     skew[dim_b] = 1;
1178     if (fabs(scale[dim_a]) < 1) {
1179         // Prevent shrinking of the selected object, while allowing mirroring
1180         scale[dim_a] = sign(scale[dim_a]);
1181     } else {
1182         // Allow expanding of the selected object by integer multiples
1183         scale[dim_a] = floor(scale[dim_a] + 0.5);
1184     }
1186     double radians = atan(skew[dim_a] / scale[dim_a]);
1188     if (state & GDK_CONTROL_MASK) {
1189         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1190         // Snap to defined angle increments
1191         int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1192         if (snaps) {
1193             double sections = floor(radians * snaps / M_PI + .5);
1194             if (fabs(sections) >= snaps / 2) {
1195                 sections = sign(sections) * (snaps / 2 - 1);
1196             }
1197             radians = (M_PI / snaps) * sections;
1198         }
1199         skew[dim_a] = tan(radians) * scale[dim_a];
1200     } else {
1201         // Snap to objects, grids, guides
1203         SnapManager &m = _desktop->namedview->snap_manager;
1204         m.setup(_desktop, false, _items_const);
1206         Inkscape::Snapper::SnapConstraint const constraint(component_vectors[dim_b]);
1207         // When skewing, we cannot snap the corners of the bounding box, see the comment in "constrainedSnapSkew" for details
1208         Geom::Point const s(skew[dim_a], scale[dim_a]);
1209         Inkscape::SnappedPoint sn = m.constrainedSnapSkew(_snap_points, _point, constraint, s, _origin, Geom::Dim2(dim_b));
1211         if (sn.getSnapped()) {
1212             // We snapped something, so change the skew to reflect it
1213             Geom::Coord const sd = sn.getSnapped() ? sn.getTransformation()[0] : NR_HUGE;
1214              _desktop->snapindicator->set_new_snaptarget(sn);
1215             skew[dim_a] = sd;
1216         } else {
1217             _desktop->snapindicator->remove_snaptarget();
1218         }
1220         m.unSetup();
1221     }
1223     // Update the handle position
1224     pt[dim_b] = initial_delta[dim_a] * skew[dim_a] + _point[dim_b];
1225     pt[dim_a] = initial_delta[dim_a] * scale[dim_a] + _origin[dim_a];
1227     // Calculate the relative affine
1228     _relative_affine = Geom::identity();
1229     _relative_affine[2*dim_a + dim_a] = (pt[dim_a] - _origin[dim_a]) / initial_delta[dim_a];
1230     _relative_affine[2*dim_a + (dim_b)] = (pt[dim_b] - _point[dim_b]) / initial_delta[dim_a];
1231     _relative_affine[2*(dim_b) + (dim_a)] = 0;
1232     _relative_affine[2*(dim_b) + (dim_b)] = 1;
1234     for (int i = 0; i < 2; i++) {
1235         if (fabs(_relative_affine[3*i]) < 1e-15) {
1236             _relative_affine[3*i] = 1e-15;
1237         }
1238     }
1240     // Update the status text
1241     double degrees = mod360symm(Geom::rad_to_deg(radians));
1242     _message_context.setF(Inkscape::IMMEDIATE_MESSAGE,
1243                           // TRANSLATORS: don't modify the first ";"
1244                           // (it will NOT be displayed as ";" - only the second one will be)
1245                           _("<b>Skew</b>: %0.2f&#176;; with <b>Ctrl</b> to snap angle"),
1246                           degrees);
1248     return TRUE;
1251 gboolean Inkscape::SelTrans::rotateRequest(Geom::Point &pt, guint state)
1253     /* When rotating (or skewing):
1254      * 1) the stroke width will not change. This makes life much easier because we don't have to
1255      *    account for that (like for scaling or stretching). As a consequence, all points will
1256      *    have the same origin for the transformation and for the snapping.
1257      * 2) When holding shift, the transformation will be relative to the point opposite of
1258      *    the handle; otherwise it will be relative to the center as set for the selection
1259      */
1261     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1262     int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1264     // rotate affine in rotate
1265     Geom::Point const d1 = _point - _origin;
1266     Geom::Point const d2 = pt     - _origin;
1268     Geom::Coord const h1 = Geom::L2(d1); // initial radius
1269     if (h1 < 1e-15) return FALSE;
1270     Geom::Point q1 = d1 / h1; // normalized initial vector to handle
1271     Geom::Coord const h2 = Geom::L2(d2); // new radius
1272     if (fabs(h2) < 1e-15) return FALSE;
1273     Geom::Point q2 = d2 / h2; // normalized new vector to handle
1275     Geom::Rotate r1(q1);
1276     Geom::Rotate r2(q2);
1278     double radians = atan2(Geom::dot(Geom::rot90(d1), d2), Geom::dot(d1, d2));;
1279     if (state & GDK_CONTROL_MASK) {
1280         // Snap to defined angle increments
1281         double cos_t = Geom::dot(q1, q2);
1282         double sin_t = Geom::dot(Geom::rot90(q1), q2);
1283         radians = atan2(sin_t, cos_t);
1284         if (snaps) {
1285             radians = ( M_PI / snaps ) * floor( radians * snaps / M_PI + .5 );
1286         }
1287         r1 = Geom::Rotate(0); //q1 = Geom::Point(1, 0);
1288         r2 = Geom::Rotate(radians); //q2 = Geom::Point(cos(radians), sin(radians));
1289     } else {
1290         SnapManager &m = _desktop->namedview->snap_manager;
1291         m.setup(_desktop, false, _items_const);
1292         // When rotating, we cannot snap the corners of the bounding box, see the comment in "constrainedSnapRotate" for details
1293         Inkscape::SnappedPoint sn = m.constrainedSnapRotate(_snap_points, _point, radians, _origin);
1294         m.unSetup();
1296         if (sn.getSnapped()) {
1297             _desktop->snapindicator->set_new_snaptarget(sn);
1298             // We snapped something, so change the rotation to reflect it
1299             radians = sn.getTransformation()[0];
1300             r1 = Geom::Rotate(0);
1301             r2 = Geom::Rotate(radians);
1302         } else {
1303             _desktop->snapindicator->remove_snaptarget();
1304         }
1306     }
1309     // Calculate the relative affine
1310     _relative_affine = r2 * r1.inverse();
1312     // Update the handle position
1313     pt = _point * Geom::Translate(-_origin) * _relative_affine * Geom::Translate(_origin);
1315     // Update the status text
1316     double degrees = mod360symm(Geom::rad_to_deg(radians));
1317     _message_context.setF(Inkscape::IMMEDIATE_MESSAGE,
1318                           // TRANSLATORS: don't modify the first ";"
1319                           // (it will NOT be displayed as ";" - only the second one will be)
1320                           _("<b>Rotate</b>: %0.2f&#176;; with <b>Ctrl</b> to snap angle"), degrees);
1322     return TRUE;
1325 // Move the item's transformation center
1326 gboolean Inkscape::SelTrans::centerRequest(Geom::Point &pt, guint state)
1328     // When dragging the transformation center while multiple items have been selected, then those
1329     // items will share a single center. While dragging that single center, it should never snap to the
1330     // centers of any of the selected objects. Therefore we will have to pass the list of selected items
1331     // to the snapper, to avoid self-snapping of the rotation center
1332     GSList *items = (GSList *) const_cast<Selection *>(_selection)->itemList();
1333     SnapManager &m = _desktop->namedview->snap_manager;
1334     m.setup(_desktop);
1335     m.setRotationCenterSource(items);
1337     if (state & GDK_CONTROL_MASK) { // with Ctrl, constrain to axes
1338         std::vector<Inkscape::Snapper::SnapConstraint> constraints;
1339         constraints.push_back(Inkscape::Snapper::SnapConstraint(_point, Geom::Point(1, 0)));
1340         constraints.push_back(Inkscape::Snapper::SnapConstraint(_point, Geom::Point(0, 1)));
1341         Inkscape::SnappedPoint sp = m.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(pt, Inkscape::SNAPSOURCE_ROTATION_CENTER), constraints, state & GDK_SHIFT_MASK);
1342         pt = sp.getPoint();
1343     }
1344     else {
1345         if (!(state & GDK_SHIFT_MASK)) { // Shift disables snapping
1346             m.freeSnapReturnByRef(pt, Inkscape::SNAPSOURCE_ROTATION_CENTER);
1347         }
1348     }
1350     m.unSetup();
1352     // status text
1353     GString *xs = SP_PX_TO_METRIC_STRING(pt[Geom::X], _desktop->namedview->getDefaultMetric());
1354     GString *ys = SP_PX_TO_METRIC_STRING(pt[Geom::Y], _desktop->namedview->getDefaultMetric());
1355     _message_context.setF(Inkscape::NORMAL_MESSAGE, _("Move <b>center</b> to %s, %s"), xs->str, ys->str);
1356     g_string_free(xs, FALSE);
1357     g_string_free(ys, FALSE);
1359     return TRUE;
1362 /*
1363  * handlers for handle movement
1364  *
1365  */
1367 void sp_sel_trans_stretch(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, Geom::Point &pt, guint state)
1369     seltrans->stretch(handle, pt, state);
1372 void sp_sel_trans_scale(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, Geom::Point &pt, guint state)
1374     seltrans->scale(pt, state);
1377 void sp_sel_trans_skew(Inkscape::SelTrans *seltrans, SPSelTransHandle const &handle, Geom::Point &pt, guint state)
1379     seltrans->skew(handle, pt, state);
1382 void sp_sel_trans_rotate(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, Geom::Point &pt, guint state)
1384     seltrans->rotate(pt, state);
1387 void Inkscape::SelTrans::stretch(SPSelTransHandle const &/*handle*/, Geom::Point &/*pt*/, guint /*state*/)
1389     transform(_absolute_affine, Geom::Point(0, 0)); // we have already accounted for origin, so pass 0,0
1392 void Inkscape::SelTrans::scale(Geom::Point &/*pt*/, guint /*state*/)
1394     transform(_absolute_affine, Geom::Point(0, 0)); // we have already accounted for origin, so pass 0,0
1397 void Inkscape::SelTrans::skew(SPSelTransHandle const &/*handle*/, Geom::Point &/*pt*/, guint /*state*/)
1399     transform(_relative_affine, _origin);
1402 void Inkscape::SelTrans::rotate(Geom::Point &/*pt*/, guint /*state*/)
1404     transform(_relative_affine, _origin);
1407 void sp_sel_trans_center(Inkscape::SelTrans *seltrans, SPSelTransHandle const &, Geom::Point &pt, guint /*state*/)
1409     seltrans->setCenter(pt);
1413 void Inkscape::SelTrans::moveTo(Geom::Point const &xy, guint state)
1415     SnapManager &m = _desktop->namedview->snap_manager;
1417     /* The amount that we've moved by during this drag */
1418     Geom::Point dxy = xy - _point;
1420     bool const alt = (state & GDK_MOD1_MASK);
1421     bool const control = (state & GDK_CONTROL_MASK);
1422     bool const shift = (state & GDK_SHIFT_MASK);
1424     if (alt) {
1426         // Alt pressed means: move only by integer multiples of the grid spacing
1428         if (control) { // ... if also constrained to the orthogonal axes
1429             if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) {
1430                 dxy[Geom::Y] = 0;
1431             } else {
1432                 dxy[Geom::X] = 0;
1433             }
1434         }
1435         m.setup(_desktop, true, _items_const);
1436         dxy = m.multipleOfGridPitch(dxy, _point);
1437         m.unSetup();
1438     } else if (shift) {
1439         if (control) { // shift & control: constrained movement without snapping
1440             if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) {
1441                 dxy[Geom::Y] = 0;
1442             } else {
1443                 dxy[Geom::X] = 0;
1444             }
1445         }
1446     } else { //!shift: with snapping
1448         /* We're snapping to things, possibly with a constraint to horizontal or
1449         ** vertical movement.  Obtain a list of possible translations and then
1450         ** pick the smallest.
1451         */
1453         m.setup(_desktop, false, _items_const);
1455         /* This will be our list of possible translations */
1456         std::list<Inkscape::SnappedPoint> s;
1458         if (control) { // constrained movement with snapping
1460             /* Snap to things, and also constrain to horizontal or vertical movement */
1462             unsigned int dim = fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y]) ? Geom::X : Geom::Y;
1463             // When doing a constrained translation, all points will move in the same direction, i.e.
1464             // either horizontally or vertically. Therefore we only have to specify the direction of
1465             // the constraint-line once. The constraint lines are parallel, but might not be colinear.
1466             // Therefore we will have to set the point through which the constraint-line runs
1467             // individually for each point to be snapped; this will be handled however by _snapTransformed()
1468             s.push_back(m.constrainedSnapTranslate(_bbox_points_for_translating,
1469                                                      _point,
1470                                                      Inkscape::Snapper::SnapConstraint(component_vectors[dim]),
1471                                                      dxy));
1473             s.push_back(m.constrainedSnapTranslate(_snap_points,
1474                                                      _point,
1475                                                      Inkscape::Snapper::SnapConstraint(component_vectors[dim]),
1476                                                      dxy));
1477         } else { // !control
1479             // Let's leave this timer code here for a while. I'll probably need it in the near future (Diederik van Lierop)
1480             /* GTimeVal starttime;
1481             GTimeVal endtime;
1482             g_get_current_time(&starttime); */
1484             /* Snap to things with no constraint */
1485             s.push_back(m.freeSnapTranslate(_bbox_points_for_translating, _point, dxy));
1486             s.push_back(m.freeSnapTranslate(_snap_points, _point, dxy));
1488               /*g_get_current_time(&endtime);
1489               double elapsed = ((((double)endtime.tv_sec - starttime.tv_sec) * G_USEC_PER_SEC + (endtime.tv_usec - starttime.tv_usec))) / 1000.0;
1490               std::cout << "Time spent snapping: " << elapsed << std::endl; */
1491         }
1492         m.unSetup();
1494         /* Pick one */
1495         Inkscape::SnappedPoint best_snapped_point;
1496         for (std::list<Inkscape::SnappedPoint>::const_iterator i = s.begin(); i != s.end(); i++) {
1497             if (i->getSnapped()) {
1498                 if (best_snapped_point.isOtherSnapBetter(*i, true)) {
1499                     best_snapped_point = *i;
1500                     dxy = i->getTransformation();
1501                 }
1502             }
1503         }
1505         if (best_snapped_point.getSnapped()) {
1506             _desktop->snapindicator->set_new_snaptarget(best_snapped_point);
1507         } else {
1508             // We didn't snap, so remove any previous snap indicator
1509             _desktop->snapindicator->remove_snaptarget();
1510             if (control) {
1511                 // If we didn't snap, then we should still constrain horizontally or vertically
1512                 // (When we did snap, then this constraint has already been enforced by
1513                 // calling constrainedSnapTranslate() above)
1514                 if (fabs(dxy[Geom::X]) > fabs(dxy[Geom::Y])) {
1515                     dxy[Geom::Y] = 0;
1516                 } else {
1517                     dxy[Geom::X] = 0;
1518                 }
1519             }
1520         }
1521     }
1523     Geom::Matrix const move((Geom::Translate(dxy)));
1524     Geom::Point const norm(0, 0);
1525     transform(move, norm);
1527     // status text
1528     GString *xs = SP_PX_TO_METRIC_STRING(dxy[Geom::X], _desktop->namedview->getDefaultMetric());
1529     GString *ys = SP_PX_TO_METRIC_STRING(dxy[Geom::Y], _desktop->namedview->getDefaultMetric());
1530     _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);
1531     g_string_free(xs, TRUE);
1532     g_string_free(ys, TRUE);
1535 // Given a location of a handle at the visual bounding box, find the corresponding location at the
1536 // geometrical bounding box
1537 Geom::Point Inkscape::SelTrans::_getGeomHandlePos(Geom::Point const &visual_handle_pos)
1539     if ( _snap_bbox_type == SPItem::GEOMETRIC_BBOX) {
1540         // When the selector tool is using geometric bboxes, then the handle is already
1541         // located at one of the geometric bbox corners
1542         return visual_handle_pos;
1543     }
1545     if (!_geometric_bbox) {
1546         //_getGeomHandlePos() can only be used after _geometric_bbox has been defined!
1547         return visual_handle_pos;
1548     }
1550     // Using the Geom::Rect constructor below ensures that "min() < max()", which is important
1551     // because this will also hold for _bbox, and which is required for get_scale_transform_with_stroke()
1552     Geom::Rect new_bbox = Geom::Rect(_origin_for_bboxpoints, visual_handle_pos); // new visual bounding box
1553     // Please note that the new_bbox might in fact be just a single line, for example when stretching (in
1554     // which case the handle and origin will be aligned vertically or horizontally)
1555     Geom::Point normalized_handle_pos = (visual_handle_pos - new_bbox.min()) * Geom::Scale(new_bbox.dimensions()).inverse();
1557     // Calculate the absolute affine while taking into account the scaling of the stroke width
1558     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1559     bool transform_stroke = prefs->getBool("/options/transform/stroke", true);
1560     Geom::Matrix abs_affine = get_scale_transform_with_stroke (*_bbox, _strokewidth, transform_stroke,
1561                     new_bbox.min()[Geom::X], new_bbox.min()[Geom::Y], new_bbox.max()[Geom::X], new_bbox.max()[Geom::Y]);
1563     // Calculate the scaled geometrical bbox
1564     Geom::Rect new_geom_bbox = Geom::Rect(_geometric_bbox->min() * abs_affine, _geometric_bbox->max() * abs_affine);
1565     // Find the location of the handle on this new geometrical bbox
1566     return normalized_handle_pos * Geom::Scale(new_geom_bbox.dimensions()) + new_geom_bbox.min(); //new position of the geometric handle
1569 Geom::Scale Inkscape::calcScaleFactors(Geom::Point const &initial_point, Geom::Point const &new_point, Geom::Point const &origin, bool const skew)
1571     // Work out the new scale factors for the bbox
1573     Geom::Point const initial_delta = initial_point - origin;
1574     Geom::Point const new_delta = new_point - origin;
1575     Geom::Point const offset = new_point - initial_point;
1576     Geom::Scale scale(1, 1);
1578     for ( unsigned int i = 0 ; i < 2 ; i++ ) {
1579         if ( fabs(initial_delta[i]) > 1e-6 ) {
1580             if (skew) {
1581                 scale[i] = offset[1-i] / initial_delta[i];
1582             } else {
1583                 scale[i] = new_delta[i] / initial_delta[i];
1584             }
1585         }
1586     }
1588     return scale;
1591 // Only for scaling/stretching
1592 Geom::Point Inkscape::SelTrans::_calcAbsAffineDefault(Geom::Scale const default_scale)
1594     Geom::Matrix abs_affine = Geom::Translate(-_origin) * Geom::Matrix(default_scale) * Geom::Translate(_origin);
1595     Geom::Point new_bbox_min = _approximate_bbox->min() * abs_affine;
1596     Geom::Point new_bbox_max = _approximate_bbox->max() * abs_affine;
1598     bool transform_stroke = false;
1599     gdouble strokewidth = 0;
1601     if ( _snap_bbox_type != SPItem::GEOMETRIC_BBOX) {
1602         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1603         transform_stroke = prefs->getBool("/options/transform/stroke", true);
1604         strokewidth = _strokewidth;
1605     }
1607     _absolute_affine = get_scale_transform_with_stroke (*_approximate_bbox, strokewidth, transform_stroke,
1608                     new_bbox_min[Geom::X], new_bbox_min[Geom::Y], new_bbox_max[Geom::X], new_bbox_max[Geom::Y]);
1610     // return the new handle position
1611     return ( _point - _origin ) * default_scale + _origin;
1614 // Only for scaling/stretching
1615 Geom::Point Inkscape::SelTrans::_calcAbsAffineGeom(Geom::Scale const geom_scale)
1617     _relative_affine = Geom::Matrix(geom_scale);
1618     _absolute_affine = Geom::Translate(-_origin_for_specpoints) * _relative_affine * Geom::Translate(_origin_for_specpoints);
1620     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1621     bool const transform_stroke = prefs->getBool("/options/transform/stroke", true);
1622     if (_geometric_bbox) {
1623         Geom::Rect visual_bbox = get_visual_bbox(_geometric_bbox, _absolute_affine, _strokewidth, transform_stroke);
1624         // return the new handle position
1625         return visual_bbox.min() + visual_bbox.dimensions() * Geom::Scale(_handle_x, _handle_y);
1626     }
1628     // Fall back scenario, in case we don't have a geometric bounding box at hand;
1629     // (Due to some bugs related to bounding boxes having at least one zero dimension; For more details
1630     // see https://bugs.launchpad.net/inkscape/+bug/318726)
1631     g_warning("No geometric bounding box has been calculated; this is a bug that needs fixing!");
1632     return _calcAbsAffineDefault(geom_scale); // this is bogus, but we must return _something_
1635 void Inkscape::SelTrans::_keepClosestPointOnly(std::vector<Inkscape::SnapCandidatePoint> &points, const Geom::Point &reference)
1637     if (points.size() < 2) return;
1639     Inkscape::SnapCandidatePoint closest_point = Inkscape::SnapCandidatePoint(Geom::Point(NR_HUGE, NR_HUGE), SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED);
1640     Geom::Coord closest_dist = NR_HUGE;
1642     for(std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); i++) {
1643         Geom::Coord dist = Geom::L2((*i).getPoint() - reference);
1644         if (i == points.begin() || dist < closest_dist) {
1645             closest_point = *i;
1646             closest_dist = dist;
1647         }
1648     }
1650     closest_point.setSourceNum(-1);
1651     points.clear();
1652     points.push_back(closest_point);
1655 /*
1656   Local Variables:
1657   mode:c++
1658   c-file-style:"stroustrup"
1659   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1660   indent-tabs-mode:nil
1661   fill-column:99
1662   End:
1663 */
1664 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :