1 /*
2  * SADT diagram support for dia
3  * Copyright(C) 2000 Cyrille Chepelov
4  *
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.
9  *
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.
14  *
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.
18  */
19 
20 #include <config.h>
21 
22 #include <string.h>
23 #include "connpoint_line.h"
24 #include "connectionpoint.h"
25 #include "dia_xml.h"
26 
27 #define DEBUG_PARENT 0
28 #define DEBUG_ORDER 0
29 
30 static void cpl_reorder_connections(ConnPointLine *cpl);
31 
new_connpoint(DiaObject * obj)32 inline static ConnectionPoint *new_connpoint(DiaObject *obj)
33 {
34   ConnectionPoint *cp = g_new0(ConnectionPoint,1);
35   cp->object = obj;
36   return cp;
37 }
38 
del_connpoint(ConnectionPoint * cp)39 inline static void del_connpoint(ConnectionPoint *cp)
40 {
41   g_free(cp);
42 }
43 
44 static ConnectionPoint *
cpl_remove_connpoint(ConnPointLine * cpl,int pos)45 cpl_remove_connpoint(ConnPointLine *cpl,int pos)
46 {
47   ConnectionPoint *cp;
48 
49   g_assert (cpl->num_connections > 0);
50 
51   if (pos >= cpl->num_connections) {
52     pos = cpl->num_connections - 1;
53   } else {
54     while (pos < 0) pos += cpl->num_connections;
55   }
56 
57   cp = (ConnectionPoint *)(g_slist_nth(cpl->connections,pos)->data);
58   g_assert(cp);
59 
60   cpl->connections = g_slist_remove(cpl->connections,(gpointer)cp);
61   object_remove_connectionpoint(cpl->parent,cp);
62 
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;
68 }
69 
70 static void
cpl_add_connectionpoint_at(ConnPointLine * cpl,int pos,ConnectionPoint * cp)71 cpl_add_connectionpoint_at(ConnPointLine *cpl, int pos,ConnectionPoint *cp)
72 {
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;
86       }
87     }
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);
94   }
95   if (pos < 0) {
96     cpl->connections = g_slist_append(cpl->connections,(gpointer)cp);
97   }
98   else {
99     cpl->connections = g_slist_insert(cpl->connections,(gpointer)cp,pos);
100   }
101   cpl->num_connections++;
102 
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). */
107 }
108 
109 inline static void
cpl_add_connectionpoint(ConnPointLine * cpl,ConnectionPoint * cp)110 cpl_add_connectionpoint(ConnPointLine *cpl,ConnectionPoint *cp)
111 {
112   cpl_add_connectionpoint_at(cpl,-1,cp);
113 }
114 
115 ConnPointLine *
connpointline_create(DiaObject * parent,int num_connections)116 connpointline_create(DiaObject *parent, int num_connections)
117 {
118   ConnPointLine *cpl;
119   int i;
120 
121   cpl = g_new0(ConnPointLine,1);
122   cpl->parent = parent;
123 
124   cpl->connections = NULL;
125   for (i=0; i<num_connections; i++) {
126     cpl_add_connectionpoint(cpl,new_connpoint(cpl->parent));
127   }
128   cpl_reorder_connections(cpl);
129   return cpl;
130 }
131 
132 void
connpointline_destroy(ConnPointLine * cpl)133 connpointline_destroy(ConnPointLine *cpl)
134 {
135   while (cpl->num_connections > 0) del_connpoint(cpl_remove_connpoint(cpl,0));
136   g_free(cpl);
137 }
138 
139 static ConnPointLine *
cpl_inplacecreate(DiaObject * obj,int nc,int * realconncount)140 cpl_inplacecreate(DiaObject *obj, int nc, int *realconncount)
141 {
142   int i;
143   ConnPointLine *newcpl;
144   ConnectionPoint *cp;
145 
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;
150 
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);
156   }
157   newcpl->num_connections = nc;
158   return newcpl;
159 }
160 
161 ConnPointLine *
connpointline_load(DiaObject * obj,ObjectNode obj_node,const gchar * name,int default_nc,int * realconncount)162 connpointline_load(DiaObject *obj,ObjectNode obj_node,
163 		   const gchar *name, int default_nc,int *realconncount)
164 {
165   ConnPointLine *cpl;
166   int nc = default_nc;
167   AttributeNode attr;
168 
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);
173 
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);
180   */
181 }
182 
183 void
connpointline_save(ConnPointLine * cpl,ObjectNode obj_node,const gchar * name)184 connpointline_save(ConnPointLine *cpl,ObjectNode obj_node,
185 		   const gchar *name)
186 {
187   data_add_int(new_attribute(obj_node, name),cpl->num_connections);
188 }
189 
190 ConnPointLine *
connpointline_copy(DiaObject * newobj,ConnPointLine * cpl,int * realconncount)191 connpointline_copy(DiaObject *newobj,ConnPointLine *cpl, int *realconncount)
192 {
193   g_assert(realconncount);
194   return cpl_inplacecreate(newobj,cpl->num_connections,realconncount);
195 }
196 
connpointline_update(ConnPointLine * cpl)197 void connpointline_update(ConnPointLine *cpl)
198 {
199 
200 }
201 
202 void
connpointline_putonaline(ConnPointLine * cpl,Point * start,Point * end)203 connpointline_putonaline(ConnPointLine *cpl,Point *start,Point *end)
204 {
205   Point se_vector;
206   real se_len,pseudopoints;
207   int i;
208   GSList *elem;
209   gint dirs;
210 
211   point_copy(&se_vector, end);
212   point_sub(&se_vector, start);
213 
214   se_len = point_len(&se_vector);
215 
216   if (se_len > 0)
217     point_normalize(&se_vector);
218 
219   cpl->start = *start;
220   cpl->end = *end;
221 
222   if (fabs(se_vector.x) > fabs(se_vector.y))
223     dirs = DIR_NORTH|DIR_SOUTH;
224   else
225     dirs = DIR_EAST|DIR_WEST;
226 
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);
237   }
238 }
239 
240 
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
object_move_connection(DiaObject * obj,int sourcepos,int destpos)244 object_move_connection(DiaObject *obj,int sourcepos,int destpos)
245 {
246   ConnectionPoint *cp;
247   g_assert(destpos < sourcepos);
248   cp = obj->connections[sourcepos];
249 
250   memmove(&(obj->connections[destpos+1]),&(obj->connections[destpos]),
251 	  sizeof(ConnectionPoint *)*(sourcepos-destpos));
252   obj->connections[destpos] = cp;
253 }
254 
255 static int
object_find_connection(DiaObject * obj,ConnectionPoint * cp,int startpos)256 object_find_connection(DiaObject *obj, ConnectionPoint *cp, int startpos)
257 {
258   int i;
259   for (i = startpos; i < obj->num_connections; i++) {
260     if (obj->connections[i] == cp) return i;
261   }
262   return -1; /* should not happen */
263 }
264 
265 
266 #if DEBUG_ORDER
obj_find_connection(DiaObject * obj,ConnectionPoint * cp)267 static int obj_find_connection(DiaObject *obj,ConnectionPoint *cp)
268 {
269   int i;
270   for (i=0;i<obj->num_connections;i++)
271     if (cp == obj->connections[i]) return i;
272   return -1;
273 }
274 
275 
cpl_dump_connections(ConnPointLine * cpl)276 static void cpl_dump_connections(ConnPointLine *cpl)
277 {
278   DiaObject *obj = cpl->parent;
279   int i;
280   GSList *elem;
281   ConnectionPoint *cp;
282 
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));
290   }
291 }
292 #endif
293 
294 static void
cpl_reorder_connections(ConnPointLine * cpl)295 cpl_reorder_connections(ConnPointLine *cpl)
296 {
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.
302 
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).
308   */
309 
310   int i,j,first;
311   ConnectionPoint *cp;
312   GSList *elem;
313   DiaObject *obj;
314 
315   if (!cpl->connections) return;
316   #if DEBUG_ORDER
317   g_message("before cpl_reorder");
318   cpl_dump_connections(cpl);
319   #endif
320 
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;
328     }
329   }
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);
338     }
339   }
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
351 }
352 
353 
354 
355 int
connpointline_can_add_point(ConnPointLine * cpl,Point * clicked)356 connpointline_can_add_point(ConnPointLine *cpl, Point *clicked)
357 {
358   return 1;
359 }
360 
361 int
connpointline_can_remove_point(ConnPointLine * cpl,Point * clicked)362 connpointline_can_remove_point(ConnPointLine *cpl, Point *clicked)
363 {
364   if (cpl->num_connections <= 1)
365     return 0;
366   else
367     return 1;
368 }
369 
370 static int
cpl_get_pointbefore(ConnPointLine * cpl,Point * clickedpoint)371 cpl_get_pointbefore(ConnPointLine *cpl, Point *clickedpoint)
372 {
373   int i, pos = -1;
374   GSList *elem;
375   ConnectionPoint *cp;
376   real dist = 65536.0;
377   real tmpdist;
378 
379   if (!clickedpoint) return 0;
380 
381   for (i=0,elem=cpl->connections;
382        i<cpl->num_connections;
383        i++,elem=g_slist_next(elem)) {
384     cp = (ConnectionPoint *)(elem->data);
385 
386     tmpdist = distance_point_point(&cp->pos,clickedpoint);
387     if (tmpdist < dist) {
388       dist = tmpdist;
389       pos = i;
390     }
391   }
392   tmpdist = distance_point_point(&cpl->end,clickedpoint);
393   if (tmpdist < dist) {
394     /*dist = tmpdist; */
395     pos = -1;
396   }
397   return pos;
398 }
399 
400 typedef struct {
401   ObjectChange obj_change;
402 
403   int add; /* How much to add or remove */
404   int applied; /* 1 if the event has been applied. */
405 
406   ConnPointLine *cpl;
407   int pos; /* Position where the change happened. */
408   ConnectionPoint **cp; /* The removed connection point. */
409 } CPLChange;
410 
411 static void
cpl_change_addremove(CPLChange * change,ConnPointLine * cpl,int action,int resultingapplied)412 cpl_change_addremove(CPLChange *change, ConnPointLine *cpl,
413 		     int action, int resultingapplied)
414 {
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;
420       }
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);
425       }
426     }
427   } else {
428     g_warning("cpl_change_addremove(): null action !");
429   }
430   change->applied = resultingapplied;
431 }
432 
433 static void
cpl_change_apply(CPLChange * change,ConnPointLine * probablynotcpl)434 cpl_change_apply(CPLChange *change, ConnPointLine *probablynotcpl)
435 {
436   cpl_change_addremove(change,change->cpl,change->add,1);
437 }
438 
439 static void
cpl_change_revert(CPLChange * change,ConnPointLine * probablynotcpl)440 cpl_change_revert(CPLChange *change, ConnPointLine *probablynotcpl)
441 {
442   cpl_change_addremove(change,change->cpl,-(change->add),0);
443 }
444 
cpl_change_free(CPLChange * change)445 static void cpl_change_free(CPLChange *change)
446 {
447   int i = ABS(change->add);
448 
449   while (i--) {
450     if (change->cp[i]) {
451       del_connpoint(change->cp[i]);
452     }
453   }
454   g_free(change->cp); change->cp = (ConnectionPoint **)(0xDEADBEEF);
455 }
456 
457 static ObjectChange *
cpl_create_change(ConnPointLine * cpl,int pos,int add)458 cpl_create_change(ConnPointLine *cpl, int pos, int add)
459 {
460   CPLChange *change;
461 
462   change = g_new0(CPLChange,1);
463 
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;
467 
468   change->cpl = cpl;
469   change->applied = 0;
470   change->add = add;
471   change->pos = pos;
472 
473   change->cp = g_malloc0(sizeof(ConnectionPoint *) * ABS(add));
474   while (add-- > 0) {
475     change->cp[add] = new_connpoint(cpl->parent);
476   }
477 
478   return (ObjectChange *)change;
479 }
480 
481 ObjectChange *
connpointline_add_points(ConnPointLine * cpl,Point * clickedpoint,int count)482 connpointline_add_points(ConnPointLine *cpl,
483 			  Point *clickedpoint, int count)
484 {
485   int pos;
486   ObjectChange *change;
487 
488   pos = cpl_get_pointbefore(cpl,clickedpoint);
489   change = cpl_create_change(cpl,pos,count);
490 
491   change->apply(change, (DiaObject *)cpl);
492   return change;
493 }
494 
495 
496 ObjectChange *
connpointline_remove_points(ConnPointLine * cpl,Point * clickedpoint,int count)497 connpointline_remove_points(ConnPointLine *cpl,
498 			     Point *clickedpoint, int count)
499 {
500   int pos;
501   ObjectChange *change;
502 
503   pos = cpl_get_pointbefore(cpl,clickedpoint);
504   change = cpl_create_change(cpl,pos,-count);
505 
506   change->apply(change, (DiaObject *)cpl);
507   return change;
508 }
509 
510 int
connpointline_adjust_count(ConnPointLine * cpl,int newcount,Point * where)511 connpointline_adjust_count(ConnPointLine *cpl,
512 			   int newcount, Point *where)
513 {
514   int oldcount,delta;
515 
516   oldcount = cpl->num_connections;
517 
518   if (newcount < 0) newcount = 0;
519 
520   delta = newcount - oldcount;
521   if (delta != 0) {
522     ObjectChange *change;
523     /*g_message("going to adjust %d (to be %d)",delta,shouldbe);*/
524 
525     if (delta > 0) {
526       change = connpointline_add_points(cpl, where, delta);
527     } else {
528       change = connpointline_remove_points(cpl, where, -delta);
529     }
530     if (change->free) change->free(change);
531     g_free(change); /* we don't really need this change object. */
532   }
533 
534 
535   return oldcount;
536 }
537 
538