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