diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index 7aaa28c623686b532bcd3839cc38e83192776479..2204140a5fe0b0d4e1223533c71698b51bdfe3bf 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/src/attributes.cpp b/src/attributes.cpp index 56bd33c243b4bf5dae6de61b6bac167e0fda0f70..20a73aecf52bd2c99cb482c36b75e4864fb8ff35 100644 --- a/src/attributes.cpp +++ b/src/attributes.cpp @@ -148,6 +148,8 @@ static SPStyleProp const props[] = { {SPAttr::CONNECTION_END, "inkscape:connection-end"}, {SPAttr::CONNECTION_START_POINT, "inkscape:connection-start-point"}, {SPAttr::CONNECTION_END_POINT, "inkscape:connection-end-point"}, + {SPAttr::CONNECTION_START_INDEX, "inkscape:connection-start-index"}, + {SPAttr::CONNECTION_END_INDEX, "inkscape:connection-end-index"}, /* SPRect */ {SPAttr::RX, "rx"}, {SPAttr::RY, "ry"}, diff --git a/src/attributes.h b/src/attributes.h index cfdf18cdcedb330b340e6708b84b9b18b485c20d..afde5f796bb5e57f16af438d6d65b552c4856911 100644 --- a/src/attributes.h +++ b/src/attributes.h @@ -147,6 +147,8 @@ enum class SPAttr { CONNECTION_END, CONNECTION_START_POINT, CONNECTION_END_POINT, + CONNECTION_START_INDEX, + CONNECTION_END_INDEX, /* SPRect */ RX, RY, 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..899073e7086e25a008d23e8a8bf3bf854b2f232b 100644 --- a/src/live_effects/effect-enum.h +++ b/src/live_effects/effect-enum.h @@ -65,6 +65,9 @@ enum EffectType { PARALLEL, PERP_BISECTOR, TANGENT_TO_CURVE, + SLICE, + CONNECTOR, + // PUT NEW LPE BEFORE EXPERIMENTAL DOEFFECTSTACK_TEST, DYNASTROKE, LATTICE, @@ -72,7 +75,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 92919363177dc169c80bf82438a9f8f62c405d46..697e189c04884307ecfa37b6584617f7f02c6ac7 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -22,6 +22,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" @@ -791,6 +792,20 @@ const EnumEffectData LPETypeData[] = { false ,//on_text false ,//experimental }, + { + CONNECTOR, + N_("Connector") ,//label + "connector" ,//key + "connector" ,//icon + "Connector" ,//untranslated name + N_("Add target of connectors to items.") ,//description + true ,//on_path + true ,//on_shape + true ,//on_group + false ,//on_image + false ,//on_text + false ,//experimental + }, #ifdef LPE_ENABLE_TEST_EFFECTS { DOEFFECTSTACK_TEST, @@ -1082,6 +1097,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; diff --git a/src/live_effects/lpe-connector.cpp b/src/live_effects/lpe-connector.cpp new file mode 100644 index 0000000000000000000000000000000000000000..26e195c7d602920d52d66eb14cf3511f27d0a922 --- /dev/null +++ b/src/live_effects/lpe-connector.cpp @@ -0,0 +1,87 @@ +// 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 "live_effects/lpeobject.h" +#include "object/sp-conn-end.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) +{ + registerParameter(&connector_points); +} + +LPEConnector::~LPEConnector() = default; + +void +LPEConnector::doOnApply (SPLPEItem const* lpeitem) +{ + std::vector points; + const gchar *conexionpoints = lpeitem->getAttribute("inkscape:connection-points"); + if (conexionpoints) { + points = sp_conn_end_get_points(conexionpoints); + } else { + points.emplace_back(0,0); + } + Geom::Affine i2d = lpeitem->i2doc_affine(); + for (unsigned int i = 0; i < points.size(); ++i) { + points[i] *= i2d.inverse(); + } + connector_points.param_set_and_write_new_value(points); +} + +void +LPEConnector::doBeforeEffect (SPLPEItem const* lpeitem) +{ + std::vector points = connector_points.data(); + if (!points.empty()) { + points.emplace_back(0,0); + } + Inkscape::SVGOStringStream os; + Geom::Affine i2d = lpeitem->i2doc_affine(); + for (unsigned int i = 0; i < points.size(); ++i) { + if (i != 0) { + // separate items with pipe symbol + os << " "; + } + points[i] *= i2d; + os << points[i][Geom::X] << " " << points[i][Geom::Y]; + } + sp_lpe_item->setAttribute("inkscape:connection-points", os.str().c_str()); +} + +Geom::PathVector +LPEConnector::doEffect_path (Geom::PathVector const & path_in) +{ + return path_in; +} + +} //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..dc6f11ebdc3d6d32a364b14337bea27bfc63e59b --- /dev/null +++ b/src/live_effects/lpe-connector.h @@ -0,0 +1,46 @@ +// 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/lpeobject.h" +#include "live_effects/lpeobject-reference.h" +#include "live_effects/lpegroupbbox.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; + Geom::PathVector doEffect_path (Geom::PathVector const & path_in) override; + +private: + ConnectorPointArrayParam connector_points; + 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..70331478063430eebaad513a3e86b072f5f130c2 --- /dev/null +++ b/src/live_effects/parameter/connectorpointarray.cpp @@ -0,0 +1,179 @@ +// 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 "ui/knot/knot-holder.h" + +#include "live_effects/effect.h" +#include "live_effects/lpe-connector.h" + + +#include + +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::param_transform_multiply(Geom::Affine const &postmul, bool /*set*/) +{ +} + +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); + Geom::OptRect bbox = item->visualBounds(); + if (bbox) { + Geom::Point center = (*bbox).midpoint(); + _pparam->_vector.at(_index) = (s - center); + 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()); + } + Geom::OptRect bbox = item->visualBounds(); + if (bbox) { + Geom::Point center = (*bbox).midpoint(); + return (center +_pparam->_vector.at(_index)); + } else { + return Geom::Point(Geom::infinity(),Geom::infinity()); + } +} + +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..3d49a586fb00b021fc5f80e41a3ae7bc94073992 --- /dev/null +++ b/src/live_effects/parameter/connectorpointarray.h @@ -0,0 +1,78 @@ +// 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 +#include <2geom/point.h> + +#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 param_transform_multiply(Geom::Affine const& postmul, bool /*set*/) 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/sp-conn-end-pair.cpp b/src/object/sp-conn-end-pair.cpp index c6aa18a48592a1dffdc838034ba03e85f596b56a..6094d1163714acb934b264e10ebf5334ecf2cb8c 100644 --- a/src/object/sp-conn-end-pair.cpp +++ b/src/object/sp-conn-end-pair.cpp @@ -61,6 +61,7 @@ void SPConnEndPair::release() handle_ix->_group_connection.disconnect(); g_free(handle_ix->href); handle_ix->href = nullptr; + handle_ix->index = Glib::ustring::npos; handle_ix->ref.detach(); } @@ -81,8 +82,10 @@ void sp_conn_end_pair_build(SPObject *object) object->readAttr(SPAttr::CONNECTOR_TYPE); object->readAttr(SPAttr::CONNECTION_START); object->readAttr(SPAttr::CONNECTION_START_POINT); + object->readAttr(SPAttr::CONNECTION_START_INDEX); object->readAttr(SPAttr::CONNECTION_END); object->readAttr(SPAttr::CONNECTION_END_POINT); + object->readAttr(SPAttr::CONNECTION_END_INDEX); object->readAttr(SPAttr::CONNECTOR_CURVATURE); } @@ -141,12 +144,26 @@ void SPConnEndPair::setAttr(const SPAttr key, gchar const *const value) case SPAttr::CONNECTION_START_POINT: this->_connEnd[0]->setAttacherSubHref(value); break; + case SPAttr::CONNECTION_START_INDEX: + if (value) { + this->_connEnd[0]->setAttacherIndex((size_t)atoi(value)); + } else { + this->_connEnd[0]->setAttacherIndex(Glib::ustring::npos); + } + break; case SPAttr::CONNECTION_END: this->_connEnd[1]->setAttacherHref(value); break; case SPAttr::CONNECTION_END_POINT: this->_connEnd[1]->setAttacherSubHref(value); break; + case SPAttr::CONNECTION_END_INDEX: + if (value) { + this->_connEnd[0]->setAttacherIndex((size_t)atoi(value)); + } else { + this->_connEnd[0]->setAttacherIndex(Glib::ustring::npos); + } + break; } } @@ -156,6 +173,8 @@ void SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const "inkscape:connection-start", "inkscape:connection-end"}; char const * const point_attrs[] = { "inkscape:connection-start-point", "inkscape:connection-end-point"}; + char const * const index_attrs[] = { + "inkscape:connection-start-index", "inkscape:connection-end-index"}; for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) { const Inkscape::URI* U = this->_connEnd[handle_ix]->ref.getURI(); if (U) { @@ -167,6 +186,10 @@ void SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const auto str = P->str(); repr->setAttribute(point_attrs[handle_ix], str); } + size_t Q = this->_connEnd[handle_ix]->index; + if (Q != Glib::ustring::npos) { + repr->setAttribute(index_attrs[handle_ix], Glib::ustring::format(Q).c_str()); + } } if (_connType == SP_CONNECTOR_POLYLINE || _connType == SP_CONNECTOR_ORTHOGONAL) { repr->setAttribute("inkscape:connector-curvature", Glib::Ascii::dtostr(_connCurvature)); @@ -224,8 +247,30 @@ void SPConnEndPair::getEndpoints(Geom::Point endPts[]) const Geom::Affine i2d = _path->i2doc_affine(); for (unsigned h = 0; h < 2; ++h) { + // we add connexion points if (h2attItem[h]) { - endPts[h] = h2attItem[h]->getAvoidRef().getConnectionPointPos(); + const gchar *conexionpoints = h2attItem[h]->getAttribute("inkscape:connection-points"); + if (conexionpoints && this->_connEnd[h] && this->_connEnd[h]->index) { + std::vector points = sp_conn_end_get_points(conexionpoints); + Geom::OptRect bbox = h2attItem[h]->visualBounds(); + std::cout << this->_connEnd[h]->index << "zzz" << std::endl; + std::cout << points.size() << "zaaazz" << std::endl; + if (bbox && this->_connEnd[h]->index < points.size()) { + Geom::Point center = (*bbox).midpoint(); + auto obj = this->_connEnd[h]->ref.getObject(); + SPUse *use = dynamic_cast(obj); + Geom::Point gap = points[this->_connEnd[h]->index]; + if(use) { + endPts[h] = (center + gap) * use->transform.withoutTranslation(); + } else { + endPts[h] = (center + gap); + } + } else { + endPts[h] = h2attItem[h]->getAvoidRef().getConnectionPointPos(); + } + } else { + endPts[h] = h2attItem[h]->getAvoidRef().getConnectionPointPos(); + } } else if (!curve->is_empty()) { if (h == 0) { endPts[h] = *(curve->first_point()) * i2d; diff --git a/src/object/sp-conn-end.cpp b/src/object/sp-conn-end.cpp index a419ae1784cf663dd2f1aae24acaa76e6611970c..34d45f86f4a505eef37dd62c6acc50abd3ebf286 100644 --- a/src/object/sp-conn-end.cpp +++ b/src/object/sp-conn-end.cpp @@ -30,6 +30,7 @@ SPConnEnd::SPConnEnd(SPObject *const owner) , sub_ref(owner) , href(nullptr) , sub_href(nullptr) + , index(Glib::ustring::npos) // Default to center connection endpoint , _changed_connection() , _delete_connection() @@ -231,6 +232,10 @@ static void sp_conn_end_deleted(SPObject *, SPObject *const owner, unsigned cons char const * const point_attrs[] = { "inkscape:connection-start-point", "inkscape:connection-end-point"}; owner->removeAttribute(point_attrs[handle_ix]); + + char const * const index_attrs[] = { + "inkscape:connection-start-index", "inkscape:connection-end-index"}; + owner->removeAttribute(index_attrs[handle_ix]); /* I believe this will trigger sp_conn_end_href_changed. */ } @@ -251,6 +256,66 @@ void SPConnEnd::setAttacherHref(gchar const *value) } } +void SPConnEnd::setAttacherIndex(size_t value) +{ + index = value; +} + + +// forked from sp-polyline:33 +std::vector sp_conn_end_get_points(const gchar * value) +{ + std::vector result; + const gchar * cptr; + char * eptr; + + if (!value) { + return result; + } + + cptr = value; + eptr = nullptr; + + while (TRUE) { + gdouble x, y; + + while (*cptr != '\0' && (*cptr == ',' || *cptr == '\x20' || *cptr == '\x9' || *cptr == '\xD' || *cptr == '\xA')) { + cptr++; + } + + if (!*cptr) { + break; + } + + x = g_ascii_strtod (cptr, &eptr); + + if (eptr == cptr) { + break; + } + + cptr = eptr; + + while (*cptr != '\0' && (*cptr == ',' || *cptr == '\x20' || *cptr == '\x9' || *cptr == '\xD' || *cptr == '\xA')) { + cptr++; + } + + if (!*cptr) { + break; + } + + y = g_ascii_strtod (cptr, &eptr); + + if (eptr == cptr) { + break; + } + + cptr = eptr; + result.emplace_back(x,y); + } + + return result; +} + void SPConnEnd::setAttacherSubHref(gchar const *value) { // TODO This could check the URI object is actually a sub-object diff --git a/src/object/sp-conn-end.h b/src/object/sp-conn-end.h index 1779a5b9b695b31eee22280b0710f9d2c4cbfc47..a95941a3ca72e83b127ed1711df41771155283c6 100644 --- a/src/object/sp-conn-end.h +++ b/src/object/sp-conn-end.h @@ -31,6 +31,7 @@ public: SPUseReference sub_ref; char *href = nullptr; char *sub_href = nullptr; + size_t index = Glib::ustring::npos; /** Change of href string (not a modification of the attributes of the referrent). */ sigc::connection _changed_connection; @@ -46,7 +47,7 @@ public: void setAttacherHref(char const * value); void setAttacherSubHref(char const * value); - + void setAttacherIndex(size_t value); private: SPConnEnd(SPConnEnd const &) = delete; // no copy @@ -59,6 +60,7 @@ 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_end_detach(SPObject *const owner, unsigned const handle_ix); +std::vector sp_conn_end_get_points(const gchar * value); #endif /* !SEEN_SP_CONN_END */ diff --git a/src/object/sp-item.cpp b/src/object/sp-item.cpp index 8b1d903f5c6618e5e995a03b3aa8d8fd311bd483..361fecfedcc8017a8a6d4c37dafa3db7398365bf 100644 --- a/src/object/sp-item.cpp +++ b/src/object/sp-item.cpp @@ -45,6 +45,7 @@ #include "sp-textpath.h" #include "sp-title.h" #include "sp-use.h" +#include "sp-conn-end.h" #include "style.h" #include "uri.h" @@ -1573,7 +1574,20 @@ void SPItem::doWriteTransform(Geom::Affine const &transform, Geom::Affine const if (lpeitem && lpeitem->hasPathEffectRecursive()) { sp_lpe_item_update_patheffect(lpeitem, true, false); } - + const gchar *conexionpoints = getAttribute("inkscape:connection-points"); + if (conexionpoints) { + std::vector points = sp_conn_end_get_points(conexionpoints); + Inkscape::SVGOStringStream os; + for (unsigned int i = 0; i < points.size(); ++i) { + if (i != 0) { + // separate items with pipe symbol + os << " "; + } + points[i] *= advertized_transform.withoutTranslation(); + os << points[i]; + } + setAttribute("inkscape:connection-points", os.str().c_str()); + } // send the relative transform with a _transformed_signal _transformed_signal.emit(&advertized_transform, this); } diff --git a/src/object/sp-path.cpp b/src/object/sp-path.cpp index 613c02c6fdfc8ee6864b1d809cd9257846622aa5..50abdfdc42dd69cb5e91e6226c5bde9717b36bb3 100644 --- a/src/object/sp-path.cpp +++ b/src/object/sp-path.cpp @@ -273,6 +273,8 @@ void SPPath::set(SPAttr key, const gchar* value) { case SPAttr::CONNECTION_END: case SPAttr::CONNECTION_START_POINT: case SPAttr::CONNECTION_END_POINT: + case SPAttr::CONNECTION_START_INDEX: + case SPAttr::CONNECTION_END_INDEX: this->connEndPair.setAttr(key, value); break; diff --git a/src/object/sp-use.cpp b/src/object/sp-use.cpp index d29060ef94b2ed15d32a94f8c3220b80e99ed250..28dfe178ea6665fbeeb5b3eecf8eb6d4c173c10d 100644 --- a/src/object/sp-use.cpp +++ b/src/object/sp-use.cpp @@ -37,6 +37,7 @@ #include "style.h" #include "sp-use.h" +#include "sp-conn-end.h" #include "sp-symbol.h" #include "sp-root.h" #include "sp-use-reference.h" @@ -680,7 +681,21 @@ SPItem *SPUse::unlink() { Inkscape::XML::Node *newchild = child->duplicate(xml_doc); copy->appendChild(newchild); } - + const gchar *conexionpoints = orig->getAttribute("inkscape:connection-points"); + if (conexionpoints) { + Inkscape::SVGOStringStream os; + std::vector points = sp_conn_end_get_points(conexionpoints); + Geom::Affine transform = orig->transform; + for (unsigned int i = 0; i < points.size(); ++i) { + if (i != 0) { + // separate items with pipe symbol + os << " "; + } + points[i] *= transform.withoutTranslation(); + os << points[i][Geom::X] << " " << points[i][Geom::Y]; + } + copy->setAttribute("inkscape:connection-points", os.str().c_str()); + } // viewBox transformation t = symbol->c2p * t; } else { // just copy diff --git a/src/path-chemistry.cpp b/src/path-chemistry.cpp index 969c44871284723bb51695740c1bbed8a215264a..68e97688ff9894d4f5ed9c1dbe0b80a2c1a8abc3 100644 --- a/src/path-chemistry.cpp +++ b/src/path-chemistry.cpp @@ -400,8 +400,10 @@ sp_item_list_to_curves(const std::vector &items, std::vector& if (item->getAttribute("inkscape:connector-type") != nullptr) { item->removeAttribute("inkscape:connection-start"); item->removeAttribute("inkscape:connection-start-point"); + item->removeAttribute("inkscape:connection-start-index"); item->removeAttribute("inkscape:connection-end"); item->removeAttribute("inkscape:connection-end-point"); + item->removeAttribute("inkscape:connection-end-index"); item->removeAttribute("inkscape:connector-type"); item->removeAttribute("inkscape:connector-curvature"); did = true; diff --git a/src/ui/knot/knot-holder.h b/src/ui/knot/knot-holder.h index 397179b9651031c7dc9d05390a400a4952e04e12..329669952307defdba7b8650df21712435c19d32 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 6329fcb3ac9b807b2f6712f3b75e2140dce62c22..fa87c2a420d179c2315f289636083ef7d10e8a3d 100644 --- a/src/ui/tools/connector-tool.cpp +++ b/src/ui/tools/connector-tool.cpp @@ -178,7 +178,9 @@ ConnectorTool::ConnectorTool() , clickedhandle(nullptr) , shref(nullptr) , sub_shref(nullptr) + , sindex(Glib::ustring::npos) , ehref(nullptr) + , eindex(Glib::ustring::npos) , sub_ehref(nullptr) { } @@ -204,6 +206,8 @@ ConnectorTool::~ConnectorTool() this->shref = nullptr; } + this->sindex = Glib::ustring::npos; + this->eindex = Glib::ustring::npos; g_assert( this->newConnRef == nullptr ); } @@ -338,21 +342,27 @@ void ConnectorTool::cc_clear_active_conn() } -bool ConnectorTool::_ptHandleTest(Geom::Point& p, gchar **href, gchar **subhref) +bool ConnectorTool::_ptHandleTest(Geom::Point& p, gchar **href, gchar **subhref, size_t &kindex) { - if (this->active_handle && (this->knots.find(this->active_handle) != this->knots.end())) { - p = this->active_handle->pos; - *href = g_strdup_printf("#%s", this->active_handle->owner->getId()); - if(this->active_handle->sub_owner) { - auto id = this->active_handle->sub_owner->getAttribute("id"); - if(id) { - *subhref = g_strdup_printf("#%s", id); + if (this->active_handle) { + auto found = this->knots.find(this->active_handle); + if (found != this->knots.end()) { + kindex = std::distance(std::begin(this->knots), found); + p = this->active_handle->pos; + *href = g_strdup_printf("#%s", this->active_handle->owner->getId()); + if(this->active_handle->sub_owner) { + auto id = this->active_handle->sub_owner->getAttribute("id"); + if(id) { + *subhref = g_strdup_printf("#%s", id); + } + kindex = Glib::ustring::npos; + } else { + *subhref = nullptr; } - } else { - *subhref = nullptr; + return true; } - return true; } + kindex = Glib::ustring::npos; *href = nullptr; *subhref = nullptr; return false; @@ -503,7 +513,7 @@ bool ConnectorTool::_handleButtonPress(GdkEventButton const &bevent) Geom::Point p = event_dt; // Test whether we clicked on a connection point - bool found = this->_ptHandleTest(p, &this->shref, &this->sub_shref); + bool found = this->_ptHandleTest(p, &this->shref, &this->sub_shref, this->sindex); if (!found) { // This is the first point, so just snap it to the grid @@ -529,7 +539,7 @@ bool ConnectorTool::_handleButtonPress(GdkEventButton const &bevent) this->_setSubsequentPoint(p); this->_finishSegment(p); - this->_ptHandleTest(p, &this->ehref, &this->sub_ehref); + this->_ptHandleTest(p, &this->ehref, &this->sub_ehref, this->eindex); if (this->npoints != 0) { this->_finish(); } @@ -684,7 +694,7 @@ bool ConnectorTool::_handleButtonRelease(GdkEventButton const &revent) this->_setSubsequentPoint(p); this->_finishSegment(p); // Test whether we clicked on a connection point - this->_ptHandleTest(p, &this->ehref, &this->sub_ehref); + this->_ptHandleTest(p, &this->ehref, &this->sub_ehref, this->eindex); if (this->npoints != 0) { this->_finish(); } @@ -765,15 +775,17 @@ void ConnectorTool::_reroutingFinish(Geom::Point *const p) // Test whether we clicked on a connection point gchar *shape_label; gchar *sub_label; - bool found = this->_ptHandleTest(*p, &shape_label, &sub_label); - + size_t kindex = Glib::ustring::npos; + bool found = this->_ptHandleTest(*p, &shape_label, &sub_label, kindex); if (found) { if (this->clickedhandle == this->endpt_handle[0]) { this->clickeditem->setAttribute("inkscape:connection-start", shape_label); this->clickeditem->setAttribute("inkscape:connection-start-point", sub_label); + this->clickeditem->setAttribute("inkscape:connection-start-index", kindex != Glib::ustring::npos ? Glib::ustring::format(kindex).c_str() : nullptr); } else { this->clickeditem->setAttribute("inkscape:connection-end", shape_label); this->clickeditem->setAttribute("inkscape:connection-end-point", sub_label); + this->clickeditem->setAttribute("inkscape:connection-end-index", kindex != Glib::ustring::npos ? Glib::ustring::format(kindex).c_str() : nullptr); } g_free(shape_label); if(sub_label) { @@ -903,6 +915,9 @@ void ConnectorTool::_flushWhite(SPCurve *c) if(this->sub_shref) { this->newconn->setAttribute( "inkscape:connection-start-point", this->sub_shref); } + if(this->sindex != Glib::ustring::npos) { + this->newconn->setAttribute( "inkscape:connection-start-index", Glib::ustring::format(this->sindex).c_str()); + } } if (this->ehref) { @@ -911,6 +926,9 @@ void ConnectorTool::_flushWhite(SPCurve *c) if(this->sub_ehref) { this->newconn->setAttribute( "inkscape:connection-end-point", this->sub_ehref); } + if(this->eindex != Glib::ustring::npos) { + this->newconn->setAttribute( "inkscape:connection-end-index", Glib::ustring::format(this->eindex).c_str()); + } } // Process pending updates. this->newconn->updateRepr(); @@ -1064,12 +1082,22 @@ static bool endpt_handler(GdkEvent *event, ConnectorTool *cc) return consumed; } -void ConnectorTool::_activeShapeAddKnot(SPItem* item, SPItem* subitem) +void ConnectorTool::_activeShapeAddKnot(SPItem* item, SPItem* subitem, Geom::Point *p) { SPKnot *knot = new SPKnot(desktop, "", Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "CanvasItemCtrl:ConnectorTool:Shape"); knot->owner = item; - - if (subitem) { + if (p) { + Geom::OptRect bbox = item->visualBounds(); + Geom::Point center = Geom::Point(Geom::infinity(),Geom::infinity()); + if (bbox) { + center = (*bbox).midpoint(); + } + knot->setShape(Inkscape::CANVAS_ITEM_CTRL_SHAPE_SQUARE); + knot->setSize(11); // Must be odd + knot->setAnchor(SP_ANCHOR_CENTER); + knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff, 0xff0000ff); + knot->setPosition(center + *p, 0); + } else if (subitem) { SPUse *use = dynamic_cast(item); g_assert(use != nullptr); knot->sub_owner = subitem; @@ -1138,6 +1166,14 @@ void ConnectorTool::_setActiveShape(SPItem *item) this->_activeShapeAddKnot((SPItem *) &child, nullptr); } } + // we add connexion points + const gchar *conexionpoints = item->getAttribute("inkscape:connection-points"); + if (conexionpoints) { + std::vector points = sp_conn_end_get_points(conexionpoints); + for (auto point : points) { + this->_activeShapeAddKnot(item, nullptr, &point); + } + } // Special connector points in a symbol SPUse *use = dynamic_cast(item); if(use) { @@ -1148,6 +1184,15 @@ void ConnectorTool::_setActiveShape(SPItem *item) this->_activeShapeAddKnot(item, (SPItem *) &child); } } + // we add connexion points + const gchar *conexionpoints = orig->getAttribute("inkscape:connection-points"); + if (conexionpoints) { + std::vector points = sp_conn_end_get_points(conexionpoints); + for (auto point : points) { + point *= use->transform.withoutTranslation(); + this->_activeShapeAddKnot(item, nullptr, &point); + } + } } // Center point to any object this->_activeShapeAddKnot(item, nullptr); diff --git a/src/ui/tools/connector-tool.h b/src/ui/tools/connector-tool.h index 28d67d7e99719d4c574020019f417366314b3102..0fd078248986034d874ef27862432018b35b9a05 100644 --- a/src/ui/tools/connector-tool.h +++ b/src/ui/tools/connector-tool.h @@ -107,8 +107,10 @@ public: sigc::connection endpt_handler_connection[2]; gchar *shref; gchar *sub_shref; + size_t sindex; gchar *ehref; gchar *sub_ehref; + size_t eindex; static std::string const prefsPath; @@ -140,9 +142,9 @@ private: void _concatColorsAndFlush(); void _flushWhite(SPCurve *gc); - void _activeShapeAddKnot(SPItem* item, SPItem* subitem); + void _activeShapeAddKnot(SPItem* item, SPItem* subitem, Geom::Point *p = nullptr); void _setActiveShape(SPItem *item); - bool _ptHandleTest(Geom::Point& p, gchar **href, gchar **subhref); + bool _ptHandleTest(Geom::Point& p, gchar **href, gchar **subhref, size_t &kindex); void _reroutingFinish(Geom::Point *const p); }; diff --git a/testfiles/src/attributes-test.cpp b/testfiles/src/attributes-test.cpp index 3ec953679ed638a4aeacea647cafb1262195ded5..5b8fb227689781c9f50da6fb2c99f997cee41947 100644 --- a/testfiles/src/attributes-test.cpp +++ b/testfiles/src/attributes-test.cpp @@ -388,9 +388,11 @@ std::vector getKnownAttrs() AttributeInfo("inkscape:color", true), AttributeInfo("inkscape:connection-end", true), AttributeInfo("inkscape:connection-end-point", true), + AttributeInfo("inkscape:connection-end-index", true), AttributeInfo("inkscape:connection-points", true), AttributeInfo("inkscape:connection-start", true), AttributeInfo("inkscape:connection-start-point", true), + AttributeInfo("inkscape:connection-start-index", true), AttributeInfo("inkscape:connector-avoid", true), AttributeInfo("inkscape:connector-curvature", true), AttributeInfo("inkscape:connector-spacing", true),