cafd592a3622017f42f62fafdd7253bd1b17f461
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 ¢er_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)
517 {
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));
532 }
534 TransformHandleSet::~TransformHandleSet()
535 {
536 for (unsigned i = 0; i < 17; ++i) {
537 delete _handles[i];
538 }
539 }
541 /** Sets the mode of transform handles (scale or rotate). */
542 void TransformHandleSet::setMode(Mode m)
543 {
544 _mode = m;
545 _updateVisibility(_visible);
546 }
548 Geom::Rect TransformHandleSet::bounds()
549 {
550 return Geom::Rect(*_scale_corners[0], *_scale_corners[2]);
551 }
553 ControlPoint &TransformHandleSet::rotationCenter()
554 {
555 return *_center;
556 }
558 void TransformHandleSet::setVisible(bool v)
559 {
560 if (_visible != v) {
561 _visible = v;
562 _updateVisibility(_visible);
563 }
564 }
566 void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center)
567 {
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 }
580 }
582 bool TransformHandleSet::event(GdkEvent*)
583 {
584 return false;
585 }
587 void TransformHandleSet::_emitTransform(Geom::Matrix const &t)
588 {
589 signal_transform.emit(t);
590 _center->transform(t);
591 }
593 void TransformHandleSet::_setActiveHandle(ControlPoint *th)
594 {
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);
602 }
604 void TransformHandleSet::_clearActiveHandle()
605 {
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);
611 }
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)
617 {
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 }
661 }
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 :