2006-12-03 Dimitris Glezos <dimitris@glezos.com>
[dia.git] / lib / connpoint_line.c
blob1b8decc6f40577451ab24dd3f4c82b75125ecbf1
1 /*
2 * SADT diagram support for dia
3 * Copyright(C) 2000 Cyrille Chepelov
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 #include <config.h>
22 #include <string.h>
23 #include "connpoint_line.h"
24 #include "connectionpoint.h"
25 #include "dia_xml.h"
27 #define DEBUG_PARENT 0
28 #define DEBUG_ORDER 0
30 static void cpl_reorder_connections(ConnPointLine *cpl);
32 inline static ConnectionPoint *new_connpoint(DiaObject *obj)
34 ConnectionPoint *cp = g_new0(ConnectionPoint,1);
35 cp->object = obj;
36 return cp;
39 inline static void del_connpoint(ConnectionPoint *cp)
41 g_free(cp);
44 static ConnectionPoint *
45 cpl_remove_connpoint(ConnPointLine *cpl,int pos)
47 ConnectionPoint *cp;
49 g_assert (cpl->num_connections > 0);
51 if (pos >= cpl->num_connections) {
52 pos = cpl->num_connections - 1;
53 } else {
54 while (pos < 0) pos += cpl->num_connections;
57 cp = (ConnectionPoint *)(g_slist_nth(cpl->connections,pos)->data);
58 g_assert(cp);
60 cpl->connections = g_slist_remove(cpl->connections,(gpointer)cp);
61 object_remove_connectionpoint(cpl->parent,cp);
63 cpl->num_connections--;
64 /* removing a point doesn't change the order of the remaining ones, so we
65 don't need to call cpl_reorder_connections. */
66 /* The caller is responsible for freeing the removed connection point */
67 return cp;
70 static void
71 cpl_add_connectionpoint_at(ConnPointLine *cpl, int pos,ConnectionPoint *cp)
73 if (pos == 0) {
74 /* special case handling so that the order of CPL groups in
75 the parent's CP list is preserved. */
76 int fpos,i;
77 ConnectionPoint *fcp;
78 g_assert(cpl->connections);
79 fpos = -1;
80 fcp = (ConnectionPoint *)(cpl->connections->data);
81 g_assert(fcp);
82 for (i=0; i<cpl->parent->num_connections; i++) {
83 if (cpl->parent->connections[i] == fcp) {
84 fpos = i;
85 break;
88 g_assert(fpos >= 0);
89 object_add_connectionpoint_at(cpl->parent,cp,fpos);
90 }else {
91 /* XXX : make this a little better ; try to insert at the correct
92 position right away to eliminate cpl_reorder_connection */
93 object_add_connectionpoint(cpl->parent,cp);
95 if (pos < 0) {
96 cpl->connections = g_slist_append(cpl->connections,(gpointer)cp);
98 else {
99 cpl->connections = g_slist_insert(cpl->connections,(gpointer)cp,pos);
101 cpl->num_connections++;
103 /* we should call
104 cpl_reorder_connections(cpl);
105 before we leave the object !! However, this is delayed, for the case
106 several CP's are added at once (initialisation). */
109 inline static void
110 cpl_add_connectionpoint(ConnPointLine *cpl,ConnectionPoint *cp)
112 cpl_add_connectionpoint_at(cpl,-1,cp);
115 ConnPointLine *
116 connpointline_create(DiaObject *parent, int num_connections)
118 ConnPointLine *cpl;
119 int i;
121 cpl = g_new0(ConnPointLine,1);
122 cpl->parent = parent;
124 cpl->connections = NULL;
125 for (i=0; i<num_connections; i++) {
126 cpl_add_connectionpoint(cpl,new_connpoint(cpl->parent));
128 cpl_reorder_connections(cpl);
129 return cpl;
132 void
133 connpointline_destroy(ConnPointLine *cpl)
135 while (cpl->num_connections > 0) del_connpoint(cpl_remove_connpoint(cpl,0));
136 g_free(cpl);
139 static ConnPointLine *
140 cpl_inplacecreate(DiaObject *obj, int nc, int *realconncount)
142 int i;
143 ConnPointLine *newcpl;
144 ConnectionPoint *cp;
146 /* This thing creates a connection point line without actually adding
147 connection points to the parent object. */
148 newcpl = g_new0(ConnPointLine,1);
149 newcpl->parent = obj;
151 for (i=0; i < nc; i++,(*realconncount)++) {
152 cp = g_new0(ConnectionPoint,1);
153 cp->object = newcpl->parent;
154 obj->connections[*realconncount] = cp;
155 newcpl->connections = g_slist_append(newcpl->connections,cp);
157 newcpl->num_connections = nc;
158 return newcpl;
161 ConnPointLine *
162 connpointline_load(DiaObject *obj,ObjectNode obj_node,
163 const gchar *name, int default_nc,int *realconncount)
165 ConnPointLine *cpl;
166 int nc = default_nc;
167 AttributeNode attr;
169 attr = object_find_attribute(obj_node, name);
170 if (attr != NULL)
171 nc = data_int(attribute_first_data(attr));
172 cpl = connpointline_create(obj,nc);
174 if (realconncount) (*realconncount) += cpl->num_connections;
175 return cpl;
176 /* NOT this !
177 return cpl_inplacecreate(obj,
178 load_int(obj_node,name,default_nc),
179 realconncount);
183 void
184 connpointline_save(ConnPointLine *cpl,ObjectNode obj_node,
185 const gchar *name)
187 data_add_int(new_attribute(obj_node, name),cpl->num_connections);
190 ConnPointLine *
191 connpointline_copy(DiaObject *newobj,ConnPointLine *cpl, int *realconncount)
193 g_assert(realconncount);
194 return cpl_inplacecreate(newobj,cpl->num_connections,realconncount);
197 void connpointline_update(ConnPointLine *cpl)
202 void
203 connpointline_putonaline(ConnPointLine *cpl,Point *start,Point *end)
205 Point se_vector;
206 real se_len,pseudopoints;
207 int i;
208 GSList *elem;
209 gint dirs;
211 point_copy(&se_vector, end);
212 point_sub(&se_vector, start);
214 se_len = point_len(&se_vector);
216 if (se_len > 0)
217 point_normalize(&se_vector);
219 cpl->start = *start;
220 cpl->end = *end;
222 if (fabs(se_vector.x) > fabs(se_vector.y))
223 dirs = DIR_NORTH|DIR_SOUTH;
224 else
225 dirs = DIR_EAST|DIR_WEST;
227 pseudopoints = cpl->num_connections + 1; /* here, we count the start and end
228 points as eating real positions. */
229 for (i=0, elem=cpl->connections;
230 i<cpl->num_connections;
231 i++,elem=g_slist_next(elem)) {
232 ConnectionPoint *cp = (ConnectionPoint *)(elem->data);
233 cp->pos = se_vector;
234 cp->directions = dirs;
235 point_scale(&cp->pos,se_len * (i+1.0)/pseudopoints);
236 point_add(&cp->pos,start);
241 /* These object_* functions are useful to me, because of what they do, I think
242 they belong to lib/object.c ; should I move them ? */
243 static void
244 object_move_connection(DiaObject *obj,int sourcepos,int destpos)
246 ConnectionPoint *cp;
247 g_assert(destpos < sourcepos);
248 cp = obj->connections[sourcepos];
250 memmove(&(obj->connections[destpos+1]),&(obj->connections[destpos]),
251 sizeof(ConnectionPoint *)*(sourcepos-destpos));
252 obj->connections[destpos] = cp;
255 static int
256 object_find_connection(DiaObject *obj, ConnectionPoint *cp, int startpos)
258 int i;
259 for (i = startpos; i < obj->num_connections; i++) {
260 if (obj->connections[i] == cp) return i;
262 return -1; /* should not happen */
266 #if DEBUG_ORDER
267 static int obj_find_connection(DiaObject *obj,ConnectionPoint *cp)
269 int i;
270 for (i=0;i<obj->num_connections;i++)
271 if (cp == obj->connections[i]) return i;
272 return -1;
276 static void cpl_dump_connections(ConnPointLine *cpl)
278 DiaObject *obj = cpl->parent;
279 int i;
280 GSList *elem;
281 ConnectionPoint *cp;
283 g_message("CPL order dump");
284 for (i=0,elem = cpl->connections;
285 i<cpl->num_connections;
286 i++,elem = g_slist_next(elem)) {
287 cp = (ConnectionPoint *)(elem->data);
288 g_message("connection %p %d@CPL %d@OBJ",
289 cp,i,obj_find_connection(obj,cp));
292 #endif
294 static void
295 cpl_reorder_connections(ConnPointLine *cpl)
297 /* This is needed, so that we don't mess up the loaded connections if
298 we save after the user has removed and added some connection points.
299 Normally, if an object owns several CPL, the order of the groups of
300 connectionpoints in its connectionpoint list should not change, as long
301 as we call this function whenever we do something.
303 The CPL has two big responsiblities here : first, it messes with
304 the parent object's structures (ugh), second, it must ensure that its
305 first CP is inserted so that it is found first in the parent's CP list,
306 and that the order of CP groups in the parent's CP list is respected (so
307 that the parent could have several different CPL and rely on the order).
310 int i,j,first;
311 ConnectionPoint *cp;
312 GSList *elem;
313 DiaObject *obj;
315 if (!cpl->connections) return;
316 #if DEBUG_ORDER
317 g_message("before cpl_reorder");
318 cpl_dump_connections(cpl);
319 #endif
321 first = -1;
322 cp = (ConnectionPoint *)(cpl->connections->data);
323 obj = cpl->parent;
324 for (i=0; i<obj->num_connections; i++){
325 if (obj->connections[i] == cp) {
326 first = i;
327 break;
330 g_assert(first >= 0); /* otherwise things went loose badly. */
331 for (i=0,j=first,elem=cpl->connections;
332 i<cpl->num_connections;
333 elem=g_slist_next(elem),i++,j++) {
334 cp = (ConnectionPoint *)(elem->data); /* = cpl->connections[i] */
335 if ( cp != obj->connections[j]) { /* first time will always be false.
336 Is GCC that smart ? Probably not. */
337 object_move_connection(obj,object_find_connection(obj,cp,j),j);
340 #if DEBUG_ORDER
341 g_message("after cpl_reorder");
342 cpl_dump_connections(cpl);
343 #endif
344 #if DEBUG_PARENT
345 j = 0;
346 for (i=0; i<cpl->parent->num_connections;i++)
347 if (!cpl->parent->connections[i]) j++;
348 /* We should never make such holes !*/
349 if (j) g_warning("in cpl_reorder_connections there are %d holes in the parent's ConnectionPoint list !",j);
350 #endif
355 int
356 connpointline_can_add_point(ConnPointLine *cpl, Point *clicked)
358 return 1;
362 connpointline_can_remove_point(ConnPointLine *cpl, Point *clicked)
364 if (cpl->num_connections <= 1)
365 return 0;
366 else
367 return 1;
370 static int
371 cpl_get_pointbefore(ConnPointLine *cpl, Point *clickedpoint)
373 int i, pos = -1;
374 GSList *elem;
375 ConnectionPoint *cp;
376 real dist = 65536.0;
377 real tmpdist;
379 if (!clickedpoint) return 0;
381 for (i=0,elem=cpl->connections;
382 i<cpl->num_connections;
383 i++,elem=g_slist_next(elem)) {
384 cp = (ConnectionPoint *)(elem->data);
386 tmpdist = distance_point_point(&cp->pos,clickedpoint);
387 if (tmpdist < dist) {
388 dist = tmpdist;
389 pos = i;
392 tmpdist = distance_point_point(&cpl->end,clickedpoint);
393 if (tmpdist < dist) {
394 /*dist = tmpdist; */
395 pos = -1;
397 return pos;
400 typedef struct {
401 ObjectChange obj_change;
403 int add; /* How much to add or remove */
404 int applied; /* 1 if the event has been applied. */
406 ConnPointLine *cpl;
407 int pos; /* Position where the change happened. */
408 ConnectionPoint **cp; /* The removed connection point. */
409 } CPLChange;
411 static void
412 cpl_change_addremove(CPLChange *change, ConnPointLine *cpl,
413 int action, int resultingapplied)
415 if (action != 0) {
416 if (action > 0) { /* We should add */
417 while (action--) {
418 cpl_add_connectionpoint_at(cpl,change->pos,change->cp[action]);
419 change->cp[action] = NULL;
421 cpl_reorder_connections(cpl);
422 } else { /* We should remove. Warning, action is negative. */
423 while (action++) {
424 change->cp[-action] = cpl_remove_connpoint(cpl,change->pos);
427 } else {
428 g_warning("cpl_change_addremove(): null action !");
430 change->applied = resultingapplied;
433 static void
434 cpl_change_apply(CPLChange *change, ConnPointLine *probablynotcpl)
436 cpl_change_addremove(change,change->cpl,change->add,1);
439 static void
440 cpl_change_revert(CPLChange *change, ConnPointLine *probablynotcpl)
442 cpl_change_addremove(change,change->cpl,-(change->add),0);
445 static void cpl_change_free(CPLChange *change)
447 int i = ABS(change->add);
449 while (i--) {
450 if (change->cp[i]) {
451 del_connpoint(change->cp[i]);
454 g_free(change->cp); change->cp = (ConnectionPoint **)(0xDEADBEEF);
457 static ObjectChange *
458 cpl_create_change(ConnPointLine *cpl, int pos, int add)
460 CPLChange *change;
462 change = g_new0(CPLChange,1);
464 change->obj_change.apply = (ObjectChangeApplyFunc) cpl_change_apply;
465 change->obj_change.revert = (ObjectChangeRevertFunc) cpl_change_revert;
466 change->obj_change.free = (ObjectChangeFreeFunc) cpl_change_free;
468 change->cpl = cpl;
469 change->applied = 0;
470 change->add = add;
471 change->pos = pos;
473 change->cp = g_malloc0(sizeof(ConnectionPoint *) * ABS(add));
474 while (add-- > 0) {
475 change->cp[add] = new_connpoint(cpl->parent);
478 return (ObjectChange *)change;
481 ObjectChange *
482 connpointline_add_points(ConnPointLine *cpl,
483 Point *clickedpoint, int count)
485 int pos;
486 ObjectChange *change;
488 pos = cpl_get_pointbefore(cpl,clickedpoint);
489 change = cpl_create_change(cpl,pos,count);
491 change->apply(change, (DiaObject *)cpl);
492 return change;
496 ObjectChange *
497 connpointline_remove_points(ConnPointLine *cpl,
498 Point *clickedpoint, int count)
500 int pos;
501 ObjectChange *change;
503 pos = cpl_get_pointbefore(cpl,clickedpoint);
504 change = cpl_create_change(cpl,pos,-count);
506 change->apply(change, (DiaObject *)cpl);
507 return change;
510 int
511 connpointline_adjust_count(ConnPointLine *cpl,
512 int newcount, Point *where)
514 int oldcount,delta;
516 oldcount = cpl->num_connections;
518 if (newcount < 0) newcount = 0;
520 delta = newcount - oldcount;
521 if (delta != 0) {
522 ObjectChange *change;
523 /*g_message("going to adjust %d (to be %d)",delta,shouldbe);*/
525 if (delta > 0) {
526 change = connpointline_add_points(cpl, where, delta);
527 } else {
528 change = connpointline_remove_points(cpl, where, -delta);
530 if (change->free) change->free(change);
531 g_free(change); /* we don't really need this change object. */
535 return oldcount;