Code

First GSoC node tool commit to Bazaar
[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 /** Transform all selected control points by the supplied affine transformation. */
144 void ControlPointSelection::transform(Geom::Matrix const &m)
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();
155 /** Align control points on the specified axis. */
156 void ControlPointSelection::align(Geom::Dim2 axis)
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     }
174 /** Equdistantly distribute control points by moving them in the specified dimension. */
175 void ControlPointSelection::distribute(Geom::Dim2 d)
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     }
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()
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;
221 Geom::OptRect ControlPointSelection::bounds()
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;
232 void ControlPointSelection::showTransformHandles(bool v, bool one_node)
234     _one_node_handles = one_node;
235     _handles_visible = v;
236     _updateTransformHandles(false);
239 void ControlPointSelection::hideTransformHandles()
241     _handles->setVisible(false);
243 void ControlPointSelection::restoreTransformHandles()
245     _updateTransformHandles(true);
248 void ControlPointSelection::_selectionGrabbed(SelectableControlPoint *p, GdkEventMotion *event)
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     }
260 void ControlPointSelection::_selectionDragged(Geom::Point const &old_pos, Geom::Point &new_pos,
261     GdkEventMotion *event)
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());
280             
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();
293 void ControlPointSelection::_selectionUngrabbed()
295     _dragging = false;
296     _grabbed_point = NULL;
297     restoreTransformHandles();
298     signal_commit.emit(COMMIT_MOUSE_MOVE);
301 void ControlPointSelection::_updateTransformHandles(bool preserve_center)
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     }
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)
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;
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)
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;
387 bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir)
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;
408     
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;
415 bool ControlPointSelection::_keyboardFlip(Geom::Dim2 d)
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;
436 void ControlPointSelection::_commitTransform(CommitEvent ce)
438     _updateTransformHandles(true);
439     signal_commit.emit(ce);
442 bool ControlPointSelection::event(GdkEvent *event)
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;
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 :