/* 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.3     08 April 1993                  */
     /*                version 1.2     20 November 1992               */
     /*                version 1.1     20 April 1992                  */
     /*                version 1.0     04 March 1992                  */
     /* X window system client for the University of Minnesota        */
     /*                                Internet Gopher System.        */
     /* Allan Tuchman, University of Illinois at Urbana-Champaign     */
     /*                Computing and Communications Services Office   */
     /* Copyright 1992, 1993 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 "panel.h"
#include "error.h"
#include "globals.h"
#include "xglobals.h"
#include "appres.h"
#include "popres.h"
#include "typeres.h"
#include "util.h"
#include "status.h"
#include "compatR4.h"
#include "itemList.h"
#include "dirList.h"
#include "markList.h"
#include "itemInfo.h"
#include "help.h"
#include "cso.h"
#include "index.h"
#include "text.h"
#include "options.h"
#include "single.h"
#include "gopher.h"

#include "osdep.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	Boolean optionsPanelExists = FALSE;
static	Boolean singlePanelExists = FALSE;

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

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


static 	String	fallbackResources[] = {
			"",
			(char *) 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 *)}
		};
	Arg			args[5];
	XrmOptionDescRec	opts[2];
	Cardinal		narg = (Cardinal) 0,
				nopt = (Cardinal) 0;
#ifndef XGOPHER_X11R4
	int			*cArgc = argc;
#else
	Cardinal		*cArgc = (Cardinal *) argc;
#endif

        /* initialize X window system */

        topLevel   = XtAppInitialize (&appcon, GOPHER_CLASS,
                        opts, nopt, cArgc, argv,
			fallbackResources, args, narg);

	scr = (XtPointer) XtScreen(topLevel);
	XtAppAddConverter(appcon, XtRString, XtRPixmap, XmuCvtStringToBitmap,
			screenConvertArg, XtNumber(screenConvertArg));

	XtAppSetTypeConverter(appcon, XtRString, XgRShowLevel,
			(XtTypeConverter) cvtStringToShowLevel,
			NULL, (Cardinal) 0,
			XtCacheNone, NULL); 

	XtAppSetTypeConverter(appcon, XtRString, XgRPositionFrom,
			(XtTypeConverter) cvtStringToPositionFrom,
			NULL, (Cardinal) 0,
			XtCacheNone, NULL); 

	XtAppSetTypeConverter(appcon, XtRString, XgRJustification,
			(XtTypeConverter) cvtStringToJustification,
			NULL, (Cardinal) 0,
			XtCacheNone, NULL); 

	XtAppSetTypeConverter(appcon, XtRString, XgRCopyType,
			(XtTypeConverter) cvtStringToCopyType,
			NULL, (Cardinal) 0,
			XtCacheNone, NULL); 
		
	if (! getOptions(*argc, argv)) return FALSE;

	windowStarted = True;

	return TRUE;
}


/* "done" macro from the Xmu library - used by those type converters */

#define	done(type, value) \
	{							\
	    if (toVal->addr != NULL) {				\
		if (toVal->size < sizeof(type)) {		\
		    toVal->size = sizeof(type);			\
		    return False;				\
		}						\
		*(type*)(toVal->addr) = (value);		\
	    }							\
	    else {						\
		static type static_val;				\
		static_val = (value);				\
		toVal->addr = (XtPointer)&static_val;		\
	    }							\
	    toVal->size = sizeof(type);				\
	    return True;					\
	}


/* cvtStringToShow
   type converter for show resource keywords to enumerated type values */

Boolean
cvtStringToShowLevel(dpy, args, numArgs, fromVal, toVal, closureRet)
Display		*dpy;
XrmValuePtr	args;
Cardinal	*numArgs;
XrmValuePtr	fromVal, toVal;
XtPointer	*closureRet;
{
	char	*s = (char *) fromVal->addr;

	if (s == NULL) return;

	if (strcasecmp(showEAll, s) == 0)
					done( int, (int) showAll );
	if (strcasecmp(showEKnown, s) == 0)
					done( int, (int) showKnown );
	if (strcasecmp(showEAccessible, s) == 0)
					done( int, (int) showAccessible );
	if (strcasecmp(showEAvailable, s) == 0)
					done( int, (int) showAvailable );

	XtDisplayStringConversionWarning(dpy, s, XgRShowLevel);
	return False;
}


/* cvtStringToPositionFrom
   type converter for positionFrom resource to enumerated type values */

Boolean
cvtStringToPositionFrom(dpy, args, numArgs, fromVal, toVal, closureRet)
Display		*dpy;
XrmValuePtr	args;
Cardinal	*numArgs;
XrmValuePtr	fromVal, toVal;
XtPointer	*closureRet;
{
	char	*s = (char *) fromVal->addr;

	if (s == NULL) return;

	if (strcasecmp(fromENone, s) == 0)
					done( int, (int) from_none );
	if (strcasecmp(fromEPointer, s) == 0)
					done( int, (int) from_pointer );
	if (strcasecmp(fromEMain, s) == 0)
					done( int, (int) from_main );
	if (strcasecmp(fromEScreen, s) == 0)
					done( int, (int) from_screen );
	if (strcasecmp(fromEWidget, s) == 0)
					done( int, (int) from_widget );

	XtDisplayStringConversionWarning(dpy, s, XgRPositionFrom);
	return False;
}


/* cvtStringToJustification
   type converter for Justification resource to enumerated type values */

Boolean
cvtStringToJustification(dpy, args, numArgs, fromVal, toVal, closureRet)
Display		*dpy;
XrmValuePtr	args;
Cardinal	*numArgs;
XrmValuePtr	fromVal, toVal;
XtPointer	*closureRet;
{
	char	*s = (char *) fromVal->addr;

	if (s == NULL) return;

	if (strcasecmp(justETop, s) == 0  || strcasecmp(justELeft, s) == 0 )
					done( int, (int) justify_top_left );
	if (strcasecmp(justECenter, s) == 0)
					done( int, (int) justify_center );
	if (strcasecmp(justEBottom, s) == 0 || strcasecmp(justERight, s) == 0)
					done( int, (int) justify_bottom_right );

	XtDisplayStringConversionWarning(dpy, s, XgRJustification);
	return False;
}


/* cvtStringToCopyType
   type converter for datatype resource keywords to enumerated type values */

Boolean
cvtStringToCopyType(dpy, args, numArgs, fromVal, toVal, closureRet)
Display		*dpy;
XrmValuePtr	args;
Cardinal	*numArgs;
XrmValuePtr	fromVal, toVal;
XtPointer	*closureRet;
{
	char	*s = (char *) fromVal->addr;

	if (s == NULL) return;

	if (strcasecmp(copyTypeENone, s) == 0)
					done( int, (int) copyTypeNone );
	if (strcasecmp(copyTypeEAscii, s) == 0)
					done( int, (int) copyTypeAscii );
	if (strcasecmp(copyTypeEBinary, s) == 0  ||
	    strcasecmp(copyTypeEBinaryEOF, s) == 0)
					done( int, (int) copyTypeBinaryEOF );
	if (strcasecmp(copyTypeEUnspec, s) == 0)
					done( int, (int) copyTypeUnspec );

	XtDisplayStringConversionWarning(dpy, s, XgRCopyType);
	return False;
}


/* initMainPanel
   creates the main Xgopher panel */

BOOLEAN
initMainPanel()
{

	makeGopherPanel(topLevel);

	XtRealizeWidget(topLevel);

	makeStatusPanel(topLevel);
}


/* 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;
}


/* closeGUIandQuit
   terminate the GUI (X window system) cleanly, then exit the application */

void
closeGUIandQuit(rc)
int	rc;
{
	XtDestroyApplicationContext(appcon);
	exit(rc);
}


/* loadMarks
   load bookmarks from the bookmark file */

void
loadMarks()
{
	markLoadProc(NULL, NULL, NULL);
}


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

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


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

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


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

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


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

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


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

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


/* makeOptionsWorkProc
   X work proc to create the Options panel during spare cycles. */

static Boolean
makeOptionsWorkProc(clientData)
XtPointer	clientData;
{
	Widget		topLevel = (Widget) clientData;
	makeOptionsPanel(topLevel);
	optionsPanelExists = TRUE;
	return TRUE;
}


/* makeSingleWorkProc
   X work proc to create the Single Item panel during spare cycles. */

static Boolean
makeSingleWorkProc(clientData)
XtPointer	clientData;
{
	Widget		topLevel = (Widget) clientData;
	makeSinglePanel(topLevel);
	singlePanelExists = 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    = XtAppAddWorkProc (appcon, makeSaveDialogWorkProc,
					(XtPointer) topLevel);
	indexWPID   = XtAppAddWorkProc (appcon, makeIndexWorkProc,
					(XtPointer) topLevel);
	csoWPID	    = XtAppAddWorkProc (appcon, makeCsoPanelWorkProc,
					(XtPointer) topLevel);
	infoWPID    = XtAppAddWorkProc (appcon, makeInfoDialogWorkProc,
					(XtPointer) topLevel);
	singleWPID  = XtAppAddWorkProc (appcon, makeSingleWorkProc,
					(XtPointer) topLevel);
	optionsWPID = XtAppAddWorkProc (appcon, makeOptionsWorkProc,
					(XtPointer) topLevel);
	errWPID	    = XtAppAddWorkProc (appcon, makeErrorDialogWorkProc,
					(XtPointer) topLevel);

	XtAppMainLoop(appcon);
}


/* makeXThings
   make things such as icons, bitmaps, and parsed translation tables
   that the application will need */

#include "oneLine.h"
#include "bitmaps/pgdown.xbm"
#include "bitmaps/pgup.xbm"
#include "bitmaps/pulldown.xbm"
#include "bitmaps/check.xbm"
#include "bitmaps/uncheck.xbm"
#include <X11/bitmaps/gray3>

void
makeXThings()
{
	wmDeleteAtom = XInternAtom(XtDisplay(topLevel), "WM_DELETE_WINDOW",
				False);
				
	oneLineParsed = XtParseTranslationTable(OneLineTextTr);

	pageDownPixmap = XCreateBitmapFromData (XtDisplay(topLevel),
				RootWindowOfScreen(XtScreen(topLevel)),
						(char *) pgdown_bits,
						pgdown_width, pgdown_height);

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

	pulldownPixmap = XCreateBitmapFromData (XtDisplay(topLevel),
				RootWindowOfScreen(XtScreen(topLevel)),
						(char *) pulldown_bits,
						pulldown_width,
						pulldown_height);

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

	checkPixmap = XCreateBitmapFromData (XtDisplay(topLevel),
				RootWindowOfScreen(XtScreen(topLevel)),
						(char *) check_bits,
						check_width, check_height);

	uncheckPixmap = XCreateBitmapFromData (XtDisplay(topLevel),
				RootWindowOfScreen(XtScreen(topLevel)),
						(char *) uncheck_bits,
						uncheck_width, uncheck_height);
}


/* getSelectedItem
   get the currently selected gopher item */

gopherItemP
getSelectedItem()
{
	return whichItemSelected();
}


/* 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, host, port)
char		*message;
statusType	statType;
char		*host;
int		port;
{

	if (! windowStarted) return;

        if (statType != STAT_USER) {
		displayStatusPanel(statType, message, host, port);
	} else {
		removeStatusPanel();
	}
}


/* displayCurrent
   display the current directory. */

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


	if (current != NULL) {
		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 != NULL) {
		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.  */

	if (current != NULL) {
		need = itemListLength(&(current->contents)) + 1;
	} else {
		need = 0;
	}
	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;
		}
	}


	if (current != NULL) {
		changeDirLabel(USER_STRING(current->selectorItem));
	} else {
		changeDirLabel("Empty Directory");
	}

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

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

	return;
}


/* displayBookmarks
   display the current bookmark list. */

void
displayBookmarks()
{
	gopherItemP	gi;
	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 ((gi = firstMark()) != NULL) {
		while (gi != NULL) {
			markStringList[i] = USER_STRING_PREFIX(gi);
			i++;
			gi = nextItem(gi);
		}
	}

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

	return;
}


/* checkPanelButtons
   ensure that the panel buttons have proper sensitivity. */

void
checkPanelButtons()
{
	checkButtonState(BS_all);
}


/* clearBookmarks
   properly clear the bookmark list. */

void
clearBookmarks()
{
	unmarkAllProc(NULL, NULL, NULL);
}


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

void
showFile(gi, title, fileName, indexString)
gopherItemP	gi;
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, fileTextClass, gi, title, fileName);
	} else {
		displayIndexTempFile(topLevel, fileTextClass, gi, 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;
}


/* showOptionsPanel
   display the options panel. */

void
showOptionsPanel()
{

	if (! windowStarted) return;

	if (! optionsPanelExists) {
		makeOptionsPanel(topLevel);
		optionsPanelExists = TRUE;
		if (optionsWPID != nullWPID) XtRemoveWorkProc (optionsWPID);
	}

	displayOptionsPanel();

	return;
}


/* showSinglePanel
   display the single gopher item entry panel. */

void
showSinglePanel()
{

	if (! windowStarted) return;

	if (! singlePanelExists) {
		makeSinglePanel(topLevel);
		singlePanelExists = TRUE;
		if (singleWPID != nullWPID) XtRemoveWorkProc (singleWPID);
	}

	displaySinglePanel();

	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, helpTextClass, (gopherItemP) NULL,
					"Gopher Assistance", string);
	else 
		displayTextString(topLevel, helpTextClass, (gopherItemP) NULL,
					title, string);
	
	return;
}


/* showItemInfo
   show information about a gopher item in a text window */

void
showItemInfo(gi)
gopherItemP	gi;
{
        char    *string;

        string = getItemInfoText(gi, 0);
        if (string == NULL)
                showError ("No information on this item is available.");
	else 
		displayTextString(topLevel, infoTextClass, (gopherItemP) NULL,
					"Information", string);
	
	return;
}


/* setPopupGeometry
   preset the geometry specification for topLevelShell's before they
   are realized. */

void
setPopupGeometry(w, placement)
Widget			w;
popupPosResources	*placement;
{
	Arg		args[5];
	Cardinal	n;
	Position	rootX, rootY;
	Dimension	popupWidth, popupHeight;
	Dimension	screenWidth, screenHeight;


	if (w == NULL) return;
	if (XtClass(w) != topLevelShellWidgetClass) return;
	if (placement->positionFrom == from_none) return;

	/* the value "+0+0" is sufficient to let us 
	   move the window later when it is displayed. */

	XtSetArg(args[0], XtNgeometry, "+0+0");
	XtSetValues(w, args, (Cardinal) 1);

	return;
}


/*  findPopupPosition
    Auxiliary routine used by positionAPopup.  This routine computes the
    target position on the root window, but doesn't actually do the move */

void
findPopupPosition(w, fromWidget, placement, rootX, rootY)
Widget			w;
Widget			fromWidget;
popupPosResources	*placement;
Position		*rootX, *rootY;
{
	   /*
	     w     	is the popup widget.
	     fromWidget	is the shell widget to position relative to
			(for from_main or from_widget)
	     placement	is a struct containing the positioning information.	
	   */
	Arg		args[5];
	Cardinal	n;
	Dimension	popupWidth, popupHeight;
	Dimension	screenWidth, screenHeight;


	*rootX = 0;
	*rootY = 0;

	if (w == NULL) return;
	if (placement->positionFrom == from_none) return;

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

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

	/* find a reference point */

	switch (placement->positionFrom) {
	    case from_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 from_main:
	    case from_widget:
		{
		    Dimension	panelWidth, panelHeight;
		    Position	relX, relY;

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

		    if (placement->xPercent) {
			    relX = ((int) panelWidth * placement->xPosition)
			    		/ 100;
		    } else {
			    relX = placement->xPosition;
		    }
		    if (placement->yPercent) {
			    relY = ((int) panelHeight * placement->yPosition)
					/ 100;
		    } else {
			    relY = placement->yPosition;
		    }
		    XtTranslateCoords(fromWidget, relX, relY, rootX, rootY);
		}
		break;

	    case from_screen:
		{
		    if (placement->xPercent) {
			    *rootX = ((int) screenWidth * placement->xPosition)
					/ 100;
		    } else {
			    *rootX = placement->xPosition;
		    }

		    if (placement->yPercent) {
			    *rootY = ((int) screenHeight * placement->yPosition)
					/ 100;
		    } else {
			    *rootY = placement->yPosition;
		    }
		}
		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 (placement->horizontalJustification == justify_center)
						*rootX -= popupWidth / 2;
	else if (placement->horizontalJustification == justify_bottom_right)
						*rootX -= popupWidth;

	if (placement->verticalJustification == justify_center) 
						*rootY -= popupHeight / 2;
	else if (placement->verticalJustification == justify_bottom_right) 
						*rootY -= popupHeight;

	/* force the popup to be on screen */

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

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


/* positionAPopup
   set the position of a popup window. */

void
positionAPopup(w, fromWidget, placement)
Widget			w;
Widget			fromWidget;
popupPosResources	*placement;
{
	   /*
	     w     	is the popup widget.
	     fromWidget	is the shell widget to position relative to
			(for from_main or from_widget)
	     placement	is a struct containing the positioning information.	
	   */
	Arg		args[5];
	Cardinal	n;
	Position	rootX, rootY;


	if (w == NULL) return;
	if (placement->positionFrom == from_none) return;

	findPopupPosition(w, fromWidget, placement, &rootX, &rootY);

	if (XtClass(w) == transientShellWidgetClass) {
		/*
		XtMoveWidget(w, (Position) rootX, (Position) rootY);
		*/
		n = 0;
		XtSetArg(args[n], XtNx, rootX);  n++;
		XtSetArg(args[n], XtNy, rootY);  n++;
		XtSetValues(w, args, n);
	} else if (XtIsRealized(w)) {
		XMoveWindow(XtDisplay(w), XtWindow(w),
				(int) rootX, (int) rootY);
	} else {

		/* we likely won't get here because most popups
		   are realized earlier in order to install the
		   ICCCM event handling easily. */

		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;
}


static BOOLEAN	ding = FALSE;
#define MAX_WAIT_TIME	3000	/* milliseconds */

/* maxWaitTimeProc
   we don't want to be dispatching events for more than this time */

static void
maxWaitTimeProc(clientData, id)
XtPointer	clientData;
XtIntervalId	*id;
{
	ding = TRUE;
}


/* WaitForAllPendingEventsAndExpose
   Clears out the event queue and optionally waits until it sees
   an expose event for a specific window.  In the latter case,
   it will also offer a timeout to prevent indefinite hangs. */

void
WaitForAllPendingEventsAndExpose(dpy, needToSee)
Display		*dpy;
Window		needToSee;
{
	static XEvent	event;
	BOOLEAN		gotExposed=FALSE;
	XtIntervalId	waitTimerId = (XtIntervalId) 0;

	XSync(dpy, False);
	if (needToSee == (Window) NULL) {
		while( XtAppPending (appcon) ) {
			XtAppNextEvent(appcon, &event);
			XtDispatchEvent (&event);
		}
	} else {

		/* empty the event queue, and keep doing so until we
		   see a specific expose event or until were tired
		   of waiting. */

		waitTimerId = XtAppAddTimeOut(appcon, MAX_WAIT_TIME,
					maxWaitTimeProc, NULL);
		ding = FALSE;
		while( XtAppPending (appcon)  ||  (!gotExposed  &&  !ding)) {
			XtAppNextEvent(appcon, &event);
			if (event.type == Expose  &&
					event.xexpose.window == needToSee) {
				gotExposed = TRUE;
			}

			XtDispatchEvent (&event);
		}
		if (! ding) {
			XtRemoveTimeOut(waitTimerId);
		}
	}
}


