	Main body of HTML viewer

	The viewer conforms to W3A. It exports [[initHTML]],
	[[openHTML]], [[writeHTML]], [[closeHTML]], [[infoHTML]] and
	[[eventHTML]].

	[[initHTML]] is called when the applet is loaded.

	[[openHTML]] starts a new viewer for document [[doc]]. It
	creates an XfwfHTML2 widget.

	[[writeHTML]] is passed blocks of characters and passes them
	to the widget.

<<*>>=
#include <config.h>
#include <Xm/Xm.h>
#include <Xm/ScrolledW.h>
#include <Xm/DrawingA.h>
#include <Xm/Form.h>
#include <Xm/PanedW.h>
#include <Xm/Label.h>
#include <X11/xpm.h>
#include <w3a.h>
#include <w3alib.h>
#include <str.h>
#include <url.h>
#include <Xfwf/HTML2.h>

#define USE_STATUS 1
#define USE_BALLOON 0
@

	The ID-Widget list associates IDs and widgets. These
	are IDs of sub-viewers, that may be needed in [[eventHTML]].

	The ID-Instance list associates IDs and instances.

<<*>>=
typedef struct _IdWidget {
    long id;
    Widget w;
    struct _IdWidget *next;
} *IdWidget;

typedef struct {
    W3ADocumentInfo *info;
    Widget w;					/* HTML widget */
#if USE_STATUS
    Widget status;				/* Label with URL */
#endif
#ifdef USE_BALLOON
    Widget balloon;
#endif
    long nchars;				/* Needed in infoHTML */
    IdWidget idlist;
} *Instance;

typedef struct _IdInstance {
    long id;
    Instance i;
    struct _IdInstance *next;
} *IdInstance;

static IdInstance instancelist = NULL;

static void store_id(Instance instance, long id, Widget w)
{
    IdWidget h;
    new(h); h->id = id; h->w = w; h->next = instance->idlist;
    instance->idlist = h;
}

static Widget id_to_widget(Instance instance, long id)
{
    IdWidget p;
    for (p = instance->idlist; p; p = p->next)
	if (p->id == id) return p->w;
    return NULL;
}

static void dispose_idlist(IdWidget list)
{
    if (list) {
	if (list->id != -1) W3AcloseView(list->id);
	dispose_idlist(list->next);
	dispose(list);
    }
}

/* store -- store an ID/Instance combination */
static void store(Instance i, long id)
{
    IdInstance h;
    new(h); h->id = id; h->i = i; h->next = instancelist; instancelist = h;
}

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

/* find -- find the Instance associated with an ID */
static Instance find(long id)
{
    IdInstance h;
    assert(instancelist);
    for (h = instancelist; h->id != id; h = h->next) assert(h->next);
    return h->i;
}
@

	[[activate_cb]] is a callback for when the user clicks on a
	hyperlink. The [[call_data]] is a pointer to an
	[[XfwfFTextCallbackStruct]], which in turn contains a field
	[[data]], which is a pointer to an [[XfwfSSGMLData]] struct.

<<*>>=
static void activate_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    XfwfFTextCallbackStruct *info = (XfwfFTextCallbackStruct *) call_data;
    XfwfSSGMLData *data = (XfwfSSGMLData *) info->data;
    char *url = data->url;
    Instance instance = (Instance) client_data;
    W3ADocumentInfo *doc;

    /* debug("html activate: %s\n", data->url); */
#if USE_BALLOON
    if (instance->balloon) {
	XtPopdown(instance->balloon);
	instance->balloon = NULL;
    }
#endif
    doc = new_doc();
    if (data->ismap) {				/* Append x,y */
	newarray(doc->url, strlen(url) + 25);
	sprintf(doc->url, "%s?%d,%d", url, info->x, info->y);
    } else
	doc->url = newstring(url);
    doc->referer = newstring(instance->info->url);
    (void) W3Aprocess(doc, GET_METHOD, NULL, 0);
    dispose_doc(doc);
}
@

	[[activate_form_cb]] is a callback for when the user clicks on
	a submit button in a form. The [[call_data]] is a pointer to an
	[[XfwfActivateFormInfo]], which in turn contains fields [[url]],
	[[data]], [[length]] and [[method]].

<<*>>=
static void activate_form_cb(Widget w, XtPointer client_data,
			     XtPointer call_data)
{
    XfwfActivateFormInfo *info = (XfwfActivateFormInfo *) call_data;
    Instance instance = (Instance) client_data;
    W3ADocumentInfo *doc;

    /* debug("html activate_form: %s\n", info->url); */
#if USE_BALLOON
    if (instance->balloon) {
	XtPopdown(instance->balloon);
	instance->balloon = NULL;
    }
#endif
    doc = new_doc();
    doc->url = newstring(info->url);
    doc->referer = newstring(instance->info->url);
    (void) W3Aprocess(doc, info->method, info->data, info->length);
    dispose_doc(doc);
}
@

	[[enterleave_cb]] is a callback for when the mouse enters or
	leaves a chunk with attached data.
	
<<*>>=
static void enterleave_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
#if USE_STATUS || USE_BALLOON
    XfwfFTextCallbackStruct *info = (XfwfFTextCallbackStruct *) call_data;
    XfwfSSGMLData *data = (XfwfSSGMLData *) info->data;
    Instance instance = (Instance) client_data;
    URI base, uri;
    Position x, y;
    char *url;
#endif

#if USE_STATUS
    if (info->reason == XfwfEnter) {
	if (! URL_parse(data->url, &uri)) return; /* Error in URL */
	if (uri.tp == URI_Rel) {
	    if (! URL_parse(instance->info->url, &base)) return; /* ??? */
	    URL_expand(&uri, base);
	}
	url = uri2str(uri);
	XtVaSetValues(instance->status, XtVaTypedArg, XmNlabelString,
		      XtRString, url, strlen(url), NULL);
	dispose(url);
    } else {
	assert(info->reason == XfwfLeave);
	XtVaSetValues(instance->status, XtVaTypedArg, XmNlabelString,
		      XtRString, "", 1, NULL);
    }
#endif
#if USE_BALLOON
    if (info->reason == XfwfEnter) {
	if (! URL_parse(data->url, &uri)) return; /* Error in URL */
	if (uri.tp == URI_Rel) {
	    if (! URL_parse(instance->info->url, &base)) return; /* ??? */
	    URL_expand(&uri, base);
	}
	url = uri2str(uri);
	XtTranslateCoords(instance->w, info->x, info->y, &x, &y);
	instance->balloon = XtVaCreatePopupShell
	    ("balloon", overrideShellWidgetClass, instance->w,
	     XtNx, x + 5, XtNy, y + 5, NULL);
	(void) XtVaCreateManagedWidget
	    ("balloon-label", xmLabelWidgetClass, instance->balloon,
	     XtVaTypedArg, XmNlabelString, XtRString, url, strlen(url),
	     NULL);
	dispose(url);
	XtPopup(instance->balloon, XtGrabNone);
    } else {
	assert(info->reason == XfwfLeave);
	if (instance->balloon) XtDestroyWidget(instance->balloon);
	instance->balloon = NULL;
    }
#endif
}
@

	|resolve_cb| is called by the HTML widget when it has a URL
	and wants to transform it into a child widget. The callback
	function uses |W3Asubprocess| to open a sub-viewer retrieve
	the correspnonding document. The returned ID is stored,
	because |eventHTML| may need to know the widget that
	corresponds to this ID.

<<*>>=
static void resolve_cb(Widget w, XtPointer client_data, XtPointer call_data)
{
    XfwfResolveRec *info = (XfwfResolveRec *) call_data;
    Instance instance = (Instance) client_data;
    W3ADocumentInfo *doc;
    long id;

    /* fprintf(stderr, "resolve image: %s\n", info->url); */

    doc = new_doc();
    doc->url = newstring(info->url);
    doc->referer = newstring(instance->info->url);
    id = W3Asubprocess(doc, GET_METHOD, NULL, 0, info->widget);
    store_id(instance, id, info->widget);
    dispose_doc(doc);
}


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

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


EXPORT Bool openHTML(const W3ADocumentInfo doc, W3AWindow workarea, long id)
{
    Widget w, form, status;
    Instance instance;
    Pixmap bg;

    new(instance);
    store(instance, id);

#if USE_STATUS
#define STATUS_HT 20
#if 1
    form = XtVaCreateManagedWidget
	("html-form", xmFormWidgetClass, workarea, NULL);
    w = XtVaCreateManagedWidget
	("html", xfwfHTML2WidgetClass, form,
	 XmNtopAttachment, XmATTACH_FORM,
	 XmNleftAttachment, XmATTACH_FORM,
	 XmNrightAttachment, XmATTACH_FORM,
	 XmNbottomAttachment, XmATTACH_FORM,
	 XmNbottomOffset, STATUS_HT,
	 XtNbase, doc.url,
	 NULL);
    status = XtVaCreateManagedWidget
	("html-status", xmLabelWidgetClass, form,
	 XmNtopAttachment, XmATTACH_WIDGET,
	 XmNtopWidget, w,
	 XmNbottomAttachment, XmATTACH_FORM,
	 XmNleftAttachment, XmATTACH_FORM,
	 XmNrightAttachment, XmATTACH_FORM,
	 NULL);
#else /* 0 */
    form = XtVaCreateManagedWidget
	("html-form", xmPanedWindowWidgetClass, workarea, NULL);
    w = XtVaCreateManagedWidget
	("html", xfwfHTML2WidgetClass, form, NULL);
    status = XtVaCreateManagedWidget
	("html-status", xmLabelWidgetClass, form, NULL);
#endif /* 0 */
#else /* USE_STATUS */
    w = XtVaCreateManagedWidget("html", xfwfHTML2WidgetClass, workarea, NULL);
#endif
    XtAddCallback(w, XtNactivate, activate_cb, instance);
    XtAddCallback(w, XtNactivateForm, activate_form_cb, instance);
    XtAddCallback(w, XtNenter, enterleave_cb, instance);
    XtAddCallback(w, XtNleave, enterleave_cb, instance);
    XtAddCallback(w, XtNresolveURL, resolve_cb, instance);

    instance->w = w;
#if USE_STATUS
    instance->status = status;
#endif
#if USE_BALLOON
    instance->balloon = NULL;
#endif
    instance->info = new_doc();
    copy_doc(instance->info, doc);
    instance->nchars = 0;
    instance->idlist = NULL;

#if 0
    /* Nice background (this is not the way it should be done, though) */
    if (XpmReadFileToPixmap
	(XtDisplay(w), RootWindowOfScreen(XtScreen(w)),
	 "Flock.pm", &bg, NULL, NULL) == XpmSuccess) {
	XtVaSetValues(w, XtNbackgroundPixmap, bg, NULL);
	XtVaSetValues(XtParent(w), XtNbackgroundPixmap, bg, NULL);
    }
#endif
    return TRUE;
}


EXPORT Bool infoHTML(long id, W3ADocumentInfo *info)
{
    Instance instance = find(id);
    char *s = NULL;

    XtVaGetValues(instance->w, XtNtitle, &s, NULL);
    if (s) {
	dispose(info->title);
	info->title = newstring(s);
	trim(info->title);
	dispose(instance->info->title);
	instance->info->title = newstring(info->title);
    }
    info->size = instance->nchars;
    return TRUE;
}

EXPORT int writeHTML(long id, const char *buf, size_t nbytes)
{
    Instance instance = find(id);
    Widget workarea = XtParent(XtParent(instance->w));

    XfwfAddText(instance->w, buf, nbytes);
    instance->nchars += nbytes;
    return nbytes;
}


EXPORT Bool closeHTML(long id)
{
    Instance instance = find(id);

#if USE_STATUS
    XtDestroyWidget(XtParent(instance->w));
#else
    XtDestroyWidget(instance->w);
#endif
    dispose_doc(instance->info);
    dispose_idlist(instance->idlist);
    delete(id);
    return TRUE;
}


EXPORT void eventHTML(long id, long source, long eventtype, void *params)
{
    Instance instance = id == -1 ? NULL : find(id);
    struct _coords {int x, y;};
    Widget child;
    int x, y;

    if (id == -1) return;			/* Not interested in */
						/* events to class */
    /* debug("HTML viewer %ld received event %ld from %ld\n",
	  id, eventtype, source); */
    if (eventtype == POINT_SELECT
	&& (child = id_to_widget(instance, source)) != NULL) {
	x = ((struct _coords *) params)->x;
	y = ((struct _coords *) params)->y;
	XfwfPassClick(instance->w, child, x, y);
    }
}
