14#include <unordered_set>
16#include <gdk/gdkkeysyms.h>
17#include <glibmm/i18n.h>
38struct hash_nodelist_iterator
41 return std::hash<NodeList::iterator::pointer>()(&*i);
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;
52void find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
58 Node *
node =
dynamic_cast<Node*
>(i);
61 if (!iter.next() || !iter.prev()) join_iters.insert(iter);
64 if (join_iters.size() < 2)
return;
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) {
77 closest_pair = std::make_pair(*i, *j);
81 pairs.push_back(closest_pair);
82 join_iters.erase(closest_pair.first);
83 join_iters.erase(closest_pair.second);
89bool prepare_join(IterPair &join_iters)
92 if (join_iters.first.next())
93 std::swap(join_iters.first, join_iters.second);
99 if (join_iters.first.next()) {
100 if (join_iters.second.next()) {
103 std::swap(join_iters.first, join_iters.second);
106 if (join_iters.second.next()) {
124 sigc::hide( sigc::hide(
136 std::erase_if(
_mmap, [] (
auto const &i) {
137 return i.second->empty();
148 std::set<ShapeRecord> shapes(s);
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()) {
163 std::shared_ptr<PathManipulator> hold(i->second);
170 _mmap.insert(std::make_pair(sr_new, hold));
179 for (
const auto & r : shapes) {
180 auto lpobj = cast<LivePathEffectObject>(r.object);
181 if (!is<SPPath>(r.object) && !lpobj)
continue;
190 _mmap.insert(std::make_pair(r, newpm));
210 MapType::iterator last_i;
211 SubpathList::iterator last_j;
213 bool anything_found =
false;
214 bool anynode_found =
false;
216 for (MapType::iterator i =
_mmap.begin(); i !=
_mmap.end(); ++i) {
218 for (SubpathList::iterator j = sp.begin(); j != sp.end(); ++j) {
219 anynode_found =
true;
225 anything_found =
true;
227 if (dir == -1)
goto exit_loop;
238 if (!anything_found) {
253 if (++last_k == (*last_j)->end()) {
256 if (last_j == last_i->second->subpathList().end()) {
258 if (last_i ==
_mmap.end()) {
259 last_i =
_mmap.begin();
261 last_j = last_i->second->subpathList().begin();
263 last_k = (*last_j)->begin();
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();
272 last_j = last_i->second->subpathList().end();
275 last_k = (*last_j)->end();
293 bool retract_handles = (type ==
NODE_CUSP);
303 if (retract_handles) {
307 node->front()->retract();
308 node->back()->retract();
313 _done(retract_handles ? _(
"Retract handles") : _(
"Change node type"));
321 _done(_(
"Straighten segments"));
323 _done(_(
"Make segments curves"));
331 _done(_(
"Add nodes"));
337 _done(_(
"Add extremum nodes"));
345 _done(_(
"Add nodes"));
352 _done(_(
"Duplicate nodes"));
360 _done(_(
"Copy nodes"));
372 if (mouseover_node) {
377 for (
auto &
join : joins) {
378 bool same_path = prepare_join(
join);
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();
388 if (
join.first == preserve_pos) {
389 joined_pos =
join.first->position();
391 }
else if (
join.second == preserve_pos) {
392 joined_pos =
join.second->position();
399 join.first->move(joined_pos);
400 Node *joined_node =
join.first.ptr();
401 if (!
join.second->front()->isDegenerate()) {
404 if (!
join.first->back()->isDegenerate()) {
412 sp_first.
splice(sp_first.
end(), sp_second);
430 _done(_(
"Break nodes"),
true);
451 for (
auto &
join : joins) {
452 bool same_path = prepare_join(
join);
460 sp_first.
splice(sp_first.
end(), sp_second);
483 _done(
"Align nodes to a horizontal line");
485 _done(
"Align nodes to a vertical line");
494 _done(
"Distribute nodes horizontally");
496 _done(
"Distribute nodes vertically");
504 _done(
"Reverse subpaths");
507 _done(
"Reverse selected subpaths");
526 _done(
"Scale nodes");
531 for (
auto & i :
_mmap) {
605 auto &pm = n->nodeList().subpathList().pm();
612 if (which != 0)
break;
615 if (which == 0)
break;
621 case GDK_KEY_bracketleft:
622 case GDK_KEY_braceleft:
625 case GDK_KEY_bracketright:
626 case GDK_KEY_braceright:
631 case GDK_KEY_greater:
632 pm.scaleHandle(n, which, 1, one_pixel);
636 pm.scaleHandle(n, which, -1, one_pixel);
650 case GDK_KEY_KP_Insert:
698 case GDK_KEY_KP_Delete:
699 case GDK_KEY_BackSpace:
706 bool del_preserves_shape = prefs->
getBool(
"/tools/nodes/delete_preserves_shape",
true);
801 for (
auto &it :
_mmap) {
802 if (it.second->event(tool,
event)) {
818 gchar
const *reason =
nullptr;
819 gchar
const *
key =
nullptr;
822 reason = _(
"Move nodes");
825 reason = _(
"Move nodes horizontally");
829 reason = _(
"Move nodes vertically");
833 reason = _(
"Rotate nodes");
836 reason = _(
"Rotate nodes");
840 reason = _(
"Scale nodes uniformly");
843 reason = _(
"Scale nodes");
846 reason = _(
"Scale nodes uniformly");
847 key =
"node:scale:uniform";
850 reason = _(
"Scale nodes horizontally");
851 key =
"node:scale:x";
854 reason = _(
"Scale nodes vertically");
855 key =
"node:scale:y";
858 reason = _(
"Skew nodes horizontally");
862 reason = _(
"Skew nodes vertically");
866 reason = _(
"Flip nodes horizontally");
869 reason = _(
"Flip nodes vertically");
894 _done(reason, alert_LPE);
904 return prefs->
getColor(
"/tools/nodes/clipping_path_color",
"#00ff00ff");
906 return prefs->
getColor(
"/tools/nodes/mask_color",
"#0000ffff");
908 return prefs->
getColor(
"/tools/nodes/lpe_param_color",
"#009000ff");
911 return prefs->
getColor(
"/tools/nodes/highlight_color",
"#ff0000ff");;
Store paths to a PathVector.
Two-dimensional point that doubles as a vector.
uint32_t toRGBA(double opacity=1.0) const
Return an sRGB conversion of the color in RGBA int32 format.
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.
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.
SPDesktop *const _desktop
bool rightControl() const
void event(CanvasEvent const &event)
void copySelectedPath(Geom::PathBuilder *builder)
void setItems(std::set< ShapeRecord > const &)
Change the set of items to edit.
void showOutline(bool show)
void showPathDirection(bool show)
void distributeNodes(Geom::Dim2 d)
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 setNodeType(NodeType t)
void deleteNodes(NodeDeleteMode mode)
void updateOutlineColors()
void move(Geom::Point const &delta)
friend class PathManipulator
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 _show_path_direction
void shiftSelection(int dir)
void insertNode(Geom::Point pt)
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 invertSelectionInSubpaths()
void scale(Geom::Point const ¢er, Geom::Point const &scale)
void showHandles(bool show)
void _commit(CommitEvent cps)
Commit changes to XML and add undo stack entry based on the action that was done.
~MultiPathManipulator() override
void setSegmentType(SegmentType t)
MultiPathManipulator(PathSharedData &data)
NodeIterator< value_type > iterator
static NodeList & get(Node *n)
static iterator get_iterator(Node *n)
void splice(iterator pos, NodeList &list)
iterator erase(iterator pos)
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 setLiveOutline(bool set)
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 setLiveObjects(bool set)
void showPathDirection(bool show)
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 insertNode(Geom::Point)
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.
ExtremumType
Type of extremum points to add in PathManipulator::insertNodeAtExtremum.
ControlPointSelection & _selection
virtual NodeType type() const =0
Get the type of the node.
SPDocument * getDocument() const
SPObject is an abstract base class of all of the document nodes at the SVG document level.
SVG <path> implementation.
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).
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)
Angle distance(Angle const &a, Angle const &b)
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.
@ NODE_CUSP
Cusp node - no handle constraints.
@ NODE_SYMMETRIC
Symmetric node - handles must be colinear and of equal length.
@ NODE_AUTO
Auto node - handles adjusted automatically based on neighboring nodes.
@ NODE_SMOOTH
Smooth node - handles must be colinear.
CommitEvent
This is used to provide sensible messages on the undo stack.
@ COMMIT_KEYBOARD_SCALE_Y
@ COMMIT_MOUSE_SCALE_UNIFORM
@ COMMIT_KEYBOARD_SCALE_X
@ COMMIT_KEYBOARD_SCALE_UNIFORM
SegmentType
Types of segments supported in the node tool.
@ SEGMENT_STRAIGHT
Straight linear segment.
@ SEGMENT_CUBIC_BEZIER
Bezier curve with two control points.
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
Path manipulator - a component that edits a single path on-canvas.
Abstract base class for events.
Movement of the mouse pointer.
Geom::Affine edit_transform
Glib::RefPtr< Gtk::Builder > builder