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 std::cout << "startTransform()" << std::endl;
204 selection->setOriginalPoints();
205 }
206 virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
207 Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
208 Geom::Point vold = _origin - scc, vnew = new_pos - scc;
210 // avoid exploding the selection
211 if (Geom::are_near(vold[Geom::X], 0) || Geom::are_near(vold[Geom::Y], 0))
212 return Geom::identity();
214 double scale[2] = { vnew[Geom::X] / vold[Geom::X], vnew[Geom::Y] / vold[Geom::Y] };
216 if (held_alt(*event)) {
217 for (unsigned i = 0; i < 2; ++i) {
218 if (scale[i] >= 1.0) scale[i] = round(scale[i]);
219 else scale[i] = 1.0 / round(1.0 / scale[i]);
220 }
221 } else {
222 //SPDesktop *desktop = _th._desktop; // Won't work as _desktop is protected
223 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
224 SnapManager &m = desktop->namedview->snap_manager;
226 // The lines below have been copied from Handle::dragged() in node.cpp, and need to be
227 // activated if we want to snap to unselected (i.e. stationary) nodes and stationary pieces of paths of the
228 // path that's currently being edited
229 /*
230 std::vector<Inkscape::SnapCandidatePoint> unselected;
231 typedef ControlPointSelection::Set Set;
232 Set &nodes = _parent->_selection.allPoints();
233 for (Set::iterator i = nodes.begin(); i != nodes.end(); ++i) {
234 Node *n = static_cast<Node*>(*i);
235 Inkscape::SnapCandidatePoint p(n->position(), n->_snapSourceType(), n->_snapTargetType());
236 unselected.push_back(p);
237 }
238 m.setupIgnoreSelection(_desktop, true, &unselected);
239 */
241 m.setupIgnoreSelection(_desktop);
243 Inkscape::SnappedPoint sp;
244 if (held_control(*event)) {
245 scale[0] = scale[1] = std::min(scale[0], scale[1]);
246 sp = m.constrainedSnapScale(_snap_points, _origin, Geom::Scale(scale[0], scale[1]), scc);
247 } else {
248 sp = m.freeSnapScale(_snap_points, _origin, Geom::Scale(scale[0], scale[1]), scc);
249 }
250 m.unSetup();
252 if (sp.getSnapped()) {
253 Geom::Point result = sp.getTransformation();
254 scale[0] = result[0];
255 scale[1] = result[1];
256 }
257 }
259 _last_scale_x = scale[0];
260 _last_scale_y = scale[1];
261 Geom::Matrix t = Geom::Translate(-scc)
262 * Geom::Scale(scale[0], scale[1])
263 * Geom::Translate(scc);
264 return t;
265 }
266 virtual CommitEvent getCommitEvent() {
267 return _last_transform.isUniformScale()
268 ? COMMIT_MOUSE_SCALE_UNIFORM
269 : COMMIT_MOUSE_SCALE;
270 }
271 private:
272 static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
273 sp_select_context_get_type();
274 switch (c % 2) {
275 case 0: return Glib::wrap(handles[1], true);
276 default: return Glib::wrap(handles[0], true);
277 }
278 }
279 Geom::Point _sc_center;
280 Geom::Point _sc_opposite;
281 unsigned _corner;
282 };
284 /// Side scaling handle for node transforms
285 class ScaleSideHandle : public ScaleHandle {
286 public:
287 ScaleSideHandle(TransformHandleSet &th, unsigned side)
288 : ScaleHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
289 , _side(side)
290 {}
291 protected:
292 virtual void startTransform() {
293 _sc_center = _th.rotationCenter();
294 Geom::Rect b = _th.bounds();
295 _sc_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
296 _last_scale_x = _last_scale_y = 1.0;
297 }
298 virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
299 Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
300 Geom::Point vs;
301 Geom::Dim2 d1 = static_cast<Geom::Dim2>((_side + 1) % 2);
302 Geom::Dim2 d2 = static_cast<Geom::Dim2>(_side % 2);
304 // avoid exploding the selection
305 if (Geom::are_near(scc[d1], _origin[d1]))
306 return Geom::identity();
308 vs[d1] = (new_pos - scc)[d1] / (_origin - scc)[d1];
309 if (held_alt(*event)) {
310 if (vs[d1] >= 1.0) vs[d1] = round(vs[d1]);
311 else vs[d1] = 1.0 / round(1.0 / vs[d1]);
312 }
313 vs[d2] = held_control(*event) ? vs[d1] : 1.0;
315 _last_scale_x = vs[Geom::X];
316 _last_scale_y = vs[Geom::Y];
317 Geom::Matrix t = Geom::Translate(-scc)
318 * Geom::Scale(vs)
319 * Geom::Translate(scc);
320 return t;
321 }
322 virtual CommitEvent getCommitEvent() {
323 return _last_transform.isUniformScale()
324 ? COMMIT_MOUSE_SCALE_UNIFORM
325 : COMMIT_MOUSE_SCALE;
326 }
327 private:
328 static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned c) {
329 sp_select_context_get_type();
330 switch (c % 2) {
331 case 0: return Glib::wrap(handles[3], true);
332 default: return Glib::wrap(handles[2], true);
333 }
334 }
335 Geom::Point _sc_center;
336 Geom::Point _sc_opposite;
337 unsigned _side;
338 };
340 /// Rotation handle for node transforms
341 class RotateHandle : public TransformHandle {
342 public:
343 RotateHandle(TransformHandleSet &th, unsigned corner)
344 : TransformHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner))
345 , _corner(corner)
346 {}
347 protected:
349 virtual void startTransform() {
350 _rot_center = _th.rotationCenter();
351 _rot_opposite = _th.bounds().corner(_corner + 2);
352 _last_angle = 0;
353 }
355 virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
356 {
357 Geom::Point rotc = held_shift(*event) ? _rot_opposite : _rot_center;
358 double angle = Geom::angle_between(_origin - rotc, new_pos - rotc);
359 if (held_control(*event)) {
360 angle = snap_angle(angle);
361 }
362 _last_angle = angle;
363 Geom::Matrix t = Geom::Translate(-rotc)
364 * Geom::Rotate(angle)
365 * Geom::Translate(rotc);
366 return t;
367 }
369 virtual CommitEvent getCommitEvent() { return COMMIT_MOUSE_ROTATE; }
371 virtual Glib::ustring _getTip(unsigned state) {
372 if (state_held_shift(state)) {
373 if (state_held_control(state)) {
374 return format_tip(C_("Transform handle tip",
375 "<b>Shift+Ctrl</b>: rotate around the opposite corner and snap "
376 "angle to %f° increments"), snap_increment_degrees());
377 }
378 return C_("Transform handle tip", "<b>Shift</b>: rotate around the opposite corner");
379 }
380 if (state_held_control(state)) {
381 return format_tip(C_("Transform handle tip",
382 "<b>Ctrl</b>: snap angle to %f° increments"), snap_increment_degrees());
383 }
384 return C_("Transform handle tip", "<b>Rotation handle</b>: drag to rotate "
385 "the selection around the rotation center");
386 }
388 virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
389 return format_tip(C_("Transform handle tip", "Rotate by %.2f°"),
390 _last_angle * 360.0);
391 }
393 virtual bool _hasDragTips() { return true; }
395 private:
396 static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
397 sp_select_context_get_type();
398 switch (c % 4) {
399 case 0: return Glib::wrap(handles[10], true);
400 case 1: return Glib::wrap(handles[8], true);
401 case 2: return Glib::wrap(handles[6], true);
402 default: return Glib::wrap(handles[4], true);
403 }
404 }
405 Geom::Point _rot_center;
406 Geom::Point _rot_opposite;
407 unsigned _corner;
408 static double _last_angle;
409 };
410 double RotateHandle::_last_angle = 0;
412 class SkewHandle : public TransformHandle {
413 public:
414 SkewHandle(TransformHandleSet &th, unsigned side)
415 : TransformHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
416 , _side(side)
417 {}
419 protected:
421 virtual void startTransform() {
422 _skew_center = _th.rotationCenter();
423 Geom::Rect b = _th.bounds();
424 _skew_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
425 _last_angle = 0;
426 _last_horizontal = _side % 2;
427 }
429 virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
430 {
431 Geom::Point scc = held_shift(*event) ? _skew_center : _skew_opposite;
432 // d1 and d2 are reversed with respect to ScaleSideHandle
433 Geom::Dim2 d1 = static_cast<Geom::Dim2>(_side % 2);
434 Geom::Dim2 d2 = static_cast<Geom::Dim2>((_side + 1) % 2);
435 Geom::Point proj, scale(1.0, 1.0);
437 // Skew handles allow scaling up to integer multiples of the original size
438 // in the second direction; prevent explosions
439 // TODO should the scaling part be only active with Alt?
440 if (!Geom::are_near(_origin[d2], scc[d2])) {
441 scale[d2] = (new_pos - scc)[d2] / (_origin - scc)[d2];
442 }
444 if (scale[d2] < 1.0) {
445 scale[d2] = copysign(1.0, scale[d2]);
446 } else {
447 scale[d2] = floor(scale[d2]);
448 }
450 // Calculate skew angle. The angle is calculated with regards to the point obtained
451 // by projecting the handle position on the relevant side of the bounding box.
452 // This avoids degeneracies when moving the skew angle over the rotation center
453 proj[d1] = new_pos[d1];
454 proj[d2] = scc[d2] + (_origin[d2] - scc[d2]) * scale[d2];
455 double angle = 0;
456 if (!Geom::are_near(proj[d2], scc[d2]))
457 angle = Geom::angle_between(_origin - scc, proj - scc);
458 if (held_control(*event)) angle = snap_angle(angle);
460 // skew matrix has the from [[1, k],[0, 1]] for horizontal skew
461 // and [[1,0],[k,1]] for vertical skew.
462 Geom::Matrix skew = Geom::identity();
463 // correct the sign of the tangent
464 skew[d2 + 1] = (d1 == Geom::X ? -1.0 : 1.0) * tan(angle);
466 _last_angle = angle;
467 Geom::Matrix t = Geom::Translate(-scc)
468 * Geom::Scale(scale) * skew
469 * Geom::Translate(scc);
470 return t;
471 }
473 virtual CommitEvent getCommitEvent() {
474 return _side % 2
475 ? COMMIT_MOUSE_SKEW_Y
476 : COMMIT_MOUSE_SKEW_X;
477 }
479 virtual Glib::ustring _getTip(unsigned state) {
480 if (state_held_shift(state)) {
481 if (state_held_control(state)) {
482 return format_tip(C_("Transform handle tip",
483 "<b>Shift+Ctrl</b>: skew about the rotation center with snapping "
484 "to %f° increments"), snap_increment_degrees());
485 }
486 return C_("Transform handle tip", "<b>Shift</b>: skew about the rotation center");
487 }
488 if (state_held_control(state)) {
489 return format_tip(C_("Transform handle tip",
490 "<b>Ctrl</b>: snap skew angle to %f° increments"), snap_increment_degrees());
491 }
492 return C_("Transform handle tip",
493 "<b>Skew handle</b>: drag to skew (shear) selection about "
494 "the opposite handle");
495 }
497 virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
498 if (_last_horizontal) {
499 return format_tip(C_("Transform handle tip", "Skew horizontally by %.2f°"),
500 _last_angle * 360.0);
501 } else {
502 return format_tip(C_("Transform handle tip", "Skew vertically by %.2f°"),
503 _last_angle * 360.0);
504 }
505 }
507 virtual bool _hasDragTips() { return true; }
509 private:
511 static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned s) {
512 sp_select_context_get_type();
513 switch (s % 4) {
514 case 0: return Glib::wrap(handles[9], true);
515 case 1: return Glib::wrap(handles[7], true);
516 case 2: return Glib::wrap(handles[5], true);
517 default: return Glib::wrap(handles[11], true);
518 }
519 }
520 Geom::Point _skew_center;
521 Geom::Point _skew_opposite;
522 unsigned _side;
523 static bool _last_horizontal;
524 static double _last_angle;
525 };
526 bool SkewHandle::_last_horizontal = false;
527 double SkewHandle::_last_angle = 0;
529 class RotationCenter : public ControlPoint {
530 public:
531 RotationCenter(TransformHandleSet &th)
532 : ControlPoint(th._desktop, Geom::Point(), Gtk::ANCHOR_CENTER, _get_pixbuf(),
533 ¢er_cset, th._transform_handle_group)
534 , _th(th)
535 {
536 setVisible(false);
537 }
539 protected:
540 virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event) {
541 SnapManager &sm = _desktop->namedview->snap_manager;
542 sm.setup(_desktop);
543 bool snap = !held_shift(*event) && sm.someSnapperMightSnap();
544 if (held_control(*event)) {
545 // constrain to axes
546 Geom::Point origin = _last_drag_origin();
547 std::vector<Inkscape::Snapper::SnapConstraint> constraints;
548 constraints.push_back(Inkscape::Snapper::SnapConstraint(origin, Geom::Point(1, 0)));
549 constraints.push_back(Inkscape::Snapper::SnapConstraint(origin, Geom::Point(0, 1)));
550 new_pos = sm.multipleConstrainedSnaps(Inkscape::SnapCandidatePoint(new_pos,
551 SNAPSOURCE_ROTATION_CENTER), constraints, held_shift(*event)).getPoint();
552 } else if (snap) {
553 sm.freeSnapReturnByRef(new_pos, SNAPSOURCE_ROTATION_CENTER);
554 }
555 sm.unSetup();
556 }
557 virtual Glib::ustring _getTip(unsigned /*state*/) {
558 return C_("Transform handle tip",
559 "<b>Rotation center</b>: drag to change the origin of transforms");
560 }
562 private:
564 static Glib::RefPtr<Gdk::Pixbuf> _get_pixbuf() {
565 sp_select_context_get_type();
566 return Glib::wrap(handles[12], true);
567 }
569 TransformHandleSet &_th;
570 };
572 TransformHandleSet::TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group)
573 : Manipulator(d)
574 , _active(0)
575 , _transform_handle_group(th_group)
576 , _mode(MODE_SCALE)
577 , _in_transform(false)
578 , _visible(true)
579 {
580 _trans_outline = static_cast<CtrlRect*>(sp_canvas_item_new(sp_desktop_controls(_desktop),
581 SP_TYPE_CTRLRECT, NULL));
582 sp_canvas_item_hide(_trans_outline);
583 _trans_outline->setDashed(true);
585 for (unsigned i = 0; i < 4; ++i) {
586 _scale_corners[i] = new ScaleCornerHandle(*this, i);
587 _scale_sides[i] = new ScaleSideHandle(*this, i);
588 _rot_corners[i] = new RotateHandle(*this, i);
589 _skew_sides[i] = new SkewHandle(*this, i);
590 }
591 _center = new RotationCenter(*this);
592 // when transforming, update rotation center position
593 signal_transform.connect(sigc::mem_fun(*_center, &RotationCenter::transform));
594 }
596 TransformHandleSet::~TransformHandleSet()
597 {
598 for (unsigned i = 0; i < 17; ++i) {
599 delete _handles[i];
600 }
601 }
603 /** Sets the mode of transform handles (scale or rotate). */
604 void TransformHandleSet::setMode(Mode m)
605 {
606 _mode = m;
607 _updateVisibility(_visible);
608 }
610 Geom::Rect TransformHandleSet::bounds()
611 {
612 return Geom::Rect(*_scale_corners[0], *_scale_corners[2]);
613 }
615 ControlPoint &TransformHandleSet::rotationCenter()
616 {
617 return *_center;
618 }
620 void TransformHandleSet::setVisible(bool v)
621 {
622 if (_visible != v) {
623 _visible = v;
624 _updateVisibility(_visible);
625 }
626 }
628 void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center)
629 {
630 if (_in_transform) {
631 _trans_outline->setRectangle(r);
632 } else {
633 for (unsigned i = 0; i < 4; ++i) {
634 _scale_corners[i]->move(r.corner(i));
635 _scale_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
636 _rot_corners[i]->move(r.corner(i));
637 _skew_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
638 }
639 if (!preserve_center) _center->move(r.midpoint());
640 if (_visible) _updateVisibility(true);
641 }
642 }
644 bool TransformHandleSet::event(GdkEvent*)
645 {
646 return false;
647 }
649 void TransformHandleSet::_emitTransform(Geom::Matrix const &t)
650 {
651 signal_transform.emit(t);
652 _center->transform(t);
653 }
655 void TransformHandleSet::_setActiveHandle(ControlPoint *th)
656 {
657 _active = th;
658 if (_in_transform)
659 throw std::logic_error("Transform initiated when another transform in progress");
660 _in_transform = true;
661 // hide all handles except the active one
662 _updateVisibility(false);
663 sp_canvas_item_show(_trans_outline);
664 }
666 void TransformHandleSet::_clearActiveHandle()
667 {
668 // This can only be called from handles, so they had to be visible before _setActiveHandle
669 sp_canvas_item_hide(_trans_outline);
670 _active = 0;
671 _in_transform = false;
672 _updateVisibility(_visible);
673 }
675 /** Update the visibility of transformation handles according to settings and the dimensions
676 * of the bounding box. It hides the handles that would have no effect or lead to
677 * discontinuities. Additionally, side handles for which there is no space are not shown. */
678 void TransformHandleSet::_updateVisibility(bool v)
679 {
680 if (v) {
681 Geom::Rect b = bounds();
682 Geom::Point handle_size(
683 gdk_pixbuf_get_width(handles[0]) / _desktop->current_zoom(),
684 gdk_pixbuf_get_height(handles[0]) / _desktop->current_zoom());
685 Geom::Point bp = b.dimensions();
687 // do not scale when the bounding rectangle has zero width or height
688 bool show_scale = (_mode == MODE_SCALE) && !Geom::are_near(b.minExtent(), 0);
689 // do not rotate if the bounding rectangle is degenerate
690 bool show_rotate = (_mode == MODE_ROTATE_SKEW) && !Geom::are_near(b.maxExtent(), 0);
691 bool show_scale_side[2], show_skew[2];
693 // show sides if:
694 // a) there is enough space between corner handles, or
695 // b) corner handles are not shown, but side handles make sense
696 // this affects horizontal and vertical scale handles; skew handles never
697 // make sense if rotate handles are not shown
698 for (unsigned i = 0; i < 2; ++i) {
699 Geom::Dim2 d = static_cast<Geom::Dim2>(i);
700 Geom::Dim2 otherd = static_cast<Geom::Dim2>((i+1)%2);
701 show_scale_side[i] = (_mode == MODE_SCALE);
702 show_scale_side[i] &= (show_scale ? bp[d] >= handle_size[d]
703 : !Geom::are_near(bp[otherd], 0));
704 show_skew[i] = (show_rotate && bp[d] >= handle_size[d]
705 && !Geom::are_near(bp[otherd], 0));
706 }
707 for (unsigned i = 0; i < 4; ++i) {
708 _scale_corners[i]->setVisible(show_scale);
709 _rot_corners[i]->setVisible(show_rotate);
710 _scale_sides[i]->setVisible(show_scale_side[i%2]);
711 _skew_sides[i]->setVisible(show_skew[i%2]);
712 }
713 // show rotation center if there is enough space (?)
714 _center->setVisible(show_rotate /*&& bp[Geom::X] > handle_size[Geom::X]
715 && bp[Geom::Y] > handle_size[Geom::Y]*/);
716 } else {
717 for (unsigned i = 0; i < 17; ++i) {
718 if (_handles[i] != _active)
719 _handles[i]->setVisible(false);
720 }
721 }
723 }
725 } // namespace UI
726 } // namespace Inkscape
728 /*
729 Local Variables:
730 mode:c++
731 c-file-style:"stroustrup"
732 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
733 indent-tabs-mode:nil
734 fill-column:99
735 End:
736 */
737 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :