1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * A class for handling shape interaction with libavoid.
4  *
5  * Authors:
6  *   Michael Wybrow <mjwybrow@users.sourceforge.net>
7  *   Abhishek Sharma
8  *
9  * Copyright (C) 2005 Michael Wybrow
10  *
11  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12  */
13 
14 
15 #include <cstring>
16 #include <string>
17 #include <iostream>
18 
19 #include "2geom/convex-hull.h"
20 #include "2geom/line.h"
21 
22 #include "conn-avoid-ref.h"
23 #include "desktop.h"
24 #include "document-undo.h"
25 #include "document.h"
26 #include "inkscape.h"
27 #include "verbs.h"
28 
29 #include "display/curve.h"
30 
31 #include "3rdparty/adaptagrams/libavoid/router.h"
32 #include "3rdparty/adaptagrams/libavoid/shape.h"
33 
34 #include "object/sp-namedview.h"
35 #include "object/sp-shape.h"
36 
37 #include "svg/stringstream.h"
38 
39 #include "xml/node.h"
40 
41 using Inkscape::DocumentUndo;
42 
43 using Avoid::Router;
44 
45 static Avoid::Polygon avoid_item_poly(SPItem const *item);
46 
47 
SPAvoidRef(SPItem * spitem)48 SPAvoidRef::SPAvoidRef(SPItem *spitem)
49     : shapeRef(nullptr)
50     , item(spitem)
51     , setting(false)
52     , new_setting(false)
53     , _transformed_connection()
54 {
55 }
56 
57 
~SPAvoidRef()58 SPAvoidRef::~SPAvoidRef()
59 {
60     _transformed_connection.disconnect();
61 
62     // If the document is being destroyed then the router instance
63     // and the ShapeRefs will have been destroyed with it.
64     Router *router = item->document->getRouter();
65 
66     if (shapeRef && router) {
67         router->deleteShape(shapeRef);
68     }
69     shapeRef = nullptr;
70 }
71 
72 
setAvoid(char const * value)73 void SPAvoidRef::setAvoid(char const *value)
74 {
75     // Don't keep avoidance information for cloned objects.
76     if ( !item->cloned ) {
77         new_setting = false;
78         if (value && (strcmp(value, "true") == 0)) {
79             new_setting = true;
80         }
81     }
82 }
83 
handleSettingChange()84 void SPAvoidRef::handleSettingChange()
85 {
86     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
87     if (desktop == nullptr) {
88         return;
89     }
90     if (desktop->getDocument() != item->document) {
91         // We don't want to go any further if the active desktop's document
92         // isn't the same as the document that this item is part of.  This
93         // case can happen if a new document is loaded from the file chooser
94         // or via the recent file menu.  In this case, we can end up here
95         // as a result of a ensureUpToDate performed on a
96         // document not yet attached to the active desktop.
97         return;
98     }
99 
100     if (new_setting == setting) {
101         // Don't need to make any changes
102         return;
103     }
104     setting = new_setting;
105 
106     Router *router = item->document->getRouter();
107 
108     _transformed_connection.disconnect();
109     if (new_setting) {
110         Avoid::Polygon poly = avoid_item_poly(item);
111         if (poly.size() > 0) {
112             _transformed_connection = item->connectTransformed(
113                     sigc::ptr_fun(&avoid_item_move));
114 
115             char const *id = item->getAttribute("id");
116             g_assert(id != nullptr);
117 
118             // Get a unique ID for the item.
119             GQuark itemID = g_quark_from_string(id);
120 
121             shapeRef = new Avoid::ShapeRef(router, poly, itemID);
122         }
123     }
124     else if (shapeRef)
125     {
126         router->deleteShape(shapeRef);
127         shapeRef = nullptr;
128     }
129 }
130 
131 
getAttachedShapes(const unsigned int type)132 std::vector<SPItem *> SPAvoidRef::getAttachedShapes(const unsigned int type)
133 {
134     std::vector<SPItem *> list;
135 
136     Avoid::IntList shapes;
137     GQuark shapeId = g_quark_from_string(item->getId());
138     item->document->getRouter()->attachedShapes(shapes, shapeId, type);
139 
140     Avoid::IntList::iterator finish = shapes.end();
141     for (Avoid::IntList::iterator i = shapes.begin(); i != finish; ++i) {
142         const gchar *connId = g_quark_to_string(*i);
143         SPObject *obj = item->document->getObjectById(connId);
144         if (obj == nullptr) {
145             g_warning("getAttachedShapes: Object with id=\"%s\" is not "
146                     "found. Skipping.", connId);
147             continue;
148         }
149         SPItem *shapeItem = SP_ITEM(obj);
150         list.push_back(shapeItem);
151     }
152     return list;
153 }
154 
155 
getAttachedConnectors(const unsigned int type)156 std::vector<SPItem *> SPAvoidRef::getAttachedConnectors(const unsigned int type)
157 {
158     std::vector<SPItem *> list;
159 
160     Avoid::IntList conns;
161     GQuark shapeId = g_quark_from_string(item->getId());
162     item->document->getRouter()->attachedConns(conns, shapeId, type);
163 
164     Avoid::IntList::iterator finish = conns.end();
165     for (Avoid::IntList::iterator i = conns.begin(); i != finish; ++i) {
166         const gchar *connId = g_quark_to_string(*i);
167         SPObject *obj = item->document->getObjectById(connId);
168         if (obj == nullptr) {
169             g_warning("getAttachedConnectors: Object with id=\"%s\" is not "
170                     "found. Skipping.", connId);
171             continue;
172         }
173         SPItem *connItem = SP_ITEM(obj);
174         list.push_back(connItem);
175     }
176     return list;
177 }
178 
getConnectionPointPos()179 Geom::Point SPAvoidRef::getConnectionPointPos()
180 {
181     g_assert(item);
182     // the center is all we are interested in now; we used to care
183     // about non-center points, but that's moot.
184     Geom::OptRect bbox = item->documentVisualBounds();
185     return (bbox) ? bbox->midpoint() : Geom::Point(0, 0);
186 }
187 
approxCurveWithPoints(SPCurve * curve)188 static std::vector<Geom::Point> approxCurveWithPoints(SPCurve *curve)
189 {
190     // The number of segments to use for not straight curves approximation
191     const unsigned NUM_SEGS = 4;
192 
193     const Geom::PathVector& curve_pv = curve->get_pathvector();
194 
195     // The structure to hold the output
196     std::vector<Geom::Point> poly_points;
197 
198     // Iterate over all curves, adding the endpoints for linear curves and
199     // sampling the other curves
200     double seg_size = 1.0 / NUM_SEGS;
201     double at;
202     at = 0;
203     Geom::PathVector::const_iterator pit = curve_pv.begin();
204     while (pit != curve_pv.end())
205     {
206         Geom::Path::const_iterator cit = pit->begin();
207         while (cit != pit->end())
208         {
209             if (cit == pit->begin())
210             {
211                 poly_points.push_back(cit->initialPoint());
212             }
213 
214             if (dynamic_cast<Geom::CubicBezier const*>(&*cit))
215             {
216                 at += seg_size;
217                 if (at <= 1.0 )
218                     poly_points.push_back(cit->pointAt(at));
219                 else
220                 {
221                     at = 0.0;
222                     ++cit;
223                 }
224             }
225             else
226             {
227                 poly_points.push_back(cit->finalPoint());
228                 ++cit;
229             }
230         }
231         ++pit;
232     }
233     return poly_points;
234 }
235 
approxItemWithPoints(SPItem const * item,const Geom::Affine & item_transform)236 static std::vector<Geom::Point> approxItemWithPoints(SPItem const *item, const Geom::Affine& item_transform)
237 {
238     // The structure to hold the output
239     std::vector<Geom::Point> poly_points;
240     std::unique_ptr<SPCurve> item_curve;
241 
242     if (SP_IS_GROUP(item))
243     {
244         SPGroup* group = SP_GROUP(item);
245         // consider all first-order children
246         std::vector<SPItem*> itemlist = sp_item_group_item_list(group);
247         for (auto child_item : itemlist) {
248             std::vector<Geom::Point> child_points = approxItemWithPoints(child_item, item_transform * child_item->transform);
249             poly_points.insert(poly_points.end(), child_points.begin(), child_points.end());
250         }
251     }
252     else if (SP_IS_SHAPE(item))
253     {
254         auto shape = static_cast<SPShape const *>(item);
255         SP_SHAPE(item)->set_shape();
256         item_curve = SPCurve::copy(shape->curve());
257         // make sure it has an associated curve
258         if (item_curve)
259         {
260             // apply transformations (up to common ancestor)
261             item_curve->transform(item_transform);
262         }
263     } else {
264         auto bbox = item->documentPreferredBounds();
265         if (bbox) {
266             item_curve = SPCurve::new_from_rect(*bbox);
267         }
268     }
269 
270     if (item_curve) {
271         std::vector<Geom::Point> curve_points = approxCurveWithPoints(item_curve.get());
272         poly_points.insert(poly_points.end(), curve_points.begin(), curve_points.end());
273     }
274 
275     return poly_points;
276 }
avoid_item_poly(SPItem const * item)277 static Avoid::Polygon avoid_item_poly(SPItem const *item)
278 {
279     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
280     g_assert(desktop != nullptr);
281     double spacing = desktop->namedview->connector_spacing;
282 
283     Geom::Affine itd_mat = item->i2doc_affine();
284     std::vector<Geom::Point> hull_points;
285     hull_points = approxItemWithPoints(item, itd_mat);
286 
287     // create convex hull from all sampled points
288     Geom::ConvexHull hull(hull_points);
289 
290     // enlarge path by "desktop->namedview->connector_spacing"
291     // store expanded convex hull in Avoid::Polygn
292     Avoid::Polygon poly;
293     if (hull.empty()) {
294         return poly;
295     }
296 
297     Geom::Line hull_edge(hull.back(), hull.front());
298     Geom::Line prev_parallel_hull_edge;
299     prev_parallel_hull_edge.setOrigin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
300     prev_parallel_hull_edge.setVector(hull_edge.versor());
301     int hull_size = hull.size();
302     for (int i = 0; i < hull_size; ++i)
303     {
304         if (i + 1 == hull_size) {
305             hull_edge.setPoints(hull.back(), hull.front());
306         } else {
307             hull_edge.setPoints(hull[i], hull[i + 1]);
308         }
309         Geom::Line parallel_hull_edge;
310         parallel_hull_edge.setOrigin(hull_edge.origin()+hull_edge.versor().ccw()*spacing);
311         parallel_hull_edge.setVector(hull_edge.versor());
312 
313         // determine the intersection point
314         try {
315             Geom::OptCrossing int_pt = Geom::intersection(parallel_hull_edge, prev_parallel_hull_edge);
316             if (int_pt)
317             {
318                 Avoid::Point avoid_pt((parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::X],
319                                         (parallel_hull_edge.origin()+parallel_hull_edge.versor()*int_pt->ta)[Geom::Y]);
320                 poly.ps.push_back(avoid_pt);
321             }
322             else
323             {
324                 // something went wrong...
325                 std::cout<<"conn-avoid-ref.cpp: avoid_item_poly: Geom:intersection failed."<<std::endl;
326             }
327         }
328         catch (Geom::InfiniteSolutions const &e) {
329             // the parallel_hull_edge and prev_parallel_hull_edge lie on top of each other, hence infinite crossings
330             g_message("conn-avoid-ref.cpp: trying to get crossings of identical lines");
331         }
332         prev_parallel_hull_edge = parallel_hull_edge;
333     }
334 
335     return poly;
336 }
337 
338 
get_avoided_items(std::vector<SPItem * > & list,SPObject * from,SPDesktop * desktop,bool initialised)339 std::vector<SPItem *> get_avoided_items(std::vector<SPItem *> &list, SPObject *from, SPDesktop *desktop,
340         bool initialised)
341 {
342     for (auto& child: from->children) {
343         if (SP_IS_ITEM(&child) &&
344             !desktop->isLayer(SP_ITEM(&child)) &&
345             !SP_ITEM(&child)->isLocked() &&
346             !desktop->itemIsHidden(SP_ITEM(&child)) &&
347             (!initialised || SP_ITEM(&child)->getAvoidRef().shapeRef)
348             )
349         {
350             list.push_back(SP_ITEM(&child));
351         }
352 
353         if (SP_IS_ITEM(&child) && desktop->isLayer(SP_ITEM(&child))) {
354             list = get_avoided_items(list, &child, desktop, initialised);
355         }
356     }
357 
358     return list;
359 }
360 
361 
avoid_item_move(Geom::Affine const *,SPItem * moved_item)362 void avoid_item_move(Geom::Affine const */*mp*/, SPItem *moved_item)
363 {
364     Avoid::ShapeRef *shapeRef = moved_item->getAvoidRef().shapeRef;
365     g_assert(shapeRef);
366 
367     Router *router = moved_item->document->getRouter();
368     Avoid::Polygon poly = avoid_item_poly(moved_item);
369     if (!poly.empty()) {
370         router->moveShape(shapeRef, poly);
371     }
372 }
373 
374 
init_avoided_shape_geometry(SPDesktop * desktop)375 void init_avoided_shape_geometry(SPDesktop *desktop)
376 {
377     // Don't count this as changes to the document,
378     // it is basically just late initialisation.
379     SPDocument *document = desktop->getDocument();
380     DocumentUndo::ScopedInsensitive _no_undo(document);
381 
382     bool initialised = false;
383     std::vector<SPItem *> tmp;
384     std::vector<SPItem *> items = get_avoided_items(tmp, desktop->currentRoot(), desktop,
385             initialised);
386 
387     for (auto item : items) {
388         item->getAvoidRef().handleSettingChange();
389     }
390 }
391 
392 
393 /*
394   Local Variables:
395   mode:c++
396   c-file-style:"stroustrup"
397   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
398   indent-tabs-mode:nil
399   fill-column:99
400   End:
401 */
402 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
403