2a3498d7f8bdcd8b57151c0e82e38e9304f475ca
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 {
53 signal_update.connect( sigc::bind(
54 sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
55 true));
56 signal_point_changed.connect(
57 sigc::hide( sigc::hide(
58 sigc::bind(
59 sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
60 false))));
61 _handles->signal_transform.connect(
62 sigc::mem_fun(*this, &ControlPointSelection::transform));
63 _handles->signal_commit.connect(
64 sigc::mem_fun(*this, &ControlPointSelection::_commitTransform));
65 }
67 ControlPointSelection::~ControlPointSelection()
68 {
69 clear();
70 delete _handles;
71 }
73 /** Add a control point to the selection. */
74 std::pair<ControlPointSelection::iterator, bool> ControlPointSelection::insert(const value_type &x)
75 {
76 iterator found = _points.find(x);
77 if (found != _points.end()) {
78 return std::pair<iterator, bool>(found, false);
79 }
81 boost::shared_ptr<connlist_type> clist(new connlist_type());
83 // hide event param and always return false
84 /*clist->push_back(
85 x->signal_grabbed.connect(
86 sigc::bind_return(
87 sigc::bind<0>(
88 sigc::mem_fun(*this, &ControlPointSelection::_selectionGrabbed),
89 x),
90 false)));
91 clist->push_back(
92 x->signal_dragged.connect(
93 sigc::mem_fun(*this, &ControlPointSelection::_selectionDragged)));
94 clist->push_back(
95 x->signal_ungrabbed.connect(
96 sigc::hide(
97 sigc::mem_fun(*this, &ControlPointSelection::_selectionUngrabbed))));
98 clist->push_back(
99 x->signal_clicked.connect(
100 sigc::hide(
101 sigc::bind<0>(
102 sigc::mem_fun(*this, &ControlPointSelection::_selectionClicked),
103 x))));*/
105 found = _points.insert(std::make_pair(x, clist)).first;
107 x->updateState();
108 _rot_radius.reset();
109 signal_point_changed.emit(x, true);
111 return std::pair<iterator, bool>(found, true);
112 }
114 /** Remove a point from the selection. */
115 void ControlPointSelection::erase(iterator pos)
116 {
117 SelectableControlPoint *erased = pos->first;
118 boost::shared_ptr<connlist_type> clist = pos->second;
119 for (connlist_type::iterator i = clist->begin(); i != clist->end(); ++i) {
120 i->disconnect();
121 }
122 _points.erase(pos);
123 erased->updateState();
124 _rot_radius.reset();
125 signal_point_changed.emit(erased, false);
126 }
127 ControlPointSelection::size_type ControlPointSelection::erase(const key_type &k)
128 {
129 iterator pos = _points.find(k);
130 if (pos == _points.end()) return 0;
131 erase(pos);
132 return 1;
133 }
134 void ControlPointSelection::erase(iterator first, iterator last)
135 {
136 while (first != last) erase(first++);
137 }
139 /** Remove all points from the selection, making it empty. */
140 void ControlPointSelection::clear()
141 {
142 for (iterator i = begin(); i != end(); )
143 erase(i++);
144 }
146 /** Select all points that this selection can contain. */
147 void ControlPointSelection::selectAll()
148 {
149 for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
150 insert(*i);
151 }
152 }
153 /** Select all points inside the given rectangle (in desktop coordinates). */
154 void ControlPointSelection::selectArea(Geom::Rect const &r)
155 {
156 for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
157 if (r.contains(**i))
158 insert(*i);
159 }
160 }
161 /** Unselect all selected points and select all unselected points. */
162 void ControlPointSelection::invertSelection()
163 {
164 for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
165 if ((*i)->selected()) erase(*i);
166 else insert(*i);
167 }
168 }
169 void ControlPointSelection::spatialGrow(SelectableControlPoint *origin, int dir)
170 {
171 bool grow = (dir > 0);
172 Geom::Point p = origin->position();
173 double best_dist = grow ? HUGE_VAL : 0;
174 SelectableControlPoint *match = NULL;
175 for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
176 bool selected = (*i)->selected();
177 if (grow && !selected) {
178 double dist = Geom::distance((*i)->position(), p);
179 if (dist < best_dist) {
180 best_dist = dist;
181 match = *i;
182 }
183 }
184 if (!grow && selected) {
185 double dist = Geom::distance((*i)->position(), p);
186 // use >= to also deselect the origin node when it's the last one selected
187 if (dist >= best_dist) {
188 best_dist = dist;
189 match = *i;
190 }
191 }
192 }
193 if (match) {
194 if (grow) insert(match);
195 else erase(match);
196 }
197 }
199 /** Transform all selected control points by the given affine transformation. */
200 void ControlPointSelection::transform(Geom::Matrix const &m)
201 {
202 for (iterator i = _points.begin(); i != _points.end(); ++i) {
203 SelectableControlPoint *cur = i->first;
204 cur->transform(m);
205 }
206 // TODO preserving the rotation radius needs some rethinking...
207 if (_rot_radius) (*_rot_radius) *= m.descrim();
208 signal_update.emit();
209 }
211 /** Align control points on the specified axis. */
212 void ControlPointSelection::align(Geom::Dim2 axis)
213 {
214 if (empty()) return;
215 Geom::Dim2 d = static_cast<Geom::Dim2>((axis + 1) % 2);
217 Geom::OptInterval bound;
218 for (iterator i = _points.begin(); i != _points.end(); ++i) {
219 bound.unionWith(Geom::OptInterval(i->first->position()[d]));
220 }
222 double new_coord = bound->middle();
223 for (iterator i = _points.begin(); i != _points.end(); ++i) {
224 Geom::Point pos = i->first->position();
225 pos[d] = new_coord;
226 i->first->move(pos);
227 }
228 }
230 /** Equdistantly distribute control points by moving them in the specified dimension. */
231 void ControlPointSelection::distribute(Geom::Dim2 d)
232 {
233 if (empty()) return;
235 // this needs to be a multimap, otherwise it will fail when some points have the same coord
236 typedef std::multimap<double, SelectableControlPoint*> SortMap;
238 SortMap sm;
239 Geom::OptInterval bound;
240 // first we insert all points into a multimap keyed by the aligned coord to sort them
241 // simultaneously we compute the extent of selection
242 for (iterator i = _points.begin(); i != _points.end(); ++i) {
243 Geom::Point pos = i->first->position();
244 sm.insert(std::make_pair(pos[d], i->first));
245 bound.unionWith(Geom::OptInterval(pos[d]));
246 }
248 // now we iterate over the multimap and set aligned positions.
249 double step = size() == 1 ? 0 : bound->extent() / (size() - 1);
250 double start = bound->min();
251 unsigned num = 0;
252 for (SortMap::iterator i = sm.begin(); i != sm.end(); ++i, ++num) {
253 Geom::Point pos = i->second->position();
254 pos[d] = start + num * step;
255 i->second->move(pos);
256 }
257 }
259 /** Get the bounds of the selection.
260 * @return Smallest rectangle containing the positions of all selected points,
261 * or nothing if the selection is empty */
262 Geom::OptRect ControlPointSelection::pointwiseBounds()
263 {
264 Geom::OptRect bound;
265 for (iterator i = _points.begin(); i != _points.end(); ++i) {
266 SelectableControlPoint *cur = i->first;
267 Geom::Point p = cur->position();
268 if (!bound) {
269 bound = Geom::Rect(p, p);
270 } else {
271 bound->expandTo(p);
272 }
273 }
274 return bound;
275 }
277 Geom::OptRect ControlPointSelection::bounds()
278 {
279 Geom::OptRect bound;
280 for (iterator i = _points.begin(); i != _points.end(); ++i) {
281 SelectableControlPoint *cur = i->first;
282 Geom::OptRect r = cur->bounds();
283 bound.unionWith(r);
284 }
285 return bound;
286 }
288 void ControlPointSelection::showTransformHandles(bool v, bool one_node)
289 {
290 _one_node_handles = one_node;
291 _handles_visible = v;
292 _updateTransformHandles(false);
293 }
295 void ControlPointSelection::hideTransformHandles()
296 {
297 _handles->setVisible(false);
298 }
299 void ControlPointSelection::restoreTransformHandles()
300 {
301 _updateTransformHandles(true);
302 }
304 void ControlPointSelection::toggleTransformHandlesMode()
305 {
306 if (_handles->mode() == TransformHandleSet::MODE_SCALE) {
307 _handles->setMode(TransformHandleSet::MODE_ROTATE_SKEW);
308 if (size() == 1) _handles->rotationCenter().setVisible(false);
309 } else {
310 _handles->setMode(TransformHandleSet::MODE_SCALE);
311 }
312 }
314 void ControlPointSelection::_pointGrabbed()
315 {
316 hideTransformHandles();
317 _dragging = true;
318 }
320 void ControlPointSelection::_pointDragged(Geom::Point const &old_pos, Geom::Point &new_pos,
321 GdkEventMotion */*event*/)
322 {
323 Geom::Point delta = new_pos - old_pos;
324 for (iterator i = _points.begin(); i != _points.end(); ++i) {
325 SelectableControlPoint *cur = i->first;
326 cur->move(cur->position() + delta);
327 }
328 _handles->rotationCenter().move(_handles->rotationCenter().position() + delta);
329 signal_update.emit();
330 }
332 void ControlPointSelection::_pointUngrabbed()
333 {
334 _dragging = false;
335 _grabbed_point = NULL;
336 restoreTransformHandles();
337 signal_commit.emit(COMMIT_MOUSE_MOVE);
338 }
340 bool ControlPointSelection::_pointClicked(SelectableControlPoint *p, GdkEventButton *event)
341 {
342 // clicking a selected node should toggle the transform handles between rotate and scale mode,
343 // if they are visible
344 if (held_shift(*event)) return false;
345 if (_handles_visible && p->selected()) {
346 toggleTransformHandlesMode();
347 return true;
348 }
349 return false;
350 }
352 void ControlPointSelection::_updateTransformHandles(bool preserve_center)
353 {
354 if (_dragging) return;
356 if (_handles_visible && size() > 1) {
357 Geom::OptRect b = pointwiseBounds();
358 _handles->setBounds(*b, preserve_center);
359 _handles->setVisible(true);
360 } else if (_one_node_handles && size() == 1) { // only one control point in selection
361 SelectableControlPoint *p = begin()->first;
362 _handles->setBounds(p->bounds());
363 _handles->rotationCenter().move(p->position());
364 _handles->rotationCenter().setVisible(false);
365 _handles->setVisible(true);
366 } else {
367 _handles->setVisible(false);
368 }
369 }
371 /** Moves the selected points along the supplied unit vector according to
372 * the modifier state of the supplied event. */
373 bool ControlPointSelection::_keyboardMove(GdkEventKey const &event, Geom::Point const &dir)
374 {
375 if (held_control(event)) return false;
376 unsigned num = 1 + consume_same_key_events(shortcut_key(event), 0);
378 Geom::Point delta = dir * num;
379 if (held_shift(event)) delta *= 10;
380 if (held_alt(event)) {
381 delta /= _desktop->current_zoom();
382 } else {
383 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
384 double nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000);
385 delta *= nudge;
386 }
388 transform(Geom::Translate(delta));
389 if (fabs(dir[Geom::X]) > 0) {
390 signal_commit.emit(COMMIT_KEYBOARD_MOVE_X);
391 } else {
392 signal_commit.emit(COMMIT_KEYBOARD_MOVE_Y);
393 }
394 return true;
395 }
397 /** Rotates the selected points in the given direction according to the modifier state
398 * from the supplied event.
399 * @param event Key event to take modifier state from
400 * @param dir Direction of rotation (math convention: 1 = counterclockwise, -1 = clockwise)
401 */
402 bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir)
403 {
404 if (empty()) return false;
406 Geom::Point rc = _handles->rotationCenter();
407 if (!_rot_radius) {
408 Geom::Rect b = *(size() == 1 ? bounds() : pointwiseBounds());
409 double maxlen = 0;
410 for (unsigned i = 0; i < 4; ++i) {
411 double len = (b.corner(i) - rc).length();
412 if (len > maxlen) maxlen = len;
413 }
414 _rot_radius = maxlen;
415 }
417 double angle;
418 if (held_alt(event)) {
419 // Rotate by "one pixel". We interpret this as rotating by an angle that causes
420 // the topmost point of a circle circumscribed about the selection's bounding box
421 // to move on an arc 1 screen pixel long.
422 angle = atan2(1.0 / _desktop->current_zoom(), *_rot_radius) * dir;
423 } else {
424 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
425 int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
426 angle = M_PI * dir / snaps;
427 }
429 // translate to origin, rotate, translate back to original position
430 Geom::Matrix m = Geom::Translate(-rc)
431 * Geom::Rotate(angle) * Geom::Translate(rc);
432 transform(m);
433 signal_commit.emit(COMMIT_KEYBOARD_ROTATE);
434 return true;
435 }
438 bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir)
439 {
440 if (empty()) return false;
442 // TODO should the saved rotation center or the current center be used?
443 Geom::Rect bound = (size() == 1 ? *bounds() : *pointwiseBounds());
444 double maxext = bound.maxExtent();
445 if (Geom::are_near(maxext, 0)) return false;
446 Geom::Point center = _handles->rotationCenter().position();
448 double length_change;
449 if (held_alt(event)) {
450 // Scale by "one pixel". It means shrink/grow 1px for the larger dimension
451 // of the bounding box.
452 length_change = 1.0 / _desktop->current_zoom() * dir;
453 } else {
454 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
455 length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000);
456 length_change *= dir;
457 }
458 double scale = (maxext + length_change) / maxext;
460 Geom::Matrix m = Geom::Translate(-center) * Geom::Scale(scale) * Geom::Translate(center);
461 transform(m);
462 signal_commit.emit(COMMIT_KEYBOARD_SCALE_UNIFORM);
463 return true;
464 }
466 bool ControlPointSelection::_keyboardFlip(Geom::Dim2 d)
467 {
468 if (empty()) return false;
470 Geom::Scale scale_transform(1, 1);
471 if (d == Geom::X) {
472 scale_transform = Geom::Scale(-1, 1);
473 } else {
474 scale_transform = Geom::Scale(1, -1);
475 }
477 SelectableControlPoint *scp =
478 dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
479 Geom::Point center = scp ? scp->position() : _handles->rotationCenter().position();
481 Geom::Matrix m = Geom::Translate(-center) * scale_transform * Geom::Translate(center);
482 transform(m);
483 signal_commit.emit(d == Geom::X ? COMMIT_FLIP_X : COMMIT_FLIP_Y);
484 return true;
485 }
487 void ControlPointSelection::_commitTransform(CommitEvent ce)
488 {
489 _updateTransformHandles(true);
490 signal_commit.emit(ce);
491 }
493 bool ControlPointSelection::event(GdkEvent *event)
494 {
495 // implement generic event handling that should apply for all control point selections here;
496 // for example, keyboard moves and transformations. This way this functionality doesn't need
497 // to be duplicated in many places
498 // Later split out so that it can be reused in object selection
500 switch (event->type) {
501 case GDK_KEY_PRESS:
502 // do not handle key events if the selection is empty
503 if (empty()) break;
505 switch(shortcut_key(event->key)) {
506 // moves
507 case GDK_Up:
508 case GDK_KP_Up:
509 case GDK_KP_8:
510 return _keyboardMove(event->key, Geom::Point(0, 1));
511 case GDK_Down:
512 case GDK_KP_Down:
513 case GDK_KP_2:
514 return _keyboardMove(event->key, Geom::Point(0, -1));
515 case GDK_Right:
516 case GDK_KP_Right:
517 case GDK_KP_6:
518 return _keyboardMove(event->key, Geom::Point(1, 0));
519 case GDK_Left:
520 case GDK_KP_Left:
521 case GDK_KP_4:
522 return _keyboardMove(event->key, Geom::Point(-1, 0));
524 // rotates
525 case GDK_bracketleft:
526 return _keyboardRotate(event->key, 1);
527 case GDK_bracketright:
528 return _keyboardRotate(event->key, -1);
530 // scaling
531 case GDK_less:
532 case GDK_comma:
533 return _keyboardScale(event->key, -1);
534 case GDK_greater:
535 case GDK_period:
536 return _keyboardScale(event->key, 1);
538 // TODO: skewing
540 // flipping
541 // NOTE: H is horizontal flip, while Shift+H switches transform handle mode!
542 case GDK_h:
543 case GDK_H:
544 if (held_shift(event->key)) {
545 toggleTransformHandlesMode();
546 return true;
547 }
548 // any modifiers except shift should cause no action
549 if (held_any_modifiers(event->key)) break;
550 return _keyboardFlip(Geom::X);
551 case GDK_v:
552 case GDK_V:
553 if (held_any_modifiers(event->key)) break;
554 return _keyboardFlip(Geom::Y);
555 default: break;
556 }
557 break;
558 default: break;
559 }
560 return false;
561 }
563 } // namespace UI
564 } // namespace Inkscape
566 /*
567 Local Variables:
568 mode:c++
569 c-file-style:"stroustrup"
570 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
571 indent-tabs-mode:nil
572 fill-column:99
573 End:
574 */
575 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :