Code

First GSoC node tool commit to Bazaar
[inkscape.git] / src / ui / tool / multi-path-manipulator.cpp
1 /** @file
2  * Path manipulator - 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 <tr1/unordered_set>
12 #include <boost/shared_ptr.hpp>
13 #include <glib.h>
14 #include <glibmm/i18n.h>
15 #include "desktop.h"
16 #include "desktop-handles.h"
17 #include "document.h"
18 #include "message-stack.h"
19 #include "sp-path.h"
20 #include "ui/tool/control-point-selection.h"
21 #include "ui/tool/event-utils.h"
22 #include "ui/tool/node.h"
23 #include "ui/tool/multi-path-manipulator.h"
24 #include "ui/tool/path-manipulator.h"
26 namespace std { using namespace tr1; }
28 namespace Inkscape {
29 namespace UI {
31 namespace {
32 typedef std::pair<NodeList::iterator, NodeList::iterator> IterPair;
33 typedef std::vector<IterPair> IterPairList;
34 typedef std::unordered_set<NodeList::iterator> IterSet;
35 typedef std::multimap<double, IterPair> DistanceMap;
36 typedef std::pair<double, IterPair> DistanceMapItem;
38 /** Find two selected endnodes.
39  * @returns -1 if not enough endnodes selected, 1 if too many, 0 if OK */
40 void find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
41 {
42     IterSet join_iters;
43     DistanceMap dists;
45     // find all endnodes in selection
46     for (ControlPointSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
47         Node *node = dynamic_cast<Node*>(i->first);
48         if (!node) continue;
49         NodeList::iterator iter = NodeList::get_iterator(node);
50         if (!iter.next() || !iter.prev()) join_iters.insert(iter);
51     }
53     if (join_iters.size() < 2) return;
55     // Below we find the closest pairs. The algorithm is O(N^3).
56     // We can go down to O(N^2 log N) by using O(N^2) memory, by putting all pairs
57     // with their distances in a multimap (not worth it IMO).
58     while (join_iters.size() >= 2) {
59         double closest = DBL_MAX;
60         IterPair closest_pair;
61         for (IterSet::iterator i = join_iters.begin(); i != join_iters.end(); ++i) {
62             for (IterSet::iterator j = join_iters.begin(); j != i; ++j) {
63                 double dist = Geom::distance(**i, **j);
64                 if (dist < closest) {
65                     closest = dist;
66                     closest_pair = std::make_pair(*i, *j);
67                 }
68             }
69         }
70         pairs.push_back(closest_pair);
71         join_iters.erase(closest_pair.first);
72         join_iters.erase(closest_pair.second);
73     }
74 }
76 /** After this function, first should be at the end of path and second at the beginnning.
77  * @returns True if the nodes are in the same subpath */
78 bool prepare_join(IterPair &join_iters)
79 {
80     if (&NodeList::get(join_iters.first) == &NodeList::get(join_iters.second)) {
81         if (join_iters.first.next()) // if first is begin, swap the iterators
82             std::swap(join_iters.first, join_iters.second);
83         return true;
84     }
86     NodeList &sp_first = NodeList::get(join_iters.first);
87     NodeList &sp_second = NodeList::get(join_iters.second);
88     if (join_iters.first.next()) { // first is begin
89         if (join_iters.second.next()) { // second is begin
90             sp_first.reverse();
91         } else { // second is end
92             std::swap(join_iters.first, join_iters.second);
93         }
94     } else { // first is end
95         if (join_iters.second.next()) { // second is begin
96             // do nothing
97         } else { // second is end
98             sp_second.reverse();
99         }
100     }
101     return false;
103 } // anonymous namespace
106 MultiPathManipulator::MultiPathManipulator(PathSharedData const &data, sigc::connection &chg)
107     : PointManipulator(data.node_data.desktop, *data.node_data.selection)
108     , _path_data(data)
109     , _changed(chg)
111     //
112     _selection.signal_commit.connect(
113         sigc::mem_fun(*this, &MultiPathManipulator::_commit));
114     _selection.signal_point_changed.connect(
115         sigc::hide( sigc::hide(
116             signal_coords_changed.make_slot())));
119 MultiPathManipulator::~MultiPathManipulator()
121     _mmap.clear();
124 /** Remove empty manipulators. */
125 void MultiPathManipulator::cleanup()
127     for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) {
128         if (i->second->empty()) _mmap.erase(i++);
129         else ++i;
130     }
133 void MultiPathManipulator::setItems(std::map<SPPath*,
134     std::pair<Geom::Matrix, guint32> > const &items)
136     typedef std::map<SPPath*, std::pair<Geom::Matrix, guint32> > TransMap;
137     typedef std::set<SPPath*> ItemSet;
138     ItemSet to_remove, to_add, current, new_items;
140     for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
141         current.insert(i->first);
142     }
143     for (TransMap::const_iterator i = items.begin(); i != items.end(); ++i) {
144         new_items.insert(i->first);
145     }
147     std::set_difference(current.begin(), current.end(), new_items.begin(), new_items.end(),
148         std::inserter(to_remove, to_remove.end()));
149     std::set_difference(new_items.begin(), new_items.end(), current.begin(), current.end(),
150         std::inserter(to_add, to_add.end()));
152     for (ItemSet::iterator i = to_remove.begin(); i != to_remove.end(); ++i) {
153         _mmap.erase(*i);
154     }
155     for (ItemSet::iterator i = to_add.begin(); i != to_add.end(); ++i) {
156         boost::shared_ptr<PathManipulator> pm;
157         TransMap::const_iterator f = items.find(*i);
158         pm.reset(new PathManipulator(_path_data, *i, f->second.first, f->second.second));
159         pm->showHandles(_show_handles);
160         pm->showOutline(_show_outline);
161         pm->showPathDirection(_show_path_direction);
162         _mmap.insert(std::make_pair(*i, pm));
163     }
166 void MultiPathManipulator::selectSubpaths()
168     if (_selection.empty()) {
169         invokeForAll(&PathManipulator::selectAll);
170     } else {
171         invokeForAll(&PathManipulator::selectSubpaths);
172     }
174 void MultiPathManipulator::selectAll()
176     invokeForAll(&PathManipulator::selectAll);
179 void MultiPathManipulator::selectArea(Geom::Rect const &area, bool take)
181     if (take) _selection.clear();
182     invokeForAll(&PathManipulator::selectArea, area);
185 void MultiPathManipulator::shiftSelection(int dir)
187     invokeForAll(&PathManipulator::shiftSelection, dir);
189 void MultiPathManipulator::invertSelection()
191     invokeForAll(&PathManipulator::invertSelection);
193 void MultiPathManipulator::invertSelectionInSubpaths()
195     invokeForAll(&PathManipulator::invertSelectionInSubpaths);
197 void MultiPathManipulator::deselect()
199     _selection.clear();
202 void MultiPathManipulator::setNodeType(NodeType type)
204     if (_selection.empty()) return;
205     for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
206         Node *node = dynamic_cast<Node*>(i->first);
207         if (node) node->setType(type);
208     }
209     _done(_("Change node type"));
212 void MultiPathManipulator::setSegmentType(SegmentType type)
214     if (_selection.empty()) return;
215     invokeForAll(&PathManipulator::setSegmentType, type);
216     if (type == SEGMENT_STRAIGHT) {
217         _done(_("Straighten segments"));
218     } else {
219         _done(_("Make segments curves"));
220     }
223 void MultiPathManipulator::insertNodes()
225     invokeForAll(&PathManipulator::insertNodes);
226     _done(_("Add nodes"));
229 void MultiPathManipulator::joinNodes()
231     // Node join has two parts. In the first one we join two subpaths by fusing endpoints
232     // into one. In the second we fuse nodes in each subpath.
233     IterPairList joins;
234     NodeList::iterator preserve_pos;
235     Node *mouseover_node = dynamic_cast<Node*>(ControlPoint::mouseovered_point);
236     if (mouseover_node) {
237         preserve_pos = NodeList::get_iterator(mouseover_node);
238     }
239     find_join_iterators(_selection, joins);
241     for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
242         bool same_path = prepare_join(*i);
243         bool mouseover = true;
244         NodeList &sp_first = NodeList::get(i->first);
245         NodeList &sp_second = NodeList::get(i->second);
246         i->first->setType(NODE_CUSP, false);
248         Geom::Point joined_pos, pos_front, pos_back;
249         pos_front = *i->second->front();
250         pos_back = *i->first->back();
251         if (i->first == preserve_pos) {
252             joined_pos = *i->first;
253         } else if (i->second == preserve_pos) {
254             joined_pos = *i->second;
255         } else {
256             joined_pos = Geom::middle_point(pos_back, pos_front);
257             mouseover = false;
258         }
260         // if the handles aren't degenerate, don't move them
261         i->first->move(joined_pos);
262         Node *joined_node = i->first.ptr();
263         if (!i->second->front()->isDegenerate()) {
264             joined_node->front()->setPosition(pos_front);
265         }
266         if (!i->first->back()->isDegenerate()) {
267             joined_node->back()->setPosition(pos_back);
268         }
269         if (mouseover) {
270             // Second node could be mouseovered, but it will be deleted, so we must change
271             // the preserve_pos iterator to the first node.
272             preserve_pos = i->first;
273         }
274         sp_second.erase(i->second);
276         if (same_path) {
277             sp_first.setClosed(true);
278         } else {
279             sp_first.splice(sp_first.end(), sp_second);
280             sp_second.kill();
281         }
282         _selection.insert(i->first.ptr());
283     }
284     // Second part replaces contiguous selections of nodes with single nodes
285     invokeForAll(&PathManipulator::weldNodes, preserve_pos);
286     _doneWithCleanup(_("Join nodes"));
289 void MultiPathManipulator::breakNodes()
291     if (_selection.empty()) return;
292     invokeForAll(&PathManipulator::breakNodes);
293     _done(_("Break nodes"));
296 void MultiPathManipulator::deleteNodes(bool keep_shape)
298     if (_selection.empty()) return;
299     invokeForAll(&PathManipulator::deleteNodes, keep_shape);
300     _doneWithCleanup(_("Delete nodes"));
303 /** Join selected endpoints to create segments. */
304 void MultiPathManipulator::joinSegment()
306     IterPairList joins;
307     find_join_iterators(_selection, joins);
308     if (joins.empty()) {
309         _desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
310             _("There must be at least 2 endnodes in selection"));
311         return;
312     }
314     for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
315         bool same_path = prepare_join(*i);
316         NodeList &sp_first = NodeList::get(i->first);
317         NodeList &sp_second = NodeList::get(i->second);
318         i->first->setType(NODE_CUSP, false);
319         i->second->setType(NODE_CUSP, false);
320         if (same_path) {
321             sp_first.setClosed(true);
322         } else {
323             sp_first.splice(sp_first.end(), sp_second);
324             sp_second.kill();
325         }
326     }
328     _doneWithCleanup("Join segment");
331 void MultiPathManipulator::deleteSegments()
333     if (_selection.empty()) return;
334     invokeForAll(&PathManipulator::deleteSegments);
335     _doneWithCleanup("Delete segments");
338 void MultiPathManipulator::alignNodes(Geom::Dim2 d)
340     _selection.align(d);
341     if (d == Geom::X) {
342         _done("Align nodes to a horizontal line");
343     } else {
344         _done("Align nodes to a vertical line");
345     }
348 void MultiPathManipulator::distributeNodes(Geom::Dim2 d)
350     _selection.distribute(d);
351     if (d == Geom::X) {
352         _done("Distrubute nodes horizontally");
353     } else {
354         _done("Distribute nodes vertically");
355     }
358 void MultiPathManipulator::reverseSubpaths()
360     invokeForAll(&PathManipulator::reverseSubpaths);
361     _done("Reverse selected subpaths");
364 void MultiPathManipulator::move(Geom::Point const &delta)
366     _selection.transform(Geom::Translate(delta));
367     _done("Move nodes");
370 void MultiPathManipulator::showOutline(bool show)
372     invokeForAll(&PathManipulator::showOutline, show);
373     _show_outline = show;
376 void MultiPathManipulator::showHandles(bool show)
378     invokeForAll(&PathManipulator::showHandles, show);
379     _show_handles = show;
382 void MultiPathManipulator::showPathDirection(bool show)
384     invokeForAll(&PathManipulator::showPathDirection, show);
385     _show_path_direction = show;
388 bool MultiPathManipulator::event(GdkEvent *event)
390     switch (event->type) {
391     case GDK_KEY_PRESS:
392         switch (shortcut_key(event->key)) {
393         case GDK_Insert:
394         case GDK_KP_Insert:
395             insertNodes();
396             return true;
397         case GDK_i:
398         case GDK_I:
399             if (held_only_shift(event->key)) {
400                 insertNodes();
401                 return true;
402             }
403             break;
404         case GDK_j:
405         case GDK_J:
406             if (held_only_shift(event->key)) {
407                 joinNodes();
408                 return true;
409             }
410             if (held_only_alt(event->key)) {
411                 joinSegment();
412                 return true;
413             }
414             break;
415         case GDK_b:
416         case GDK_B:
417             if (held_only_shift(event->key)) {
418                 breakNodes();
419                 return true;
420             }
421             break;
422         case GDK_Delete:
423         case GDK_KP_Delete:
424         case GDK_BackSpace:
425             if (held_shift(event->key)) break;
426             if (held_alt(event->key)) {
427                 deleteSegments();
428             } else {
429                 deleteNodes(!held_control(event->key));
430             }
431             return true;
432         case GDK_c:
433         case GDK_C:
434             if (held_only_shift(event->key)) {
435                 setNodeType(NODE_CUSP);
436                 return true;
437             }
438             break;
439         case GDK_s:
440         case GDK_S:
441             if (held_only_shift(event->key)) {
442                 setNodeType(NODE_SMOOTH);
443                 return true;
444             }
445             break;
446         case GDK_a:
447         case GDK_A:
448             if (held_only_shift(event->key)) {
449                 setNodeType(NODE_AUTO);
450                 return true;
451             }
452             break;
453         case GDK_y:
454         case GDK_Y:
455             if (held_only_shift(event->key)) {
456                 setNodeType(NODE_SYMMETRIC);
457                 return true;
458             }
459             break;
460         case GDK_r:
461         case GDK_R:
462             if (held_only_shift(event->key)) {
463                 reverseSubpaths();
464                 break;
465             }
466             break;
467         default:
468             break;
469         }
470         break;
471     default: break;
472     }
474     for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
475         if (i->second->event(event)) return true;
476     }
477     return false;
480 void MultiPathManipulator::_commit(CommitEvent cps)
482     gchar const *reason = NULL;
483     gchar const *key = NULL;
484     switch(cps) {
485     case COMMIT_MOUSE_MOVE:
486         reason = _("Move nodes");
487         break;
488     case COMMIT_KEYBOARD_MOVE_X:
489         reason = _("Move nodes horizontally");
490         key = "node:move:x";
491         break;
492     case COMMIT_KEYBOARD_MOVE_Y:
493         reason = _("Move nodes vertically");
494         key = "node:move:y";
495         break;
496     case COMMIT_MOUSE_ROTATE:
497         reason = _("Rotate nodes");
498         break;
499     case COMMIT_KEYBOARD_ROTATE:
500         reason = _("Rotate nodes");
501         key = "node:rotate";
502         break;
503     case COMMIT_MOUSE_SCALE_UNIFORM:
504         reason = _("Scale nodes uniformly");
505         break;
506     case COMMIT_MOUSE_SCALE:
507         reason = _("Scale nodes");
508         break;
509     case COMMIT_KEYBOARD_SCALE_UNIFORM:
510         reason = _("Scale nodes uniformly");
511         key = "node:scale:uniform";
512         break;
513     case COMMIT_KEYBOARD_SCALE_X:
514         reason = _("Scale nodes horizontally");
515         key = "node:scale:x";
516         break;
517     case COMMIT_KEYBOARD_SCALE_Y:
518         reason = _("Scale nodes vertically");
519         key = "node:scale:y";
520         break;
521     case COMMIT_FLIP_X:
522         reason = _("Flip nodes horizontally");
523         break;
524     case COMMIT_FLIP_Y:
525         reason = _("Flip nodes vertically");
526         break;
527     default: return;
528     }
529     
530     _selection.signal_update.emit();
531     invokeForAll(&PathManipulator::writeXML);
532     if (key) {
533         sp_document_maybe_done(sp_desktop_document(_desktop), key, SP_VERB_CONTEXT_NODE, reason);
534     } else {
535         sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
536     }
537     signal_coords_changed.emit();
540 /** Commits changes to XML and adds undo stack entry. */
541 void MultiPathManipulator::_done(gchar const *reason) {
542     invokeForAll(&PathManipulator::update);
543     invokeForAll(&PathManipulator::writeXML);
544     sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
545     signal_coords_changed.emit();
548 /** Commits changes to XML, adds undo stack entry and removes empty manipulators. */
549 void MultiPathManipulator::_doneWithCleanup(gchar const *reason) {
550     _changed.block();
551     _done(reason);
552     cleanup();
553     _changed.unblock();
556 } // namespace UI
557 } // namespace Inkscape
559 /*
560   Local Variables:
561   mode:c++
562   c-file-style:"stroustrup"
563   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
564   indent-tabs-mode:nil
565   fill-column:99
566   End:
567 */
568 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :