Code

Snapping: improve calculation of metrics for scaling, modify some comments, and remov...
[inkscape.git] / src / ui / tool / transform-handle-set.cpp
1 /** @file
2  * Affine transform handles component
3  */
4 /* Authors:
5  *   Krzysztof KosiƄski <tweenk.pl@gmail.com>
6  *
7  * Copyright (C) 2009 Authors
8  * Released under GNU GPL, read the file 'COPYING' for more information
9  */
11 #include <math.h>
12 #include <algorithm>
13 #include <glib.h>
14 #include <glib/gi18n.h>
15 #include <gdk/gdk.h>
16 #include <2geom/transforms.h>
17 #include "desktop.h"
18 #include "desktop-handles.h"
19 #include "display/sodipodi-ctrlrect.h"
20 #include "preferences.h"
21 #include "snap.h"
22 #include "snap-candidate.h"
23 #include "sp-namedview.h"
24 #include "ui/tool/commit-events.h"
25 #include "ui/tool/control-point.h"
26 #include "ui/tool/control-point-selection.h"
27 #include "ui/tool/selectable-control-point.h"
28 #include "ui/tool/event-utils.h"
29 #include "ui/tool/transform-handle-set.h"
30 #include "ui/tool/node-tool.h"
32 // FIXME BRAIN DAMAGE WARNING: this is a global variable in select-context.cpp
33 // It should be moved to a header
34 extern GdkPixbuf *handles[];
35 GType sp_select_context_get_type();
37 namespace Inkscape {
38 namespace UI {
40 namespace {
41 Gtk::AnchorType corner_to_anchor(unsigned c) {
42     switch (c % 4) {
43     case 0: return Gtk::ANCHOR_NE;
44     case 1: return Gtk::ANCHOR_NW;
45     case 2: return Gtk::ANCHOR_SW;
46     default: return Gtk::ANCHOR_SE;
47     }
48 }
49 Gtk::AnchorType side_to_anchor(unsigned s) {
50     switch (s % 4) {
51     case 0: return Gtk::ANCHOR_N;
52     case 1: return Gtk::ANCHOR_W;
53     case 2: return Gtk::ANCHOR_S;
54     default: return Gtk::ANCHOR_E;
55     }
56 }
58 // TODO move those two functions into a common place
59 double snap_angle(double a) {
60     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
61     int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
62     double unit_angle = M_PI / snaps;
63     return CLAMP(unit_angle * round(a / unit_angle), -M_PI, M_PI);
64 }
65 double snap_increment_degrees() {
66     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
67     int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
68     return 180.0 / snaps;
69 }
71 ControlPoint::ColorSet thandle_cset = {
72     {0x000000ff, 0x000000ff},
73     {0x00ff6600, 0x000000ff},
74     {0x00ff6600, 0x000000ff}
75 };
77 ControlPoint::ColorSet center_cset = {
78     {0x00000000, 0x000000ff},
79     {0x00000000, 0xff0000b0},
80     {0x00000000, 0xff0000b0}    
81 };
82 } // anonymous namespace
84 /** Base class for node transform handles to simplify implementation */
85 class TransformHandle : public ControlPoint {
86 public:
87     TransformHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb)
88         : ControlPoint(th._desktop, Geom::Point(), anchor, pb, &thandle_cset,
89             th._transform_handle_group)
90         , _th(th)
91     {
92         setVisible(false);
93     }
94 protected:
95     virtual void startTransform() {}
96     virtual void endTransform() {}
97     virtual Geom::Matrix computeTransform(Geom::Point const &pos, GdkEventMotion *event) = 0;
98     virtual CommitEvent getCommitEvent() = 0;
100     Geom::Matrix _last_transform;
101     Geom::Point _origin;
102     TransformHandleSet &_th;
103     std::vector<Inkscape::SnapCandidatePoint> _snap_points;
105 private:
106     virtual bool grabbed(GdkEventMotion *) {
107         _origin = position();
108         _last_transform.setIdentity();
109         startTransform();
111         _th._setActiveHandle(this);
112         _cset = &invisible_cset;
113         _setState(_state);
115         // Collect the snap-candidates, one for each selected node. These will be stored in the _snap_points vector.
116         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
117         SnapManager &m = desktop->namedview->snap_manager;
118         InkNodeTool *nt = INK_NODE_TOOL(_desktop->event_context);
119         ControlPointSelection *selection = nt->_selected_nodes.get();
121         _snap_points = selection->getOriginalPoints();
123         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
124         if (prefs->getBool("/options/snapclosestonly/value", false)) {
125             m.keepClosestPointOnly(_snap_points, _origin);
126         }
128         return false;
129     }
130     virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event)
131     {
132         Geom::Matrix t = computeTransform(new_pos, event);
133         // protect against degeneracies
134         if (t.isSingular()) return;
135         Geom::Matrix incr = _last_transform.inverse() * t;
136         if (incr.isSingular()) return;
137         _th.signal_transform.emit(incr);
138         _last_transform = t;
139     }
140     virtual void ungrabbed(GdkEventButton *) {
141         _snap_points.clear();
142         _th._clearActiveHandle();
143         _cset = &thandle_cset;
144         _setState(_state);
145         endTransform();
146         _th.signal_commit.emit(getCommitEvent());
147     }
148 };
150 class ScaleHandle : public TransformHandle {
151 public:
152     ScaleHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb)
153         : TransformHandle(th, anchor, pb)
154     {}
155 protected:
156     virtual Glib::ustring _getTip(unsigned state) {
157         if (state_held_control(state)) {
158             if (state_held_shift(state)) {
159                 return C_("Transform handle tip",
160                     "<b>Shift+Ctrl</b>: scale uniformly about the rotation center");
161             }
162             return C_("Transform handle tip", "<b>Ctrl:</b> scale uniformly");
163         }
164         if (state_held_shift(state)) {
165             if (state_held_alt(state)) {
166                 return C_("Transform handle tip",
167                     "<b>Shift+Alt</b>: scale using an integer ratio about the rotation center");
168             }
169             return C_("Transform handle tip", "<b>Shift</b>: scale from the rotation center");
170         }
171         if (state_held_alt(state)) {
172             return C_("Transform handle tip", "<b>Alt</b>: scale using an integer ratio");
173         }
174         return C_("Transform handle tip", "<b>Scale handle</b>: drag to scale the selection");
175     }
177     virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
178         return format_tip(C_("Transform handle tip",
179             "Scale by %.2f%% x %.2f%%"), _last_scale_x * 100, _last_scale_y * 100);
180     }
182     virtual bool _hasDragTips() { return true; }
184     static double _last_scale_x, _last_scale_y;
185 };
186 double ScaleHandle::_last_scale_x = 1.0;
187 double ScaleHandle::_last_scale_y = 1.0;
189 /// Corner scaling handle for node transforms
190 class ScaleCornerHandle : public ScaleHandle {
191 public:
192     ScaleCornerHandle(TransformHandleSet &th, unsigned corner)
193         : ScaleHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner))
194         , _corner(corner)
195     {}
196 protected:
197     virtual void startTransform() {
198         _sc_center = _th.rotationCenter();
199         _sc_opposite = _th.bounds().corner(_corner + 2);
200         _last_scale_x = _last_scale_y = 1.0;
201         InkNodeTool *nt = INK_NODE_TOOL(_desktop->event_context);
202         ControlPointSelection *selection = nt->_selected_nodes.get();
203         selection->setOriginalPoints();
204     }
205     virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
206         Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
207         Geom::Point vold = _origin - scc, vnew = new_pos - scc;
209         // avoid exploding the selection
210         if (Geom::are_near(vold[Geom::X], 0) || Geom::are_near(vold[Geom::Y], 0))
211             return Geom::identity();
213         double scale[2] = { vnew[Geom::X] / vold[Geom::X], vnew[Geom::Y] / vold[Geom::Y] };
215         if (held_alt(*event)) {
216             for (unsigned i = 0; i < 2; ++i) {
217                 if (scale[i] >= 1.0) scale[i] = round(scale[i]);
218                 else scale[i] = 1.0 / round(1.0 / scale[i]);
219             }
220         } else {
221             //SPDesktop *desktop = _th._desktop; // Won't work as _desktop is protected
222             SPDesktop *desktop = SP_ACTIVE_DESKTOP;
223             SnapManager &m = desktop->namedview->snap_manager;
225             // The lines below have been copied from Handle::dragged() in node.cpp, and need to be
226             // activated if we want to snap to unselected (i.e. stationary) nodes and stationary pieces of paths of the
227             // path that's currently being edited
228             /*
229             std::vector<Inkscape::SnapCandidatePoint> unselected;
230             typedef ControlPointSelection::Set Set;
231             Set &nodes = _parent->_selection.allPoints();
232             for (Set::iterator i = nodes.begin(); i != nodes.end(); ++i) {
233                 Node *n = static_cast<Node*>(*i);
234                 Inkscape::SnapCandidatePoint p(n->position(), n->_snapSourceType(), n->_snapTargetType());
235                 unselected.push_back(p);
236             }
237             m.setupIgnoreSelection(_desktop, true, &unselected);
238             */
240             m.setupIgnoreSelection(_desktop);
242             Inkscape::SnappedPoint sp;
243             if (held_control(*event)) {
244                 scale[0] = scale[1] = std::min(scale[0], scale[1]);
245                 sp = m.constrainedSnapScale(_snap_points, _origin, Geom::Scale(scale[0], scale[1]), scc);
246             } else {
247                 sp = m.freeSnapScale(_snap_points, _origin, Geom::Scale(scale[0], scale[1]), scc);
248             }
249             m.unSetup();
251             if (sp.getSnapped()) {
252                 Geom::Point result = sp.getTransformation();
253                 scale[0] = result[0];
254                 scale[1] = result[1];
255             }
256         }
258         _last_scale_x = scale[0];
259         _last_scale_y = scale[1];
260         Geom::Matrix t = Geom::Translate(-scc)
261             * Geom::Scale(scale[0], scale[1])
262             * Geom::Translate(scc);
263         return t;
264     }
265     virtual CommitEvent getCommitEvent() {
266         return _last_transform.isUniformScale()
267             ? COMMIT_MOUSE_SCALE_UNIFORM
268             : COMMIT_MOUSE_SCALE;
269     }
270 private:
271     static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
272         sp_select_context_get_type();
273         switch (c % 2) {
274         case 0: return Glib::wrap(handles[1], true);
275         default: return Glib::wrap(handles[0], true);
276         }
277     }
278     Geom::Point _sc_center;
279     Geom::Point _sc_opposite;
280     unsigned _corner;
281 };
283 /// Side scaling handle for node transforms
284 class ScaleSideHandle : public ScaleHandle {
285 public:
286     ScaleSideHandle(TransformHandleSet &th, unsigned side)
287         : ScaleHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
288         , _side(side)
289     {}
290 protected:
291     virtual void startTransform() {
292         _sc_center = _th.rotationCenter();
293         Geom::Rect b = _th.bounds();
294         _sc_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
295         _last_scale_x = _last_scale_y = 1.0;
296     }
297     virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
298         Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
299         Geom::Point vs;
300         Geom::Dim2 d1 = static_cast<Geom::Dim2>((_side + 1) % 2);
301         Geom::Dim2 d2 = static_cast<Geom::Dim2>(_side % 2);
303         // avoid exploding the selection
304         if (Geom::are_near(scc[d1], _origin[d1]))
305             return Geom::identity();
307         vs[d1] = (new_pos - scc)[d1] / (_origin - scc)[d1];
308         if (held_alt(*event)) {
309             if (vs[d1] >= 1.0) vs[d1] = round(vs[d1]);
310             else vs[d1] = 1.0 / round(1.0 / vs[d1]);
311         }
312         vs[d2] = held_control(*event) ? vs[d1] : 1.0;
314         _last_scale_x = vs[Geom::X];
315         _last_scale_y = vs[Geom::Y];
316         Geom::Matrix t = Geom::Translate(-scc)
317             * Geom::Scale(vs)
318             * Geom::Translate(scc);
319         return t;
320     }
321     virtual CommitEvent getCommitEvent() {
322         return _last_transform.isUniformScale()
323             ? COMMIT_MOUSE_SCALE_UNIFORM
324             : COMMIT_MOUSE_SCALE;
325     }
326 private:
327     static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned c) {
328         sp_select_context_get_type();
329         switch (c % 2) {
330         case 0: return Glib::wrap(handles[3], true);
331         default: return Glib::wrap(handles[2], true);
332         }
333     }
334     Geom::Point _sc_center;
335     Geom::Point _sc_opposite;
336     unsigned _side;
337 };
339 /// Rotation handle for node transforms
340 class RotateHandle : public TransformHandle {
341 public:
342     RotateHandle(TransformHandleSet &th, unsigned corner)
343         : TransformHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner))
344         , _corner(corner)
345     {}
346 protected:
348     virtual void startTransform() {
349         _rot_center = _th.rotationCenter();
350         _rot_opposite = _th.bounds().corner(_corner + 2);
351         _last_angle = 0;
352     }
354     virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
355     {
356         Geom::Point rotc = held_shift(*event) ? _rot_opposite : _rot_center;
357         double angle = Geom::angle_between(_origin - rotc, new_pos - rotc);
358         if (held_control(*event)) {
359             angle = snap_angle(angle);
360         }
361         _last_angle = angle;
362         Geom::Matrix t = Geom::Translate(-rotc)
363             * Geom::Rotate(angle)
364             * Geom::Translate(rotc);
365         return t;
366     }
368     virtual CommitEvent getCommitEvent() { return COMMIT_MOUSE_ROTATE; }
370     virtual Glib::ustring _getTip(unsigned state) {
371         if (state_held_shift(state)) {
372             if (state_held_control(state)) {
373                 return format_tip(C_("Transform handle tip",
374                     "<b>Shift+Ctrl</b>: rotate around the opposite corner and snap "
375                     "angle to %f° increments"), snap_increment_degrees());
376             }
377             return C_("Transform handle tip", "<b>Shift</b>: rotate around the opposite corner");
378         }
379         if (state_held_control(state)) {
380             return format_tip(C_("Transform handle tip",
381                 "<b>Ctrl</b>: snap angle to %f° increments"), snap_increment_degrees());
382         }
383         return C_("Transform handle tip", "<b>Rotation handle</b>: drag to rotate "
384             "the selection around the rotation center");
385     }
387     virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
388         return format_tip(C_("Transform handle tip", "Rotate by %.2f°"),
389             _last_angle * 360.0);
390     }
392     virtual bool _hasDragTips() { return true; }
394 private:
395     static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
396         sp_select_context_get_type();
397         switch (c % 4) {
398         case 0: return Glib::wrap(handles[10], true);
399         case 1: return Glib::wrap(handles[8], true);
400         case 2: return Glib::wrap(handles[6], true);
401         default: return Glib::wrap(handles[4], true);
402         }
403     }
404     Geom::Point _rot_center;
405     Geom::Point _rot_opposite;
406     unsigned _corner;
407     static double _last_angle;
408 };
409 double RotateHandle::_last_angle = 0;
411 class SkewHandle : public TransformHandle {
412 public:
413     SkewHandle(TransformHandleSet &th, unsigned side)
414         : TransformHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
415         , _side(side)
416     {}
418 protected:
420     virtual void startTransform() {
421         _skew_center = _th.rotationCenter();
422         Geom::Rect b = _th.bounds();
423         _skew_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
424         _last_angle = 0;
425         _last_horizontal = _side % 2;
426     }
428     virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
429     {
430         Geom::Point scc = held_shift(*event) ? _skew_center : _skew_opposite;
431         // d1 and d2 are reversed with respect to ScaleSideHandle
432         Geom::Dim2 d1 = static_cast<Geom::Dim2>(_side % 2);
433         Geom::Dim2 d2 = static_cast<Geom::Dim2>((_side + 1) % 2);
434         Geom::Point proj, scale(1.0, 1.0);
436         // Skew handles allow scaling up to integer multiples of the original size
437         // in the second direction; prevent explosions
438         // TODO should the scaling part be only active with Alt?
439         if (!Geom::are_near(_origin[d2], scc[d2])) {
440             scale[d2] = (new_pos - scc)[d2] / (_origin - scc)[d2];
441         }
443         if (scale[d2] < 1.0) {
444             scale[d2] = copysign(1.0, scale[d2]);
445         } else {
446             scale[d2] = floor(scale[d2]);
447         }
449         // Calculate skew angle. The angle is calculated with regards to the point obtained
450         // by projecting the handle position on the relevant side of the bounding box.
451         // This avoids degeneracies when moving the skew angle over the rotation center
452         proj[d1] = new_pos[d1];
453         proj[d2] = scc[d2] + (_origin[d2] - scc[d2]) * scale[d2];
454         double angle = 0;
455         if (!Geom::are_near(proj[d2], scc[d2]))
456             angle = Geom::angle_between(_origin - scc, proj - scc);
457         if (held_control(*event)) angle = snap_angle(angle);
459         // skew matrix has the from [[1, k],[0, 1]] for horizontal skew
460         // and [[1,0],[k,1]] for vertical skew.
461         Geom::Matrix skew = Geom::identity();
462         // correct the sign of the tangent
463         skew[d2 + 1] = (d1 == Geom::X ? -1.0 : 1.0) * tan(angle);
465         _last_angle = angle;
466         Geom::Matrix t = Geom::Translate(-scc)
467             * Geom::Scale(scale) * skew
468             * Geom::Translate(scc);
469         return t;
470     }
472     virtual CommitEvent getCommitEvent() {
473         return _side % 2
474             ? COMMIT_MOUSE_SKEW_Y
475             : COMMIT_MOUSE_SKEW_X;
476     }
478     virtual Glib::ustring _getTip(unsigned state) {
479         if (state_held_shift(state)) {
480             if (state_held_control(state)) {
481                 return format_tip(C_("Transform handle tip",
482                     "<b>Shift+Ctrl</b>: skew about the rotation center with snapping "
483                     "to %f° increments"), snap_increment_degrees());
484             }
485             return C_("Transform handle tip", "<b>Shift</b>: skew about the rotation center");
486         }
487         if (state_held_control(state)) {
488             return format_tip(C_("Transform handle tip",
489                 "<b>Ctrl</b>: snap skew angle to %f° increments"), snap_increment_degrees());
490         }
491         return C_("Transform handle tip",
492             "<b>Skew handle</b>: drag to skew (shear) selection about "
493             "the opposite handle");
494     }
496     virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
497         if (_last_horizontal) {
498             return format_tip(C_("Transform handle tip", "Skew horizontally by %.2f°"),
499                 _last_angle * 360.0);
500         } else {
501             return format_tip(C_("Transform handle tip", "Skew vertically by %.2f°"),
502                 _last_angle * 360.0);
503         }
504     }
506     virtual bool _hasDragTips() { return true; }
508 private:
510     static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned s) {
511         sp_select_context_get_type();
512         switch (s % 4) {
513         case 0: return Glib::wrap(handles[9], true);
514         case 1: return Glib::wrap(handles[7], true);
515         case 2: return Glib::wrap(handles[5], true);
516         default: return Glib::wrap(handles[11], true);
517         }
518     }
519     Geom::Point _skew_center;
520     Geom::Point _skew_opposite;
521     unsigned _side;
522     static bool _last_horizontal;
523     static double _last_angle;
524 };
525 bool SkewHandle::_last_horizontal = false;
526 double SkewHandle::_last_angle = 0;
528 class RotationCenter : public ControlPoint {
529 public:
530     RotationCenter(TransformHandleSet &th)
531         : ControlPoint(th._desktop, Geom::Point(), Gtk::ANCHOR_CENTER, _get_pixbuf(),
532             &center_cset, th._transform_handle_group)
533         , _th(th)
534     {
535         setVisible(false);
536     }
538 protected:
539     virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event) {
540         SnapManager &sm = _desktop->namedview->snap_manager;
541         sm.setup(_desktop);
542         bool snap = !held_shift(*event) && sm.someSnapperMightSnap();
543         if (held_control(*event)) {
544             // constrain to axes
545             Geom::Point origin = _last_drag_origin();
546             std::vector<Inkscape::Snapper::SnapConstraint> constraints;
547             constraints.push_back(Inkscape::Snapper::SnapConstraint(origin, Geom::Point(1, 0)));
548             constraints.push_back(Inkscape::Snapper::SnapConstraint(origin, Geom::Point(0, 1)));
549             new_pos = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos,
550                 SNAPSOURCE_ROTATION_CENTER), constraints, held_shift(*event)).getPoint();
551         } else if (snap) {
552             sm.freeSnapReturnByRef(new_pos, SNAPSOURCE_ROTATION_CENTER);
553         }
554         sm.unSetup();
555     }
556     virtual Glib::ustring _getTip(unsigned /*state*/) {
557         return C_("Transform handle tip",
558             "<b>Rotation center</b>: drag to change the origin of transforms");
559     }
561 private:
563     static Glib::RefPtr<Gdk::Pixbuf> _get_pixbuf() {
564         sp_select_context_get_type();
565         return Glib::wrap(handles[12], true);
566     }
568     TransformHandleSet &_th;
569 };
571 TransformHandleSet::TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group)
572     : Manipulator(d)
573     , _active(0)
574     , _transform_handle_group(th_group)
575     , _mode(MODE_SCALE)
576     , _in_transform(false)
577     , _visible(true)
579     _trans_outline = static_cast<CtrlRect*>(sp_canvas_item_new(sp_desktop_controls(_desktop),
580         SP_TYPE_CTRLRECT, NULL));
581     sp_canvas_item_hide(_trans_outline);
582     _trans_outline->setDashed(true);
584     for (unsigned i = 0; i < 4; ++i) {
585         _scale_corners[i] = new ScaleCornerHandle(*this, i);
586         _scale_sides[i] = new ScaleSideHandle(*this, i);
587         _rot_corners[i] = new RotateHandle(*this, i);
588         _skew_sides[i] = new SkewHandle(*this, i);
589     }
590     _center = new RotationCenter(*this);
591     // when transforming, update rotation center position
592     signal_transform.connect(sigc::mem_fun(*_center, &RotationCenter::transform));
595 TransformHandleSet::~TransformHandleSet()
597     for (unsigned i = 0; i < 17; ++i) {
598         delete _handles[i];
599     }
602 /** Sets the mode of transform handles (scale or rotate). */
603 void TransformHandleSet::setMode(Mode m)
605     _mode = m;
606     _updateVisibility(_visible);
609 Geom::Rect TransformHandleSet::bounds()
611     return Geom::Rect(*_scale_corners[0], *_scale_corners[2]);
614 ControlPoint &TransformHandleSet::rotationCenter()
616     return *_center;
619 void TransformHandleSet::setVisible(bool v)
621     if (_visible != v) {
622         _visible = v;
623         _updateVisibility(_visible);
624     }
627 void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center)
629     if (_in_transform) {
630         _trans_outline->setRectangle(r);
631     } else {
632         for (unsigned i = 0; i < 4; ++i) {
633             _scale_corners[i]->move(r.corner(i));
634             _scale_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
635             _rot_corners[i]->move(r.corner(i));
636             _skew_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
637         }
638         if (!preserve_center) _center->move(r.midpoint());
639         if (_visible) _updateVisibility(true);
640     }
643 bool TransformHandleSet::event(GdkEvent*)
645     return false;
648 void TransformHandleSet::_emitTransform(Geom::Matrix const &t)
650     signal_transform.emit(t);
651     _center->transform(t);
654 void TransformHandleSet::_setActiveHandle(ControlPoint *th)
656     _active = th;
657     if (_in_transform)
658         throw std::logic_error("Transform initiated when another transform in progress");
659     _in_transform = true;
660     // hide all handles except the active one
661     _updateVisibility(false);
662     sp_canvas_item_show(_trans_outline);
665 void TransformHandleSet::_clearActiveHandle()
667     // This can only be called from handles, so they had to be visible before _setActiveHandle
668     sp_canvas_item_hide(_trans_outline);
669     _active = 0;
670     _in_transform = false;
671     _updateVisibility(_visible);
674 /** Update the visibility of transformation handles according to settings and the dimensions
675  * of the bounding box. It hides the handles that would have no effect or lead to
676  * discontinuities. Additionally, side handles for which there is no space are not shown. */
677 void TransformHandleSet::_updateVisibility(bool v)
679     if (v) {
680         Geom::Rect b = bounds();
681         Geom::Point handle_size(
682             gdk_pixbuf_get_width(handles[0]) / _desktop->current_zoom(),
683             gdk_pixbuf_get_height(handles[0]) / _desktop->current_zoom());
684         Geom::Point bp = b.dimensions();
686         // do not scale when the bounding rectangle has zero width or height
687         bool show_scale = (_mode == MODE_SCALE) && !Geom::are_near(b.minExtent(), 0);
688         // do not rotate if the bounding rectangle is degenerate
689         bool show_rotate = (_mode == MODE_ROTATE_SKEW) && !Geom::are_near(b.maxExtent(), 0);
690         bool show_scale_side[2], show_skew[2];
692         // show sides if:
693         // a) there is enough space between corner handles, or
694         // b) corner handles are not shown, but side handles make sense
695         // this affects horizontal and vertical scale handles; skew handles never
696         // make sense if rotate handles are not shown
697         for (unsigned i = 0; i < 2; ++i) {
698             Geom::Dim2 d = static_cast<Geom::Dim2>(i);
699             Geom::Dim2 otherd = static_cast<Geom::Dim2>((i+1)%2);
700             show_scale_side[i] = (_mode == MODE_SCALE);
701             show_scale_side[i] &= (show_scale ? bp[d] >= handle_size[d]
702                 : !Geom::are_near(bp[otherd], 0));
703             show_skew[i] = (show_rotate && bp[d] >= handle_size[d]
704                 && !Geom::are_near(bp[otherd], 0));
705         }
706         for (unsigned i = 0; i < 4; ++i) {
707             _scale_corners[i]->setVisible(show_scale);
708             _rot_corners[i]->setVisible(show_rotate);
709             _scale_sides[i]->setVisible(show_scale_side[i%2]);
710             _skew_sides[i]->setVisible(show_skew[i%2]);
711         }
712         // show rotation center if there is enough space (?)
713         _center->setVisible(show_rotate /*&& bp[Geom::X] > handle_size[Geom::X]
714             && bp[Geom::Y] > handle_size[Geom::Y]*/);
715     } else {
716         for (unsigned i = 0; i < 17; ++i) {
717             if (_handles[i] != _active)
718                 _handles[i]->setVisible(false);
719         }
720     }
721     
724 } // namespace UI
725 } // namespace Inkscape
727 /*
728   Local Variables:
729   mode:c++
730   c-file-style:"stroustrup"
731   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
732   indent-tabs-mode:nil
733   fill-column:99
734   End:
735 */
736 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :