From 7d1ba76a35db3a2f1ad7adcdd074400d5da2f37d Mon Sep 17 00:00:00 2001 From: Jabier Arraiza Date: Tue, 24 Nov 2020 22:47:54 +0100 Subject: [PATCH 1/3] Add connectors LPE This new LPE work in two flawors: 1: Group mode. Use Martin Owens work adding custom knot targets to groups, this allow Add and remove targets using LPE knots. By the way it fixes a issue on apply LPE to clones from symbols, that not work well, now add to them clone LPE and allow to add more LPE to the "symbol" for example this one. 2: Path mode. This work ony in paths defined as inkscape connector only, this LPE mode add a gap between crossings with other connector lines, split on crossing for use middle markers and allow a gap at start/end Also allow link style to whole connectors optionaly, and add a paremeter to respect top uncrossed What I need to do also: I add a system on connector lines to detect crossings using AVOID methods only Also I wrap AVOID router (one per document handling all connectors) into a tiny class to allow have a reference to the non AVOID related to update all connector network when I need Also added option to keep last used settings on tool Finaly I fix a important bug (maybe can have a better deep fix) on drawing.cpp that become some times the canvas has a non transformed affine. This become in randonly extra scale of items on node and selet tool, also in text somentimes, and with LPE. --- po/POTFILES.src.in | 2 + .../Tango/scalable/actions/lpe-connector.svg | 152 ++++ .../scalable/actions/lpe-connector.svg | 152 ++++ .../actions/lpe-connector-symbolic.svg | 152 ++++ .../actions/lpe-connector-symbolic.svg.svg | 150 ++++ .../actions/lpe-connector-symbolic.svg | 157 +++++ src/conn-avoid-ref.cpp | 31 +- src/display/drawing.cpp | 12 +- src/document.cpp | 127 +++- src/document.h | 10 +- src/live_effects/CMakeLists.txt | 4 + src/live_effects/effect-enum.h | 7 +- src/live_effects/effect.cpp | 86 ++- src/live_effects/effect.h | 10 +- src/live_effects/lpe-clone-original.cpp | 159 +++-- src/live_effects/lpe-clone-original.h | 1 + src/live_effects/lpe-connector.cpp | 331 +++++++++ src/live_effects/lpe-connector.h | 57 ++ .../parameter/connectorpointarray.cpp | 159 +++++ .../parameter/connectorpointarray.h | 70 ++ src/object/CMakeLists.txt | 2 + src/object/object-set.h | 3 +- src/object/sp-conn-end-pair.cpp | 62 +- src/object/sp-conn-end-pair.h | 14 +- src/object/sp-conn-end.cpp | 40 +- src/object/sp-conn-end.h | 2 +- src/object/sp-conn-router.cpp | 136 ++++ src/object/sp-conn-router.h | 63 ++ src/object/sp-lpe-item.cpp | 13 +- src/object/sp-shape.cpp | 11 + src/object/sp-shape.h | 1 + src/preferences-skeleton.h | 2 +- src/selection-chemistry.cpp | 29 +- src/ui/dialog/inkscape-preferences.cpp | 3 +- src/ui/dialog/livepatheffect-add.cpp | 6 + src/ui/dialog/livepatheffect-editor.cpp | 30 +- src/ui/dialog/livepatheffect-editor.cpp.orig | 650 ++++++++++++++++++ src/ui/knot/knot-holder.h | 2 + src/ui/tools/connector-tool.cpp | 46 +- 39 files changed, 2727 insertions(+), 217 deletions(-) create mode 100644 share/icons/Tango/scalable/actions/lpe-connector.svg create mode 100644 share/icons/hicolor/scalable/actions/lpe-connector.svg create mode 100644 share/icons/hicolor/symbolic/actions/lpe-connector-symbolic.svg create mode 100644 share/icons/hicolor/symbolic/actions/lpe-connector-symbolic.svg.svg create mode 100644 share/icons/multicolor/symbolic/actions/lpe-connector-symbolic.svg create mode 100644 src/live_effects/lpe-connector.cpp create mode 100644 src/live_effects/lpe-connector.h create mode 100644 src/live_effects/parameter/connectorpointarray.cpp create mode 100644 src/live_effects/parameter/connectorpointarray.h create mode 100644 src/object/sp-conn-router.cpp create mode 100644 src/object/sp-conn-router.h create mode 100644 src/ui/dialog/livepatheffect-editor.cpp.orig diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index cd486f3ce1..f228e32743 100644 --- a/po/POTFILES.src.in +++ b/po/POTFILES.src.in @@ -126,6 +126,7 @@ ../src/live_effects/lpe-bspline.cpp ../src/live_effects/lpe-circle_with_radius.cpp ../src/live_effects/lpe-clone-original.cpp +../src/live_effects/lpe-connector.cpp ../src/live_effects/lpe-constructgrid.cpp ../src/live_effects/lpe-copy_rotate.cpp ../src/live_effects/lpe-curvestitch.cpp @@ -169,6 +170,7 @@ ../src/live_effects/lpe-vonkoch.cpp ../src/live_effects/parameter/bool.cpp ../src/live_effects/parameter/colorpicker.cpp +../src/live_effects/parameter/connectorpointarray.cpp ../src/live_effects/parameter/enum.h ../src/live_effects/parameter/fontbutton.cpp ../src/live_effects/parameter/item.cpp diff --git a/share/icons/Tango/scalable/actions/lpe-connector.svg b/share/icons/Tango/scalable/actions/lpe-connector.svg new file mode 100644 index 0000000000..6590154fc8 --- /dev/null +++ b/share/icons/Tango/scalable/actions/lpe-connector.svg @@ -0,0 +1,152 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/lpe-connector.svg b/share/icons/hicolor/scalable/actions/lpe-connector.svg new file mode 100644 index 0000000000..6590154fc8 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/lpe-connector.svg @@ -0,0 +1,152 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/symbolic/actions/lpe-connector-symbolic.svg b/share/icons/hicolor/symbolic/actions/lpe-connector-symbolic.svg new file mode 100644 index 0000000000..33d1447639 --- /dev/null +++ b/share/icons/hicolor/symbolic/actions/lpe-connector-symbolic.svg @@ -0,0 +1,152 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/symbolic/actions/lpe-connector-symbolic.svg.svg b/share/icons/hicolor/symbolic/actions/lpe-connector-symbolic.svg.svg new file mode 100644 index 0000000000..6ebae50c47 --- /dev/null +++ b/share/icons/hicolor/symbolic/actions/lpe-connector-symbolic.svg.svg @@ -0,0 +1,150 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/multicolor/symbolic/actions/lpe-connector-symbolic.svg b/share/icons/multicolor/symbolic/actions/lpe-connector-symbolic.svg new file mode 100644 index 0000000000..e51a258331 --- /dev/null +++ b/share/icons/multicolor/symbolic/actions/lpe-connector-symbolic.svg @@ -0,0 +1,157 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/conn-avoid-ref.cpp b/src/conn-avoid-ref.cpp index ec64cfa8fb..701a1a3758 100644 --- a/src/conn-avoid-ref.cpp +++ b/src/conn-avoid-ref.cpp @@ -11,31 +11,26 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include "conn-avoid-ref.h" #include -#include #include +#include #include "2geom/convex-hull.h" #include "2geom/line.h" - -#include "conn-avoid-ref.h" +#include "3rdparty/adaptagrams/libavoid/router.h" +#include "3rdparty/adaptagrams/libavoid/shape.h" #include "desktop.h" +#include "display/curve.h" #include "document-undo.h" #include "document.h" #include "inkscape.h" -#include "verbs.h" - -#include "display/curve.h" - -#include "3rdparty/adaptagrams/libavoid/router.h" -#include "3rdparty/adaptagrams/libavoid/shape.h" - +#include "object/sp-conn-router.h" #include "object/sp-namedview.h" #include "object/sp-shape.h" - #include "svg/stringstream.h" - +#include "verbs.h" #include "xml/node.h" using Inkscape::DocumentUndo; @@ -61,10 +56,10 @@ SPAvoidRef::~SPAvoidRef() // If the document is being destroyed then the router instance // and the ShapeRefs will have been destroyed with it. - Router *router = item->document->getRouter(); + SPConnRouter *router = item->document->getRouter(); if (shapeRef && router) { - router->deleteShape(shapeRef); + router->getAvoidRouter()->deleteShape(shapeRef); } shapeRef = nullptr; } @@ -103,7 +98,7 @@ void SPAvoidRef::handleSettingChange() } setting = new_setting; - Router *router = item->document->getRouter(); + Router *router = item->document->getRouter()->getAvoidRouter(); _transformed_connection.disconnect(); if (new_setting) { @@ -135,7 +130,7 @@ std::vector SPAvoidRef::getAttachedShapes(const unsigned int type) Avoid::IntList shapes; GQuark shapeId = g_quark_from_string(item->getId()); - item->document->getRouter()->attachedShapes(shapes, shapeId, type); + item->document->getRouter()->getAvoidRouter()->attachedShapes(shapes, shapeId, type); Avoid::IntList::iterator finish = shapes.end(); for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) { @@ -159,7 +154,7 @@ std::vector SPAvoidRef::getAttachedConnectors(const unsigned int type) Avoid::IntList conns; GQuark shapeId = g_quark_from_string(item->getId()); - item->document->getRouter()->attachedConns(conns, shapeId, type); + item->document->getRouter()->getAvoidRouter()->attachedConns(conns, shapeId, type); Avoid::IntList::iterator finish = conns.end(); for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) { @@ -364,7 +359,7 @@ void avoid_item_move(Geom::Affine const */*mp*/, SPItem *moved_item) Avoid::ShapeRef *shapeRef = moved_item->getAvoidRef().shapeRef; g_assert(shapeRef); - Router *router = moved_item->document->getRouter(); + Router *router = moved_item->document->getRouter()->getAvoidRouter(); Avoid::Polygon poly = avoid_item_poly(moved_item); if (!poly.empty()) { router->moveShape(shapeRef, poly); diff --git a/src/display/drawing.cpp b/src/display/drawing.cpp index bf203c8985..6f955e6d8e 100644 --- a/src/display/drawing.cpp +++ b/src/display/drawing.cpp @@ -11,9 +11,11 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include "display/drawing.h" + #include -#include "display/drawing.h" +#include "display/control/canvas-item-drawing.h" #include "nr-filter-gaussian.h" #include "nr-filter-types.h" @@ -165,7 +167,13 @@ void Drawing::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset) { if (_root) { - _root->update(area, ctx, flags, reset); + // TODO fix why UpdateContext change on translation + // This change fix it but maybe need a deep fix + if (_canvas_item_drawing) { + _root->update(area, _canvas_item_drawing->get_context(), flags, reset); + } else { + _root->update(area, ctx, flags, reset); + } } if ((flags & DrawingItem::STATE_CACHE) || (flags & DrawingItem::STATE_ALL)) { // process the updated cache scores diff --git a/src/document.cpp b/src/document.cpp index 5a9548e953..ad9b08bbbd 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -37,43 +37,36 @@ #define noSP_DOCUMENT_DEBUG_IDLE #define noSP_DOCUMENT_DEBUG_UNDO -#include -#include -#include - #include <2geom/transforms.h> +#include +#include +#include +#include "3rdparty/adaptagrams/libavoid/router.h" +#include "3rdparty/libcroco/cr-parser.h" +#include "3rdparty/libcroco/cr-sel-eng.h" +#include "3rdparty/libcroco/cr-selector.h" #include "desktop.h" -#include "io/dir-util.h" +#include "display/drawing.h" #include "document-undo.h" #include "file.h" #include "id-clash.h" #include "inkscape-version.h" -#include "inkscape.h" #include "inkscape-window.h" -#include "profile-manager.h" -#include "rdf.h" - -#include "actions/actions-canvas-snapping.h" - -#include "display/drawing.h" - -#include "3rdparty/adaptagrams/libavoid/router.h" - -#include "3rdparty/libcroco/cr-parser.h" -#include "3rdparty/libcroco/cr-sel-eng.h" -#include "3rdparty/libcroco/cr-selector.h" - +#include "inkscape.h" +#include "io/dir-util.h" #include "live_effects/lpeobject.h" #include "object/persp3d.h" +#include "object/sp-conn-router.h" #include "object/sp-defs.h" #include "object/sp-factory.h" #include "object/sp-namedview.h" #include "object/sp-root.h" #include "object/sp-symbol.h" - +#include "profile-manager.h" +#include "rdf.h" +#include "actions/actions-canvas-snapping.h" #include "widgets/desktop-widget.h" - #include "xml/croco-node-iface.h" #include "xml/rebase-hrefs.h" @@ -108,7 +101,7 @@ SPDocument::SPDocument() : document_name(nullptr), actionkey(), profileManager(nullptr), // deferred until after other initialization - router(new Avoid::Router(Avoid::PolyLineRouting|Avoid::OrthogonalRouting)), + router(new SPConnRouter(Avoid::PolyLineRouting|Avoid::OrthogonalRouting)), oldSignalsConnected(false), current_persp3d(nullptr), current_persp3d_impl(nullptr), @@ -124,7 +117,7 @@ SPDocument::SPDocument() : // Penalise libavoid for choosing paths with needless extra segments. // This results in much better looking orthogonal connector paths. - router->setRoutingPenalty(Avoid::segmentPenalty); + router->getAvoidRouter()->setRoutingPenalty(Avoid::segmentPenalty); _serial = next_serial++; @@ -1239,7 +1232,7 @@ gint SPDocument::ensureUpToDate() // changed objects and provide new routings. This may cause some objects // to be modified, hence the second update pass. if (pass == 1) { - router->processTransaction(); + router->getAvoidRouter()->processTransaction(); } } @@ -1273,7 +1266,7 @@ SPDocument::rerouting_handler() // Process any queued movement actions and determine new routings for // object-avoiding connectors. Callbacks will be used to update and // redraw affected connectors. - router->processTransaction(); + router->getAvoidRouter()->processTransaction(); // We don't need to handle rerouting again until there are further // diagram updates. @@ -1428,6 +1421,43 @@ static SPItem *find_item_at_point(std::deque *nodes, unsigned int dkey, return seen; } +/** +Returns the all items (in z-order) from the descendants of group (recursively) which +is at the point p, or NULL if none. Honors into_groups on whether to recurse into +non-layer groups or not. Honors take_insensitive on whether to return insensitive +items. If upto != NULL, then if item upto is encountered (at any level), stops searching +upwards in z-order and returns what it has found so far (i.e. the found item is +guaranteed to be lower than upto). Requires a list of nodes built by +build_flat_item_list. + */ +static std::vector find_items_at_point(std::deque *nodes, unsigned int dkey, Geom::Point const &p, + SPItem *upto = nullptr) +{ + std::vector items; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gdouble delta = prefs->getDouble("/options/cursortolerance/value", 1.0); + + SPItem *child; + bool seen_upto = (!upto); + for (auto node : *nodes) { + child = node; + if (!seen_upto) { + if (child == upto) + seen_upto = true; + continue; + } + Inkscape::DrawingItem *arenaitem = child->get_arenaitem(dkey); + if (arenaitem) { + arenaitem->drawing().update(); + if (arenaitem->pick(p, delta, 1) != nullptr) { + items.push_back(child); + } + } + } + + return items; +} + /** Returns the topmost non-layer group from the descendants of group which is at point p, or NULL if none. Recurses into layers but not into groups. @@ -1493,7 +1523,7 @@ std::vector SPDocument::getItemsPartiallyInBox(unsigned int dkey, Geom: std::vector x; return find_items_in_area(x, SP_GROUP(this->root), dkey, box, overlaps, take_hidden, take_insensitive, take_groups, enter_groups); } - +// this add top items till limit in a list of points std::vector SPDocument::getItemsAtPoints(unsigned const key, std::vector points, bool all_layers, size_t limit) const { std::vector items; @@ -1538,6 +1568,51 @@ std::vector SPDocument::getItemsAtPoints(unsigned const key, std::vecto return items; } +// this add all items till limit in a point +std::vector SPDocument::getItemsAtPoint(unsigned const key, Geom::Point point, bool all_layers, + size_t limit) const +{ + std::vector items; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // When picking along the path, we don't want small objects close together + // (such as hatching strokes) to obscure each other by their deltas, + // so we temporarily set delta to a small value + gdouble saved_delta = prefs->getDouble("/options/cursortolerance/value", 1.0); + prefs->setDouble("/options/cursortolerance/value", 0.25); + + // Cache a flattened SVG DOM to speed up selection. + if (!_node_cache_valid) { + _node_cache.clear(); + build_flat_item_list(key, SP_GROUP(this->root), true); + _node_cache_valid = true; + } + SPObject *current_layer = nullptr; + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + Inkscape::LayerModel *layer_model = nullptr; + if (desktop) { + current_layer = desktop->currentLayer(); + layer_model = desktop->layers; + } + size_t item_counter = 0; + std::vector tmp = find_items_at_point(&_node_cache, key, point); + for (auto item : tmp) { + if (all_layers || (layer_model && layer_model->layerForObject(item) == current_layer)) { + items.push_back(item); + item_counter++; + // limit 0 = no limit + if (item_counter == limit) { + prefs->setDouble("/options/cursortolerance/value", saved_delta); + return items; + } + } + } + + // and now we restore it back + prefs->setDouble("/options/cursortolerance/value", saved_delta); + + return items; +} SPItem *SPDocument::getItemAtPoint( unsigned const key, Geom::Point const &p, bool const into_groups, SPItem *upto) const diff --git a/src/document.h b/src/document.h index f6145ce807..6328f229c7 100644 --- a/src/document.h +++ b/src/document.h @@ -63,8 +63,6 @@ extern bool sp_no_convert_text_baseline_spacing; // look in 0.92+. extern bool sp_do_not_fix_pre_92; - - namespace Avoid { class Router; } @@ -73,6 +71,7 @@ class SPItem; class SPObject; class SPGroup; class SPRoot; +class SPConnRouter; namespace Inkscape { class Selection; @@ -168,9 +167,8 @@ public: // Document structure ----------------- Inkscape::ProfileManager* getProfileManager() const { return profileManager; } - Avoid::Router* getRouter() const { return router; } + SPConnRouter *getRouter() const { return router; } - /** Returns our SPRoot */ SPRoot *getRoot() { return root; } SPRoot const *getRoot() const { return root; } @@ -249,6 +247,8 @@ public: std::vector getItemsPartiallyInBox(unsigned int dkey, Geom::Rect const &box, bool take_hidden = false, bool take_insensitive = false, bool take_groups = true, bool enter_groups = false) const; SPItem *getItemAtPoint(unsigned int key, Geom::Point const &p, bool into_groups, SPItem *upto = nullptr) const; std::vector getItemsAtPoints(unsigned const key, std::vector points, bool all_layers = true, size_t limit = 0) const ; + std::vector getItemsAtPoint(unsigned const key, Geom::Point point, bool all_layers = true, + size_t limit = 0) const; SPItem *getGroupAtPoint(unsigned int key, Geom::Point const &p) const; /** @@ -296,7 +296,7 @@ private: // Document ------------------------------ Inkscape::ProfileManager* profileManager; // Color profile. - Avoid::Router *router; // Instance of the connector router + SPConnRouter *router; // Instance of the connector router // Document status ----------------------- diff --git a/src/live_effects/CMakeLists.txt b/src/live_effects/CMakeLists.txt index 939e7ee443..5f5bedaed7 100644 --- a/src/live_effects/CMakeLists.txt +++ b/src/live_effects/CMakeLists.txt @@ -12,6 +12,7 @@ set(live_effects_SRC lpe-transform_2pts.cpp lpe-circle_with_radius.cpp lpe-clone-original.cpp + lpe-connector.cpp lpe-constructgrid.cpp lpe-copy_rotate.cpp lpe-curvestitch.cpp @@ -68,6 +69,7 @@ set(live_effects_SRC parameter/array.cpp parameter/bool.cpp + parameter/connectorpointarray.cpp parameter/colorpicker.cpp parameter/hidden.cpp parameter/item-reference.cpp @@ -107,6 +109,7 @@ set(live_effects_SRC lpe-transform_2pts.h lpe-circle_with_radius.h lpe-clone-original.h + lpe-connector.h lpe-constructgrid.h lpe-copy_rotate.h lpe-curvestitch.h @@ -164,6 +167,7 @@ set(live_effects_SRC parameter/array.h parameter/bool.h + parameter/connectorpointarray.h parameter/colorpicker.h parameter/hidden.h parameter/enum.h diff --git a/src/live_effects/effect-enum.h b/src/live_effects/effect-enum.h index 166e4d3fa9..8e1394c801 100644 --- a/src/live_effects/effect-enum.h +++ b/src/live_effects/effect-enum.h @@ -16,7 +16,8 @@ namespace Inkscape { namespace LivePathEffect { //Please fill in the same order than in effect.cpp:98 -enum EffectType { +enum EffectType +{ BEND_PATH = 0, GEARS, PATTERN_ALONG_PATH, @@ -65,6 +66,9 @@ enum EffectType { PARALLEL, PERP_BISECTOR, TANGENT_TO_CURVE, + SLICE, + CONNECTOR, + // PUT NEW LPE BEFORE EXPERIMENTAL DOEFFECTSTACK_TEST, DYNASTROKE, LATTICE, @@ -72,7 +76,6 @@ enum EffectType { RECURSIVE_SKELETON, TEXT_LABEL, EMBRODERY_STITCH, - SLICE, INVALID_LPE // This must be last (I made it such that it is not needed anymore I think..., Don't trust on it being // last. - johan) }; diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index 4863a62a19..6e03edf230 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -13,6 +13,12 @@ //#define LPE_ENABLE_TEST_EFFECTS //uncomment for toy effects // include effects: +#include +#include +#include +#include + +#include "display/curve.h" #include "live_effects/lpe-angle_bisector.h" #include "live_effects/lpe-attach-path.h" #include "live_effects/lpe-bendpath.h" @@ -22,6 +28,7 @@ #include "live_effects/lpe-circle_3pts.h" #include "live_effects/lpe-circle_with_radius.h" #include "live_effects/lpe-clone-original.h" +#include "live_effects/lpe-connector.h" #include "live_effects/lpe-constructgrid.h" #include "live_effects/lpe-copy_rotate.h" #include "live_effects/lpe-curvestitch.h" @@ -69,28 +76,18 @@ #include "live_effects/lpe-text_label.h" #include "live_effects/lpe-transform_2pts.h" #include "live_effects/lpe-vonkoch.h" - #include "live_effects/lpeobject.h" - -#include "xml/node-event-vector.h" -#include "xml/sp-css-attr.h" - -#include "display/curve.h" #include "message-stack.h" +#include "object/sp-defs.h" +#include "object/sp-root.h" +#include "object/sp-shape.h" #include "path-chemistry.h" #include "ui/icon-loader.h" #include "ui/tools-switch.h" #include "ui/tools/node-tool.h" #include "ui/tools/pen-tool.h" - -#include "object/sp-defs.h" -#include "object/sp-root.h" -#include "object/sp-shape.h" - -#include -#include -#include -#include +#include "xml/node-event-vector.h" +#include "xml/sp-css-attr.h" namespace Inkscape { @@ -676,7 +673,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { CIRCLE_WITH_RADIUS, @@ -690,7 +687,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { CIRCLE_3PTS, @@ -704,7 +701,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { EXTRUDE, @@ -718,7 +715,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { LINE_SEGMENT, @@ -732,7 +729,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { PARALLEL, @@ -746,7 +743,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { PERP_BISECTOR, @@ -760,7 +757,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { TANGENT_TO_CURVE, @@ -774,7 +771,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, /* 1.1 */ { @@ -786,7 +783,21 @@ const EnumEffectData LPETypeData[] = { N_("Slices the item into parts. It can also be applied multiple times.") ,//description true ,//on_path true ,//on_shape - true ,//on_group + true ,//on_group + false ,//on_image + false ,//on_text + false ,//experimental + }, + { + CONNECTOR, + N_("Connector") ,//label + "connector" ,//key + "lpe-connector" ,//icon + "Connector" ,//untranslated name + N_("You can add this LPE to groups to add target points to it or to paths to add crossing gaps.") ,//description + false ,//on_path + false ,//on_shape + true ,//on_group false ,//on_image false ,//on_text false ,//experimental @@ -804,7 +815,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { DYNASTROKE, @@ -818,7 +829,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { LATTICE, @@ -832,7 +843,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { PATH_LENGTH, @@ -846,7 +857,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { RECURSIVE_SKELETON, @@ -860,7 +871,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { TEXT_LABEL, @@ -874,7 +885,7 @@ const EnumEffectData LPETypeData[] = { true ,//on_group false ,//on_image false ,//on_text - true ,//experimental + true ,//experimental }, { EMBRODERY_STITCH, @@ -1082,6 +1093,9 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) case SLICE: neweffect = static_cast(new LPESlice(lpeobj)); break; + case CONNECTOR: + neweffect = static_cast(new LPEConnector(lpeobj)); + break; default: g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr); neweffect = nullptr; @@ -1549,6 +1563,18 @@ Effect::update_helperpath() { } } +/** + * Get selection + */ +Inkscape::Selection *Effect::getSelection() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop) { + return desktop->getSelection(); + } + return nullptr; +} + /** * This *creates* a new widget, management of deletion should be done by the caller */ diff --git a/src/live_effects/effect.h b/src/live_effects/effect.h index 2b68661347..60dbe227dd 100644 --- a/src/live_effects/effect.h +++ b/src/live_effects/effect.h @@ -8,15 +8,16 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ -#include "effect-enum.h" -#include "parameter/bool.h" -#include "parameter/hidden.h" -#include "ui/widget/registry.h" #include <2geom/forward.h> #include #include #include +#include "effect-enum.h" +#include "parameter/bool.h" +#include "parameter/hidden.h" +#include "selection.h" +#include "ui/widget/registry.h" #define LPE_CONVERSION_TOLERANCE 0.01 // FIXME: find good solution for this. @@ -122,6 +123,7 @@ public: void addHandles(KnotHolder *knotholder, SPItem *item); std::vector getCanvasIndicators(SPLPEItem const* lpeitem); void update_helperpath(); + Inkscape::Selection *getSelection(); bool has_exception; inline bool providesOwnFlashPaths() const { diff --git a/src/live_effects/lpe-clone-original.cpp b/src/live_effects/lpe-clone-original.cpp index 6c357ba34b..c118da0b83 100644 --- a/src/live_effects/lpe-clone-original.cpp +++ b/src/live_effects/lpe-clone-original.cpp @@ -6,23 +6,22 @@ */ #include "live_effects/lpe-clone-original.h" -#include "live_effects/lpe-spiro.h" + +#include "display/curve.h" #include "live_effects/lpe-bspline.h" -#include "live_effects/lpeobject.h" +#include "live_effects/lpe-spiro.h" #include "live_effects/lpeobject-reference.h" -#include "display/curve.h" -#include "svg/path-string.h" -#include "svg/svg.h" - -#include "ui/tools-switch.h" -#include "ui/tools/node-tool.h" +#include "live_effects/lpeobject.h" #include "object/sp-clippath.h" #include "object/sp-mask.h" #include "object/sp-path.h" #include "object/sp-shape.h" +#include "object/sp-symbol.h" #include "object/sp-text.h" -#include "display/curve.h" - +#include "svg/path-string.h" +#include "svg/svg.h" +#include "ui/tools-switch.h" +#include "ui/tools/node-tool.h" #include "xml/sp-css-attr.h" // TODO due to internal breakage in glibmm headers, this must be last: @@ -156,13 +155,45 @@ LPECloneOriginal::cloneAttrbutes(SPObject *origin, SPObject *dest, const gchar * index++; } } + SPSymbol *symbol_origin = dynamic_cast(origin); + group_dest = dynamic_cast(dest); + if (symbol_origin && group_dest && symbol_origin->getItemCount() != group_dest->getItemCount()) { + sp_lpe_item_enable_path_effects(sp_lpe_item, false); + std::vector childs = symbol_origin->childList(true); + std::vector childsdest = group_dest->childList(true); + for (auto &child : childsdest) { + child->deleteObject(true); + } + for (auto &child : childs) { + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + Inkscape::XML::Node *dup = child->getRepr()->duplicate(xml_doc); + dest->getRepr()->appendChild(dup); + Inkscape::GC::release(dup); + } + sp_lpe_item_enable_path_effects(sp_lpe_item, true); + } + if (symbol_origin && group_dest) { + std::vector childs = group_origin->childList(true); + size_t index = 0; + for (auto &child : childs) { + SPObject *dest_child = group_dest->nthChild(index); + cloneAttrbutes(child, dest_child, attributes, css_properties, init); + index++; + } + } //Attributes SPShape * shape_origin = dynamic_cast(origin); SPShape * shape_dest = dynamic_cast(dest); SPItem * item_origin = dynamic_cast(origin); SPItem * item_dest = dynamic_cast(dest); - SPMask * mask_origin = dynamic_cast(item_origin->getMaskObject()); - SPMask * mask_dest = dynamic_cast(item_dest->getMaskObject()); + SPMask *mask_origin = nullptr; + SPMask *mask_dest = nullptr; + if (item_origin) { + mask_origin = dynamic_cast(item_origin->getMaskObject()); + } + if (item_dest) { + mask_dest = dynamic_cast(item_dest->getMaskObject()); + } if(mask_origin && mask_dest) { std::vector mask_list = mask_origin->childList(true); std::vector mask_list_dest = mask_dest->childList(true); @@ -175,9 +206,15 @@ LPECloneOriginal::cloneAttrbutes(SPObject *origin, SPObject *dest, const gchar * } } } - - SPClipPath *clippath_origin = SP_ITEM(origin)->getClipObject(); - SPClipPath *clippath_dest = SP_ITEM(dest)->getClipObject(); + + SPClipPath *clippath_origin = nullptr; + SPClipPath *clippath_dest = nullptr; + if (item_origin) { + clippath_origin = dynamic_cast(item_origin->getClipObject()); + } + if (item_dest) { + clippath_dest = dynamic_cast(item_dest->getClipObject()); + } if(clippath_origin && clippath_dest) { std::vector clippath_list = clippath_origin->childList(true); std::vector clippath_list_dest = clippath_dest->childList(true); @@ -190,6 +227,40 @@ LPECloneOriginal::cloneAttrbutes(SPObject *origin, SPObject *dest, const gchar * } } } + SPCSSAttr *css_origin = sp_repr_css_attr_new(); + sp_repr_css_attr_add_from_string(css_origin, origin->getRepr()->attribute("style")); + SPCSSAttr *css_dest = sp_repr_css_attr_new(); + sp_repr_css_attr_add_from_string(css_dest, dest->getRepr()->attribute("style")); + if (init) { + css_dest = css_origin; + } + gchar **styleattarray = g_strsplit(old_css_properties.c_str(), ",", 0); + gchar **styleiter = styleattarray; + while (*styleiter != nullptr) { + const char *attribute = (*styleiter); + if (strlen(attribute)) { + sp_repr_css_set_property(css_dest, attribute, nullptr); + } + styleiter++; + } + styleattarray = g_strsplit(css_properties, ",", 0); + styleiter = styleattarray; + while (*styleiter != nullptr) { + const char *attribute = (*styleiter); + if (strlen(attribute)) { + const char *origin_attribute = sp_repr_css_property(css_origin, attribute, ""); + if (!strlen(origin_attribute)) { //==0 + sp_repr_css_set_property(css_dest, attribute, nullptr); + } else { + sp_repr_css_set_property(css_dest, attribute, origin_attribute); + } + } + styleiter++; + } + g_strfreev(styleattarray); + Glib::ustring css_str; + sp_repr_css_write_string(css_dest, css_str); + dest->getRepr()->setAttributeOrRemoveIfEmpty("style", css_str); gchar ** attarray = g_strsplit(old_attributes.c_str(), ",", 0); gchar ** iter = attarray; while (*iter != nullptr) { @@ -203,6 +274,7 @@ LPECloneOriginal::cloneAttrbutes(SPObject *origin, SPObject *dest, const gchar * iter = attarray; while (*iter != nullptr) { const char* attribute = (*iter); + std::cout << attribute << std::endl; if (strlen(attribute) && shape_dest && shape_origin) { if (std::strcmp(attribute, "d") == 0) { std::unique_ptr c; @@ -252,40 +324,6 @@ LPECloneOriginal::cloneAttrbutes(SPObject *origin, SPObject *dest, const gchar * dest->getRepr()->setAttribute("transform", origin->getRepr()->attribute("transform")); } g_strfreev (attarray); - SPCSSAttr *css_origin = sp_repr_css_attr_new(); - sp_repr_css_attr_add_from_string(css_origin, origin->getRepr()->attribute("style")); - SPCSSAttr *css_dest = sp_repr_css_attr_new(); - sp_repr_css_attr_add_from_string(css_dest, dest->getRepr()->attribute("style")); - if (init) { - css_dest = css_origin; - } - gchar ** styleattarray = g_strsplit(old_css_properties.c_str(), ",", 0); - gchar ** styleiter = styleattarray; - while (*styleiter != nullptr) { - const char* attribute = (*styleiter); - if (strlen(attribute)) { - sp_repr_css_set_property (css_dest, attribute, nullptr); - } - styleiter++; - } - styleattarray = g_strsplit(css_properties, ",", 0); - styleiter = styleattarray; - while (*styleiter != nullptr) { - const char* attribute = (*styleiter); - if (strlen(attribute)) { - const char* origin_attribute = sp_repr_css_property(css_origin, attribute, ""); - if (!strlen(origin_attribute)) { //==0 - sp_repr_css_set_property (css_dest, attribute, nullptr); - } else { - sp_repr_css_set_property (css_dest, attribute, origin_attribute); - } - } - styleiter++; - } - g_strfreev (styleattarray); - Glib::ustring css_str; - sp_repr_css_write_string(css_dest,css_str); - dest->getRepr()->setAttributeOrRemoveIfEmpty("style", css_str); } void @@ -302,6 +340,7 @@ LPECloneOriginal::doBeforeEffect (SPLPEItem const* lpeitem){ } SPText *text_origin = dynamic_cast(orig); SPGroup *group_origin = dynamic_cast(orig); + SPSymbol *symbol_origin = dynamic_cast(orig); SPItem *dest = dynamic_cast(sp_lpe_item); const gchar * id = orig->getId(); bool init = g_strcmp0(id, linked) != 0 && !is_load; @@ -323,6 +362,9 @@ LPECloneOriginal::doBeforeEffect (SPLPEItem const* lpeitem){ if (attr.size() && attributes_str.empty()) { attr.erase (attr.size()-1, 1); } + if (symbol_origin) { + attr += ",style"; + } auto css_properties_str = css_properties.param_getSVGValue(); Glib::ustring style_attr = ""; if (style_attr.size() && css_properties_str.empty()) { @@ -330,10 +372,10 @@ LPECloneOriginal::doBeforeEffect (SPLPEItem const* lpeitem){ } style_attr += css_properties_str + ","; cloneAttrbutes(orig, dest, attr.c_str(), style_attr.c_str(), init); - if (!group_origin && linkeditem.last_transform.isTranslation()) { + if (!group_origin && !symbol_origin && linkeditem.last_transform.isTranslation()) { Geom::Affine orig = sp_lpe_item->transform; sp_lpe_item->transform *= orig.inverse() * linkeditem.last_transform.inverse() * orig; - linkeditem.last_transform = Geom::identity(); + linkeditem.last_transform = Geom::identity(); } old_css_properties = css_properties.param_getSVGValue(); old_attributes = attributes.param_getSVGValue(); @@ -384,6 +426,21 @@ LPECloneOriginal::doEffect (SPCurve * curve) } } +void LPECloneOriginal::doAfterEffect(SPLPEItem const *lpeitem, SPCurve *curve) +{ + if (linkeditem.linksToItem()) { + SPItem *orig = dynamic_cast(linkeditem.getObject()); + if (!orig) { + return; + } + SPSymbol *symbol_origin = dynamic_cast(orig); + if (symbol_origin) { + sp_lpe_item->removeAllPathEffects(true); + Effect::createAndApply(CONNECTOR, sp_lpe_item->document, sp_lpe_item); + } + } +} + } // namespace LivePathEffect } /* namespace Inkscape */ diff --git a/src/live_effects/lpe-clone-original.h b/src/live_effects/lpe-clone-original.h index 0ef479f3d0..8793b6a4f7 100644 --- a/src/live_effects/lpe-clone-original.h +++ b/src/live_effects/lpe-clone-original.h @@ -28,6 +28,7 @@ public: ~LPECloneOriginal() override; void doEffect (SPCurve * curve) override; void doBeforeEffect (SPLPEItem const* lpeitem) override; + void doAfterEffect(SPLPEItem const *lpeitem, SPCurve *curve) override; void cloneAttrbutes(SPObject *origin, SPObject *dest, const gchar * attributes, const gchar * css_properties, bool init); void modified(SPObject */*obj*/, guint /*flags*/); Gtk::Widget *newWidget() override; diff --git a/src/live_effects/lpe-connector.cpp b/src/live_effects/lpe-connector.cpp new file mode 100644 index 0000000000..dc13eb0a93 --- /dev/null +++ b/src/live_effects/lpe-connector.cpp @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * LPE implementation: slices a path with respect to a given line. + */ +/* + * Authors: + * Jabiertxof + * + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "live_effects/lpe-connector.h" + +#include "display/curve.h" +#include "document-undo.h" +#include "live_effects/lpeobject.h" +#include "object/sp-conn-end.h" +#include "object/sp-conn-router.h" +#include "object/sp-item-group.h" +#include "object/sp-path.h" + + +// TODO due to internal breakage in glibmm headers, this must be last: +#include + + +namespace Inkscape { +namespace LivePathEffect { + +LPEConnector::LPEConnector(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + connector_points(_("Connector points"), _("Connector Points"), "connector_points", &wr, this), + crossing_gap(_("Crossing gap:"), _("Gap between crossings"), "crossing_gap", &wr, this, 5.0), + extrem_gap(_("Extrem gap:"), _("Extrem gap"), "extrem_gap", &wr, this, 0.0), + nocrossone(_("Keep one crossing unchanged"), _("Dont cross gap in one connector"), "nocrossone", &wr, this, true), + crossing_markers(_("Split connector on crossings"), _("Split connector on crossings to allow middle markers"), "crossing_markers", &wr, this, false), + linkstyle(_("One style"), _("Keep one style in all connectors"), "linkstyle", &wr, this, true) +{ + registerParameter(&connector_points); + registerParameter(&crossing_gap); + registerParameter(&extrem_gap); + registerParameter(&nocrossone); + registerParameter(&crossing_markers); + registerParameter(&linkstyle); + crossing_gap.param_set_range(0, Geom::infinity()); + crossing_gap.param_set_increments(1, 5); + crossing_gap.param_set_digits(4); + extrem_gap.param_set_range(0, Geom::infinity()); + extrem_gap.param_set_increments(1, 5); + extrem_gap.param_set_digits(4); +} + +LPEConnector::~LPEConnector() = default; + +Gtk::Widget *LPEConnector::newWidget() +{ + // use manage here, because after deletion of Effect object, others might still be pointing to this widget. + Gtk::VBox *vbox = Gtk::manage(new Gtk::VBox(Effect::newWidget())); + vbox->set_border_width(5); + vbox->set_homogeneous(false); + vbox->set_spacing(6); + SPGroup *group = dynamic_cast(sp_lpe_item); + if (!group) { + std::vector::iterator it = param_vector.begin(); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter *param = *it; + if (param->param_key != "connector_points") { + Gtk::Widget *widg = dynamic_cast(param->param_newWidget()); + Glib::ustring *tip = param->param_getTooltip(); + if (widg) { + vbox->pack_start(*widg, true, true, 2); + if (tip) { + widg->set_tooltip_text(*tip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + } + } + ++it; + } + if (Gtk::Widget *widg = defaultParamSet()) { + vbox->pack_start(*widg, true, true, 2); + } + } + return dynamic_cast(vbox); +} + +void LPEConnector::doOnApply(SPLPEItem const *lpeitem) +{ + if (!sp_lpe_item) { + return; + } + SPGroup *group = dynamic_cast(sp_lpe_item); + if (group) { + std::vector points; + for (auto &child : group->children) { + if (child.getAttribute("inkscape:connector")) { + SPPath *path = dynamic_cast(&child); + if (path) { + auto c = path->curve(); + if (c) { + auto p = c->first_point(); + if (p) { + points.push_back(*p); + } + } + } + } + } + connector_points.param_set_and_write_new_value(points); + } else { + if (sp_lpe_item->countLPEOfType(CONNECTOR) > 1) { + sp_lpe_item->removeCurrentPathEffect(false); + return; + } + SPConnRouter *router = getSPDoc()->getRouter(); + Glib::ustring lpeid = ""; + if (router && router->get_lpe() == "") { + router->set_lpe(Glib::ustring(this->getLPEObj()->getId())); + } else { + lpeid = sp_lpe_item->getAttribute("inkscape:path-effect"); + sp_lpe_item->setAttribute("inkscape:path-effect", (Glib::ustring("#") + router->get_lpe()).c_str()); + } + SPPath *path = dynamic_cast(sp_lpe_item); + sp_conn_redraw_path(path); + if (SPObject *elemref = getSPDoc()->getObjectById(lpeid.c_str())) { + elemref->deleteObject(true); + } + } +} + +void LPEConnector::doOnRemove(SPLPEItem const * /*lpeitem*/) +{ + // set "keep paths" hook on sp-lpe-item.cpp + if (keep_paths) { + processObjects(LPE_TO_OBJECTS); + items.clear(); + return; + } + processObjects(LPE_ERASE); +} + +void LPEConnector::doBeforeEffect(SPLPEItem const *lpeitem) +{ + SPGroup *group = dynamic_cast(sp_lpe_item); + if (group) { + original_bbox(lpeitem, false, true); + Geom::Point center(boundingbox_X.middle(), boundingbox_Y.middle()); + std::vector points = connector_points.data(); + Inkscape::XML::Document *xml_doc = getSPDoc()->getReprDoc(); + if (points.empty()) { + Glib::ustring id = "connector-0-"; + id += this->lpeobj->getId(); + items.clear(); + items.push_back(id); + Inkscape::XML::Node *resultnode = xml_doc->createElement("svg:path"); + resultnode->setAttribute("inkscape:connector", "true"); + Inkscape::SVGOStringStream os; + os << "M " << center << " l 0 0"; + resultnode->setAttribute("d", os.str().c_str()); + resultnode->setAttribute("id", id.c_str()); + points.push_back(center); + connector_points.param_set_and_write_new_value(points); + } else { + std::vector points = connector_points.data(); + Inkscape::SVGOStringStream os; + size_t prevsize = items.size(); + items.clear(); + SPGroup *group = dynamic_cast(sp_lpe_item); + unsigned int i; + for (i = 0; i < points.size(); ++i) { + Glib::ustring id = "connector-"; + id += Glib::ustring::format(i); + id += "-"; + id += this->lpeobj->getId(); + items.push_back(id); + SPObject *elemref = getSPDoc()->getObjectById(id.c_str()); + Inkscape::XML::Node *phantom = nullptr; + if (elemref) { + phantom = elemref->getRepr(); + } else { + phantom = xml_doc->createElement("svg:path"); + phantom->setAttribute("inkscape:connector", "true"); + phantom->setAttribute("id", id.c_str()); + elemref = group->appendChildRepr(phantom); + Inkscape::GC::release(phantom); + } + Inkscape::SVGOStringStream os; + os << "M " << points[i] << " l 0 0"; + phantom->setAttribute("d", os.str().c_str()); + } + while (i < prevsize) { + Glib::ustring id = "connector-"; + id += Glib::ustring::format(i); + id += "-"; + id += this->lpeobj->getId(); + items.push_back(id); + SPObject *elemref = getSPDoc()->getObjectById(id.c_str()); + if (elemref) { + elemref->deleteObject(true); + } + ++i; + } + } + } else { + if (crossing_markers && crossing_gap > 0) { + crossing_gap.param_set_value(0); + crossing_gap.write_to_SVG(); + } + SPConnRouter *router = getSPDoc()->getRouter(); + if (router) { + router->set_lpe(Glib::ustring(this->getLPEObj()->getId())); + if (linkstyle) { + router->apply_style(sp_lpe_item->getAttribute("style")); + } + if (router->get_lpe() != "" && + (Glib::ustring("#") + router->get_lpe()) != sp_lpe_item->getAttribute("inkscape:path-effect")) { + sp_lpe_item->setAttribute("inkscape:path-effect", (Glib::ustring("#") + router->get_lpe()).c_str()); + } + } + } +} + +bool sp_reorder_along(Geom::Point a, Geom::Point b, Geom::Point c) +{ + return Geom::distance(a, c) < Geom::distance(b, c); +} + +Geom::PathVector LPEConnector::doEffect_path(Geom::PathVector const &path_in) +{ + Geom::PathVector path_out = path_in; + SPPath *path = dynamic_cast(sp_lpe_item); + SPGroup *group = dynamic_cast(sp_lpe_item); + if (group) { + return path_out; + } else if (path) { + SPConnRouter *router = getSPDoc()->getRouter(); + std::vector points = connector_points.data(); + Geom::Affine i2d = sp_lpe_item->i2doc_affine(); + Geom::Path &pathin = path_out[0]; + Geom::Coord sizeopen = pathin.size_open(); + if (extrem_gap) { + double t = 0; + double length_part = pathin.front().length(); + if (length_part && length_part > extrem_gap) { + t = extrem_gap / length_part; + } + double t1 = 0; + length_part = pathin.back_open().length(); + if (length_part && length_part > extrem_gap) { + t1 = 1 - (extrem_gap / length_part); + } + if (t && t1) { + pathin = pathin.portion(t, (sizeopen - 1) + t1); + } + } + std::vector pointsclean; + for (auto point : points) { + point *= i2d.inverse(); + Geom::Coord distance; + boost::optional pos = path_out.nearestTime(point, &distance); + if (distance < 0.01 && pos) { + pointsclean.push_back(point); + } + } + sort(pointsclean.begin(), pointsclean.end(), bind(sp_reorder_along, _1, _2, path_out.initialPoint())); + for (auto point : pointsclean) { + Geom::Coord distance; + boost::optional pos = path_out.nearestTime(point, &distance); + if (distance < 0.01 && pos) { + if (nocrossone && router && router->pairs_at_point(point).first && + router->pairs_at_point(point).first->_path == sp_lpe_item) { + continue; + } + Geom::Path &pathin = path_out[(*pos).path_index]; + Geom::Coord sizeopen = pathin.size_open(); + Geom::Path startpath = pathin.portion(0, (*pos).curve_index + (*pos).t); + Geom::Path endpath = pathin.portion((*pos).curve_index + (*pos).t, sizeopen); + if (crossing_markers) { + startpath.append(endpath); + path_out.push_back(startpath); + path_out.erase(path_out.begin() + (*pos).path_index); + continue; + } + if (crossing_gap == 0) { + continue; + } + double t = 0; + double length_part = startpath.back_open().length(); + if (length_part && length_part > crossing_gap) { + t = 1 - (crossing_gap / length_part); + startpath = startpath.portion(0, t); + } + double t2 = 0; + length_part = endpath.back_open().length(); + if (length_part && length_part > crossing_gap) { + t2 = crossing_gap / length_part; + endpath = endpath.portion(t2, endpath.size_open()); + } + if (t) { + path_out.push_back(startpath); + } + if (t2 && t) { + path_out.push_back(endpath); + } + if (t2 && t) { + path_out.erase(path_out.begin() + (*pos).path_index); + } + } + } + } + return path_out; +} + +} // namespace LivePathEffect +} /* namespace Inkscape */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/live_effects/lpe-connector.h b/src/live_effects/lpe-connector.h new file mode 100644 index 0000000000..3e4170a602 --- /dev/null +++ b/src/live_effects/lpe-connector.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INKSCAPE_LPE_CONNECTOR_H +#define INKSCAPE_LPE_CONNECTOR_H + +/** \file + * LPE implementation: mirrors a path with respect to a given line. + */ +/* + * Authors: + * Maximilian Albert + * Johan Engelen + * Jabiertxof + * + * Copyright (C) Johan Engelen 2007 + * Copyright (C) Maximilin Albert 2008 + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "live_effects/effect.h" +#include "live_effects/lpegroupbbox.h" +#include "live_effects/lpeobject-reference.h" +#include "live_effects/lpeobject.h" +#include "live_effects/parameter/bool.h" +#include "live_effects/parameter/connectorpointarray.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEConnector + : public Effect + , GroupBBoxEffect +{ +public: + LPEConnector(LivePathEffectObject *lpeobject); + ~LPEConnector() override; + void doOnApply(SPLPEItem const *lpeitem) override; + void doBeforeEffect(SPLPEItem const *lpeitem) override; + Gtk::Widget *newWidget() override; + void doOnRemove(SPLPEItem const * /*lpeitem*/) override; + Geom::PathVector doEffect_path(Geom::PathVector const &path_in) override; + ConnectorPointArrayParam connector_points; + +private: + ScalarParam crossing_gap; + ScalarParam extrem_gap; + BoolParam crossing_markers; + BoolParam nocrossone; + BoolParam linkstyle; + LPEConnector(const LPEConnector &) = delete; + LPEConnector &operator=(const LPEConnector &) = delete; +}; + +} // namespace LivePathEffect +} // namespace Inkscape + +#endif diff --git a/src/live_effects/parameter/connectorpointarray.cpp b/src/live_effects/parameter/connectorpointarray.cpp new file mode 100644 index 0000000000..38954c07eb --- /dev/null +++ b/src/live_effects/parameter/connectorpointarray.cpp @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) Johan Engelen 2007 + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "connectorpointarray.h" + +#include + +#include "live_effects/effect.h" +#include "live_effects/lpe-connector.h" +#include "ui/knot/knot-holder.h" + +namespace Inkscape { + +namespace LivePathEffect { + +ConnectorPointArrayParam::ConnectorPointArrayParam(const Glib::ustring &label, const Glib::ustring &tip, + const Glib::ustring &key, Inkscape::UI::Widget::Registry *wr, + Effect *effect) + : ArrayParam(label, tip, key, wr, effect, 0) +{ + knot_shape = Inkscape::CANVAS_ITEM_CTRL_SHAPE_DIAMOND; + knot_mode = Inkscape::CANVAS_ITEM_CTRL_MODE_XOR; + knot_color = 0xff88ff00; +} + +ConnectorPointArrayParam::~ConnectorPointArrayParam() = default; + +Gtk::Widget *ConnectorPointArrayParam::param_newWidget() +{ + return nullptr; +} + +void ConnectorPointArrayParam::set_oncanvas_looks(Inkscape::CanvasItemCtrlShape shape, + Inkscape::CanvasItemCtrlMode mode, guint32 color) +{ + knot_shape = shape; + knot_mode = mode; + knot_color = color; +} + +ConnectorPointArrayParamKnotHolderEntity::ConnectorPointArrayParamKnotHolderEntity(ConnectorPointArrayParam *p, + unsigned int index) + : _pparam(p) + , _index(index) +{} + +void ConnectorPointArrayParamKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const & /*origin*/, + guint state) +{ + using namespace Geom; + + if (!valid_index(_index)) { + return; + } + Geom::Point s = snap_knot_position(p, state); + _pparam->_vector.at(_index) = s; + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); +} + +Geom::Point ConnectorPointArrayParamKnotHolderEntity::knot_get() const +{ + using namespace Geom; + + if (!valid_index(_index)) { + return Geom::Point(Geom::infinity(), Geom::infinity()); + } + return _pparam->_vector.at(_index); +} + +void ConnectorPointArrayParamKnotHolderEntity::knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, + guint state) +{ + _pparam->param_effect->refresh_widgets = true; + _pparam->write_to_SVG(); +} + +void ConnectorPointArrayParamKnotHolderEntity::knot_click(guint state) +{ + if (state & GDK_CONTROL_MASK) { + if (state & GDK_MOD1_MASK) { + // delete the clicked knot + std::vector &vec = _pparam->_vector; + if (vec.size() > 1) { // Force don't remove last knot + vec.erase(vec.begin() + _index); + _pparam->param_set_and_write_new_value(vec); + // shift knots down one index + for (auto &ent : parent_holder->entity) { + ConnectorPointArrayParamKnotHolderEntity *pspa_ent = + dynamic_cast(ent); + if (pspa_ent && pspa_ent->_pparam == this->_pparam) { // check if the knotentity belongs to this + // connectorpointarray parameter + if (pspa_ent->_index > this->_index) { + --pspa_ent->_index; + } + } + }; + // temporary hide, when knotholder were recreated it finally drop + this->knot->hide(); + } + return; + } else { + // add a knot to XML + std::vector &vec = _pparam->_vector; + vec.insert(vec.begin() + _index, 1, vec.at(_index)); // this clicked knot is duplicated + _pparam->param_set_and_write_new_value(vec); + + // shift knots up one index + for (auto &ent : parent_holder->entity) { + ConnectorPointArrayParamKnotHolderEntity *pspa_ent = + dynamic_cast(ent); + if (pspa_ent && pspa_ent->_pparam == this->_pparam) { // check if the knotentity belongs to this + // connectorpointarray parameter + if (pspa_ent->_index > this->_index) { + ++pspa_ent->_index; + } + } + }; + // add knot to knotholder + ConnectorPointArrayParamKnotHolderEntity *e = + new ConnectorPointArrayParamKnotHolderEntity(_pparam, _index + 1); + e->create(this->desktop, this->item, parent_holder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:Connector", + _("Connector control point: drag to move the connector point. Ctrl+click adds a " + "control point, Ctrl+Alt+click deletes it"), + _pparam->knot_color); + parent_holder->add(e); + } + } +} + +void ConnectorPointArrayParam::addKnotHolderEntities(KnotHolder *knotholder, SPItem *item) +{ + for (unsigned int i = 0; i < _vector.size(); ++i) { + ConnectorPointArrayParamKnotHolderEntity *e = new ConnectorPointArrayParamKnotHolderEntity(this, i); + e->create(nullptr, item, knotholder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:Connector", + _("Connector control point: drag to move the connector point. Ctrl+click adds a " + "control point, Ctrl+Alt+click deletes it"), + knot_color); + knotholder->add(e); + } +} + +} /* namespace LivePathEffect */ + +} /* namespace Inkscape */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/live_effects/parameter/connectorpointarray.h b/src/live_effects/parameter/connectorpointarray.h new file mode 100644 index 0000000000..efe0fe0512 --- /dev/null +++ b/src/live_effects/parameter/connectorpointarray.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INKSCAPE_LIVEPATHEFFECT_CONNECTOR_POINT_ARRAY_H +#define INKSCAPE_LIVEPATHEFFECT_CONNECTOR_POINT_ARRAY_H + +/* + * Inkscape::LivePathEffectParameters + * + * Copyright (C) Johan Engelen 2007 + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <2geom/point.h> +#include + +#include "live_effects/parameter/array.h" +#include "ui/knot/knot-holder-entity.h" + +namespace Inkscape { + +namespace LivePathEffect { + +class ConnectorPointArrayParam : public ArrayParam +{ +public: + ConnectorPointArrayParam(const Glib::ustring &label, const Glib::ustring &tip, const Glib::ustring &key, + Inkscape::UI::Widget::Registry *wr, Effect *effect); + ~ConnectorPointArrayParam() override; + + ConnectorPointArrayParam(const ConnectorPointArrayParam &) = delete; + ConnectorPointArrayParam &operator=(const ConnectorPointArrayParam &) = delete; + + Gtk::Widget *param_newWidget() override; + + void set_oncanvas_looks(Inkscape::CanvasItemCtrlShape shape, Inkscape::CanvasItemCtrlMode mode, guint32 color); + + bool providesKnotHolderEntities() const override { return true; } + void addKnotHolderEntities(KnotHolder *knotholder, SPItem *item) override; + friend class ConnectorPointArrayParamKnotHolderEntity; + +private: + Inkscape::CanvasItemCtrlShape knot_shape = Inkscape::CANVAS_ITEM_CTRL_SHAPE_DIAMOND; + Inkscape::CanvasItemCtrlMode knot_mode = Inkscape::CANVAS_ITEM_CTRL_MODE_XOR; + guint32 knot_color; +}; + +class ConnectorPointArrayParamKnotHolderEntity : public KnotHolderEntity +{ +public: + ConnectorPointArrayParamKnotHolderEntity(ConnectorPointArrayParam *p, unsigned int index); + ~ConnectorPointArrayParamKnotHolderEntity() override = default; + + void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) override; + void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override; + Geom::Point knot_get() const override; + void knot_click(guint state) override; + + /** Checks whether the index falls within the size of the parameter's vector */ + bool valid_index(unsigned int index) const { return (_pparam->_vector.size() > index); }; + +private: + ConnectorPointArrayParam *_pparam; + unsigned int _index; +}; + +} // namespace LivePathEffect + +} // namespace Inkscape + +#endif diff --git a/src/object/CMakeLists.txt b/src/object/CMakeLists.txt index e44d56f581..4a19efb8be 100644 --- a/src/object/CMakeLists.txt +++ b/src/object/CMakeLists.txt @@ -12,6 +12,7 @@ set(object_SRC sp-clippath.cpp sp-conn-end-pair.cpp sp-conn-end.cpp + sp-conn-router.cpp sp-defs.cpp sp-desc.cpp sp-dimensions.cpp @@ -97,6 +98,7 @@ set(object_SRC sp-clippath.h sp-conn-end-pair.h sp-conn-end.h + sp-conn-router.h sp-defs.h sp-desc.h sp-dimensions.h diff --git a/src/object/object-set.h b/src/object/object-set.h index 7844237f4e..566c414e88 100644 --- a/src/object/object-set.h +++ b/src/object/object-set.h @@ -368,7 +368,8 @@ public: void removeLPESRecursive(bool keep_paths); void relink(); void cloneOriginal(); - void cloneOriginalPathLPE(bool allow_transforms = false); + void cloneOriginalPathLPE(bool allow_transforms = false, SPObject *parent = nullptr, + size_t pos = Glib::ustring::npos); Inkscape::XML::Node* group(); void popFromGroup(); void ungroup(); diff --git a/src/object/sp-conn-end-pair.cpp b/src/object/sp-conn-end-pair.cpp index c6aa18a485..ea43adcd53 100644 --- a/src/object/sp-conn-end-pair.cpp +++ b/src/object/sp-conn-end-pair.cpp @@ -13,20 +13,23 @@ */ #include -#include #include +#include +#include "3rdparty/adaptagrams/libavoid/router.h" #include "attributes.h" -#include "sp-conn-end.h" -#include "uri.h" #include "display/curve.h" -#include "xml/repr.h" -#include "sp-path.h" -#include "sp-use.h" -#include "3rdparty/adaptagrams/libavoid/router.h" #include "document.h" +#include "inkscape.h" +#include "live_effects/effect-enum.h" +#include "live_effects/lpe-connector.h" +#include "sp-conn-end.h" +#include "sp-conn-router.h" #include "sp-item-group.h" - +#include "sp-path.h" +#include "sp-use.h" +#include "uri.h" +#include "xml/repr.h" SPConnEndPair::SPConnEndPair(SPPath *const owner) : _path(owner) @@ -50,6 +53,13 @@ SPConnEndPair::~SPConnEndPair() delete handle_ix; handle_ix = nullptr; } + SPDocument *document = _path->document; + if (document) { + SPConnRouter *router = document->getRouter(); + if (router) { + router->removeConnEndPair(this); + } + } } void SPConnEndPair::release() @@ -70,6 +80,7 @@ void SPConnEndPair::release() if (_connRef && routerInstanceExists) { _connRef->router()->deleteConnector(_connRef); + _path->document->getRouter()->release(); } _connRef = nullptr; @@ -95,6 +106,24 @@ static void avoid_conn_transformed(Geom::Affine const */*mp*/, SPItem *moved_ite } } +void SPConnEndPair::crossings(const bool optimisedForConnectorType, const bool fromRouter) +{ + auto router = _path->document->getRouter(); + if (router) { + if (!fromRouter) { + std::vector points = router->crossings(); + SPLPEItem *lpeitem = dynamic_cast(_path); + if (lpeitem) { + Inkscape::LivePathEffect::LPEConnector *lpe = dynamic_cast( + lpeitem->getPathEffectOfType(Inkscape::LivePathEffect::CONNECTOR)); + if (lpe) { + lpe->connector_points.param_set_and_write_new_value(points); + } + } + _path->document->getRouter()->reroute_router(this); + } + } +} void SPConnEndPair::setAttr(const SPAttr key, gchar const *const value) { @@ -105,11 +134,12 @@ void SPConnEndPair::setAttr(const SPAttr key, gchar const *const value) if (!_connRef) { _connType = new_conn_type; - Avoid::Router *router = _path->document->getRouter(); + Avoid::Router *router = _path->document->getRouter()->getAvoidRouter(); _connRef = new Avoid::ConnRef(router); _connRef->setRoutingType(new_conn_type == SP_CONNECTOR_POLYLINE ? Avoid::ConnType_PolyLine : Avoid::ConnType_Orthogonal); _transformed_connection = _path->connectTransformed(sigc::ptr_fun(&avoid_conn_transformed)); + _path->document->getRouter()->addConnEndPair(this); } else if (new_conn_type != _connType) { _connType = new_conn_type; _connRef->setRoutingType(new_conn_type == SP_CONNECTOR_POLYLINE ? @@ -120,6 +150,7 @@ void SPConnEndPair::setAttr(const SPAttr key, gchar const *const value) _connType = SP_CONNECTOR_NOAVOID; if (_connRef) { + _path->document->getRouter()->removeConnEndPair(this); _connRef->router()->deleteConnector(_connRef); _connRef = nullptr; _transformed_connection.disconnect(); @@ -367,13 +398,20 @@ bool SPConnEndPair::reroutePathFromLibavoid() // Do nothing return false; } - - SPCurve *curve = _path->curve(); - + SPLPEItem *lpeitem = dynamic_cast(_path); + SPCurve *curve = nullptr; + if (lpeitem && lpeitem->hasPathEffectOfType(Inkscape::LivePathEffect::EffectType::CONNECTOR)) { + curve = const_cast(_path->curveForEdit()); + } else { + curve = _path->curve(); + } recreateCurve(curve, _connRef, _connCurvature); Geom::Affine doc2item = _path->i2doc_affine().inverse(); curve->transform(doc2item); + /* if (lpeitem && lpeitem->hasPathEffectOfType(Inkscape::LivePathEffect::EffectType::CONNECTOR)) { + _path->setCurveBeforeLPE(std::move(curve)); + } */ return true; } diff --git a/src/object/sp-conn-end-pair.h b/src/object/sp-conn-end-pair.h index 2391123504..ee12d0b1aa 100644 --- a/src/object/sp-conn-end-pair.h +++ b/src/object/sp-conn-end-pair.h @@ -16,10 +16,19 @@ #include #include -#include "3rdparty/adaptagrams/libavoid/connector.h" #include "attributes.h" +namespace Avoid { +class ConnRef; +} + +namespace Inkscape { +namespace LivePathEffect { +class LPEConnector; +} +} // namespace Inkscape +class SPConnRouter; class SPConnEnd; class SPCurve; class SPPath; @@ -50,6 +59,7 @@ public: friend void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, double curvature); void tellLibavoidNewEndpoints(bool const processTransaction = false); bool reroutePathFromLibavoid(); + void crossings(const bool optimisedForConnectorType, const bool fromRouter); void makePathInvalid(); void update(); bool isAutoRoutingConn(); @@ -70,6 +80,8 @@ private: // A sigc connection for transformed signal. sigc::connection _transformed_connection; + friend class SPConnRouter; + friend class Inkscape::LivePathEffect::LPEConnector; }; diff --git a/src/object/sp-conn-end.cpp b/src/object/sp-conn-end.cpp index a419ae1784..f96ee194fb 100644 --- a/src/object/sp-conn-end.cpp +++ b/src/object/sp-conn-end.cpp @@ -10,18 +10,18 @@ #include "sp-conn-end.h" #include -#include #include +#include +#include "2geom/path-intersection.h" #include "bad-uri-exception.h" #include "display/curve.h" -#include "xml/repr.h" -#include "sp-path.h" -#include "uri.h" #include "document.h" +#include "live_effects/effect-enum.h" #include "sp-item-group.h" -#include "2geom/path-intersection.h" - +#include "sp-path.h" +#include "uri.h" +#include "xml/repr.h" static void change_endpts(SPCurve *const curve, double const endPos[2]); @@ -107,7 +107,13 @@ static bool try_get_intersect_point_with_item(SPPath* conn, SPItem* item, const bool at_start, double& intersect_pos) { // Copy the curve and apply transformations up to common ancestor. - auto conn_curve = conn->curve()->copy(); + SPLPEItem *lpeitem = dynamic_cast(conn); + std::unique_ptr conn_curve; + if (lpeitem && lpeitem->hasPathEffectOfType(Inkscape::LivePathEffect::EffectType::CONNECTOR)) { + conn_curve = conn->curveBeforeLPE()->copy(); + } else { + conn_curve = conn->curve()->copy(); + } conn_curve->transform(conn_transform); Geom::PathVector conn_pv = conn_curve->get_pathvector(); @@ -137,8 +143,8 @@ static bool try_get_intersect_point_with_item(SPPath* conn, SPItem* item, return result; } - -static void sp_conn_get_route_and_redraw(SPPath *const path, const bool updatePathRepr = true) +static void sp_conn_get_route_and_redraw(SPPath *const path, const bool updatePathRepr = true, + const bool fromRouter = false) { // Get the new route around obstacles. bool rerouted = path->connEndPair.reroutePathFromLibavoid(); @@ -154,7 +160,14 @@ static void sp_conn_get_route_and_redraw(SPPath *const path, const bool updatePa // Set sensible values in case there the connector ends are not // attached to any shapes. - Geom::PathVector conn_pv = path->curve()->get_pathvector(); + SPLPEItem *lpeitem = dynamic_cast(path); + SPCurve *conn_curve = nullptr; + if (lpeitem && lpeitem->hasPathEffectOfType(Inkscape::LivePathEffect::EffectType::CONNECTOR)) { + conn_curve = path->curveForEdit(); + } else { + conn_curve = path->curve(); + } + Geom::PathVector conn_pv = conn_curve->get_pathvector(); double endPos[2] = { 0.0, static_cast(conn_pv[0].size()) }; for (unsigned h = 0; h < 2; ++h) { @@ -165,7 +178,8 @@ static void sp_conn_get_route_and_redraw(SPPath *const path, const bool updatePa (h == 0), endPos[h]); } } - change_endpts(path->curve(), endPos); + change_endpts(conn_curve, endPos); + path->connEndPair.crossings(true, fromRouter); if (updatePathRepr) { path->updateRepr(); path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); @@ -202,9 +216,9 @@ void sp_conn_reroute_path_immediate(SPPath *const path) sp_conn_get_route_and_redraw(path, updatePathRepr); } -void sp_conn_redraw_path(SPPath *const path) +void sp_conn_redraw_path(SPPath *const path, const bool fromRouter) { - sp_conn_get_route_and_redraw(path); + sp_conn_get_route_and_redraw(path, true, fromRouter); } diff --git a/src/object/sp-conn-end.h b/src/object/sp-conn-end.h index 1779a5b9b6..961578926c 100644 --- a/src/object/sp-conn-end.h +++ b/src/object/sp-conn-end.h @@ -57,7 +57,7 @@ void sp_conn_end_href_changed(SPObject *old_ref, SPObject *ref, SPConnEnd *connEnd, SPPath *path, unsigned const handle_ix); void sp_conn_reroute_path(SPPath *const path); void sp_conn_reroute_path_immediate(SPPath *const path); -void sp_conn_redraw_path(SPPath *const path); +void sp_conn_redraw_path(SPPath *const path, const bool fromRouter = false); void sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix); diff --git a/src/object/sp-conn-router.cpp b/src/object/sp-conn-router.cpp new file mode 100644 index 0000000000..787d590a7c --- /dev/null +++ b/src/object/sp-conn-router.cpp @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A class for handling basic routing. + * + * Authors: + * Jabier Arraiza + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "sp-conn-router.h" + +#include "3rdparty/adaptagrams/libavoid/router.h" +#include "display/curve.h" +#include "sp-conn-end-pair.h" +#include "sp-conn-end.h" +#include "sp-path.h" + +SPConnRouter::SPConnRouter(const unsigned int flags) + : _router(new Avoid::Router(flags)) + , _lpe("") +{} + +SPConnRouter::~SPConnRouter() +{ + _conn_endpairs.clear(); +} + +void SPConnRouter::release() +{ + _conn_endpairs.clear(); +} + +void SPConnRouter::set_lpe(Glib::ustring lpe) +{ + _lpe = lpe; +} + +size_t SPConnRouter::size() +{ + return _conn_endpairs.size(); +} + +void SPConnRouter::addConnEndPair(SPConnEndPair *cnnpair) +{ + _conn_endpairs.insert(cnnpair); +} + +void SPConnRouter::removeConnEndPair(SPConnEndPair *cnnpair) +{ + std::set::iterator itr = _conn_endpairs.find(cnnpair); + if (itr != _conn_endpairs.end()) { + _conn_endpairs.erase(itr); + } +} + +void SPConnRouter::reroute_router(SPConnEndPair *origin) +{ + for (auto cnnpair : _conn_endpairs) { + if (cnnpair != origin) { + sp_conn_redraw_path(cnnpair->_path, true); + } + } +} + +void SPConnRouter::apply_style(Glib::ustring style) +{ + for (auto cnnpair : _conn_endpairs) { + if (cnnpair->_path->getAttribute("style") && cnnpair->_path->getAttribute("style") != style) { + cnnpair->_path->setAttribute("style", style); + } + } +} + +std::pair> SPConnRouter::pairs_at_point(Geom::Point p) +{ + SPConnEndPair *first = nullptr; + std::vector crossings; + for (auto cnnpair : _conn_endpairs) { + Geom::Coord distance; + boost::optional pos = cnnpair->_path->curve()->get_pathvector().nearestTime(p, &distance); + if (distance < 0.01 && pos) { + if (!first) { + first = cnnpair; + } + crossings.push_back(cnnpair); + } + } + return std::make_pair(first, crossings); +} + +std::vector SPConnRouter::crossings(const bool optimisedForConnectorType) +{ + Avoid::Router *router = getAvoidRouter(); + if (router) { + Avoid::PointSet crossingPoints; + Avoid::ConnRefList::iterator fin = router->connRefs.end(); + for (Avoid::ConnRefList::iterator i = router->connRefs.begin(); i != fin; ++i) { + Avoid::Polygon iRoute = (*i)->displayRoute(); + Avoid::ConnRefList::iterator j = i; + for (++j; j != fin; ++j) { + // Determine if this pair overlap + Avoid::Polygon jRoute = (*j)->displayRoute(); + Avoid::ConnRef *iConn = (optimisedForConnectorType) ? *i : nullptr; + Avoid::ConnRef *jConn = (optimisedForConnectorType) ? *j : nullptr; + Avoid::ConnectorCrossings cross(iRoute, true, jRoute, iConn, jConn); + cross.crossingPoints = &crossingPoints; + cross.checkForBranchingSegments = true; + for (size_t jInd = 1; jInd < jRoute.size(); ++jInd) { + const bool finalSegment = ((jInd + 1) == jRoute.size()); + + // Normal crossings aren't counted if we pass the pointers + // for the connectors, so don't pass them. + cross.countForSegment(jInd, finalSegment); + } + } + } + std::vector points; + for (auto crossp : crossingPoints) { + Geom::Point point(crossp.x, crossp.y); + points.push_back(point); + } + return points; + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-conn-router.h b/src/object/sp-conn-router.h new file mode 100644 index 0000000000..aee4626b10 --- /dev/null +++ b/src/object/sp-conn-router.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_SP_CONN_ROUTER +#define SEEN_SP_CONN_ROUTER + +/* + * A class for handling a connector router. + * + * Authors: + * Jabier Arraiza + * + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include +#include + +#include "2geom/point.h" + +namespace Avoid { +class Router; +} + +class SPPath; +class SPConnEndPair; +class SPConnEnd; + +class SPConnRouter +{ +public: + SPConnRouter(const unsigned int flags); + ~SPConnRouter(); + void release(); + size_t size(); + Glib::ustring get_lpe() const { return _lpe; } + void set_lpe(Glib::ustring lpe); + void addConnEndPair(SPConnEndPair *cnnpair); + void removeConnEndPair(SPConnEndPair *cnnpair); + void reroute_router(SPConnEndPair *origin); + void apply_style(Glib::ustring style); + std::vector crossings(const bool optimisedForConnectorType = true); + std::pair> pairs_at_point(Geom::Point p); + Avoid::Router *getAvoidRouter() const { return _router; } + +private: + Glib::ustring _lpe; + std::set _conn_endpairs; + Avoid::Router *_router; +}; + +#endif /* !SEEN_SP_CONN_ROUTER */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/object/sp-lpe-item.cpp b/src/object/sp-lpe-item.cpp index 0e86471845..7ec6c876a7 100755 --- a/src/object/sp-lpe-item.cpp +++ b/src/object/sp-lpe-item.cpp @@ -698,7 +698,7 @@ void SPLPEItem::removeCurrentPathEffect(bool keep_paths) void SPLPEItem::removeAllPathEffects(bool keep_paths) { if (keep_paths) { - if (path_effect_list->empty()) { + if (!path_effect_list || path_effect_list->empty()) { return; } } @@ -771,7 +771,7 @@ void SPLPEItem::upCurrentPathEffect() /** used for shapes so they can see if they should also disable shape calculation and read from d= */ bool SPLPEItem::hasBrokenPathEffect() const { - if (path_effect_list->empty()) { + if (!path_effect_list || path_effect_list->empty()) { return false; } @@ -789,7 +789,7 @@ bool SPLPEItem::hasBrokenPathEffect() const bool SPLPEItem::hasPathEffectOfType(int const type, bool is_ready) const { - if (path_effect_list->empty()) { + if (!path_effect_list || path_effect_list->empty()) { return false; } @@ -1239,7 +1239,7 @@ Inkscape::LivePathEffect::Effect* SPLPEItem::getNextLPE(Inkscape::LivePathEffect size_t SPLPEItem::countLPEOfType(int const type, bool inc_hidden, bool is_ready) const { size_t counter = 0; - if (path_effect_list->empty()) { + if (!path_effect_list || path_effect_list->empty()) { return counter; } @@ -1340,7 +1340,10 @@ bool SPLPEItem::forkPathEffectsIfNecessary(unsigned int nr_of_allowed_users, boo // Clones of the LPEItem will increase the refcount of the lpeobjects. // Therefore, nr_of_allowed_users should be increased with the number of clones (i.e. refs to the lpeitem) nr_of_allowed_users += this->hrefcount; - + SPPath *path = dynamic_cast(this); + if (path && path->getAttribute("inkscape:connector-type")) { + return false; + } std::vector old_lpeobjs, new_lpeobjs; PathEffectList effect_list = this->getEffectList(); for (auto & it : effect_list) diff --git a/src/object/sp-shape.cpp b/src/object/sp-shape.cpp index 1d5df5ea37..68c8307676 100644 --- a/src/object/sp-shape.cpp +++ b/src/object/sp-shape.cpp @@ -1157,6 +1157,17 @@ SPCurve const *SPShape::curveForEdit() const return curve(); } +/** + * Return a borrowed pointer of the curve for edit + */ +SPCurve *SPShape::curveForEdit() +{ + if (_curve_before_lpe) { + return _curve_before_lpe.get(); + } + return curve(); +} + void SPShape::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { if (this->_curve == nullptr) { return; diff --git a/src/object/sp-shape.h b/src/object/sp-shape.h index fd39213099..47c8b0e7ce 100644 --- a/src/object/sp-shape.h +++ b/src/object/sp-shape.h @@ -43,6 +43,7 @@ public: ~SPShape() override; SPCurve *curve(); + SPCurve *curveForEdit(); SPCurve const *curve() const; SPCurve const *curveBeforeLPE() const; SPCurve const *curveForEdit() const; diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 32ac0432f3..52e00ceac9 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -139,7 +139,7 @@ static char const preferences_skeleton[] = - + diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index ac76c43dbc..7fd8f17503 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -2950,7 +2950,7 @@ void ObjectSet::cloneOriginal() /** * This applies the Fill Between Many LPE, and has it refer to the selection. */ -void ObjectSet::cloneOriginalPathLPE(bool allow_transforms) +void ObjectSet::cloneOriginalPathLPE(bool allow_transforms, SPObject *parent, size_t pos) { Inkscape::SVGOStringStream os; @@ -2958,7 +2958,7 @@ void ObjectSet::cloneOriginalPathLPE(bool allow_transforms) auto items_= items(); bool multiple = false; for (auto i=items_.begin();i!=items_.end();++i){ - if (SP_IS_SHAPE(*i) || SP_IS_TEXT(*i) || SP_IS_GROUP(*i)) { + if (SP_IS_SHAPE(*i) || SP_IS_TEXT(*i) || SP_IS_GROUP(*i) || SP_IS_SYMBOL(*i)) { if (firstItem) { os << "|"; multiple = true; @@ -2970,7 +2970,10 @@ void ObjectSet::cloneOriginalPathLPE(bool allow_transforms) } if (firstItem) { Inkscape::XML::Document *xml_doc = document()->getReprDoc(); - SPObject *parent = firstItem->parent; + if (!parent) { + parent = firstItem->parent; + pos = firstItem->getPosition(); + } // create the LPE Inkscape::XML::Node *lpe_repr = xml_doc->createElement("inkscape:path-effect"); if (multiple) { @@ -2990,23 +2993,37 @@ void ObjectSet::cloneOriginalPathLPE(bool allow_transforms) Inkscape::GC::release(lpe_repr); Inkscape::XML::Node* clone = nullptr; SPGroup *firstgroup = dynamic_cast(firstItem); + SPSymbol *firstsymbol = dynamic_cast(firstItem); if (firstgroup) { if (!multiple) { clone = firstgroup->getRepr()->duplicate(xml_doc); } + } else if (firstsymbol) { + if (!multiple) { + clone = firstsymbol->getRepr()->duplicate(xml_doc); + } } else { // create the new path clone = xml_doc->createElement("svg:path"); clone->setAttribute("d", "M 0 0"); - } if (clone) { // add the new clone to the top of the original's parent - parent->appendChildRepr(clone); + if (pos != Glib::ustring::npos) { + Inkscape::XML::Node *prev = nullptr; + if (pos) { + prev = parent->nthChild(pos - 1)->getRepr(); + } + parent->addChild(clone, prev); + } else { + parent->appendChildRepr(clone); + } // select the new object: set(clone); Inkscape::GC::release(clone); - SPObject *clone_obj = document()->getObjectById(clone->attribute("id")); + const gchar *id = clone->attribute("id"); + unSymbol(); + SPObject *clone_obj = document()->getObjectById(id); SPLPEItem *clone_lpeitem = dynamic_cast(clone_obj); if (clone_lpeitem) { clone_lpeitem->addPathEffect(lpe_id_href, false); diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index d25cf9e315..fe6ced4a65 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -767,7 +767,7 @@ void InkscapePreferences::AddNewObjectsStyle(DialogPage &p, Glib::ustring const else p.add_group_header( _("Style of new objects")); PrefRadioButton* current = Gtk::manage( new PrefRadioButton); - current->init ( _("Last used style"), prefs_path + "/usecurrent", 1, true, nullptr); + current->init(_("Last used style"), prefs_path + "/usecurrent", 1, true, current); p.add_line( true, "", *current, "", _("Apply the style you last set on an object")); @@ -1018,6 +1018,7 @@ void InkscapePreferences::initPageTools() //Connector this->AddSelcueCheckbox(_page_connector, "/tools/connector", true); + this->AddNewObjectsStyle(_page_connector, "/tools/connector"); _page_connector.add_line(false, "", _connector_ignore_text, "", _("If on, connector attachment points will not be shown for text objects")); diff --git a/src/ui/dialog/livepatheffect-add.cpp b/src/ui/dialog/livepatheffect-add.cpp index e1c8003cec..35e03b16a4 100644 --- a/src/ui/dialog/livepatheffect-add.cpp +++ b/src/ui/dialog/livepatheffect-add.cpp @@ -544,6 +544,10 @@ bool LivePathEffectAdd::on_filter(Gtk::FlowBoxChild *child) disable = true; } else if (_item_type == "shape" && !converter.get_on_shape(data->id)) { disable = true; + } else if (_item_type == "connector" && data->id == Inkscape::LivePathEffect::CONNECTOR) { + disable = false; + } else if (_item_type == "connector" && data->id != Inkscape::LivePathEffect::CONNECTOR) { + disable = true; } else if (_item_type == "path" && !converter.get_on_path(data->id)) { disable = true; } @@ -933,6 +937,8 @@ void LivePathEffectAdd::show(SPDesktop *desktop) dial._item_type = ""; if (group) { dial._item_type = "group"; + } else if (path && path->getAttribute("inkscape:connector-type")) { + dial._item_type = "connector"; } else if (path) { dial._item_type = "path"; } else if (shape) { diff --git a/src/ui/dialog/livepatheffect-editor.cpp b/src/ui/dialog/livepatheffect-editor.cpp index 8a22570693..68297c12a0 100644 --- a/src/ui/dialog/livepatheffect-editor.cpp +++ b/src/ui/dialog/livepatheffect-editor.cpp @@ -20,27 +20,25 @@ #include "desktop.h" #include "document-undo.h" #include "document.h" -#include "inkscape.h" -#include "livepatheffect-add.h" -#include "path-chemistry.h" -#include "selection-chemistry.h" -#include "verbs.h" - #include "helper/action.h" -#include "ui/icon-loader.h" - +#include "inkscape.h" #include "live_effects/effect.h" #include "live_effects/lpeobject-reference.h" #include "live_effects/lpeobject.h" - +#include "livepatheffect-add.h" #include "object/sp-item-group.h" #include "object/sp-path.h" -#include "object/sp-use.h" +#include "object/sp-symbol.h" #include "object/sp-text.h" - +#include "object/sp-use.h" +#include "path-chemistry.h" +#include "selection-chemistry.h" +#include "ui/icon-loader.h" #include "ui/icon-names.h" +#include "ui/tools-switch.h" #include "ui/tools/node-tool.h" #include "ui/widget/imagetoggler.h" +#include "verbs.h" namespace Inkscape { namespace UI { @@ -319,6 +317,7 @@ LivePathEffectEditor::onSelectionChanged(Inkscape::Selection *sel) SPItem *orig = use->get_original(); if ( dynamic_cast(orig) || dynamic_cast(orig) || + dynamic_cast(orig) || dynamic_cast(orig) ) { // Note that an SP_USE cannot have an LPE applied, so we only need to worry about the "add effect" case. @@ -426,6 +425,7 @@ LivePathEffectEditor::onAdd() if ( sel && !sel->isEmpty() ) { SPItem *item = sel->singleItem(); if (item) { + tools_switch(current_desktop, TOOLS_SELECT); if ( dynamic_cast(item) ) { // show effectlist dialog using Inkscape::UI::Dialog::LivePathEffectAdd; @@ -457,14 +457,18 @@ LivePathEffectEditor::onAdd() // convert to path, apply CLONE_ORIGINAL LPE, link it to the cloned path // test whether linked object is supported by the CLONE_ORIGINAL LPE + SPItem *orig = use->get_original(); if ( dynamic_cast(orig) || dynamic_cast(orig) || + dynamic_cast(orig) || dynamic_cast(orig) ) { + // allow use symbols and position in sampe index + SPObject *parent = item->parent; + size_t pos = item->getPosition(); // select original sel->set(orig); - // delete clone but remember its id and transform gchar *id = g_strdup(item->getRepr()->attribute("id")); gchar *transform = g_strdup(item->getRepr()->attribute("transform")); @@ -472,7 +476,7 @@ LivePathEffectEditor::onAdd() item = nullptr; // run sp_selection_clone_original_path_lpe - sel->cloneOriginalPathLPE(true); + sel->cloneOriginalPathLPE(true, parent, pos); SPItem *new_item = sel->singleItem(); // Check that the cloning was successful. We don't want to change the ID of the original referenced path! diff --git a/src/ui/dialog/livepatheffect-editor.cpp.orig b/src/ui/dialog/livepatheffect-editor.cpp.orig new file mode 100644 index 0000000000..bb09fe2626 --- /dev/null +++ b/src/ui/dialog/livepatheffect-editor.cpp.orig @@ -0,0 +1,650 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Live Path Effect editing dialog - implementation. + */ +/* Authors: + * Johan Engelen + * Steren Giannini + * Bastien Bouclet + * Abhishek Sharma + * + * Copyright (C) 2007 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "livepatheffect-editor.h" + +#include + +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "inkscape.h" +#include "livepatheffect-add.h" +#include "path-chemistry.h" +#include "selection-chemistry.h" +#include "verbs.h" + +#include "helper/action.h" +#include "ui/icon-loader.h" + +#include "live_effects/effect.h" +#include "live_effects/lpeobject-reference.h" +#include "live_effects/lpeobject.h" + +#include "object/sp-item-group.h" +#include "object/sp-path.h" +#include "object/sp-use.h" +#include "object/sp-symbol.h" +#include "object/sp-text.h" + +#include "ui/icon-names.h" +#include "ui/tools-switch.h" +#include "ui/tools/node-tool.h" +#include "ui/widget/imagetoggler.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + + +/*#################### + * Callback functions + */ + + +void lpeeditor_selection_changed (Inkscape::Selection * selection, gpointer data) +{ + LivePathEffectEditor *lpeeditor = static_cast(data); + lpeeditor->selection_changed_lock = true; + lpeeditor->lpe_list_locked = false; + lpeeditor->onSelectionChanged(selection); + lpeeditor->_on_button_release(nullptr); //to force update widgets + lpeeditor->selection_changed_lock = false; +} + +void lpeeditor_selection_modified (Inkscape::Selection * selection, guint /*flags*/, gpointer data) +{ + + LivePathEffectEditor *lpeeditor = static_cast(data); + lpeeditor->lpe_list_locked = false; + lpeeditor->onSelectionChanged(selection); +} + +static void lpe_style_button(Gtk::Button& btn, char const* iconName) +{ + GtkWidget *child = sp_get_icon_image(iconName, GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_widget_show( child ); + btn.add(*Gtk::manage(Glib::wrap(child))); + btn.set_relief(Gtk::RELIEF_NONE); +} + + +/* + * LivePathEffectEditor + * + * TRANSLATORS: this dialog is accessible via menu Path - Path Effect Editor... + * + */ + +LivePathEffectEditor::LivePathEffectEditor() + : DialogBase("/dialogs/livepatheffect", SP_VERB_DIALOG_LIVE_PATH_EFFECT) + , lpe_list_locked(false) + , effectwidget(nullptr) + , status_label("", Gtk::ALIGN_CENTER) + , effectcontrol_frame("") + , button_add() + , button_remove() + , button_up() + , button_down() + , current_desktop(nullptr) + , current_lpeitem(nullptr) + , current_lperef(nullptr) + , effectcontrol_vbox(Gtk::ORIENTATION_VERTICAL) + , effectlist_vbox(Gtk::ORIENTATION_VERTICAL) + , effectapplication_hbox(Gtk::ORIENTATION_HORIZONTAL, 4) +{ + set_spacing(4); + + //Add the TreeView, inside a ScrolledWindow, with the button underneath: + scrolled_window.add(effectlist_view); + scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + scrolled_window.set_shadow_type(Gtk::SHADOW_IN); + scrolled_window.set_size_request(210, 70); + + effectcontrol_vbox.set_spacing(4); + + effectlist_vbox.pack_start(scrolled_window, Gtk::PACK_EXPAND_WIDGET); + effectlist_vbox.pack_end(toolbar_hbox, Gtk::PACK_SHRINK); + effectcontrol_eventbox.add_events(Gdk::BUTTON_RELEASE_MASK); + effectcontrol_eventbox.signal_button_release_event().connect(sigc::mem_fun(*this, &LivePathEffectEditor::_on_button_release) ); + effectcontrol_eventbox.add(effectcontrol_vbox); + effectcontrol_frame.add(effectcontrol_eventbox); + + button_add.set_tooltip_text(_("Add path effect")); + lpe_style_button(button_add, INKSCAPE_ICON("list-add")); + button_add.set_relief(Gtk::RELIEF_NONE); + + button_remove.set_tooltip_text(_("Delete current path effect")); + lpe_style_button(button_remove, INKSCAPE_ICON("list-remove")); + button_remove.set_relief(Gtk::RELIEF_NONE); + + button_up.set_tooltip_text(_("Raise the current path effect")); + lpe_style_button(button_up, INKSCAPE_ICON("go-up")); + button_up.set_relief(Gtk::RELIEF_NONE); + + button_down.set_tooltip_text(_("Lower the current path effect")); + lpe_style_button(button_down, INKSCAPE_ICON("go-down")); + button_down.set_relief(Gtk::RELIEF_NONE); + + // Add toolbar items to toolbar + toolbar_hbox.set_layout (Gtk::BUTTONBOX_END); + toolbar_hbox.add( button_add ); + toolbar_hbox.set_child_secondary( button_add , true); + toolbar_hbox.add( button_remove ); + toolbar_hbox.set_child_secondary( button_remove , true); + toolbar_hbox.add( button_up ); + toolbar_hbox.add( button_down ); + + //Create the Tree model: + effectlist_store = Gtk::ListStore::create(columns); + effectlist_view.set_model(effectlist_store); + effectlist_view.set_headers_visible(false); + + // Handle tree selections + effectlist_selection = effectlist_view.get_selection(); + effectlist_selection->signal_changed().connect( sigc::mem_fun(*this, &LivePathEffectEditor::on_effect_selection_changed) ); + + //Add the visibility icon column: + Inkscape::UI::Widget::ImageToggler *eyeRenderer = Gtk::manage( new Inkscape::UI::Widget::ImageToggler( + INKSCAPE_ICON("object-visible"), INKSCAPE_ICON("object-hidden")) ); + int visibleColNum = effectlist_view.append_column("is_visible", *eyeRenderer) - 1; + eyeRenderer->signal_toggled().connect( sigc::mem_fun(*this, &LivePathEffectEditor::on_visibility_toggled) ); + eyeRenderer->property_activatable() = true; + Gtk::TreeViewColumn* col = effectlist_view.get_column(visibleColNum); + if ( col ) { + col->add_attribute( eyeRenderer->property_active(), columns.col_visible ); + } + + //Add the effect name column: + effectlist_view.append_column("Effect", columns.col_name); + + pack_start(effectlist_vbox, true, true); + pack_start(status_label, false, false); + pack_start(effectcontrol_frame, false, false); + + effectcontrol_frame.hide(); + selection_changed_lock = false; + // connect callback functions to buttons + button_add.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onAdd)); + button_remove.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onRemove)); + button_up.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onUp)); + button_down.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onDown)); + + update(); + show_all_children(); +} + +LivePathEffectEditor::~LivePathEffectEditor() +{ + if (effectwidget) { + effectcontrol_vbox.remove(*effectwidget); + delete effectwidget; + effectwidget = nullptr; + } + + if (current_desktop) { + selection_changed_connection.disconnect(); + selection_modified_connection.disconnect(); + } +} + +bool LivePathEffectEditor::_on_button_release(GdkEventButton* button_event) { + Glib::RefPtr sel = effectlist_view.get_selection(); + if (sel->count_selected_rows () == 0) { + return true; + } + Gtk::TreeModel::iterator it = sel->get_selected(); + LivePathEffect::LPEObjectReference * lperef = (*it)[columns.lperef]; + if (lperef && current_lpeitem && current_lperef != lperef) { + if (lperef->getObject()) { + LivePathEffect::Effect * effect = lperef->lpeobject->get_lpe(); + if (effect) { + effect->refresh_widgets = true; + showParams(*effect); + } + } + } + return true; +} + +void +LivePathEffectEditor::showParams(LivePathEffect::Effect& effect) +{ + if (effectwidget && !effect.refresh_widgets) { + return; + } + if (effectwidget) { + effectcontrol_vbox.remove(*effectwidget); + delete effectwidget; + effectwidget = nullptr; + } + effectwidget = effect.newWidget(); + effectcontrol_frame.set_label(effect.getName()); + effectcontrol_vbox.pack_start(*effectwidget, true, true); + + button_remove.show(); + status_label.hide(); + effectcontrol_frame.show(); + effectcontrol_vbox.show_all_children(); + // fixme: add resizing of dialog + effect.refresh_widgets = false; +} + +void +LivePathEffectEditor::selectInList(LivePathEffect::Effect* effect) +{ + Gtk::TreeNodeChildren chi = effectlist_view.get_model()->children(); + for (Gtk::TreeIter ci = chi.begin() ; ci != chi.end(); ci++) { + if (ci->get_value(columns.lperef)->lpeobject->get_lpe() == effect && effectlist_view.get_selection()) + effectlist_view.get_selection()->select(ci); + } +} + + +void +LivePathEffectEditor::showText(Glib::ustring const &str) +{ + if (effectwidget) { + effectcontrol_vbox.remove(*effectwidget); + delete effectwidget; + effectwidget = nullptr; + } + + status_label.show(); + status_label.set_label(str); + + effectcontrol_frame.hide(); + + // fixme: do resizing of dialog ? +} + +void +LivePathEffectEditor::set_sensitize_all(bool sensitive) +{ + //combo_effecttype.set_sensitive(sensitive); + button_add.set_sensitive(sensitive); + button_remove.set_sensitive(sensitive); + effectlist_view.set_sensitive(sensitive); + button_up.set_sensitive(sensitive); + button_down.set_sensitive(sensitive); +} + +void +LivePathEffectEditor::onSelectionChanged(Inkscape::Selection *sel) +{ + if (lpe_list_locked) { + // this was triggered by selecting a row in the list, so skip reloading + lpe_list_locked = false; + return; + } + current_lpeitem = nullptr; + effectlist_store->clear(); + + if ( sel && !sel->isEmpty() ) { + SPItem *item = sel->singleItem(); + if ( item ) { + SPLPEItem *lpeitem = dynamic_cast(item); + if ( lpeitem ) { + effect_list_reload(lpeitem); + current_lpeitem = lpeitem; + set_sensitize_all(true); + if ( lpeitem->hasPathEffect() ) { + Inkscape::LivePathEffect::Effect *lpe = lpeitem->getCurrentLPE(); + if (lpe) { + showParams(*lpe); + lpe_list_locked = true; + selectInList(lpe); + } else { + showText(_("Unknown effect is applied")); + } + } else { + showText(_("Click button to add an effect")); + button_remove.set_sensitive(false); + button_up.set_sensitive(false); + button_down.set_sensitive(false); + } + } else { + SPUse *use = dynamic_cast(item); + if ( use ) { + // test whether linked object is supported by the CLONE_ORIGINAL LPE + SPItem *orig = use->get_original(); + if ( dynamic_cast(orig) || + dynamic_cast(orig) || + dynamic_cast(orig) || + dynamic_cast(orig) ) + { + // Note that an SP_USE cannot have an LPE applied, so we only need to worry about the "add effect" case. + set_sensitize_all(true); + showText(_("Click add button to convert clone")); + button_remove.set_sensitive(false); + button_up.set_sensitive(false); + button_down.set_sensitive(false); + } else { + showText(_("Select a path or shape")); + set_sensitize_all(false); + } + } else { + showText(_("Select a path or shape")); + set_sensitize_all(false); + } + } + } else { + showText(_("Only one item can be selected")); + set_sensitize_all(false); + } + } else { + showText(_("Select a path or shape")); + set_sensitize_all(false); + } +} + +/* + * First clears the effectlist_store, then appends all effects from the effectlist. + */ +void +LivePathEffectEditor::effect_list_reload(SPLPEItem *lpeitem) +{ + effectlist_store->clear(); + + PathEffectList effectlist = lpeitem->getEffectList(); + PathEffectList::iterator it; + for( it = effectlist.begin() ; it!=effectlist.end(); ++it) + { + if ( !(*it)->lpeobject ) { + continue; + } + + if ((*it)->lpeobject->get_lpe()) { + Gtk::TreeModel::Row row = *(effectlist_store->append()); + row[columns.col_name] = (*it)->lpeobject->get_lpe()->getName(); + row[columns.lperef] = *it; + row[columns.col_visible] = (*it)->lpeobject->get_lpe()->isVisible(); + } else { + Gtk::TreeModel::Row row = *(effectlist_store->append()); + row[columns.col_name] = _("Unknown effect"); + row[columns.lperef] = *it; + row[columns.col_visible] = false; + } + } +} + + +void +LivePathEffectEditor::setDesktop(SPDesktop *desktop) +{ + if ( desktop == current_desktop ) { + return; + } + + if (current_desktop) { + selection_changed_connection.disconnect(); + selection_modified_connection.disconnect(); + } + + lpe_list_locked = false; + current_desktop = desktop; + if (desktop) { + Inkscape::Selection *selection = desktop->getSelection(); + selection_changed_connection = selection->connectChanged( + sigc::bind (sigc::ptr_fun(&lpeeditor_selection_changed), this ) ); + selection_modified_connection = selection->connectModified( + sigc::bind (sigc::ptr_fun(&lpeeditor_selection_modified), this ) ); + + onSelectionChanged(selection); + } else { + onSelectionChanged(nullptr); + } +} + +void LivePathEffectEditor::update() +{ + if (!_app) { + std::cerr << "LivePathEffectEditor::update(): _app is null" << std::endl; + return; + } + + setDesktop(getDesktop()); +} + +/*######################################################################## +# BUTTON CLICK HANDLERS (callbacks) +########################################################################*/ + +// TODO: factor out the effect applying code which can be called from anywhere. (selection-chemistry.cpp also needs it) +void +LivePathEffectEditor::onAdd() +{ + Inkscape::Selection *sel = _getSelection(); + if ( sel && !sel->isEmpty() ) { + SPItem *item = sel->singleItem(); + if (item) { + tools_switch(current_desktop, TOOLS_SELECT); + if ( dynamic_cast(item) ) { + // show effectlist dialog + using Inkscape::UI::Dialog::LivePathEffectAdd; + LivePathEffectAdd::show(current_desktop); + if ( !LivePathEffectAdd::isApplied()) { + return; + } + + SPDocument *doc = current_desktop->doc(); + + const LivePathEffect::EnumEffectData *data = + LivePathEffectAdd::getActiveData(); + if (!data) { + return; + } + item = sel->singleItem(); // get new item + + LivePathEffect::Effect::createAndApply(data->key.c_str(), doc, item); + + DocumentUndo::done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Create and apply path effect")); + + lpe_list_locked = false; + onSelectionChanged(sel); + } else { + SPUse *use = dynamic_cast(item); + if ( use ) { + // item is a clone. do not show effectlist dialog. + // convert to path, apply CLONE_ORIGINAL LPE, link it to the cloned path + + // test whether linked object is supported by the CLONE_ORIGINAL LPE + + SPItem *orig = use->get_original(); + if ( dynamic_cast(orig) || + dynamic_cast(orig) || + dynamic_cast(orig) || + dynamic_cast(orig) ) + { + // allow use symbols and position in sampe index + SPObject *parent = item->parent; + size_t pos = item->getPosition(); + // select original + sel->set(orig); + // delete clone but remember its id and transform + gchar *id = g_strdup(item->getRepr()->attribute("id")); + gchar *transform = g_strdup(item->getRepr()->attribute("transform")); + item->deleteObject(false); + item = nullptr; + + // run sp_selection_clone_original_path_lpe + sel->cloneOriginalPathLPE(true, parent, pos); + + SPItem *new_item = sel->singleItem(); + // Check that the cloning was successful. We don't want to change the ID of the original referenced path! + if (new_item && (new_item != orig)) { + new_item->setAttribute("id", id); + new_item->setAttribute("transform", transform); + } + g_free(id); + g_free(transform); + + /// \todo Add the LPE stack of the original path? + + SPDocument *doc = current_desktop->doc(); + DocumentUndo::done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Create and apply Clone original path effect")); + + lpe_list_locked = false; + onSelectionChanged(sel); + } + } + } + } + } +} + +void +LivePathEffectEditor::onRemove() +{ + Inkscape::Selection *sel = _getSelection(); + if ( sel && !sel->isEmpty() ) { + SPItem *item = sel->singleItem(); + SPLPEItem *lpeitem = dynamic_cast(item); + if ( lpeitem ) { + sp_lpe_item_update_patheffect(lpeitem, false, false); + lpeitem->removeCurrentPathEffect(false); + current_lperef = nullptr; + DocumentUndo::done( current_desktop->getDocument(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Remove path effect") ); + lpe_list_locked = false; + onSelectionChanged(sel); + } + } + +} + +void LivePathEffectEditor::onUp() +{ + Inkscape::Selection *sel = _getSelection(); + if ( sel && !sel->isEmpty() ) { + SPItem *item = sel->singleItem(); + SPLPEItem *lpeitem = dynamic_cast(item); + if ( lpeitem ) { + Inkscape::LivePathEffect::Effect *lpe = lpeitem->getCurrentLPE(); + lpeitem->upCurrentPathEffect(); + DocumentUndo::done( current_desktop->getDocument(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Move path effect up") ); + + effect_list_reload(lpeitem); + if (lpe) { + showParams(*lpe); + lpe_list_locked = true; + selectInList(lpe); + } + } + } +} + +void LivePathEffectEditor::onDown() +{ + Inkscape::Selection *sel = _getSelection(); + if ( sel && !sel->isEmpty() ) { + SPItem *item = sel->singleItem(); + SPLPEItem *lpeitem = dynamic_cast(item); + if ( lpeitem ) { + Inkscape::LivePathEffect::Effect *lpe = lpeitem->getCurrentLPE(); + lpeitem->downCurrentPathEffect(); + + DocumentUndo::done( current_desktop->getDocument(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Move path effect down") ); + effect_list_reload(lpeitem); + if (lpe) { + showParams(*lpe); + lpe_list_locked = true; + selectInList(lpe); + } + } + } +} + +void LivePathEffectEditor::on_effect_selection_changed() +{ + Glib::RefPtr sel = effectlist_view.get_selection(); + if (sel->count_selected_rows () == 0) { + button_remove.set_sensitive(false); + return; + } + button_remove.set_sensitive(true); + Gtk::TreeModel::iterator it = sel->get_selected(); + LivePathEffect::LPEObjectReference * lperef = (*it)[columns.lperef]; + + if (lperef && current_lpeitem && current_lperef != lperef) { + // The last condition ignore Gtk::TreeModel may occasionally be changed emitted when nothing has happened + if (lperef->getObject()) { + lpe_list_locked = true; // prevent reload of the list which would lose selection + current_lpeitem->setCurrentPathEffect(lperef); + current_lperef = lperef; + LivePathEffect::Effect * effect = lperef->lpeobject->get_lpe(); + if (effect) { + effect->refresh_widgets = true; + showParams(*effect); + // To reload knots and helper paths + Inkscape::Selection *sel = _getSelection(); + if ( sel && !sel->isEmpty() && !selection_changed_lock) { + SPLPEItem *lpeitem = dynamic_cast(sel->singleItem()); + if (lpeitem) { + sel->clear(); + sel->add(lpeitem); + Inkscape::UI::Tools::sp_update_helperpath(current_desktop); + } + } + } + } + } +} + +void LivePathEffectEditor::on_visibility_toggled( Glib::ustring const& str ) +{ + + Gtk::TreeModel::Children::iterator iter = effectlist_view.get_model()->get_iter(str); + Gtk::TreeModel::Row row = *iter; + + LivePathEffect::LPEObjectReference * lpeobjref = row[columns.lperef]; + + if ( lpeobjref && lpeobjref->lpeobject->get_lpe() ) { + bool newValue = !row[columns.col_visible]; + row[columns.col_visible] = newValue; + /* FIXME: this explicit writing to SVG is wrong. The lpe_item should have a method to disable/enable an effect within its stack. + * So one can call: lpe_item->setActive(lpeobjref->lpeobject); */ + lpeobjref->lpeobject->get_lpe()->getRepr()->setAttribute("is_visible", newValue ? "true" : "false"); + Inkscape::Selection *sel = _getSelection(); + if ( sel && !sel->isEmpty() ) { + SPItem *item = sel->singleItem(); + SPLPEItem *lpeitem = dynamic_cast(item); + if ( lpeitem ) { + lpeobjref->lpeobject->get_lpe()->doOnVisibilityToggled(lpeitem); + } + } + DocumentUndo::done( current_desktop->getDocument(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, + newValue ? _("Activate path effect") : _("Deactivate path effect")); + } +} + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/knot/knot-holder.h b/src/ui/knot/knot-holder.h index 397179b965..b3842f84bc 100644 --- a/src/ui/knot/knot-holder.h +++ b/src/ui/knot/knot-holder.h @@ -32,6 +32,7 @@ class Node; } namespace LivePathEffect { class PowerStrokePointArrayParamKnotHolderEntity; +class ConnectorPointArrayParamKnotHolderEntity; class SatellitesArrayParam; class FilletChamferKnotHolderEntity; } @@ -76,6 +77,7 @@ public: friend class Inkscape::UI::ShapeEditor; // FIXME why? friend class Inkscape::LivePathEffect::SatellitesArrayParam; // why? friend class Inkscape::LivePathEffect::PowerStrokePointArrayParamKnotHolderEntity; // why? + friend class Inkscape::LivePathEffect::ConnectorPointArrayParamKnotHolderEntity; // why? friend class Inkscape::LivePathEffect::FilletChamferKnotHolderEntity; // why? protected: diff --git a/src/ui/tools/connector-tool.cpp b/src/ui/tools/connector-tool.cpp index 5dba80ba23..ff84b2868e 100644 --- a/src/ui/tools/connector-tool.cpp +++ b/src/ui/tools/connector-tool.cpp @@ -67,46 +67,40 @@ * */ -#include -#include +#include "ui/tools/connector-tool.h" +#include +#include #include #include -#include +#include +#include "3rdparty/adaptagrams/libavoid/router.h" #include "context-fns.h" #include "desktop-style.h" #include "desktop.h" +#include "display/control/canvas-item-bpath.h" +#include "display/control/canvas-item-ctrl.h" +#include "display/curve.h" #include "document-undo.h" #include "document.h" #include "inkscape.h" #include "message-context.h" #include "message-stack.h" -#include "selection.h" -#include "snap.h" -#include "verbs.h" - -#include "display/control/canvas-item-bpath.h" -#include "display/control/canvas-item-ctrl.h" -#include "display/curve.h" - -#include "3rdparty/adaptagrams/libavoid/router.h" - #include "object/sp-conn-end.h" +#include "object/sp-conn-router.h" #include "object/sp-flowtext.h" #include "object/sp-namedview.h" #include "object/sp-path.h" +#include "object/sp-symbol.h" #include "object/sp-text.h" #include "object/sp-use.h" -#include "object/sp-symbol.h" - -#include "ui/knot/knot.h" -#include "ui/widget/canvas.h" // Enter events - +#include "selection.h" +#include "snap.h" #include "svg/svg.h" - -#include "ui/tools/connector-tool.h" - +#include "ui/knot/knot.h" +#include "ui/widget/canvas.h" // Enter events +#include "verbs.h" #include "xml/node-event-vector.h" using Inkscape::DocumentUndo; @@ -513,7 +507,6 @@ bool ConnectorTool::_handleButtonPress(GdkEventButton const &bevent) m.unSetup(); } this->_setInitialPoint(p); - } this->state = SP_CONNECTOR_CONTEXT_DRAGGING; ret = true; @@ -819,7 +812,7 @@ void ConnectorTool::_setSubsequentPoint(Geom::Point const p) Avoid::Point dst(d[Geom::X], d[Geom::Y]); if (!this->newConnRef) { - Avoid::Router *router = desktop->getDocument()->getRouter(); + Avoid::Router *router = desktop->getDocument()->getRouter()->getAvoidRouter(); this->newConnRef = new Avoid::ConnRef(router); this->newConnRef->setEndpoint(Avoid::VertID::src, src); if (this->isOrthogonal) @@ -1100,8 +1093,11 @@ void ConnectorTool::_activeShapeAddKnot(SPItem* item, SPItem* subitem) void ConnectorTool::_setActiveShape(SPItem *item) { - g_assert(item != nullptr ); - + // We can get into this situation so remove the assert and do a check + // g_assert(item != nullptr ); + if (!item) { + return; + } if (this->active_shape != item) { // The active shape has changed // Rebuild everything -- GitLab From 55ca3d5d440f786032e51d19872f215dc866bdbc Mon Sep 17 00:00:00 2001 From: Jabier Arraiza Date: Tue, 24 Nov 2020 22:48:36 +0100 Subject: [PATCH 2/3] Add @speleo3 improvements, thanks --- src/live_effects/lpe-connector.cpp | 3 +++ src/object/sp-conn-router.cpp | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/live_effects/lpe-connector.cpp b/src/live_effects/lpe-connector.cpp index dc13eb0a93..f89c8f95b8 100644 --- a/src/live_effects/lpe-connector.cpp +++ b/src/live_effects/lpe-connector.cpp @@ -232,6 +232,9 @@ bool sp_reorder_along(Geom::Point a, Geom::Point b, Geom::Point c) Geom::PathVector LPEConnector::doEffect_path(Geom::PathVector const &path_in) { + if (path_in.empty()) { + return path_in; + } Geom::PathVector path_out = path_in; SPPath *path = dynamic_cast(sp_lpe_item); SPGroup *group = dynamic_cast(sp_lpe_item); diff --git a/src/object/sp-conn-router.cpp b/src/object/sp-conn-router.cpp index 787d590a7c..36813bcca3 100644 --- a/src/object/sp-conn-router.cpp +++ b/src/object/sp-conn-router.cpp @@ -91,6 +91,7 @@ std::pair> SPConnRouter::pairs_at_ std::vector SPConnRouter::crossings(const bool optimisedForConnectorType) { + std::vector points; Avoid::Router *router = getAvoidRouter(); if (router) { Avoid::PointSet crossingPoints; @@ -115,13 +116,12 @@ std::vector SPConnRouter::crossings(const bool optimisedForConnecto } } } - std::vector points; for (auto crossp : crossingPoints) { Geom::Point point(crossp.x, crossp.y); points.push_back(point); } - return points; } + return points; } /* -- GitLab From 43419f6884aec90ae93a108b23c4bbdaaa81e025 Mon Sep 17 00:00:00 2001 From: Jabier Arraiza Date: Fri, 27 Nov 2020 23:29:31 +0100 Subject: [PATCH 3/3] Add continuous to connectors --- src/attributes.cpp | 1 + src/attributes.h | 1 + src/live_effects/lpe-connector.cpp | 19 ++++++++++++++----- src/object/sp-conn-end-pair.cpp | 16 ++++++++++++++++ src/object/sp-conn-end-pair.h | 2 ++ src/object/sp-path.cpp | 1 + src/path-chemistry.cpp | 1 + testfiles/src/attributes-test.cpp | 1 + 8 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/attributes.cpp b/src/attributes.cpp index 531bc16edb..d8ede579ca 100644 --- a/src/attributes.cpp +++ b/src/attributes.cpp @@ -143,6 +143,7 @@ static SPStyleProp const props[] = { /* (Note: XML representation of connectors may change in future.) */ {SPAttr::CONNECTOR_TYPE, "inkscape:connector-type"}, {SPAttr::CONNECTOR_CURVATURE, "inkscape:connector-curvature"}, + {SPAttr::CONNECTOR_CONTINUOUS, "inkscape:connector-continuous"}, {SPAttr::INKSCAPE_CONNECTOR_SPACING, "inkscape:connector-spacing"}, {SPAttr::CONNECTION_START, "inkscape:connection-start"}, {SPAttr::CONNECTION_END, "inkscape:connection-end"}, diff --git a/src/attributes.h b/src/attributes.h index 95553b4ae5..65ef218629 100644 --- a/src/attributes.h +++ b/src/attributes.h @@ -142,6 +142,7 @@ enum class SPAttr { INKSCAPE_ORIGINAL_D, CONNECTOR_TYPE, CONNECTOR_CURVATURE, + CONNECTOR_CONTINUOUS, INKSCAPE_CONNECTOR_SPACING, CONNECTION_START, CONNECTION_END, diff --git a/src/live_effects/lpe-connector.cpp b/src/live_effects/lpe-connector.cpp index f89c8f95b8..03f69fd8be 100644 --- a/src/live_effects/lpe-connector.cpp +++ b/src/live_effects/lpe-connector.cpp @@ -279,6 +279,9 @@ Geom::PathVector LPEConnector::doEffect_path(Geom::PathVector const &path_in) router->pairs_at_point(point).first->_path == sp_lpe_item) { continue; } + if (path->connEndPair.getContinuous()) { + //continue; + } Geom::Path &pathin = path_out[(*pos).path_index]; Geom::Coord sizeopen = pathin.size_open(); Geom::Path startpath = pathin.portion(0, (*pos).curve_index + (*pos).t); @@ -296,13 +299,21 @@ Geom::PathVector LPEConnector::doEffect_path(Geom::PathVector const &path_in) double length_part = startpath.back_open().length(); if (length_part && length_part > crossing_gap) { t = 1 - (crossing_gap / length_part); - startpath = startpath.portion(0, t); + if (t > 0) { + startpath = startpath.portion(0, t); + } else { + t = 0; + } } double t2 = 0; length_part = endpath.back_open().length(); if (length_part && length_part > crossing_gap) { t2 = crossing_gap / length_part; - endpath = endpath.portion(t2, endpath.size_open()); + if (t2 < endpath.size_open()) { + endpath = endpath.portion(t2, endpath.size_open()); + } else { + t2 = 0; + } } if (t) { path_out.push_back(startpath); @@ -310,9 +321,7 @@ Geom::PathVector LPEConnector::doEffect_path(Geom::PathVector const &path_in) if (t2 && t) { path_out.push_back(endpath); } - if (t2 && t) { - path_out.erase(path_out.begin() + (*pos).path_index); - } + path_out.erase(path_out.begin() + (*pos).path_index); } } } diff --git a/src/object/sp-conn-end-pair.cpp b/src/object/sp-conn-end-pair.cpp index ea43adcd53..3b52dcc8ef 100644 --- a/src/object/sp-conn-end-pair.cpp +++ b/src/object/sp-conn-end-pair.cpp @@ -36,6 +36,7 @@ SPConnEndPair::SPConnEndPair(SPPath *const owner) , _connRef(nullptr) , _connType(SP_CONNECTOR_NOAVOID) , _connCurvature(0.0) + , _connContinuous(true) , _transformed_connection() { for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) { @@ -95,6 +96,7 @@ void sp_conn_end_pair_build(SPObject *object) object->readAttr(SPAttr::CONNECTION_END); object->readAttr(SPAttr::CONNECTION_END_POINT); object->readAttr(SPAttr::CONNECTOR_CURVATURE); + object->readAttr(SPAttr::CONNECTOR_CONTINUOUS); } @@ -166,6 +168,15 @@ void SPConnEndPair::setAttr(const SPAttr key, gchar const *const value) } } break; + case SPAttr::CONNECTOR_CONTINUOUS: + if (value) { + _connContinuous = value == "true"?true:false; + if (_connRef && _connRef->isInitialised()) { + // Redraw the connector, but only if it has been initialised. + sp_conn_reroute_path(_path); + } + } + break; case SPAttr::CONNECTION_START: this->_connEnd[0]->setAttacherHref(value); break; @@ -272,6 +283,11 @@ gdouble SPConnEndPair::getCurvature() const return _connCurvature; } +gboolean SPConnEndPair::getContinuous() const +{ + return _connContinuous; +} + SPConnEnd** SPConnEndPair::getConnEnds() { return _connEnd; diff --git a/src/object/sp-conn-end-pair.h b/src/object/sp-conn-end-pair.h index ee12d0b1aa..c1830a22d8 100644 --- a/src/object/sp-conn-end-pair.h +++ b/src/object/sp-conn-end-pair.h @@ -54,6 +54,7 @@ public: void getAttachedItems(SPItem *[2]) const; void getEndpoints(Geom::Point endPts[]) const; double getCurvature() const; + gboolean getContinuous() const; SPConnEnd** getConnEnds(); bool isOrthogonal() const; friend void recreateCurve(SPCurve *curve, Avoid::ConnRef *connRef, double curvature); @@ -77,6 +78,7 @@ private: int _connType; double _connCurvature; + bool _connContinuous; // A sigc connection for transformed signal. sigc::connection _transformed_connection; diff --git a/src/object/sp-path.cpp b/src/object/sp-path.cpp index 879fb4c85c..94efa219f0 100644 --- a/src/object/sp-path.cpp +++ b/src/object/sp-path.cpp @@ -269,6 +269,7 @@ void SPPath::set(SPAttr key, const gchar* value) { case SPAttr::CONNECTOR_TYPE: case SPAttr::CONNECTOR_CURVATURE: + case SPAttr::CONNECTOR_CONTINUOUS: case SPAttr::CONNECTION_START: case SPAttr::CONNECTION_END: case SPAttr::CONNECTION_START_POINT: diff --git a/src/path-chemistry.cpp b/src/path-chemistry.cpp index e1f90d1bee..5b71c34574 100644 --- a/src/path-chemistry.cpp +++ b/src/path-chemistry.cpp @@ -401,6 +401,7 @@ sp_item_list_to_curves(const std::vector &items, std::vector& item->removeAttribute("inkscape:connection-end-point"); item->removeAttribute("inkscape:connector-type"); item->removeAttribute("inkscape:connector-curvature"); + item->removeAttribute("inkscape:connector-continuous"); did = true; } continue; // already a path, and no path effect diff --git a/testfiles/src/attributes-test.cpp b/testfiles/src/attributes-test.cpp index 759c057923..cc039246d3 100644 --- a/testfiles/src/attributes-test.cpp +++ b/testfiles/src/attributes-test.cpp @@ -394,6 +394,7 @@ std::vector getKnownAttrs() AttributeInfo("inkscape:connection-start-point", true), AttributeInfo("inkscape:connector-avoid", true), AttributeInfo("inkscape:connector-curvature", true), + AttributeInfo("inkscape:connector-continuous", true), AttributeInfo("inkscape:connector-spacing", true), AttributeInfo("inkscape:connector-type", true), AttributeInfo("inkscape:corner0", true), -- GitLab