Code

Warning cleanup
[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         */
107         gchar ** strarray = g_strsplit(value, "|", 0);
108         gchar ** iter = strarray;
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 if (deletes.find(connEnds[ix]->id) != deletes.end()) {
179                     sp_conn_end_detach(path, ix);
180                 }
181             }
182         }
183     }
184     g_slist_free(conns);
185     // Remove all deleted connection points
186     if (deletes.size())
187         for (std::set<int>::iterator it = deletes.begin(); it != deletes.end(); ++it)
188             connection_points.erase(*it);
191 void SPAvoidRef::setConnectionPointsAttrUndoable(const gchar* value, const gchar* action)
193     SPDocument* doc = SP_OBJECT_DOCUMENT(item);
195     sp_object_setAttribute( SP_OBJECT(item), "inkscape:connection-points", value, 0 );
196     item->updateRepr();
197     sp_document_ensure_up_to_date(doc);
198     sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, action);
201 void SPAvoidRef::addConnectionPoint(ConnectionPoint &cp)
203     Inkscape::SVGOStringStream ostr;
204     bool first = true;
205     int newId = 1;
206     if ( connection_points.size() )
207     {
208         for (IdConnectionPointMap::iterator it = connection_points.begin(); ; )
209         {
210             if ( first )
211             {
212                 first = false;
213                 ostr<<it->second;
214             }
215             else
216                 ostr<<'|'<<it->second;
217             IdConnectionPointMap::iterator prev_it = it;
218             ++it;
219             if ( it == connection_points.end() || prev_it->first + 1 != it->first )
220             {
221                 newId = prev_it->first + 1;
222                 break;
223             }
224         }
225     }
226     cp.id = newId;
227     if ( first )
228     {
229         first = false;
230         ostr<<cp;
231     }
232     else
233         ostr<<'|'<<cp;
235     this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Added a new connection point") );
238 void SPAvoidRef::updateConnectionPoint(ConnectionPoint &cp)
240     Inkscape::SVGOStringStream ostr;
241     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
242     if ( cp_pos != connection_points.end() )
243     {
244         bool first = true;
245         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it)
246         {
247             ConnectionPoint* to_write;
248             if ( it != cp_pos )
249                 to_write = &it->second;
250             else
251                 to_write = &cp;
252             if ( first )
253             {
254                 first = false;
255                 ostr<<*to_write;
256             }
257             else
258                 ostr<<'|'<<*to_write;
259         }
260         this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Moved a connection point") );
261     }
264 void SPAvoidRef::deleteConnectionPoint(ConnectionPoint &cp)
266     Inkscape::SVGOStringStream ostr;
267     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
268     if ( cp_pos != connection_points.end() ) {
269         bool first = true;
270         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it) {
271             if ( it != cp_pos ) {
272                 if ( first ) {
273                     first = false;
274                     ostr<<it->second;
275                 } else {
276                     ostr<<'|'<<it->second;
277                 }
278             }
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;
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);
318             // Get a unique ID for the item.
319             GQuark itemID = g_quark_from_string(id);
321             shapeRef = new Avoid::ShapeRef(router, poly, itemID);
323             router->addShape(shapeRef);
324         }
325     }
326     else
327     {
328         g_assert(shapeRef);
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);
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);
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     // TODO investigate why this was asking for the active desktop:
390     SPDesktop *desktop = inkscape_active_desktop();
392     if ( type == ConnPointDefault )
393     {
394         // For now, just default to the centre of the item
395         Geom::OptRect bbox = item->getBounds(sp_item_i2doc_affine(item));
396         pos = (bbox) ? bbox->midpoint() : Geom::Point(0, 0);
397     }
398     else
399     {
400         // Get coordinates from the list of connection points
401         // that are attached to the item
402         pos = connection_points[id].pos * transform;
403     }
405     return pos;
408 bool SPAvoidRef::isValidConnPointId( const int type, const int id )
410     if ( type < 0 || type > 1 )
411         return false;
412     else
413     {
414         if ( type == ConnPointDefault )
415             if ( id < 0 || id > 8 )
416                 return false;
417             else
418             {
419             }
420         else
421             return connection_points.find( id ) != connection_points.end();
422     }
424     return true;
427 static Avoid::Polygon avoid_item_poly(SPItem const *item)
429     SPDesktop *desktop = inkscape_active_desktop();
430     g_assert(desktop != NULL);
432     // TODO: The right way to do this is to return the convex hull of
433     //       the object, or an approximation in the case of a rounded
434     //       object.  Specific SPItems will need to have a new
435     //       function that returns points for the convex hull.
436     //       For some objects it is enough to feed the snappoints to
437     //       some convex hull code, though not NR::ConvexHull as this
438     //       only keeps the bounding box of the convex hull currently.
440     double spacing = desktop->namedview->connector_spacing;
442     // [sommer] If item is a shape, use an approximation of its convex hull
443     {
444         // MJW: Disable this for the moment.  It still has some issues.
445         const bool convex_hull_approximation_enabled = false;
447         if ( convex_hull_approximation_enabled && SP_IS_SHAPE (item) ) {
448             // The number of points to use for approximation
449             const unsigned NUM_POINTS = 64;
451 //             printf("[sommer] is a shape\n");
452             SPCurve* curve = sp_shape_get_curve (SP_SHAPE (item));
453             if (curve) {
454 //                 printf("[sommer] is a curve\n");
456                 // apply all transformations
457                 Geom::Matrix itd_mat = sp_item_i2doc_affine(item);
458                 curve->transform(itd_mat);
460                 // iterate over all paths
461                 const Geom::PathVector& curve_pv = curve->get_pathvector();
462                 std::vector<Geom::Point> hull_points;
463                 for (Geom::PathVector::const_iterator i = curve_pv.begin(); i != curve_pv.end(); i++) {
464                     const Geom::Path& curve_pv_path = *i;
465 //                     printf("[sommer] tracing sub-path\n");
467                     // FIXME: enlarge path by "desktop->namedview->connector_spacing" (using sp_selected_path_do_offset)?
469                     // use appropriate fraction of points for this path (first one gets any remainder)
470                     unsigned num_points = NUM_POINTS / curve_pv.size();
471                     if (i == curve_pv.begin()) num_points += NUM_POINTS - (num_points * curve_pv.size());
472                     printf("[sommer] using %d points for this path\n", num_points);
474                     // sample points along the path for approximation of convex hull
475                     for (unsigned n = 0; n < num_points; n++) {
476                         double at = curve_pv_path.size() / static_cast<double>(num_points) * n;
477                         Geom::Point pt = curve_pv_path.pointAt(at);
478                         hull_points.push_back(pt);
479                     }
480                 }
482                 curve->unref();
484                 // create convex hull from all sampled points
485                 Geom::ConvexHull hull(hull_points);
487                 // store expanded convex hull in Avoid::Polygn
488                 unsigned n = 0;
489                 Avoid::Polygon poly;
490 /*
491                 const Geom::Point& old_pt = *hull.boundary.begin();
492 */
494                 Geom::Line hull_edge(*hull.boundary.begin(), *(hull.boundary.begin()+1));
495                 Geom::Line parallel_hull_edge;
496                 parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
497                 parallel_hull_edge.versor(hull_edge.versor());
498                 Geom::Line bisector = Geom::make_angle_bisector_line( *(hull.boundary.end()), *hull.boundary.begin(),
499                                                                       *(hull.boundary.begin()+1));
500                 Geom::OptCrossing int_pt = Geom::intersection(parallel_hull_edge, bisector);
502                 if (int_pt)
503                 {
504                     Avoid::Point avoid_pt((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 //                     printf("[sommer] %f, %f\n", old_pt[Geom::X], old_pt[Geom::Y]);
507 /*                    printf("[sommer] %f, %f\n", (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X],
508                                                 (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);*/
509                     poly.ps.push_back(avoid_pt);
510                 }
511                 for (std::vector<Geom::Point>::const_iterator i = hull.boundary.begin() + 1; i != hull.boundary.end(); i++, n++) {
512 /*
513                         const Geom::Point& old_pt = *i;
514 */
515                         Geom::Line hull_edge(*i, *(i+1));
516                         Geom::Line parallel_hull_edge;
517                         parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
518                         parallel_hull_edge.versor(hull_edge.versor());
519                         Geom::Line bisector = Geom::make_angle_bisector_line( *(i-1), *i, *(i+1));
520                         Geom::OptCrossing intersect_pt = Geom::intersection(parallel_hull_edge, bisector);
522                         if (int_pt)
523                         {
524                             Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X],
525                                                   (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);
526 /*                            printf("[sommer] %f, %f\n", old_pt[Geom::X], old_pt[Geom::Y]);
527                             printf("[sommer] %f, %f\n", (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X],
528                                                         (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);*/
529                             poly.ps.push_back(avoid_pt);
530                         }
531                 }
534                 return poly;
535             }// else printf("[sommer] is no curve\n");
536         }// else printf("[sommer] is no shape\n");
537     }
539     Geom::OptRect rHull = item->getBounds(sp_item_i2doc_affine(item));
540     if (!rHull) {
541         return Avoid::Polygon();
542     }
544     // Add a little buffer around the edge of each object.
545     Geom::Rect rExpandedHull = *rHull;
546     rExpandedHull.expandBy(spacing);
547     Avoid::Polygon poly(4);
549     for (size_t n = 0; n < 4; ++n) {
550         Geom::Point hullPoint = rExpandedHull.corner(n);
551         poly.ps[n].x = hullPoint[Geom::X];
552         poly.ps[n].y = hullPoint[Geom::Y];
553     }
555     return poly;
559 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop,
560         bool initialised)
562     for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
563             child != NULL; child = SP_OBJECT_NEXT(child) ) {
564         if (SP_IS_ITEM(child) &&
565             !desktop->isLayer(SP_ITEM(child)) &&
566             !SP_ITEM(child)->isLocked() &&
567             !desktop->itemIsHidden(SP_ITEM(child)) &&
568             (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
569             )
570         {
571             list = g_slist_prepend (list, SP_ITEM(child));
572         }
574         if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
575             list = get_avoided_items(list, child, desktop, initialised);
576         }
577     }
579     return list;
583 void avoid_item_move(Geom::Matrix const */*mp*/, SPItem *moved_item)
585     Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
586     g_assert(shapeRef);
588     Router *router = moved_item->document->router;
589     Avoid::Polygon poly = avoid_item_poly(moved_item);
590     if (!poly.empty()) {
591         router->moveShape(shapeRef, poly);
592     }
596 void init_avoided_shape_geometry(SPDesktop *desktop)
598     // Don't count this as changes to the document,
599     // it is basically just late initialisation.
600     SPDocument *document = sp_desktop_document(desktop);
601     bool saved = sp_document_get_undo_sensitive(document);
602     sp_document_set_undo_sensitive(document, false);
604     bool initialised = false;
605     GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
606             initialised);
608     for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
609         SPItem *item = reinterpret_cast<SPItem *>(iter->data);
610         item->avoidRef->handleSettingChange();
611     }
613     if (items) {
614         g_slist_free(items);
615     }
616     sp_document_set_undo_sensitive(document, saved);
620 /*
621   Local Variables:
622   mode:c++
623   c-file-style:"stroustrup"
624   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
625   indent-tabs-mode:nil
626   fill-column:99
627   End:
628 */
629 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :