Code

Pot and Dutch translation update
[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 "libavoid/shape.h"
32 #include "xml/node.h"
33 #include "document.h"
34 #include "desktop.h"
35 #include "desktop-handles.h"
36 #include "sp-namedview.h"
37 #include "sp-item-group.h"
38 #include "inkscape.h"
39 #include <glibmm/i18n.h>
43 using Avoid::Router;
45 static Avoid::Polygon avoid_item_poly(SPItem const *item);
48 SPAvoidRef::SPAvoidRef(SPItem *spitem)
49     : shapeRef(NULL)
50     , item(spitem)
51     , setting(false)
52     , new_setting(false)
53     , _transformed_connection()
54 {
55 }
58 SPAvoidRef::~SPAvoidRef()
59 {
60     _transformed_connection.disconnect();
62     // If the document is being destroyed then the router instance
63     // and the ShapeRefs will have been destroyed with it.
64     const bool routerInstanceExists = (item->document->router != NULL);
66     if (shapeRef && routerInstanceExists) {
67         // Deleting the shapeRef will remove it completely from 
68         // an existing Router instance.
69         delete shapeRef;
70     }
71     shapeRef = NULL;
72 }
75 void SPAvoidRef::setAvoid(char const *value)
76 {
77     if (SP_OBJECT_IS_CLONED(item)) {
78         // Don't keep avoidance information for cloned objects.
79         return;
80     }
81     new_setting = false;
82     if (value && (strcmp(value, "true") == 0)) {
83         new_setting = true;
84     }
85 }
87 void print_connection_points(std::map<int, ConnectionPoint>& cp)
88 {
89     std::map<int, ConnectionPoint>::iterator i;
90     for (i=cp.begin(); i!=cp.end(); ++i)
91     {
92         const ConnectionPoint& p = i->second;
93         std::cout<<p.id<<" "<<p.type<<" "<<p.pos[Geom::X]<<" "<<p.pos[Geom::Y]<<std::endl;
94     }
95 }
97 void SPAvoidRef::setConnectionPoints(gchar const *value)
98 {
99     std::set<int> updates;
100     std::set<int> deletes;
101     std::set<int> seen;
103     if (value)
104     {
105         /* Rebuild the connection points list.
106            Update the connectors for which
107            the endpoint has changed.
108         */
110         gchar ** strarray = g_strsplit(value, "|", 0);
111         gchar ** iter = strarray;
113         while (*iter != NULL) {
114             ConnectionPoint cp;
115             Inkscape::SVGIStringStream is(*iter);
116             is>>cp;
117             cp.type = ConnPointUserDefined;
119             /* Mark this connection point as seen, so we can delete
120                the other ones.
121             */
122             seen.insert(cp.id);
123             if ( connection_points.find(cp.id) != connection_points.end() )
124             {
125                 /* An already existing connection point.
126                    Check to see if changed, and, if it is
127                    the case, trigger connector update for
128                    the connector attached to this connection
129                    point. This is done by adding the
130                    connection point to a list of connection
131                    points to be updated.
132                 */
133                 if ( connection_points[cp.id] != cp )
134                     // The connection point got updated.
135                     // Put it in the update list.
136                     updates.insert(cp.id);
137             }
138             connection_points[cp.id] = cp;
139             ++iter;
140         }
141         /* Delete the connection points that didn't appear
142            in the new connection point list.
143         */
144         std::map<int, ConnectionPoint>::iterator it;
146         for (it=connection_points.begin(); it!=connection_points.end(); ++it)
147             if ( seen.find(it->first) == seen.end())
148                 deletes.insert(it->first);
149         g_strfreev(strarray);
150     }
151     else
152     {
153         /* Delete all the user-defined connection points
154            Actually we do this by adding them to the list
155            of connection points to be deleted.
156         */
157         std::map<int, ConnectionPoint>::iterator it;
159         for (it=connection_points.begin(); it!=connection_points.end(); ++it)
160             deletes.insert(it->first);
161     }
162     /* Act upon updates and deletes.
163     */
164     if (deletes.empty() && updates.empty())
165         // Nothing to do, just return.
166         return;
167     // Get a list of attached connectors.
168     GSList* conns = getAttachedConnectors(Avoid::runningToAndFrom);
169     for (GSList *i = conns; i != NULL; i = i->next)
170     {
171         SPPath* path = SP_PATH(i->data);
172         SPConnEnd** connEnds = path->connEndPair.getConnEnds();
173         for (int ix=0; ix<2; ++ix) {
174             if (connEnds[ix]->type == ConnPointUserDefined) {
175                 if (updates.find(connEnds[ix]->id) != updates.end()) {
176                     if (path->connEndPair.isAutoRoutingConn()) {
177                         path->connEndPair.tellLibavoidNewEndpoints();
178                     } else {
179                     }
180                 }
181                 else if (deletes.find(connEnds[ix]->id) != deletes.end()) {
182                     sp_conn_end_detach(path, ix);
183                 }
184             }
185         }
186     }
187     g_slist_free(conns);
188     // Remove all deleted connection points
189     if (deletes.size())
190         for (std::set<int>::iterator it = deletes.begin(); it != deletes.end(); ++it)
191             connection_points.erase(*it);
194 void SPAvoidRef::setConnectionPointsAttrUndoable(const gchar* value, const gchar* action)
196     SPDocument* doc = SP_OBJECT_DOCUMENT(item);
198     sp_object_setAttribute( SP_OBJECT(item), "inkscape:connection-points", value, 0 );
199     item->updateRepr();
200     sp_document_ensure_up_to_date(doc);
201     sp_document_done(doc, SP_VERB_CONTEXT_CONNECTOR, action);
204 void SPAvoidRef::addConnectionPoint(ConnectionPoint &cp)
206     Inkscape::SVGOStringStream ostr;
207     bool first = true;
208     int newId = 1;
209     if ( connection_points.size() )
210     {
211         for (IdConnectionPointMap::iterator it = connection_points.begin(); ; )
212         {
213             if ( first )
214             {
215                 first = false;
216                 ostr<<it->second;
217             }
218             else
219                 ostr<<'|'<<it->second;
220             IdConnectionPointMap::iterator prev_it = it;
221             ++it;
222             if ( it == connection_points.end() || prev_it->first + 1 != it->first )
223             {
224                 newId = prev_it->first + 1;
225                 break;
226             }
227         }
228     }
229     cp.id = newId;
230     if ( first )
231     {
232         first = false;
233         ostr<<cp;
234     }
235     else
236         ostr<<'|'<<cp;
238     this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Add a new connection point") );
241 void SPAvoidRef::updateConnectionPoint(ConnectionPoint &cp)
243     Inkscape::SVGOStringStream ostr;
244     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
245     if ( cp_pos != connection_points.end() )
246     {
247         bool first = true;
248         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it)
249         {
250             ConnectionPoint* to_write;
251             if ( it != cp_pos )
252                 to_write = &it->second;
253             else
254                 to_write = &cp;
255             if ( first )
256             {
257                 first = false;
258                 ostr<<*to_write;
259             }
260             else
261                 ostr<<'|'<<*to_write;
262         }
263         this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Move a connection point") );
264     }
267 void SPAvoidRef::deleteConnectionPoint(ConnectionPoint &cp)
269     Inkscape::SVGOStringStream ostr;
270     IdConnectionPointMap::iterator cp_pos = connection_points.find( cp.id );
271     if ( cp_pos != connection_points.end() ) {
272         bool first = true;
273         for (IdConnectionPointMap::iterator it = connection_points.begin(); it != connection_points.end(); ++it) {
274             if ( it != cp_pos ) {
275                 if ( first ) {
276                     first = false;
277                     ostr<<it->second;
278                 } else {
279                     ostr<<'|'<<it->second;
280                 }
281             }
282         }
283         this->setConnectionPointsAttrUndoable( ostr.str().c_str(), _("Remove a connection point") );
284     }
287 void SPAvoidRef::handleSettingChange(void)
289     SPDesktop *desktop = inkscape_active_desktop();
290     if (desktop == NULL) {
291         return;
292     }
293     if (sp_desktop_document(desktop) != item->document) {
294         // We don't want to go any further if the active desktop's document
295         // isn't the same as the document that this item is part of.  This
296         // case can happen if a new document is loaded from the file chooser
297         // or via the recent file menu.  In this case, we can end up here
298         // as a rersult of a sp_document_ensure_up_to_date performed on a
299         // document not yet attached to the active desktop.
300         return;
301     }
303     if (new_setting == setting) {
304         // Don't need to make any changes
305         return;
306     }
307     setting = new_setting;
309     Router *router = item->document->router;
311     _transformed_connection.disconnect();
312     if (new_setting) {
313         Avoid::Polygon poly = avoid_item_poly(item);
314         if (poly.size() > 0) {
315             _transformed_connection = item->connectTransformed(
316                     sigc::ptr_fun(&avoid_item_move));
318             const char *id = SP_OBJECT_REPR(item)->attribute("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         // Deleting the shapeRef will remove it completely from 
334         // an existing Router instance.
335         delete shapeRef;
336         shapeRef = NULL;
337     }
341 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
343     GSList *list = NULL;
345     Avoid::IntList shapes;
346     GQuark shapeId = g_quark_from_string(item->getId());
347     item->document->router->attachedShapes(shapes, shapeId, type);
349     Avoid::IntList::iterator finish = shapes.end();
350     for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
351         const gchar *connId = g_quark_to_string(*i);
352         SPObject *obj = item->document->getObjectById(connId);
353         if (obj == NULL) {
354             g_warning("getAttachedShapes: Object with id=\"%s\" is not "
355                     "found. Skipping.", connId);
356             continue;
357         }
358         SPItem *shapeItem = SP_ITEM(obj);
359         list = g_slist_prepend(list, shapeItem);
360     }
361     return list;
365 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
367     GSList *list = NULL;
369     Avoid::IntList conns;
370     GQuark shapeId = g_quark_from_string(item->getId());
371     item->document->router->attachedConns(conns, shapeId, type);
373     Avoid::IntList::iterator finish = conns.end();
374     for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
375         const gchar *connId = g_quark_to_string(*i);
376         SPObject *obj = item->document->getObjectById(connId);
377         if (obj == NULL) {
378             g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
379                     "found. Skipping.", connId);
380             continue;
381         }
382         SPItem *connItem = SP_ITEM(obj);
383         list = g_slist_prepend(list, connItem);
384     }
385     return list;
388 Geom::Point SPAvoidRef::getConnectionPointPos(const int type, const int id)
390     g_assert(item);
391     Geom::Point pos;
392     const Geom::Matrix& transform = sp_item_i2doc_affine(item);
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         {
450             if (cit == pit->begin())
451             {
452                 poly_points.push_back(cit->initialPoint());
453             }
455             if (dynamic_cast<Geom::CubicBezier const*>(&*cit))
456             {
457                 at += seg_size;
458                 if (at <= 1.0 )
459                     poly_points.push_back(cit->pointAt(at));
460                 else
461                 {
462                     at = 0.0;
463                     ++cit;
464                 }
465             }
466             else
467             {
468                 poly_points.push_back(cit->finalPoint());
469                 ++cit;
470             }
471         }
472         ++pit;
473     }
474     return poly_points;
477 static std::vector<Geom::Point> approxItemWithPoints(SPItem const *item, const Geom::Matrix& item_transform)
479     // The structure to hold the output
480     std::vector<Geom::Point> poly_points;
482     if (SP_IS_GROUP(item))
483     {
484         SPGroup* group = SP_GROUP(item);
485         // consider all first-order children
486         for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) {
487             SPItem* child_item = SP_ITEM(i->data);
488             std::vector<Geom::Point> child_points = approxItemWithPoints(child_item, item_transform * child_item->transform);
489             poly_points.insert(poly_points.end(), child_points.begin(), child_points.end());
490         }
491     }
492     else if (SP_IS_SHAPE(item))
493     {
494         SPCurve* item_curve = sp_shape_get_curve(SP_SHAPE(item));
495         // make sure it has an associated curve
496         if (item_curve)
497         {
498             // apply transformations (up to common ancestor)
499             item_curve->transform(item_transform);
500             std::vector<Geom::Point> curve_points = approxCurveWithPoints(item_curve);
501             poly_points.insert(poly_points.end(), curve_points.begin(), curve_points.end());
502             item_curve->unref();
503         }
504     }
506     return poly_points;
508 static Avoid::Polygon avoid_item_poly(SPItem const *item)
510     SPDesktop *desktop = inkscape_active_desktop();
511     g_assert(desktop != NULL);
512     double spacing = desktop->namedview->connector_spacing;
514     Geom::Matrix itd_mat = sp_item_i2doc_affine(item);
515     std::vector<Geom::Point> hull_points;
516     hull_points = approxItemWithPoints(item, itd_mat);
518     // create convex hull from all sampled points
519     Geom::ConvexHull hull(hull_points);
521     // enlarge path by "desktop->namedview->connector_spacing"
522     // store expanded convex hull in Avoid::Polygn
523     Avoid::Polygon poly;
525     Geom::Line hull_edge(hull[-1], hull[0]);
526     Geom::Line prev_parallel_hull_edge;
527     prev_parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
528     prev_parallel_hull_edge.versor(hull_edge.versor());
529     int hull_size = hull.boundary.size();
530     for (int i = 0; i < hull_size; ++i)
531     {
532         hull_edge.setBy2Points(hull[i], hull[i+1]);
533         Geom::Line parallel_hull_edge;
534         parallel_hull_edge.origin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
535         parallel_hull_edge.versor(hull_edge.versor());
536         
537         // determine the intersection point
538         
539         Geom::OptCrossing int_pt = Geom::intersection(parallel_hull_edge, prev_parallel_hull_edge);
540         if (int_pt)
541         {
542             Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X],
543                                     (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);
544             poly.ps.push_back(avoid_pt);
545         }
546         else
547         {
548             // something went wrong...
549             std::cout<<"conn-avoid-ref.cpp: avoid_item_poly: Geom:intersection failed."<<std::endl;
550         }
551         prev_parallel_hull_edge = parallel_hull_edge;
552     }
553     return poly;
557 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop,
558         bool initialised)
560     for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
561             child != NULL; child = SP_OBJECT_NEXT(child) ) {
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 = sp_document_get_undo_sensitive(document);
600     sp_document_set_undo_sensitive(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     sp_document_set_undo_sensitive(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 :