X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fsp-conn-end.cpp;h=07dd852f230fe988b809d812b4ad7fc215e5fc7d;hb=HEAD;hp=80ecb060f23704ff292382c080b45a00bc35d72e;hpb=e77e2e02d8ab186389f86e48b1834d95f51cf68d;p=inkscape.git diff --git a/src/sp-conn-end.cpp b/src/sp-conn-end.cpp index 80ecb060f..07dd852f2 100644 --- a/src/sp-conn-end.cpp +++ b/src/sp-conn-end.cpp @@ -1,24 +1,29 @@ #include #include +#include #include "display/curve.h" -#include "libnr/nr-matrix-div.h" #include "libnr/nr-matrix-fns.h" #include "xml/repr.h" #include "sp-conn-end.h" #include "sp-path.h" #include "uri.h" #include "document.h" +#include "sp-item-group.h" +#include "2geom/path.h" +#include "2geom/pathvector.h" +#include "2geom/path-intersection.h" -static void change_endpts(SPCurve *const curve, NR::Point const h2endPt[2]); -static NR::Point calc_bbox_conn_pt(NR::Rect const &bbox, NR::Point const &p); -static double signed_one(double const x); +static void change_endpts(SPCurve *const curve, double const endPos[2]); SPConnEnd::SPConnEnd(SPObject *const owner) : ref(owner), href(NULL), + // Default to center connection endpoint + type(ConnPointDefault), + id(4), _changed_connection(), _delete_connection(), _transformed_connection() @@ -36,201 +41,188 @@ get_nearest_common_ancestor(SPObject const *const obj, SPItem const *const objs[ return anc_sofar; } -static void -sp_conn_end_move_compensate(NR::Matrix const */*mp*/, SPItem */*moved_item*/, - SPPath *const path, - bool const updatePathRepr = true) -{ - // TODO: SPItem::getBounds gives the wrong result for some objects - // that have internal representations that are updated later - // by the sp_*_update functions, e.g., text. - sp_document_ensure_up_to_date(path->document); - // Get the new route around obstacles. - path->connEndPair.reroutePath(); +static bool try_get_intersect_point_with_item_recursive(Geom::PathVector& conn_pv, SPItem* item, + const Geom::Matrix& item_transform, double& intersect_pos) { - SPItem *h2attItem[2]; - path->connEndPair.getAttachedItems(h2attItem); - if ( !h2attItem[0] && !h2attItem[1] ) { - if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - path->updateRepr(); + double initial_pos = intersect_pos; + // if this is a group... + if (SP_IS_GROUP(item)) { + SPGroup* group = SP_GROUP(item); + + // consider all first-order children + double child_pos = 0.0; + for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) { + SPItem* child_item = SP_ITEM(i->data); + try_get_intersect_point_with_item_recursive(conn_pv, child_item, + item_transform * child_item->transform, child_pos); + if (intersect_pos < child_pos) + intersect_pos = child_pos; } - return; + return intersect_pos != initial_pos; } - SPItem const *const path_item = SP_ITEM(path); - SPObject const *const ancestor = get_nearest_common_ancestor(path_item, h2attItem); - NR::Matrix const path2anc(from_2geom(i2anc_affine(path_item, ancestor))); - - if (h2attItem[0] != NULL && h2attItem[1] != NULL) { - /* Initial end-points: centre of attached object. */ - NR::Point h2endPt_icoordsys[2]; - NR::Matrix h2i2anc[2]; - NR::Rect h2bbox_icoordsys[2]; - NR::Point last_seg_endPt[2] = { - path->curve->second_point(), - path->curve->penultimate_point() - }; - for (unsigned h = 0; h < 2; ++h) { - NR::Maybe bbox = h2attItem[h]->getBounds(NR::identity()); - if (!bbox) { - if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - path->updateRepr(); - } - return; - } - h2bbox_icoordsys[h] = *bbox; - h2i2anc[h] = from_2geom(i2anc_affine(h2attItem[h], ancestor)); - h2endPt_icoordsys[h] = h2bbox_icoordsys[h].midpoint(); - } + // if this is not a shape, nothing to be done + if (!SP_IS_SHAPE(item)) return false; - // For each attached object, change the corresponding point to be - // on the edge of the bbox. - NR::Point h2endPt_pcoordsys[2]; - for (unsigned h = 0; h < 2; ++h) { - h2endPt_icoordsys[h] = calc_bbox_conn_pt(h2bbox_icoordsys[h], - ( last_seg_endPt[h] / h2i2anc[h] )); - h2endPt_pcoordsys[h] = h2endPt_icoordsys[h] * h2i2anc[h] / path2anc; - } - change_endpts(path->curve, h2endPt_pcoordsys); - } else { - // We leave the unattached endpoint where it is, and adjust the - // position of the attached endpoint to be on the edge of the bbox. - unsigned ind; - NR::Point other_endpt; - NR::Point last_seg_pt; - if (h2attItem[0] != NULL) { - other_endpt = path->curve->last_point(); - last_seg_pt = path->curve->second_point(); - ind = 0; - } - else { - other_endpt = path->curve->first_point(); - last_seg_pt = path->curve->penultimate_point(); - ind = 1; - } - NR::Point h2endPt_icoordsys[2]; - NR::Matrix h2i2anc; - - NR::Rect otherpt_rect = NR::Rect(other_endpt, other_endpt); - NR::Rect h2bbox_icoordsys[2] = { otherpt_rect, otherpt_rect }; - NR::Maybe bbox = h2attItem[ind]->getBounds(NR::identity()); - if (!bbox) { - if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - path->updateRepr(); - } - return; + // make sure it has an associated curve + SPCurve* item_curve = SP_SHAPE(item)->getCurve(); + if (!item_curve) return false; + + // apply transformations (up to common ancestor) + item_curve->transform(item_transform); + + const Geom::PathVector& curve_pv = item_curve->get_pathvector(); + Geom::CrossingSet cross = crossings(conn_pv, curve_pv); + // iterate over all Crossings + for (Geom::CrossingSet::const_iterator i = cross.begin(); i != cross.end(); i++) { + const Geom::Crossings& cr = *i; + + for (Geom::Crossings::const_iterator i = cr.begin(); i != cr.end(); i++) { + const Geom::Crossing& cr_pt = *i; + if ( intersect_pos < cr_pt.ta) + intersect_pos = cr_pt.ta; } + } - h2bbox_icoordsys[ind] = *bbox; - h2i2anc = from_2geom(i2anc_affine(h2attItem[ind], ancestor)); - h2endPt_icoordsys[ind] = h2bbox_icoordsys[ind].midpoint(); + item_curve->unref(); - h2endPt_icoordsys[!ind] = other_endpt; + return intersect_pos != initial_pos; +} + + +// This function returns the outermost intersection point between the path (a connector) +// and the item given. If the item is a group, then the component items are considered. +// The transforms given should be to a common ancestor of both the path and item. +// +static bool try_get_intersect_point_with_item(SPPath* conn, SPItem* item, + const Geom::Matrix& item_transform, const Geom::Matrix& conn_transform, + const bool at_start, double& intersect_pos) { - // For the attached object, change the corresponding point to be - // on the edge of the bbox. - NR::Point h2endPt_pcoordsys[2]; - h2endPt_icoordsys[ind] = calc_bbox_conn_pt(h2bbox_icoordsys[ind], - ( last_seg_pt / h2i2anc )); - h2endPt_pcoordsys[ind] = h2endPt_icoordsys[ind] * h2i2anc / path2anc; + // Copy the curve and apply transformations up to common ancestor. + SPCurve* conn_curve = conn->curve->copy(); + conn_curve->transform(conn_transform); - // Leave the other where it is. - h2endPt_pcoordsys[!ind] = other_endpt; + Geom::PathVector conn_pv = conn_curve->get_pathvector(); - change_endpts(path->curve, h2endPt_pcoordsys); + // If this is not the starting point, use Geom::Path::reverse() to reverse the path + if (!at_start) + { + // connectors are actually a single path, so consider the first element from a Geom::PathVector + conn_pv[0] = conn_pv[0].reverse(); } + + // We start with the intersection point at the beginning of the path + intersect_pos = 0.0; + + // Find the intersection. + bool result = try_get_intersect_point_with_item_recursive(conn_pv, item, item_transform, intersect_pos); + + if (!result) + // No intersection point has been found (why?) + // just default to connector end + intersect_pos = 0; + // If not at the starting point, recompute position with respect to original path + if (!at_start) + intersect_pos = conn_pv[0].size() - intersect_pos; + // Free the curve copy. + conn_curve->unref(); + + return result; +} + + +static void +sp_conn_get_route_and_redraw(SPPath *const path, + const bool updatePathRepr = true) +{ + // Get the new route around obstacles. + bool rerouted = path->connEndPair.reroutePathFromLibavoid(); + if (!rerouted) { + return; + } + + SPItem *h2attItem[2]; + path->connEndPair.getAttachedItems(h2attItem); + + SPItem const *const path_item = SP_ITEM(path); + SPObject const *const ancestor = get_nearest_common_ancestor(path_item, h2attItem); + Geom::Matrix const path2anc(i2anc_affine(path_item, ancestor)); + + // Set sensible values incase there the connector ends are not + // attached to any shapes. + Geom::PathVector conn_pv = path->curve->get_pathvector(); + double endPos[2] = { 0, conn_pv[0].size() }; + + SPConnEnd** _connEnd = path->connEndPair.getConnEnds(); + for (unsigned h = 0; h < 2; ++h) { + if (h2attItem[h] && _connEnd[h]->type == ConnPointDefault && _connEnd[h]->id == ConnPointPosCC) { + Geom::Matrix h2i2anc = i2anc_affine(h2attItem[h], ancestor); + try_get_intersect_point_with_item(path, h2attItem[h], h2i2anc, path2anc, + (h == 0), endPos[h]); + } + } + change_endpts(path->curve, endPos); if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); path->updateRepr(); + path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } } -// TODO: This triggering of makeInvalidPath could be cleaned up to be -// another option passed to move_compensate. + static void -sp_conn_end_shape_move_compensate(NR::Matrix const *mp, SPItem *moved_item, +sp_conn_end_shape_move(Geom::Matrix const */*mp*/, SPItem */*moved_item*/, SPPath *const path) { if (path->connEndPair.isAutoRoutingConn()) { - path->connEndPair.makePathInvalid(); + path->connEndPair.tellLibavoidNewEndpoints(); } - sp_conn_end_move_compensate(mp, moved_item, path); } void -sp_conn_adjust_invalid_path(SPPath *const path) +sp_conn_reroute_path(SPPath *const path) { - sp_conn_end_move_compensate(NULL, NULL, path); + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } } + void -sp_conn_adjust_path(SPPath *const path) +sp_conn_reroute_path_immediate(SPPath *const path) { if (path->connEndPair.isAutoRoutingConn()) { - path->connEndPair.makePathInvalid(); + bool processTransaction = true; + path->connEndPair.tellLibavoidNewEndpoints(processTransaction); } // Don't update the path repr or else connector dragging is slowed by // constant update of values to the xml editor, and each step is also // needlessly remembered by undo/redo. bool const updatePathRepr = false; - sp_conn_end_move_compensate(NULL, NULL, path, updatePathRepr); + sp_conn_get_route_and_redraw(path, updatePathRepr); } -static NR::Point -calc_bbox_conn_pt(NR::Rect const &bbox, NR::Point const &p) +void sp_conn_redraw_path(SPPath *const path) { - using NR::X; - using NR::Y; - NR::Point const ctr(bbox.midpoint()); - NR::Point const lengths(bbox.dimensions()); - if ( ctr == p ) { - /* Arbitrarily choose centre of right edge. */ - return NR::Point(ctr[X] + .5 * lengths[X], - ctr[Y]); - } - NR::Point const cp( p - ctr ); - NR::Dim2 const edgeDim = ( ( fabs(lengths[Y] * cp[X]) < - fabs(lengths[X] * cp[Y]) ) - ? Y - : X ); - NR::Dim2 const otherDim = (NR::Dim2) !edgeDim; - NR::Point offset; - offset[edgeDim] = (signed_one(cp[edgeDim]) - * lengths[edgeDim]); - offset[otherDim] = (lengths[edgeDim] - * cp[otherDim] - / fabs(cp[edgeDim])); - g_assert((offset[otherDim] >= 0) == (cp[otherDim] >= 0)); -#ifndef NDEBUG - for (unsigned d = 0; d < 2; ++d) { - g_assert(fabs(offset[d]) <= lengths[d] + .125); - } -#endif - return ctr + .5 * offset; + sp_conn_get_route_and_redraw(path); } -static double signed_one(double const x) -{ - return (x < 0 - ? -1. - : 1.); -} static void -change_endpts(SPCurve *const curve, NR::Point const h2endPt[2]) +change_endpts(SPCurve *const curve, double const endPos[2]) { -#if 0 - curve->reset(); - curve->moveto(h2endPt[0]); - curve->lineto(h2endPt[1]); -#else - curve->move_endpoints(h2endPt[0], h2endPt[1]); -#endif + // Use Geom::Path::portion to cut the curve at the end positions + if (endPos[0] > endPos[1]) + { + // Path is "negative", reset the curve and return + curve->reset(); + return; + } + const Geom::Path& old_path = curve->get_pathvector()[0]; + Geom::PathVector new_path_vector; + new_path_vector.push_back(old_path.portion(endPos[0], endPos[1])); + curve->set_pathvector(new_path_vector); } static void @@ -239,9 +231,10 @@ sp_conn_end_deleted(SPObject *, SPObject *const owner, unsigned const handle_ix) // todo: The first argument is the deleted object, or just NULL if // called by sp_conn_end_detach. g_return_if_fail(handle_ix < 2); - char const *const attr_str[] = {"inkscape:connection-start", - "inkscape:connection-end"}; - SP_OBJECT_REPR(owner)->setAttribute(attr_str[handle_ix], NULL); + char const * const attr_strs[] = {"inkscape:connection-start", "inkscape:connection-start-point", + "inkscape:connection-end", "inkscape:connection-end-point"}; + SP_OBJECT_REPR(owner)->setAttribute(attr_strs[2*handle_ix], NULL); + SP_OBJECT_REPR(owner)->setAttribute(attr_strs[2*handle_ix+1], NULL); /* I believe this will trigger sp_conn_end_href_changed. */ } @@ -252,29 +245,119 @@ sp_conn_end_detach(SPObject *const owner, unsigned const handle_ix) } void -SPConnEnd::setAttacherHref(gchar const *value) +SPConnEnd::setAttacherHref(gchar const *value, SPPath* /*path*/) { if ( value && href && ( strcmp(value, href) == 0 ) ) { /* No change, do nothing. */ - } else { - g_free(href); - href = NULL; - if (value) { - // First, set the href field, because sp_conn_end_href_changed will need it. + } + else + { + if (!value) + { + ref.detach(); + g_free(href); + href = NULL; + } + else + { + bool validRef = true; href = g_strdup(value); - // Now do the attaching, which emits the changed signal. try { ref.attach(Inkscape::URI(value)); } catch (Inkscape::BadURIException &e) { /* TODO: Proper error handling as per - * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. (Also needed for - * sp-use.) */ + * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. (Also needed for + * sp-use.) */ g_warning("%s", e.what()); + validRef = false; + } + + if ( !validRef ) + { ref.detach(); + g_free(href); + href = NULL; + } + } + } +} + +void +SPConnEnd::setAttacherEndpoint(gchar const *value, SPPath* /*path*/) +{ + + /* References to the connection points have the following format + , where t is the type of the point, which + can be either "d" for default or "u" for user-defined, and + id is the local (inside the item) id of the connection point. + In the case of default points id represents the position on the + item (i.e. Top-Left, Center-Center, etc.). + */ + + bool changed = false; + ConnPointType newtype = type; + + if (!value) + { + // Default to center endpoint + type = ConnPointDefault; + id = 4; + } + else + { + switch (value[0]) + { + case 'd': + if ( newtype != ConnPointDefault ) + { + newtype = ConnPointDefault; + changed = true; + } + break; + case 'u': + if ( newtype != ConnPointUserDefined) + { + newtype = ConnPointUserDefined; + changed = true; + } + break; + default: + g_warning("Bad reference to a connection point."); + } + + int newid = (int) g_ascii_strtod( value+1, 0 ); + if ( id != newid ) + { + id = newid; + changed = true; + } + + // We have to verify that the reference to the + // connection point is a valid one. + + if ( changed ) + { + + // Get the item the connector is attached to + SPItem* item = ref.getObject(); + if ( item ) + { + if (!item->avoidRef->isValidConnPointId( newtype, newid ) ) + { + g_warning("Bad reference to a connection point."); + } + else + { + type = newtype; + id = newid; + } + /* // Update the connector + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } + */ } - } else { - ref.detach(); } } } @@ -295,7 +378,7 @@ sp_conn_end_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, = SP_OBJECT(refobj)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_conn_end_deleted), SP_OBJECT(path), handle_ix)); connEnd._transformed_connection - = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move_compensate), + = SP_ITEM(refobj)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_conn_end_shape_move), path)); } }