Code

Pot and Dutch translation update
[inkscape.git] / src / ui / tool / multi-path-manipulator.cpp
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;
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)
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())));
137 MultiPathManipulator::~MultiPathManipulator()
139     _mmap.clear();
142 /** Remove empty manipulators. */
143 void MultiPathManipulator::cleanup()
145     for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) {
146         if (i->second->empty()) _mmap.erase(i++);
147         else ++i;
148     }
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)
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     }
201 void MultiPathManipulator::selectSubpaths()
203     if (_selection.empty()) {
204         _selection.selectAll();
205     } else {
206         invokeForAll(&PathManipulator::selectSubpaths);
207     }
210 void MultiPathManipulator::shiftSelection(int dir)
212     invokeForAll(&PathManipulator::shiftSelection, dir);
215 void MultiPathManipulator::invertSelectionInSubpaths()
217     invokeForAll(&PathManipulator::invertSelectionInSubpaths);
220 void MultiPathManipulator::setNodeType(NodeType type)
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"));
248 void MultiPathManipulator::setSegmentType(SegmentType type)
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     }
259 void MultiPathManipulator::insertNodes()
261     invokeForAll(&PathManipulator::insertNodes);
262     _done(_("Add nodes"));
265 void MultiPathManipulator::duplicateNodes()
267     invokeForAll(&PathManipulator::duplicateNodes);
268     _done(_("Duplicate nodes"));
271 void MultiPathManipulator::joinNodes()
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"));
333 void MultiPathManipulator::breakNodes()
335     if (_selection.empty()) return;
336     invokeForAll(&PathManipulator::breakNodes);
337     _done(_("Break nodes"));
340 void MultiPathManipulator::deleteNodes(bool keep_shape)
342     if (_selection.empty()) return;
343     invokeForAll(&PathManipulator::deleteNodes, keep_shape);
344     _doneWithCleanup(_("Delete nodes"));
347 /** Join selected endpoints to create segments. */
348 void MultiPathManipulator::joinSegments()
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");
373 void MultiPathManipulator::deleteSegments()
375     if (_selection.empty()) return;
376     invokeForAll(&PathManipulator::deleteSegments);
377     _doneWithCleanup("Delete segments");
380 void MultiPathManipulator::alignNodes(Geom::Dim2 d)
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     }
390 void MultiPathManipulator::distributeNodes(Geom::Dim2 d)
392     _selection.distribute(d);
393     if (d == Geom::X) {
394         _done("Distrubute nodes horizontally");
395     } else {
396         _done("Distribute nodes vertically");
397     }
400 void MultiPathManipulator::reverseSubpaths()
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     }
411 void MultiPathManipulator::move(Geom::Point const &delta)
413     _selection.transform(Geom::Translate(delta));
414     _done("Move nodes");
417 void MultiPathManipulator::showOutline(bool show)
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;
426 void MultiPathManipulator::showHandles(bool show)
428     invokeForAll(&PathManipulator::showHandles, show);
429     _show_handles = show;
432 void MultiPathManipulator::showPathDirection(bool show)
434     invokeForAll(&PathManipulator::showPathDirection, show);
435     _show_path_direction = show;
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)
444     invokeForAll(&PathManipulator::setLiveOutline, set);
445     _live_outline = set;
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)
454     invokeForAll(&PathManipulator::setLiveObjects, set);
455     _live_objects = set;
458 void MultiPathManipulator::updateOutlineColors()
460     //for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
461     //    i->second->setOutlineColor(_getOutlineColor(i->first.role));
462     //}
465 bool MultiPathManipulator::event(GdkEvent *event)
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;
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)
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     }
705     
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();
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();
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();
732 /** Get an outline color based on the shape's role (normal, mask, LPE parameter, etc.). */
733 guint32 MultiPathManipulator::_getOutlineColor(ShapeRole role)
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     }
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 :