Code

A simple layout document as to what, why and how is cppification.
[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 "helper/geom-curves.h"
23 #include "svg/stringstream.h"
24 #include "conn-avoid-ref.h"
25 #include "connection-points.h"
26 #include "sp-conn-end.h"
27 #include "sp-path.h"
28 #include "libavoid/router.h"
29 #include "libavoid/connector.h"
30 #include "libavoid/geomtypes.h"
31 #include "xml/node.h"
32 #include "document.h"
33 #include "desktop.h"
34 #include "desktop-handles.h"
35 #include "sp-namedview.h"
36 #include "sp-item-group.h"
37 #include "inkscape.h"
38 #include <glibmm/i18n.h>
42 using Avoid::Router;
44 static Avoid::Polygon avoid_item_poly(SPItem const *item);
47 SPAvoidRef::SPAvoidRef(SPItem *spitem)
48     : shapeRef(NULL)
49     , item(spitem)
50     , setting(false)
51     , new_setting(false)
52     , _transformed_connection()
53 {
54 }
57 SPAvoidRef::~SPAvoidRef()
58 {
59     _transformed_connection.disconnect();
61     // If the document is being destroyed then the router instance
62     // and the ShapeRefs will have been destroyed with it.
63     const bool routerInstanceExists = (item->document->router != NULL);
65     if (shapeRef && routerInstanceExists) {
66         Router *router = shapeRef->router();
67         router->removeShape(shapeRef);
68         delete shapeRef;
69     }
70     shapeRef = NULL;
71 }
74 void SPAvoidRef::setAvoid(char const *value)
75 {
76     if (SP_OBJECT_IS_CLONED(item)) {
77         // Don't keep avoidance information for cloned objects.
78         return;
79     }
80     new_setting = false;
81     if (value && (strcmp(value, "true") == 0)) {
82         new_setting = true;
83     }
84 }
86 void print_connection_points(std::map<int, ConnectionPoint>& cp)
87 {
88     std::map<int, ConnectionPoint>::iterator i;
89     for (i=cp.begin(); i!=cp.end(); ++i)
90     {
91         const ConnectionPoint& p = i->second;
92         std::cout<<p.id<<" "<<p.type<<" "<<p.pos[Geom::X]<<" "<<p.pos[Geom::Y]<<std::endl;
93     }
94 }
96 void SPAvoidRef::setConnectionPoints(gchar const *value)
97 {
98     std::set<int> updates;
99     std::set<int> deletes;
100     std::set<int> seen;
102     if (value)
103     {
104         /* Rebuild the connection points list.
105            Update the connectors for which
106            the endpoint has changed.
107         */
109         gchar ** strarray = g_strsplit(value, "|", 0);
110         gchar ** iter = strarray;
112         while (*iter != NULL) {
113             ConnectionPoint cp;
114             Inkscape::SVGIStringStream is(*iter);
115             is>>cp;
116             cp.type = ConnPointUserDefined;
118             /* Mark this connection point as seen, so we can delete
119                the other ones.
120             */
121             seen.insert(cp.id);
122             if ( connection_points.find(cp.id) != connection_points.end() )
123             {
124                 /* An already existing connection point.
125                    Check to see if changed, and, if it is
126                    the case, trigger connector update for
127                    the connector attached to this connection
128                    point. This is done by adding the
129                    connection point to a list of connection
130                    points to be updated.
131                 */
132                 if ( connection_points[cp.id] != cp )
133                     // The connection point got updated.
134                     // Put it in the update list.
135                     updates.insert(cp.id);
136             }
137             connection_points[cp.id] = cp;
138             ++iter;
139         }
140         /* Delete the connection points that didn't appear
141            in the new connection point list.
142         */
143         std::map<int, ConnectionPoint>::iterator it;
145         for (it=connection_points.begin(); it!=connection_points.end(); ++it)
146             if ( seen.find(it->first) == seen.end())
147                 deletes.insert(it->first);
148         g_strfreev(strarray);
149     }
150     else
151     {
152         /* Delete all the user-defined connection points
153            Actually we do this by adding them to the list
154            of connection points to be deleted.
155         */
156         std::map<int, ConnectionPoint>::iterator it;
158         for (it=connection_points.begin(); it!=connection_points.end(); ++it)
159             deletes.insert(it->first);
160     }
161     /* Act upon updates and deletes.
162     */
163     if (deletes.empty() && updates.empty())
164         // Nothing to do, just return.
165         return;
166     // Get a list of attached connectors.
167     GSList* conns = getAttachedConnectors(Avoid::runningToAndFrom);
168     for (GSList *i = conns; i != NULL; i = i->next)
169     {
170         SPPath* path = SP_PATH(i->data);
171         SPConnEnd** connEnds = path->connEndPair.getConnEnds();
172         for (int ix=0; ix<2; ++ix) {
173             if (connEnds[ix]->type == ConnPointUserDefined) {
174                 if (updates.find(connEnds[ix]->id) != updates.end()) {
175                     if (path->connEndPair.isAutoRoutingConn()) {
176                         path->connEndPair.tellLibavoidNewEndpoints();
177                     } else {
178                     }
179                 }
180                 else if (deletes.find(connEnds[ix]->id) != deletes.end()) {
181                     sp_conn_end_detach(path, ix);
182                 }
183             }
184         }
185     }
186     g_slist_free(conns);
187     // Remove all deleted connection points
188     if (deletes.size())
189         for (std::set<int>::iterator it = deletes.begin(); it != deletes.end(); ++it)
190             connection_points.erase(*it);
193 void SPAvoidRef::setConnectionPointsAttrUndoable(const gchar* value, const gchar* action)
195     SPDocument* doc = SP_OBJECT_DOCUMENT(item);
197     SP_OBJECT(item)->setAttribute( "inkscape:connection-points", value, 0 );
198     item->updateRepr();
199     doc->ensure_up_to_date();
200     SPDocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, action);
203 void SPAvoidRef::addConnectionPoint(ConnectionPoint &cp)
205     Inkscape::SVGOStringStream ostr;
206     bool first = true;
207     int newId = 1;
208     if ( connection_points.size() )
209     {
210         for (IdConnectionPointMap::iterator it = connection_points.begin(); ; )
211         {
212             if ( first )
213             {
214                 first = false;
215                 ostr<<it->second;
216             }
217             else
218                 ostr<<'|'<<it->second;
219             IdConnectionPointMap::iterator prev_it = it;
220             ++it;
221             if ( it == connection_points.end() || prev_it->first + 1 != it->first )
222             {
223                 newId = prev_it->first + 1;
224                 break;
225             }
226         }
227     }
228     cp.id = newId;
229     if ( first )
230     {
231         first = false;
232         ostr<<cp;
233     }
234     else
235         ostr<<'|'<<cp;
237     this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Add a new connection point") );
240 void SPAvoidRef::updateConnectionPoint(ConnectionPoint &cp)
242     Inkscape::SVGOStringStream ostr;
243     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
244     if ( cp_pos != connection_points.end() )
245     {
246         bool first = true;
247         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it)
248         {
249             ConnectionPoint* to_write;
250             if ( it != cp_pos )
251                 to_write = &it->second;
252             else
253                 to_write = &cp;
254             if ( first )
255             {
256                 first = false;
257                 ostr<<*to_write;
258             }
259             else
260                 ostr<<'|'<<*to_write;
261         }
262         this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Move a connection point") );
263     }
266 void SPAvoidRef::deleteConnectionPoint(ConnectionPoint &cp)
268     Inkscape::SVGOStringStream ostr;
269     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
270     if ( cp_pos != connection_points.end() ) {
271         bool first = true;
272         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it) {
273             if ( it != cp_pos ) {
274                 if ( first ) {
275                     first = false;
276                     ostr<<it->second;
277                 } else {
278                     ostr<<'|'<<it->second;
279                 }
280             }
281         }
282         this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Remove a connection point") );
283     }
286 void SPAvoidRef::handleSettingChange(void)
288     SPDesktop *desktop = inkscape_active_desktop();
289     if (desktop == NULL) {
290         return;
291     }
292     if (sp_desktop_document(desktop) != item->document) {
293         // We don't want to go any further if the active desktop's document
294         // isn't the same as the document that this item is part of.  This
295         // case can happen if a new document is loaded from the file chooser
296         // or via the recent file menu.  In this case, we can end up here
297         // as a rersult of a sp_document_ensure_up_to_date performed on a
298         // document not yet attached to the active desktop.
299         return;
300     }
302     if (new_setting == setting) {
303         // Don't need to make any changes
304         return;
305     }
306     setting = new_setting;
308     Router *router = item->document->router;
310     _transformed_connection.disconnect();
311     if (new_setting) {
312         Avoid::Polygon poly = avoid_item_poly(item);
313         if (poly.size() > 0) {
314             _transformed_connection = item->connectTransformed(
315                     sigc::ptr_fun(&avoid_item_move));
317             //const char *id = SP_OBJECT_REPR(item)->attribute("id");
318             const char *id = item->getAttribute("id");
319             g_assert(id != NULL);
321             // Get a unique ID for the item.
322             GQuark itemID = g_quark_from_string(id);
324             shapeRef = new Avoid::ShapeRef(router, poly, itemID);
326             router->addShape(shapeRef);
327         }
328     }
329     else
330     {
331         g_assert(shapeRef);
333         router->removeShape(shapeRef);
334         delete shapeRef;
335         shapeRef = NULL;
336     }
340 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
342     GSList *list = NULL;
344     Avoid::IntList shapes;
345     GQuark shapeId = g_quark_from_string(item->getId());
346     item->document->router->attachedShapes(shapes, shapeId, type);
348     Avoid::IntList::iterator finish = shapes.end();
349     for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
350         const gchar *connId = g_quark_to_string(*i);
351         SPObject *obj = item->document->getObjectById(connId);
352         if (obj == NULL) {
353             g_warning("getAttachedShapes: Object with id=\"%s\" is not "
354                     "found. Skipping.", connId);
355             continue;
356         }
357         SPItem *shapeItem = SP_ITEM(obj);
358         list = g_slist_prepend(list, shapeItem);
359     }
360     return list;
364 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
366     GSList *list = NULL;
368     Avoid::IntList conns;
369     GQuark shapeId = g_quark_from_string(item->getId());
370     item->document->router->attachedConns(conns, shapeId, type);
372     Avoid::IntList::iterator finish = conns.end();
373     for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
374         const gchar *connId = g_quark_to_string(*i);
375         SPObject *obj = item->document->getObjectById(connId);
376         if (obj == NULL) {
377             g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
378                     "found. Skipping.", connId);
379             continue;
380         }
381         SPItem *connItem = SP_ITEM(obj);
382         list = g_slist_prepend(list, connItem);
383     }
384     return list;
387 Geom::Point SPAvoidRef::getConnectionPointPos(const int type, const int id)
389     g_assert(item);
390     Geom::Point pos;
391     const Geom::Matrix& transform = item->i2doc_affine();
392     // TODO investigate why this was asking for the active desktop:
393     SPDesktop *desktop = inkscape_active_desktop();
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             if (dynamic_cast<Geom::CubicBezier const*>(&*cit))
451             {
452                 at += seg_size;
453                 if (at <= 1.0 )
454                     poly_points.push_back(cit->pointAt(at));
455                 else
456                 {
457                     at = 0.0;
458                     ++cit;
459                 }
460             }
461             else
462             {
463                 poly_points.push_back(cit->finalPoint());
464                 ++cit;
465             }
466         ++pit;
467     }
468     return poly_points;
471 static std::vector<Geom::Point> approxItemWithPoints(SPItem const *item, const Geom::Matrix& item_transform)
473     // The structure to hold the output
474     std::vector<Geom::Point> poly_points;
476     if (SP_IS_GROUP(item))
477     {
478         SPGroup* group = SP_GROUP(item);
479         // consider all first-order children
480         for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) {
481             SPItem* child_item = SP_ITEM(i->data);
482             std::vector<Geom::Point> child_points = approxItemWithPoints(child_item, item_transform * child_item->transform);
483             poly_points.insert(poly_points.end(), child_points.begin(), child_points.end());
484         }
485     }
486     else if (SP_IS_SHAPE(item))
487     {
488         SPCurve* item_curve = SP_SHAPE(item)->getCurve();
489         // make sure it has an associated curve
490         if (item_curve)
491         {
492             // apply transformations (up to common ancestor)
493             item_curve->transform(item_transform);
494             std::vector<Geom::Point> curve_points = approxCurveWithPoints(item_curve);
495             poly_points.insert(poly_points.end(), curve_points.begin(), curve_points.end());
496             item_curve->unref();
497         }
498     }
500     return poly_points;
502 static Avoid::Polygon avoid_item_poly(SPItem const *item)
504     SPDesktop *desktop = inkscape_active_desktop();
505     g_assert(desktop != NULL);
506     double spacing = desktop->namedview->connector_spacing;
508     Geom::Matrix itd_mat = item->i2doc_affine();
509     std::vector<Geom::Point> hull_points;
510     hull_points = approxItemWithPoints(item, itd_mat);
512     // create convex hull from all sampled points
513     Geom::ConvexHull hull(hull_points);
515     // enlarge path by "desktop->namedview->connector_spacing"
516     // store expanded convex hull in Avoid::Polygn
517     Avoid::Polygon poly;
519     Geom::Line hull_edge(hull[-1], hull[0]);
520     Geom::Line prev_parallel_hull_edge;
521     prev_parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
522     prev_parallel_hull_edge.versor(hull_edge.versor());
523     int hull_size = hull.boundary.size();
524     for (int i = 0; i <= hull_size; ++i)
525     {
526         hull_edge.setBy2Points(hull[i], hull[i+1]);
527         Geom::Line parallel_hull_edge;
528         parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
529         parallel_hull_edge.versor(hull_edge.versor());
530         
531         // determine the intersection point
532         
533         Geom::OptCrossing int_pt = Geom::intersection(parallel_hull_edge, prev_parallel_hull_edge);
534         if (int_pt)
535         {
536             Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X],
537                                     (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);
538             poly.ps.push_back(avoid_pt);
539         }
540         else
541         {
542             // something went wrong...
543             std::cout<<"conn-avoid-ref.cpp: avoid_item_poly: Geom:intersection failed."<<std::endl;
544         }
545         prev_parallel_hull_edge = parallel_hull_edge;
546     }
547     return poly;
551 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop,
552         bool initialised)
554     for (SPObject *child = SP_OBJECT(from)->first_child() ;
555             child != NULL; child = SP_OBJECT_NEXT(child) ) {
556         if (SP_IS_ITEM(child) &&
557             !desktop->isLayer(SP_ITEM(child)) &&
558             !SP_ITEM(child)->isLocked() &&
559             !desktop->itemIsHidden(SP_ITEM(child)) &&
560             (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
561             )
562         {
563             list = g_slist_prepend (list, SP_ITEM(child));
564         }
566         if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
567             list = get_avoided_items(list, child, desktop, initialised);
568         }
569     }
571     return list;
575 void avoid_item_move(Geom::Matrix const */*mp*/, SPItem *moved_item)
577     Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
578     g_assert(shapeRef);
580     Router *router = moved_item->document->router;
581     Avoid::Polygon poly = avoid_item_poly(moved_item);
582     if (!poly.empty()) {
583         router->moveShape(shapeRef, poly);
584     }
588 void init_avoided_shape_geometry(SPDesktop *desktop)
590     // Don't count this as changes to the document,
591     // it is basically just late initialisation.
592     SPDocument *document = sp_desktop_document(desktop);
593     bool saved = SPDocumentUndo::get_undo_sensitive(document);
594         SPDocumentUndo::set_undo_sensitive(document, false);
596     bool initialised = false;
597     GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
598             initialised);
600     for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
601         SPItem *item = reinterpret_cast<SPItem *>(iter->data);
602         item->avoidRef->handleSettingChange();
603     }
605     if (items) {
606         g_slist_free(items);
607     }
608         SPDocumentUndo::set_undo_sensitive(document, saved);
612 /*
613   Local Variables:
614   mode:c++
615   c-file-style:"stroustrup"
616   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
617   indent-tabs-mode:nil
618   fill-column:99
619   End:
620 */
621 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :