Code

* src/sp-conn-end-pair.cpp: Detach connectors from empty groups
[inkscape.git] / src / sp-conn-end-pair.cpp
1 /*
2  * A class for handling connector endpoint movement and libavoid interaction.
3  *
4  * Authors:
5  *   Peter Moulder <pmoulder@mail.csse.monash.edu.au>
6  *   Michael Wybrow <mjwybrow@users.sourceforge.net>
7  *
8  *    * Copyright (C) 2004-2005 Monash University
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
13 #include "attributes.h"
14 #include "sp-conn-end.h"
15 #include "uri.h"
16 #include "display/curve.h"
17 #include "xml/repr.h"
18 #include "sp-path.h"
19 #include "libavoid/vertices.h"
20 #include "libavoid/router.h"
21 #include "document.h"
22 #include "sp-item-group.h"
25 SPConnEndPair::SPConnEndPair(SPPath *const owner)
26     : _invalid_path_connection()
27     , _path(owner)
28     , _connRef(NULL)
29     , _connType(SP_CONNECTOR_NOAVOID)
30     , _transformed_connection()
31 {
32     for (unsigned handle_ix = 0; handle_ix <= 1; ++handle_ix) {
33         this->_connEnd[handle_ix] = new SPConnEnd(SP_OBJECT(owner));
34         this->_connEnd[handle_ix]->_changed_connection
35             = this->_connEnd[handle_ix]->ref.changedSignal()
36             .connect(sigc::bind(sigc::ptr_fun(sp_conn_end_href_changed),
37                                 this->_connEnd[handle_ix], owner, handle_ix));
38     }
39 }
41 SPConnEndPair::~SPConnEndPair()
42 {
43     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
44         delete this->_connEnd[handle_ix];
45         this->_connEnd[handle_ix] = NULL;
46     }
47     if (_connRef) {
48         _connRef->removeFromGraph();
49         delete _connRef;
50         _connRef = NULL;
51     }
53     _invalid_path_connection.disconnect();
54     _transformed_connection.disconnect();
55 }
57 void
58 SPConnEndPair::release()
59 {
60     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
61         this->_connEnd[handle_ix]->_changed_connection.disconnect();
62         this->_connEnd[handle_ix]->_delete_connection.disconnect();
63         this->_connEnd[handle_ix]->_transformed_connection.disconnect();
64         g_free(this->_connEnd[handle_ix]->href);
65         this->_connEnd[handle_ix]->href = NULL;
66         this->_connEnd[handle_ix]->ref.detach();
67     }
68 }
70 void
71 sp_conn_end_pair_build(SPObject *object)
72 {
73     sp_object_read_attr(object, "inkscape:connector-type");
74     sp_object_read_attr(object, "inkscape:connection-start");
75     sp_object_read_attr(object, "inkscape:connection-end");
76 }
79 static void
80 avoid_conn_move(NR::Matrix const *mp, SPItem *moved_item)
81 {
82     // Reroute connector
83     SPPath *path = SP_PATH(moved_item);
84     path->connEndPair.makePathInvalid();
85     sp_conn_adjust_invalid_path(path);
86 }
89 void
90 SPConnEndPair::setAttr(unsigned const key, gchar const *const value)
91 {
92     if (key == SP_ATTR_CONNECTOR_TYPE) {
93         if (value && (strcmp(value, "polyline") == 0)) {
94             _connType = SP_CONNECTOR_POLYLINE;
96             Avoid::Router *router = _path->document->router;
97             GQuark itemID = g_quark_from_string(SP_OBJECT(_path)->id);
98             _connRef = new Avoid::ConnRef(router, itemID);
99             _invalid_path_connection = connectInvalidPath(
100                     sigc::ptr_fun(&sp_conn_adjust_invalid_path));
101             _transformed_connection = _path->connectTransformed(
102                     sigc::ptr_fun(&avoid_conn_move));
103         }
104         else {
105             _connType = SP_CONNECTOR_NOAVOID;
107             if (_connRef) {
108                 _connRef->removeFromGraph();
109                 delete _connRef;
110                 _connRef = NULL;
111                 _invalid_path_connection.disconnect();
112                 _transformed_connection.disconnect();
113             }
114         }
115         return;
117     }
119     unsigned const handle_ix = key - SP_ATTR_CONNECTION_START;
120     g_assert( handle_ix <= 1 );
121     this->_connEnd[handle_ix]->setAttacherHref(value);
124 void
125 SPConnEndPair::writeRepr(Inkscape::XML::Node *const repr) const
127     for (unsigned handle_ix = 0; handle_ix < 2; ++handle_ix) {
128         if (this->_connEnd[handle_ix]->ref.getURI()) {
129             char const * const attr_strs[] = {"inkscape:connection-start",
130                                               "inkscape:connection-end"};
131             gchar *uri_string = this->_connEnd[handle_ix]->ref.getURI()->toString();
132             repr->setAttribute(attr_strs[handle_ix], uri_string);
133             g_free(uri_string);
134         }
135     }
138 void
139 SPConnEndPair::getAttachedItems(SPItem *h2attItem[2]) const {
140     for (unsigned h = 0; h < 2; ++h) {
141         h2attItem[h] = this->_connEnd[h]->ref.getObject();
143         // Deal with the case of the attached object being an empty group.
144         // A group containing no items does not have a valid bbox, so 
145         // causes problems for the auto-routing code.  Also, since such a
146         // group no longer has an onscreen representation and can only be
147         // selected through the XML editor, it makes sense just to detach
148         // connectors from them.
149         if (SP_IS_GROUP(h2attItem[h])) {
150             if (SP_GROUP(h2attItem[h])->group->getItemCount() == 0) {
151                 // This group is empty, so detach.
152                 sp_conn_end_detach(_path, h);
153                 h2attItem[h] = NULL;
154             }
155         }
156     }
159 void
160 SPConnEndPair::getEndpoints(NR::Point endPts[]) const {
161     SPCurve *curve = _path->curve;
162     SPItem *h2attItem[2];
163     getAttachedItems(h2attItem);
165     for (unsigned h = 0; h < 2; ++h) {
166         if ( h2attItem[h] ) {
167             NR::Rect const bbox = h2attItem[h]->invokeBbox(sp_item_i2doc_affine(h2attItem[h]));
168             endPts[h] = bbox.midpoint();
169         }
170         else
171         {
172             if (h == 0) {
173                 endPts[h] = sp_curve_first_point(curve);
174             }
175             else {
176                 endPts[h] = sp_curve_last_point(curve);
177             }
178         }
179     }
182 sigc::connection
183 SPConnEndPair::connectInvalidPath(sigc::slot<void, SPPath *> slot)
185     return _invalid_path_signal.connect(slot);
188 static void emitPathInvalidationNotification(void *ptr)
190     // We emit a signal here rather than just calling the reroute function
191     // since this allows all the movement action computation to happen,
192     // then all connectors (that require it) will be rerouted.  Otherwise,
193     // one connector could get rerouted several times as a result of
194     // dragging a couple of shapes.
196     SPPath *path = SP_PATH(ptr);
197     path->connEndPair._invalid_path_signal.emit(path);
200 void
201 SPConnEndPair::rerouteFromManipulation(void)
203     _connRef->makePathInvalid();
204     sp_conn_adjust_path(_path);
207 void
208 SPConnEndPair::reroute(void)
210     sp_conn_adjust_path(_path);
213 // Called from sp_path_update to initialise the endpoints.
214 void
215 SPConnEndPair::update(void)
217     if (_connType != SP_CONNECTOR_NOAVOID) {
218         g_assert(_connRef != NULL);
219         if (!(_connRef->isInitialised())) {
220             NR::Point endPt[2];
221             getEndpoints(endPt);
223             Avoid::Point src = { endPt[0][NR::X], endPt[0][NR::Y] };
224             Avoid::Point dst = { endPt[1][NR::X], endPt[1][NR::Y] };
226             _connRef->lateSetup(src, dst);
227             _connRef->setCallback(&emitPathInvalidationNotification, _path);
228         }
229         // Store the ID of the objects attached to the connector.
230         storeIds();
231     }
235 void SPConnEndPair::storeIds(void)
237     if (_connEnd[0]->href) {
238         // href begins with a '#' which we don't want.
239         const char *startId = _connEnd[0]->href + 1;
240         GQuark itemId = g_quark_from_string(startId);
241         _connRef->setEndPointId(Avoid::VertID::src, itemId);
242     }
243     else {
244         _connRef->setEndPointId(Avoid::VertID::src, 0);
245     }
246     if (_connEnd[1]->href) {
247         // href begins with a '#' which we don't want.
248         const char *endId = _connEnd[1]->href + 1;
249         GQuark itemId = g_quark_from_string(endId);
250         _connRef->setEndPointId(Avoid::VertID::tar, itemId);
251     }
252     else {
253         _connRef->setEndPointId(Avoid::VertID::tar, 0);
254     }
258 bool
259 SPConnEndPair::isAutoRoutingConn(void)
261     if (_connType != SP_CONNECTOR_NOAVOID) {
262         return true;
263     }
264     return false;
267 void
268 SPConnEndPair::makePathInvalid(void)
270     _connRef->makePathInvalid();
273 void
274 SPConnEndPair::reroutePath(void)
276     if (!isAutoRoutingConn()) {
277         // Do nothing
278         return;
279     }
281     SPCurve *curve = _path->curve;
283     NR::Point endPt[2];
284     getEndpoints(endPt);
286     Avoid::Point src = { endPt[0][NR::X], endPt[0][NR::Y] };
287     Avoid::Point dst = { endPt[1][NR::X], endPt[1][NR::Y] };
289     _connRef->updateEndPoint(Avoid::VertID::src, src);
290     _connRef->updateEndPoint(Avoid::VertID::tar, dst);
292     _connRef->generatePath(src, dst);
294     Avoid::PolyLine route = _connRef->route();
295     _connRef->calcRouteDist();
297     sp_curve_reset(curve);
298     sp_curve_moveto(curve, endPt[0]);
300     for (int i = 1; i < route.pn; ++i) {
301         NR::Point p(route.ps[i].x, route.ps[i].y);
302         sp_curve_lineto(curve, p);
303     }
306 /*
307   Local Variables:
308   mode:c++
309   c-file-style:"stroustrup"
310   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
311   indent-tabs-mode:nil
312   fill-column:99
313   End:
314 */
315 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :