/* text.c
   make and manage the text popups */

     /*---------------------------------------------------------------*/
     /* 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 <sys/file.h> */ /* now comes from osdep.h */

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#include <X11/Xaw/Box.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Shell.h>

#include "KeyWSink.h"

#include "conf.h"
#include "osdep.h"
#include "appres.h"
#include "compatR4.h"
#include "xglobals.h"
#include "text.h"
#include "misc.h"
#include "subst.h"
#include "gopher.h"
#include "item.h"

#define	PERCENT	'%'

typedef struct _subItem {
		char	title[USER_STRING_LEN];
		char	host[HOST_STRING_LEN];
	} subItem;

typedef struct _textElement {
		Widget		textShell;
		Widget		textDisplay;
		int		seqNumber;
		TextClass	class;
		subItem		origFields;
		Boolean		used;
		Boolean		hold;
		Boolean		deleteFile;
		char		*displayedFile;
		char		**wordList;
		char		*stringValue;
		struct _textElement *next;
	} textElement, *textElementP;

static	textElementP	textElementHead = NULL;

static	textSeqNumber = 0;

#define THIS_POPUP_NAME		"textPopup"

				/* default values */
static popupPosResources	placement = {

	/* position at the top of the main panel, centered across */
	from_none, 0, 0, justify_top_left, justify_top_left, False, False
	};



/* textStringToFile
   if a text widget has a string, rather than file value, copy the
   string to a file, and set the structure to reflect this so things
   will be cleaned up. */

static Boolean
textStringToFile(tep)
textElementP	tep;
{
	char	*tempfile = XtMalloc(sizeof(char) * PATH_NAME_LEN);
	FILE	*fp;

	getTempFile(tempfile);
	if ((fp = fopen (tempfile, "w")) == NULL) {
		return False;
	}
	fwrite (tep->stringValue, sizeof(char),
				strlen(tep->stringValue), fp);
	fclose(fp);
	tep->displayedFile = tempfile;
	tep->deleteFile = True;
	free(tep->stringValue);		/* free(), not XtFree() */
	tep->stringValue = NULL;

	return True;
}


/* pageDownProc
   scroll down a page in a text widget */

void
pageDownProc(w, client_data, call_data)
Widget		w;
XtPointer	client_data, call_data;
{
	Widget	tw = (Widget) client_data;

	XtCallActionProc(tw, "next-page", NULL, NULL, 0);
}


/* pageUpProc
   scroll up a page in a text widget */

void
pageUpProc(w, client_data, call_data)
Widget		w;
XtPointer	client_data, call_data;
{
	Widget	tw = (Widget) client_data;

	XtCallActionProc(tw, "previous-page", NULL, NULL, 0);
}


/* holdProc
   hold the text window up, possibly overriding the concurrentText resource. */

void
holdProc(w, client_data, call_data)
Widget		w;
XtPointer	client_data, call_data;
{
	textElementP	tep = (textElementP) client_data;

	tep->hold = True;
}


/* printProc
   send the contents of a text widget to the printer */

void
printProc(w, client_data, call_data)
Widget		w;
XtPointer	client_data, call_data;
{
	textElementP	tep = (textElementP) client_data;
	char		*fileName;
	char		*printCommand;
	char		errorMessage[MESSAGE_STRING_LEN];
	char		*out, *in, *f;
	Boolean		hasFile = False;
	gopherItemP	fakeGI;

	if (! appResources->allowPrint) {
		return;
	}

	if (tep->displayedFile == NULL) {
		if (! textStringToFile(tep) ) {
			showError(
"Text cannot be printed because a temporary file cannot be created.");
			return;
		}
	}

	fakeGI = newItem();
	strcpy(fakeGI->host, tep->origFields.host);
	strcpy(USER_STRING(fakeGI), tep->origFields.title);
	fakeGI->port = 0;

	fileName = tep->displayedFile;

	printCommand = editCommand(fakeGI, appResources->printCommand,
				fileName, fileName);
	

	if (system(printCommand) == 127) {
		sprintf (errorMessage,
			"The print command could not be executed:\n%s",
			printCommand);
		showError(errorMessage);
	}
	freeItem(fakeGI);
	free(printCommand);

	return;
}


/* saveDisplayedText
   copy a displayed text file to a user-specified file obtained from the
   file-save popup.  This routine is a callback to that popup. */

BOOLEAN
saveDisplayedText(outFD, clientData)
int		outFD;
XtPointer	clientData;
{
	textElementP	tep = (textElementP) clientData;
	int	inFD;

	if (tep->displayedFile == NULL) {
		write( outFD, tep->stringValue,
				sizeof(char)*strlen(tep->stringValue) );
	} else {
		if ((inFD = open(tep->displayedFile, O_RDONLY, 0)) < 0) {
			showError( "Problem saving text file" );
			return FALSE;
		}

		{
#define		    BUF_SZ	1024
		    char	buf[BUF_SZ];
		    int		cc;

		    cc = read(inFD, buf, BUF_SZ);
		    while (cc > 0) {
			(void) write(outFD, buf, cc);
			cc = read(inFD, buf, BUF_SZ);
		    }

		    if (cc < 0) {
			close(inFD);
			return FALSE;
		    }
		}
	}
	
	close (inFD);
	return TRUE;
}


/* saveProc
   save the contents of a text widget to a file */

void
saveProc(w, client_data, call_data)
Widget		w;
XtPointer	client_data, call_data;
{
	textElementP	tep = (textElementP) client_data;

	if (tep == NULL) return;

	if (! appResources->allowSave) {
		return;
	}

	saveRequest( tep->textShell, (XtPointer) tep);
}


/* freeTextResources
   free all resources associated with a textElement structure. */

static void
freeTextResources(tep)
textElementP	tep;
{
	if (tep->deleteFile)
		if (unlink(tep->displayedFile) != 0) {
			fprintf (stderr,
		"Warning: a gopher internal file could not be removed.\n");
			fprintf (stderr,
			    "         This may indicate a system error.\n");
		}

	if (tep->displayedFile != NULL) {
		XtFree(tep->displayedFile);
		tep->displayedFile = NULL;
	}
	freeWordList(tep->wordList);
	tep->wordList      = NULL;
	tep->deleteFile    = False;
	if (tep->stringValue != NULL) {
		free(tep->stringValue);		/* free(), not XtFree() */
	}
	tep->stringValue   = NULL;

#ifdef XGOPHER_X11R4
	/* the following should not be necessary, but some R4 versions
	   seem to not really free their string value! */

	{
		Cardinal        n;
		Arg             args[3];

		n = 0;
		XtSetArg(args[n], XtNtype, XawAsciiString);  n++;
		XtSetArg(args[n], XtNstring, "");  n++;
		XtSetValues(tep->textDisplay, args, n);
	}
#endif  /* XGOPHER_X11R4 */
}


/* doneProc
   Done showing text file in a popup, clean up */

void
doneProc(w, client_data, call_data)
Widget		w;
XtPointer	client_data, call_data;
{
	int		i;
	textElementP	tep = (textElementP) client_data;

	if (tep == NULL) return;

	XtPopdown(tep->textShell);

	freeTextResources(tep);
	tep->used = False;

	return;
}


/* cleanUpTextProc
   Allow a final chance to remove all the temp files text knows about. */

void
cleanUpTextProc()
{
	textElementP	tep;


	for (tep=textElementHead; tep != NULL; tep = tep->next) 
		if (tep->used && tep->deleteFile)
			(void) unlink(tep->displayedFile);
	
	return;
}


/* RemoveText
   handle a done action specified via a translation.
   Capitalization is for X convention. */

static void
RemoveText(w, event, params, numParams)
Widget          w;
XEvent          *event;
String          *params;
Cardinal        *numParams;
{
	Widget		button;
	textElementP	tep;

	/* need the widget id of the Done button for that particular
	   text window. */
	
	button = XtNameToWidget(w, "*textDone");

        XtCallCallbacks(button, XtNcallback, NULL);
}


/* removeAllText
   invoke the "done" callback for each text window displayed */

void
removeAllText()
{
	textElementP	tep;

	for (tep=textElementHead; tep != NULL; tep = tep->next) 
		if (tep->used)
			doneProc(tep->textShell, tep, NULL);
	
	return;
}


/* createTextShell
   Build a text popup */

static textElementP
createTextShell(topLevel)
Widget	topLevel;
{
	Widget		doneButton, pageDownButton, pageUpButton, 
			printButton, saveButton, holdButton;
	Widget		buttonBox, textForm;
	Arg		args[10];
	Cardinal	n;
	int		i, w, h;
	textElementP	tep;
	Widget		kwSink;

	static Boolean	actionsAdded=False;
	static XtActionsRec     actionsTable[] = {
				{"removeText",   RemoveText}
				};

	if (!actionsAdded) {
		XtAppAddActions(appcon, actionsTable, XtNumber(actionsTable));
		actionsAdded = True;

		/* find the popup placement for this shell */

		{
		popupPosResources *resourcePlacement;

		resourcePlacement = getPopupPosResources(
					THIS_POPUP_NAME, POPUP_POS_CLASS, &placement);
		bcopy( (char *) resourcePlacement, (char *) &placement,
					sizeof(popupPosResources) );
		}

		}

	if ((tep = (textElementP) malloc(sizeof(textElement))) == NULL) {
	    showError("Too many windows are already open to show this file.");
	    return NULL;
	}
	tep->next = textElementHead;
	textElementHead = tep;

	/* create TEXT display popup */
		n=0;
		XtSetArg(args[n], XtNtitle, "Gopher Text");  n++;
		/*
		XtSetArg(args[n], XtNiconName, "Gopher Text");  n++;
		*/
	tep->textShell = XtCreatePopupShell("textShell",
					topLevelShellWidgetClass,
					topLevel, args, n);
	
	
	/* create TEXT FORM */
		n=0;
	textForm = XtCreateManagedWidget("textForm", formWidgetClass,
					tep->textShell, args, n);

	/* create BUTTON BOX */
		n=0;
		XtSetArg(args[n], XtNorientation, XtorientHorizontal);  n++;
		XtSetArg(args[n], XtNtop,	XawChainTop);  n++;
		XtSetArg(args[n], XtNbottom,	XawChainTop);  n++;
		XtSetArg(args[n], XtNleft,	XawChainLeft);  n++;
		XtSetArg(args[n], XtNright,	XawChainLeft);  n++;
	buttonBox = XtCreateManagedWidget("textButtonBox", boxWidgetClass,
					textForm, args, n);

	/* create TEXT display */
		n=0;
		XtSetArg(args[n], XtNstring, "");  n++;
		XtSetArg(args[n], XtNeditType, XawtextRead);  n++;
		XtSetArg(args[n], XtNscrollVertical,
					XawtextScrollWhenNeeded);  n++;
		XtSetArg(args[n], XtNfromVert,	buttonBox);  n++;
		XtSetArg(args[n], XtNtop,	XawChainTop);  n++;
		XtSetArg(args[n], XtNbottom,	XawChainBottom);  n++;
		XtSetArg(args[n], XtNleft,	XawChainLeft);  n++;
		XtSetArg(args[n], XtNright,	XawChainRight);  n++;
	tep->textDisplay = XtCreateManagedWidget("textDisplay",
					asciiTextWidgetClass,
					textForm, args, n);
		setTextWidgetSize(tep->textDisplay, 80, 24);

		/* add the key word sink */
		{
#define			TAB_COUNT	32	/* same as in AsciiText.c */
			Widget	kwSink;
			int	i, tabs[TAB_COUNT], tab;

			kwSink = XtCreateWidget("kwSink",
					keyWSinkObjectClass,
					tep->textDisplay, args, n);

			for (i=0, tab=0 ; i < TAB_COUNT ; i++)
				tabs[i] = (tab += 8);
			XawTextSinkSetTabs(kwSink, TAB_COUNT, tabs);

			n=0;
			XtSetArg(args[n], XtNtextSink, kwSink);  n++;
			XtSetValues(tep->textDisplay, args, n);
		}


	/* create DONE button */
	
		n=0;
	doneButton = XtCreateManagedWidget("textDone", commandWidgetClass,
					buttonBox, args, n);
		XtAddCallback(doneButton, XtNcallback,
				doneProc, (XtPointer) tep);

	/* create PAGE_DOWN button */
	
		n=0;
#ifndef XGOPHER_X11R4
		XtSetArg(args[n], XtNleftBitmap, pageDownPixmap);  n++;
#endif
	pageDownButton = XtCreateManagedWidget("textPageDown",
					commandWidgetClass,
					buttonBox, args, n);
		XtAddCallback(pageDownButton, XtNcallback,
				pageDownProc, (XtPointer) tep->textDisplay);

	/* create PAGE_UP button */
	
		n=0;
#ifndef XGOPHER_X11R4
		XtSetArg(args[n], XtNleftBitmap, pageUpPixmap);  n++;
#endif
	pageUpButton = XtCreateManagedWidget("textPageUp", commandWidgetClass,
					buttonBox, args, n);
		XtAddCallback(pageUpButton, XtNcallback,
				pageUpProc, (XtPointer) tep->textDisplay);

	/* create PRINT button */
	
	if (appResources->allowPrint) {
			n=0;
		printButton = XtCreateManagedWidget("textPrint",
						commandWidgetClass,
						buttonBox, args, n);
			XtAddCallback(printButton, XtNcallback,
					printProc, (XtPointer) tep);
	}

	/* create SAVE button */
	
	if (appResources->allowSave) {
			n=0;
		saveButton = XtCreateManagedWidget("textSave",
						commandWidgetClass,
						buttonBox, args, n);
			XtAddCallback(saveButton, XtNcallback,
					saveProc, (XtPointer) tep);
	}

	/* create HOLD button */
	
	if (appResources->concurrentText > 0  &&  appResources->allowHold) {
			n=0;
		holdButton = XtCreateManagedWidget("textHold",
						commandWidgetClass,
						buttonBox, args, n);
			XtAddCallback(holdButton, XtNcallback,
					holdProc, (XtPointer) tep);
	}

	setPopupGeometry(tep->textShell, &placement);

        /* for ICCCM window manager protocol complience */

        XtOverrideTranslations (tep->textShell,
            XtParseTranslationTable ("<Message>WM_PROTOCOLS: removeText()"));
        XtRealizeWidget(tep->textShell);
        (void) XSetWMProtocols (XtDisplay(tep->textShell),
				XtWindow(tep->textShell),
                                &wmDeleteAtom, 1);

	return tep;
}


/* saveItemInfo
   copy info from the original gopher item, if any, to internal fields. */

saveItemInfo(localItem, gi, title)
subItem		*localItem;
gopherItemP	gi;
char		*title;
{
	if (gi != (gopherItemP) NULL) {
		strcpy (localItem->host, gi->host);
		strcpy (localItem->title, USER_STRING(gi));
	} else {
		(localItem->host)[0] = NULLC;
		strcpy (localItem->title, title);
	}
}


/* getTextElement
   find a free textElement structure or get a new one. */

static textElementP
getTextElement(topLevel, class)
Widget		topLevel;
TextClass	class;
{
	textElementP	tep = NULL, p, oldest;
	int		n;

	if (appResources->commonText) class = defaultTextClass;

	/* if concurrentText is set > 0, then we have to find
	   out how many of these displays are popped up and reuse the
	   oldest if necessary.  */

	if (appResources->concurrentText> 0) { 

		n=0;
		oldest = textElementHead;
		for (p=textElementHead; p!=NULL; p = p->next) {
			if (!p->used) continue;
			if (p->hold) continue;
			if (p->class == class) {
				n++;
				if (p->seqNumber < oldest->seqNumber) {
					oldest = p;
				}
			}
		}
		if (n >= appResources->concurrentText) {
			tep = oldest;
			freeTextResources(tep);

			return tep;
		}
	}


	for (tep=textElementHead; (tep!=NULL && tep->used); tep = tep->next);

	if (tep == NULL) {
		if ((tep = createTextShell(topLevel)) == NULL) {
			return NULL;
		}
	}

	tep->seqNumber = textSeqNumber++;
	tep->class     = class;
	tep->hold      = False;
	return tep;
}


/* displayTempFile
   Load a text popup with a temp file */

void
displayTempFile(topLevel, class, gi, title, fileName)
Widget		topLevel;
TextClass	class;
gopherItemP	gi;
char		*title;
char		*fileName;
{
	Arg		args[10];
	Cardinal	n;
	char		*textFileName;
	textElementP	tep;
	Widget		kwSink;


	tep = getTextElement(topLevel, class);

	if ((textFileName = (char *) XtMalloc(strlen(fileName)+1)) ==
								NULL) {
		showError("Unable to allocate additional memory.");
		return;
	}
	strcpy(textFileName, fileName);
	tep->displayedFile = textFileName;
	tep->deleteFile    = True;
	tep->used = True;
	tep->wordList = NULL;
	tep->stringValue = NULL;
	saveItemInfo(&(tep->origFields), gi, title);

	/* set keyword resource in text sink */

	n = 0;
	XtSetArg(args[n], XtNtextSink, &kwSink);  n++;
	XtGetValues(tep->textDisplay, args, n);

	n = 0;
	XtSetArg(args[n], XtNwordList, NULL);  n++;
	XtSetValues(kwSink, args, n);

	/* set title and file name */

	n = 0;
	XtSetArg(args[n], XtNtitle, tep->origFields.title);  n++;
	if (appResources->nameText) {
		XtSetArg(args[n], XtNiconName, title);  n++;
	}
	XtSetValues(tep->textShell, args, n);

	n = 0;
	XtSetArg(args[n], XtNtype, XawAsciiFile);  n++;
	XtSetArg(args[n], XtNstring, textFileName);  n++;
	XtSetValues(tep->textDisplay, args, n);

	positionAPopup(tep->textShell, topLevel, &placement);
	XtPopup(tep->textShell, XtGrabNone);
	return;
}


/* displayIndexTempFile
   Load a text popup with a temp file and index words for highlighting */

void
displayIndexTempFile(topLevel, class, gi, title, fileName, indexString)
Widget		topLevel;
TextClass	class;
gopherItemP	gi;
char		*title;
char		*fileName;
char		*indexString;
{
	Arg		args[10];
	Cardinal	n;
	char		*textFileName;
	textElementP	tep;
	Widget		kwSink;


	tep = getTextElement(topLevel, class);

	if ((textFileName = (char *) XtMalloc(strlen(fileName)+1)) ==
								NULL) {
		showError("Unable to allocate additional memory.");
		return;
	}
	strcpy(textFileName, fileName);
	tep->displayedFile = textFileName;
	tep->deleteFile    = True;
	tep->used = True;
	tep->stringValue = NULL;
	saveItemInfo(&(tep->origFields), gi, title);


	/* set keyword resource in text sink */

	n = 0;
	XtSetArg(args[n], XtNtextSink, &kwSink);  n++;
	XtGetValues(tep->textDisplay, args, n);

	n = 0;
	if (indexString == NULL) {
		tep->wordList = NULL;
		XtSetArg(args[n], XtNwordList, NULL);  n++;
	} else {
		tep->wordList = makeWordList(indexString);
		XtSetArg(args[n], XtNwordList, tep->wordList);  n++;
	}
	XtSetValues(kwSink, args, n);

	/* set title and file name */

	n = 0;
	XtSetArg(args[n], XtNtitle, tep->origFields.title);  n++;
	if (appResources->nameText) {
		XtSetArg(args[n], XtNiconName, title);  n++;
	}
	XtSetValues(tep->textShell, args, n);

	n = 0;
	XtSetArg(args[n], XtNtype, XawAsciiFile);  n++;
	XtSetArg(args[n], XtNstring, textFileName);  n++;
	XtSetValues(tep->textDisplay, args, n);

	positionAPopup(tep->textShell, topLevel, &placement);
	XtPopup(tep->textShell, XtGrabNone);
	return;
}


/* displayTextString
   Load a text popup with the contents of a text string */

void
displayTextString(topLevel, class, gi, title, string)
Widget		topLevel;
TextClass	class;
gopherItemP	gi;
char		*title;
char		*string;
{
	Arg		args[10];
	Cardinal	n;
	textElementP	tep;
	Widget		kwSink;


	tep = getTextElement(topLevel, class);

	tep->displayedFile = NULL;
	tep->deleteFile    = False;
	tep->used	   = True;
	tep->wordList	   = NULL;
	tep->stringValue   = string;
	saveItemInfo(&(tep->origFields), gi, title);

	/* set keyword resource in text sink */

	n = 0;
	XtSetArg(args[n], XtNtextSink, &kwSink);  n++;
	XtGetValues(tep->textDisplay, args, n);

	n = 0;
	XtSetArg(args[n], XtNwordList, NULL);  n++;
	XtSetValues(kwSink, args, n);

	/* set title and file name */

	n = 0;
	XtSetArg(args[n], XtNtitle, tep->origFields.title);  n++;
	if (appResources->nameText) {
		XtSetArg(args[n], XtNiconName, title);  n++;
	}
	XtSetValues(tep->textShell, args, n);

	n = 0;
	XtSetArg(args[n], XtNtype, XawAsciiString);  n++;
	XtSetArg(args[n], XtNstring, string);  n++;
	XtSetValues(tep->textDisplay, args, n);

	positionAPopup(tep->textShell, topLevel, &placement);
	XtPopup(tep->textShell, XtGrabNone);
	return;
}
