	XPM viewer

	This is a WWW applet (conforming to the W3A API) for viewing
	images in XPM 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 really interested in the
	information about the document that it is viewing. It simply
	ignores the [[W3ADocumentInfo]] that is passed to [[openXPM]],
	except that it keeps the URL for use in a possible error
	message.

	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 XPM 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 ([[initXPM]]). The translations are
	installed in every widget that is passed to [[openXPM]].

	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..


	Bert Bos <bert@let.rug.nl>, 11 Aug 1994

<<*>>=
#include <config.h>
#include <w3a.h>
#include <str.h>
#include <Xm/Xm.h>
#include <X11/extensions/shape.h>
#include <X11/xpm.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 */
    XpmAttributes attr;				/* Holds size of image */
    Pixmap image;				/* Holds decoded image */
    Pixmap mask;				/* For transparency */
    GC gc;                                      /* GC for image */
} *Buffer;

#define abs(a) ((a) < 0 ? -(a) : (a))
#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;
}




/* parseXPM -- decode XPM image data and create a Pixmap from it */
static int parseXPM(Buffer b)
{
    int status;

    /* Call XPM routine to parse data */
    b->buf[b->buflen] = '\0';
    b->attr.valuemask = 0;
    b->mask = None;
    status = XpmCreatePixmapFromBuffer
	(XtDisplay(b->canvas), RootWindowOfScreen(XtScreen(b->canvas)),
	 b->buf, &b->image, &b->mask, &b->attr);
    if (status != XpmSuccess) return status;

    /* Try to set widget size & shape; draw image */
    XtVaSetValues(b->canvas, XtNwidth, b->attr.width,
		  XtNheight, b->attr.height, NULL);
    if (XtIsRealized(b->canvas) && b->mask != None) {
	XShapeCombineMask(XtDisplay(b->canvas), XtWindow(b->canvas),
			  ShapeBounding, 0, 0, b->mask, ShapeSet);
	XFreePixmap(XtDisplay(b->canvas), b->mask);
	b->mask = None;
    }
    if (XtIsRealized(b->canvas))
	XCopyArea(XtDisplay(b->canvas), b->image, XtWindow(b->canvas), b->gc,
		  0, 0, b->attr.width, b->attr.height, 0, 0);

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


/* expose -- redraw the exposed area of an XPM image */
static void redraw(Widget w, XEvent *ev, String *parms, Cardinal *nparms)
{
    Buffer b;
    int wd, ht, x, y;

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

    /* Only on the first ExposeEvent: set the window shape */
    if (b->mask != None) {
    	XShapeCombineMask(XtDisplay(w), XtWindow(w),
			      ShapeBounding, 0, 0, b->mask, ShapeSet);
	    XFreePixmap(XtDisplay(w), b->mask);
	    b->mask = None;
    }
    /* Draw image to exposed rectangle */
    x = ev->xexpose.x;
    y = ev->xexpose.y;
    wd = min(b->attr.width, ev->xexpose.width);
    ht = min(b->attr.height, ev->xexpose.height);
    XCopyArea(XtDisplay(w), b->image, XtWindow(w), b->gc, x, y, wd, ht, x, y);
}

/* 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;
    W3Aevent(b, POINT_SELECT, &coords);
}


static XtActionsRec actions[] = {
    {"XPM_expose", redraw},
    {"XPM_click", click}
};

static char translations[] =
    "<Expose>: XPM_expose(%ld)\n\
    <Btn1Down>,<Btn1Up>: XPM_click(%ld)";



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

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


/* openXPM -- start a new XPM viewer, return its ID */
EXPORT Bool openXPM(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->image = None;
#ifdef own_widget
    b->canvas = XtVaCreateManagedWidget
	("xpm", coreWidgetClass, window,
	 XtNwidth, 40, XtNheight, 40, NULL);	/* Initial size != 0 */
#else
    b->canvas = window;
#endif
    b->gc = DefaultGC(dpy, screen);

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


/* writeXPM -- add image data to the buffer, decode when complete */
EXPORT int writeXPM(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);
	memcpy(b->buf + b->buflen, buf, nbytes);
	b->buflen += nbytes;
	return nbytes;				/* All bytes processed */
    } else if (parseXPM(b) == XpmSuccess) {	/* Parse data */
	return 0;				/* Data OK */
    } else {					/* Error while parsing */
	errno = EFORMAT;
	return -1;
    }
}


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

    XpmFreeAttributes(&b->attr);
    XFreePixmap(XtDisplay(b->canvas), b->mask);
    XFreePixmap(XtDisplay(b->canvas), b->image);
#ifdef own_widget
    XtDestroyWidget(b->canvas);
#else
    /* Remove shape mask */
    XShapeCombineMask(XtDisplay(b->canvas), XtWindow(b->canvas),
		      ShapeBounding, 0, 0, None, ShapeSet);
#endif
    return TRUE;
}


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


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