1/* $Id$ $Revision$ */
2/* vim:set shiftwidth=4 ts=8: */
3
4/*************************************************************************
5 * Copyright (c) 2011 AT&T Intellectual Property
6 * All rights reserved. This program and the accompanying materials
7 * are made available under the terms of the Eclipse Public License v1.0
8 * which accompanies this distribution, and is available at
9 * http://www.eclipse.org/legal/epl-v10.html
10 *
11 * Contributors: See CVS logs. Details at http://www.graphviz.org/
12 *************************************************************************/
13
14#include <cghdr.h>
15
16static char DRName[] = "_AG_pending";
17
18typedef struct symlist_s {
19 Agsym_t *sym;
20 struct symlist_s *link;
21} symlist_t;
22
23/* this record describes one pending callback on one object */
24typedef struct {
25 Dtlink_t link;
26 IDTYPE key; /* universal key for main or sub-object */
27 Agraph_t *g;
28 Agobj_t *obj;
29 symlist_t *symlist; /* attributes involved */
30} pending_cb_t;
31
32typedef struct {
33 Agrec_t h;
34 struct {
35 Dict_t *g, *n, *e;
36 } ins, mod, del;
37} pendingset_t;
38
39static void free_symlist(pending_cb_t * pcb)
40{
41 symlist_t *s, *t;
42
43 for (s = pcb->symlist; s; s = t) {
44 t = s->link;
45 agfree(pcb->g, s);
46 }
47}
48
49static void freef(Dict_t * dict, void *ptr, Dtdisc_t * disc)
50{
51 pending_cb_t *pcb;
52
53 NOTUSED(dict);
54 NOTUSED(disc);
55 pcb = ptr;
56 free_symlist(pcb);
57 agfree(pcb->g, pcb);
58}
59
60static Dtdisc_t Disc = {
61 offsetof(pending_cb_t, key), /* sort by 'key' */
62 sizeof(uint64_t),
63 0, /* link offset */
64 NIL(Dtmake_f),
65 freef,
66 NIL(Dtcompar_f),
67 NIL(Dthash_f)
68};
69
70static Dict_t *dictof(pendingset_t * ds, Agobj_t * obj, int kind)
71{
72 Dict_t **dict_ref = NIL(Dict_t **);
73
74 dict_ref = 0;
75 switch (AGTYPE(obj)) {
76 case AGRAPH:
77 switch (kind) {
78 case CB_INITIALIZE:
79 dict_ref = &(ds->ins.g);
80 break;
81 case CB_UPDATE:
82 dict_ref = &(ds->mod.g);
83 break;
84 case CB_DELETION:
85 dict_ref = &(ds->del.g);
86 break;
87 default:
88 break;
89 }
90 break;
91 case AGNODE:
92 switch (kind) {
93 case CB_INITIALIZE:
94 dict_ref = &(ds->ins.n);
95 break;
96 case CB_UPDATE:
97 dict_ref = &(ds->mod.n);
98 break;
99 case CB_DELETION:
100 dict_ref = &(ds->del.n);
101 break;
102 default:
103 break;
104 }
105 break;
106 case AGEDGE:
107 switch (kind) {
108 case CB_INITIALIZE:
109 dict_ref = &(ds->ins.e);
110 break;
111 case CB_UPDATE:
112 dict_ref = &(ds->mod.e);
113 break;
114 case CB_DELETION:
115 dict_ref = &(ds->del.e);
116 break;
117 default:
118 break;
119 }
120 break;
121 default:
122 break;
123 }
124
125 if (dict_ref == 0)
126 agerr(AGERR, "pend dictof a bad object");
127 if (*dict_ref == NIL(Dict_t *))
128 *dict_ref = agdtopen(agraphof(obj), &Disc, Dttree);
129 return *dict_ref;
130}
131
132static IDTYPE genkey(Agobj_t * obj)
133{
134 return obj->tag.id;
135}
136
137static pending_cb_t *lookup(Dict_t * dict, Agobj_t * obj)
138{
139 pending_cb_t key, *rv;
140
141 key.key = genkey(obj);
142 rv = (pending_cb_t *) dtsearch(dict, &key);
143 return rv;
144}
145
146static void record_sym(Agobj_t * obj, pending_cb_t * handle,
147 Agsym_t * optsym)
148{
149 symlist_t *sym, *nsym, *psym;
150
151 psym = NIL(symlist_t *);
152 for (sym = handle->symlist; sym; psym = sym, sym = sym->link) {
153 if (sym->sym == optsym)
154 break;
155 if (sym == NIL(symlist_t *)) {
156 nsym = agalloc(agraphof(obj), sizeof(symlist_t));
157 nsym->sym = optsym;
158 if (psym)
159 psym->link = nsym;
160 else
161 handle->symlist = nsym;
162 }
163 /* else we already have a callback registered */
164 }
165}
166
167static pending_cb_t *insert(Dict_t * dict, Agraph_t * g, Agobj_t * obj,
168 Agsym_t * optsym)
169{
170 pending_cb_t *handle;
171 handle = agalloc(agraphof(obj), sizeof(pending_cb_t));
172 handle->obj = obj;
173 handle->key = genkey(obj);
174 handle->g = g;
175 if (optsym) {
176 handle->symlist =
177 (symlist_t *) agalloc(handle->g, sizeof(symlist_t));
178 handle->symlist->sym = optsym;
179 }
180 dtinsert(dict, handle);
181 return handle;
182}
183
184static void purge(Dict_t * dict, Agobj_t * obj)
185{
186 pending_cb_t *handle;
187
188 if ((handle = lookup(dict, obj))) {
189 dtdelete(dict, handle);
190 }
191}
192
193void agrecord_callback(Agraph_t * g, Agobj_t * obj, int kind,
194 Agsym_t * optsym)
195{
196 pendingset_t *pending;
197 Dict_t *dict;
198 pending_cb_t *handle;
199
200 pending =
201 (pendingset_t *) agbindrec(g, DRName, sizeof(pendingset_t), FALSE);
202
203 switch (kind) {
204 case CB_INITIALIZE:
205 assert(lookup(dictof(pending, obj, CB_UPDATE), obj) == 0);
206 assert(lookup(dictof(pending, obj, CB_DELETION), obj) == 0);
207 dict = dictof(pending, obj, CB_INITIALIZE);
208 handle = lookup(dict, obj);
209 if (handle == 0)
210 handle = insert(dict, g, obj, optsym);
211 break;
212 case CB_UPDATE:
213 if (lookup(dictof(pending, obj, CB_INITIALIZE), obj))
214 break;
215 if (lookup(dictof(pending, obj, CB_DELETION), obj))
216 break;
217 dict = dictof(pending, obj, CB_UPDATE);
218 handle = lookup(dict, obj);
219 if (handle == 0)
220 handle = insert(dict, g, obj, optsym);
221 record_sym(obj, handle, optsym);
222 break;
223 case CB_DELETION:
224 purge(dictof(pending, obj, CB_INITIALIZE), obj);
225 purge(dictof(pending, obj, CB_UPDATE), obj);
226 dict = dictof(pending, obj, CB_DELETION);
227 handle = lookup(dict, obj);
228 if (handle == 0)
229 handle = insert(dict, g, obj, optsym);
230 break;
231 default:
232 agerr(AGERR,"agrecord_callback of a bad object");
233 }
234}
235
236static void cb(Dict_t * dict, int callback_kind)
237{
238 pending_cb_t *pcb;
239 Agraph_t *g;
240 symlist_t *psym;
241 Agcbstack_t *stack;
242
243 if (dict)
244 while ((pcb = (pending_cb_t *) dtfirst(dict))) {
245 g = pcb->g;
246 stack = g->clos->cb;
247 switch (callback_kind) {
248 case CB_INITIALIZE:
249 aginitcb(g, pcb->obj, stack);
250 break;
251 case CB_UPDATE:
252 for (psym = pcb->symlist; psym; psym = psym->link)
253 agupdcb(g, pcb->obj, psym->sym, stack);
254 break;
255 case CB_DELETION:
256 agdelcb(g, pcb->obj, stack);
257 break;
258 }
259 dtdelete(dict, pcb);
260 }
261}
262
263static void agrelease_callbacks(Agraph_t * g)
264{
265 pendingset_t *pending;
266 if (NOT(g->clos->callbacks_enabled)) {
267 g->clos->callbacks_enabled = TRUE;
268 pending =
269 (pendingset_t *) agbindrec(g, DRName, sizeof(pendingset_t),
270 FALSE);
271 /* this destroys objects in the opposite of their order of creation */
272 cb(pending->ins.g, CB_INITIALIZE);
273 cb(pending->ins.n, CB_INITIALIZE);
274 cb(pending->ins.e, CB_INITIALIZE);
275
276 cb(pending->mod.g, CB_UPDATE);
277 cb(pending->mod.n, CB_UPDATE);
278 cb(pending->mod.e, CB_UPDATE);
279
280 cb(pending->del.e, CB_DELETION);
281 cb(pending->del.n, CB_DELETION);
282 cb(pending->del.g, CB_DELETION);
283 }
284}
285
286int agcallbacks(Agraph_t * g, int flag)
287{
288 if (flag && NOT(g->clos->callbacks_enabled))
289 agrelease_callbacks(g);
290 if (g->clos->callbacks_enabled) {
291 g->clos->callbacks_enabled = flag;
292 return TRUE;
293 }
294 g->clos->callbacks_enabled = flag;
295 return FALSE;
296}
297