Code

Revert the inverted coordinate system fix. 3D Boxes and guides
[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 <boost/none.hpp>
12 #include <2geom/transforms.h>
13 #include "desktop.h"
14 #include "preferences.h"
15 #include "ui/tool/control-point-selection.h"
16 #include "ui/tool/event-utils.h"
17 #include "ui/tool/selectable-control-point.h"
18 #include "ui/tool/transform-handle-set.h"
20 namespace Inkscape {
21 namespace UI {
23 /**
24  * @class ControlPointSelection
25  * @brief Group of selected control points.
26  *
27  * Some operations can be performed on all selected points regardless of their type, therefore
28  * this class is also a Manipulator. It handles the transformations of points using
29  * the keyboard.
30  *
31  * The exposed interface is similar to that of an STL set. Internally, a hash map is used.
32  * @todo Correct iterators (that don't expose the connection list)
33  */
35 /** @var ControlPointSelection::signal_update
36  * Fires when the display needs to be updated to reflect changes.
37  */
38 /** @var ControlPointSelection::signal_point_changed
39  * Fires when a control point is added to or removed from the selection.
40  * The first param contains a pointer to the control point that changed sel. state. 
41  * The second says whether the point is currently selected.
42  */
43 /** @var ControlPointSelection::signal_commit
44  * Fires when a change that needs to be committed to XML happens.
45  */
47 ControlPointSelection::ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_group)
48     : Manipulator(d)
49     , _handles(new TransformHandleSet(d, th_group))
50     , _dragging(false)
51     , _handles_visible(true)
52     , _one_node_handles(false)
53 {
54     signal_update.connect( sigc::bind(
55         sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
56         true));
57     ControlPoint::signal_mouseover_change.connect(
58         sigc::hide(
59             sigc::mem_fun(*this, &ControlPointSelection::_mouseoverChanged)));
60     _handles->signal_transform.connect(
61         sigc::mem_fun(*this, &ControlPointSelection::transform));
62     _handles->signal_commit.connect(
63         sigc::mem_fun(*this, &ControlPointSelection::_commitHandlesTransform));
64 }
66 ControlPointSelection::~ControlPointSelection()
67 {
68     clear();
69     delete _handles;
70 }
72 /** Add a control point to the selection. */
73 std::pair<ControlPointSelection::iterator, bool> ControlPointSelection::insert(const value_type &x)
74 {
75     iterator found = _points.find(x);
76     if (found != _points.end()) {
77         return std::pair<iterator, bool>(found, false);
78     }
80     found = _points.insert(x).first;
82     x->updateState();
83     _pointChanged(x, true);
85     return std::pair<iterator, bool>(found, true);
86 }
88 /** Remove a point from the selection. */
89 void ControlPointSelection::erase(iterator pos)
90 {
91     SelectableControlPoint *erased = *pos;
92     _points.erase(pos);
93     erased->updateState();
94     _pointChanged(erased, false);
95 }
96 ControlPointSelection::size_type ControlPointSelection::erase(const key_type &k)
97 {
98     iterator pos = _points.find(k);
99     if (pos == _points.end()) return 0;
100     erase(pos);
101     return 1;
103 void ControlPointSelection::erase(iterator first, iterator last)
105     while (first != last) erase(first++);
108 /** Remove all points from the selection, making it empty. */
109 void ControlPointSelection::clear()
111     for (iterator i = begin(); i != end(); )
112         erase(i++);
115 /** Select all points that this selection can contain. */
116 void ControlPointSelection::selectAll()
118     for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
119         insert(*i);
120     }
122 /** Select all points inside the given rectangle (in desktop coordinates). */
123 void ControlPointSelection::selectArea(Geom::Rect const &r)
125     for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
126         if (r.contains(**i))
127             insert(*i);
128     }
130 /** Unselect all selected points and select all unselected points. */
131 void ControlPointSelection::invertSelection()
133     for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
134         if ((*i)->selected()) erase(*i);
135         else insert(*i);
136     }
138 void ControlPointSelection::spatialGrow(SelectableControlPoint *origin, int dir)
140     bool grow = (dir > 0);
141     Geom::Point p = origin->position();
142     double best_dist = grow ? HUGE_VAL : 0;
143     SelectableControlPoint *match = NULL;
144     for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
145         bool selected = (*i)->selected();
146         if (grow && !selected) {
147             double dist = Geom::distance((*i)->position(), p);
148             if (dist < best_dist) {
149                 best_dist = dist;
150                 match = *i;
151             }
152         }
153         if (!grow && selected) {
154             double dist = Geom::distance((*i)->position(), p);
155             // use >= to also deselect the origin node when it's the last one selected
156             if (dist >= best_dist) {
157                 best_dist = dist;
158                 match = *i;
159             }
160         }
161     }
162     if (match) {
163         if (grow) insert(match);
164         else erase(match);
165     }
168 /** Transform all selected control points by the given affine transformation. */
169 void ControlPointSelection::transform(Geom::Matrix const &m)
171     for (iterator i = _points.begin(); i != _points.end(); ++i) {
172         SelectableControlPoint *cur = *i;
173         cur->transform(m);
174     }
175     _updateBounds();
176     // TODO preserving the rotation radius needs some rethinking...
177     if (_rot_radius) (*_rot_radius) *= m.descrim();
178     if (_mouseover_rot_radius) (*_mouseover_rot_radius) *= m.descrim();
179     signal_update.emit();
182 /** Align control points on the specified axis. */
183 void ControlPointSelection::align(Geom::Dim2 axis)
185     if (empty()) return;
186     Geom::Dim2 d = static_cast<Geom::Dim2>((axis + 1) % 2);
188     Geom::OptInterval bound;
189     for (iterator i = _points.begin(); i != _points.end(); ++i) {
190         bound.unionWith(Geom::OptInterval((*i)->position()[d]));
191     }
193     double new_coord = bound->middle();
194     for (iterator i = _points.begin(); i != _points.end(); ++i) {
195         Geom::Point pos = (*i)->position();
196         pos[d] = new_coord;
197         (*i)->move(pos);
198     }
201 /** Equdistantly distribute control points by moving them in the specified dimension. */
202 void ControlPointSelection::distribute(Geom::Dim2 d)
204     if (empty()) return;
206     // this needs to be a multimap, otherwise it will fail when some points have the same coord
207     typedef std::multimap<double, SelectableControlPoint*> SortMap;
209     SortMap sm;
210     Geom::OptInterval bound;
211     // first we insert all points into a multimap keyed by the aligned coord to sort them
212     // simultaneously we compute the extent of selection
213     for (iterator i = _points.begin(); i != _points.end(); ++i) {
214         Geom::Point pos = (*i)->position();
215         sm.insert(std::make_pair(pos[d], (*i)));
216         bound.unionWith(Geom::OptInterval(pos[d]));
217     }
219     // now we iterate over the multimap and set aligned positions.
220     double step = size() == 1 ? 0 : bound->extent() / (size() - 1);
221     double start = bound->min();
222     unsigned num = 0;
223     for (SortMap::iterator i = sm.begin(); i != sm.end(); ++i, ++num) {
224         Geom::Point pos = i->second->position();
225         pos[d] = start + num * step;
226         i->second->move(pos);
227     }
230 /** Get the bounds of the selection.
231  * @return Smallest rectangle containing the positions of all selected points,
232  *         or nothing if the selection is empty */
233 Geom::OptRect ControlPointSelection::pointwiseBounds()
235     return _bounds;
238 Geom::OptRect ControlPointSelection::bounds()
240     return size() == 1 ? (*_points.begin())->bounds() : _bounds;
243 void ControlPointSelection::showTransformHandles(bool v, bool one_node)
245     _one_node_handles = one_node;
246     _handles_visible = v;
247     _updateTransformHandles(false);
250 void ControlPointSelection::hideTransformHandles()
252     _handles->setVisible(false);
254 void ControlPointSelection::restoreTransformHandles()
256     _updateTransformHandles(true);
259 void ControlPointSelection::toggleTransformHandlesMode()
261     if (_handles->mode() == TransformHandleSet::MODE_SCALE) {
262         _handles->setMode(TransformHandleSet::MODE_ROTATE_SKEW);
263         if (size() == 1) _handles->rotationCenter().setVisible(false);
264     } else {
265         _handles->setMode(TransformHandleSet::MODE_SCALE);
266     }
269 void ControlPointSelection::_pointGrabbed(SelectableControlPoint *point)
271     hideTransformHandles();
272     _dragging = true;
273     _grabbed_point = point;
274     _farthest_point = point;
275     double maxdist = 0;
276     for (iterator i = _points.begin(); i != _points.end(); ++i) {
277         _original_positions.insert(std::make_pair(*i, (*i)->position()));
278         double dist = Geom::distance(*_grabbed_point, **i);
279         if (dist > maxdist) {
280             maxdist = dist;
281             _farthest_point = *i;
282         }
283     }
286 void ControlPointSelection::_pointDragged(Geom::Point &new_pos, GdkEventMotion *event)
288     Geom::Point abs_delta = new_pos - _original_positions[_grabbed_point];
289     double fdist = Geom::distance(_original_positions[_grabbed_point], _original_positions[_farthest_point]);
290     if (held_alt(*event) && fdist > 0) {
291         // sculpting
292         for (iterator i = _points.begin(); i != _points.end(); ++i) {
293             SelectableControlPoint *cur = (*i);
294             double dist = Geom::distance(_original_positions[cur], _original_positions[_grabbed_point]);
295             double deltafrac = 0.5 + 0.5 * cos(M_PI * dist/fdist);
296             cur->move(_original_positions[cur] + abs_delta * deltafrac);
297         }
298     } else {
299         Geom::Point delta = new_pos - _grabbed_point->position();
300         for (iterator i = _points.begin(); i != _points.end(); ++i) {
301             SelectableControlPoint *cur = (*i);
302             cur->move(_original_positions[cur] + abs_delta);
303         }
304         _handles->rotationCenter().move(_handles->rotationCenter().position() + delta);
305     }
306     signal_update.emit();
309 void ControlPointSelection::_pointUngrabbed()
311     _original_positions.clear();
312     _dragging = false;
313     _grabbed_point = _farthest_point = NULL;
314     _updateBounds();
315     restoreTransformHandles();
316     signal_commit.emit(COMMIT_MOUSE_MOVE);
319 bool ControlPointSelection::_pointClicked(SelectableControlPoint *p, GdkEventButton *event)
321     // clicking a selected node should toggle the transform handles between rotate and scale mode,
322     // if they are visible
323     if (held_no_modifiers(*event) && _handles_visible && p->selected()) {
324         toggleTransformHandlesMode();
325         return true;
326     }
327     return false;
330 void ControlPointSelection::_pointChanged(SelectableControlPoint *p, bool selected)
332     _updateBounds();
333     _updateTransformHandles(false);
334     if (_bounds)
335         _handles->rotationCenter().move(_bounds->midpoint());
337     signal_point_changed.emit(p, selected);
340 void ControlPointSelection::_mouseoverChanged()
342     _mouseover_rot_radius = boost::none;
345 void ControlPointSelection::_updateBounds()
347     _rot_radius = boost::none;
348     _bounds = Geom::OptRect();
349     for (iterator i = _points.begin(); i != _points.end(); ++i) {
350         SelectableControlPoint *cur = (*i);
351         Geom::Point p = cur->position();
352         if (!_bounds) {
353             _bounds = Geom::Rect(p, p);
354         } else {
355             _bounds->expandTo(p);
356         }
357     }
360 void ControlPointSelection::_updateTransformHandles(bool preserve_center)
362     if (_dragging) return;
364     if (_handles_visible && size() > 1) {
365         _handles->setBounds(*bounds(), preserve_center);
366         _handles->setVisible(true);
367     } else if (_one_node_handles && size() == 1) { // only one control point in selection
368         SelectableControlPoint *p = *begin();
369         _handles->setBounds(p->bounds());
370         _handles->rotationCenter().move(p->position());
371         _handles->rotationCenter().setVisible(false);
372         _handles->setVisible(true);
373     } else {
374         _handles->setVisible(false);
375     }
378 /** Moves the selected points along the supplied unit vector according to
379  * the modifier state of the supplied event. */
380 bool ControlPointSelection::_keyboardMove(GdkEventKey const &event, Geom::Point const &dir)
382     if (held_control(event)) return false;
383     unsigned num = 1 + combine_key_events(shortcut_key(event), 0);
385     Geom::Point delta = dir * num; 
386     if (held_shift(event)) delta *= 10;
387     if (held_alt(event)) {
388         delta /= _desktop->current_zoom();
389     } else {
390         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
391         double nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000);
392         delta *= nudge;
393     }
395     transform(Geom::Translate(delta));
396     if (fabs(dir[Geom::X]) > 0) {
397         signal_commit.emit(COMMIT_KEYBOARD_MOVE_X);
398     } else {
399         signal_commit.emit(COMMIT_KEYBOARD_MOVE_Y);
400     }
401     return true;
404 /** @brief Computes the distance to the farthest corner of the bounding box.
405  * Used to determine what it means to "rotate by one pixel". */
406 double ControlPointSelection::_rotationRadius(Geom::Point const &rc)
408     if (empty()) return 1.0; // some safe value
409     Geom::Rect b = *bounds();
410     double maxlen = 0;
411     for (unsigned i = 0; i < 4; ++i) {
412         double len = Geom::distance(b.corner(i), rc);
413         if (len > maxlen) maxlen = len;
414     }
415     return maxlen;
418 /** Rotates the selected points in the given direction according to the modifier state
419  * from the supplied event.
420  * @param event Key event to take modifier state from
421  * @param dir   Direction of rotation (math convention: 1 = counterclockwise, -1 = clockwise)
422  */
423 bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir)
425     if (empty()) return false;
427     Geom::Point rc;
429     // rotate around the mouseovered point, or the selection's rotation center
430     // if nothing is mouseovered
431     double radius;
432     SelectableControlPoint *scp =
433         dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
434     if (scp) {
435         rc = scp->position();
436         if (!_mouseover_rot_radius) {
437             _mouseover_rot_radius = _rotationRadius(rc);
438         }
439         radius = *_mouseover_rot_radius;
440     } else {
441         rc = _handles->rotationCenter();
442         if (!_rot_radius) {
443             _rot_radius = _rotationRadius(rc);
444         }
445         radius = *_rot_radius;
446     }
448     double angle;
449     if (held_alt(event)) {
450         // Rotate by "one pixel". We interpret this as rotating by an angle that causes
451         // the topmost point of a circle circumscribed about the selection's bounding box
452         // to move on an arc 1 screen pixel long.
453         angle = atan2(1.0 / _desktop->current_zoom(), radius) * dir;
454     } else {
455         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
456         int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
457         angle = M_PI * dir / snaps;
458     }
460     // translate to origin, rotate, translate back to original position
461     Geom::Matrix m = Geom::Translate(-rc)
462         * Geom::Rotate(angle) * Geom::Translate(rc);
463     transform(m);
464     signal_commit.emit(COMMIT_KEYBOARD_ROTATE);
465     return true;
469 bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir)
471     if (empty()) return false;
473     double maxext = bounds()->maxExtent();
474     if (Geom::are_near(maxext, 0)) return false;
476     Geom::Point center;
477     SelectableControlPoint *scp =
478         dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
479     if (scp) {
480         center = scp->position();
481     } else {
482         center = _handles->rotationCenter().position();
483     }
485     double length_change;
486     if (held_alt(event)) {
487         // Scale by "one pixel". It means shrink/grow 1px for the larger dimension
488         // of the bounding box.
489         length_change = 1.0 / _desktop->current_zoom() * dir;
490     } else {
491         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
492         length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000);
493         length_change *= dir;
494     }
495     double scale = (maxext + length_change) / maxext;
496     
497     Geom::Matrix m = Geom::Translate(-center) * Geom::Scale(scale) * Geom::Translate(center);
498     transform(m);
499     signal_commit.emit(COMMIT_KEYBOARD_SCALE_UNIFORM);
500     return true;
503 bool ControlPointSelection::_keyboardFlip(Geom::Dim2 d)
505     if (empty()) return false;
507     Geom::Scale scale_transform(1, 1);
508     if (d == Geom::X) {
509         scale_transform = Geom::Scale(-1, 1);
510     } else {
511         scale_transform = Geom::Scale(1, -1);
512     }
514     SelectableControlPoint *scp =
515         dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
516     Geom::Point center = scp ? scp->position() : _handles->rotationCenter().position();
518     Geom::Matrix m = Geom::Translate(-center) * scale_transform * Geom::Translate(center);
519     transform(m);
520     signal_commit.emit(d == Geom::X ? COMMIT_FLIP_X : COMMIT_FLIP_Y);
521     return true;
524 void ControlPointSelection::_commitHandlesTransform(CommitEvent ce)
526     _updateBounds();
527     _updateTransformHandles(true);
528     signal_commit.emit(ce);
531 bool ControlPointSelection::event(GdkEvent *event)
533     // implement generic event handling that should apply for all control point selections here;
534     // for example, keyboard moves and transformations. This way this functionality doesn't need
535     // to be duplicated in many places
536     // Later split out so that it can be reused in object selection
538     switch (event->type) {
539     case GDK_KEY_PRESS:
540         // do not handle key events if the selection is empty
541         if (empty()) break;
543         switch(shortcut_key(event->key)) {
544         // moves
545         case GDK_Up:
546         case GDK_KP_Up:
547         case GDK_KP_8:
548             return _keyboardMove(event->key, Geom::Point(0, 1));
549         case GDK_Down:
550         case GDK_KP_Down:
551         case GDK_KP_2:
552             return _keyboardMove(event->key, Geom::Point(0, -1));
553         case GDK_Right:
554         case GDK_KP_Right:
555         case GDK_KP_6:
556             return _keyboardMove(event->key, Geom::Point(1, 0));
557         case GDK_Left:
558         case GDK_KP_Left:
559         case GDK_KP_4:
560             return _keyboardMove(event->key, Geom::Point(-1, 0));
562         // rotates
563         case GDK_bracketleft:
564             return _keyboardRotate(event->key, 1);
565         case GDK_bracketright:
566             return _keyboardRotate(event->key, -1);
568         // scaling
569         case GDK_less:
570         case GDK_comma:
571             return _keyboardScale(event->key, -1);
572         case GDK_greater:
573         case GDK_period:
574             return _keyboardScale(event->key, 1);
576         // TODO: skewing
578         // flipping
579         // NOTE: H is horizontal flip, while Shift+H switches transform handle mode!
580         case GDK_h:
581         case GDK_H:
582             if (held_shift(event->key)) {
583                 toggleTransformHandlesMode();
584                 return true;
585             }
586             // any modifiers except shift should cause no action
587             if (held_any_modifiers(event->key)) break;
588             return _keyboardFlip(Geom::X);
589         case GDK_v:
590         case GDK_V:
591             if (held_any_modifiers(event->key)) break;
592             return _keyboardFlip(Geom::Y);
593         default: break;
594         }
595         break;
596     default: break;
597     }
598     return false;
601 } // namespace UI
602 } // namespace Inkscape
604 /*
605   Local Variables:
606   mode:c++
607   c-file-style:"stroustrup"
608   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
609   indent-tabs-mode:nil
610   fill-column:99
611   End:
612 */
613 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :