
/*
 * html_browser.c -- use a XmHTMLText widget to view the contents
 * of arbitrary HTML files choosen by the user from a fileSelection
 * dialog or by following hyperlinks in a document.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Xm/Xm.h>
#include <Xm/FileSB.h>
#include <Xm/MainW.h>
#include <Xm/RowColumn.h>
#include <XmHTML/XmHTML.h>
#include <sys/types.h>
#include <sys/stat.h>

/* you might have to include <strings.h> for strcasecmp() proto */


static void readFile(Widget html, String file);
static void fileCB(Widget item, int item_no);
static void fileOkCB(Widget w, Widget html,
	XmFileSelectionBoxCallbackStruct *cbs);
static void anchorCB(Widget html, XtPointer data, XmHTMLAnchorPtr cbs);
static String mimeType(String filename, FILE *file);

int
main(int argc, char **argv)
{
	Widget top, mainw, menubar, menu, html;
	XtAppContext context;
	XmString file, new, quit;

	/* initialize toolkit and create toplevel shell */
	top = XtVaAppInitialize(&context, "HTMLDemos", NULL, 0,
		&argc, argv, NULL, NULL);

	/* mainwindow for the application */
	mainw = XtVaCreateManagedWidget("mainw",
		xmMainWindowWidgetClass, top,
		NULL);

	/* Create a simple MenuBar that contains one menu */
	file = XmStringCreateSimple("File");
	menubar = XmVaCreateSimpleMenuBar(mainw, "menubar",
		XmVaCASCADEBUTTON, file, 'F',
		NULL);
	XmStringFree(file);

	/* menu is "File", callback is fileCB() */
	new = XmStringCreateSimple("Open...");
	quit = XmStringCreateSimple("Quit");
	menu = XmVaCreateSimplePulldownMenu(menubar, "fileMenu", 0,
		(XtCallbackProc)fileCB,
		XmVaPUSHBUTTON, new, 'O', NULL, NULL,
		XmVaSEPARATOR,
		XmVaPUSHBUTTON, quit, 'Q', NULL, NULL,
		NULL);
	XmStringFree(new);
	XmStringFree(quit);

	/* Menubar is done, manage it */
	XtManageChild(menubar);

	/* Create a simple XmHTML widget */
	html = XtVaCreateManagedWidget("html",
		xmHTMLWidgetClass, mainw,
		XmNwidth, 400,
		XmNheight, 600,
		NULL);

	/* add a callback to respond to anchor activation */
	XtAddCallback(html, XmNactivateCallback,
		(XtCallbackProc)anchorCB, NULL);

	/* store html as userdata in "File" menu for fileCB() callback */
	XtVaSetValues(menu, XmNuserData, html, NULL);

	XtManageChild(html);

	XmMainWindowSetAreas(mainw, menubar, NULL, NULL, NULL, html);
	XtRealizeWidget(top);
	XtAppMainLoop(context);

	/* never reached but keeps compiler happy */
	return(EXIT_SUCCESS);
}

/* The "File" menu was selected, popup a file selection */
static void
fileCB(Widget item, int item_no)
{
	static Widget dialog;
	Widget html;

	if(item_no == 1)
		exit(0);		/* user choose Quit */

	if(!dialog)
	{
		Widget menu = XtParent(item);
		dialog = XmCreateFileSelectionDialog(menu, "fileSB", NULL, 0);

		/* get the html widget handle stored as userdata in file menu */
		XtVaGetValues(menu, XmNuserData, &html, NULL);
		XtAddCallback(dialog, XmNokCallback,
			(XtCallbackProc)fileOkCB, html);
		XtAddCallback(dialog, XmNcancelCallback,
			(XtCallbackProc)XtUnmanageChild, NULL);
	}
	XtManageChild(dialog);
	XtPopup(XtParent(dialog), XtGrabNone);

	/* call XMapRaised to make sure it's visible */
	XMapRaised(XtDisplay(dialog), XtWindow(XtParent(dialog)));
}

/*
 * Callback routine when the user selects Ok in the fileSelectionDialog.
 * The name of the file to load is passed to the routine that will do
 * the actual loading.
 */
static void
fileOkCB(Widget w, Widget html, XmFileSelectionBoxCallbackStruct *cbs)
{
	char *filename;

	XmStringGetLtoR(cbs->value, XmSTRING_DEFAULT_CHARSET, &filename);

	if(!filename || *filename == '\0')
	{
		/* nothing typed? */
		if(filename)
			XtFree(filename);
		return;
	}

	/* pass to readFile */
	readFile(html, filename);

	/* all done */
	XtFree(filename);
}

/*
 * Read the given file, determine the mime (= content) type and set
 * the contents of the file in the HTML widget.
 */
static void
readFile(Widget html, String filename)
{
	struct stat statbuf;
	String text;
	FILE *file;
	String mime;
	int retval;

	/* make sure the file is a regular file and open it */
	if((retval = stat(filename, &statbuf)) == -1 || 
		(statbuf.st_mode & S_IFMT) != S_IFREG)
	{
		if(retval == -1)
			perror(filename);		/* couldn't open */
		else
			fprintf(stderr, "%s: not a regular file\n", filename);
		return;
	}

	/* open the file and determine it's content type */
	if(!(file = fopen(filename, "r")) || !(mime = mimeType(filename, file)))
	{
		perror(filename);		/* couldn't read */
		fclose(file);
	}

	if(!strcmp(mime, "image/"))
	{
		/* An image was selected */

		/* we do not need the file but the name of the file */
		fclose(file);

		/*
		 * Set the name of the image to load as the value. XmHTML will
		 * perform the loading of the image if necessary.
		 */
		XtVaSetValues(html,
			XmNmimeType, mime,
			XmNvalue, filename,
			NULL);
		return;
	}

	/* A true HTML or a plain text file. Read and set */
	if(!(text = XtMalloc((unsigned int)(statbuf.st_size+1))))
	{
		fclose(file);
		fprintf(stderr, "Could not load %s: malloc failed for %li bytes\n",
			filename, (unsigned long)statbuf.st_size+1);
		return;
	}

	/* file was opened, read it */
	if(!fread(text, sizeof(char), statbuf.st_size + 1, file))
		fprintf(stderr, "Warning: %s: may not have read entire file!\n",
			filename);

	fclose(file);

	/* NULL terminate */
	text[statbuf.st_size] = '\0';

	/* insert contents in the HTML widget */
	XtVaSetValues(html,
		XmNmimeType, mime,
		XmNvalue, text,
		NULL);

	XtFree(text);
}

/*
 * Determine if the given file is a HTML file or an image that XmHTML
 * recognizes.
 */
static String
mimeType(String filename, FILE *file)
{
	String chPtr;
	char buf[128];
	unsigned char img_type;

	/* check if there is a . in the filename somewhere */
	if((chPtr = strstr(filename, ".")) != NULL)
	{
		String start;

		/*
		 * check if this was a html file or not. We start at the end of
		 * the given filename and walk to the front. We don't use the
		 * return value from strstr() since the dot may have occured in a
		 * pathname.
		 */
		for(start = &filename[strlen(filename)-1];
			*start != '.' && *start != '/' ; start--)
		{
			/* any of these extensions are considered HTML files by default */
			if(!strcasecmp(start, ".html") || !strcasecmp(start, ".htm"))
				return("text/html");
		}
	}
	/*
	 * No or a non-matching extension. Read the first line and see if it's
	 * an image or if this file contains HTML.
	 */
	if(!(chPtr = fgets(buf, 128, file)))
		return(NULL);

	if((img_type = XmHTMLImageGetType(filename, buf, 128)) == IMAGE_ERROR)
		return(NULL);
	else if(img_type == IMAGE_UNKNOWN)
	{
		/*
		 * Image type not known by XmHTML or not an image.
		 * Check if it contains HTML.
		 */

		/* walk to the first < */
		for(chPtr = buf; *chPtr != '\0' && *chPtr != '<'; chPtr++);

		/* No < found, assume plain text */
		if(*chPtr == '\0')
			return("text/plain");

		if(!strncasecmp(chPtr, "<!doctype",9) ||
			!strncasecmp(chPtr, "<html", 5) ||
			!strncasecmp(chPtr, "<head", 5) ||
			!strncasecmp(chPtr, "<body", 5))
			return("text/html");

		/* default to plain text */
		return("text/plain");
	}

	/* known imagetype but check if support is available */
	if((img_type == IMAGE_JPEG && !XmHTMLImageJPEGSupported()) ||
		(img_type == IMAGE_PNG && !XmHTMLImagePNGSupported())) 
	{
		fprintf(stderr, "File %s contains an unsupported image type\n",
			filename);
		return(NULL);
	}

	/* it's a known and supported image type */
	return("image/");
}

/*
 * A hyperlink was selected in the currently displayed document.
 * Act according to the type of the selected hyperlink.
 */
static void
anchorCB(Widget html, XtPointer data, XmHTMLAnchorPtr cbs)
{
	if(cbs->reason != XmCR_ACTIVATE)
		return;

	switch(cbs->url_type)
	{
		case ANCHOR_JUMP:
			/*
			 * A named anchor was selected, See if it is a valid one */
			if(XmHTMLAnchorGetId(html, cbs->href) != -1)
			{
				/*
				 * Tell the widget it must scroll the selected hyperlink
				 * into view and have it render all references to it as
				 * visited.
				 */
				cbs->doit = True;
				cbs->visited = True;
				return;
			}
			cbs->doit = False;
			return;
		case ANCHOR_FILE_LOCAL:
		case ANCHOR_FILE_REMOTE:
			{
				String chPtr;

				/* Reference to a remote file */
				if((chPtr = strstr(cbs->href, "file:")) != NULL)
				{
					/*
					 * For the following cases, the file is local:
					 * file:/some/path				-- no host
					 * file:///some/path			-- null host
					 * file://localhost/some/path	-- localhost
					 */

					/* skip the "file:" marker */
					chPtr += 5;
					if(!strcmp(chPtr, "///"))
						chPtr += 3;
					else if(!strcmp(chPtr, "//localhost"))
						chPtr += 11;
					else if(*chPtr != '/')
					{
						fprintf(stderr, "%s: unsupported hyperlink type\n",
							cbs->href);
							return;
					}
				}
				else
					chPtr = cbs->href;

				/* read and load the file */
				readFile(html, chPtr);
			}
			break;
		default:
			fprintf(stderr, "%s: unsupported hyperlink type\n", cbs->href);
	}
}

