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 "xml/simple-node.cpp"
20 #include "document.h"
21 #include "prefs-utils.h"
23 #include "desktop.h"
24 #include "desktop-handles.h"
25 #include "sp-namedview.h"
26 #include "inkscape.h"
29 static Avoid::Polygn avoid_item_poly(SPItem const *item);
32 SPAvoidRef::SPAvoidRef(SPItem *spitem)
33 : shapeRef(NULL)
34 , item(spitem)
35 , setting(false)
36 , new_setting(false)
37 , _transformed_connection()
38 {
39 }
42 SPAvoidRef::~SPAvoidRef()
43 {
44 _transformed_connection.disconnect();
45 if (shapeRef) {
46 // shapeRef is finalised by delShape,
47 // so no memory is lost here.
48 Avoid::delShape(shapeRef);
49 shapeRef = NULL;
50 }
51 }
54 void SPAvoidRef::setAvoid(char const *value)
55 {
56 if (SP_OBJECT_IS_CLONED(item)) {
57 // Don't keep avoidance information for cloned objects.
58 return;
59 }
60 new_setting = false;
61 if (value && (strcmp(value, "true") == 0)) {
62 new_setting = true;
63 }
64 }
67 void SPAvoidRef::handleSettingChange(void)
68 {
69 SPDesktop *desktop = inkscape_active_desktop();
70 if (desktop == NULL) {
71 return;
72 }
74 if (new_setting == setting) {
75 // Don't need to make any changes
76 return;
77 }
79 _transformed_connection.disconnect();
80 if (new_setting) {
81 _transformed_connection = item->connectTransformed(
82 sigc::ptr_fun(&avoid_item_move));
84 Avoid::Polygn poly = avoid_item_poly(item);
85 if (poly.pn > 0) {
86 const char *id = SP_OBJECT_REPR(item)->attribute("id");
87 g_assert(id != NULL);
89 // Get a unique ID for the item.
90 GQuark itemID = g_quark_from_string(id);
92 shapeRef = new Avoid::ShapeRef(itemID, poly);
93 Avoid::freePoly(poly);
95 Avoid::addShape(shapeRef);
96 }
97 }
98 else
99 {
100 g_assert(shapeRef);
102 // shapeRef is finalised by delShape,
103 // so no memory is lost here.
104 Avoid::delShape(shapeRef);
105 shapeRef = NULL;
106 }
107 setting = new_setting;
108 }
111 static Avoid::Polygn avoid_item_poly(SPItem const *item)
112 {
113 SPDesktop *desktop = inkscape_active_desktop();
114 g_assert(desktop != NULL);
116 Avoid::Polygn poly;
118 // TODO: The right way to do this is to return the convex hull of
119 // the object, or an approximation in the case of a rounded
120 // object. Specific SPItems will need to have a new
121 // function that returns points for the convex hull.
122 // For some objects it is enough to feed the snappoints to
123 // some convex hull code, though not NR::ConvexHull as this
124 // only keeps the bounding box of the convex hull currently.
126 // TODO: SPItem::invokeBbox gives the wrong result for some objects
127 // that have internal representations that are updated later
128 // by the sp_*_update functions, e.g., text.
129 sp_document_ensure_up_to_date(item->document);
131 NR::Rect rHull = item->invokeBbox(sp_item_i2doc_affine(item));
134 double spacing = desktop->namedview->connector_spacing;
136 // Add a little buffer around the edge of each object.
137 NR::Rect rExpandedHull = NR::expand(rHull, -spacing);
138 poly = Avoid::newPoly(4);
140 for (unsigned n = 0; n < 4; ++n) {
141 // TODO: I think the winding order in libavoid or inkscape might
142 // be backwards, probably due to the inverse y co-ordinates
143 // used for the screen. The '3 - n' reverses the order.
144 /* On "correct" winding order: Winding order of NR::Rect::corner is in a positive
145 * direction, like libart. "Positive direction" means the same as in most of Inkscape and
146 * SVG: if you visualize y as increasing upwards, as is the convention in mathematics, then
147 * positive angle is visualized as anticlockwise, as in mathematics; so if you visualize y
148 * as increasing downwards, as is common outside of mathematics, then positive angle
149 * direction is visualized as clockwise, as is common outside of mathematics. This
150 * convention makes it easier mix pure mathematics code with graphics code: the important
151 * thing when mixing code is that the number values stored in variables (representing y
152 * coordinate, angle) match up; variables store numbers, not visualized positions, and the
153 * programmer is free to switch between visualizations when thinking about a given piece of
154 * code.
155 *
156 * MathWorld, libart and NR::Rect::corner all seem to take positive winding (i.e. winding
157 * that yields +1 winding number inside a simple closed shape) to mean winding in a
158 * positive angle. This, together with the observation that variables store numbers rather
159 * than positions, suggests that NR::Rect::corner uses the right direction.
160 */
161 NR::Point hullPoint = rExpandedHull.corner(3 - n);
162 poly.ps[n].x = hullPoint[NR::X];
163 poly.ps[n].y = hullPoint[NR::Y];
164 }
166 return poly;
167 }
170 GSList *get_avoided_items(GSList *list, SPObject *from, SPDesktop *desktop,
171 bool initialised)
172 {
173 for (SPObject *child = sp_object_first_child(SP_OBJECT(from)) ;
174 child != NULL; child = SP_OBJECT_NEXT(child) ) {
175 if (SP_IS_ITEM(child) &&
176 !desktop->isLayer(SP_ITEM(child)) &&
177 !SP_ITEM(child)->isLocked() &&
178 !desktop->itemIsHidden(SP_ITEM(child)) &&
179 (!initialised || SP_ITEM(child)->avoidRef->shapeRef)
180 )
181 {
182 list = g_slist_prepend (list, SP_ITEM(child));
183 }
185 if (SP_IS_ITEM(child) && desktop->isLayer(SP_ITEM(child))) {
186 list = get_avoided_items(list, child, desktop, initialised);
187 }
188 }
190 return list;
191 }
194 void avoid_item_move(NR::Matrix const *mp, SPItem *moved_item)
195 {
196 Avoid::ShapeRef *shapeRef = moved_item->avoidRef->shapeRef;
197 g_assert(shapeRef);
199 Avoid::Polygn poly = avoid_item_poly(moved_item);
200 if (poly.pn > 0) {
201 // moveShape actually destroys the old shapeRef and returns a new one.
202 moved_item->avoidRef->shapeRef = Avoid::moveShape(shapeRef, &poly);
203 Avoid::freePoly(poly);
204 }
205 }
208 void init_avoided_shape_geometry(SPDesktop *desktop)
209 {
210 // Don't count this as changes to the document,
211 // it is basically just llate initialisation.
212 SPDocument *document = SP_DT_DOCUMENT(desktop);
213 gboolean saved = sp_document_get_undo_sensitive(document);
214 sp_document_set_undo_sensitive(document, FALSE);
216 bool initialised = false;
217 GSList *items = get_avoided_items(NULL, desktop->currentRoot(), desktop,
218 initialised);
220 for ( GSList const *iter = items ; iter != NULL ; iter = iter->next ) {
221 SPItem *item = reinterpret_cast<SPItem *>(iter->data);
222 item->avoidRef->handleSettingChange();
223 }
225 if (items) {
226 g_slist_free(items);
227 }
228 sp_document_set_undo_sensitive(document, saved);
229 }
232 /*
233 Local Variables:
234 mode:c++
235 c-file-style:"stroustrup"
236 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
237 indent-tabs-mode:nil
238 fill-column:99
239 End:
240 */
241 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :