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);
455 for (
auto &
join : joins) {
456 bool same_path = prepare_join(
join);
464 sp_first.
splice(sp_first.
end(), sp_second);
487 _done(
"Align nodes to a horizontal line");
489 _done(
"Align nodes to a vertical line");
498 _done(
"Distribute nodes horizontally");
500 _done(
"Distribute nodes vertically");
508 _done(
"Reverse subpaths");
511 _done(
"Reverse selected subpaths");
530 _done(
"Scale nodes");
535 for (
auto & i :
_mmap) {
609 auto &pm = n->nodeList().subpathList().pm();
616 if (which != 0)
break;
619 if (which == 0)
break;
625 case GDK_KEY_bracketleft:
626 case GDK_KEY_braceleft:
629 case GDK_KEY_bracketright:
630 case GDK_KEY_braceright:
635 case GDK_KEY_greater:
636 pm.scaleHandle(n, which, 1, one_pixel);
640 pm.scaleHandle(n, which, -1, one_pixel);
656 case GDK_KEY_KP_Insert:
765 case GDK_KEY_KP_Delete:
766 case GDK_KEY_BackSpace:
787 for (
auto &it :
_mmap) {
788 if (it.second->event(tool,
event)) {
804 gchar
const *reason =
nullptr;
805 gchar
const *
key =
nullptr;
808 reason = _(
"Move nodes");
811 reason = _(
"Move nodes horizontally");
815 reason = _(
"Move nodes vertically");
819 reason = _(
"Rotate nodes");
822 reason = _(
"Rotate nodes");
826 reason = _(
"Scale nodes uniformly");
829 reason = _(
"Scale nodes");
832 reason = _(
"Scale nodes uniformly");
833 key =
"node:scale:uniform";
836 reason = _(
"Scale nodes horizontally");
837 key =
"node:scale:x";
840 reason = _(
"Scale nodes vertically");
841 key =
"node:scale:y";
844 reason = _(
"Skew nodes horizontally");
848 reason = _(
"Skew nodes vertically");
852 reason = _(
"Flip nodes horizontally");
855 reason = _(
"Flip nodes vertically");
880 _done(reason, alert_LPE);
890 return prefs->
getColor(
"/tools/nodes/clipping_path_color",
"#00ff00ff");
892 return prefs->
getColor(
"/tools/nodes/mask_color",
"#0000ffff");
894 return prefs->
getColor(
"/tools/nodes/lpe_param_color",
"#009000ff");
897 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.
static Preferences * get()
Access the singleton Preferences object.
Colors::Color getColor(Glib::ustring const &pref_path, std::string const &def="black")
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
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 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.
void deleteNodes()
Delete nodes, use the preference to decide which mode to use.
~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 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 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 mod_alt(unsigned modifiers)
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 mod_ctrl(unsigned modifiers)
bool mod_shift_only(unsigned modifiers)
bool mod_shift(unsigned modifiers)
bool mod_alt_only(unsigned modifiers)
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