Code

Spray Tool clean up and Toolbar refactoring
[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_setAttribute( SP_OBJECT(item), "inkscape:connection-points", value, 0 );
198     item->updateRepr();
199     sp_document_ensure_up_to_date(doc);
200     sp_document_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             g_assert(id != NULL);
320             // Get a unique ID for the item.
321             GQuark itemID = g_quark_from_string(id);
323             shapeRef = new Avoid::ShapeRef(router, poly, itemID);
325             router->addShape(shapeRef);
326         }
327     }
328     else
329     {
330         g_assert(shapeRef);
332         router->removeShape(shapeRef);
333         delete shapeRef;
334         shapeRef = NULL;
335     }
339 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
341     GSList *list = NULL;
343     Avoid::IntList shapes;
344     GQuark shapeId = g_quark_from_string(item->id);
345     item->document->router->attachedShapes(shapes, shapeId, type);
347     Avoid::IntList::iterator finish = shapes.end();
348     for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
349         const gchar *connId = g_quark_to_string(*i);
350         SPObject *obj = item->document->getObjectById(connId);
351         if (obj == NULL) {
352             g_warning("getAttachedShapes: Object with id=\"%s\" is not "
353                     "found. Skipping.", connId);
354             continue;
355         }
356         SPItem *shapeItem = SP_ITEM(obj);
357         list = g_slist_prepend(list, shapeItem);
358     }
359     return list;
363 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
365     GSList *list = NULL;
367     Avoid::IntList conns;
368     GQuark shapeId = g_quark_from_string(item->id);
369     item->document->router->attachedConns(conns, shapeId, type);
371     Avoid::IntList::iterator finish = conns.end();
372     for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
373         const gchar *connId = g_quark_to_string(*i);
374         SPObject *obj = item->document->getObjectById(connId);
375         if (obj == NULL) {
376             g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
377                     "found. Skipping.", connId);
378             continue;
379         }
380         SPItem *connItem = SP_ITEM(obj);
381         list = g_slist_prepend(list, connItem);
382     }
383     return list;
386 Geom::Point SPAvoidRef::getConnectionPointPos(const int type, const int id)
388     g_assert(item);
389     Geom::Point pos;
390     const Geom::Matrix& transform = sp_item_i2doc_affine(item);
391     // TODO investigate why this was asking for the active desktop:
392     SPDesktop *desktop = inkscape_active_desktop();
394     if ( type == ConnPointDefault )
395     {
396         // For now, just default to the centre of the item
397         Geom::OptRect bbox = item->getBounds(sp_item_i2doc_affine(item));
398         pos = (bbox) ? bbox->midpoint() : Geom::Point(0, 0);
399     }
400     else
401     {
402         // Get coordinates from the list of connection points
403         // that are attached to the item
404         pos = connection_points[id].pos * transform;
405     }
407     return pos;
410 bool SPAvoidRef::isValidConnPointId( const int type, const int id )
412     if ( type < 0 || type > 1 )
413         return false;
414     else
415     {
416         if ( type == ConnPointDefault )
417             if ( id < 0 || id > 8 )
418                 return false;
419             else
420             {
421             }
422         else
423             return connection_points.find( id ) != connection_points.end();
424     }
426     return true;
429 static std::vector<Geom::Point> approxCurveWithPoints(SPCurve *curve)
431     // The number of segments to use for not straight curves approximation
432     const unsigned NUM_SEGS = 4;
433     
434     const Geom::PathVector& curve_pv = curve->get_pathvector();
435    
436     // The structure to hold the output
437     std::vector<Geom::Point> poly_points;
439     // Iterate over all curves, adding the endpoints for linear curves and
440     // sampling the other curves
441     double seg_size = 1.0 / NUM_SEGS;
442     double at;
443     at = 0;
444     Geom::PathVector::const_iterator pit = curve_pv.begin();
445     while (pit != curve_pv.end())
446     {
447         Geom::Path::const_iterator cit = pit->begin();
448         while (cit != pit->end())
449             if (dynamic_cast<Geom::CubicBezier const*>(&*cit))
450             {
451                 at += seg_size;
452                 if (at <= 1.0 )
453                     poly_points.push_back(cit->pointAt(at));
454                 else
455                 {
456                     at = 0.0;
457                     ++cit;
458                 }
459             }
460             else
461             {
462                 poly_points.push_back(cit->finalPoint());
463                 ++cit;
464             }
465         ++pit;
466     }
467     return poly_points;
470 static std::vector<Geom::Point> approxItemWithPoints(SPItem const *item, const Geom::Matrix& item_transform)
472     // The structure to hold the output
473     std::vector<Geom::Point> poly_points;
475     if (SP_IS_GROUP(item))
476     {
477         SPGroup* group = SP_GROUP(item);
478         // consider all first-order children
479         for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) {
480             SPItem* child_item = SP_ITEM(i->data);
481             std::vector<Geom::Point> child_points = approxItemWithPoints(child_item, item_transform * child_item->transform);
482             poly_points.insert(poly_points.end(), child_points.begin(), child_points.end());
483         }
484     }
485     else if (SP_IS_SHAPE(item))
486     {
487         SPCurve* item_curve = sp_shape_get_curve(SP_SHAPE(item));
488         // make sure it has an associated curve
489         if (item_curve)
490         {
491             // apply transformations (up to common ancestor)
492             item_curve->transform(item_transform);
493             std::vector<Geom::Point> curve_points = approxCurveWithPoints(item_curve);
494             poly_points.insert(poly_points.end(), curve_points.begin(), curve_points.end());
495             item_curve->unref();
496         }
497     }
499     return poly_points;
501 static Avoid::Polygon avoid_item_poly(SPItem const *item)
503     SPDesktop *desktop = inkscape_active_desktop();
504     g_assert(desktop != NULL);
505     double spacing = desktop->namedview->connector_spacing;
507     Geom::Matrix itd_mat = sp_item_i2doc_affine(item);
508     std::vector<Geom::Point> hull_points;
509     hull_points = approxItemWithPoints(item, itd_mat);
511     // create convex hull from all sampled points
512     Geom::ConvexHull hull(hull_points);
514     // enlarge path by "desktop->namedview->connector_spacing"
515     // store expanded convex hull in Avoid::Polygn
516     Avoid::Polygon poly;
518     Geom::Line hull_edge(hull[-1], hull[0]);
519     Geom::Line prev_parallel_hull_edge;
520     prev_parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
521     prev_parallel_hull_edge.versor(hull_edge.versor());
522     int hull_size = hull.boundary.size();
523     for (int i = 0; i <= hull_size; ++i)
524     {
525         hull_edge.setBy2Points(hull[i], hull[i+1]);
526         Geom::Line parallel_hull_edge;
527         parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
528         parallel_hull_edge.versor(hull_edge.versor());
529         
530         // determine the intersection point
531         
532         Geom::OptCrossing int_pt = Geom::intersection(parallel_hull_edge, prev_parallel_hull_edge);
533         if (int_pt)
534         {
535             Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X],
536                                     (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);
537             poly.ps.push_back(avoid_pt);
538         }
539         else
540         {
541             // something went wrong...
542             std::cout<<"conn-avoid-ref.cpp: avoid_item_poly: Geom:intersection failed."<<std::endl;
543         }
544         prev_parallel_hull_edge = parallel_hull_edge;
545     }
546     return poly;
550 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop,
551         bool initialised)
553     for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
554             child != NULL; child = SP_OBJECT_NEXT(child) ) {
555         if (SP_IS_ITEM(child) &&
556             !desktop->isLayer(SP_ITEM(child)) &&
557             !SP_ITEM(child)->isLocked() &&
558             !desktop->itemIsHidden(SP_ITEM(child)) &&
559             (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
560             )
561         {
562             list = g_slist_prepend (list, SP_ITEM(child));
563         }
565         if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
566             list = get_avoided_items(list, child, desktop, initialised);
567         }
568     }
570     return list;
574 void avoid_item_move(Geom::Matrix const */*mp*/, SPItem *moved_item)
576     Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
577     g_assert(shapeRef);
579     Router *router = moved_item->document->router;
580     Avoid::Polygon poly = avoid_item_poly(moved_item);
581     if (!poly.empty()) {
582         router->moveShape(shapeRef, poly);
583     }
587 void init_avoided_shape_geometry(SPDesktop *desktop)
589     // Don't count this as changes to the document,
590     // it is basically just late initialisation.
591     SPDocument *document = sp_desktop_document(desktop);
592     bool saved = sp_document_get_undo_sensitive(document);
593     sp_document_set_undo_sensitive(document, false);
595     bool initialised = false;
596     GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
597             initialised);
599     for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
600         SPItem *item = reinterpret_cast<SPItem *>(iter->data);
601         item->avoidRef->handleSettingChange();
602     }
604     if (items) {
605         g_slist_free(items);
606     }
607     sp_document_set_undo_sensitive(document, saved);
611 /*
612   Local Variables:
613   mode:c++
614   c-file-style:"stroustrup"
615   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
616   indent-tabs-mode:nil
617   fill-column:99
618   End:
619 */
620 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :