1 /** @file
2 * Node selection - implementation
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 <2geom/transforms.h>
12 #include "desktop.h"
13 #include "preferences.h"
14 #include "ui/tool/control-point-selection.h"
15 #include "ui/tool/event-utils.h"
16 #include "ui/tool/selectable-control-point.h"
17 #include "ui/tool/transform-handle-set.h"
19 namespace Inkscape {
20 namespace UI {
22 /**
23 * @class ControlPointSelection
24 * @brief Group of selected control points.
25 *
26 * Some operations can be performed on all selected points regardless of their type, therefore
27 * this class is also a Manipulator. It handles the transformations of points using
28 * the keyboard.
29 *
30 * The exposed interface is similar to that of an STL set. Internally, a hash map is used.
31 * @todo Correct iterators (that don't expose the connection list)
32 */
34 /** @var ControlPointSelection::signal_update
35 * Fires when the display needs to be updated to reflect changes.
36 */
37 /** @var ControlPointSelection::signal_point_changed
38 * Fires when a control point is added to or removed from the selection.
39 * The first param contains a pointer to the control point that changed sel. state.
40 * The second says whether the point is currently selected.
41 */
42 /** @var ControlPointSelection::signal_commit
43 * Fires when a change that needs to be committed to XML happens.
44 */
46 ControlPointSelection::ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_group)
47 : Manipulator(d)
48 , _handles(new TransformHandleSet(d, th_group))
49 , _dragging(false)
50 , _handles_visible(true)
51 , _one_node_handles(false)
52 , _sculpt_enabled(false)
53 , _sculpting(false)
54 {
55 signal_update.connect( sigc::bind(
56 sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
57 true));
58 signal_point_changed.connect(
59 sigc::hide( sigc::hide(
60 sigc::bind(
61 sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
62 false))));
63 _handles->signal_transform.connect(
64 sigc::mem_fun(*this, &ControlPointSelection::transform));
65 _handles->signal_commit.connect(
66 sigc::mem_fun(*this, &ControlPointSelection::_commitTransform));
67 }
69 ControlPointSelection::~ControlPointSelection()
70 {
71 clear();
72 delete _handles;
73 }
75 /** Add a control point to the selection. */
76 std::pair<ControlPointSelection::iterator, bool> ControlPointSelection::insert(const value_type &x)
77 {
78 iterator found = _points.find(x);
79 if (found != _points.end()) {
80 return std::pair<iterator, bool>(found, false);
81 }
83 boost::shared_ptr<connlist_type> clist(new connlist_type());
85 // hide event param and always return false
86 clist->push_back(
87 x->signal_grabbed.connect(
88 sigc::bind_return(
89 sigc::bind<0>(
90 sigc::mem_fun(*this, &ControlPointSelection::_selectionGrabbed),
91 x),
92 false)));
93 clist->push_back(
94 x->signal_dragged.connect(
95 sigc::mem_fun(*this, &ControlPointSelection::_selectionDragged)));
96 // hide event parameter
97 clist->push_back(
98 x->signal_ungrabbed.connect(
99 sigc::hide(
100 sigc::mem_fun(*this, &ControlPointSelection::_selectionUngrabbed))));
102 found = _points.insert(std::make_pair(x, clist)).first;
104 x->updateState();
105 _rot_radius.reset();
106 signal_point_changed.emit(x, true);
108 return std::pair<iterator, bool>(found, true);
109 }
111 /** Remove a point from the selection. */
112 void ControlPointSelection::erase(iterator pos)
113 {
114 SelectableControlPoint *erased = pos->first;
115 boost::shared_ptr<connlist_type> clist = pos->second;
116 for (connlist_type::iterator i = clist->begin(); i != clist->end(); ++i) {
117 i->disconnect();
118 }
119 _points.erase(pos);
120 erased->updateState();
121 _rot_radius.reset();
122 signal_point_changed.emit(erased, false);
123 }
124 ControlPointSelection::size_type ControlPointSelection::erase(const key_type &k)
125 {
126 iterator pos = _points.find(k);
127 if (pos == _points.end()) return 0;
128 erase(pos);
129 return 1;
130 }
131 void ControlPointSelection::erase(iterator first, iterator last)
132 {
133 while (first != last) erase(first++);
134 }
136 /** Remove all points from the selection, making it empty. */
137 void ControlPointSelection::clear()
138 {
139 for (iterator i = begin(); i != end(); )
140 erase(i++);
141 }
143 /** Transform all selected control points by the supplied affine transformation. */
144 void ControlPointSelection::transform(Geom::Matrix const &m)
145 {
146 for (iterator i = _points.begin(); i != _points.end(); ++i) {
147 SelectableControlPoint *cur = i->first;
148 cur->transform(m);
149 }
150 // TODO preserving the rotation radius needs some rethinking...
151 if (_rot_radius) (*_rot_radius) *= m.descrim();
152 signal_update.emit();
153 }
155 /** Align control points on the specified axis. */
156 void ControlPointSelection::align(Geom::Dim2 axis)
157 {
158 if (empty()) return;
159 Geom::Dim2 d = static_cast<Geom::Dim2>((axis + 1) % 2);
161 Geom::OptInterval bound;
162 for (iterator i = _points.begin(); i != _points.end(); ++i) {
163 bound.unionWith(Geom::OptInterval(i->first->position()[d]));
164 }
166 double new_coord = bound->middle();
167 for (iterator i = _points.begin(); i != _points.end(); ++i) {
168 Geom::Point pos = i->first->position();
169 pos[d] = new_coord;
170 i->first->move(pos);
171 }
172 }
174 /** Equdistantly distribute control points by moving them in the specified dimension. */
175 void ControlPointSelection::distribute(Geom::Dim2 d)
176 {
177 if (empty()) return;
179 // this needs to be a multimap, otherwise it will fail when some points have the same coord
180 typedef std::multimap<double, SelectableControlPoint*> SortMap;
182 SortMap sm;
183 Geom::OptInterval bound;
184 // first we insert all points into a multimap keyed by the aligned coord to sort them
185 // simultaneously we compute the extent of selection
186 for (iterator i = _points.begin(); i != _points.end(); ++i) {
187 Geom::Point pos = i->first->position();
188 sm.insert(std::make_pair(pos[d], i->first));
189 bound.unionWith(Geom::OptInterval(pos[d]));
190 }
192 // now we iterate over the multimap and set aligned positions.
193 double step = size() == 1 ? 0 : bound->extent() / (size() - 1);
194 double start = bound->min();
195 unsigned num = 0;
196 for (SortMap::iterator i = sm.begin(); i != sm.end(); ++i, ++num) {
197 Geom::Point pos = i->second->position();
198 pos[d] = start + num * step;
199 i->second->move(pos);
200 }
201 }
203 /** Get the bounds of the selection.
204 * @return Smallest rectangle containing the positions of all selected points,
205 * or nothing if the selection is empty */
206 Geom::OptRect ControlPointSelection::pointwiseBounds()
207 {
208 Geom::OptRect bound;
209 for (iterator i = _points.begin(); i != _points.end(); ++i) {
210 SelectableControlPoint *cur = i->first;
211 Geom::Point p = cur->position();
212 if (!bound) {
213 bound = Geom::Rect(p, p);
214 } else {
215 bound->expandTo(p);
216 }
217 }
218 return bound;
219 }
221 Geom::OptRect ControlPointSelection::bounds()
222 {
223 Geom::OptRect bound;
224 for (iterator i = _points.begin(); i != _points.end(); ++i) {
225 SelectableControlPoint *cur = i->first;
226 Geom::OptRect r = cur->bounds();
227 bound.unionWith(r);
228 }
229 return bound;
230 }
232 void ControlPointSelection::showTransformHandles(bool v, bool one_node)
233 {
234 _one_node_handles = one_node;
235 _handles_visible = v;
236 _updateTransformHandles(false);
237 }
239 void ControlPointSelection::hideTransformHandles()
240 {
241 _handles->setVisible(false);
242 }
243 void ControlPointSelection::restoreTransformHandles()
244 {
245 _updateTransformHandles(true);
246 }
248 void ControlPointSelection::_selectionGrabbed(SelectableControlPoint *p, GdkEventMotion *event)
249 {
250 hideTransformHandles();
251 _dragging = true;
252 if (held_alt(*event) && _sculpt_enabled) {
253 _sculpting = true;
254 _grabbed_point = p;
255 } else {
256 _sculpting = false;
257 }
258 }
260 void ControlPointSelection::_selectionDragged(Geom::Point const &old_pos, Geom::Point &new_pos,
261 GdkEventMotion *event)
262 {
263 Geom::Point delta = new_pos - old_pos;
264 /*if (_sculpting) {
265 // for now we only support the default sculpting profile (bell)
266 // others will be added when preferences will be able to store enumerated values
267 double pressure, alpha;
268 if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &pressure)) {
269 pressure = CLAMP(pressure, 0.2, 0.8);
270 } else {
271 pressure = 0.5;
272 }
274 alpha = 1 - 2 * fabs(pressure - 0.5);
275 if (pressure > 0.5) alpha = 1/alpha;
277 for (iterator i = _points.begin(); i != _points.end(); ++i) {
278 SelectableControlPoint *cur = i->first;
279 double dist = Geom::distance(cur->position(), _grabbed_point->position());
281 cur->move(cur->position() + delta);
282 }
283 } else*/ {
284 for (iterator i = _points.begin(); i != _points.end(); ++i) {
285 SelectableControlPoint *cur = i->first;
286 cur->move(cur->position() + delta);
287 }
288 _handles->rotationCenter().move(_handles->rotationCenter().position() + delta);
289 }
290 signal_update.emit();
291 }
293 void ControlPointSelection::_selectionUngrabbed()
294 {
295 _dragging = false;
296 _grabbed_point = NULL;
297 restoreTransformHandles();
298 signal_commit.emit(COMMIT_MOUSE_MOVE);
299 }
301 void ControlPointSelection::_updateTransformHandles(bool preserve_center)
302 {
303 if (_dragging) return;
305 if (_handles_visible && size() > 1) {
306 Geom::OptRect b = pointwiseBounds();
307 _handles->setBounds(*b, preserve_center);
308 _handles->setVisible(true);
309 } else if (_one_node_handles && size() == 1) { // only one control point in selection
310 SelectableControlPoint *p = begin()->first;
311 _handles->setBounds(p->bounds());
312 _handles->rotationCenter().move(p->position());
313 _handles->rotationCenter().setVisible(false);
314 _handles->setVisible(true);
315 } else {
316 _handles->setVisible(false);
317 }
318 }
320 /** Moves the selected points along the supplied unit vector according to
321 * the modifier state of the supplied event. */
322 bool ControlPointSelection::_keyboardMove(GdkEventKey const &event, Geom::Point const &dir)
323 {
324 if (held_control(event)) return false;
325 unsigned num = 1 + consume_same_key_events(shortcut_key(event), 0);
327 Geom::Point delta = dir * num;
328 if (held_shift(event)) delta *= 10;
329 if (held_alt(event)) {
330 delta /= _desktop->current_zoom();
331 } else {
332 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
333 double nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000);
334 delta *= nudge;
335 }
337 transform(Geom::Translate(delta));
338 if (fabs(dir[Geom::X]) > 0) {
339 signal_commit.emit(COMMIT_KEYBOARD_MOVE_X);
340 } else {
341 signal_commit.emit(COMMIT_KEYBOARD_MOVE_Y);
342 }
343 return true;
344 }
346 /** Rotates the selected points in the given direction according to the modifier state
347 * from the supplied event.
348 * @param event Key event to take modifier state from
349 * @param dir Direction of rotation (math convention: 1 = counterclockwise, -1 = clockwise)
350 */
351 bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir)
352 {
353 if (empty()) return false;
355 Geom::Point rc = _handles->rotationCenter();
356 if (!_rot_radius) {
357 Geom::Rect b = *(size() == 1 ? bounds() : pointwiseBounds());
358 double maxlen = 0;
359 for (unsigned i = 0; i < 4; ++i) {
360 double len = (b.corner(i) - rc).length();
361 if (len > maxlen) maxlen = len;
362 }
363 _rot_radius = maxlen;
364 }
366 double angle;
367 if (held_alt(event)) {
368 // Rotate by "one pixel". We interpret this as rotating by an angle that causes
369 // the topmost point of a circle circumscribed about the selection's bounding box
370 // to move on an arc 1 screen pixel long.
371 angle = atan2(1.0 / _desktop->current_zoom(), *_rot_radius) * dir;
372 } else {
373 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
374 int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
375 angle = M_PI * dir / snaps;
376 }
378 // translate to origin, rotate, translate back to original position
379 Geom::Matrix m = Geom::Translate(-rc)
380 * Geom::Rotate(angle) * Geom::Translate(rc);
381 transform(m);
382 signal_commit.emit(COMMIT_KEYBOARD_ROTATE);
383 return true;
384 }
387 bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir)
388 {
389 if (empty()) return false;
391 // TODO should the saved rotation center or the current center be used?
392 Geom::Rect bound = (size() == 1 ? *bounds() : *pointwiseBounds());
393 double maxext = bound.maxExtent();
394 if (Geom::are_near(maxext, 0)) return false;
395 Geom::Point center = _handles->rotationCenter().position();
397 double length_change;
398 if (held_alt(event)) {
399 // Scale by "one pixel". It means shrink/grow 1px for the larger dimension
400 // of the bounding box.
401 length_change = 1.0 / _desktop->current_zoom() * dir;
402 } else {
403 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
404 length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000);
405 length_change *= dir;
406 }
407 double scale = (maxext + length_change) / maxext;
409 Geom::Matrix m = Geom::Translate(-center) * Geom::Scale(scale) * Geom::Translate(center);
410 transform(m);
411 signal_commit.emit(COMMIT_KEYBOARD_SCALE_UNIFORM);
412 return true;
413 }
415 bool ControlPointSelection::_keyboardFlip(Geom::Dim2 d)
416 {
417 if (empty()) return false;
419 Geom::Scale scale_transform(1, 1);
420 if (d == Geom::X) {
421 scale_transform = Geom::Scale(-1, 1);
422 } else {
423 scale_transform = Geom::Scale(1, -1);
424 }
426 SelectableControlPoint *scp =
427 dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
428 Geom::Point center = scp ? scp->position() : _handles->rotationCenter().position();
430 Geom::Matrix m = Geom::Translate(-center) * scale_transform * Geom::Translate(center);
431 transform(m);
432 signal_commit.emit(d == Geom::X ? COMMIT_FLIP_X : COMMIT_FLIP_Y);
433 return true;
434 }
436 void ControlPointSelection::_commitTransform(CommitEvent ce)
437 {
438 _updateTransformHandles(true);
439 signal_commit.emit(ce);
440 }
442 bool ControlPointSelection::event(GdkEvent *event)
443 {
444 // implement generic event handling that should apply for all control point selections here;
445 // for example, keyboard moves and transformations. This way this functionality doesn't need
446 // to be duplicated in many places
447 // Later split out so that it can be reused in object selection
449 switch (event->type) {
450 case GDK_KEY_PRESS:
451 // do not handle key events if the selection is empty
452 if (empty()) break;
454 switch(shortcut_key(event->key)) {
455 // moves
456 case GDK_Up:
457 case GDK_KP_Up:
458 case GDK_KP_8:
459 return _keyboardMove(event->key, Geom::Point(0, 1));
460 case GDK_Down:
461 case GDK_KP_Down:
462 case GDK_KP_2:
463 return _keyboardMove(event->key, Geom::Point(0, -1));
464 case GDK_Right:
465 case GDK_KP_Right:
466 case GDK_KP_6:
467 return _keyboardMove(event->key, Geom::Point(1, 0));
468 case GDK_Left:
469 case GDK_KP_Left:
470 case GDK_KP_4:
471 return _keyboardMove(event->key, Geom::Point(-1, 0));
473 // rotates
474 case GDK_bracketleft:
475 return _keyboardRotate(event->key, 1);
476 case GDK_bracketright:
477 return _keyboardRotate(event->key, -1);
479 // scaling
480 case GDK_less:
481 case GDK_comma:
482 return _keyboardScale(event->key, -1);
483 case GDK_greater:
484 case GDK_period:
485 return _keyboardScale(event->key, 1);
487 // TODO: skewing
489 // flipping
490 // NOTE: H is horizontal flip, while Shift+H switches transform handle mode!
491 case GDK_h:
492 case GDK_H:
493 if (held_shift(event->key)) {
494 // TODO make a method for mode switching
495 if (_handles->mode() == TransformHandleSet::MODE_SCALE) {
496 _handles->setMode(TransformHandleSet::MODE_ROTATE_SKEW);
497 if (size() == 1) _handles->rotationCenter().setVisible(false);
498 } else {
499 _handles->setMode(TransformHandleSet::MODE_SCALE);
500 }
501 return true;
502 }
503 // any modifiers except shift should cause no action
504 if (held_any_modifiers(event->key)) break;
505 return _keyboardFlip(Geom::X);
506 case GDK_v:
507 case GDK_V:
508 if (held_any_modifiers(event->key)) break;
509 return _keyboardFlip(Geom::Y);
510 default: break;
511 }
512 break;
513 default: break;
514 }
515 return false;
516 }
518 } // namespace UI
519 } // namespace Inkscape
521 /*
522 Local Variables:
523 mode:c++
524 c-file-style:"stroustrup"
525 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
526 indent-tabs-mode:nil
527 fill-column:99
528 End:
529 */
530 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :