Code

cafd592a3622017f42f62fafdd7253bd1b17f461
[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 "sp-namedview.h"
23 #include "ui/tool/commit-events.h"
24 #include "ui/tool/control-point.h"
25 #include "ui/tool/event-utils.h"
26 #include "ui/tool/transform-handle-set.h"
28 // FIXME BRAIN DAMAGE WARNING: this is a global variable in select-context.cpp
29 // It should be moved to a header
30 extern GdkPixbuf *handles[];
31 GType sp_select_context_get_type();
33 namespace Inkscape {
34 namespace UI {
36 namespace {
37 Gtk::AnchorType corner_to_anchor(unsigned c) {
38     switch (c % 4) {
39     case 0: return Gtk::ANCHOR_NE;
40     case 1: return Gtk::ANCHOR_NW;
41     case 2: return Gtk::ANCHOR_SW;
42     default: return Gtk::ANCHOR_SE;
43     }
44 }
45 Gtk::AnchorType side_to_anchor(unsigned s) {
46     switch (s % 4) {
47     case 0: return Gtk::ANCHOR_N;
48     case 1: return Gtk::ANCHOR_W;
49     case 2: return Gtk::ANCHOR_S;
50     default: return Gtk::ANCHOR_E;
51     }
52 }
54 // TODO move those two functions into a common place
55 double snap_angle(double a) {
56     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
57     int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
58     double unit_angle = M_PI / snaps;
59     return CLAMP(unit_angle * round(a / unit_angle), -M_PI, M_PI);
60 }
61 double snap_increment_degrees() {
62     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
63     int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
64     return 180.0 / snaps;
65 }
67 ControlPoint::ColorSet thandle_cset = {
68     {0x000000ff, 0x000000ff},
69     {0x00ff6600, 0x000000ff},
70     {0x00ff6600, 0x000000ff}
71 };
73 ControlPoint::ColorSet center_cset = {
74     {0x00000000, 0x000000ff},
75     {0x00000000, 0xff0000b0},
76     {0x00000000, 0xff0000b0}    
77 };
78 } // anonymous namespace
80 /** Base class for node transform handles to simplify implementation */
81 class TransformHandle : public ControlPoint {
82 public:
83     TransformHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb)
84         : ControlPoint(th._desktop, Geom::Point(), anchor, pb, &thandle_cset,
85             th._transform_handle_group)
86         , _th(th)
87     {
88         setVisible(false);
89     }
90 protected:
91     virtual void startTransform() {}
92     virtual void endTransform() {}
93     virtual Geom::Matrix computeTransform(Geom::Point const &pos, GdkEventMotion *event) = 0;
94     virtual CommitEvent getCommitEvent() = 0;
96     Geom::Matrix _last_transform;
97     Geom::Point _origin;
98     TransformHandleSet &_th;
99 private:
100     virtual bool grabbed(GdkEventMotion *) {
101         _origin = position();
102         _last_transform.setIdentity();
103         startTransform();
105         _th._setActiveHandle(this);
106         _cset = &invisible_cset;
107         _setState(_state);
108         return false;
109     }
110     virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event)
111     {
112         Geom::Matrix t = computeTransform(new_pos, event);
113         // protect against degeneracies
114         if (t.isSingular()) return;
115         Geom::Matrix incr = _last_transform.inverse() * t;
116         if (incr.isSingular()) return;
117         _th.signal_transform.emit(incr);
118         _last_transform = t;
119     }
120     virtual void ungrabbed(GdkEventButton *) {
121         _th._clearActiveHandle();
122         _cset = &thandle_cset;
123         _setState(_state);
124         endTransform();
125         _th.signal_commit.emit(getCommitEvent());
126     }
127 };
129 class ScaleHandle : public TransformHandle {
130 public:
131     ScaleHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb)
132         : TransformHandle(th, anchor, pb)
133     {}
134 protected:
135     virtual Glib::ustring _getTip(unsigned state) {
136         if (state_held_control(state)) {
137             if (state_held_shift(state)) {
138                 return C_("Transform handle tip",
139                     "<b>Shift+Ctrl</b>: scale uniformly about the rotation center");
140             }
141             return C_("Transform handle tip", "<b>Ctrl:</b> scale uniformly");
142         }
143         if (state_held_shift(state)) {
144             if (state_held_alt(state)) {
145                 return C_("Transform handle tip",
146                     "<b>Shift+Alt</b>: scale using an integer ratio about the rotation center");
147             }
148             return C_("Transform handle tip", "<b>Shift</b>: scale from the rotation center");
149         }
150         if (state_held_alt(state)) {
151             return C_("Transform handle tip", "<b>Alt</b>: scale using an integer ratio");
152         }
153         return C_("Transform handle tip", "<b>Scale handle</b>: drag to scale the selection");
154     }
156     virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
157         return format_tip(C_("Transform handle tip",
158             "Scale by %.2f%% x %.2f%%"), _last_scale_x * 100, _last_scale_y * 100);
159     }
161     virtual bool _hasDragTips() { return true; }
163     static double _last_scale_x, _last_scale_y;
164 };
165 double ScaleHandle::_last_scale_x = 1.0;
166 double ScaleHandle::_last_scale_y = 1.0;
168 /// Corner scaling handle for node transforms
169 class ScaleCornerHandle : public ScaleHandle {
170 public:
171     ScaleCornerHandle(TransformHandleSet &th, unsigned corner)
172         : ScaleHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner))
173         , _corner(corner)
174     {}
175 protected:
176     virtual void startTransform() {
177         _sc_center = _th.rotationCenter();
178         _sc_opposite = _th.bounds().corner(_corner + 2);
179         _last_scale_x = _last_scale_y = 1.0;
180     }
181     virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
182         Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
183         Geom::Point vold = _origin - scc, vnew = new_pos - scc;
184         // avoid exploding the selection
185         if (Geom::are_near(vold[Geom::X], 0) || Geom::are_near(vold[Geom::Y], 0))
186             return Geom::identity();
188         double scale[2] = { vnew[Geom::X] / vold[Geom::X], vnew[Geom::Y] / vold[Geom::Y] };
189         if (held_alt(*event)) {
190             for (unsigned i = 0; i < 2; ++i) {
191                 if (scale[i] >= 1.0) scale[i] = round(scale[i]);
192                 else scale[i] = 1.0 / round(1.0 / scale[i]);
193             }
194         } else if (held_control(*event)) {
195             scale[0] = scale[1] = std::min(scale[0], scale[1]);
196         }
197         _last_scale_x = scale[0];
198         _last_scale_y = scale[1];
199         Geom::Matrix t = Geom::Translate(-scc)
200             * Geom::Scale(scale[0], scale[1])
201             * Geom::Translate(scc);
202         return t;
203     }
204     virtual CommitEvent getCommitEvent() {
205         return _last_transform.isUniformScale()
206             ? COMMIT_MOUSE_SCALE_UNIFORM
207             : COMMIT_MOUSE_SCALE;
208     }
209 private:
210     static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
211         sp_select_context_get_type();
212         switch (c % 2) {
213         case 0: return Glib::wrap(handles[1], true);
214         default: return Glib::wrap(handles[0], true);
215         }
216     }
217     Geom::Point _sc_center;
218     Geom::Point _sc_opposite;
219     unsigned _corner;
220 };
222 /// Side scaling handle for node transforms
223 class ScaleSideHandle : public ScaleHandle {
224 public:
225     ScaleSideHandle(TransformHandleSet &th, unsigned side)
226         : ScaleHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
227         , _side(side)
228     {}
229 protected:
230     virtual void startTransform() {
231         _sc_center = _th.rotationCenter();
232         Geom::Rect b = _th.bounds();
233         _sc_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
234         _last_scale_x = _last_scale_y = 1.0;
235     }
236     virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
237         Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
238         Geom::Point vs;
239         Geom::Dim2 d1 = static_cast<Geom::Dim2>((_side + 1) % 2);
240         Geom::Dim2 d2 = static_cast<Geom::Dim2>(_side % 2);
242         // avoid exploding the selection
243         if (Geom::are_near(scc[d1], _origin[d1]))
244             return Geom::identity();
246         vs[d1] = (new_pos - scc)[d1] / (_origin - scc)[d1];
247         if (held_alt(*event)) {
248             if (vs[d1] >= 1.0) vs[d1] = round(vs[d1]);
249             else vs[d1] = 1.0 / round(1.0 / vs[d1]);
250         }
251         vs[d2] = held_control(*event) ? vs[d1] : 1.0;
253         _last_scale_x = vs[Geom::X];
254         _last_scale_y = vs[Geom::Y];
255         Geom::Matrix t = Geom::Translate(-scc)
256             * Geom::Scale(vs)
257             * Geom::Translate(scc);
258         return t;
259     }
260     virtual CommitEvent getCommitEvent() {
261         return _last_transform.isUniformScale()
262             ? COMMIT_MOUSE_SCALE_UNIFORM
263             : COMMIT_MOUSE_SCALE;
264     }
265 private:
266     static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned c) {
267         sp_select_context_get_type();
268         switch (c % 2) {
269         case 0: return Glib::wrap(handles[3], true);
270         default: return Glib::wrap(handles[2], true);
271         }
272     }
273     Geom::Point _sc_center;
274     Geom::Point _sc_opposite;
275     unsigned _side;
276 };
278 /// Rotation handle for node transforms
279 class RotateHandle : public TransformHandle {
280 public:
281     RotateHandle(TransformHandleSet &th, unsigned corner)
282         : TransformHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner))
283         , _corner(corner)
284     {}
285 protected:
287     virtual void startTransform() {
288         _rot_center = _th.rotationCenter();
289         _rot_opposite = _th.bounds().corner(_corner + 2);
290         _last_angle = 0;
291     }
293     virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
294     {
295         Geom::Point rotc = held_shift(*event) ? _rot_opposite : _rot_center;
296         double angle = Geom::angle_between(_origin - rotc, new_pos - rotc);
297         if (held_control(*event)) {
298             angle = snap_angle(angle);
299         }
300         _last_angle = angle;
301         Geom::Matrix t = Geom::Translate(-rotc)
302             * Geom::Rotate(angle)
303             * Geom::Translate(rotc);
304         return t;
305     }
307     virtual CommitEvent getCommitEvent() { return COMMIT_MOUSE_ROTATE; }
309     virtual Glib::ustring _getTip(unsigned state) {
310         if (state_held_shift(state)) {
311             if (state_held_control(state)) {
312                 return format_tip(C_("Transform handle tip",
313                     "<b>Shift+Ctrl</b>: rotate around the opposite corner and snap "
314                     "angle to %f° increments"), snap_increment_degrees());
315             }
316             return C_("Transform handle tip", "<b>Shift</b>: rotate around the opposite corner");
317         }
318         if (state_held_control(state)) {
319             return format_tip(C_("Transform handle tip",
320                 "<b>Ctrl</b>: snap angle to %f° increments"), snap_increment_degrees());
321         }
322         return C_("Transform handle tip", "<b>Rotation handle</b>: drag to rotate "
323             "the selection around the rotation center");
324     }
326     virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
327         return format_tip(C_("Transform handle tip", "Rotate by %.2f°"),
328             _last_angle * 360.0);
329     }
331     virtual bool _hasDragTips() { return true; }
333 private:
334     static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
335         sp_select_context_get_type();
336         switch (c % 4) {
337         case 0: return Glib::wrap(handles[10], true);
338         case 1: return Glib::wrap(handles[8], true);
339         case 2: return Glib::wrap(handles[6], true);
340         default: return Glib::wrap(handles[4], true);
341         }
342     }
343     Geom::Point _rot_center;
344     Geom::Point _rot_opposite;
345     unsigned _corner;
346     static double _last_angle;
347 };
348 double RotateHandle::_last_angle = 0;
350 class SkewHandle : public TransformHandle {
351 public:
352     SkewHandle(TransformHandleSet &th, unsigned side)
353         : TransformHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
354         , _side(side)
355     {}
357 protected:
359     virtual void startTransform() {
360         _skew_center = _th.rotationCenter();
361         Geom::Rect b = _th.bounds();
362         _skew_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
363         _last_angle = 0;
364         _last_horizontal = _side % 2;
365     }
367     virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
368     {
369         Geom::Point scc = held_shift(*event) ? _skew_center : _skew_opposite;
370         // d1 and d2 are reversed with respect to ScaleSideHandle
371         Geom::Dim2 d1 = static_cast<Geom::Dim2>(_side % 2);
372         Geom::Dim2 d2 = static_cast<Geom::Dim2>((_side + 1) % 2);
373         Geom::Point proj, scale(1.0, 1.0);
375         // Skew handles allow scaling up to integer multiples of the original size
376         // in the second direction; prevent explosions
377         // TODO should the scaling part be only active with Alt?
378         if (!Geom::are_near(_origin[d2], scc[d2])) {
379             scale[d2] = (new_pos - scc)[d2] / (_origin - scc)[d2];
380         }
382         if (scale[d2] < 1.0) {
383             scale[d2] = copysign(1.0, scale[d2]);
384         } else {
385             scale[d2] = floor(scale[d2]);
386         }
388         // Calculate skew angle. The angle is calculated with regards to the point obtained
389         // by projecting the handle position on the relevant side of the bounding box.
390         // This avoids degeneracies when moving the skew angle over the rotation center
391         proj[d1] = new_pos[d1];
392         proj[d2] = scc[d2] + (_origin[d2] - scc[d2]) * scale[d2];
393         double angle = 0;
394         if (!Geom::are_near(proj[d2], scc[d2]))
395             angle = Geom::angle_between(_origin - scc, proj - scc);
396         if (held_control(*event)) angle = snap_angle(angle);
398         // skew matrix has the from [[1, k],[0, 1]] for horizontal skew
399         // and [[1,0],[k,1]] for vertical skew.
400         Geom::Matrix skew = Geom::identity();
401         // correct the sign of the tangent
402         skew[d2 + 1] = (d1 == Geom::X ? -1.0 : 1.0) * tan(angle);
404         _last_angle = angle;
405         Geom::Matrix t = Geom::Translate(-scc)
406             * Geom::Scale(scale) * skew
407             * Geom::Translate(scc);
408         return t;
409     }
411     virtual CommitEvent getCommitEvent() {
412         return _side % 2
413             ? COMMIT_MOUSE_SKEW_Y
414             : COMMIT_MOUSE_SKEW_X;
415     }
417     virtual Glib::ustring _getTip(unsigned state) {
418         if (state_held_shift(state)) {
419             if (state_held_control(state)) {
420                 return format_tip(C_("Transform handle tip",
421                     "<b>Shift+Ctrl</b>: skew about the rotation center with snapping "
422                     "to %f° increments"), snap_increment_degrees());
423             }
424             return C_("Transform handle tip", "<b>Shift</b>: skew about the rotation center");
425         }
426         if (state_held_control(state)) {
427             return format_tip(C_("Transform handle tip",
428                 "<b>Ctrl</b>: snap skew angle to %f° increments"), snap_increment_degrees());
429         }
430         return C_("Transform handle tip",
431             "<b>Skew handle</b>: drag to skew (shear) selection about "
432             "the opposite handle");
433     }
435     virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
436         if (_last_horizontal) {
437             return format_tip(C_("Transform handle tip", "Skew horizontally by %.2f°"),
438                 _last_angle * 360.0);
439         } else {
440             return format_tip(C_("Transform handle tip", "Skew vertically by %.2f°"),
441                 _last_angle * 360.0);
442         }
443     }
445     virtual bool _hasDragTips() { return true; }
447 private:
449     static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned s) {
450         sp_select_context_get_type();
451         switch (s % 4) {
452         case 0: return Glib::wrap(handles[9], true);
453         case 1: return Glib::wrap(handles[7], true);
454         case 2: return Glib::wrap(handles[5], true);
455         default: return Glib::wrap(handles[11], true);
456         }
457     }
458     Geom::Point _skew_center;
459     Geom::Point _skew_opposite;
460     unsigned _side;
461     static bool _last_horizontal;
462     static double _last_angle;
463 };
464 bool SkewHandle::_last_horizontal = false;
465 double SkewHandle::_last_angle = 0;
467 class RotationCenter : public ControlPoint {
468 public:
469     RotationCenter(TransformHandleSet &th)
470         : ControlPoint(th._desktop, Geom::Point(), Gtk::ANCHOR_CENTER, _get_pixbuf(),
471             &center_cset, th._transform_handle_group)
472         , _th(th)
473     {
474         setVisible(false);
475     }
477 protected:
478     virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event) {
479         SnapManager &sm = _desktop->namedview->snap_manager;
480         sm.setup(_desktop);
481         bool snap = !held_shift(*event) && sm.someSnapperMightSnap();
482         if (held_control(*event)) {
483             // constrain to axes
484             Geom::Point origin = _last_drag_origin();
485             std::vector<Inkscape::Snapper::SnapConstraint> constraints;
486             constraints.push_back(Inkscape::Snapper::SnapConstraint(origin, Geom::Point(1, 0)));
487             constraints.push_back(Inkscape::Snapper::SnapConstraint(origin, Geom::Point(0, 1)));
488             new_pos = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos,
489                 SNAPSOURCE_ROTATION_CENTER), constraints, held_shift(*event)).getPoint();
490         } else if (snap) {
491             sm.freeSnapReturnByRef(new_pos, SNAPSOURCE_ROTATION_CENTER);
492         }
493         sm.unSetup();
494     }
495     virtual Glib::ustring _getTip(unsigned /*state*/) {
496         return C_("Transform handle tip",
497             "<b>Rotation center</b>: drag to change the origin of transforms");
498     }
500 private:
502     static Glib::RefPtr<Gdk::Pixbuf> _get_pixbuf() {
503         sp_select_context_get_type();
504         return Glib::wrap(handles[12], true);
505     }
507     TransformHandleSet &_th;
508 };
510 TransformHandleSet::TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group)
511     : Manipulator(d)
512     , _active(0)
513     , _transform_handle_group(th_group)
514     , _mode(MODE_SCALE)
515     , _in_transform(false)
516     , _visible(true)
518     _trans_outline = static_cast<CtrlRect*>(sp_canvas_item_new(sp_desktop_controls(_desktop),
519         SP_TYPE_CTRLRECT, NULL));
520     sp_canvas_item_hide(_trans_outline);
521     _trans_outline->setDashed(true);
523     for (unsigned i = 0; i < 4; ++i) {
524         _scale_corners[i] = new ScaleCornerHandle(*this, i);
525         _scale_sides[i] = new ScaleSideHandle(*this, i);
526         _rot_corners[i] = new RotateHandle(*this, i);
527         _skew_sides[i] = new SkewHandle(*this, i);
528     }
529     _center = new RotationCenter(*this);
530     // when transforming, update rotation center position
531     signal_transform.connect(sigc::mem_fun(*_center, &RotationCenter::transform));
534 TransformHandleSet::~TransformHandleSet()
536     for (unsigned i = 0; i < 17; ++i) {
537         delete _handles[i];
538     }
541 /** Sets the mode of transform handles (scale or rotate). */
542 void TransformHandleSet::setMode(Mode m)
544     _mode = m;
545     _updateVisibility(_visible);
548 Geom::Rect TransformHandleSet::bounds()
550     return Geom::Rect(*_scale_corners[0], *_scale_corners[2]);
553 ControlPoint &TransformHandleSet::rotationCenter()
555     return *_center;
558 void TransformHandleSet::setVisible(bool v)
560     if (_visible != v) {
561         _visible = v;
562         _updateVisibility(_visible);
563     }
566 void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center)
568     if (_in_transform) {
569         _trans_outline->setRectangle(r);
570     } else {
571         for (unsigned i = 0; i < 4; ++i) {
572             _scale_corners[i]->move(r.corner(i));
573             _scale_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
574             _rot_corners[i]->move(r.corner(i));
575             _skew_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
576         }
577         if (!preserve_center) _center->move(r.midpoint());
578         if (_visible) _updateVisibility(true);
579     }
582 bool TransformHandleSet::event(GdkEvent*)
584     return false;
587 void TransformHandleSet::_emitTransform(Geom::Matrix const &t)
589     signal_transform.emit(t);
590     _center->transform(t);
593 void TransformHandleSet::_setActiveHandle(ControlPoint *th)
595     _active = th;
596     if (_in_transform)
597         throw std::logic_error("Transform initiated when another transform in progress");
598     _in_transform = true;
599     // hide all handles except the active one
600     _updateVisibility(false);
601     sp_canvas_item_show(_trans_outline);
604 void TransformHandleSet::_clearActiveHandle()
606     // This can only be called from handles, so they had to be visible before _setActiveHandle
607     sp_canvas_item_hide(_trans_outline);
608     _active = 0;
609     _in_transform = false;
610     _updateVisibility(_visible);
613 /** Update the visibility of transformation handles according to settings and the dimensions
614  * of the bounding box. It hides the handles that would have no effect or lead to
615  * discontinuities. Additionally, side handles for which there is no space are not shown. */
616 void TransformHandleSet::_updateVisibility(bool v)
618     if (v) {
619         Geom::Rect b = bounds();
620         Geom::Point handle_size(
621             gdk_pixbuf_get_width(handles[0]) / _desktop->current_zoom(),
622             gdk_pixbuf_get_height(handles[0]) / _desktop->current_zoom());
623         Geom::Point bp = b.dimensions();
625         // do not scale when the bounding rectangle has zero width or height
626         bool show_scale = (_mode == MODE_SCALE) && !Geom::are_near(b.minExtent(), 0);
627         // do not rotate if the bounding rectangle is degenerate
628         bool show_rotate = (_mode == MODE_ROTATE_SKEW) && !Geom::are_near(b.maxExtent(), 0);
629         bool show_scale_side[2], show_skew[2];
631         // show sides if:
632         // a) there is enough space between corner handles, or
633         // b) corner handles are not shown, but side handles make sense
634         // this affects horizontal and vertical scale handles; skew handles never
635         // make sense if rotate handles are not shown
636         for (unsigned i = 0; i < 2; ++i) {
637             Geom::Dim2 d = static_cast<Geom::Dim2>(i);
638             Geom::Dim2 otherd = static_cast<Geom::Dim2>((i+1)%2);
639             show_scale_side[i] = (_mode == MODE_SCALE);
640             show_scale_side[i] &= (show_scale ? bp[d] >= handle_size[d]
641                 : !Geom::are_near(bp[otherd], 0));
642             show_skew[i] = (show_rotate && bp[d] >= handle_size[d]
643                 && !Geom::are_near(bp[otherd], 0));
644         }
645         for (unsigned i = 0; i < 4; ++i) {
646             _scale_corners[i]->setVisible(show_scale);
647             _rot_corners[i]->setVisible(show_rotate);
648             _scale_sides[i]->setVisible(show_scale_side[i%2]);
649             _skew_sides[i]->setVisible(show_skew[i%2]);
650         }
651         // show rotation center if there is enough space (?)
652         _center->setVisible(show_rotate /*&& bp[Geom::X] > handle_size[Geom::X]
653             && bp[Geom::Y] > handle_size[Geom::Y]*/);
654     } else {
655         for (unsigned i = 0; i < 17; ++i) {
656             if (_handles[i] != _active)
657                 _handles[i]->setVisible(false);
658         }
659     }
660     
663 } // namespace UI
664 } // namespace Inkscape
666 /*
667   Local Variables:
668   mode:c++
669   c-file-style:"stroustrup"
670   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
671   indent-tabs-mode:nil
672   fill-column:99
673   End:
674 */
675 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :