Inkscape
Vector Graphics Editor
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages Concepts
multi-path-manipulator.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * Krzysztof Kosiński <tweenk.pl@gmail.com>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 2009 Authors
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include <unordered_set>
15
16#include <gdk/gdkkeysyms.h>
17#include <glibmm/i18n.h>
18
19#include "desktop.h"
20#include "document-undo.h"
21#include "node.h"
22
24
25#include "object/sp-path.h"
26
27#include "ui/icon-names.h"
32
33namespace Inkscape {
34namespace UI {
35
36namespace {
37
38struct hash_nodelist_iterator
39{
40 std::size_t operator()(NodeList::iterator i) const {
41 return std::hash<NodeList::iterator::pointer>()(&*i);
42 }
43};
44
45typedef std::pair<NodeList::iterator, NodeList::iterator> IterPair;
46typedef std::vector<IterPair> IterPairList;
47typedef std::unordered_set<NodeList::iterator, hash_nodelist_iterator> IterSet;
48typedef std::multimap<double, IterPair> DistanceMap;
49typedef std::pair<double, IterPair> DistanceMapItem;
50
52void find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
53{
54 IterSet join_iters;
55
56 // find all endnodes in selection
57 for (auto i : sel) {
58 Node *node = dynamic_cast<Node*>(i);
59 if (!node) continue;
61 if (!iter.next() || !iter.prev()) join_iters.insert(iter);
62 }
63
64 if (join_iters.size() < 2) return;
65
66 // Below we find the closest pairs. The algorithm is O(N^3).
67 // We can go down to O(N^2 log N) by using O(N^2) memory, by putting all pairs
68 // with their distances in a multimap (not worth it IMO).
69 while (join_iters.size() >= 2) {
70 double closest = DBL_MAX;
71 IterPair closest_pair;
72 for (IterSet::iterator i = join_iters.begin(); i != join_iters.end(); ++i) {
73 for (IterSet::iterator j = join_iters.begin(); j != i; ++j) {
74 double dist = Geom::distance((*i)->position(), (*j)->position());
75 if (dist < closest) {
76 closest = dist;
77 closest_pair = std::make_pair(*i, *j);
78 }
79 }
80 }
81 pairs.push_back(closest_pair);
82 join_iters.erase(closest_pair.first);
83 join_iters.erase(closest_pair.second);
84 }
85}
86
89bool prepare_join(IterPair &join_iters)
90{
91 if (&NodeList::get(join_iters.first) == &NodeList::get(join_iters.second)) {
92 if (join_iters.first.next()) // if first is begin, swap the iterators
93 std::swap(join_iters.first, join_iters.second);
94 return true;
95 }
96
97 NodeList &sp_first = NodeList::get(join_iters.first);
98 NodeList &sp_second = NodeList::get(join_iters.second);
99 if (join_iters.first.next()) { // first is begin
100 if (join_iters.second.next()) { // second is begin
101 sp_first.reverse();
102 } else { // second is end
103 std::swap(join_iters.first, join_iters.second);
104 }
105 } else { // first is end
106 if (join_iters.second.next()) { // second is begin
107 // do nothing
108 } else { // second is end
109 sp_second.reverse();
110 }
111 }
112 return false;
113}
114} // anonymous namespace
115
116
118 : PointManipulator(data.node_data.desktop, *data.node_data.selection)
119 , _path_data(data)
120{
122 sigc::mem_fun(*this, &MultiPathManipulator::_commit));
124 sigc::hide( sigc::hide(
125 signal_coords_changed.make_slot())));
126}
127
132
135{
136 std::erase_if(_mmap, [] (auto const &i) {
137 return i.second->empty();
138 });
139}
140
146void MultiPathManipulator::setItems(std::set<ShapeRecord> const &s)
147{
148 std::set<ShapeRecord> shapes(s);
149
150 // iterate over currently edited items, modifying / removing them as necessary
151 for (MapType::iterator i = _mmap.begin(); i != _mmap.end();) {
152 std::set<ShapeRecord>::iterator si = shapes.find(i->first);
153 if (si == shapes.end()) {
154 // This item is no longer supposed to be edited - remove its manipulator
155 i = _mmap.erase(i);
156 } else {
157 ShapeRecord const &sr = i->first;
158 ShapeRecord const &sr_new = *si;
159 // if the shape record differs, replace the key only and modify other values
160 if (sr.edit_transform != sr_new.edit_transform ||
161 sr.role != sr_new.role)
162 {
163 std::shared_ptr<PathManipulator> hold(i->second);
164 if (sr.edit_transform != sr_new.edit_transform)
165 hold->setControlsTransform(sr_new.edit_transform);
166 if (sr.role != sr_new.role) {
167 //hold->setOutlineColor(_getOutlineColor(sr_new.role));
168 }
169 i = _mmap.erase(i);
170 _mmap.insert(std::make_pair(sr_new, hold));
171 } else {
172 ++i;
173 }
174 shapes.erase(si); // remove the processed record
175 }
176 }
177
178 // add newly selected items
179 for (const auto & r : shapes) {
180 auto lpobj = cast<LivePathEffectObject>(r.object);
181 if (!is<SPPath>(r.object) && !lpobj) continue;
182 std::shared_ptr<PathManipulator> newpm(new PathManipulator(*this, (SPPath*) r.object,
183 r.edit_transform, _getOutlineColor(r.role, r.object).toRGBA(), r.lpe_key));
184 newpm->showHandles(_show_handles);
185 // always show outlines for clips and masks
186 newpm->showOutline(_show_outline || r.role != SHAPE_ROLE_NORMAL);
187 newpm->showPathDirection(_show_path_direction);
188 newpm->setLiveOutline(_live_outline);
189 newpm->setLiveObjects(_live_objects);
190 _mmap.insert(std::make_pair(r, newpm));
191 }
192}
193
202
204{
205 if (empty()) return;
206
207 // 1. find last selected node
208 // 2. select the next node; if the last node or nothing is selected,
209 // select first node
210 MapType::iterator last_i;
211 SubpathList::iterator last_j;
212 NodeList::iterator last_k;
213 bool anything_found = false;
214 bool anynode_found = false;
215
216 for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
217 SubpathList &sp = i->second->subpathList();
218 for (SubpathList::iterator j = sp.begin(); j != sp.end(); ++j) {
219 anynode_found = true;
220 for (NodeList::iterator k = (*j)->begin(); k != (*j)->end(); ++k) {
221 if (k->selected()) {
222 last_i = i;
223 last_j = j;
224 last_k = k;
225 anything_found = true;
226 // when tabbing backwards, we want the first node
227 if (dir == -1) goto exit_loop;
228 }
229 }
230 }
231 }
232 exit_loop:
233
234 // NOTE: we should not assume the _selection contains only nodes
235 // in future it might also contain handles and other types of control points
236 // this is why we use a flag instead in the loop above, instead of calling
237 // selection.empty()
238 if (!anything_found) {
239 // select first / last node
240 // this should never fail because there must be at least 1 non-empty manipulator
241 if (anynode_found) {
242 if (dir == 1) {
243 _selection.insert((*_mmap.begin()->second->subpathList().begin())->begin().ptr());
244 } else {
245 _selection.insert((--(*--(--_mmap.end())->second->subpathList().end())->end()).ptr());
246 }
247 }
248 return;
249 }
250
251 // three levels deep - w00t!
252 if (dir == 1) {
253 if (++last_k == (*last_j)->end()) {
254 // here, last_k points to the node to be selected
255 ++last_j;
256 if (last_j == last_i->second->subpathList().end()) {
257 ++last_i;
258 if (last_i == _mmap.end()) {
259 last_i = _mmap.begin();
260 }
261 last_j = last_i->second->subpathList().begin();
262 }
263 last_k = (*last_j)->begin();
264 }
265 } else {
266 if (!last_k || last_k == (*last_j)->begin()) {
267 if (last_j == last_i->second->subpathList().begin()) {
268 if (last_i == _mmap.begin()) {
269 last_i = _mmap.end();
270 }
271 --last_i;
272 last_j = last_i->second->subpathList().end();
273 }
274 --last_j;
275 last_k = (*last_j)->end();
276 }
277 --last_k;
278 }
280 _selection.insert(last_k.ptr());
281}
282
287
289{
290 if (_selection.empty()) return;
291
292 // When all selected nodes are already cusp, retract their handles
293 bool retract_handles = (type == NODE_CUSP);
294
295 for (auto i : _selection) {
296 Node *node = dynamic_cast<Node*>(i);
297 if (node) {
298 retract_handles &= (node->type() == NODE_CUSP);
299 node->setType(type);
300 }
301 }
302
303 if (retract_handles) {
304 for (auto i : _selection) {
305 Node *node = dynamic_cast<Node*>(i);
306 if (node) {
307 node->front()->retract();
308 node->back()->retract();
309 }
310 }
311 }
312
313 _done(retract_handles ? _("Retract handles") : _("Change node type"));
314}
315
317{
318 if (_selection.empty()) return;
320 if (type == SEGMENT_STRAIGHT) {
321 _done(_("Straighten segments"));
322 } else {
323 _done(_("Make segments curves"));
324 }
325}
326
328{
329 if (_selection.empty()) return;
331 _done(_("Add nodes"));
332}
334{
335 if (_selection.empty()) return;
337 _done(_("Add extremum nodes"));
338}
339
341{
342 // When double clicking to insert nodes, we might not have a selection of nodes (and we don't need one)
343 // so don't check for "_selection.empty()" here, contrary to the other methods above and below this one
345 _done(_("Add nodes"));
346}
347
349{
350 if (_selection.empty()) return;
352 _done(_("Duplicate nodes"));
353}
354
362
364{
365 if (_selection.empty()) return;
367 // Node join has two parts. In the first one we join two subpaths by fusing endpoints
368 // into one. In the second we fuse nodes in each subpath.
369 IterPairList joins;
370 NodeList::iterator preserve_pos;
371 Node *mouseover_node = dynamic_cast<Node*>(ControlPoint::mouseovered_point);
372 if (mouseover_node) {
373 preserve_pos = NodeList::get_iterator(mouseover_node);
374 }
375 find_join_iterators(_selection, joins);
376
377 for (auto & join : joins) {
378 bool same_path = prepare_join(join);
379 NodeList &sp_first = NodeList::get(join.first);
380 NodeList &sp_second = NodeList::get(join.second);
381 join.first->setType(NODE_CUSP, false);
382
383 Geom::Point joined_pos, pos_handle_front, pos_handle_back;
384 pos_handle_front = join.second->front()->position();
385 pos_handle_back = join.first->back()->position();
386
387 // When we encounter the mouseover node, we unset the iterator - it will be invalidated
388 if (join.first == preserve_pos) {
389 joined_pos = join.first->position();
390 preserve_pos = NodeList::iterator();
391 } else if (join.second == preserve_pos) {
392 joined_pos = join.second->position();
393 preserve_pos = NodeList::iterator();
394 } else {
395 joined_pos = Geom::middle_point(join.first->position(), join.second->position());
396 }
397
398 // if the handles aren't degenerate, don't move them
399 join.first->move(joined_pos);
400 Node *joined_node = join.first.ptr();
401 if (!join.second->front()->isDegenerate()) {
402 joined_node->front()->setPosition(pos_handle_front);
403 }
404 if (!join.first->back()->isDegenerate()) {
405 joined_node->back()->setPosition(pos_handle_back);
406 }
407 sp_second.erase(join.second);
408
409 if (same_path) {
410 sp_first.setClosed(true);
411 } else {
412 sp_first.splice(sp_first.end(), sp_second);
413 sp_second.kill();
414 }
415 _selection.insert(join.first.ptr());
416 }
417
418 if (joins.empty()) {
419 // Second part replaces contiguous selections of nodes with single nodes
421 }
422
423 _doneWithCleanup(_("Join nodes"), true);
424}
425
427{
428 if (_selection.empty()) return;
430 _done(_("Break nodes"), true);
431}
432
436
443
446{
447 if (_selection.empty()) return;
448 IterPairList joins;
449 find_join_iterators(_selection, joins);
450
451 for (auto & join : joins) {
452 bool same_path = prepare_join(join);
453 NodeList &sp_first = NodeList::get(join.first);
454 NodeList &sp_second = NodeList::get(join.second);
455 join.first->setType(NODE_CUSP, false);
456 join.second->setType(NODE_CUSP, false);
457 if (same_path) {
458 sp_first.setClosed(true);
459 } else {
460 sp_first.splice(sp_first.end(), sp_second);
461 sp_second.kill();
462 }
463 }
464
465 if (joins.empty()) {
467 }
468 _doneWithCleanup("Join segments", true);
469}
470
472{
473 if (_selection.empty()) return;
475 _doneWithCleanup("Delete segments", true);
476}
477
479{
480 if (_selection.empty()) return;
481 _selection.align(d, target);
482 if (d == Geom::X) {
483 _done("Align nodes to a horizontal line");
484 } else {
485 _done("Align nodes to a vertical line");
486 }
487}
488
490{
491 if (_selection.empty()) return;
493 if (d == Geom::X) {
494 _done("Distribute nodes horizontally");
495 } else {
496 _done("Distribute nodes vertically");
497 }
498}
499
501{
502 if (_selection.empty()) {
504 _done("Reverse subpaths");
505 } else {
507 _done("Reverse selected subpaths");
508 }
509}
510
512{
513 if (_selection.empty()) return;
515 _done("Move nodes");
516}
517
518void MultiPathManipulator::scale(Geom::Point const &center, Geom::Point const &scale)
519{
520 if (_selection.empty()) return;
521
522 Geom::Translate const n2d(-center);
523 Geom::Translate const d2n(center);
525
526 _done("Scale nodes");
527}
528
530{
531 for (auto & i : _mmap) {
532 // always show outlines for clipping paths and masks
533 i.second->showOutline(show || i.first.role != SHAPE_ROLE_NORMAL);
534 }
535 _show_outline = show;
536}
537
543
549
561
573
575{
576 //for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
577 // i->second->setOutlineColor(_getOutlineColor(i->first.role));
578 //}
579}
580
585
590
592{
594 unsigned key = 0;
595 if (event.type() == EventType::KEY_PRESS) {
596 key = static_cast<KeyPressEvent const &>(event).keyval;
597 }
598
599 // Single handle adjustments go here.
600 if (_selection.size() == 1 && event.type() == EventType::KEY_PRESS) {
601 do {
602 auto n = dynamic_cast<Node*>(*_selection.begin());
603 if (!n) break;
604
605 auto &pm = n->nodeList().subpathList().pm();
606
607 int which = 0;
609 which = 1;
610 }
612 if (which != 0) break; // ambiguous
613 which = -1;
614 }
615 if (which == 0) break; // no handle chosen
616 bool one_pixel = _tracker.leftAlt() || _tracker.rightAlt();
617
618 switch (key) {
619 // single handle functions
620 // rotation
621 case GDK_KEY_bracketleft:
622 case GDK_KEY_braceleft:
623 pm.rotateHandle(n, which, -_desktop->yaxisdir(), one_pixel);
624 return true;
625 case GDK_KEY_bracketright:
626 case GDK_KEY_braceright:
627 pm.rotateHandle(n, which, _desktop->yaxisdir(), one_pixel);
628 return true;
629 // adjust length
630 case GDK_KEY_period:
631 case GDK_KEY_greater:
632 pm.scaleHandle(n, which, 1, one_pixel);
633 return true;
634 case GDK_KEY_comma:
635 case GDK_KEY_less:
636 pm.scaleHandle(n, which, -1, one_pixel);
637 return true;
638 default:
639 break;
640 }
641 } while (false);
642 }
643
644 bool ret = false;
645
647 [&] (KeyPressEvent const &event) {
648 switch (key) {
649 case GDK_KEY_Insert:
650 case GDK_KEY_KP_Insert:
651 // Insert - insert nodes in the middle of selected segments
652 insertNodes();
653 ret = true;
654 return;
655 case GDK_KEY_i:
656 case GDK_KEY_I:
657 if (held_only_shift(event)) {
658 // Shift+I - insert nodes (alternate keybinding for Mac keyboards
659 // that don't have the Insert key)
660 insertNodes();
661 ret = true;
662 return;
663 }
664 break;
665 case GDK_KEY_d:
666 case GDK_KEY_D:
667 if (held_only_shift(event)) {
669 ret = true;
670 return;
671 }
672 break;
673 case GDK_KEY_j:
674 case GDK_KEY_J:
675 if (held_only_shift(event)) {
676 // Shift+J - join nodes
677 joinNodes();
678 ret = true;
679 return;
680 }
681 if (held_only_alt(event)) {
682 // Alt+J - join segments
683 joinSegments();
684 ret = true;
685 return;
686 }
687 break;
688 case GDK_KEY_b:
689 case GDK_KEY_B:
690 if (held_only_shift(event)) {
691 // Shift+B - break nodes
692 breakNodes();
693 ret = true;
694 return;
695 }
696 break;
697 case GDK_KEY_Delete:
698 case GDK_KEY_KP_Delete:
699 case GDK_KEY_BackSpace:
700 if (held_shift(event)) break;
701 if (held_alt(event)) {
702 // Alt+Delete - delete segments
704 } else {
706 bool del_preserves_shape = prefs->getBool("/tools/nodes/delete_preserves_shape", true);
707 //MK: how can multi-path-manipulator know it is dealing with a bspline if it's checking tool mode???
708 /*
709 // pass keep_shape = true when:
710 // a) del preserves shape, and control is not pressed
711 // b) ctrl+del preserves shape (del_preserves_shape is false), and control is pressed
712 // Hence xor
713 guint mode = prefs->getInt("/tools/freehand/pen/freehand-mode", 0);
714 //if the trace is bspline ( mode 2)
715 if(mode==2){
716 // is this correct ?
717 if(del_preserves_shape ^ held_control(event)){
718 deleteNodes(false);
719 } else {
720 deleteNodes(true);
721 }
722 } else {
723 */
724 auto mode =
729
730 // Delete any selected gradient nodes as well
732 }
733 ret = true;
734 return;
735 case GDK_KEY_c:
736 case GDK_KEY_C:
737 if (held_only_shift(event)) {
738 // Shift+C - make nodes cusp
740 ret = true;
741 return;
742 }
743 break;
744 case GDK_KEY_s:
745 case GDK_KEY_S:
746 if (held_only_shift(event)) {
747 // Shift+S - make nodes smooth
749 ret = true;
750 return;
751 }
752 break;
753 case GDK_KEY_a:
754 case GDK_KEY_A:
755 if (held_only_shift(event)) {
756 // Shift+A - make nodes auto-smooth
758 ret = true;
759 return;
760 }
761 break;
762 case GDK_KEY_y:
763 case GDK_KEY_Y:
764 if (held_only_shift(event)) {
765 // Shift+Y - make nodes symmetric
767 ret = true;
768 return;
769 }
770 break;
771 case GDK_KEY_r:
772 case GDK_KEY_R:
773 if (held_only_shift(event)) {
774 // Shift+R - reverse subpaths
776 ret = true;
777 return;
778 }
779 break;
780 case GDK_KEY_l:
781 case GDK_KEY_L:
782 if (held_only_shift(event)) {
783 // Shift+L - make segments linear
785 ret = true;
786 return;
787 }
788 case GDK_KEY_u:
789 case GDK_KEY_U:
790 if (held_only_shift(event)) {
791 // Shift+U - make segments curves
793 ret = true;
794 return;
795 }
796 default:
797 break;
798 }
799 },
800 [&] (MotionEvent const &event) {
801 for (auto &it : _mmap) {
802 if (it.second->event(tool, event)) {
803 ret = true;
804 return;
805 }
806 }
807 },
808 [&] (CanvasEvent const &event) {}
809 );
810
811 return ret;
812}
813
817{
818 gchar const *reason = nullptr;
819 gchar const *key = nullptr;
820 switch(cps) {
822 reason = _("Move nodes");
823 break;
825 reason = _("Move nodes horizontally");
826 key = "node:move:x";
827 break;
829 reason = _("Move nodes vertically");
830 key = "node:move:y";
831 break;
833 reason = _("Rotate nodes");
834 break;
836 reason = _("Rotate nodes");
837 key = "node:rotate";
838 break;
840 reason = _("Scale nodes uniformly");
841 break;
843 reason = _("Scale nodes");
844 break;
846 reason = _("Scale nodes uniformly");
847 key = "node:scale:uniform";
848 break;
850 reason = _("Scale nodes horizontally");
851 key = "node:scale:x";
852 break;
854 reason = _("Scale nodes vertically");
855 key = "node:scale:y";
856 break;
858 reason = _("Skew nodes horizontally");
859 key = "node:skew:x";
860 break;
862 reason = _("Skew nodes vertically");
863 key = "node:skew:y";
864 break;
865 case COMMIT_FLIP_X:
866 reason = _("Flip nodes horizontally");
867 break;
868 case COMMIT_FLIP_Y:
869 reason = _("Flip nodes vertically");
870 break;
871 default: return;
872 }
873
876 if (key) {
877 DocumentUndo::maybeDone(_desktop->getDocument(), key, reason, INKSCAPE_ICON("tool-node-editor"));
878 } else {
879 DocumentUndo::done(_desktop->getDocument(), reason, INKSCAPE_ICON("tool-node-editor"));
880 }
882}
883
885void MultiPathManipulator::_done(gchar const *reason, bool alert_LPE) {
888 DocumentUndo::done(_desktop->getDocument(), reason, INKSCAPE_ICON("tool-node-editor"));
890}
891
893void MultiPathManipulator::_doneWithCleanup(char const *reason, bool alert_LPE) {
894 _done(reason, alert_LPE);
895 cleanup();
896}
897
900{
902 switch(role) {
904 return prefs->getColor("/tools/nodes/clipping_path_color", "#00ff00ff");
905 case SHAPE_ROLE_MASK:
906 return prefs->getColor("/tools/nodes/mask_color", "#0000ffff");
908 return prefs->getColor("/tools/nodes/lpe_param_color", "#009000ff");
910 default:
911 return prefs->getColor("/tools/nodes/highlight_color", "#ff0000ff");;
912 }
913}
914
915} // namespace UI
916} // namespace Inkscape
917
918/*
919 Local Variables:
920 mode:c++
921 c-file-style:"stroustrup"
922 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
923 indent-tabs-mode:nil
924 fill-column:99
925 End:
926*/
927// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Store paths to a PathVector.
Definition path-sink.h:226
Two-dimensional point that doubles as a vector.
Definition point.h:66
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
uint32_t toRGBA(double opacity=1.0) const
Return an sRGB conversion of the color in RGBA int32 format.
Definition color.cpp:111
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
Colors::Color getColor(Glib::ustring const &pref_path, std::string const &def="black")
std::pair< iterator, bool > insert(const value_type &x, bool notify=true, bool to_update=true)
Add a control point to the selection.
sigc::signal< void()> signal_update
Fires when the display needs to be updated to reflect changes.
sigc::signal< void(std::vector< SelectableControlPoint * >, bool)> signal_selection_changed
void align(Geom::Dim2 d, AlignTargetNode target=AlignTargetNode::MID_NODE)
Align control points on the specified axis.
void transform(Geom::Affine const &m)
Transform all selected control points by the given affine transformation.
sigc::signal< void(CommitEvent)> signal_commit
Fires when a change that needs to be committed to XML happens.
void clear()
Remove all points from the selection, making it empty.
void selectAll()
Select all points that this selection can contain.
void distribute(Geom::Dim2 d)
Equdistantly distribute control points by moving them in the specified dimension.
static ControlPoint * mouseovered_point
Holds the currently mouseovered control point.
void setPosition(Geom::Point const &p) override
Relocate the control point without side effects.
Definition node.cpp:292
SPDesktop *const _desktop
Definition manipulator.h:43
void event(CanvasEvent const &event)
void copySelectedPath(Geom::PathBuilder *builder)
void setItems(std::set< ShapeRecord > const &)
Change the set of items to edit.
void _done(gchar const *reason, bool alert_LPE=true)
Commits changes to XML and adds undo stack entry.
sigc::signal< void()> signal_coords_changed
Emitted whenever the coordinates shown in the status bar need updating.
void setLiveObjects(bool set)
Set live object update status.
void joinSegments()
Join selected endpoints to create segments.
void setLiveOutline(bool set)
Set live outline update status.
void invokeForAll(R(PathManipulator::*method)())
void alignNodes(Geom::Dim2 d, AlignTargetNode target=AlignTargetNode::MID_NODE)
void cleanup()
Remove empty manipulators.
void insertNodesAtExtrema(ExtremumType extremum)
Colors::Color _getOutlineColor(ShapeRole role, SPObject *object)
Get an outline color based on the shape's role (normal, mask, LPE parameter, etc.).
bool event(Inkscape::UI::Tools::ToolBase *tool, CanvasEvent const &event) override
Handle input event. Returns true if handled.
void _doneWithCleanup(gchar const *reason, bool alert_LPE=false)
Commits changes to XML, adds undo stack entry and removes empty manipulators.
void scale(Geom::Point const &center, Geom::Point const &scale)
void _commit(CommitEvent cps)
Commit changes to XML and add undo stack entry based on the action that was done.
NodeIterator< value_type > iterator
Definition node.h:343
static NodeList & get(Node *n)
Definition node.cpp:1917
static iterator get_iterator(Node *n)
Definition node.h:422
void splice(iterator pos, NodeList &list)
Definition node.cpp:1796
iterator end()
Definition node.h:359
iterator erase(iterator pos)
Definition node.cpp:1892
void setClosed(bool c)
Definition node.h:376
Handle * front()
Definition node.h:164
Handle * back()
Definition node.h:165
void update(bool alert_LPE=false)
Update the display and the outline of the path.
void setSegmentType(SegmentType)
Make selected segments curves / lines.
void hideDragPoint()
Hide the curve drag point until the next motion event.
void insertNodeAtExtremum(ExtremumType extremum)
Insert a new node at the extremum of the selected segments.
void breakNodes()
Break the subpath at selected nodes.
void reverseSubpaths(bool selected_only)
Reverse subpaths of the path.
void showHandles(bool show)
Set the visibility of handles.
void weldNodes(NodeList::iterator preserve_pos=NodeList::iterator())
Replace contiguous selections of nodes in each subpath with one node.
void selectSubpaths()
Select all nodes in subpaths that have something selected.
void deleteSegments()
Removes selected segments.
void invertSelectionInSubpaths()
Invert selection in the selected subpaths.
void duplicateNodes()
Insert new nodes exactly at the positions of selected nodes while preserving shape.
void deleteNodes(NodeDeleteMode mode)
Delete selected nodes in the path, optionally substituting deleted segments with bezier curves in a w...
void insertNodes()
Insert a new node in the middle of each selected segment.
void weldSegments()
Remove nodes in the middle of selected segments.
void writeXML()
Store the changes to the path in XML.
void copySelectedPath(Geom::PathBuilder *builder)
Copy the selected nodes using the PathBuilder.
Tool component that edits something on the canvas using selectable control points.
Definition manipulator.h:53
ExtremumType
Type of extremum points to add in PathManipulator::insertNodeAtExtremum.
Definition manipulator.h:62
ControlPointSelection & _selection
Definition manipulator.h:70
List of node lists.
Definition node.h:442
Base class for Event processors.
Definition tool-base.h:107
bool deleteSelectedDrag(bool just_one)
Delete a selected GrDrag point.
virtual NodeType type() const =0
Get the type of the node.
SPDocument * getDocument() const
Definition desktop.h:189
double yaxisdir() const
Definition desktop.h:426
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SVG <path> implementation.
Definition sp-path.h:29
Control point selection - stores a set of control points and applies transformations to them.
Editable view implementation.
TODO: insert short description here.
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
Inkscape::XML::Node * node
Multi path manipulator - a tool component that edits multiple paths at once.
double dist(const Point &a, const Point &b)
Definition geometry.cpp:310
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
Point middle_point(LineSegment const &_segment)
ShapeRole
Role of the shape in the drawing - affects outline display and color.
@ SHAPE_ROLE_CLIPPING_PATH
NodeType
Types of nodes supported in the node tool.
Definition node-types.h:20
@ NODE_CUSP
Cusp node - no handle constraints.
Definition node-types.h:21
@ NODE_SYMMETRIC
Symmetric node - handles must be colinear and of equal length.
Definition node-types.h:24
@ NODE_AUTO
Auto node - handles adjusted automatically based on neighboring nodes.
Definition node-types.h:23
@ NODE_SMOOTH
Smooth node - handles must be colinear.
Definition node-types.h:22
CommitEvent
This is used to provide sensible messages on the undo stack.
@ COMMIT_MOUSE_SCALE_UNIFORM
@ COMMIT_KEYBOARD_SCALE_UNIFORM
SegmentType
Types of segments supported in the node tool.
Definition node-types.h:30
@ SEGMENT_STRAIGHT
Straight linear segment.
Definition node-types.h:31
@ SEGMENT_CUBIC_BEZIER
Bezier curve with two control points.
Definition node-types.h:32
Helper class to stream background task notifications as a series of messages.
bool held_ctrl(CanvasEvent const &event)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
static Glib::ustring join(std::vector< Glib::ustring > const &accels, char const separator)
bool held_shift(CanvasEvent const &event)
bool held_alt(CanvasEvent const &event)
bool held_only_shift(CanvasEvent const &event)
bool held_only_alt(CanvasEvent const &event)
static cairo_user_data_key_t key
int mode
Path manipulator - a component that edits a single path on-canvas.
static const Point data[]
Abstract base class for events.
A key has been pressed.
Movement of the mouse pointer.
int delta
SPDesktop * desktop
Glib::RefPtr< Gtk::Builder > builder