--- /dev/null
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+* This software is part of the graphviz package *
+* http://www.graphviz.org/ *
+* *
+* Copyright (c) 1994-2004 AT&T Corp. *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Corp. *
+* *
+* Information and Software Systems Research *
+* AT&T Research, Florham Park NJ *
+**********************************************************/
+
+#include <cghdr.h>
+
+static char DRName[] = "_AG_pending";
+
+typedef struct symlist_s {
+ Agsym_t *sym;
+ struct symlist_s *link;
+} symlist_t;
+
+/* this record describes one pending callback on one object */
+typedef struct {
+ Dtlink_t link;
+ unsigned long key; /* universal key for main or sub-object */
+ Agraph_t *g;
+ Agobj_t *obj;
+ symlist_t *symlist; /* attributes involved */
+} pending_cb_t;
+
+typedef struct {
+ Agrec_t h;
+ struct {
+ Dict_t *g, *n, *e;
+ } ins, mod, del;
+} pendingset_t;
+
+static void free_symlist(pending_cb_t * pcb)
+{
+ symlist_t *s, *t;
+
+ for (s = pcb->symlist; s; s = t) {
+ t = s->link;
+ agfree(pcb->g, s);
+ }
+}
+
+static void freef(Dict_t * dict, void *ptr, Dtdisc_t * disc)
+{
+ pending_cb_t *pcb;
+
+ NOTUSED(dict);
+ NOTUSED(disc);
+ pcb = ptr;
+ free_symlist(pcb);
+ agfree(pcb->g, pcb);
+}
+
+static Dtdisc_t Disc = {
+ offsetof(pending_cb_t, key), /* sort by 'key' */
+ sizeof(unsigned long),
+ 0, /* link offset */
+ NIL(Dtmake_f),
+ freef,
+ NIL(Dtcompar_f),
+ NIL(Dthash_f)
+};
+
+static Dict_t *dictof(pendingset_t * ds, Agobj_t * obj, int kind)
+{
+ Dict_t **dict_ref = NIL(Dict_t **);
+
+ dict_ref = 0;
+ switch (AGTYPE(obj)) {
+ case AGRAPH:
+ switch (kind) {
+ case CB_INITIALIZE:
+ dict_ref = &(ds->ins.g);
+ break;
+ case CB_UPDATE:
+ dict_ref = &(ds->mod.g);
+ break;
+ case CB_DELETION:
+ dict_ref = &(ds->del.g);
+ break;
+ default:
+ break;
+ }
+ break;
+ case AGNODE:
+ switch (kind) {
+ case CB_INITIALIZE:
+ dict_ref = &(ds->ins.n);
+ break;
+ case CB_UPDATE:
+ dict_ref = &(ds->mod.n);
+ break;
+ case CB_DELETION:
+ dict_ref = &(ds->del.n);
+ break;
+ default:
+ break;
+ }
+ break;
+ case AGEDGE:
+ switch (kind) {
+ case CB_INITIALIZE:
+ dict_ref = &(ds->ins.e);
+ break;
+ case CB_UPDATE:
+ dict_ref = &(ds->mod.e);
+ break;
+ case CB_DELETION:
+ dict_ref = &(ds->del.e);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (dict_ref == 0)
+ agerr(AGERR, "pend dictof a bad object");
+ if (*dict_ref == NIL(Dict_t *))
+ *dict_ref = agdtopen(agraphof(obj), &Disc, Dttree);
+ return *dict_ref;
+}
+
+static unsigned long genkey(Agobj_t * obj)
+{
+ return obj->tag.id;
+}
+
+static pending_cb_t *lookup(Dict_t * dict, Agobj_t * obj)
+{
+ pending_cb_t key, *rv;
+
+ key.key = genkey(obj);
+ rv = (pending_cb_t *) dtsearch(dict, &key);
+ return rv;
+}
+
+static void record_sym(Agobj_t * obj, pending_cb_t * handle,
+ Agsym_t * optsym)
+{
+ symlist_t *sym, *nsym, *psym;
+
+ psym = NIL(symlist_t *);
+ for (sym = handle->symlist; sym; psym = sym, sym = sym->link) {
+ if (sym->sym == optsym)
+ break;
+ if (sym == NIL(symlist_t *)) {
+ nsym = agalloc(agraphof(obj), sizeof(symlist_t));
+ nsym->sym = optsym;
+ if (psym)
+ psym->link = nsym;
+ else
+ handle->symlist = nsym;
+ }
+ /* else we already have a callback registered */
+ }
+}
+
+static pending_cb_t *insert(Dict_t * dict, Agraph_t * g, Agobj_t * obj,
+ Agsym_t * optsym)
+{
+ pending_cb_t *handle;
+ handle = agalloc(agraphof(obj), sizeof(pending_cb_t));
+ handle->obj = obj;
+ handle->key = genkey(obj);
+ handle->g = g;
+ if (optsym) {
+ handle->symlist =
+ (symlist_t *) agalloc(handle->g, sizeof(symlist_t));
+ handle->symlist->sym = optsym;
+ }
+ dtinsert(dict, handle);
+ return handle;
+}
+
+static void purge(Dict_t * dict, Agobj_t * obj)
+{
+ pending_cb_t *handle;
+
+ if ((handle = lookup(dict, obj))) {
+ dtdelete(dict, handle);
+ }
+}
+
+void agrecord_callback(Agraph_t * g, Agobj_t * obj, int kind,
+ Agsym_t * optsym)
+{
+ pendingset_t *pending;
+ Dict_t *dict;
+ pending_cb_t *handle;
+
+ pending =
+ (pendingset_t *) agbindrec(g, DRName, sizeof(pendingset_t), FALSE);
+
+ switch (kind) {
+ case CB_INITIALIZE:
+ assert(lookup(dictof(pending, obj, CB_UPDATE), obj) == 0);
+ assert(lookup(dictof(pending, obj, CB_DELETION), obj) == 0);
+ dict = dictof(pending, obj, CB_INITIALIZE);
+ handle = lookup(dict, obj);
+ if (handle == 0)
+ handle = insert(dict, g, obj, optsym);
+ break;
+ case CB_UPDATE:
+ if (lookup(dictof(pending, obj, CB_INITIALIZE), obj))
+ break;
+ if (lookup(dictof(pending, obj, CB_DELETION), obj))
+ break;
+ dict = dictof(pending, obj, CB_UPDATE);
+ handle = lookup(dict, obj);
+ if (handle == 0)
+ handle = insert(dict, g, obj, optsym);
+ record_sym(obj, handle, optsym);
+ break;
+ case CB_DELETION:
+ purge(dictof(pending, obj, CB_INITIALIZE), obj);
+ purge(dictof(pending, obj, CB_UPDATE), obj);
+ dict = dictof(pending, obj, CB_DELETION);
+ handle = lookup(dict, obj);
+ if (handle == 0)
+ handle = insert(dict, g, obj, optsym);
+ break;
+ default:
+ agerr(AGERR,"agrecord_callback of a bad object");
+ }
+}
+
+static void cb(Dict_t * dict, int callback_kind)
+{
+ pending_cb_t *pcb;
+ Agraph_t *g;
+ symlist_t *psym;
+ Agcbstack_t *stack;
+
+ if (dict)
+ while ((pcb = (pending_cb_t *) dtfirst(dict))) {
+ g = pcb->g;
+ stack = g->clos->cb;
+ switch (callback_kind) {
+ case CB_INITIALIZE:
+ aginitcb(g, pcb->obj, stack);
+ break;
+ case CB_UPDATE:
+ for (psym = pcb->symlist; psym; psym = psym->link)
+ agupdcb(g, pcb->obj, psym->sym, stack);
+ break;
+ case CB_DELETION:
+ agdelcb(g, pcb->obj, stack);
+ break;
+ }
+ dtdelete(dict, pcb);
+ }
+}
+
+static void agrelease_callbacks(Agraph_t * g)
+{
+ pendingset_t *pending;
+ if (NOT(g->clos->callbacks_enabled)) {
+ g->clos->callbacks_enabled = TRUE;
+ pending =
+ (pendingset_t *) agbindrec(g, DRName, sizeof(pendingset_t),
+ FALSE);
+ /* this destroys objects in the opposite of their order of creation */
+ cb(pending->ins.g, CB_INITIALIZE);
+ cb(pending->ins.n, CB_INITIALIZE);
+ cb(pending->ins.e, CB_INITIALIZE);
+
+ cb(pending->mod.g, CB_UPDATE);
+ cb(pending->mod.n, CB_UPDATE);
+ cb(pending->mod.e, CB_UPDATE);
+
+ cb(pending->del.e, CB_DELETION);
+ cb(pending->del.n, CB_DELETION);
+ cb(pending->del.g, CB_DELETION);
+ }
+}
+
+int agcallbacks(Agraph_t * g, int flag)
+{
+ if (flag && NOT(g->clos->callbacks_enabled))
+ agrelease_callbacks(g);
+ if (g->clos->callbacks_enabled) {
+ g->clos->callbacks_enabled = flag;
+ return TRUE;
+ }
+ g->clos->callbacks_enabled = flag;
+ return FALSE;
+}
--- /dev/null
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+* This software is part of the graphviz package *
+* http://www.graphviz.org/ *
+* *
+* Copyright (c) 1994-2004 AT&T Corp. *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Corp. *
+* *
+* Information and Software Systems Research *
+* AT&T Research, Florham Park NJ *
+**********************************************************/
+
+#include <cghdr.h>
+
+/*
+ * run time records
+ */
+
+static void set_data(Agobj_t * obj, Agrec_t * data, int mtflock)
+{
+ Agedge_t *e;
+
+ obj->data = data;
+ obj->tag.mtflock = mtflock;
+ if ((AGTYPE(obj) == AGINEDGE) || (AGTYPE(obj) == AGOUTEDGE)) {
+ e = agopp((Agedge_t *) obj);
+ AGDATA(e) = data;
+ e->base.tag.mtflock = mtflock;
+ }
+}
+
+/* find record in circular list and do optional move-to-front */
+Agrec_t *aggetrec(void *obj, char *name, int mtf)
+{
+ Agobj_t *hdr;
+ Agrec_t *d, *first;
+
+ hdr = (Agobj_t *) obj;
+ first = d = hdr->data;
+ while (d) {
+ if ((d->name == name) || streq(name, d->name))
+ break;
+ d = d->next;
+ if (d == first) {
+ d = NIL(Agrec_t *);
+ break;
+ }
+ }
+ if (d) {
+ if (hdr->tag.mtflock) {
+ if (mtf && (hdr->data != d))
+ agerr(AGERR, "move to front lock inconsistency");
+ } else {
+ if ((d != first) || (mtf != hdr->tag.mtflock))
+ set_data(hdr, d, mtf); /* Always optimize */
+ }
+ }
+ return d;
+}
+
+/* insert the record in data list of this object (only) */
+static void objputrec(Agraph_t * g, Agobj_t * obj, void *arg)
+{
+ Agrec_t *firstrec, *newrec;
+
+ newrec = arg;
+ firstrec = obj->data;
+ if (firstrec == NIL(Agrec_t *))
+ newrec->next = newrec; /* 0 elts */
+ else {
+ if (firstrec->next == firstrec) {
+ firstrec->next = newrec; /* 1 elt */
+ newrec->next = firstrec;
+ } else {
+ newrec->next = firstrec->next;
+ firstrec->next = newrec;
+ }
+ }
+ if (NOT(obj->tag.mtflock))
+ set_data(obj, newrec, FALSE);
+}
+
+/* attach a new record of the given size to the object.
+ */
+void *agbindrec(void *arg_obj, char *recname, unsigned int recsize,
+ int mtf)
+{
+ Agraph_t *g;
+ Agobj_t *obj;
+ Agrec_t *rec;
+
+ obj = (Agobj_t *) arg_obj;
+ g = agraphof(obj);
+ rec = aggetrec(obj, recname, FALSE);
+ if ((rec == NIL(Agrec_t *)) && (recsize > 0)) {
+ rec = (Agrec_t *) agalloc(g, (int) recsize);
+ rec->name = agstrdup(g, recname);
+ switch (obj->tag.objtype) {
+ case AGRAPH:
+ objputrec(g, obj, rec);
+ break;
+ case AGNODE:
+ objputrec(g, obj, rec);
+ break;
+ case AGINEDGE:
+ case AGOUTEDGE:
+ objputrec(g, obj, rec);
+ break;
+ }
+ }
+ if (mtf)
+ aggetrec(arg_obj, recname, TRUE);
+ return (void *) rec;
+}
+
+
+/* if obj points to rec, move its data pointer. break any mtf lock(?) */
+static void objdelrec(Agraph_t * g, Agobj_t * obj, void *arg_rec)
+{
+ Agrec_t *rec = (Agrec_t *) arg_rec, *newrec;
+ if (obj->data == rec) {
+ if (rec->next == rec)
+ newrec = NIL(Agrec_t *);
+ else
+ newrec = rec->next;
+ set_data(obj, newrec, FALSE);
+ }
+}
+
+/* delete a record from a circular data list */
+static void listdelrec(Agobj_t * obj, Agrec_t * rec)
+{
+ Agrec_t *prev;
+
+ prev = obj->data;
+ while (prev->next != rec) {
+ prev = prev->next;
+ assert(prev != obj->data);
+ }
+ /* following is a harmless no-op if the list is trivial */
+ prev->next = rec->next;
+}
+
+int agdelrec(void *arg_obj, char *name)
+{
+ Agobj_t *obj;
+ Agrec_t *rec;
+ Agraph_t *g;
+
+ obj = (Agobj_t *) arg_obj;
+ g = agraphof(obj);
+ rec = aggetrec(obj, name, FALSE);
+ if (rec) {
+ listdelrec(obj, rec); /* zap it from the circular list */
+ switch (obj->tag.objtype) { /* refresh any stale pointers */
+ case AGRAPH:
+ objdelrec(g, obj, rec);
+ break;
+ case AGNODE:
+ case AGINEDGE:
+ case AGOUTEDGE:
+ agapply(agroot(g), obj, objdelrec, rec, FALSE);
+ break;
+ }
+ agstrfree(g, rec->name);
+ agfree(g, rec);
+ return SUCCESS;
+ } else
+ return FAILURE;
+}
+
+static void simple_delrec(Agraph_t * g, Agobj_t * obj, void *rec_name)
+{
+ agdelrec(obj, rec_name);
+}
+
+#ifdef OLD
+void agclean(Agraph_t * g, char *graphdata, char *nodedata, char *edgedata)
+{
+ Agnode_t *n;
+ Agedge_t *e;
+
+ if (nodedata || edgedata) {
+ for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
+ if (edgedata)
+ for (e = agfstout(n); e; e = agnxtout(g, e)) {
+ agdelrec(e, edgedata);
+ }
+ if (nodedata)
+ agdelrec(n, nodedata);
+ }
+ }
+ agdelrec(g, graphdata);
+}
+#endif
+
+void aginit(Agraph_t * g, int kind, char *rec_name, int rec_size, int mtf)
+{
+ Agnode_t *n;
+ Agedge_t *e;
+
+ switch (kind) {
+ case AGRAPH:
+ agbindrec(g, rec_name, rec_size, mtf);
+ break;
+ case AGNODE:
+ case AGOUTEDGE:
+ case AGINEDGE:
+ for (n = agfstnode(g); n; n = agnxtnode(g, n))
+ if (kind == AGNODE)
+ agbindrec(n, rec_name, rec_size, mtf);
+ else {
+ for (e = agfstout(g, n); e; e = agnxtout(g, e))
+ agbindrec(e, rec_name, rec_size, mtf);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void agclean(Agraph_t * g, int kind, char *rec_name)
+{
+ Agnode_t *n;
+ Agedge_t *e;
+
+ switch (kind) {
+ case AGRAPH:
+ agapply(g, (Agobj_t *) g, simple_delrec, rec_name, TRUE);
+ break;
+ case AGNODE:
+ case AGOUTEDGE:
+ case AGINEDGE:
+ for (n = agfstnode(g); n; n = agnxtnode(g, n))
+ if (kind == AGNODE)
+ agdelrec(n, rec_name);
+ else {
+ for (e = agfstout(g, n); e; e = agnxtout(g, e))
+ agdelrec(e, rec_name);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void agrecclose(Agobj_t * obj)
+{
+ Agraph_t *g;
+ Agrec_t *rec, *nrec;
+
+ g = agraphof(obj);
+ if ((rec = obj->data)) {
+ do {
+ nrec = rec->next;
+ agstrfree(g, rec->name);
+ agfree(g, rec);
+ rec = nrec;
+ } while (rec != obj->data);
+ }
+ obj->data = NIL(Agrec_t *);
+}
--- /dev/null
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+* This software is part of the graphviz package *
+* http://www.graphviz.org/ *
+* *
+* Copyright (c) 1994-2004 AT&T Corp. *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Corp. *
+* *
+* Information and Software Systems Research *
+* AT&T Research, Florham Park NJ *
+**********************************************************/
+
+#include <cghdr.h>
+
+#ifdef DMALLOC
+#include "dmalloc.h"
+#endif
+
+/*
+ * reference counted strings.
+ */
+
+static unsigned long HTML_BIT; /* msbit of unsigned long */
+static unsigned long CNT_BITS; /* complement of HTML_BIT */
+
+typedef struct refstr_t {
+ Dtlink_t link;
+ unsigned long refcnt;
+ char *s;
+ char store[1]; /* this is actually a dynamic array */
+} refstr_t;
+
+static Dtdisc_t Refstrdisc = {
+ offsetof(refstr_t, s), /* key */
+ -1, /* size */
+ 0, /* link offset */
+ NIL(Dtmake_f),
+ agdictobjfree,
+ NIL(Dtcompar_f),
+ NIL(Dthash_f),
+ agdictobjmem,
+ NIL(Dtevent_f)
+};
+
+static Dict_t *Refdict_default;
+
+/* refdict:
+ * Return the string dictionary associated with g.
+ * If necessary, create it.
+ * As a side-effect, set html masks. This assumes 8-bit bytes.
+ */
+static Dict_t *refdict(Agraph_t * g)
+{
+ Dict_t **dictref;
+
+ if (g)
+ dictref = &(g->clos->strdict);
+ else
+ dictref = &Refdict_default;
+ if (*dictref == NIL(Dict_t *)) {
+ *dictref = agdtopen(g, &Refstrdisc, Dttree);
+ HTML_BIT = ((unsigned int) 1) << (sizeof(unsigned int) * 8 - 1);
+ CNT_BITS = ~HTML_BIT;
+ }
+ return *dictref;
+}
+
+void agstrclose(Agraph_t * g)
+{
+ agdtclose(g, refdict(g));
+}
+
+static refstr_t *refsymbind(Dict_t * strdict, char *s)
+{
+ refstr_t key, *r;
+ key.s = s;
+ r = (refstr_t *) dtsearch(strdict, &key);
+ return r;
+}
+
+static char *refstrbind(Dict_t * strdict, char *s)
+{
+ refstr_t *r;
+ r = refsymbind(strdict, s);
+ if (r)
+ return r->s;
+ else
+ return NIL(char *);
+}
+
+char *agstrbind(Agraph_t * g, char *s)
+{
+ return refstrbind(refdict(g), s);
+}
+
+char *agstrdup(Agraph_t * g, char *s)
+{
+ refstr_t *r;
+ Dict_t *strdict;
+ size_t sz;
+
+ if (s == NIL(char *))
+ return NIL(char *);
+ strdict = refdict(g);
+ r = refsymbind(strdict, s);
+ if (r)
+ r->refcnt++;
+ else {
+ sz = sizeof(refstr_t) + strlen(s);
+ if (g)
+ r = (refstr_t *) agalloc(g, sz);
+ else
+ r = (refstr_t *) malloc(sz);
+ r->refcnt = 1;
+ strcpy(r->store, s);
+ r->s = r->store;
+ dtinsert(strdict, r);
+ }
+ return r->s;
+}
+
+char *agstrdup_html(Agraph_t * g, char *s)
+{
+ refstr_t *r;
+ Dict_t *strdict;
+ size_t sz;
+
+ if (s == NIL(char *))
+ return NIL(char *);
+ strdict = refdict(g);
+ r = refsymbind(strdict, s);
+ if (r)
+ r->refcnt++;
+ else {
+ sz = sizeof(refstr_t) + strlen(s);
+ if (g)
+ r = (refstr_t *) agalloc(g, sz);
+ else
+ r = (refstr_t *) malloc(sz);
+ r->refcnt = 1 | HTML_BIT;
+ strcpy(r->store, s);
+ r->s = r->store;
+ dtinsert(strdict, r);
+ }
+ return r->s;
+}
+
+int agstrfree(Agraph_t * g, char *s)
+{
+ refstr_t *r;
+ Dict_t *strdict;
+
+ if (s == NIL(char *))
+ return FAILURE;
+
+ strdict = refdict(g);
+ r = refsymbind(strdict, s);
+ if (r && (r->s == s)) {
+ r->refcnt--;
+ if ((r->refcnt && CNT_BITS) == 0) {
+ agdtdelete(g, strdict, r);
+ /*
+ if (g) agfree(g,r);
+ else free(r);
+ */
+ }
+ }
+ if (r == NIL(refstr_t *))
+ return FAILURE;
+ return SUCCESS;
+}
+
+/* aghtmlstr:
+ * Return true if s is an HTML string.
+ * We assume s points to the datafield store[0] of a refstr.
+ */
+int aghtmlstr(char *s)
+{
+ refstr_t *key;
+
+ if (s == NULL)
+ return 0;
+ key = (refstr_t *) (s - offsetof(refstr_t, store[0]));
+ return (key->refcnt & HTML_BIT);
+}
+
+#ifdef DEBUG
+static int refstrprint(Dict_t * dict, void *ptr, void *user)
+{
+ refstr_t *r;
+
+ NOTUSED(dict);
+ r = ptr;
+ NOTUSED(user);
+ write(2, r->s, strlen(r->s));
+ write(2, "\n", 1);
+ return 0;
+}
+
+void agrefstrdump(Agraph_t * g)
+{
+ dtwalk(Refdict_default, refstrprint, 0);
+}
+#endif