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