5f60f117a3d0ceb32aa4dfb5e03d58799613e9dc
1 /** @file
2 * Multi path manipulator - implementation
3 */
4 /* Authors:
5 * Krzysztof KosiĆski <tweenk.pl@gmail.com>
6 * Abhishek Sharma
7 *
8 * Copyright (C) 2009 Authors
9 * Released under GNU GPL, read the file 'COPYING' for more information
10 */
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 "live_effects/lpeobject.h"
19 #include "message-stack.h"
20 #include "preferences.h"
21 #include "sp-path.h"
22 #include "ui/tool/control-point-selection.h"
23 #include "ui/tool/event-utils.h"
24 #include "ui/tool/node.h"
25 #include "ui/tool/multi-path-manipulator.h"
26 #include "ui/tool/path-manipulator.h"
27 #include "util/unordered-containers.h"
29 #ifdef USE_GNU_HASHES
30 namespace __gnu_cxx {
31 template<>
32 struct hash<Inkscape::UI::NodeList::iterator> {
33 size_t operator()(Inkscape::UI::NodeList::iterator const &n) const {
34 return reinterpret_cast<size_t>(n.ptr());
35 }
36 };
37 } // namespace __gnu_cxx
38 #endif // USE_GNU_HASHES
40 namespace Inkscape {
41 namespace UI {
43 namespace {
45 struct hash_nodelist_iterator
46 : public std::unary_function<NodeList::iterator, std::size_t>
47 {
48 std::size_t operator()(NodeList::iterator i) const {
49 return INK_HASH<NodeList::iterator::pointer>()(&*i);
50 }
51 };
53 typedef std::pair<NodeList::iterator, NodeList::iterator> IterPair;
54 typedef std::vector<IterPair> IterPairList;
55 typedef INK_UNORDERED_SET<NodeList::iterator, hash_nodelist_iterator> IterSet;
56 typedef std::multimap<double, IterPair> DistanceMap;
57 typedef std::pair<double, IterPair> DistanceMapItem;
59 /** Find pairs of selected endnodes suitable for joining. */
60 void find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
61 {
62 IterSet join_iters;
63 DistanceMap dists;
65 // find all endnodes in selection
66 for (ControlPointSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
67 Node *node = dynamic_cast<Node*>(*i);
68 if (!node) continue;
69 NodeList::iterator iter = NodeList::get_iterator(node);
70 if (!iter.next() || !iter.prev()) join_iters.insert(iter);
71 }
73 if (join_iters.size() < 2) return;
75 // Below we find the closest pairs. The algorithm is O(N^3).
76 // We can go down to O(N^2 log N) by using O(N^2) memory, by putting all pairs
77 // with their distances in a multimap (not worth it IMO).
78 while (join_iters.size() >= 2) {
79 double closest = DBL_MAX;
80 IterPair closest_pair;
81 for (IterSet::iterator i = join_iters.begin(); i != join_iters.end(); ++i) {
82 for (IterSet::iterator j = join_iters.begin(); j != i; ++j) {
83 double dist = Geom::distance(**i, **j);
84 if (dist < closest) {
85 closest = dist;
86 closest_pair = std::make_pair(*i, *j);
87 }
88 }
89 }
90 pairs.push_back(closest_pair);
91 join_iters.erase(closest_pair.first);
92 join_iters.erase(closest_pair.second);
93 }
94 }
96 /** After this function, first should be at the end of path and second at the beginnning.
97 * @returns True if the nodes are in the same subpath */
98 bool prepare_join(IterPair &join_iters)
99 {
100 if (&NodeList::get(join_iters.first) == &NodeList::get(join_iters.second)) {
101 if (join_iters.first.next()) // if first is begin, swap the iterators
102 std::swap(join_iters.first, join_iters.second);
103 return true;
104 }
106 NodeList &sp_first = NodeList::get(join_iters.first);
107 NodeList &sp_second = NodeList::get(join_iters.second);
108 if (join_iters.first.next()) { // first is begin
109 if (join_iters.second.next()) { // second is begin
110 sp_first.reverse();
111 } else { // second is end
112 std::swap(join_iters.first, join_iters.second);
113 }
114 } else { // first is end
115 if (join_iters.second.next()) { // second is begin
116 // do nothing
117 } else { // second is end
118 sp_second.reverse();
119 }
120 }
121 return false;
122 }
123 } // anonymous namespace
126 MultiPathManipulator::MultiPathManipulator(PathSharedData &data, sigc::connection &chg)
127 : PointManipulator(data.node_data.desktop, *data.node_data.selection)
128 , _path_data(data)
129 , _changed(chg)
130 {
131 _selection.signal_commit.connect(
132 sigc::mem_fun(*this, &MultiPathManipulator::_commit));
133 _selection.signal_point_changed.connect(
134 sigc::hide( sigc::hide(
135 signal_coords_changed.make_slot())));
136 }
138 MultiPathManipulator::~MultiPathManipulator()
139 {
140 _mmap.clear();
141 }
143 /** Remove empty manipulators. */
144 void MultiPathManipulator::cleanup()
145 {
146 for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) {
147 if (i->second->empty()) _mmap.erase(i++);
148 else ++i;
149 }
150 }
152 /** @brief Change the set of items to edit.
153 *
154 * This method attempts to preserve as much of the state as possible. */
155 void MultiPathManipulator::setItems(std::set<ShapeRecord> const &s)
156 {
157 std::set<ShapeRecord> shapes(s);
159 // iterate over currently edited items, modifying / removing them as necessary
160 for (MapType::iterator i = _mmap.begin(); i != _mmap.end();) {
161 std::set<ShapeRecord>::iterator si = shapes.find(i->first);
162 if (si == shapes.end()) {
163 // This item is no longer supposed to be edited - remove its manipulator
164 _mmap.erase(i++);
165 } else {
166 ShapeRecord const &sr = i->first;
167 ShapeRecord const &sr_new = *si;
168 // if the shape record differs, replace the key only and modify other values
169 if (sr.edit_transform != sr_new.edit_transform ||
170 sr.role != sr_new.role)
171 {
172 boost::shared_ptr<PathManipulator> hold(i->second);
173 if (sr.edit_transform != sr_new.edit_transform)
174 hold->setControlsTransform(sr_new.edit_transform);
175 if (sr.role != sr_new.role) {
176 //hold->setOutlineColor(_getOutlineColor(sr_new.role));
177 }
178 _mmap.erase(sr);
179 _mmap.insert(std::make_pair(sr_new, hold));
180 }
181 shapes.erase(si); // remove the processed record
182 ++i;
183 }
184 }
186 // add newly selected items
187 for (std::set<ShapeRecord>::iterator i = shapes.begin(); i != shapes.end(); ++i) {
188 ShapeRecord const &r = *i;
189 if (!SP_IS_PATH(r.item) && !IS_LIVEPATHEFFECT(r.item)) continue;
190 boost::shared_ptr<PathManipulator> newpm(new PathManipulator(*this, (SPPath*) r.item,
191 r.edit_transform, _getOutlineColor(r.role), r.lpe_key));
192 newpm->showHandles(_show_handles);
193 // always show outlines for clips and masks
194 newpm->showOutline(_show_outline || r.role != SHAPE_ROLE_NORMAL);
195 newpm->showPathDirection(_show_path_direction);
196 newpm->setLiveOutline(_live_outline);
197 newpm->setLiveObjects(_live_objects);
198 _mmap.insert(std::make_pair(r, newpm));
199 }
200 }
202 void MultiPathManipulator::selectSubpaths()
203 {
204 if (_selection.empty()) {
205 _selection.selectAll();
206 } else {
207 invokeForAll(&PathManipulator::selectSubpaths);
208 }
209 }
211 void MultiPathManipulator::shiftSelection(int dir)
212 {
213 invokeForAll(&PathManipulator::shiftSelection, dir);
214 }
216 void MultiPathManipulator::invertSelectionInSubpaths()
217 {
218 invokeForAll(&PathManipulator::invertSelectionInSubpaths);
219 }
221 void MultiPathManipulator::setNodeType(NodeType type)
222 {
223 if (_selection.empty()) return;
225 // When all selected nodes are already cusp, retract their handles
226 bool retract_handles = (type == NODE_CUSP);
228 for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
229 Node *node = dynamic_cast<Node*>(*i);
230 if (node) {
231 retract_handles &= (node->type() == NODE_CUSP);
232 node->setType(type);
233 }
234 }
236 if (retract_handles) {
237 for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
238 Node *node = dynamic_cast<Node*>(*i);
239 if (node) {
240 node->front()->retract();
241 node->back()->retract();
242 }
243 }
244 }
246 _done(retract_handles ? _("Retract handles") : _("Change node type"));
247 }
249 void MultiPathManipulator::setSegmentType(SegmentType type)
250 {
251 if (_selection.empty()) return;
252 invokeForAll(&PathManipulator::setSegmentType, type);
253 if (type == SEGMENT_STRAIGHT) {
254 _done(_("Straighten segments"));
255 } else {
256 _done(_("Make segments curves"));
257 }
258 }
260 void MultiPathManipulator::insertNodes()
261 {
262 invokeForAll(&PathManipulator::insertNodes);
263 _done(_("Add nodes"));
264 }
266 void MultiPathManipulator::duplicateNodes()
267 {
268 invokeForAll(&PathManipulator::duplicateNodes);
269 _done(_("Duplicate nodes"));
270 }
272 void MultiPathManipulator::joinNodes()
273 {
274 invokeForAll(&PathManipulator::hideDragPoint);
275 // Node join has two parts. In the first one we join two subpaths by fusing endpoints
276 // into one. In the second we fuse nodes in each subpath.
277 IterPairList joins;
278 NodeList::iterator preserve_pos;
279 Node *mouseover_node = dynamic_cast<Node*>(ControlPoint::mouseovered_point);
280 if (mouseover_node) {
281 preserve_pos = NodeList::get_iterator(mouseover_node);
282 }
283 find_join_iterators(_selection, joins);
285 for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
286 bool same_path = prepare_join(*i);
287 NodeList &sp_first = NodeList::get(i->first);
288 NodeList &sp_second = NodeList::get(i->second);
289 i->first->setType(NODE_CUSP, false);
291 Geom::Point joined_pos, pos_handle_front, pos_handle_back;
292 pos_handle_front = *i->second->front();
293 pos_handle_back = *i->first->back();
295 // When we encounter the mouseover node, we unset the iterator - it will be invalidated
296 if (i->first == preserve_pos) {
297 joined_pos = *i->first;
298 preserve_pos = NodeList::iterator();
299 } else if (i->second == preserve_pos) {
300 joined_pos = *i->second;
301 preserve_pos = NodeList::iterator();
302 } else {
303 joined_pos = Geom::middle_point(*i->first, *i->second);
304 }
306 // if the handles aren't degenerate, don't move them
307 i->first->move(joined_pos);
308 Node *joined_node = i->first.ptr();
309 if (!i->second->front()->isDegenerate()) {
310 joined_node->front()->setPosition(pos_handle_front);
311 }
312 if (!i->first->back()->isDegenerate()) {
313 joined_node->back()->setPosition(pos_handle_back);
314 }
315 sp_second.erase(i->second);
317 if (same_path) {
318 sp_first.setClosed(true);
319 } else {
320 sp_first.splice(sp_first.end(), sp_second);
321 sp_second.kill();
322 }
323 _selection.insert(i->first.ptr());
324 }
326 if (joins.empty()) {
327 // Second part replaces contiguous selections of nodes with single nodes
328 invokeForAll(&PathManipulator::weldNodes, preserve_pos);
329 }
331 _doneWithCleanup(_("Join nodes"));
332 }
334 void MultiPathManipulator::breakNodes()
335 {
336 if (_selection.empty()) return;
337 invokeForAll(&PathManipulator::breakNodes);
338 _done(_("Break nodes"));
339 }
341 void MultiPathManipulator::deleteNodes(bool keep_shape)
342 {
343 if (_selection.empty()) return;
344 invokeForAll(&PathManipulator::deleteNodes, keep_shape);
345 _doneWithCleanup(_("Delete nodes"));
346 }
348 /** Join selected endpoints to create segments. */
349 void MultiPathManipulator::joinSegments()
350 {
351 IterPairList joins;
352 find_join_iterators(_selection, joins);
354 for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
355 bool same_path = prepare_join(*i);
356 NodeList &sp_first = NodeList::get(i->first);
357 NodeList &sp_second = NodeList::get(i->second);
358 i->first->setType(NODE_CUSP, false);
359 i->second->setType(NODE_CUSP, false);
360 if (same_path) {
361 sp_first.setClosed(true);
362 } else {
363 sp_first.splice(sp_first.end(), sp_second);
364 sp_second.kill();
365 }
366 }
368 if (joins.empty()) {
369 invokeForAll(&PathManipulator::weldSegments);
370 }
371 _doneWithCleanup("Join segments");
372 }
374 void MultiPathManipulator::deleteSegments()
375 {
376 if (_selection.empty()) return;
377 invokeForAll(&PathManipulator::deleteSegments);
378 _doneWithCleanup("Delete segments");
379 }
381 void MultiPathManipulator::alignNodes(Geom::Dim2 d)
382 {
383 _selection.align(d);
384 if (d == Geom::X) {
385 _done("Align nodes to a horizontal line");
386 } else {
387 _done("Align nodes to a vertical line");
388 }
389 }
391 void MultiPathManipulator::distributeNodes(Geom::Dim2 d)
392 {
393 _selection.distribute(d);
394 if (d == Geom::X) {
395 _done("Distrubute nodes horizontally");
396 } else {
397 _done("Distribute nodes vertically");
398 }
399 }
401 void MultiPathManipulator::reverseSubpaths()
402 {
403 if (_selection.empty()) {
404 invokeForAll(&PathManipulator::reverseSubpaths, false);
405 _done("Reverse subpaths");
406 } else {
407 invokeForAll(&PathManipulator::reverseSubpaths, true);
408 _done("Reverse selected subpaths");
409 }
410 }
412 void MultiPathManipulator::move(Geom::Point const &delta)
413 {
414 _selection.transform(Geom::Translate(delta));
415 _done("Move nodes");
416 }
418 void MultiPathManipulator::showOutline(bool show)
419 {
420 for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
421 // always show outlines for clipping paths and masks
422 i->second->showOutline(show || i->first.role != SHAPE_ROLE_NORMAL);
423 }
424 _show_outline = show;
425 }
427 void MultiPathManipulator::showHandles(bool show)
428 {
429 invokeForAll(&PathManipulator::showHandles, show);
430 _show_handles = show;
431 }
433 void MultiPathManipulator::showPathDirection(bool show)
434 {
435 invokeForAll(&PathManipulator::showPathDirection, show);
436 _show_path_direction = show;
437 }
439 /** @brief Set live outline update status
440 * When set to true, outline will be updated continuously when dragging
441 * or transforming nodes. Otherwise it will only update when changes are committed
442 * to XML. */
443 void MultiPathManipulator::setLiveOutline(bool set)
444 {
445 invokeForAll(&PathManipulator::setLiveOutline, set);
446 _live_outline = set;
447 }
449 /** @brief Set live object update status
450 * When set to true, objects will be updated continuously when dragging
451 * or transforming nodes. Otherwise they will only update when changes are committed
452 * to XML. */
453 void MultiPathManipulator::setLiveObjects(bool set)
454 {
455 invokeForAll(&PathManipulator::setLiveObjects, set);
456 _live_objects = set;
457 }
459 void MultiPathManipulator::updateOutlineColors()
460 {
461 //for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
462 // i->second->setOutlineColor(_getOutlineColor(i->first.role));
463 //}
464 }
466 bool MultiPathManipulator::event(GdkEvent *event)
467 {
468 _tracker.event(event);
469 guint key = 0;
470 if (event->type == GDK_KEY_PRESS) {
471 key = shortcut_key(event->key);
472 }
474 // Single handle adjustments go here.
475 if (_selection.size() == 1 && event->type == GDK_KEY_PRESS) {
476 do {
477 Node *n = dynamic_cast<Node *>(*_selection.begin());
478 if (!n) break;
480 PathManipulator &pm = n->nodeList().subpathList().pm();
482 int which = 0;
483 if (_tracker.rightAlt() || _tracker.rightControl()) {
484 which = 1;
485 }
486 if (_tracker.leftAlt() || _tracker.leftControl()) {
487 if (which != 0) break; // ambiguous
488 which = -1;
489 }
490 if (which == 0) break; // no handle chosen
491 bool one_pixel = _tracker.leftAlt() || _tracker.rightAlt();
492 bool handled = true;
494 switch (key) {
495 // single handle functions
496 // rotation
497 case GDK_bracketleft:
498 case GDK_braceleft:
499 pm.rotateHandle(n, which, 1, one_pixel);
500 break;
501 case GDK_bracketright:
502 case GDK_braceright:
503 pm.rotateHandle(n, which, -1, one_pixel);
504 break;
505 // adjust length
506 case GDK_period:
507 case GDK_greater:
508 pm.scaleHandle(n, which, 1, one_pixel);
509 break;
510 case GDK_comma:
511 case GDK_less:
512 pm.scaleHandle(n, which, -1, one_pixel);
513 break;
514 default:
515 handled = false;
516 break;
517 }
519 if (handled) return true;
520 } while(0);
521 }
524 switch (event->type) {
525 case GDK_KEY_PRESS:
526 switch (key) {
527 case GDK_Insert:
528 case GDK_KP_Insert:
529 // Insert - insert nodes in the middle of selected segments
530 insertNodes();
531 return true;
532 case GDK_i:
533 case GDK_I:
534 if (held_only_shift(event->key)) {
535 // Shift+I - insert nodes (alternate keybinding for Mac keyboards
536 // that don't have the Insert key)
537 insertNodes();
538 return true;
539 }
540 break;
541 case GDK_d:
542 case GDK_D:
543 if (held_only_shift(event->key)) {
544 duplicateNodes();
545 return true;
546 }
547 case GDK_j:
548 case GDK_J:
549 if (held_only_shift(event->key)) {
550 // Shift+J - join nodes
551 joinNodes();
552 return true;
553 }
554 if (held_only_alt(event->key)) {
555 // Alt+J - join segments
556 joinSegments();
557 return true;
558 }
559 break;
560 case GDK_b:
561 case GDK_B:
562 if (held_only_shift(event->key)) {
563 // Shift+B - break nodes
564 breakNodes();
565 return true;
566 }
567 break;
568 case GDK_Delete:
569 case GDK_KP_Delete:
570 case GDK_BackSpace:
571 if (held_shift(event->key)) break;
572 if (held_alt(event->key)) {
573 // Alt+Delete - delete segments
574 deleteSegments();
575 } else {
576 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
577 bool del_preserves_shape = prefs->getBool("/tools/nodes/delete_preserves_shape", true);
578 // pass keep_shape = true when:
579 // a) del preserves shape, and control is not pressed
580 // b) ctrl+del preserves shape (del_preserves_shape is false), and control is pressed
581 // Hence xor
582 deleteNodes(del_preserves_shape ^ held_control(event->key));
583 }
584 return true;
585 case GDK_c:
586 case GDK_C:
587 if (held_only_shift(event->key)) {
588 // Shift+C - make nodes cusp
589 setNodeType(NODE_CUSP);
590 return true;
591 }
592 break;
593 case GDK_s:
594 case GDK_S:
595 if (held_only_shift(event->key)) {
596 // Shift+S - make nodes smooth
597 setNodeType(NODE_SMOOTH);
598 return true;
599 }
600 break;
601 case GDK_a:
602 case GDK_A:
603 if (held_only_shift(event->key)) {
604 // Shift+A - make nodes auto-smooth
605 setNodeType(NODE_AUTO);
606 return true;
607 }
608 break;
609 case GDK_y:
610 case GDK_Y:
611 if (held_only_shift(event->key)) {
612 // Shift+Y - make nodes symmetric
613 setNodeType(NODE_SYMMETRIC);
614 return true;
615 }
616 break;
617 case GDK_r:
618 case GDK_R:
619 if (held_only_shift(event->key)) {
620 // Shift+R - reverse subpaths
621 reverseSubpaths();
622 return true;
623 }
624 break;
625 case GDK_l:
626 case GDK_L:
627 if (held_only_shift(event->key)) {
628 // Shift+L - make segments linear
629 setSegmentType(SEGMENT_STRAIGHT);
630 return true;
631 }
632 case GDK_u:
633 case GDK_U:
634 if (held_only_shift(event->key)) {
635 // Shift+L - make segments curves
636 setSegmentType(SEGMENT_CUBIC_BEZIER);
637 return true;
638 }
639 default:
640 break;
641 }
642 break;
643 case GDK_MOTION_NOTIFY:
644 combine_motion_events(_desktop->canvas, event->motion, 0);
645 for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
646 if (i->second->event(event)) return true;
647 }
648 break;
649 default: break;
650 }
652 return false;
653 }
655 /** Commit changes to XML and add undo stack entry based on the action that was done. Invoked
656 * by sub-manipulators, for example TransformHandleSet and ControlPointSelection. */
657 void MultiPathManipulator::_commit(CommitEvent cps)
658 {
659 gchar const *reason = NULL;
660 gchar const *key = NULL;
661 switch(cps) {
662 case COMMIT_MOUSE_MOVE:
663 reason = _("Move nodes");
664 break;
665 case COMMIT_KEYBOARD_MOVE_X:
666 reason = _("Move nodes horizontally");
667 key = "node:move:x";
668 break;
669 case COMMIT_KEYBOARD_MOVE_Y:
670 reason = _("Move nodes vertically");
671 key = "node:move:y";
672 break;
673 case COMMIT_MOUSE_ROTATE:
674 reason = _("Rotate nodes");
675 break;
676 case COMMIT_KEYBOARD_ROTATE:
677 reason = _("Rotate nodes");
678 key = "node:rotate";
679 break;
680 case COMMIT_MOUSE_SCALE_UNIFORM:
681 reason = _("Scale nodes uniformly");
682 break;
683 case COMMIT_MOUSE_SCALE:
684 reason = _("Scale nodes");
685 break;
686 case COMMIT_KEYBOARD_SCALE_UNIFORM:
687 reason = _("Scale nodes uniformly");
688 key = "node:scale:uniform";
689 break;
690 case COMMIT_KEYBOARD_SCALE_X:
691 reason = _("Scale nodes horizontally");
692 key = "node:scale:x";
693 break;
694 case COMMIT_KEYBOARD_SCALE_Y:
695 reason = _("Scale nodes vertically");
696 key = "node:scale:y";
697 break;
698 case COMMIT_MOUSE_SKEW_X:
699 reason = _("Skew nodes horizontally");
700 key = "node:skew:x";
701 break;
702 case COMMIT_MOUSE_SKEW_Y:
703 reason = _("Skew nodes vertically");
704 key = "node:skew:y";
705 break;
706 case COMMIT_FLIP_X:
707 reason = _("Flip nodes horizontally");
708 break;
709 case COMMIT_FLIP_Y:
710 reason = _("Flip nodes vertically");
711 break;
712 default: return;
713 }
715 _selection.signal_update.emit();
716 invokeForAll(&PathManipulator::writeXML);
717 if (key) {
718 DocumentUndo::maybeDone(sp_desktop_document(_desktop), key, SP_VERB_CONTEXT_NODE, reason);
719 } else {
720 DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
721 }
722 signal_coords_changed.emit();
723 }
725 /** Commits changes to XML and adds undo stack entry. */
726 void MultiPathManipulator::_done(gchar const *reason) {
727 invokeForAll(&PathManipulator::update);
728 invokeForAll(&PathManipulator::writeXML);
729 DocumentUndo::done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
730 signal_coords_changed.emit();
731 }
733 /** Commits changes to XML, adds undo stack entry and removes empty manipulators. */
734 void MultiPathManipulator::_doneWithCleanup(gchar const *reason) {
735 _changed.block();
736 _done(reason);
737 cleanup();
738 _changed.unblock();
739 }
741 /** Get an outline color based on the shape's role (normal, mask, LPE parameter, etc.). */
742 guint32 MultiPathManipulator::_getOutlineColor(ShapeRole role)
743 {
744 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
745 switch(role) {
746 case SHAPE_ROLE_CLIPPING_PATH:
747 return prefs->getColor("/tools/nodes/clipping_path_color", 0x00ff00ff);
748 case SHAPE_ROLE_MASK:
749 return prefs->getColor("/tools/nodes/mask_color", 0x0000ffff);
750 case SHAPE_ROLE_LPE_PARAM:
751 return prefs->getColor("/tools/nodes/lpe_param_color", 0x009000ff);
752 case SHAPE_ROLE_NORMAL:
753 default:
754 return prefs->getColor("/tools/nodes/outline_color", 0xff0000ff);
755 }
756 }
758 } // namespace UI
759 } // namespace Inkscape
761 /*
762 Local Variables:
763 mode:c++
764 c-file-style:"stroustrup"
765 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
766 indent-tabs-mode:nil
767 fill-column:99
768 End:
769 */
770 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :