	XBM viewer

	This is a WWW applet (conforming to the W3A API) for viewing
	images in XBM format. Apart from showing the image, it also
	accepts mouse clicks, which it passes to the browser by means
	of the [[W3Aevent]] function.

	Note that this viewer is not interested in the information
	about the document that it is viewing. It simply ignores the
	[[W3ADocumentInfo]] that is passed to [[openXBM]].

	A mouse click causes a call to [[W3Aevent]], with event type
	5000 and a pointer to a pair (x,y) of integers as parameter.


	The viewer draws directly into the widget that is provided by
	the main browser. Since it is not known what kind of widget it
	is (probably Composite or XmManager), the viewer assumes only
	that it is a subclass of Core. It installs a set of
	translations and actions to override whatever the widget
	already has.

	The action functions are registered only once, when the viewer
	is initialized ([[initXBM]]). The translations are installed
	in every widget that is passed to [[openXBM]].

	The action functions have a single parameter, which is used to
	pass the Buffer with info about the image to the action
	function. Since action functions can only have string
	arguments, the pointer to the Buffer is encoded as a decimal
	number.

	The [[XBM_map]] action is invoked when the window is mapped.
	It is used to set the shape mask, because the mask can only be
	applied after the widget has been realized. The Map event
	seems a good place for it.


	Bert Bos <bert@let.rug.nl>, 14 Sep 1994

<<*>>=
#include <config.h>
#include <w3a.h>
#include <str.h>
#include <Xm/Xm.h>
#include <X11/extensions/shape.h>

/* Defined in w3a.h: */
/* #define POINT_SELECT 5000			/* W3A event type */

/* #define own_widget */

typedef struct {                                /* A viewer instance */
    char *buf;                                  /* Image source data */
    size_t buflen;                              /* Length of buf in bytes */
    Widget canvas;                              /* The widget to draw into */
    Pixmap mask;				/* Holds decoded image */
    Pixmap bground;				/* Same, but with depth */
} *Buffer;

#define min(a, b) ((a) < (b) ? (a) : (b))

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

static Assoc assoclist = NULL;

/* store -- store an ID/Buffer combination */
static void store(Buffer 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(assoclist->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 Buffer find(long id)
{
    Assoc h;
    assert(assoclist);
    for (h = assoclist; h->id != id; h = h->next) assert(h->next);
    return h->b;
}


/* parseXBM -- decode XBM image data and create a Pixmap from it */
static Bool parseXBM(Buffer b)
{
    Display *dpy = XtDisplay(b->canvas);
    int screen = XScreenNumberOfScreen(XtScreen(b->canvas));
    unsigned int depth = DefaultDepth(dpy, screen);
    Window root = RootWindow(dpy, screen);
    int i, width, height, dummy, j, n;
    GC gc = DefaultGC(dpy, screen);
    char *s, c, *data;

    b->buf[b->buflen] = '\0';
    s = b->buf;

    /* Find width of bitmap */
    if (sscanf(s, "# define %*s %i %n", &width, &i) != 1) return FALSE;
    s += i;

    /* Find height of bitmap */
    if (sscanf(s, "# define %*s %i %n", &height, &i) != 1) return FALSE;
    s += i;

    /* Skip possible hotspot */
    if (*s == '#') {
	if (sscanf(s, "# define %*s %i %n", &dummy, &i) != 1) return FALSE;
	s += i;
	if (sscanf(s, "# define %*s %i %n", &dummy, &i) != 1) return FALSE;
	s += i;
    }

    /* Read data bytes, no error checking... */
    newarray(data, (width + 7)/8 * height);
    while (*s && *s != '{') s++;
    if (*s) s++;
    j = 0;
    while (sscanf(s, " %i %c %n", &n, &c, &i) == 2) {
	data[j] = n;
	s += i;
	j++;
    }

    /* Call X routine to parse data */
    b->mask = XCreateBitmapFromData(dpy, root, data, width, height);
    if (!b->mask) return FALSE;

    /* Create Pixmap from Bitmap, to set background */
    b->bground = XCreatePixmap(dpy, root, width, height, depth);
    if (b->bground == None) {
	if (b->mask) XFreePixmap(dpy, b->mask);
	return FALSE;
    }
    XCopyPlane(dpy, b->mask, b->bground, gc, 0, 0, width, height, 0, 0, 1);

    /* Try to set widget size & shape; set image as background */
    XtVaSetValues(b->canvas,
		  XtNwidth, width,
		  XtNheight, height,
		  XtNbackgroundPixmap, b->bground,
		  NULL);

    if (XtIsRealized(b->canvas)) {
	XShapeCombineMask(dpy, XtWindow(b->canvas),
			  ShapeBounding, 0, 0, b->mask, ShapeSet);
	XFreePixmap(dpy, b->mask);
	b->mask = None;
    }

    dispose(b->buf);				/* Remove source data */
    return TRUE;
}


/* map -- set the shape mask when the window is mapped */
static void map(Widget w, XEvent *ev, String *parms, Cardinal *nparms)
{
    Buffer b;

    /* Decode string argument as a Buffer pointer */
    assert(*nparms == 1);
    if (sscanf(parms[0], "%ld", &b) != 1) assert(!"Missing parameter");
    if (b->mask == None) return;

    /* Set the window shape */
    XShapeCombineMask(XtDisplay(w), XtWindow(w),
		      ShapeBounding, 0, 0, b->mask, ShapeSet);
    XFreePixmap(XtDisplay(w), b->mask);
    b->mask = None;
}


/* click -- action for mouse clicks on canvas */
/* ARGSUSED */
static void click(Widget w, XEvent *ev, String *params, Cardinal *nparams)
{
    long b;
    struct {int x, y;} coords;

    assert(*nparams == 1);
    if (sscanf(params[0], "%ld", &b) != 1) assert(!"Missing parameter");
	coords.x = ev->xbutton.x;
	coords.y = ev->xbutton.y;
    debug("Clicked on image: (%d,%d)\n", coords.x, coords.y);
    W3Aevent(b, POINT_SELECT, &coords);
}


static XtActionsRec actions[] = {
    {"XBM_map", map},
    {"XBM_click", click}
};

static char translations[] =
    "<Map>: XBM_map(%ld)\n\
    <Btn1Down>,<Btn1Up>: XBM_click(%ld)";


/* initXBM -- initialize the XBM viewer class */
EXPORT Bool initXBM(char ***mime_types, int *nrtypes, float **prefs)
{
    static char *types[] = {"image/x-xbitmap"};
    static float pref[] = {1.0};

    *mime_types = types;
    *nrtypes = 1;
    *prefs = pref;
    XtAppAddActions(XtWidgetToApplicationContext(W3Atoplevel()),
        actions, XtNumber(actions));
    return TRUE;
}


/* openXBM -- start a new XBM viewer, return its ID */
EXPORT Bool openXBM(const W3ADocumentInfo doc, W3AWindow window, long id)
{
    int screen = XScreenNumberOfScreen(XtScreen(window));
    Display *dpy = XtDisplay(window);
    Buffer b;
    char s[256];

    /* Create buffer for data */
    new(b);
    store(b, id);
    b->buf = NULL;
    b->buflen = 0;
    b->mask = None;
    b->bground = None;
#ifdef own_widget
    b->canvas = XtVaCreateManagedWidget
	("xbm", coreWidgetClass, window,
	 XtNwidth, 40, XtNheight, 40, NULL);	/* Initial size != 0 */
#else
    b->canvas = window;
#endif

    /* Install our own event handlers on the window */
    sprintf(s, translations, (long) b, (long) b);
    XtOverrideTranslations(b->canvas, XtParseTranslationTable(s));

    return TRUE;
}


/* writeXBM -- add image data to the buffer, decode when complete */
EXPORT int writeXBM(long id, const char *buf, size_t nbytes)
{
    Buffer b = find(id);

    if (nbytes != 0) {                          /* Add data to buffer */
	renewarray(b->buf, b->buflen + nbytes + 1); /* +1 needed in parseXBM */
	memcpy(b->buf + b->buflen, buf, nbytes);
	b->buflen += nbytes;
	return nbytes;				/* All bytes processed */
    } else if (parseXBM(b)) {			/* Parse data */
	return 0;				/* Data OK */
    } else {					/* Error while parsing */
	errno = EFORMAT;
	return -1;
    }
}


/* closeXBM -- close an XBM viewer */
EXPORT Bool closeXBM(long id)
{
    Buffer b = find(id);

    if (b->bground != None) XFreePixmap(XtDisplay(b->canvas), b->bground);
    if (b->mask != None) XFreePixmap(XtDisplay(b->canvas), b->mask);
#ifdef own_widget
    XtDestroyWidget(b->canvas);
#else
    /* Remove shape mask */
    XShapeCombineMask(XtDisplay(b->canvas), XtWindow(b->canvas),
		      ShapeBounding, 0, 0, None, ShapeSet);
#endif
    delete(id);
    return TRUE;
}


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


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