/* gui.c
   Implement functions of the GUI (Graphical User Interface).  
   The GUI is the X window system.  This file is the interface
   to all other X-related functions in panel.c, error.c, save.c,
   text.c, etc. */


     /*---------------------------------------------------------------*/
     /* Xgopher        version 1.1     20 April 1991                  */
     /*                version 1.0     04 March 1991                  */
     /* X window system client for the University of Minnesota        */
     /*                                Internet Gopher System.        */
     /* Allan Tuchman, University of Illinois at Urbana-Champaign     */
     /*                Computing Services Office                      */
     /* Copyright 1992 by                                             */
     /*           the Board of Trustees of the University of Illinois */
     /* Permission is granted to freely copy and redistribute this    */
     /* software with the copyright notice intact.                    */
     /*---------------------------------------------------------------*/


#include <stdio.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Simple.h>	/* where XtNcursor is */
#include <X11/Xmu/Converters.h>
#include <X11/cursorfont.h>
#include <X11/Xaw/Text.h>


#include "conf.h"
#include "gui.h"
#include "error.h"
#include "globals.h"
#include "xglobals.h"
#include "resources.h"
#include "util.h"
#include "help.h"
#include "list.h"
#include "cso.h"
#include "index.h"
#include "gopher.h"


gopherAppResources * getApplicationResources(
);


static	Widget	topLevel;
static	Boolean	windowStarted = False;

static	Boolean errorDialogExists = FALSE;
static	Boolean infoDialogExists = FALSE;
static	Boolean saveDialogExists = FALSE;
static	Boolean csoPanelExists = FALSE;
static	Boolean indexPanelExists = FALSE;

static  XtWorkProcId nullWPID = (XtWorkProcId) 0;
static  XtWorkProcId saveWPID  = (XtWorkProcId) 0,
		     indexWPID = (XtWorkProcId) 0,
		     csoWPID   = (XtWorkProcId) 0,
		     errWPID   = (XtWorkProcId) 0,
		     infoWPID  = (XtWorkProcId) 0;

static char	*emptyList[] = {"<none>", NULL};


/* initGUI
   Initialize the X window system */

BOOLEAN
initGUI(argc, argv)
int	*argc;
char	**argv;
{
	static XtPointer	scr;
	static XtConvertArgRec screenConvertArg[] = {
		{XtAddress, (XtPointer)&scr, sizeof(Screen *)}
		};

        /* initialize X window system */

        topLevel   = XtInitialize (gopherName, GOPHER_CLASS,
                        NULL, 0, argc, argv);

	scr = (XtPointer) XtScreen(topLevel);
	XtAddConverter(XtRString, XtRPixmap, XmuCvtStringToBitmap,
			screenConvertArg, XtNumber(screenConvertArg));
		
	if (! getOptions(*argc, argv)) return FALSE;

	makeGopherPanel(topLevel);

	XtRealizeWidget(topLevel);

	windowStarted = True;

	return TRUE;
}


/* getOptions
   Use the GUI (X window system) to access application options */

static BOOLEAN
getOptions(argc, argv)
int	argc;
char	**argv;
{
	if ((appResources = getApplicationResources(topLevel, argc, argv)) ==
									NULL) 
		return False;
	else
		return True;
}


/* makeSaveDialogWorkProc
   X work proc to create the Save Dialog during spare cycles. */

static Boolean
makeSaveDialogWorkProc(topLevel)
Widget	topLevel;
{
	makeSaveDialog(topLevel);
	saveDialogExists = TRUE;
	return TRUE;
}


/* makeInfoDialogWorkProc
   X work proc to create the Info Dialog during spare cycles. */

static Boolean
makeInfoDialogWorkProc(topLevel)
Widget	topLevel;
{
	makeInfoDialog(topLevel);
	infoDialogExists = TRUE;
	return TRUE;
}


/* makeErrorDialogWorkProc
   X work proc to create the Error Dialog during spare cycles. */

static Boolean
makeErrorDialogWorkProc(topLevel)
Widget	topLevel;
{
	makeErrorDialog(topLevel);
	errorDialogExists = TRUE;
	return TRUE;
}


/* makeCsoPanelWorkProc
   X work proc to create the CSO (ph) panel during spare cycles. */

static Boolean
makeCsoPanelWorkProc(topLevel)
Widget	topLevel;
{
	makeCsoPanel(topLevel);
	csoPanelExists = TRUE;
	return TRUE;
}


/* makeIndexWorkProc
   X work proc to create the Index Search panel during spare cycles. */

static Boolean
makeIndexWorkProc(topLevel)
Widget	topLevel;
{
	makeIndexPanel(topLevel);
	indexPanelExists = TRUE;
	return TRUE;
}


/* markCurrentDirectory
   set a bookmark at the current directory (by an event other than
   the standard click on the bookmark button) */

void
markCurrentDirectory()
{
	markProc(NULL, NULL, NULL);
}


/* doUserRequests
   The main loop in the GUI process - await user events, and process them. */

void
doUserRequests()
{
	saveWPID = XtAddWorkProc (makeSaveDialogWorkProc, (XtPointer) topLevel);
	indexWPID = XtAddWorkProc (makeIndexWorkProc, (XtPointer) topLevel);
	csoWPID = XtAddWorkProc (makeCsoPanelWorkProc, (XtPointer) topLevel);
	infoWPID = XtAddWorkProc (makeInfoDialogWorkProc, (XtPointer) topLevel);
	errWPID = XtAddWorkProc (makeErrorDialogWorkProc, (XtPointer) topLevel);

	XtMainLoop();
}


/* makeXThings
   make icons and bitmaps that the application will need */

#include "bitmaps/pgdown.xbm"
#include "bitmaps/pgup.xbm"
#include <X11/bitmaps/gray3>

void
makeXThings()
{
	pageDownPixmap = XCreateBitmapFromData (XtDisplay(topLevel),
				RootWindowOfScreen(XtScreen(topLevel)),
						pgdown_bits,
						pgdown_width, pgdown_height);

	pageUpPixmap = XCreateBitmapFromData (XtDisplay(topLevel),
				RootWindowOfScreen(XtScreen(topLevel)),
						pgup_bits,
						pgup_width, pgup_height);

	gray = XCreatePixmapFromBitmapData (XtDisplay(topLevel),
				RootWindowOfScreen(XtScreen(topLevel)),
						gray3_bits,
						gray3_width, gray3_height,
				BlackPixelOfScreen(XtScreen(topLevel)),
				WhitePixelOfScreen(XtScreen(topLevel)),
				DefaultDepthOfScreen(XtScreen(topLevel))
						);
}


/* showError
   Cause an error message to be displayed on the user's screen. */

void
showError(message)
char    *message;
{
	if (windowStarted) {
		if (! errorDialogExists) {
			makeErrorDialog(topLevel);
			errorDialogExists = TRUE;
			if (errWPID != nullWPID) XtRemoveWorkProc (errWPID);
		}
		displayError(message, False);
	} else {
		fprintf (stderr, "NOTE!\n%s\n", message);
	}
	LOG (logFP, "ERROR:\n%s\n", message);
}


/* showFatalError
   Cause a fatal error message to be displayed on the user's screen. */

void
showFatalError(message)
char    *message;
{
	if (windowStarted) {
		if (! errorDialogExists) {
			makeErrorDialog(topLevel);
			errorDialogExists = TRUE;
			if (errWPID != nullWPID) XtRemoveWorkProc (errWPID);
		}
		displayError(message, True);
	} else {
		fprintf (stderr, "NOTE: Unrecoverable error!\n%s\n", message);
	}
	LOG (logFP, "FATAL ERROR:\n%s\n", message);
}


/* showInfo
   Cause an information message to be displayed on the user's screen. */

void
showInfo(message)
char    *message;
{
	if (windowStarted) {
		if (! infoDialogExists) {
			makeInfoDialog(topLevel);
			infoDialogExists = TRUE;
			if (infoWPID != nullWPID) XtRemoveWorkProc (infoWPID);
		}
		displayInfo(message);
	} else {
		fprintf (stderr, "NOTE!\n%s\n", message);
	}
}


/* showStatus
   indicate status of the gopher process to the user by messages, icons,
   and/or cursor changes. */

void
showStatus(message, statType)
char    *message;
int     statType;
{

	if (! windowStarted) return;

        changeStatusLabel (message, statType);
}


/* displayCurrent
   display the current directory. */

void
displayCurrent()
{
	gopherItemP	gi;
	int		i, need;
	static	int	dirStringListLen = 0;
	static	char	**dirStringList = NULL;
	gopherDirP      current = getCurrentDirectory();


	LOG(logFP,
	    "Current directory: \'%s'\n\tSelector \'%s\'\n\tat \'%s\' %d\n",
		USER_STRING(current->selectorItem),
		vStringValue(&(current->selectorItem->selector)),
		current->selectorItem->host,
		current->selectorItem->port);

	if (!windowStarted) return;
	if (current->created == NOT_LOADED) updateDirectory(current);

	/* The array of string pointers is allocated a reasonably
	   large amount initially.  After this, if more are needed,
	   the current array is freed, then the new required number
	   is allocated.  */

	need = itemListLength(&(current->contents)) + 1;
	if (need > dirStringListLen) {
		need = need > MIN_DIR_STRING_LIST_LEN ?
				need : MIN_DIR_STRING_LIST_LEN;
		if (dirStringList != NULL) free(dirStringList);
		dirStringList = NULL;
		if ((dirStringList = (char **) malloc(need * sizeof(char *)))
								== NULL) {
			showError(
"Unable to allocate sufficient memory to display this directory.");
			changeDirList(emptyList);
			return;
		}
	}


	changeDirLabel(USER_STRING(current->selectorItem));

	i = 0;
	if ((gi = current->contents.first) != NULL) {
		for ( ; gi != NULL; i++, gi = gi->next)
			dirStringList[i] = USER_STRING_PREFIX(gi);
	}

	dirStringList[i] = (char *) NULL;
	
	changeDirList(dirStringList);

	return;
}


/* displayBookmarks
   display the current bookmark list. */

void
displayBookmarks()
{
	gopherDirP	gd;
	int		i, need;
	static	int	markStringListLen = 0;
	static	char	**markStringList = NULL;

	if (!windowStarted) return;

	/* The array of string pointers is allocated a reasonably
	   large amount initially.  After this, if more are needed,
	   the current array is freed, then the new required number
	   is allocated.  */

	need = markListLength() + 1;
	if (need > markStringListLen) {
		need = need > MIN_MARK_STRING_LIST_LEN ?
				need : MIN_MARK_STRING_LIST_LEN;
		if (markStringList != NULL) free(markStringList);
		markStringList = NULL;
		if ((markStringList = (char **) malloc(need * sizeof(char *)))
								== NULL) {
			showError(
"Unable to allocate sufficient memory to display the bookmark list.");
			changeMarkList(emptyList);
			return;
		}
	}

	i = 0;
	if ((gd = firstMark()) != NULL) {
		for ( ; gd != NULL; i++, gd = gd->next)
			markStringList[i] = USER_STRING(gd->selectorItem);
	}

	markStringList[i] = (char *) NULL;
	
	changeMarkList(markStringList);

	return;
}


/* showFile
   display a file on the screen. */

void
showFile(title, fileName, indexString)
char	*title;
char	*fileName;
char	*indexString;
{

	if (! windowStarted) return;

	if (! saveDialogExists) {
		makeSaveDialog(topLevel);
		saveDialogExists = TRUE;
		if (saveWPID != nullWPID) XtRemoveWorkProc (saveWPID);
	}

	if (indexString == NULL) {
		displayTempFile(topLevel, title, fileName);
	} else {
		displayIndexTempFile(topLevel, title, fileName, indexString);
	}

	return;
}


/* showNameServer
   display a CSO name server panel. */

void
showNameServer(title, s)
char	*title;
int	s;
{

	if (! windowStarted) return;

	LOG (logFP, "CSO name server at:%s\n", title);

	if (! csoPanelExists) {
		makeCsoPanel(topLevel);
		csoPanelExists = TRUE;
		if (csoWPID != nullWPID) XtRemoveWorkProc (csoWPID);
	}
	if (! displayCsoPanel(title, s)) {
		showError(
		"Cannot display CSO Name Server\n(Is one already active?)");
	}

	return;
}


/* showIndex
   display a Index search panel. */

void
showIndex(gi)
gopherItemP	gi;
{

	if (! windowStarted) return;

	LOG (logFP, "Index search:%s\n", USER_STRING(gi));

	if (! indexPanelExists) {
		makeIndexPanel(topLevel);
		indexPanelExists = TRUE;
		if (indexWPID != nullWPID) XtRemoveWorkProc (indexWPID);
	}

	displayIndexPanel((XtPointer) gi, USER_STRING(gi));

	return;
}


/* showHelp
   show a help item in a text window */

void
showHelp(key)
char	*key;
{
        char    *string;
	char	title[HELP_SEC_TITLE_LEN];

        string = getHelpText(key, title);
        if (string == NULL)
                showError ("No help is available.");
        else if (title[0] == '\0') 
		displayTextString(topLevel, "Gopher Assistance", string);
	else 
		displayTextString(topLevel, title, string);
	
	return;
}


/* positionPopup
   set the position of a popup window. 
   w     is the popup widget.
   from  takes the values:{ POS_none, POS_pointer, POS_appPanel, POS_screen}
   fromWidget is the shell widget to position relative to (for POS_appPanel)
   x, y  are integer percent values for a point of the appPanel or screen
   hJust/vJust are {0, 1, 2} for left/top, center, right/bottom justification
	 of the popup against the point x, y.
   */

void
positionPopup(w, from, fromWidget, x, y, hJust, vJust)
Widget	w, fromWidget;
int	from;
int	x, y;
int	hJust, vJust;
{
	Arg		args[5];
	Cardinal	n;
	Position	rootX, rootY;
	Dimension	popupWidth, popupHeight;
	Dimension	screenWidth, screenHeight;


	if (w == NULL) return;
	if (from == POS_none) return;

	screenWidth = WidthOfScreen(XtScreen(w));
	screenHeight = HeightOfScreen(XtScreen(w));

	if (XtClass(w) == transientShellWidgetClass) {
		XtRealizeWidget(w);
	}

	/* find a reference point */

	switch (from) {
	    case POS_pointer:
		{
		    Window	root, child;
		    int		ptrX, ptrY, winX, winY;
		    unsigned int	keysButtons;

		    XQueryPointer(XtDisplay(w), XtWindow(XtParent(w)),
				&root, &child, &ptrX, &ptrY, &winX, &winY,
				&keysButtons);
		    
		    rootX = (Position) ptrX;
		    rootY = (Position) ptrY;
		}
		break;

	    case POS_appPanel:
		{
		    Dimension	panelWidth, panelHeight;
		    Position	relX, relY;

		    n = 0;
		    XtSetArg(args[n], XtNwidth, &panelWidth);  n++;
		    XtSetArg(args[n], XtNheight, &panelHeight);  n++;
		    XtGetValues(fromWidget, args, n);

		    relX = panelWidth * x / 100;
		    relY = panelHeight * y / 100;
		    XtTranslateCoords(fromWidget, relX, relY, &rootX, &rootY);
		}
		break;

	    case POS_screen:
		{
		    rootX = screenWidth * x / 100;
		    rootY = screenHeight * y / 100;
		}
		break;
	}

	/* apply justification left/right, top/bottom */

	n = 0;
	XtSetArg(args[n], XtNwidth, &popupWidth);  n++;
	XtSetArg(args[n], XtNheight, &popupHeight);  n++;
	XtGetValues(w, args, n);

	if (hJust == 1) rootX -= popupWidth / 2;
	else if (hJust == 2) rootX -= popupWidth;

	if (vJust == 1) rootY -= popupHeight / 2;
	else if (vJust == 2) rootY -= popupHeight;

	/* force the popup to be on screen */

	if (rootX < 0)	rootX = 0;
	else if (rootX + popupWidth > screenWidth)
			rootX = screenWidth - popupWidth;

	if (rootY < 0)	rootY = 0;
	else if (rootY + popupHeight > screenHeight)
			rootY = screenHeight - popupHeight;

	if (XtClass(w) == transientShellWidgetClass  ||  XtIsRealized(w)) {
		XtMoveWidget(w, (Position) rootX, (Position) rootY);
	} else {
		static char	geom[16];

		sprintf(geom, "+%d+%d", (int) rootX, (int) rootY);
		XtSetArg(args[0], XtNgeometry, geom);
		XtSetValues(w, args, (Cardinal) 1);
	}

	return;
}


/* setTextWidgetSize
   Use the selected font size and other attributes to set the
   size of a text widget.  This routine should be called just
   after a text widget is created. */

void
setTextWidgetSize(textWidget, width, height)
Widget	textWidget;
int	width, height;
{
	XawTextWrapMode	wrap;
	XFontStruct	*textFontStruct;
	int		direction, ascent, descent;
	XCharStruct	overall;
	Position	topMargin, bottomMargin;
	Position	leftMargin, rightMargin;
	Dimension	w, h;
	Arg		args[10];
	Cardinal	n;

	if (textWidget == NULL) return;

	n=0;
	if (height > 1) {
		XtSetArg(args[n], XtNwrap, &wrap);  n++;
	}
	XtSetArg(args[n], XtNfont, &textFontStruct);  n++;
	XtSetArg(args[n], XtNtopMargin, &topMargin);  n++;
	XtSetArg(args[n], XtNbottomMargin, &bottomMargin);  n++;
	XtSetArg(args[n], XtNleftMargin, &leftMargin);  n++;
	XtSetArg(args[n], XtNrightMargin, &rightMargin);  n++;
	XtGetValues(textWidget, args, n);
	if (height > 1) {
		if (wrap == XawtextWrapNever) {
			n=0;
			XtSetArg(args[n], XtNscrollHorizontal,
				XawtextScrollWhenNeeded);  n++;
			XtSetValues(textWidget, args, n);
		}
	} 

	/* use some randomly-sized characters to get width in case
	   it is a proportionately-spaced font */

	XTextExtents(textFontStruct, "mljf", 4,
		&direction, &ascent, &descent, &overall);

	w = (Dimension) ( leftMargin + rightMargin + 
			  (int) (overall.width * ( (float) width / 4.0) ) );
	h = (Dimension) ( (topMargin + bottomMargin) +
			  ( (ascent + descent) * height) );
	
	/* The following is a hack.
	   The scrollbar steals away space from the text content
	   of the window if and when it is displayed.  If
	   "scrollWhenNeeded" is set, then the scrollbar does not
	   exist yet and may not ever exist.  So we have no way
	   to find out its width.

	   We're not as likely to have a horizontal bar as a vertical
	   one, so ignore the horizontal one.  It's less crucial.
	   However, we'd like to always provide a text display which
	   is at least 80 characters wide by default.

	   So, We'll just add 14 pixels for the scrollbar thickness.
	   Which is the default provided in the Xaw Scrollbar.c
	   source.  I hate doing this, but there seems to be no
	   other way.  We'll similarly add 2 pixels for border
	   width, then quietly slink off into the night.
	*/
	w = w + 16;

	n=0;
	XtSetArg(args[n], XtNwidth, w);  n++;
	XtSetArg(args[n], XtNheight, h); n++;
	XtSetValues(textWidget, args, n);
}


/* getTextSize
   To find out the size in pixels required to hold a certain number of
   rows and columns of text, given the font and other attributes
   of a widget.  This routine should only be called for widgets
   which contain text, such as label and list. */

void
getTextSize(someWidget, width, height, w, h)
Widget		someWidget;
int		width, height;
Dimension	*w, *h;
{
	XawTextWrapMode	wrap;
	XFontStruct	*textFontStruct;
	int		direction, ascent, descent;
	XCharStruct	overall;
	Position	topMargin, bottomMargin;
	Arg		args[10];
	Cardinal	n;

	if (someWidget == NULL) return;

	n=0;
	XtSetArg(args[n], XtNfont, &textFontStruct);  n++;
	XtGetValues(someWidget, args, n);

	/* use some randomly-sized characters to get width in case
	   it is a proportionately-spaced font */

	XTextExtents(textFontStruct, "mljf", 4,
		&direction, &ascent, &descent, &overall);

	*w = (Dimension) ((int) (overall.width * ( (float) width / 4.0) ) );
	*h = (Dimension) (ascent + descent) * height;
}
