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 }
78 Router *router = item->document->router;
80 if (new_setting == setting) {
81 // Don't need to make any changes
82 return;
83 }
85 _transformed_connection.disconnect();
86 if (new_setting) {
87 _transformed_connection = item->connectTransformed(
88 sigc::ptr_fun(&avoid_item_move));
90 Avoid::Polygn poly = avoid_item_poly(item);
91 if (poly.pn > 0) {
92 const char *id = SP_OBJECT_REPR(item)->attribute("id");
93 g_assert(id != NULL);
95 // Get a unique ID for the item.
96 GQuark itemID = g_quark_from_string(id);
98 shapeRef = new Avoid::ShapeRef(router, itemID, poly);
99 Avoid::freePoly(poly);
101 router->addShape(shapeRef);
102 }
103 }
104 else
105 {
106 g_assert(shapeRef);
108 // shapeRef is finalised by delShape,
109 // so no memory is lost here.
110 router->delShape(shapeRef);
111 shapeRef = NULL;
112 }
113 setting = new_setting;
114 }
117 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
118 {
119 GSList *list = NULL;
121 Avoid::IntList shapes;
122 GQuark shapeId = g_quark_from_string(item->id);
123 item->document->router->attachedShapes(shapes, shapeId, type);
125 Avoid::IntList::iterator finish = shapes.end();
126 for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
127 const gchar *connId = g_quark_to_string(*i);
128 SPObject *obj = item->document->getObjectById(connId);
129 if (obj == NULL) {
130 g_warning("getAttachedShapes: Object with id=\"%s\" is not "
131 "found. Skipping.", connId);
132 continue;
133 }
134 SPItem *shapeItem = SP_ITEM(obj);
135 list = g_slist_prepend(list, shapeItem);
136 }
137 return list;
138 }
141 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
142 {
143 GSList *list = NULL;
145 Avoid::IntList conns;
146 GQuark shapeId = g_quark_from_string(item->id);
147 item->document->router->attachedConns(conns, shapeId, type);
149 Avoid::IntList::iterator finish = conns.end();
150 for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
151 const gchar *connId = g_quark_to_string(*i);
152 SPObject *obj = item->document->getObjectById(connId);
153 if (obj == NULL) {
154 g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
155 "found. Skipping.", connId);
156 continue;
157 }
158 SPItem *connItem = SP_ITEM(obj);
159 list = g_slist_prepend(list, connItem);
160 }
161 return list;
162 }
165 static Avoid::Polygn avoid_item_poly(SPItem const *item)
166 {
167 SPDesktop *desktop = inkscape_active_desktop();
168 g_assert(desktop != NULL);
170 Avoid::Polygn poly;
172 // TODO: The right way to do this is to return the convex hull of
173 // the object, or an approximation in the case of a rounded
174 // object. Specific SPItems will need to have a new
175 // function that returns points for the convex hull.
176 // For some objects it is enough to feed the snappoints to
177 // some convex hull code, though not NR::ConvexHull as this
178 // only keeps the bounding box of the convex hull currently.
180 // TODO: SPItem::invokeBbox gives the wrong result for some objects
181 // that have internal representations that are updated later
182 // by the sp_*_update functions, e.g., text.
183 sp_document_ensure_up_to_date(item->document);
185 NR::Rect rHull = item->invokeBbox(sp_item_i2doc_affine(item));
188 double spacing = desktop->namedview->connector_spacing;
190 // Add a little buffer around the edge of each object.
191 NR::Rect rExpandedHull = NR::expand(rHull, -spacing);
192 poly = Avoid::newPoly(4);
194 for (unsigned n = 0; n < 4; ++n) {
195 // TODO: I think the winding order in libavoid or inkscape might
196 // be backwards, probably due to the inverse y co-ordinates
197 // used for the screen. The '3 - n' reverses the order.
198 /* On "correct" winding order: Winding order of NR::Rect::corner is in a positive
199 * direction, like libart. "Positive direction" means the same as in most of Inkscape and
200 * SVG: if you visualize y as increasing upwards, as is the convention in mathematics, then
201 * positive angle is visualized as anticlockwise, as in mathematics; so if you visualize y
202 * as increasing downwards, as is common outside of mathematics, then positive angle
203 * direction is visualized as clockwise, as is common outside of mathematics. This
204 * convention makes it easier mix pure mathematics code with graphics code: the important
205 * thing when mixing code is that the number values stored in variables (representing y
206 * coordinate, angle) match up; variables store numbers, not visualized positions, and the
207 * programmer is free to switch between visualizations when thinking about a given piece of
208 * code.
209 *
210 * MathWorld, libart and NR::Rect::corner all seem to take positive winding (i.e. winding
211 * that yields +1 winding number inside a simple closed shape) to mean winding in a
212 * positive angle. This, together with the observation that variables store numbers rather
213 * than positions, suggests that NR::Rect::corner uses the right direction.
214 */
215 NR::Point hullPoint = rExpandedHull.corner(3 - n);
216 poly.ps[n].x = hullPoint[NR::X];
217 poly.ps[n].y = hullPoint[NR::Y];
218 }
220 return poly;
221 }
224 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop,
225 bool initialised)
226 {
227 for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
228 child != NULL; child = SP_OBJECT_NEXT(child) ) {
229 if (SP_IS_ITEM(child) &&
230 !desktop->isLayer(SP_ITEM(child)) &&
231 !SP_ITEM(child)->isLocked() &&
232 !desktop->itemIsHidden(SP_ITEM(child)) &&
233 (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
234 )
235 {
236 list = g_slist_prepend (list, SP_ITEM(child));
237 }
239 if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
240 list = get_avoided_items(list, child, desktop, initialised);
241 }
242 }
244 return list;
245 }
248 void avoid_item_move(NR::Matrix const *mp, SPItem *moved_item)
249 {
250 Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
251 g_assert(shapeRef);
253 Router *router = moved_item->document->router;
254 Avoid::Polygn poly = avoid_item_poly(moved_item);
255 if (poly.pn > 0) {
256 router->moveShape(shapeRef, &poly);
257 Avoid::freePoly(poly);
258 }
259 }
262 void init_avoided_shape_geometry(SPDesktop *desktop)
263 {
264 // Don't count this as changes to the document,
265 // it is basically just llate initialisation.
266 SPDocument *document = SP_DT_DOCUMENT(desktop);
267 gboolean saved = sp_document_get_undo_sensitive(document);
268 sp_document_set_undo_sensitive(document, FALSE);
270 bool initialised = false;
271 GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
272 initialised);
274 for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
275 SPItem *item = reinterpret_cast<SPItem *>(iter->data);
276 item->avoidRef->handleSettingChange();
277 }
279 if (items) {
280 g_slist_free(items);
281 }
282 sp_document_set_undo_sensitive(document, saved);
283 }
286 /*
287 Local Variables:
288 mode:c++
289 c-file-style:"stroustrup"
290 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
291 indent-tabs-mode:nil
292 fill-column:99
293 End:
294 */
295 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :