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;
102 }
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)
110 {
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())));
117 }
119 MultiPathManipulator::~MultiPathManipulator()
120 {
121 _mmap.clear();
122 }
124 /** Remove empty manipulators. */
125 void MultiPathManipulator::cleanup()
126 {
127 for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) {
128 if (i->second->empty()) _mmap.erase(i++);
129 else ++i;
130 }
131 }
133 void MultiPathManipulator::setItems(std::map<SPPath*,
134 std::pair<Geom::Matrix, guint32> > const &items)
135 {
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 }
164 }
166 void MultiPathManipulator::selectSubpaths()
167 {
168 if (_selection.empty()) {
169 invokeForAll(&PathManipulator::selectAll);
170 } else {
171 invokeForAll(&PathManipulator::selectSubpaths);
172 }
173 }
174 void MultiPathManipulator::selectAll()
175 {
176 invokeForAll(&PathManipulator::selectAll);
177 }
179 void MultiPathManipulator::selectArea(Geom::Rect const &area, bool take)
180 {
181 if (take) _selection.clear();
182 invokeForAll(&PathManipulator::selectArea, area);
183 }
185 void MultiPathManipulator::shiftSelection(int dir)
186 {
187 invokeForAll(&PathManipulator::shiftSelection, dir);
188 }
189 void MultiPathManipulator::invertSelection()
190 {
191 invokeForAll(&PathManipulator::invertSelection);
192 }
193 void MultiPathManipulator::invertSelectionInSubpaths()
194 {
195 invokeForAll(&PathManipulator::invertSelectionInSubpaths);
196 }
197 void MultiPathManipulator::deselect()
198 {
199 _selection.clear();
200 }
202 void MultiPathManipulator::setNodeType(NodeType type)
203 {
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"));
210 }
212 void MultiPathManipulator::setSegmentType(SegmentType type)
213 {
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 }
221 }
223 void MultiPathManipulator::insertNodes()
224 {
225 invokeForAll(&PathManipulator::insertNodes);
226 _done(_("Add nodes"));
227 }
229 void MultiPathManipulator::joinNodes()
230 {
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"));
287 }
289 void MultiPathManipulator::breakNodes()
290 {
291 if (_selection.empty()) return;
292 invokeForAll(&PathManipulator::breakNodes);
293 _done(_("Break nodes"));
294 }
296 void MultiPathManipulator::deleteNodes(bool keep_shape)
297 {
298 if (_selection.empty()) return;
299 invokeForAll(&PathManipulator::deleteNodes, keep_shape);
300 _doneWithCleanup(_("Delete nodes"));
301 }
303 /** Join selected endpoints to create segments. */
304 void MultiPathManipulator::joinSegment()
305 {
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");
329 }
331 void MultiPathManipulator::deleteSegments()
332 {
333 if (_selection.empty()) return;
334 invokeForAll(&PathManipulator::deleteSegments);
335 _doneWithCleanup("Delete segments");
336 }
338 void MultiPathManipulator::alignNodes(Geom::Dim2 d)
339 {
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 }
346 }
348 void MultiPathManipulator::distributeNodes(Geom::Dim2 d)
349 {
350 _selection.distribute(d);
351 if (d == Geom::X) {
352 _done("Distrubute nodes horizontally");
353 } else {
354 _done("Distribute nodes vertically");
355 }
356 }
358 void MultiPathManipulator::reverseSubpaths()
359 {
360 invokeForAll(&PathManipulator::reverseSubpaths);
361 _done("Reverse selected subpaths");
362 }
364 void MultiPathManipulator::move(Geom::Point const &delta)
365 {
366 _selection.transform(Geom::Translate(delta));
367 _done("Move nodes");
368 }
370 void MultiPathManipulator::showOutline(bool show)
371 {
372 invokeForAll(&PathManipulator::showOutline, show);
373 _show_outline = show;
374 }
376 void MultiPathManipulator::showHandles(bool show)
377 {
378 invokeForAll(&PathManipulator::showHandles, show);
379 _show_handles = show;
380 }
382 void MultiPathManipulator::showPathDirection(bool show)
383 {
384 invokeForAll(&PathManipulator::showPathDirection, show);
385 _show_path_direction = show;
386 }
388 bool MultiPathManipulator::event(GdkEvent *event)
389 {
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;
478 }
480 void MultiPathManipulator::_commit(CommitEvent cps)
481 {
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 }
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();
538 }
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();
546 }
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();
554 }
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 :