Code

dumps the graph to cout in dot format
[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  */
14 #include "sp-item.h"
15 #include "conn-avoid-ref.h"
16 #include "libnr/nr-rect-ops.h"
17 #include "libavoid/polyutil.h"
18 #include "libavoid/incremental.h"
19 #include "libavoid/connector.h"
20 #include "xml/simple-node.cpp"
21 #include "document.h"
22 #include "prefs-utils.h"
24 #include "desktop.h"
25 #include "desktop-handles.h"
26 #include "sp-namedview.h"
27 #include "inkscape.h"
30 static Avoid::Polygn avoid_item_poly(SPItem const *item);
33 SPAvoidRef::SPAvoidRef(SPItem *spitem)
34     : shapeRef(NULL)
35     , item(spitem)
36     , setting(false)
37     , new_setting(false)
38     , _transformed_connection()
39 {
40 }
43 SPAvoidRef::~SPAvoidRef()
44 {
45     _transformed_connection.disconnect();
46     if (shapeRef) {
47         // shapeRef is finalised by delShape,
48         // so no memory is lost here.
49         Avoid::delShape(shapeRef);
50         shapeRef = NULL;
51     }
52 }
55 void SPAvoidRef::setAvoid(char const *value)
56 {
57     if (SP_OBJECT_IS_CLONED(item)) {
58         // Don't keep avoidance information for cloned objects.
59         return;
60     }
61     new_setting = false;
62     if (value && (strcmp(value, "true") == 0)) {
63         new_setting = true;
64     }
65 }
68 void SPAvoidRef::handleSettingChange(void)
69 {
70     SPDesktop *desktop = inkscape_active_desktop();
71     if (desktop == NULL) {
72         return;
73     }
74     
75     if (new_setting == setting) {
76         // Don't need to make any changes
77         return;
78     }
80     _transformed_connection.disconnect();
81     if (new_setting) {
82         _transformed_connection = item->connectTransformed(
83                 sigc::ptr_fun(&avoid_item_move));
85         Avoid::Polygn poly = avoid_item_poly(item);
86         if (poly.pn > 0) {
87             const char *id = SP_OBJECT_REPR(item)->attribute("id");
88             g_assert(id != NULL);
89             
90             // Get a unique ID for the item.
91             GQuark itemID = g_quark_from_string(id);
93             shapeRef = new Avoid::ShapeRef(itemID, poly);
94             Avoid::freePoly(poly);
95         
96             Avoid::addShape(shapeRef);
97         }
98     }
99     else
100     {
101         g_assert(shapeRef);
102         
103         // shapeRef is finalised by delShape,
104         // so no memory is lost here.
105         Avoid::delShape(shapeRef);
106         shapeRef = NULL;
107     }
108     setting = new_setting;
112 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
114     GSList *list = NULL;
116     Avoid::IntList shapes;
117     GQuark shapeId = g_quark_from_string(item->id);
118     Avoid::attachedShapes(shapes, shapeId, type);
119     
120     Avoid::IntList::iterator finish = shapes.end();
121     for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
122         const gchar *connId = g_quark_to_string(*i);
123         SPObject *obj = item->document->getObjectById(connId);
124         if (obj == NULL) {
125             g_warning("getAttachedShapes: Object with id=\"%s\" is not "
126                     "found. Skipping.", connId);
127             continue;
128         }
129         SPItem *shapeItem = SP_ITEM(obj);
130         list = g_slist_prepend(list, shapeItem);
131     }
132     return list;
136 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
138     GSList *list = NULL;
140     Avoid::IntList conns;
141     GQuark shapeId = g_quark_from_string(item->id);
142     Avoid::attachedConns(conns, shapeId, type);
143     
144     Avoid::IntList::iterator finish = conns.end();
145     for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
146         const gchar *connId = g_quark_to_string(*i);
147         SPObject *obj = item->document->getObjectById(connId);
148         if (obj == NULL) {
149             g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
150                     "found. Skipping.", connId);
151             continue;
152         }
153         SPItem *connItem = SP_ITEM(obj);
154         list = g_slist_prepend(list, connItem);
155     }
156     return list;
160 static Avoid::Polygn avoid_item_poly(SPItem const *item)
162     SPDesktop *desktop = inkscape_active_desktop();
163     g_assert(desktop != NULL);
165     Avoid::Polygn poly;
167     // TODO: The right way to do this is to return the convex hull of
168     //       the object, or an approximation in the case of a rounded
169     //       object.  Specific SPItems will need to have a new
170     //       function that returns points for the convex hull.
171     //       For some objects it is enough to feed the snappoints to
172     //       some convex hull code, though not NR::ConvexHull as this
173     //       only keeps the bounding box of the convex hull currently.
175     // TODO: SPItem::invokeBbox gives the wrong result for some objects
176     //       that have internal representations that are updated later
177     //       by the sp_*_update functions, e.g., text.
178     sp_document_ensure_up_to_date(item->document);
179     
180     NR::Rect rHull = item->invokeBbox(sp_item_i2doc_affine(item));
183     double spacing = desktop->namedview->connector_spacing;
185     // Add a little buffer around the edge of each object.
186     NR::Rect rExpandedHull = NR::expand(rHull, -spacing); 
187     poly = Avoid::newPoly(4);
189     for (unsigned n = 0; n < 4; ++n) {
190         // TODO: I think the winding order in libavoid or inkscape might
191         //       be backwards, probably due to the inverse y co-ordinates
192         //       used for the screen.  The '3 - n' reverses the order.
193         /* On "correct" winding order: Winding order of NR::Rect::corner is in a positive
194          * direction, like libart.  "Positive direction" means the same as in most of Inkscape and
195          * SVG: if you visualize y as increasing upwards, as is the convention in mathematics, then
196          * positive angle is visualized as anticlockwise, as in mathematics; so if you visualize y
197          * as increasing downwards, as is common outside of mathematics, then positive angle
198          * direction is visualized as clockwise, as is common outside of mathematics.  This
199          * convention makes it easier mix pure mathematics code with graphics code: the important
200          * thing when mixing code is that the number values stored in variables (representing y
201          * coordinate, angle) match up; variables store numbers, not visualized positions, and the
202          * programmer is free to switch between visualizations when thinking about a given piece of
203          * code.
204          *
205          * MathWorld, libart and NR::Rect::corner all seem to take positive winding (i.e. winding
206          * that yields +1 winding number inside a simple closed shape) to mean winding in a
207          * positive angle.  This, together with the observation that variables store numbers rather
208          * than positions, suggests that NR::Rect::corner uses the right direction.
209          */
210         NR::Point hullPoint = rExpandedHull.corner(3 - n);
211         poly.ps[n].x = hullPoint[NR::X];
212         poly.ps[n].y = hullPoint[NR::Y];
213     }
215     return poly;
219 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop, 
220         bool initialised)
222     for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
223             child != NULL; child = SP_OBJECT_NEXT(child) ) {
224         if (SP_IS_ITEM(child) &&
225             !desktop->isLayer(SP_ITEM(child)) &&
226             !SP_ITEM(child)->isLocked() && 
227             !desktop->itemIsHidden(SP_ITEM(child)) &&
228             (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
229             )
230         {
231             list = g_slist_prepend (list, SP_ITEM(child));
232         }
234         if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
235             list = get_avoided_items(list, child, desktop, initialised);
236         }
237     }
239     return list;
243 void avoid_item_move(NR::Matrix const *mp, SPItem *moved_item)
245     Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
246     g_assert(shapeRef);
248     Avoid::Polygn poly = avoid_item_poly(moved_item);
249     if (poly.pn > 0) {
250         // moveShape actually destroys the old shapeRef and returns a new one.
251         moved_item->avoidRef->shapeRef = Avoid::moveShape(shapeRef, &poly);
252         Avoid::freePoly(poly);
253     }
257 void init_avoided_shape_geometry(SPDesktop *desktop)
259     // Don't count this as changes to the document,
260     // it is basically just llate initialisation.
261     SPDocument *document = SP_DT_DOCUMENT(desktop);
262     gboolean saved = sp_document_get_undo_sensitive(document);
263     sp_document_set_undo_sensitive(document, FALSE);
264     
265     bool initialised = false;
266     GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
267             initialised);
269     for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
270         SPItem *item = reinterpret_cast<SPItem *>(iter->data);
271         item->avoidRef->handleSettingChange();
272     }
274     if (items) {
275         g_slist_free(items);
276     }
277     sp_document_set_undo_sensitive(document, saved);
281 /*
282   Local Variables:
283   mode:c++
284   c-file-style:"stroustrup"
285   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
286   indent-tabs-mode:nil
287   fill-column:99
288   End:
289 */
290 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :