Code

Node tool: special case node duplication for endnodes - select new endnode
[inkscape.git] / src / conn-avoid-ref.cpp
1 /*
2  * A class for handling shape interaction with libavoid.
3  *
4  * Authors:
5  *   Michael Wybrow <mjwybrow@users.sourceforge.net>
6  *   Abhishek Sharma
7  *
8  * Copyright (C) 2005 Michael Wybrow
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
14 #include <cstring>
15 #include <string>
16 #include <iostream>
18 #include "sp-item.h"
19 #include "display/curve.h"
20 #include "2geom/line.h"
21 #include "2geom/crossing.h"
22 #include "2geom/convex-cover.h"
23 #include "helper/geom-curves.h"
24 #include "svg/stringstream.h"
25 #include "conn-avoid-ref.h"
26 #include "connection-points.h"
27 #include "sp-conn-end.h"
28 #include "sp-path.h"
29 #include "libavoid/router.h"
30 #include "libavoid/connector.h"
31 #include "libavoid/geomtypes.h"
32 #include "libavoid/shape.h"
33 #include "xml/node.h"
34 #include "document.h"
35 #include "desktop.h"
36 #include "desktop-handles.h"
37 #include "sp-namedview.h"
38 #include "sp-item-group.h"
39 #include "inkscape.h"
40 #include <glibmm/i18n.h>
42 using Inkscape::DocumentUndo;
44 using Avoid::Router;
46 static Avoid::Polygon avoid_item_poly(SPItem const *item);
49 SPAvoidRef::SPAvoidRef(SPItem *spitem)
50     : shapeRef(NULL)
51     , item(spitem)
52     , setting(false)
53     , new_setting(false)
54     , _transformed_connection()
55 {
56 }
59 SPAvoidRef::~SPAvoidRef()
60 {
61     _transformed_connection.disconnect();
63     // If the document is being destroyed then the router instance
64     // and the ShapeRefs will have been destroyed with it.
65     const bool routerInstanceExists = (item->document->router != NULL);
67     if (shapeRef && routerInstanceExists) {
68         // Deleting the shapeRef will remove it completely from 
69         // an existing Router instance.
70         delete shapeRef;
71     }
72     shapeRef = NULL;
73 }
76 void SPAvoidRef::setAvoid(char const *value)
77 {
78     if (SP_OBJECT_IS_CLONED(item)) {
79         // Don't keep avoidance information for cloned objects.
80         return;
81     }
82     new_setting = false;
83     if (value && (strcmp(value, "true") == 0)) {
84         new_setting = true;
85     }
86 }
88 void print_connection_points(std::map<int, ConnectionPoint>& cp)
89 {
90     std::map<int, ConnectionPoint>::iterator i;
91     for (i=cp.begin(); i!=cp.end(); ++i)
92     {
93         const ConnectionPoint& p = i->second;
94         std::cout<<p.id<<" "<<p.type<<" "<<p.pos[Geom::X]<<" "<<p.pos[Geom::Y]<<std::endl;
95     }
96 }
98 void SPAvoidRef::setConnectionPoints(gchar const *value)
99 {
100     std::set<int> updates;
101     std::set<int> deletes;
102     std::set<int> seen;
104     if (value)
105     {
106         /* Rebuild the connection points list.
107            Update the connectors for which
108            the endpoint has changed.
109         */
111         gchar ** strarray = g_strsplit(value, "|", 0);
112         gchar ** iter = strarray;
114         while (*iter != NULL) {
115             ConnectionPoint cp;
116             Inkscape::SVGIStringStream is(*iter);
117             is>>cp;
118             cp.type = ConnPointUserDefined;
120             /* Mark this connection point as seen, so we can delete
121                the other ones.
122             */
123             seen.insert(cp.id);
124             if ( connection_points.find(cp.id) != connection_points.end() )
125             {
126                 /* An already existing connection point.
127                    Check to see if changed, and, if it is
128                    the case, trigger connector update for
129                    the connector attached to this connection
130                    point. This is done by adding the
131                    connection point to a list of connection
132                    points to be updated.
133                 */
134                 if ( connection_points[cp.id] != cp )
135                     // The connection point got updated.
136                     // Put it in the update list.
137                     updates.insert(cp.id);
138             }
139             connection_points[cp.id] = cp;
140             ++iter;
141         }
142         /* Delete the connection points that didn't appear
143            in the new connection point list.
144         */
145         std::map<int, ConnectionPoint>::iterator it;
147         for (it=connection_points.begin(); it!=connection_points.end(); ++it)
148             if ( seen.find(it->first) == seen.end())
149                 deletes.insert(it->first);
150         g_strfreev(strarray);
151     }
152     else
153     {
154         /* Delete all the user-defined connection points
155            Actually we do this by adding them to the list
156            of connection points to be deleted.
157         */
158         std::map<int, ConnectionPoint>::iterator it;
160         for (it=connection_points.begin(); it!=connection_points.end(); ++it)
161             deletes.insert(it->first);
162     }
163     /* Act upon updates and deletes.
164     */
165     if (deletes.empty() && updates.empty())
166         // Nothing to do, just return.
167         return;
168     // Get a list of attached connectors.
169     GSList* conns = getAttachedConnectors(Avoid::runningToAndFrom);
170     for (GSList *i = conns; i != NULL; i = i->next)
171     {
172         SPPath* path = SP_PATH(i->data);
173         SPConnEnd** connEnds = path->connEndPair.getConnEnds();
174         for (int ix=0; ix<2; ++ix) {
175             if (connEnds[ix]->type == ConnPointUserDefined) {
176                 if (updates.find(connEnds[ix]->id) != updates.end()) {
177                     if (path->connEndPair.isAutoRoutingConn()) {
178                         path->connEndPair.tellLibavoidNewEndpoints();
179                     } else {
180                     }
181                 }
182                 else if (deletes.find(connEnds[ix]->id) != deletes.end()) {
183                     sp_conn_end_detach(path, ix);
184                 }
185             }
186         }
187     }
188     g_slist_free(conns);
189     // Remove all deleted connection points
190     if (deletes.size())
191         for (std::set<int>::iterator it = deletes.begin(); it != deletes.end(); ++it)
192             connection_points.erase(*it);
195 void SPAvoidRef::setConnectionPointsAttrUndoable(const gchar* value, const gchar* action)
197     SPDocument* doc = SP_OBJECT_DOCUMENT(item);
199     item->setAttribute( "inkscape:connection-points", value, 0 );
200     item->updateRepr();
201     doc->ensureUpToDate();
202     DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, action);
205 void SPAvoidRef::addConnectionPoint(ConnectionPoint &cp)
207     Inkscape::SVGOStringStream ostr;
208     bool first = true;
209     int newId = 1;
210     if ( connection_points.size() )
211     {
212         for (IdConnectionPointMap::iterator it = connection_points.begin(); ; )
213         {
214             if ( first )
215             {
216                 first = false;
217                 ostr<<it->second;
218             }
219             else
220                 ostr<<'|'<<it->second;
221             IdConnectionPointMap::iterator prev_it = it;
222             ++it;
223             if ( it == connection_points.end() || prev_it->first + 1 != it->first )
224             {
225                 newId = prev_it->first + 1;
226                 break;
227             }
228         }
229     }
230     cp.id = newId;
231     if ( first )
232     {
233         first = false;
234         ostr<<cp;
235     }
236     else
237         ostr<<'|'<<cp;
239     this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Add a new connection point") );
242 void SPAvoidRef::updateConnectionPoint(ConnectionPoint &cp)
244     Inkscape::SVGOStringStream ostr;
245     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
246     if ( cp_pos != connection_points.end() )
247     {
248         bool first = true;
249         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it)
250         {
251             ConnectionPoint* to_write;
252             if ( it != cp_pos )
253                 to_write = &it->second;
254             else
255                 to_write = &cp;
256             if ( first )
257             {
258                 first = false;
259                 ostr<<*to_write;
260             }
261             else
262                 ostr<<'|'<<*to_write;
263         }
264         this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Move a connection point") );
265     }
268 void SPAvoidRef::deleteConnectionPoint(ConnectionPoint &cp)
270     Inkscape::SVGOStringStream ostr;
271     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
272     if ( cp_pos != connection_points.end() ) {
273         bool first = true;
274         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it) {
275             if ( it != cp_pos ) {
276                 if ( first ) {
277                     first = false;
278                     ostr<<it->second;
279                 } else {
280                     ostr<<'|'<<it->second;
281                 }
282             }
283         }
284         this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Remove a connection point") );
285     }
288 void SPAvoidRef::handleSettingChange(void)
290     SPDesktop *desktop = inkscape_active_desktop();
291     if (desktop == NULL) {
292         return;
293     }
294     if (sp_desktop_document(desktop) != item->document) {
295         // We don't want to go any further if the active desktop's document
296         // isn't the same as the document that this item is part of.  This
297         // case can happen if a new document is loaded from the file chooser
298         // or via the recent file menu.  In this case, we can end up here
299         // as a rersult of a ensureUpToDate performed on a
300         // document not yet attached to the active desktop.
301         return;
302     }
304     if (new_setting == setting) {
305         // Don't need to make any changes
306         return;
307     }
308     setting = new_setting;
310     Router *router = item->document->router;
312     _transformed_connection.disconnect();
313     if (new_setting) {
314         Avoid::Polygon poly = avoid_item_poly(item);
315         if (poly.size() > 0) {
316             _transformed_connection = item->connectTransformed(
317                     sigc::ptr_fun(&avoid_item_move));
319             char const *id = item->getAttribute("id");
320             g_assert(id != NULL);
322             // Get a unique ID for the item.
323             GQuark itemID = g_quark_from_string(id);
325             shapeRef = new Avoid::ShapeRef(router, poly, itemID);
327             router->addShape(shapeRef);
328         }
329     }
330     else
331     {
332         g_assert(shapeRef);
334         // Deleting the shapeRef will remove it completely from 
335         // an existing Router instance.
336         delete shapeRef;
337         shapeRef = NULL;
338     }
342 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
344     GSList *list = NULL;
346     Avoid::IntList shapes;
347     GQuark shapeId = g_quark_from_string(item->getId());
348     item->document->router->attachedShapes(shapes, shapeId, type);
350     Avoid::IntList::iterator finish = shapes.end();
351     for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
352         const gchar *connId = g_quark_to_string(*i);
353         SPObject *obj = item->document->getObjectById(connId);
354         if (obj == NULL) {
355             g_warning("getAttachedShapes: Object with id=\"%s\" is not "
356                     "found. Skipping.", connId);
357             continue;
358         }
359         SPItem *shapeItem = SP_ITEM(obj);
360         list = g_slist_prepend(list, shapeItem);
361     }
362     return list;
366 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
368     GSList *list = NULL;
370     Avoid::IntList conns;
371     GQuark shapeId = g_quark_from_string(item->getId());
372     item->document->router->attachedConns(conns, shapeId, type);
374     Avoid::IntList::iterator finish = conns.end();
375     for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
376         const gchar *connId = g_quark_to_string(*i);
377         SPObject *obj = item->document->getObjectById(connId);
378         if (obj == NULL) {
379             g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
380                     "found. Skipping.", connId);
381             continue;
382         }
383         SPItem *connItem = SP_ITEM(obj);
384         list = g_slist_prepend(list, connItem);
385     }
386     return list;
389 Geom::Point SPAvoidRef::getConnectionPointPos(const int type, const int id)
391     g_assert(item);
392     Geom::Point pos;
393     const Geom::Matrix& transform = item->i2doc_affine();
395     if ( type == ConnPointDefault )
396     {
397         // For now, just default to the centre of the item
398         Geom::OptRect bbox = item->getBounds(item->i2doc_affine());
399         pos = (bbox) ? bbox->midpoint() : Geom::Point(0, 0);
400     }
401     else
402     {
403         // Get coordinates from the list of connection points
404         // that are attached to the item
405         pos = connection_points[id].pos * transform;
406     }
408     return pos;
411 bool SPAvoidRef::isValidConnPointId( const int type, const int id )
413     if ( type < 0 || type > 1 )
414         return false;
415     else
416     {
417         if ( type == ConnPointDefault )
418             if ( id < 0 || id > 8 )
419                 return false;
420             else
421             {
422             }
423         else
424             return connection_points.find( id ) != connection_points.end();
425     }
427     return true;
430 static std::vector<Geom::Point> approxCurveWithPoints(SPCurve *curve)
432     // The number of segments to use for not straight curves approximation
433     const unsigned NUM_SEGS = 4;
434     
435     const Geom::PathVector& curve_pv = curve->get_pathvector();
436    
437     // The structure to hold the output
438     std::vector<Geom::Point> poly_points;
440     // Iterate over all curves, adding the endpoints for linear curves and
441     // sampling the other curves
442     double seg_size = 1.0 / NUM_SEGS;
443     double at;
444     at = 0;
445     Geom::PathVector::const_iterator pit = curve_pv.begin();
446     while (pit != curve_pv.end())
447     {
448         Geom::Path::const_iterator cit = pit->begin();
449         while (cit != pit->end())
450         {
451             if (cit == pit->begin())
452             {
453                 poly_points.push_back(cit->initialPoint());
454             }
456             if (dynamic_cast<Geom::CubicBezier const*>(&*cit))
457             {
458                 at += seg_size;
459                 if (at <= 1.0 )
460                     poly_points.push_back(cit->pointAt(at));
461                 else
462                 {
463                     at = 0.0;
464                     ++cit;
465                 }
466             }
467             else
468             {
469                 poly_points.push_back(cit->finalPoint());
470                 ++cit;
471             }
472         }
473         ++pit;
474     }
475     return poly_points;
478 static std::vector<Geom::Point> approxItemWithPoints(SPItem const *item, const Geom::Matrix& item_transform)
480     // The structure to hold the output
481     std::vector<Geom::Point> poly_points;
483     if (SP_IS_GROUP(item))
484     {
485         SPGroup* group = SP_GROUP(item);
486         // consider all first-order children
487         for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) {
488             SPItem* child_item = SP_ITEM(i->data);
489             std::vector<Geom::Point> child_points = approxItemWithPoints(child_item, item_transform * child_item->transform);
490             poly_points.insert(poly_points.end(), child_points.begin(), child_points.end());
491         }
492     }
493     else if (SP_IS_SHAPE(item))
494     {
495         SPCurve* item_curve = SP_SHAPE(item)->getCurve();
496         // make sure it has an associated curve
497         if (item_curve)
498         {
499             // apply transformations (up to common ancestor)
500             item_curve->transform(item_transform);
501             std::vector<Geom::Point> curve_points = approxCurveWithPoints(item_curve);
502             poly_points.insert(poly_points.end(), curve_points.begin(), curve_points.end());
503             item_curve->unref();
504         }
505     }
507     return poly_points;
509 static Avoid::Polygon avoid_item_poly(SPItem const *item)
511     SPDesktop *desktop = inkscape_active_desktop();
512     g_assert(desktop != NULL);
513     double spacing = desktop->namedview->connector_spacing;
515     Geom::Matrix itd_mat = item->i2doc_affine();
516     std::vector<Geom::Point> hull_points;
517     hull_points = approxItemWithPoints(item, itd_mat);
519     // create convex hull from all sampled points
520     Geom::ConvexHull hull(hull_points);
522     // enlarge path by "desktop->namedview->connector_spacing"
523     // store expanded convex hull in Avoid::Polygn
524     Avoid::Polygon poly;
526     Geom::Line hull_edge(hull[-1], hull[0]);
527     Geom::Line prev_parallel_hull_edge;
528     prev_parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
529     prev_parallel_hull_edge.versor(hull_edge.versor());
530     int hull_size = hull.boundary.size();
531     for (int i = 0; i < hull_size; ++i)
532     {
533         hull_edge.setBy2Points(hull[i], hull[i+1]);
534         Geom::Line parallel_hull_edge;
535         parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
536         parallel_hull_edge.versor(hull_edge.versor());
537         
538         // determine the intersection point
539         
540         Geom::OptCrossing int_pt = Geom::intersection(parallel_hull_edge, prev_parallel_hull_edge);
541         if (int_pt)
542         {
543             Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X],
544                                     (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);
545             poly.ps.push_back(avoid_pt);
546         }
547         else
548         {
549             // something went wrong...
550             std::cout<<"conn-avoid-ref.cpp: avoid_item_poly: Geom:intersection failed."<<std::endl;
551         }
552         prev_parallel_hull_edge = parallel_hull_edge;
553     }
554     return poly;
558 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop,
559         bool initialised)
561     for (SPObject *child = from->firstChild() ; child != NULL; child = child->next ) {
562         if (SP_IS_ITEM(child) &&
563             !desktop->isLayer(SP_ITEM(child)) &&
564             !SP_ITEM(child)->isLocked() &&
565             !desktop->itemIsHidden(SP_ITEM(child)) &&
566             (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
567             )
568         {
569             list = g_slist_prepend (list, SP_ITEM(child));
570         }
572         if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
573             list = get_avoided_items(list, child, desktop, initialised);
574         }
575     }
577     return list;
581 void avoid_item_move(Geom::Matrix const */*mp*/, SPItem *moved_item)
583     Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
584     g_assert(shapeRef);
586     Router *router = moved_item->document->router;
587     Avoid::Polygon poly = avoid_item_poly(moved_item);
588     if (!poly.empty()) {
589         router->moveShape(shapeRef, poly);
590     }
594 void init_avoided_shape_geometry(SPDesktop *desktop)
596     // Don't count this as changes to the document,
597     // it is basically just late initialisation.
598     SPDocument *document = sp_desktop_document(desktop);
599     bool saved = DocumentUndo::getUndoSensitive(document);
600     DocumentUndo::setUndoSensitive(document, false);
602     bool initialised = false;
603     GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
604             initialised);
606     for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
607         SPItem *item = reinterpret_cast<SPItem *>(iter->data);
608         item->avoidRef->handleSettingChange();
609     }
611     if (items) {
612         g_slist_free(items);
613     }
614     DocumentUndo::setUndoSensitive(document, saved);
618 /*
619   Local Variables:
620   mode:c++
621   c-file-style:"stroustrup"
622   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
623   indent-tabs-mode:nil
624   fill-column:99
625   End:
626 */
627 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :