Code

c04ad9e49a4cb2d3a0c122653faf16a986bf721e
[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  *
7  * Copyright (C) 2005 Michael Wybrow
8  *
9  * Released under GNU GPL, read the file 'COPYING' for more information
10  */
13 #include <cstring>
14 #include <string>
15 #include <iostream>
17 #include "sp-item.h"
18 #include "display/curve.h"
19 #include "2geom/line.h"
20 #include "2geom/crossing.h"
21 #include "2geom/convex-cover.h"
22 #include "svg/stringstream.h"
23 #include "conn-avoid-ref.h"
24 #include "connection-points.h"
25 #include "sp-conn-end.h"
26 #include "sp-path.h"
27 #include "libavoid/router.h"
28 #include "libavoid/connector.h"
29 #include "libavoid/geomtypes.h"
30 #include "xml/node.h"
31 #include "document.h"
32 #include "desktop.h"
33 #include "desktop-handles.h"
34 #include "sp-namedview.h"
35 #include "inkscape.h"
36 #include <glibmm/i18n.h>
40 using Avoid::Router;
42 static Avoid::Polygon avoid_item_poly(SPItem const *item);
45 SPAvoidRef::SPAvoidRef(SPItem *spitem)
46     : shapeRef(NULL)
47     , item(spitem)
48     , setting(false)
49     , new_setting(false)
50     , _transformed_connection()
51 {
52 }
55 SPAvoidRef::~SPAvoidRef()
56 {
57     _transformed_connection.disconnect();
59     // If the document is being destroyed then the router instance 
60     // and the ShapeRefs will have been destroyed with it.
61     const bool routerInstanceExists = (item->document->router != NULL);
63     if (shapeRef && routerInstanceExists) {
64         Router *router = shapeRef->router();
65         router->removeShape(shapeRef);
66         delete shapeRef;
67     }
68     shapeRef = NULL;
69 }
72 void SPAvoidRef::setAvoid(char const *value)
73 {
74     if (SP_OBJECT_IS_CLONED(item)) {
75         // Don't keep avoidance information for cloned objects.
76         return;
77     }
78     new_setting = false;
79     if (value && (strcmp(value, "true") == 0)) {
80         new_setting = true;
81     }
82 }
84 void print_connection_points(std::map<int, ConnectionPoint>& cp)
85 {
86     std::map<int, ConnectionPoint>::iterator i;
87     for (i=cp.begin(); i!=cp.end(); ++i)
88     {
89         const ConnectionPoint& p = i->second;
90         std::cout<<p.id<<" "<<p.type<<" "<<p.pos[Geom::X]<<" "<<p.pos[Geom::Y]<<std::endl;
91     }
92 }
94 void SPAvoidRef::setConnectionPoints(gchar const *value)
95 {
96     std::set<int> updates;
97     std::set<int> deletes;
98     std::set<int> seen;
100     if (value)
101     {
102         /* Rebuild the connection points list.
103            Update the connectors for which
104            the endpoint has changed.
105         */
106         
107         gchar ** strarray = g_strsplit(value, "|", 0);
108         gchar ** iter = strarray;
109         
110         while (*iter != NULL) {
111             ConnectionPoint cp;
112             Inkscape::SVGIStringStream is(*iter);
113             is>>cp;
114             cp.type = ConnPointUserDefined;
116             /* Mark this connection point as seen, so we can delete
117                the other ones.
118             */
119             seen.insert(cp.id);
120             if ( connection_points.find(cp.id) != connection_points.end() )
121             {
122                 /* An already existing connection point.
123                    Check to see if changed, and, if it is
124                    the case, trigger connector update for
125                    the connector attached to this connection
126                    point. This is done by adding the
127                    connection point to a list of connection
128                    points to be updated.
129                 */
130                 if ( connection_points[cp.id] != cp )
131                     // The connection point got updated.
132                     // Put it in the update list.
133                     updates.insert(cp.id);
134             }
135             connection_points[cp.id] = cp;
136             ++iter;
137         }
138         /* Delete the connection points that didn't appear
139            in the new connection point list.
140         */
141         std::map<int, ConnectionPoint>::iterator it;
143         for (it=connection_points.begin(); it!=connection_points.end(); ++it)
144             if ( seen.find(it->first) == seen.end())
145                 deletes.insert(it->first);
146         g_strfreev(strarray);
147     }
148     else
149     {
150         /* Delete all the user-defined connection points
151            Actually we do this by adding them to the list
152            of connection points to be deleted.
153         */
154         std::map<int, ConnectionPoint>::iterator it;
156         for (it=connection_points.begin(); it!=connection_points.end(); ++it)
157             deletes.insert(it->first);
158     }
159     /* Act upon updates and deletes.
160     */
161     if (deletes.empty() && updates.empty())
162         // Nothing to do, just return.
163         return;
164     // Get a list of attached connectors.
165     GSList* conns = getAttachedConnectors(Avoid::runningToAndFrom);
166     for (GSList *i = conns; i != NULL; i = i->next)
167     {
168         SPPath* path = SP_PATH(i->data);
169         SPConnEnd** connEnds = path->connEndPair.getConnEnds();
170         for (int ix=0; ix<2; ++ix)
171             if (connEnds[ix]->type == ConnPointUserDefined)
172                 if (updates.find(connEnds[ix]->id) != updates.end())
173                     if (path->connEndPair.isAutoRoutingConn())
174                         path->connEndPair.tellLibavoidNewEndpoints();
175                     else
176                     {
177                     }
178                 else
179                     if (deletes.find(connEnds[ix]->id) != deletes.end())
180                         sp_conn_end_detach(path, ix);
181     }
182     g_slist_free(conns);
183     // Remove all deleted connection points
184     if (deletes.size())
185         for (std::set<int>::iterator it = deletes.begin(); it != deletes.end(); ++it)
186             connection_points.erase(*it);
189 void SPAvoidRef::setConnectionPointsAttrUndoable(const gchar* value, const gchar* action)
191     SPDocument* doc = SP_OBJECT_DOCUMENT(item);
192     
193     sp_object_setAttribute( SP_OBJECT(item), "inkscape:connection-points", value, 0 );
194     item->updateRepr();
195     sp_document_ensure_up_to_date(doc);
196     sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, action);
199 void SPAvoidRef::addConnectionPoint(ConnectionPoint &cp)
201     Inkscape::SVGOStringStream ostr;
202     bool first = true;
203     int newId = 1;
204     if ( connection_points.size() )
205     {
206         for (IdConnectionPointMap::iterator it = connection_points.begin(); ; )
207         {
208             if ( first )
209             {
210                 first = false;
211                 ostr<<it->second;
212             }
213             else
214                 ostr<<'|'<<it->second;
215             IdConnectionPointMap::iterator prev_it = it;
216             ++it;
217             if ( it == connection_points.end() || prev_it->first + 1 != it->first )
218             {
219                 newId = prev_it->first + 1;
220                 break;
221             }
222         }
223     }
224     cp.id = newId;
225     if ( first )
226     {
227         first = false;
228         ostr<<cp;
229     }
230     else
231         ostr<<'|'<<cp;
232         
233     this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Added a new connection point") );
236 void SPAvoidRef::updateConnectionPoint(ConnectionPoint &cp)
238     Inkscape::SVGOStringStream ostr;
239     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
240     if ( cp_pos != connection_points.end() )
241     {
242         bool first = true;
243         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it)
244         {
245             ConnectionPoint* to_write;
246             if ( it != cp_pos )
247                 to_write = &it->second;
248             else
249                 to_write = &cp;
250             if ( first )
251             {
252                 first = false;
253                 ostr<<*to_write;
254             }
255             else
256                 ostr<<'|'<<*to_write;
257         }
258         this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Moved a connection point") );
259     }
262 void SPAvoidRef::deleteConnectionPoint(ConnectionPoint &cp)
264     Inkscape::SVGOStringStream ostr;
265     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
266     if ( cp_pos != connection_points.end() )
267     {
268         bool first = true;
269         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it)
270         {
271             if ( it != cp_pos )
272                 if ( first )
273                 {
274                     first = false;
275                     ostr<<it->second;
276                 }
277                 else
278                     ostr<<'|'<<it->second;
279         }
280         this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Removed a connection point") );
281     }
284 void SPAvoidRef::handleSettingChange(void)
286     SPDesktop *desktop = inkscape_active_desktop();
287     if (desktop == NULL) {
288         return;
289     }
290     if (sp_desktop_document(desktop) != item->document) {
291         // We don't want to go any further if the active desktop's document
292         // isn't the same as the document that this item is part of.  This
293         // case can happen if a new document is loaded from the file chooser
294         // or via the recent file menu.  In this case, we can end up here
295         // as a rersult of a sp_document_ensure_up_to_date performed on a
296         // document not yet attached to the active desktop.
297         return;
298     }
300     if (new_setting == setting) {
301         // Don't need to make any changes
302         return;
303     }
304     setting = new_setting;
306     Router *router = item->document->router;
307     
308     _transformed_connection.disconnect();
309     if (new_setting) {
310         Avoid::Polygon poly = avoid_item_poly(item);
311         if (poly.size() > 0) {
312             _transformed_connection = item->connectTransformed(
313                     sigc::ptr_fun(&avoid_item_move));
315             const char *id = SP_OBJECT_REPR(item)->attribute("id");
316             g_assert(id != NULL);
317             
318             // Get a unique ID for the item.
319             GQuark itemID = g_quark_from_string(id);
321             shapeRef = new Avoid::ShapeRef(router, poly, itemID);
322         
323             router->addShape(shapeRef);
324         }
325     }
326     else
327     {
328         g_assert(shapeRef);
329         
330         router->removeShape(shapeRef);
331         delete shapeRef;
332         shapeRef = NULL;
333     }
337 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
339     GSList *list = NULL;
341     Avoid::IntList shapes;
342     GQuark shapeId = g_quark_from_string(item->id);
343     item->document->router->attachedShapes(shapes, shapeId, type);
344     
345     Avoid::IntList::iterator finish = shapes.end();
346     for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
347         const gchar *connId = g_quark_to_string(*i);
348         SPObject *obj = item->document->getObjectById(connId);
349         if (obj == NULL) {
350             g_warning("getAttachedShapes: Object with id=\"%s\" is not "
351                     "found. Skipping.", connId);
352             continue;
353         }
354         SPItem *shapeItem = SP_ITEM(obj);
355         list = g_slist_prepend(list, shapeItem);
356     }
357     return list;
361 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
363     GSList *list = NULL;
365     Avoid::IntList conns;
366     GQuark shapeId = g_quark_from_string(item->id);
367     item->document->router->attachedConns(conns, shapeId, type);
368     
369     Avoid::IntList::iterator finish = conns.end();
370     for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
371         const gchar *connId = g_quark_to_string(*i);
372         SPObject *obj = item->document->getObjectById(connId);
373         if (obj == NULL) {
374             g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
375                     "found. Skipping.", connId);
376             continue;
377         }
378         SPItem *connItem = SP_ITEM(obj);
379         list = g_slist_prepend(list, connItem);
380     }
381     return list;
384 Geom::Point SPAvoidRef::getConnectionPointPos(const int type, const int id)
386     g_assert(item);
387     Geom::Point pos;
388     const Geom::Matrix& transform = sp_item_i2doc_affine(item);
389     SPDesktop *desktop = inkscape_active_desktop();
391     if ( type == ConnPointDefault )
392     {
393         // For now, just default to the centre of the item
394         Geom::OptRect bbox = item->getBounds(sp_item_i2doc_affine(item));
395         pos = (bbox) ? bbox->midpoint() : Geom::Point(0, 0);
396     }
397     else
398     {
399         // Get coordinates from the list of connection points
400         // that are attached to the item
401         pos = connection_points[id].pos * transform;
402     }
404     return pos;
407 bool SPAvoidRef::isValidConnPointId( const int type, const int id )
409     if ( type < 0 || type > 1 )
410         return false;
411     else
412     {
413         if ( type == ConnPointDefault )
414             if ( id < 0 || id > 8 )
415                 return false;
416             else
417             {
418             }
419         else
420             return connection_points.find( id ) != connection_points.end();
421     }
422     
423     return true;
426 static Avoid::Polygon avoid_item_poly(SPItem const *item)
428     SPDesktop *desktop = inkscape_active_desktop();
429     g_assert(desktop != NULL);
431     // TODO: The right way to do this is to return the convex hull of
432     //       the object, or an approximation in the case of a rounded
433     //       object.  Specific SPItems will need to have a new
434     //       function that returns points for the convex hull.
435     //       For some objects it is enough to feed the snappoints to
436     //       some convex hull code, though not NR::ConvexHull as this
437     //       only keeps the bounding box of the convex hull currently.
439     double spacing = desktop->namedview->connector_spacing;
441     // [sommer] If item is a shape, use an approximation of its convex hull
442     {
443         // MJW: Disable this for the moment.  It still has some issues.
444         const bool convex_hull_approximation_enabled = false;
446         if ( convex_hull_approximation_enabled && SP_IS_SHAPE (item) ) {
447             // The number of points to use for approximation
448             const unsigned NUM_POINTS = 64; 
450 //             printf("[sommer] is a shape\n");
451             SPCurve* curve = sp_shape_get_curve (SP_SHAPE (item));
452             if (curve) {
453 //                 printf("[sommer] is a curve\n");
455                 // apply all transformations
456                 Geom::Matrix itd_mat = sp_item_i2doc_affine(item);
457                 curve->transform(itd_mat);
459                 // iterate over all paths
460                 const Geom::PathVector& curve_pv = curve->get_pathvector();
461                 std::vector<Geom::Point> hull_points;
462                 for (Geom::PathVector::const_iterator i = curve_pv.begin(); i != curve_pv.end(); i++) {
463                     const Geom::Path& curve_pv_path = *i; 
464 //                     printf("[sommer] tracing sub-path\n");
466                     // FIXME: enlarge path by "desktop->namedview->connector_spacing" (using sp_selected_path_do_offset)?
468                     // use appropriate fraction of points for this path (first one gets any remainder)
469                     unsigned num_points = NUM_POINTS / curve_pv.size();
470                     if (i == curve_pv.begin()) num_points += NUM_POINTS - (num_points * curve_pv.size());
471                     printf("[sommer] using %d points for this path\n", num_points);
473                     // sample points along the path for approximation of convex hull
474                     for (unsigned n = 0; n < num_points; n++) {
475                         double at = curve_pv_path.size() / static_cast<double>(num_points) * n;                        
476                         Geom::Point pt = curve_pv_path.pointAt(at);
477                         hull_points.push_back(pt);
478                     }
479                 }
481                 curve->unref();
483                 // create convex hull from all sampled points
484                 Geom::ConvexHull hull(hull_points);
486                 // store expanded convex hull in Avoid::Polygn
487                 unsigned n = 0;
488                 Avoid::Polygon poly;
489                 const Geom::Point& old_pt = *hull.boundary.begin();
491                 Geom::Line hull_edge(*hull.boundary.begin(), *(hull.boundary.begin()+1));
492                 Geom::Line parallel_hull_edge;
493                 parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
494                 parallel_hull_edge.versor(hull_edge.versor());
495                 Geom::Line bisector = Geom::make_angle_bisector_line( *(hull.boundary.end()), *hull.boundary.begin(),
496                                                                       *(hull.boundary.begin()+1));
497                 Geom::OptCrossing int_pt = Geom::intersection(parallel_hull_edge, bisector);
499                 if (int_pt)
500                 {
501                     Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X], 
502                                             (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);
503 //                     printf("[sommer] %f, %f\n", old_pt[Geom::X], old_pt[Geom::Y]);
504 /*                    printf("[sommer] %f, %f\n", (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X], 
505                                                 (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);*/
506                     poly.ps.push_back(avoid_pt);
507                 }
508                 for (std::vector<Geom::Point>::const_iterator i = hull.boundary.begin() + 1; i != hull.boundary.end(); i++, n++) {
509                         const Geom::Point& old_pt = *i;
510                         Geom::Line hull_edge(*i, *(i+1));
511                         Geom::Line parallel_hull_edge;
512                         parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
513                         parallel_hull_edge.versor(hull_edge.versor());
514                         Geom::Line bisector = Geom::make_angle_bisector_line( *(i-1), *i, *(i+1));
515                         Geom::OptCrossing intersect_pt = Geom::intersection(parallel_hull_edge, bisector);
517                         if (int_pt)
518                         {
519                             Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X], 
520                                                   (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);
521 /*                            printf("[sommer] %f, %f\n", old_pt[Geom::X], old_pt[Geom::Y]);
522                             printf("[sommer] %f, %f\n", (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X], 
523                                                         (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);*/
524                             poly.ps.push_back(avoid_pt);
525                         }
526                 }
527                 
529                 return poly;
530             }// else printf("[sommer] is no curve\n");
531         }// else printf("[sommer] is no shape\n");
532     }
533     
534     Geom::OptRect rHull = item->getBounds(sp_item_i2doc_affine(item));
535     if (!rHull) {
536         return Avoid::Polygon();
537     }
539     // Add a little buffer around the edge of each object.
540     Geom::Rect rExpandedHull = *rHull;
541     rExpandedHull.expandBy(spacing); 
542     Avoid::Polygon poly(4);
544     for (size_t n = 0; n < 4; ++n) {
545         Geom::Point hullPoint = rExpandedHull.corner(n);
546         poly.ps[n].x = hullPoint[Geom::X];
547         poly.ps[n].y = hullPoint[Geom::Y];
548     }
550     return poly;
554 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop, 
555         bool initialised)
557     for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
558             child != NULL; child = SP_OBJECT_NEXT(child) ) {
559         if (SP_IS_ITEM(child) &&
560             !desktop->isLayer(SP_ITEM(child)) &&
561             !SP_ITEM(child)->isLocked() && 
562             !desktop->itemIsHidden(SP_ITEM(child)) &&
563             (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
564             )
565         {
566             list = g_slist_prepend (list, SP_ITEM(child));
567         }
569         if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
570             list = get_avoided_items(list, child, desktop, initialised);
571         }
572     }
574     return list;
578 void avoid_item_move(Geom::Matrix const */*mp*/, SPItem *moved_item)
580     Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
581     g_assert(shapeRef);
583     Router *router = moved_item->document->router;
584     Avoid::Polygon poly = avoid_item_poly(moved_item);
585     if (!poly.empty()) {
586         router->moveShape(shapeRef, poly);
587     }
591 void init_avoided_shape_geometry(SPDesktop *desktop)
593     // Don't count this as changes to the document,
594     // it is basically just late initialisation.
595     SPDocument *document = sp_desktop_document(desktop);
596     bool saved = sp_document_get_undo_sensitive(document);
597     sp_document_set_undo_sensitive(document, false);
598     
599     bool initialised = false;
600     GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
601             initialised);
603     for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
604         SPItem *item = reinterpret_cast<SPItem *>(iter->data);
605         item->avoidRef->handleSettingChange();
606     }
608     if (items) {
609         g_slist_free(items);
610     }
611     sp_document_set_undo_sensitive(document, saved);
615 /*
616   Local Variables:
617   mode:c++
618   c-file-style:"stroustrup"
619   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
620   indent-tabs-mode:nil
621   fill-column:99
622   End:
623 */
624 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :