	Viewer for plain ASCII

	The viewer is an applet corresponding to W3A/V. It works by
	opening an XmText widget and displaying the text in it.

	[[instance]] is an array that maps IDs to XmText widgets.
	[[ninstances]] is the number of assigned IDs, except that
	there may be lower numbered IDs that have been freed again.
	When [[closePlain]] is called, the corresponding widget
	element of [[instance]] is set to [[NULL]], so that the ID can
	be reused.

	[[openPlain]] finds the first unused element in [[instance]].
	The index will be the ID that is passed to the caller. It then
	asks for a work area and creates an XmText widget under it.

	[[writePlain]] adds text to the XmText widget.

	[[closePlain]] destroys the XmText widget and marks the
	element in [[instance]] as free, by setting it to [[NULL]].

<<*>>=
#include <config.h>
#include <Xm/Xm.h>
#include <Xfwf/XmPager.h>
#include <w3a.h>
#include <str.h>


#define INCR 1024				/* Allocate 1K at a time */

typedef struct {
    Widget w;
    int nchars;
    int column;					/* For setting tabs */
    int buflen;
    char *buf;
} *Instance;

typedef struct _Assoc {
    long id;
    Instance b;
    struct _Assoc *next;
} *Assoc;

static Assoc assoclist = NULL;

/* store -- store an ID/Buffer combination */
static void store(Instance b, long id)
{
    Assoc h;
    new(h); h->id = id; h->b = b; h->next = assoclist; assoclist = h;
}

/* delete -- delete an ID/Buffer combination */
static void delete(long id)
{
    Assoc g, h;
    assert(assoclist);
    if (assoclist->id == id) {
	h = assoclist; assoclist = assoclist->next; dispose(h);
    } else {
	assert(h->next);
	for (h = assoclist; h->next->id != id; h = h->next) assert(h->next);
	g = h->next; h->next = g->next; dispose(g);
    }
}

/* find -- find the Buffer associated with an ID */
static Instance find(long id)
{
    Assoc h;
    assert(assoclist);
    for (h = assoclist; h->id != id; h = h->next) assert(h->next);
    return h->b;
}


EXPORT Bool initPlain(char ***mime_types, int *nrtypes, float **prefs)
{
    static char *types[] = {"text/plain"};
    static float pref[] = {1.0};

    *mime_types = types;
    *nrtypes = 1;
    *prefs = pref;
    return TRUE;
}


EXPORT Bool openPlain(const W3ADocumentInfo info, W3AWindow work_area, long id)
{
    Widget w;
    Instance h;

    new(h);
    store(h, id);
    h->w = XtVaCreateManagedWidget
	("plain", xmPagerWidgetClass, work_area, NULL);
    h->buflen = INCR;
    newarray(h->buf, h->buflen);
    /* Hack: text must not start with @ or ` */
    h->buf[0] = '\n';
    h->nchars = 1;
    h->column = 0;
    return TRUE;
}


EXPORT int writePlain(long id, const char *buf, size_t nbytes)
{
    Instance h = find(id);
    int i;

    for (i = 0; i < nbytes; i++) {
	if (h->nchars + 9 >= h->buflen) {
	    assert(h->buflen >= h->nchars && INCR > 9);
	    h->buflen += INCR;
	    renewarray(h->buf, h->buflen);
	}
	if (buf[i] == '\n') {
	    h->buf[h->nchars++] = '\n';
	    h->column = 0;
	} else if (buf[i] == '\t') {
	    do {
		h->buf[h->nchars++] = ' ';
		h->column++;
	    } while (h->column % 8 != 0);
	} else {
	    h->buf[h->nchars++] = buf[i];
	    h->column++;
	}
    }
    h->buf[h->nchars] = '\0';
    if (nbytes != 0) XtVaSetValues(h->w, XtNtext, h->buf, NULL);
    return nbytes;
}


EXPORT Bool closePlain(long id)
{
    Instance h = find(id);

    XtDestroyWidget(h->w);
    dispose(h->buf);
    return TRUE;
}


/* eventPlain -- react to events happening elsewhere */
/* ARGSUSED */
EXPORT void eventPlain(long id, long source, long eventtype, void *params)
{
    /* Doesn't handle events */
}


/* infoPlain -- a chance to modify the document info based on the contents */
/* ARGSUSED */
EXPORT Bool infoPlain(long id, W3ADocumentInfo *doc)
{
    /* No modifications */
    return TRUE;
}
