Code

* Merge from trunk
[inkscape.git] / src / ui / tool / control-point-selection.cpp
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);
111 /** Remove a point from the selection. */
112 void ControlPointSelection::erase(iterator pos)
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);
124 ControlPointSelection::size_type ControlPointSelection::erase(const key_type &k)
126     iterator pos = _points.find(k);
127     if (pos == _points.end()) return 0;
128     erase(pos);
129     return 1;
131 void ControlPointSelection::erase(iterator first, iterator last)
133     while (first != last) erase(first++);
136 /** Remove all points from the selection, making it empty. */
137 void ControlPointSelection::clear()
139     for (iterator i = begin(); i != end(); )
140         erase(i++);
143 /** Select all points that this selection can contain. */
144 void ControlPointSelection::selectAll()
146     for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
147         insert(*i);
148     }
150 /** Select all points inside the given rectangle (in desktop coordinates). */
151 void ControlPointSelection::selectArea(Geom::Rect const &r)
153     for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
154         if (r.contains(**i))
155             insert(*i);
156     }
158 /** Unselect all selected points and select all unselected points. */
159 void ControlPointSelection::invertSelection()
161     for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
162         if ((*i)->selected()) erase(*i);
163         else insert(*i);
164     }
166 void ControlPointSelection::spatialGrow(SelectableControlPoint *origin, int dir)
168     bool grow = (dir > 0);
169     Geom::Point p = origin->position();
170     double best_dist = grow ? HUGE_VAL : 0;
171     SelectableControlPoint *match = NULL;
172     for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
173         bool selected = (*i)->selected();
174         if (grow && !selected) {
175             double dist = Geom::distance((*i)->position(), p);
176             if (dist < best_dist) {
177                 best_dist = dist;
178                 match = *i;
179             }
180         }
181         if (!grow && selected) {
182             double dist = Geom::distance((*i)->position(), p);
183             // use >= to also deselect the origin node when it's the last one selected
184             if (dist >= best_dist) {
185                 best_dist = dist;
186                 match = *i;
187             }
188         }
189     }
190     if (match) {
191         if (grow) insert(match);
192         else erase(match);
193     }
196 /** Transform all selected control points by the given affine transformation. */
197 void ControlPointSelection::transform(Geom::Matrix const &m)
199     for (iterator i = _points.begin(); i != _points.end(); ++i) {
200         SelectableControlPoint *cur = i->first;
201         cur->transform(m);
202     }
203     // TODO preserving the rotation radius needs some rethinking...
204     if (_rot_radius) (*_rot_radius) *= m.descrim();
205     signal_update.emit();
208 /** Align control points on the specified axis. */
209 void ControlPointSelection::align(Geom::Dim2 axis)
211     if (empty()) return;
212     Geom::Dim2 d = static_cast<Geom::Dim2>((axis + 1) % 2);
214     Geom::OptInterval bound;
215     for (iterator i = _points.begin(); i != _points.end(); ++i) {
216         bound.unionWith(Geom::OptInterval(i->first->position()[d]));
217     }
219     double new_coord = bound->middle();
220     for (iterator i = _points.begin(); i != _points.end(); ++i) {
221         Geom::Point pos = i->first->position();
222         pos[d] = new_coord;
223         i->first->move(pos);
224     }
227 /** Equdistantly distribute control points by moving them in the specified dimension. */
228 void ControlPointSelection::distribute(Geom::Dim2 d)
230     if (empty()) return;
232     // this needs to be a multimap, otherwise it will fail when some points have the same coord
233     typedef std::multimap<double, SelectableControlPoint*> SortMap;
235     SortMap sm;
236     Geom::OptInterval bound;
237     // first we insert all points into a multimap keyed by the aligned coord to sort them
238     // simultaneously we compute the extent of selection
239     for (iterator i = _points.begin(); i != _points.end(); ++i) {
240         Geom::Point pos = i->first->position();
241         sm.insert(std::make_pair(pos[d], i->first));
242         bound.unionWith(Geom::OptInterval(pos[d]));
243     }
245     // now we iterate over the multimap and set aligned positions.
246     double step = size() == 1 ? 0 : bound->extent() / (size() - 1);
247     double start = bound->min();
248     unsigned num = 0;
249     for (SortMap::iterator i = sm.begin(); i != sm.end(); ++i, ++num) {
250         Geom::Point pos = i->second->position();
251         pos[d] = start + num * step;
252         i->second->move(pos);
253     }
256 /** Get the bounds of the selection.
257  * @return Smallest rectangle containing the positions of all selected points,
258  *         or nothing if the selection is empty */
259 Geom::OptRect ControlPointSelection::pointwiseBounds()
261     Geom::OptRect bound;
262     for (iterator i = _points.begin(); i != _points.end(); ++i) {
263         SelectableControlPoint *cur = i->first;
264         Geom::Point p = cur->position();
265         if (!bound) {
266             bound = Geom::Rect(p, p);
267         } else {
268             bound->expandTo(p);
269         }
270     }
271     return bound;
274 Geom::OptRect ControlPointSelection::bounds()
276     Geom::OptRect bound;
277     for (iterator i = _points.begin(); i != _points.end(); ++i) {
278         SelectableControlPoint *cur = i->first;
279         Geom::OptRect r = cur->bounds();
280         bound.unionWith(r);
281     }
282     return bound;
285 void ControlPointSelection::showTransformHandles(bool v, bool one_node)
287     _one_node_handles = one_node;
288     _handles_visible = v;
289     _updateTransformHandles(false);
292 void ControlPointSelection::hideTransformHandles()
294     _handles->setVisible(false);
296 void ControlPointSelection::restoreTransformHandles()
298     _updateTransformHandles(true);
301 void ControlPointSelection::_selectionGrabbed(SelectableControlPoint *p, GdkEventMotion *event)
303     hideTransformHandles();
304     _dragging = true;
305     if (held_alt(*event) && _sculpt_enabled) {
306         _sculpting = true;
307         _grabbed_point = p;
308     } else {
309         _sculpting = false;
310     }
313 void ControlPointSelection::_selectionDragged(Geom::Point const &old_pos, Geom::Point &new_pos,
314     GdkEventMotion *event)
316     Geom::Point delta = new_pos - old_pos;
317     /*if (_sculpting) {
318         // for now we only support the default sculpting profile (bell)
319         // others will be added when preferences will be able to store enumerated values
320         double pressure, alpha;
321         if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &pressure)) {
322             pressure = CLAMP(pressure, 0.2, 0.8);
323         } else {
324             pressure = 0.5;
325         }
327         alpha = 1 - 2 * fabs(pressure - 0.5);
328         if (pressure > 0.5) alpha = 1/alpha;
330         for (iterator i = _points.begin(); i != _points.end(); ++i) {
331             SelectableControlPoint *cur = i->first;
332             double dist = Geom::distance(cur->position(), _grabbed_point->position());
333             
334             cur->move(cur->position() + delta);
335         }
336     } else*/ {
337         for (iterator i = _points.begin(); i != _points.end(); ++i) {
338             SelectableControlPoint *cur = i->first;
339             cur->move(cur->position() + delta);
340         }
341         _handles->rotationCenter().move(_handles->rotationCenter().position() + delta);
342     }
343     signal_update.emit();
346 void ControlPointSelection::_selectionUngrabbed()
348     _dragging = false;
349     _grabbed_point = NULL;
350     restoreTransformHandles();
351     signal_commit.emit(COMMIT_MOUSE_MOVE);
354 void ControlPointSelection::_updateTransformHandles(bool preserve_center)
356     if (_dragging) return;
358     if (_handles_visible && size() > 1) {
359         Geom::OptRect b = pointwiseBounds();
360         _handles->setBounds(*b, preserve_center);
361         _handles->setVisible(true);
362     } else if (_one_node_handles && size() == 1) { // only one control point in selection
363         SelectableControlPoint *p = begin()->first;
364         _handles->setBounds(p->bounds());
365         _handles->rotationCenter().move(p->position());
366         _handles->rotationCenter().setVisible(false);
367         _handles->setVisible(true);
368     } else {
369         _handles->setVisible(false);
370     }
373 /** Moves the selected points along the supplied unit vector according to
374  * the modifier state of the supplied event. */
375 bool ControlPointSelection::_keyboardMove(GdkEventKey const &event, Geom::Point const &dir)
377     if (held_control(event)) return false;
378     unsigned num = 1 + consume_same_key_events(shortcut_key(event), 0);
380     Geom::Point delta = dir * num; 
381     if (held_shift(event)) delta *= 10;
382     if (held_alt(event)) {
383         delta /= _desktop->current_zoom();
384     } else {
385         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
386         double nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000);
387         delta *= nudge;
388     }
390     transform(Geom::Translate(delta));
391     if (fabs(dir[Geom::X]) > 0) {
392         signal_commit.emit(COMMIT_KEYBOARD_MOVE_X);
393     } else {
394         signal_commit.emit(COMMIT_KEYBOARD_MOVE_Y);
395     }
396     return true;
399 /** Rotates the selected points in the given direction according to the modifier state
400  * from the supplied event.
401  * @param event Key event to take modifier state from
402  * @param dir   Direction of rotation (math convention: 1 = counterclockwise, -1 = clockwise)
403  */
404 bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir)
406     if (empty()) return false;
408     Geom::Point rc = _handles->rotationCenter();
409     if (!_rot_radius) {
410         Geom::Rect b = *(size() == 1 ? bounds() : pointwiseBounds());
411         double maxlen = 0;
412         for (unsigned i = 0; i < 4; ++i) {
413             double len = (b.corner(i) - rc).length();
414             if (len > maxlen) maxlen = len;
415         }
416         _rot_radius = maxlen;
417     }
419     double angle;
420     if (held_alt(event)) {
421         // Rotate by "one pixel". We interpret this as rotating by an angle that causes
422         // the topmost point of a circle circumscribed about the selection's bounding box
423         // to move on an arc 1 screen pixel long.
424         angle = atan2(1.0 / _desktop->current_zoom(), *_rot_radius) * dir;
425     } else {
426         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
427         int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
428         angle = M_PI * dir / snaps;
429     }
431     // translate to origin, rotate, translate back to original position
432     Geom::Matrix m = Geom::Translate(-rc)
433         * Geom::Rotate(angle) * Geom::Translate(rc);
434     transform(m);
435     signal_commit.emit(COMMIT_KEYBOARD_ROTATE);
436     return true;
440 bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir)
442     if (empty()) return false;
444     // TODO should the saved rotation center or the current center be used?
445     Geom::Rect bound = (size() == 1 ? *bounds() : *pointwiseBounds());
446     double maxext = bound.maxExtent();
447     if (Geom::are_near(maxext, 0)) return false;
448     Geom::Point center = _handles->rotationCenter().position();
450     double length_change;
451     if (held_alt(event)) {
452         // Scale by "one pixel". It means shrink/grow 1px for the larger dimension
453         // of the bounding box.
454         length_change = 1.0 / _desktop->current_zoom() * dir;
455     } else {
456         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
457         length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000);
458         length_change *= dir;
459     }
460     double scale = (maxext + length_change) / maxext;
461     
462     Geom::Matrix m = Geom::Translate(-center) * Geom::Scale(scale) * Geom::Translate(center);
463     transform(m);
464     signal_commit.emit(COMMIT_KEYBOARD_SCALE_UNIFORM);
465     return true;
468 bool ControlPointSelection::_keyboardFlip(Geom::Dim2 d)
470     if (empty()) return false;
472     Geom::Scale scale_transform(1, 1);
473     if (d == Geom::X) {
474         scale_transform = Geom::Scale(-1, 1);
475     } else {
476         scale_transform = Geom::Scale(1, -1);
477     }
479     SelectableControlPoint *scp =
480         dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
481     Geom::Point center = scp ? scp->position() : _handles->rotationCenter().position();
483     Geom::Matrix m = Geom::Translate(-center) * scale_transform * Geom::Translate(center);
484     transform(m);
485     signal_commit.emit(d == Geom::X ? COMMIT_FLIP_X : COMMIT_FLIP_Y);
486     return true;
489 void ControlPointSelection::_commitTransform(CommitEvent ce)
491     _updateTransformHandles(true);
492     signal_commit.emit(ce);
495 bool ControlPointSelection::event(GdkEvent *event)
497     // implement generic event handling that should apply for all control point selections here;
498     // for example, keyboard moves and transformations. This way this functionality doesn't need
499     // to be duplicated in many places
500     // Later split out so that it can be reused in object selection
502     switch (event->type) {
503     case GDK_KEY_PRESS:
504         // do not handle key events if the selection is empty
505         if (empty()) break;
507         switch(shortcut_key(event->key)) {
508         // moves
509         case GDK_Up:
510         case GDK_KP_Up:
511         case GDK_KP_8:
512             return _keyboardMove(event->key, Geom::Point(0, 1));
513         case GDK_Down:
514         case GDK_KP_Down:
515         case GDK_KP_2:
516             return _keyboardMove(event->key, Geom::Point(0, -1));
517         case GDK_Right:
518         case GDK_KP_Right:
519         case GDK_KP_6:
520             return _keyboardMove(event->key, Geom::Point(1, 0));
521         case GDK_Left:
522         case GDK_KP_Left:
523         case GDK_KP_4:
524             return _keyboardMove(event->key, Geom::Point(-1, 0));
526         // rotates
527         case GDK_bracketleft:
528             return _keyboardRotate(event->key, 1);
529         case GDK_bracketright:
530             return _keyboardRotate(event->key, -1);
532         // scaling
533         case GDK_less:
534         case GDK_comma:
535             return _keyboardScale(event->key, -1);
536         case GDK_greater:
537         case GDK_period:
538             return _keyboardScale(event->key, 1);
540         // TODO: skewing
542         // flipping
543         // NOTE: H is horizontal flip, while Shift+H switches transform handle mode!
544         case GDK_h:
545         case GDK_H:
546             if (held_shift(event->key)) {
547                 // TODO make a method for mode switching
548                 if (_handles->mode() == TransformHandleSet::MODE_SCALE) {
549                     _handles->setMode(TransformHandleSet::MODE_ROTATE_SKEW);
550                     if (size() == 1) _handles->rotationCenter().setVisible(false);
551                 } else {
552                     _handles->setMode(TransformHandleSet::MODE_SCALE);
553                 }
554                 return true;
555             }
556             // any modifiers except shift should cause no action
557             if (held_any_modifiers(event->key)) break;
558             return _keyboardFlip(Geom::X);
559         case GDK_v:
560         case GDK_V:
561             if (held_any_modifiers(event->key)) break;
562             return _keyboardFlip(Geom::Y);
563         default: break;
564         }
565         break;
566     default: break;
567     }
568     return false;
571 } // namespace UI
572 } // namespace Inkscape
574 /*
575   Local Variables:
576   mode:c++
577   c-file-style:"stroustrup"
578   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
579   indent-tabs-mode:nil
580   fill-column:99
581   End:
582 */
583 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :