X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fconn-avoid-ref.cpp;h=21ef2deab53c14bd87502c68c47648ea9c14d249;hb=8a2e76b7021b9b960d7c30801a1a14461d9b5939;hp=540e8205fbbf191e093619c4e60e601c3faa5fa7;hpb=090b64622aefb974887ffe27031360a5022acc99;p=inkscape.git diff --git a/src/conn-avoid-ref.cpp b/src/conn-avoid-ref.cpp index 540e8205f..21ef2deab 100644 --- a/src/conn-avoid-ref.cpp +++ b/src/conn-avoid-ref.cpp @@ -3,6 +3,7 @@ * * Authors: * Michael Wybrow + * Abhishek Sharma * * Copyright (C) 2005 Michael Wybrow * @@ -10,26 +11,39 @@ */ +#include +#include +#include #include "sp-item.h" +#include "display/curve.h" +#include "2geom/line.h" +#include "2geom/crossing.h" +#include "2geom/convex-cover.h" +#include "helper/geom-curves.h" +#include "svg/stringstream.h" #include "conn-avoid-ref.h" -#include "libnr/nr-rect-ops.h" -#include "libavoid/polyutil.h" +#include "connection-points.h" +#include "sp-conn-end.h" +#include "sp-path.h" #include "libavoid/router.h" #include "libavoid/connector.h" -#include "xml/simple-node.cpp" +#include "libavoid/geomtypes.h" +#include "libavoid/shape.h" +#include "xml/node.h" #include "document.h" -#include "prefs-utils.h" - #include "desktop.h" #include "desktop-handles.h" #include "sp-namedview.h" +#include "sp-item-group.h" #include "inkscape.h" +#include +using Inkscape::DocumentUndo; using Avoid::Router; -static Avoid::Polygn avoid_item_poly(SPItem const *item); +static Avoid::Polygon avoid_item_poly(SPItem const *item); SPAvoidRef::SPAvoidRef(SPItem *spitem) @@ -45,13 +59,17 @@ SPAvoidRef::SPAvoidRef(SPItem *spitem) SPAvoidRef::~SPAvoidRef() { _transformed_connection.disconnect(); - if (shapeRef) { - Router *router = shapeRef->router(); - // shapeRef is finalised by delShape, - // so no memory is lost here. - router->delShape(shapeRef); - shapeRef = NULL; + + // If the document is being destroyed then the router instance + // and the ShapeRefs will have been destroyed with it. + const bool routerInstanceExists = (item->document->router != NULL); + + if (shapeRef && routerInstanceExists) { + // Deleting the shapeRef will remove it completely from + // an existing Router instance. + delete shapeRef; } + shapeRef = NULL; } @@ -67,6 +85,205 @@ void SPAvoidRef::setAvoid(char const *value) } } +void print_connection_points(std::map& cp) +{ + std::map::iterator i; + for (i=cp.begin(); i!=cp.end(); ++i) + { + const ConnectionPoint& p = i->second; + std::cout< updates; + std::set deletes; + std::set seen; + + if (value) + { + /* Rebuild the connection points list. + Update the connectors for which + the endpoint has changed. + */ + + gchar ** strarray = g_strsplit(value, "|", 0); + gchar ** iter = strarray; + + while (*iter != NULL) { + ConnectionPoint cp; + Inkscape::SVGIStringStream is(*iter); + is>>cp; + cp.type = ConnPointUserDefined; + + /* Mark this connection point as seen, so we can delete + the other ones. + */ + seen.insert(cp.id); + if ( connection_points.find(cp.id) != connection_points.end() ) + { + /* An already existing connection point. + Check to see if changed, and, if it is + the case, trigger connector update for + the connector attached to this connection + point. This is done by adding the + connection point to a list of connection + points to be updated. + */ + if ( connection_points[cp.id] != cp ) + // The connection point got updated. + // Put it in the update list. + updates.insert(cp.id); + } + connection_points[cp.id] = cp; + ++iter; + } + /* Delete the connection points that didn't appear + in the new connection point list. + */ + std::map::iterator it; + + for (it=connection_points.begin(); it!=connection_points.end(); ++it) + if ( seen.find(it->first) == seen.end()) + deletes.insert(it->first); + g_strfreev(strarray); + } + else + { + /* Delete all the user-defined connection points + Actually we do this by adding them to the list + of connection points to be deleted. + */ + std::map::iterator it; + + for (it=connection_points.begin(); it!=connection_points.end(); ++it) + deletes.insert(it->first); + } + /* Act upon updates and deletes. + */ + if (deletes.empty() && updates.empty()) + // Nothing to do, just return. + return; + // Get a list of attached connectors. + GSList* conns = getAttachedConnectors(Avoid::runningToAndFrom); + for (GSList *i = conns; i != NULL; i = i->next) + { + SPPath* path = SP_PATH(i->data); + SPConnEnd** connEnds = path->connEndPair.getConnEnds(); + for (int ix=0; ix<2; ++ix) { + if (connEnds[ix]->type == ConnPointUserDefined) { + if (updates.find(connEnds[ix]->id) != updates.end()) { + if (path->connEndPair.isAutoRoutingConn()) { + path->connEndPair.tellLibavoidNewEndpoints(); + } else { + } + } + else if (deletes.find(connEnds[ix]->id) != deletes.end()) { + sp_conn_end_detach(path, ix); + } + } + } + } + g_slist_free(conns); + // Remove all deleted connection points + if (deletes.size()) + for (std::set::iterator it = deletes.begin(); it != deletes.end(); ++it) + connection_points.erase(*it); +} + +void SPAvoidRef::setConnectionPointsAttrUndoable(const gchar* value, const gchar* action) +{ + SPDocument* doc = SP_OBJECT_DOCUMENT(item); + + item->setAttribute( "inkscape:connection-points", value, 0 ); + item->updateRepr(); + doc->ensureUpToDate(); + DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, action); +} + +void SPAvoidRef::addConnectionPoint(ConnectionPoint &cp) +{ + Inkscape::SVGOStringStream ostr; + bool first = true; + int newId = 1; + if ( connection_points.size() ) + { + for (IdConnectionPointMap::iterator it = connection_points.begin(); ; ) + { + if ( first ) + { + first = false; + ostr<second; + } + else + ostr<<'|'<second; + IdConnectionPointMap::iterator prev_it = it; + ++it; + if ( it == connection_points.end() || prev_it->first + 1 != it->first ) + { + newId = prev_it->first + 1; + break; + } + } + } + cp.id = newId; + if ( first ) + { + first = false; + ostr<setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Add a new connection point") ); +} + +void SPAvoidRef::updateConnectionPoint(ConnectionPoint &cp) +{ + Inkscape::SVGOStringStream ostr; + IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id ); + if ( cp_pos != connection_points.end() ) + { + bool first = true; + for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it) + { + ConnectionPoint* to_write; + if ( it != cp_pos ) + to_write = &it->second; + else + to_write = &cp; + if ( first ) + { + first = false; + ostr<<*to_write; + } + else + ostr<<'|'<<*to_write; + } + this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Move a connection point") ); + } +} + +void SPAvoidRef::deleteConnectionPoint(ConnectionPoint &cp) +{ + Inkscape::SVGOStringStream ostr; + IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id ); + if ( cp_pos != connection_points.end() ) { + bool first = true; + for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it) { + if ( it != cp_pos ) { + if ( first ) { + first = false; + ostr<second; + } else { + ostr<<'|'<second; + } + } + } + this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Remove a connection point") ); + } +} void SPAvoidRef::handleSettingChange(void) { @@ -79,7 +296,7 @@ void SPAvoidRef::handleSettingChange(void) // isn't the same as the document that this item is part of. This // case can happen if a new document is loaded from the file chooser // or via the recent file menu. In this case, we can end up here - // as a rersult of a sp_document_ensure_up_to_date performed on a + // as a rersult of a ensureUpToDate performed on a // document not yet attached to the active desktop. return; } @@ -91,33 +308,32 @@ void SPAvoidRef::handleSettingChange(void) setting = new_setting; Router *router = item->document->router; - + _transformed_connection.disconnect(); if (new_setting) { - _transformed_connection = item->connectTransformed( - sigc::ptr_fun(&avoid_item_move)); + Avoid::Polygon poly = avoid_item_poly(item); + if (poly.size() > 0) { + _transformed_connection = item->connectTransformed( + sigc::ptr_fun(&avoid_item_move)); - Avoid::Polygn poly = avoid_item_poly(item); - if (poly.pn > 0) { - const char *id = SP_OBJECT_REPR(item)->attribute("id"); + char const *id = item->getAttribute("id"); g_assert(id != NULL); - + // Get a unique ID for the item. GQuark itemID = g_quark_from_string(id); - shapeRef = new Avoid::ShapeRef(router, itemID, poly); - Avoid::freePoly(poly); - + shapeRef = new Avoid::ShapeRef(router, poly, itemID); + router->addShape(shapeRef); } } else { g_assert(shapeRef); - - // shapeRef is finalised by delShape, - // so no memory is lost here. - router->delShape(shapeRef); + + // Deleting the shapeRef will remove it completely from + // an existing Router instance. + delete shapeRef; shapeRef = NULL; } } @@ -128,9 +344,9 @@ GSList *SPAvoidRef::getAttachedShapes(const unsigned int type) GSList *list = NULL; Avoid::IntList shapes; - GQuark shapeId = g_quark_from_string(item->id); + GQuark shapeId = g_quark_from_string(item->getId()); item->document->router->attachedShapes(shapes, shapeId, type); - + Avoid::IntList::iterator finish = shapes.end(); for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) { const gchar *connId = g_quark_to_string(*i); @@ -152,9 +368,9 @@ GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type) GSList *list = NULL; Avoid::IntList conns; - GQuark shapeId = g_quark_from_string(item->id); + GQuark shapeId = g_quark_from_string(item->getId()); item->document->router->attachedConns(conns, shapeId, type); - + Avoid::IntList::iterator finish = conns.end(); for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) { const gchar *connId = g_quark_to_string(*i); @@ -170,76 +386,182 @@ GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type) return list; } - -static Avoid::Polygn avoid_item_poly(SPItem const *item) +Geom::Point SPAvoidRef::getConnectionPointPos(const int type, const int id) { - SPDesktop *desktop = inkscape_active_desktop(); - g_assert(desktop != NULL); + g_assert(item); + Geom::Point pos; + const Geom::Matrix& transform = item->i2doc_affine(); - Avoid::Polygn poly; + if ( type == ConnPointDefault ) + { + // For now, just default to the centre of the item + Geom::OptRect bbox = item->getBounds(item->i2doc_affine()); + pos = (bbox) ? bbox->midpoint() : Geom::Point(0, 0); + } + else + { + // Get coordinates from the list of connection points + // that are attached to the item + pos = connection_points[id].pos * transform; + } - // TODO: The right way to do this is to return the convex hull of - // the object, or an approximation in the case of a rounded - // object. Specific SPItems will need to have a new - // function that returns points for the convex hull. - // For some objects it is enough to feed the snappoints to - // some convex hull code, though not NR::ConvexHull as this - // only keeps the bounding box of the convex hull currently. + return pos; +} - // 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(item->document); +bool SPAvoidRef::isValidConnPointId( const int type, const int id ) +{ + if ( type < 0 || type > 1 ) + return false; + else + { + if ( type == ConnPointDefault ) + if ( id < 0 || id > 8 ) + return false; + else + { + } + else + return connection_points.find( id ) != connection_points.end(); + } + + return true; +} + +static std::vector approxCurveWithPoints(SPCurve *curve) +{ + // The number of segments to use for not straight curves approximation + const unsigned NUM_SEGS = 4; - NR::Maybe rHull = item->getBounds(sp_item_i2doc_affine(item)); - if (!rHull) { - return Avoid::newPoly(0); + const Geom::PathVector& curve_pv = curve->get_pathvector(); + + // The structure to hold the output + std::vector poly_points; + + // Iterate over all curves, adding the endpoints for linear curves and + // sampling the other curves + double seg_size = 1.0 / NUM_SEGS; + double at; + at = 0; + Geom::PathVector::const_iterator pit = curve_pv.begin(); + while (pit != curve_pv.end()) + { + Geom::Path::const_iterator cit = pit->begin(); + while (cit != pit->end()) + { + if (cit == pit->begin()) + { + poly_points.push_back(cit->initialPoint()); + } + + if (dynamic_cast(&*cit)) + { + at += seg_size; + if (at <= 1.0 ) + poly_points.push_back(cit->pointAt(at)); + else + { + at = 0.0; + ++cit; + } + } + else + { + poly_points.push_back(cit->finalPoint()); + ++cit; + } + } + ++pit; + } + return poly_points; +} + +static std::vector approxItemWithPoints(SPItem const *item, const Geom::Matrix& item_transform) +{ + // The structure to hold the output + std::vector poly_points; + + if (SP_IS_GROUP(item)) + { + SPGroup* group = SP_GROUP(item); + // consider all first-order children + for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) { + SPItem* child_item = SP_ITEM(i->data); + std::vector child_points = approxItemWithPoints(child_item, item_transform * child_item->transform); + poly_points.insert(poly_points.end(), child_points.begin(), child_points.end()); + } + } + else if (SP_IS_SHAPE(item)) + { + SPCurve* item_curve = SP_SHAPE(item)->getCurve(); + // make sure it has an associated curve + if (item_curve) + { + // apply transformations (up to common ancestor) + item_curve->transform(item_transform); + std::vector curve_points = approxCurveWithPoints(item_curve); + poly_points.insert(poly_points.end(), curve_points.begin(), curve_points.end()); + item_curve->unref(); + } } + return poly_points; +} +static Avoid::Polygon avoid_item_poly(SPItem const *item) +{ + SPDesktop *desktop = inkscape_active_desktop(); + g_assert(desktop != NULL); double spacing = desktop->namedview->connector_spacing; - // Add a little buffer around the edge of each object. - NR::Rect rExpandedHull = NR::expand(*rHull, -spacing); - poly = Avoid::newPoly(4); - - for (unsigned n = 0; n < 4; ++n) { - // TODO: I think the winding order in libavoid or inkscape might - // be backwards, probably due to the inverse y co-ordinates - // used for the screen. The '3 - n' reverses the order. - /* On "correct" winding order: Winding order of NR::Rect::corner is in a positive - * direction, like libart. "Positive direction" means the same as in most of Inkscape and - * SVG: if you visualize y as increasing upwards, as is the convention in mathematics, then - * positive angle is visualized as anticlockwise, as in mathematics; so if you visualize y - * as increasing downwards, as is common outside of mathematics, then positive angle - * direction is visualized as clockwise, as is common outside of mathematics. This - * convention makes it easier mix pure mathematics code with graphics code: the important - * thing when mixing code is that the number values stored in variables (representing y - * coordinate, angle) match up; variables store numbers, not visualized positions, and the - * programmer is free to switch between visualizations when thinking about a given piece of - * code. - * - * MathWorld, libart and NR::Rect::corner all seem to take positive winding (i.e. winding - * that yields +1 winding number inside a simple closed shape) to mean winding in a - * positive angle. This, together with the observation that variables store numbers rather - * than positions, suggests that NR::Rect::corner uses the right direction. - */ - NR::Point hullPoint = rExpandedHull.corner(3 - n); - poly.ps[n].x = hullPoint[NR::X]; - poly.ps[n].y = hullPoint[NR::Y]; - } + Geom::Matrix itd_mat = item->i2doc_affine(); + std::vector hull_points; + hull_points = approxItemWithPoints(item, itd_mat); + + // create convex hull from all sampled points + Geom::ConvexHull hull(hull_points); + // enlarge path by "desktop->namedview->connector_spacing" + // store expanded convex hull in Avoid::Polygn + Avoid::Polygon poly; + + Geom::Line hull_edge(hull[-1], hull[0]); + Geom::Line prev_parallel_hull_edge; + prev_parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing); + prev_parallel_hull_edge.versor(hull_edge.versor()); + int hull_size = hull.boundary.size(); + for (int i = 0; i < hull_size; ++i) + { + hull_edge.setBy2Points(hull[i], hull[i+1]); + Geom::Line parallel_hull_edge; + parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing); + parallel_hull_edge.versor(hull_edge.versor()); + + // determine the intersection point + + Geom::OptCrossing int_pt = Geom::intersection(parallel_hull_edge, prev_parallel_hull_edge); + if (int_pt) + { + Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X], + (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]); + poly.ps.push_back(avoid_pt); + } + else + { + // something went wrong... + std::cout<<"conn-avoid-ref.cpp: avoid_item_poly: Geom:intersection failed."<firstChild() ; child != NULL; child = child->next ) { if (SP_IS_ITEM(child) && !desktop->isLayer(SP_ITEM(child)) && - !SP_ITEM(child)->isLocked() && + !SP_ITEM(child)->isLocked() && !desktop->itemIsHidden(SP_ITEM(child)) && (!initialised || SP_ITEM(child)->avoidRef->shapeRef) ) @@ -256,16 +578,15 @@ GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop, } -void avoid_item_move(NR::Matrix const *mp, SPItem *moved_item) +void avoid_item_move(Geom::Matrix const */*mp*/, SPItem *moved_item) { Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef; g_assert(shapeRef); Router *router = moved_item->document->router; - Avoid::Polygn poly = avoid_item_poly(moved_item); - if (poly.pn > 0) { - router->moveShape(shapeRef, &poly); - Avoid::freePoly(poly); + Avoid::Polygon poly = avoid_item_poly(moved_item); + if (!poly.empty()) { + router->moveShape(shapeRef, poly); } } @@ -273,11 +594,11 @@ void avoid_item_move(NR::Matrix const *mp, SPItem *moved_item) void init_avoided_shape_geometry(SPDesktop *desktop) { // Don't count this as changes to the document, - // it is basically just llate initialisation. + // it is basically just late initialisation. SPDocument *document = sp_desktop_document(desktop); - bool saved = sp_document_get_undo_sensitive(document); - sp_document_set_undo_sensitive(document, false); - + bool saved = DocumentUndo::getUndoSensitive(document); + DocumentUndo::setUndoSensitive(document, false); + bool initialised = false; GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop, initialised); @@ -290,7 +611,7 @@ void init_avoided_shape_geometry(SPDesktop *desktop) if (items) { g_slist_free(items); } - sp_document_set_undo_sensitive(document, saved); + DocumentUndo::setUndoSensitive(document, saved); }