diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index cd486f3ce14f73b0fc6e07553a60e7f5e7cc1237..f228e327434d1c261591de6799f679ac9d2f8b1b 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 0000000000000000000000000000000000000000..6590154fc89bbcd2552398d02a72db42a5be4a2d --- /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 0000000000000000000000000000000000000000..6590154fc89bbcd2552398d02a72db42a5be4a2d --- /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 0000000000000000000000000000000000000000..33d144763954adc2d7688efab46a573d0b93cb72 --- /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 0000000000000000000000000000000000000000..6ebae50c4721c536b7a5333396e732c570f97a92 --- /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 0000000000000000000000000000000000000000..e51a258331928f96b3a762b5f82825c5b0ea8828 --- /dev/null +++ b/share/icons/multicolor/symbolic/actions/lpe-connector-symbolic.svg @@ -0,0 +1,157 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/attributes.cpp b/src/attributes.cpp index 531bc16edb516937a3556a4ef99a4a631b3f91e8..d8ede579caec6a08d5e3e1f6bcf1521dc9bca63d 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 95553b4ae535ae41ed4c354bdc47bbf5fa17f14a..65ef218629e8dd9e7d33a983d8962d2098b7bafd 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/conn-avoid-ref.cpp b/src/conn-avoid-ref.cpp index ec64cfa8fbbe44ce9a4a6fb7cbefaf9ce3838009..701a1a3758527a8e96bbbb5a520ffc2480315a3a 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 bf203c898502d22f95ef665ca76d6c0d1553b08a..6f955e6d8ecdaf2e282d84386c62d09c59587aaa 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 5a9548e9535341f5f6873dd8d1d724d799565220..ad9b08bbbdee6d0cfa55218b13b9653554998de7 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 f6145ce80725a2d099e430f7fa4d922cb9f4e210..6328f229c7f5e6eda3f96d8404f7ab5d9d612205 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 939e7ee4439f70835c37c1f8f834aa0084147351..5f5bedaed7d9a95f285f5a2238485cfbd8bc917f 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 166e4d3fa98d213a85bbec3d405916b4c9864e25..8e1394c8016d5749acfea6262833be4d1c007ac4 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 4863a62a1973642f221bf41850c445edd463b2d3..6e03edf23088021ff14cf15547bf41cb4239fe34 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 2b68661347b7e1fe050c636dd72ba0154e7acc0d..60dbe227ddb10a696dec5be7a8adf091e39cc921 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 6c357ba34ba48b5d0d4b442dafe4695539ab0a4d..c118da0b83d1e14717f6ae1e7079ff45607d74cf 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 0ef479f3d0a0e0916c60fc1dcfe6e384c6fbbae2..8793b6a4f7ea77f5176446c34fd1b539fa859d3c 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 0000000000000000000000000000000000000000..03f69fd8be80255d97f0c83d4b8d0bded94efb27 --- /dev/null +++ b/src/live_effects/lpe-connector.cpp @@ -0,0 +1,343 @@ +// 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) +{ + 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); + 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; + } + 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); + 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); + 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; + if (t2 < endpath.size_open()) { + endpath = endpath.portion(t2, endpath.size_open()); + } else { + t2 = 0; + } + } + if (t) { + path_out.push_back(startpath); + } + if (t2 && t) { + path_out.push_back(endpath); + } + 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 0000000000000000000000000000000000000000..3e4170a6021795134abb366652e11153b75d72f7 --- /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 0000000000000000000000000000000000000000..38954c07eb3b78f44ab6339eb7955993c9bd3c48 --- /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 0000000000000000000000000000000000000000..efe0fe05123f2475e84c54124116e3a1a2a35bff --- /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 e44d56f5815c4ffb3b5901bba582fd6b3ea9d789..4a19efb8be457da2646bbcee123d3e3e381e3055 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 7844237f4e6f5cdc0ca42dd93a89a04548bc3c38..566c414e881a029db57393f7812eaa16d5c869c9 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 c6aa18a48592a1dffdc838034ba03e85f596b56a..3b52dcc8ef2f72ad50abad9ad97a47e5e6b7cafc 100644 --- a/src/object/sp-conn-end-pair.cpp +++ b/src/object/sp-conn-end-pair.cpp @@ -13,26 +13,30 @@ */ #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) , _connRef(nullptr) , _connType(SP_CONNECTOR_NOAVOID) , _connCurvature(0.0) + , _connContinuous(true) , _transformed_connection() { for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) { @@ -50,6 +54,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 +81,7 @@ void SPConnEndPair::release() if (_connRef && routerInstanceExists) { _connRef->router()->deleteConnector(_connRef); + _path->document->getRouter()->release(); } _connRef = nullptr; @@ -84,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); } @@ -95,6 +108,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 +136,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 +152,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(); @@ -135,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; @@ -241,6 +283,11 @@ gdouble SPConnEndPair::getCurvature() const return _connCurvature; } +gboolean SPConnEndPair::getContinuous() const +{ + return _connContinuous; +} + SPConnEnd** SPConnEndPair::getConnEnds() { return _connEnd; @@ -367,13 +414,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 2391123504859069b876ebd8be217be291012a91..c1830a22d89922cab6b42e88e7f64df9705b0a88 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; @@ -45,11 +54,13 @@ 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); void tellLibavoidNewEndpoints(bool const processTransaction = false); bool reroutePathFromLibavoid(); + void crossings(const bool optimisedForConnectorType, const bool fromRouter); void makePathInvalid(); void update(); bool isAutoRoutingConn(); @@ -67,9 +78,12 @@ private: int _connType; double _connCurvature; + bool _connContinuous; // 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 a419ae1784cf663dd2f1aae24acaa76e6611970c..f96ee194fb04737a074dfada4e50abcb41102db4 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 1779a5b9b695b31eee22280b0710f9d2c4cbfc47..961578926c2130453482487d02762315b382b68c 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 0000000000000000000000000000000000000000..36813bcca3393f96be6a3564b4499d090df87ebe --- /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) +{ + std::vector points; + 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); + } + } + } + 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 0000000000000000000000000000000000000000..aee4626b101871fbda3bff0e9fc8e877263c95e5 --- /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 0e86471845e38e46d59aaffc8557a18d757ecacf..7ec6c876a7ec3591fb3c7386f45709a23c64a9e1 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-path.cpp b/src/object/sp-path.cpp index 879fb4c85cb897804f418ad2749aa6079d17fded..94efa219f02f35ad94c9f3a300040533874f79ba 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/object/sp-shape.cpp b/src/object/sp-shape.cpp index 1d5df5ea3769cd8f549e48119916896ed78494a7..68c830767631592e71cdaca647be91a43bd8c511 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 fd39213099e7a1d9319ad808b4e339fbe8def6df..47c8b0e7ce835c599665696ac4d51af979ead073 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/path-chemistry.cpp b/src/path-chemistry.cpp index e1f90d1beee307e1b545edfa017913e9333ab968..5b71c34574acadc43a76c9dbefd78edffe3a5c25 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/src/preferences-skeleton.h b/src/preferences-skeleton.h index 32ac0432f39432a6a8472d327e96fbecc53d9105..52e00ceac93156621c60840a0ce656455dbbf943 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 ac76c43dbcd7e7c549021a8a03afaab8c4593f16..7fd8f1750394b5fe9253d98508c6830c5437a193 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 d25cf9e315e54384f883c7678cf79768b58bca83..fe6ced4a65ebf0497937925d76d92610e39239de 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 e1c8003cec01ca3c1b76c4cbb16580505cc6744e..35e03b16a4e91a10f188494a7b9911f4bd03d69c 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 8a225706937a932209abd77cf62669c6e8178b88..68297c12a0f977e3bfd330c628de8e6046304260 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 0000000000000000000000000000000000000000..bb09fe2626951731c1ae7234d1ad65cc7ffda9ea --- /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 397179b9651031c7dc9d05390a400a4952e04e12..b3842f84bca7b4750b8b20c81a8b9d27e85a9436 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 5dba80ba23628d78922f812e5e161685c2a4362e..ff84b2868e9a099459283281420d1ee915702926 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 diff --git a/testfiles/src/attributes-test.cpp b/testfiles/src/attributes-test.cpp index 759c05792382e2a15d10a6972fa0a4d9673e7110..cc039246d309927c65bc5248016debc9c21206d3 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),