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 | |
16 | static char DRName[] = "_AG_pending" ; |
17 | |
18 | typedef 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 */ |
24 | typedef 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 | |
32 | typedef struct { |
33 | Agrec_t h; |
34 | struct { |
35 | Dict_t *g, *n, *e; |
36 | } ins, mod, del; |
37 | } pendingset_t; |
38 | |
39 | static 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 | |
49 | static 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 | |
60 | static 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 | |
70 | static 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 | |
132 | static IDTYPE genkey(Agobj_t * obj) |
133 | { |
134 | return obj->tag.id; |
135 | } |
136 | |
137 | static 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 | |
146 | static 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 | |
167 | static 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 | |
184 | static 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 | |
193 | void 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 | |
236 | static 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 | |
263 | static 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 | |
286 | int 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 | |