Code

switch from invokeBbox to getBounds (need to fix problems with empty
[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/router.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 using Avoid::Router;
32 static Avoid::Polygn avoid_item_poly(SPItem const *item);
35 SPAvoidRef::SPAvoidRef(SPItem *spitem)
36     : shapeRef(NULL)
37     , item(spitem)
38     , setting(false)
39     , new_setting(false)
40     , _transformed_connection()
41 {
42 }
45 SPAvoidRef::~SPAvoidRef()
46 {
47     _transformed_connection.disconnect();
48     if (shapeRef) {
49         Router *router = shapeRef->router();
50         // shapeRef is finalised by delShape,
51         // so no memory is lost here.
52         router->delShape(shapeRef);
53         shapeRef = NULL;
54     }
55 }
58 void SPAvoidRef::setAvoid(char const *value)
59 {
60     if (SP_OBJECT_IS_CLONED(item)) {
61         // Don't keep avoidance information for cloned objects.
62         return;
63     }
64     new_setting = false;
65     if (value && (strcmp(value, "true") == 0)) {
66         new_setting = true;
67     }
68 }
71 void SPAvoidRef::handleSettingChange(void)
72 {
73     SPDesktop *desktop = inkscape_active_desktop();
74     if (desktop == NULL) {
75         return;
76     }
77     if (sp_desktop_document(desktop) != item->document) {
78         // We don't want to go any further if the active desktop's document
79         // isn't the same as the document that this item is part of.  This
80         // case can happen if a new document is loaded from the file chooser
81         // or via the recent file menu.  In this case, we can end up here
82         // as a rersult of a sp_document_ensure_up_to_date performed on a
83         // document not yet attached to the active desktop.
84         return;
85     }
87     if (new_setting == setting) {
88         // Don't need to make any changes
89         return;
90     }
91     setting = new_setting;
93     Router *router = item->document->router;
94     
95     _transformed_connection.disconnect();
96     if (new_setting) {
97         _transformed_connection = item->connectTransformed(
98                 sigc::ptr_fun(&avoid_item_move));
100         Avoid::Polygn poly = avoid_item_poly(item);
101         if (poly.pn > 0) {
102             const char *id = SP_OBJECT_REPR(item)->attribute("id");
103             g_assert(id != NULL);
104             
105             // Get a unique ID for the item.
106             GQuark itemID = g_quark_from_string(id);
108             shapeRef = new Avoid::ShapeRef(router, itemID, poly);
109             Avoid::freePoly(poly);
110         
111             router->addShape(shapeRef);
112         }
113     }
114     else
115     {
116         g_assert(shapeRef);
117         
118         // shapeRef is finalised by delShape,
119         // so no memory is lost here.
120         router->delShape(shapeRef);
121         shapeRef = NULL;
122     }
126 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
128     GSList *list = NULL;
130     Avoid::IntList shapes;
131     GQuark shapeId = g_quark_from_string(item->id);
132     item->document->router->attachedShapes(shapes, shapeId, type);
133     
134     Avoid::IntList::iterator finish = shapes.end();
135     for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
136         const gchar *connId = g_quark_to_string(*i);
137         SPObject *obj = item->document->getObjectById(connId);
138         if (obj == NULL) {
139             g_warning("getAttachedShapes: Object with id=\"%s\" is not "
140                     "found. Skipping.", connId);
141             continue;
142         }
143         SPItem *shapeItem = SP_ITEM(obj);
144         list = g_slist_prepend(list, shapeItem);
145     }
146     return list;
150 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
152     GSList *list = NULL;
154     Avoid::IntList conns;
155     GQuark shapeId = g_quark_from_string(item->id);
156     item->document->router->attachedConns(conns, shapeId, type);
157     
158     Avoid::IntList::iterator finish = conns.end();
159     for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
160         const gchar *connId = g_quark_to_string(*i);
161         SPObject *obj = item->document->getObjectById(connId);
162         if (obj == NULL) {
163             g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
164                     "found. Skipping.", connId);
165             continue;
166         }
167         SPItem *connItem = SP_ITEM(obj);
168         list = g_slist_prepend(list, connItem);
169     }
170     return list;
174 static Avoid::Polygn avoid_item_poly(SPItem const *item)
176     SPDesktop *desktop = inkscape_active_desktop();
177     g_assert(desktop != NULL);
179     Avoid::Polygn poly;
181     // TODO: The right way to do this is to return the convex hull of
182     //       the object, or an approximation in the case of a rounded
183     //       object.  Specific SPItems will need to have a new
184     //       function that returns points for the convex hull.
185     //       For some objects it is enough to feed the snappoints to
186     //       some convex hull code, though not NR::ConvexHull as this
187     //       only keeps the bounding box of the convex hull currently.
189     // TODO: SPItem::getBounds gives the wrong result for some objects
190     //       that have internal representations that are updated later
191     //       by the sp_*_update functions, e.g., text.
192     sp_document_ensure_up_to_date(item->document);
193     
194     NR::Rect rHull = item->getBounds(sp_item_i2doc_affine(item));
197     double spacing = desktop->namedview->connector_spacing;
199     // Add a little buffer around the edge of each object.
200     NR::Rect rExpandedHull = NR::expand(rHull, -spacing); 
201     poly = Avoid::newPoly(4);
203     for (unsigned n = 0; n < 4; ++n) {
204         // TODO: I think the winding order in libavoid or inkscape might
205         //       be backwards, probably due to the inverse y co-ordinates
206         //       used for the screen.  The '3 - n' reverses the order.
207         /* On "correct" winding order: Winding order of NR::Rect::corner is in a positive
208          * direction, like libart.  "Positive direction" means the same as in most of Inkscape and
209          * SVG: if you visualize y as increasing upwards, as is the convention in mathematics, then
210          * positive angle is visualized as anticlockwise, as in mathematics; so if you visualize y
211          * as increasing downwards, as is common outside of mathematics, then positive angle
212          * direction is visualized as clockwise, as is common outside of mathematics.  This
213          * convention makes it easier mix pure mathematics code with graphics code: the important
214          * thing when mixing code is that the number values stored in variables (representing y
215          * coordinate, angle) match up; variables store numbers, not visualized positions, and the
216          * programmer is free to switch between visualizations when thinking about a given piece of
217          * code.
218          *
219          * MathWorld, libart and NR::Rect::corner all seem to take positive winding (i.e. winding
220          * that yields +1 winding number inside a simple closed shape) to mean winding in a
221          * positive angle.  This, together with the observation that variables store numbers rather
222          * than positions, suggests that NR::Rect::corner uses the right direction.
223          */
224         NR::Point hullPoint = rExpandedHull.corner(3 - n);
225         poly.ps[n].x = hullPoint[NR::X];
226         poly.ps[n].y = hullPoint[NR::Y];
227     }
229     return poly;
233 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop, 
234         bool initialised)
236     for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
237             child != NULL; child = SP_OBJECT_NEXT(child) ) {
238         if (SP_IS_ITEM(child) &&
239             !desktop->isLayer(SP_ITEM(child)) &&
240             !SP_ITEM(child)->isLocked() && 
241             !desktop->itemIsHidden(SP_ITEM(child)) &&
242             (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
243             )
244         {
245             list = g_slist_prepend (list, SP_ITEM(child));
246         }
248         if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
249             list = get_avoided_items(list, child, desktop, initialised);
250         }
251     }
253     return list;
257 void avoid_item_move(NR::Matrix const *mp, SPItem *moved_item)
259     Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
260     g_assert(shapeRef);
262     Router *router = moved_item->document->router;
263     Avoid::Polygn poly = avoid_item_poly(moved_item);
264     if (poly.pn > 0) {
265         router->moveShape(shapeRef, &poly);
266         Avoid::freePoly(poly);
267     }
271 void init_avoided_shape_geometry(SPDesktop *desktop)
273     // Don't count this as changes to the document,
274     // it is basically just llate initialisation.
275     SPDocument *document = sp_desktop_document(desktop);
276     bool saved = sp_document_get_undo_sensitive(document);
277     sp_document_set_undo_sensitive(document, false);
278     
279     bool initialised = false;
280     GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
281             initialised);
283     for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
284         SPItem *item = reinterpret_cast<SPItem *>(iter->data);
285         item->avoidRef->handleSettingChange();
286     }
288     if (items) {
289         g_slist_free(items);
290     }
291     sp_document_set_undo_sensitive(document, saved);
295 /*
296   Local Variables:
297   mode:c++
298   c-file-style:"stroustrup"
299   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
300   indent-tabs-mode:nil
301   fill-column:99
302   End:
303 */
304 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :