5a09f87650b97fba1fe1ebe24675772e12bbcb48
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>
16 #include "sp-item.h"
17 #include "conn-avoid-ref.h"
18 #include "libnr/nr-rect-ops.h"
19 #include "libavoid/polyutil.h"
20 #include "libavoid/router.h"
21 #include "libavoid/connector.h"
22 #include "xml/simple-node.h"
23 #include "document.h"
24 #include "prefs-utils.h"
26 #include "desktop.h"
27 #include "desktop-handles.h"
28 #include "sp-namedview.h"
29 #include "inkscape.h"
32 using Avoid::Router;
34 static Avoid::Polygn avoid_item_poly(SPItem const *item);
37 SPAvoidRef::SPAvoidRef(SPItem *spitem)
38 : shapeRef(NULL)
39 , item(spitem)
40 , setting(false)
41 , new_setting(false)
42 , _transformed_connection()
43 {
44 }
47 SPAvoidRef::~SPAvoidRef()
48 {
49 _transformed_connection.disconnect();
50 if (shapeRef) {
51 Router *router = shapeRef->router();
52 // shapeRef is finalised by delShape,
53 // so no memory is lost here.
54 router->delShape(shapeRef);
55 shapeRef = NULL;
56 }
57 }
60 void SPAvoidRef::setAvoid(char const *value)
61 {
62 if (SP_OBJECT_IS_CLONED(item)) {
63 // Don't keep avoidance information for cloned objects.
64 return;
65 }
66 new_setting = false;
67 if (value && (strcmp(value, "true") == 0)) {
68 new_setting = true;
69 }
70 }
73 void SPAvoidRef::handleSettingChange(void)
74 {
75 SPDesktop *desktop = inkscape_active_desktop();
76 if (desktop == NULL) {
77 return;
78 }
79 if (sp_desktop_document(desktop) != item->document) {
80 // We don't want to go any further if the active desktop's document
81 // isn't the same as the document that this item is part of. This
82 // case can happen if a new document is loaded from the file chooser
83 // or via the recent file menu. In this case, we can end up here
84 // as a rersult of a sp_document_ensure_up_to_date performed on a
85 // document not yet attached to the active desktop.
86 return;
87 }
89 if (new_setting == setting) {
90 // Don't need to make any changes
91 return;
92 }
93 setting = new_setting;
95 Router *router = item->document->router;
97 _transformed_connection.disconnect();
98 if (new_setting) {
99 Avoid::Polygn poly = avoid_item_poly(item);
100 if (poly.pn > 0) {
101 _transformed_connection = item->connectTransformed(
102 sigc::ptr_fun(&avoid_item_move));
104 const char *id = SP_OBJECT_REPR(item)->attribute("id");
105 g_assert(id != NULL);
107 // Get a unique ID for the item.
108 GQuark itemID = g_quark_from_string(id);
110 shapeRef = new Avoid::ShapeRef(router, itemID, poly);
111 Avoid::freePoly(poly);
113 router->addShape(shapeRef);
114 }
115 }
116 else
117 {
118 g_assert(shapeRef);
120 // shapeRef is finalised by delShape,
121 // so no memory is lost here.
122 router->delShape(shapeRef);
123 shapeRef = NULL;
124 }
125 }
128 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
129 {
130 GSList *list = NULL;
132 Avoid::IntList shapes;
133 GQuark shapeId = g_quark_from_string(item->id);
134 item->document->router->attachedShapes(shapes, shapeId, type);
136 Avoid::IntList::iterator finish = shapes.end();
137 for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
138 const gchar *connId = g_quark_to_string(*i);
139 SPObject *obj = item->document->getObjectById(connId);
140 if (obj == NULL) {
141 g_warning("getAttachedShapes: Object with id=\"%s\" is not "
142 "found. Skipping.", connId);
143 continue;
144 }
145 SPItem *shapeItem = SP_ITEM(obj);
146 list = g_slist_prepend(list, shapeItem);
147 }
148 return list;
149 }
152 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
153 {
154 GSList *list = NULL;
156 Avoid::IntList conns;
157 GQuark shapeId = g_quark_from_string(item->id);
158 item->document->router->attachedConns(conns, shapeId, type);
160 Avoid::IntList::iterator finish = conns.end();
161 for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
162 const gchar *connId = g_quark_to_string(*i);
163 SPObject *obj = item->document->getObjectById(connId);
164 if (obj == NULL) {
165 g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
166 "found. Skipping.", connId);
167 continue;
168 }
169 SPItem *connItem = SP_ITEM(obj);
170 list = g_slist_prepend(list, connItem);
171 }
172 return list;
173 }
176 static Avoid::Polygn avoid_item_poly(SPItem const *item)
177 {
178 SPDesktop *desktop = inkscape_active_desktop();
179 g_assert(desktop != NULL);
181 Avoid::Polygn poly;
183 // TODO: The right way to do this is to return the convex hull of
184 // the object, or an approximation in the case of a rounded
185 // object. Specific SPItems will need to have a new
186 // function that returns points for the convex hull.
187 // For some objects it is enough to feed the snappoints to
188 // some convex hull code, though not NR::ConvexHull as this
189 // only keeps the bounding box of the convex hull currently.
191 // TODO: SPItem::getBounds gives the wrong result for some objects
192 // that have internal representations that are updated later
193 // by the sp_*_update functions, e.g., text.
194 sp_document_ensure_up_to_date(item->document);
196 boost::optional<Geom::Rect> rHull = item->getBounds(sp_item_i2doc_affine(item));
197 if (!rHull) {
198 return Avoid::newPoly(0);
199 }
201 double spacing = desktop->namedview->connector_spacing;
203 // Add a little buffer around the edge of each object.
204 Geom::Rect rExpandedHull = *rHull;
205 rExpandedHull.expandBy(-spacing);
206 poly = Avoid::newPoly(4);
208 for (unsigned n = 0; n < 4; ++n) {
209 NR::Point hullPoint = rExpandedHull.corner(n);
210 poly.ps[n].x = hullPoint[NR::X];
211 poly.ps[n].y = hullPoint[NR::Y];
212 }
214 return poly;
215 }
218 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop,
219 bool initialised)
220 {
221 for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
222 child != NULL; child = SP_OBJECT_NEXT(child) ) {
223 if (SP_IS_ITEM(child) &&
224 !desktop->isLayer(SP_ITEM(child)) &&
225 !SP_ITEM(child)->isLocked() &&
226 !desktop->itemIsHidden(SP_ITEM(child)) &&
227 (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
228 )
229 {
230 list = g_slist_prepend (list, SP_ITEM(child));
231 }
233 if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
234 list = get_avoided_items(list, child, desktop, initialised);
235 }
236 }
238 return list;
239 }
242 void avoid_item_move(Geom::Matrix const */*mp*/, SPItem *moved_item)
243 {
244 Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
245 g_assert(shapeRef);
247 Router *router = moved_item->document->router;
248 Avoid::Polygn poly = avoid_item_poly(moved_item);
249 if (poly.pn > 0) {
250 router->moveShape(shapeRef, &poly);
251 Avoid::freePoly(poly);
252 }
253 }
256 void init_avoided_shape_geometry(SPDesktop *desktop)
257 {
258 // Don't count this as changes to the document,
259 // it is basically just llate initialisation.
260 SPDocument *document = sp_desktop_document(desktop);
261 bool saved = sp_document_get_undo_sensitive(document);
262 sp_document_set_undo_sensitive(document, false);
264 bool initialised = false;
265 GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
266 initialised);
268 for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
269 SPItem *item = reinterpret_cast<SPItem *>(iter->data);
270 item->avoidRef->handleSettingChange();
271 }
273 if (items) {
274 g_slist_free(items);
275 }
276 sp_document_set_undo_sensitive(document, saved);
277 }
280 /*
281 Local Variables:
282 mode:c++
283 c-file-style:"stroustrup"
284 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
285 indent-tabs-mode:nil
286 fill-column:99
287 End:
288 */
289 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :