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;
95 _transformed_connection.disconnect();
96 if (new_setting) {
97 Avoid::Polygn poly = avoid_item_poly(item);
98 if (poly.pn > 0) {
99 _transformed_connection = item->connectTransformed(
100 sigc::ptr_fun(&avoid_item_move));
102 const char *id = SP_OBJECT_REPR(item)->attribute("id");
103 g_assert(id != NULL);
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);
111 router->addShape(shapeRef);
112 }
113 }
114 else
115 {
116 g_assert(shapeRef);
118 // shapeRef is finalised by delShape,
119 // so no memory is lost here.
120 router->delShape(shapeRef);
121 shapeRef = NULL;
122 }
123 }
126 GSList *SPAvoidRef::getAttachedShapes(const unsigned int type)
127 {
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);
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;
147 }
150 GSList *SPAvoidRef::getAttachedConnectors(const unsigned int type)
151 {
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);
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;
171 }
174 static Avoid::Polygn avoid_item_poly(SPItem const *item)
175 {
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);
194 NR::Maybe<NR::Rect> rHull = item->getBounds(sp_item_i2doc_affine(item));
195 if (!rHull) {
196 return Avoid::newPoly(0);
197 }
199 double spacing = desktop->namedview->connector_spacing;
201 // Add a little buffer around the edge of each object.
202 NR::Rect rExpandedHull = NR::expand(*rHull, -spacing);
203 poly = Avoid::newPoly(4);
205 for (unsigned n = 0; n < 4; ++n) {
206 NR::Point hullPoint = rExpandedHull.corner(n);
207 poly.ps[n].x = hullPoint[NR::X];
208 poly.ps[n].y = hullPoint[NR::Y];
209 }
211 return poly;
212 }
215 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop,
216 bool initialised)
217 {
218 for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
219 child != NULL; child = SP_OBJECT_NEXT(child) ) {
220 if (SP_IS_ITEM(child) &&
221 !desktop->isLayer(SP_ITEM(child)) &&
222 !SP_ITEM(child)->isLocked() &&
223 !desktop->itemIsHidden(SP_ITEM(child)) &&
224 (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
225 )
226 {
227 list = g_slist_prepend (list, SP_ITEM(child));
228 }
230 if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
231 list = get_avoided_items(list, child, desktop, initialised);
232 }
233 }
235 return list;
236 }
239 void avoid_item_move(NR::Matrix const *mp, SPItem *moved_item)
240 {
241 Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
242 g_assert(shapeRef);
244 Router *router = moved_item->document->router;
245 Avoid::Polygn poly = avoid_item_poly(moved_item);
246 if (poly.pn > 0) {
247 router->moveShape(shapeRef, &poly);
248 Avoid::freePoly(poly);
249 }
250 }
253 void init_avoided_shape_geometry(SPDesktop *desktop)
254 {
255 // Don't count this as changes to the document,
256 // it is basically just llate initialisation.
257 SPDocument *document = sp_desktop_document(desktop);
258 bool saved = sp_document_get_undo_sensitive(document);
259 sp_document_set_undo_sensitive(document, false);
261 bool initialised = false;
262 GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
263 initialised);
265 for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
266 SPItem *item = reinterpret_cast<SPItem *>(iter->data);
267 item->avoidRef->handleSettingChange();
268 }
270 if (items) {
271 g_slist_free(items);
272 }
273 sp_document_set_undo_sensitive(document, saved);
274 }
277 /*
278 Local Variables:
279 mode:c++
280 c-file-style:"stroustrup"
281 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
282 indent-tabs-mode:nil
283 fill-column:99
284 End:
285 */
286 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :