/***********************************************************************
 *
 * TITLE:
 *	help.c
 *
 * AUTHOR:
 *	Kevin J. Miller
 *
 * DESCRIPTION:
 *	Reads a standard help/info file from the HELPPATH, and returns
 *	the results when poll.
 *
 *				CHANGE HISTORY
 *
 * $Log: help.c,v $
 * Revision 1.12  1994/08/19  22:46:15  kevin
 * raise window after managing
 *
 * Revision 1.11  1994/08/02  23:00:09  jeffb
 * increase TXTLEN to allow longer help text entries
 * added scrolling help text
 *
 * Revision 1.10  1994/03/22  20:56:53  clm
 * added (int) prototype to strcmp calls
 *
 * Revision 1.9  1994/01/06  05:12:06  kevin
 * port for V20
 *
 * Revision 1.8  1993/04/24  01:09:36  kevin
 * renamed subresource
 *
 * Revision 1.7  1993/04/23  20:18:18  kevin
 * InitHelp now returns status
 *
 * Revision 1.6  1993/04/20  01:24:39  kevin
 * added helpPath subresource
 *
 * Revision 1.5  1992/12/17  23:12:19  kevin
 * added showOk resource
 *
 * Revision 1.4  1992/01/03  03:55:40  kevin
 * removed OK button for Motif bug
 * append default helppath to HELPPATH now
 *
 * Revision 1.3  1991/11/09  00:36:38  kevin
 * added full independent help menu
 *
 * Revision 1.2  1991/11/01  23:01:42  kevin
 * changed name of help callback
 *
 * Revision 1.1  1991/10/29  03:37:56  kevin
 * Initial revision
 *
 ***********************************************************************
 *
 * WARNINGS:
 *
 * EXTERNAL CALLABLE COMPONENTS (PUBLIC):
 *	InitHelp
 *	InitHelpMenu
 *	ShowHelp
 *
 * GLOBALS:
 *
 * WAIVERS:
 *
 * NOTES:
 *
 * MANPAGE:
 *
 ***********************************************************************/

#ifndef lint
static char rcsid[] = 
    "$Id: help.c,v 1.12 1994/08/19 22:46:15 kevin OEL $";
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <Xm/Xm.h>
#include <Xm/CascadeB.h>
#include <Xm/DialogS.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <Xm/PushBG.h>
#include <Xm/RowColumn.h>
#include <Xm/ScrollBar.h>
#include <Xm/ScrolledW.h>
#include <Xm/Separator.h>

#include "help.h"

/*extern char *getenv();*/

#define BUFLEN 1024
#define KEYLEN 32
#define TXTLEN 12000

/*
 * structures for adding subresources (show OK button, help directory, and
 * any additional spacing to use when resizing shell).
 *
 * KLUDGE - the following lines remove the OK button, since is does not
 * work correctly in Motif at the present time.  They should be removed
 * when Motif is fixed, or change the default value to "True".
 *
 * the spacing subresource defaults to 0 and shouldn't be needed.  it is
 * included in case some systems need a few extra pixels of space for
 * resizing the shell to avoid displaying scrollbars.
 */

typedef struct {
    Boolean showOk;
    String helpPath;
    Dimension spacing;
} SRData, *SRDataPtr;

static XtResource resources[] = {
    {
	"showOk", "ShowOk", XmRBoolean, sizeof(Boolean),
	XtOffset(SRDataPtr, showOk), XmRString, "False"
    }, {
	"helpPath", "HelpPath", XmRString, sizeof(String),
	XtOffset(SRDataPtr, helpPath), XmRString, ""
    }, {
	"spacing", "Spacing", XmRDimension, sizeof(Dimension),
	XtOffset(SRDataPtr, spacing), XmRString, "0"
    }
};

/*
 * array of key value pairs, the number of these should be limited,
 * so a simple linear search is used
 */
struct lkp {
    char *key;
    XmString value;
    struct lkp *nxtptr;
};

static struct lkp *basptr = NULL;

/*
 * other module variables
 */
static Widget help, helpForm, helpMesg, helpVerticalScrollBar;
static XmString failMsg;
static Dimension add_horizontal = 0, add_vertical = 0;

/*
 * static function definitions
 */
static int read_helpfile(char *, char *);
static XmString lookup_help(char *);
static void helpOkCb(Widget, XtPointer, XtPointer);

/*
 * default help path, add cwd when in debug mode
 */
#ifdef DEBUG
#define DEFAULT_HELPPATH ".:/usr/lib/help:/usr/local/lib/help"
#else
#define DEFAULT_HELPPATH "/usr/lib/help:/usr/local/lib/help"
#endif

/***********************************************************************
 *
 * FUNCTION:
 *	InitHelp()
 *
 * INPUTS:
 *	parent		- (Widget) shell for application
 *	title		- (char *) title of dialog
 *	filename	- (char *) name of file containing help
 *
 * OUTPUTS:
 *
 * RETURNS:
 *	0		- on success
 *	-1		- on failure
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 * 	Loads a help file into an internal database.
 */

int 
InitHelp(Widget parent, char *title, char *filename)
{
    XmString temp;
    Arg args[20];
    Cardinal n;
    SRData data;
    Widget helpScroll, helpSep, helpOk;
    Dimension dim;
    int offset_top, offset_bottom, offset_left, offset_right;

    /* create the help window, and top level form */
    n = 0;
    XtSetArg(args[n], XmNtitle, title);  n++;
    XtSetArg(args[n], XmNdeleteResponse, XmUNMAP);  n++;
    XtSetArg(args[n], XmNborderWidth, 0);  n++;
    help = XmCreateDialogShell(parent, "help", args, n);

    n = 0;
    XtSetArg(args[n], XmNfractionBase, 5);  n++;
    XtSetArg(args[n], XmNborderWidth, 0);  n++;
    helpForm = XmCreateForm(help,"helpForm",args,n);

    /* get and handle resources */
    XtGetSubresources(help, &data, "options", "Options", resources,
	XtNumber(resources), NULL, 0);

    if (data.spacing > 0) {
       add_vertical += data.spacing;
       add_horizontal += data.spacing;
    }

    if (data.showOk) {
        /* create Ok button, increment the amount of vertical space needed */
        n = 0;
        temp = XmStringCreateSimple("Ok");
        XtSetArg(args[n],XmNlabelString,temp); n++;
        XtSetArg(args[n],XmNhighlightThickness,2);  n++;
        XtSetArg(args[n],XmNborderWidth,0);  n++;
        XtSetArg(args[n],XmNtopAttachment,XmATTACH_NONE); n++;
        XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
        XtSetArg(args[n],XmNbottomOffset,2); n++;
        XtSetArg(args[n],XmNleftAttachment,XmATTACH_POSITION); n++;
        XtSetArg(args[n],XmNleftPosition,2); n++;
        XtSetArg(args[n],XmNrightAttachment,XmATTACH_POSITION); n++;
        XtSetArg(args[n],XmNrightPosition,3); n++;
        helpOk = XmCreatePushButton(helpForm,"helpOk",args,n);
        XmStringFree(temp);
        XtAddCallback(helpOk,XmNactivateCallback,(XtCallbackProc)helpOkCb,
            (XtPointer)NULL);
        XtManageChild(helpOk);

        n = 0;
        XtSetArg(args[n],XmNheight,&dim); n++;
        XtSetArg(args[n],XmNbottomOffset,&offset_bottom); n++;
        XtGetValues(helpOk,args,n);
        add_vertical += dim + offset_bottom;

        /* create separator, increment the amount of vertical space needed */
        n = 0;
        XtSetArg(args[n],XmNborderWidth,0);  n++;
        XtSetArg(args[n],XmNtopAttachment,XmATTACH_NONE); n++;
        XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++;
        XtSetArg(args[n],XmNbottomWidget,helpOk); n++;
        XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
        XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
        helpSep = XmCreateSeparator(helpForm,"helpSep",args,n);
        XtManageChild(helpSep);

        n = 0;
        XtSetArg(args[n],XmNheight,&dim); n++;
        XtSetArg(args[n],XmNbottomOffset,&offset_bottom); n++;
        XtGetValues(helpSep,args,n);
        add_vertical += dim + offset_bottom;
    }

    /* create scrolling window for message */
    n = 0;
    XtSetArg(args[n],XmNshadowThickness,0); n++;
    XtSetArg(args[n],XmNborderWidth,0);  n++;
    XtSetArg(args[n],XmNscrollingPolicy,XmAUTOMATIC); n++;
    XtSetArg(args[n],XmNvisualPolicy,XmCONSTANT); n++;
    XtSetArg(args[n],XmNscrollBarDisplayPolicy,XmAS_NEEDED); n++;
    XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
    if (data.showOk) {
       XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++;
       XtSetArg(args[n],XmNbottomWidget,helpSep); n++;
    } else {
       XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
    }
    XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
    XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
    helpScroll = XmCreateScrolledWindow(helpForm,"helpScroll",args,n);

    /* add room horizontally for the vertical scrollbar */
    n = 0;
    XtSetArg(args[n],XmNverticalScrollBar,&helpVerticalScrollBar); n++;
    XtSetArg(args[n],XmNspacing,&dim); n++;
    XtSetArg(args[n],XmNtopOffset,&offset_top); n++;
    XtSetArg(args[n],XmNbottomOffset,&offset_bottom); n++;
    XtSetArg(args[n],XmNleftOffset,&offset_left); n++;
    XtSetArg(args[n],XmNrightOffset,&offset_right); n++;
    XtGetValues(helpScroll,args,n);
    add_vertical += offset_top + offset_bottom;
    add_horizontal += 2*dim + offset_left + offset_right;
    if (helpVerticalScrollBar != NULL) {
       n = 0;
       XtSetArg(args[n],XmNwidth,&dim); n++;
       XtGetValues(helpVerticalScrollBar,args,n);
       add_horizontal += dim;
    }

    /* create message area, and set the work window of the scroll window */
    n = 0;
    XtSetArg(args[n],XmNresizable,True); n++;
    XtSetArg(args[n],XmNalignment,XmALIGNMENT_BEGINNING); n++;
    XtSetArg(args[n],XmNborderWidth,0);  n++;
    helpMesg = XmCreateLabel(helpScroll,"helpMesg",args,n);
    XtManageChild(helpMesg);

    n = 0;
    XtSetArg(args[n],XmNworkWindow,helpMesg);
    XtSetValues(helpScroll,args,n);
    XtManageChild(helpScroll);

    failMsg = XmStringCreateSimple("Help not available.");

    return read_helpfile(filename, data.helpPath);
}

/***********************************************************************
 *
 * FUNCTION:
 *	InitHelpMenu
 *
 * INPUTS:
 *	menu_bar		- (Widget) parent widget
 *	contents		- (struct help_list []) list of help
 *				  button labels and tokens
 *	count			- (int) number of help entries
 *
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 *	Creates a help menu from a list of text tokens.
 */

void 
InitHelpMenu(Widget menuBar, struct help_list contents[], Cardinal count)
{
    Widget helpPulldown, helpCascade;
    Widget (*items)[];
    XmString temp;
    Arg args[20];
    Cardinal n;
    int i;

    /*
     * create help pulldown menu
     */
    n = 0;
    helpPulldown = XmCreatePulldownMenu(menuBar, "helpPulldown", args, n);

    /*
     * create help button for menuBar
     */
    temp = XmStringCreateSimple("Help");
    n = 0;
    XtSetArg(args[n], XmNsubMenuId, helpPulldown); n++;
    XtSetArg(args[n], XmNlabelString, temp); n++;
    XtSetArg(args[n], XmNmnemonic, 'H'); n++;
    helpCascade = XmCreateCascadeButton(menuBar, "helpCascade", args, n);
    XtManageChild(helpCascade);
    XmStringFree(temp);

    n = 0;
    XtSetArg(args[n], XmNmenuHelpWidget, helpCascade);  n++;
    XtSetValues(menuBar, args, n);

    /*
     * create all menu entries
     */
    items = (Widget (*)[])XtCalloc(count, sizeof(Widget));
    for (i = 0; i < count; ++i) {
	temp = XmStringCreateSimple(contents[i].label);
	n = 0;
	XtSetArg(args[n], XmNlabelString, temp); n++;
	(*items)[i] = XmCreatePushButtonGadget(helpPulldown, "helpItem",
	    args, n);
	XtAddCallback((*items)[i], XmNactivateCallback,
	    (XtCallbackProc)ShowHelp, contents[i].token);
	XmStringFree(temp);
    }
    XtManageChildren((*items), count);
    XtFree((char *)items);
}

/***********************************************************************
 *
 * FUNCTION:
 *	ShowHelp()
 *
 * INPUTS:
 *	key		- (char *) name of file containing help
 *
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 * 	Callback to display a help window
 */

void 
ShowHelp(Widget w, char *key, XmAnyCallbackStruct *call_data)
{
    XmString message;
    Arg args[20];
    Cardinal n;
    Dimension height, width;
    int val, size, incr, pageincr;

    message = lookup_help(key);

    XtManageChild(helpForm);

    /* set the new help message, and determine the new size */
    n = 0;
    XtSetArg(args[n], XmNlabelString, message);  n++;
    XtSetValues(helpMesg, args, n);

    n = 0;
    XtSetArg(args[n],XmNheight,&height); n++;
    XtSetArg(args[n],XmNwidth,&width); n++;
    XtGetValues(helpMesg,args,n);

    /* resize shell, adding appropriate spacing */
    n = 0;
    XtSetArg(args[n],XmNheight,height+add_vertical); n++;
    XtSetArg(args[n],XmNwidth,width+add_horizontal); n++;
    XtSetValues(help,args,n);

    /* reposition scroll bar to the top */
    if (helpVerticalScrollBar != NULL && XtIsManaged(helpVerticalScrollBar)) {
       XmScrollBarGetValues(helpVerticalScrollBar,&val,&size,&incr,&pageincr);
       XmScrollBarSetValues(helpVerticalScrollBar,0,size,incr,pageincr,True);
    }

    XtManageChild(help);
    XRaiseWindow(XtDisplay(help), XtWindow(help));
}

/***********************************************************************
 *
 * FUNCTION:
 *	read_helpfile()
 *
 * INPUTS:
 *	filename	- (char *) name of file containing help
 *	helpPathRes	- (char *) helpPath subresource
 *
 * OUTPUTS:
 *	none
 *
 * RETURNS:
 *	0		- on success
 *	-1		- on failure
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 * 	Loads a help file into an internal database.
 */

static int 
read_helpfile(char *filename, char *helpPathRes)
{
    FILE *fp;			/* help file pointer */
    char *envval;		/* value of HELPPATH environment string */
    char *helppath;		/* list of help directories */
    char *token;		/* individual help directory */
    char filepath[BUFLEN];	/* name of help file */
    char buf[BUFLEN];		/* input text buffer */
    int tmpstat;		/* stat result */
    struct stat sb;		/* stat buffer */
    int code;			/* return code */
    char key[KEYLEN];		/* search key for help */
    char value[TXTLEN];		/* array of help text */
    int length;			/* current length of text string */
    int line;			/* current line number in help file */
    int clip_error;		/* TRUE if description if too long */
    struct lkp **curptr;	/* used to allocate linked list */
    int i;			/* local index */

    /*
     * return code on failure
     */
    code = -1;

    /*
     * first use "helpPath" subresource if available
     * then use environment variable if available
     * finally use DEFAULT_HELPPATH
     */
    if (*helpPathRes != '\0') {
	helppath = XtNewString(helpPathRes);
    } else if ((envval = getenv("HELPPATH")) != NULL) {
	/*
	 * add space for ':' and '\0'
	 */
	helppath = (char *)XtMalloc((Cardinal)(strlen(envval) +
	    strlen(DEFAULT_HELPPATH) + 2));
	sprintf(helppath, "%s:%s", envval, DEFAULT_HELPPATH);
    } else {
	helppath = XtNewString(DEFAULT_HELPPATH);
    }

    /*
     * scan through directories for the file
     */
    fp = NULL;
    for (token = strtok(helppath, ":"); token != NULL;
	    token = strtok(NULL, ":")) {
	strcat(strcat(strcpy(filepath, token), "/"), filename);
	tmpstat = stat(filepath, &sb);
	if ((tmpstat == 0) && (S_ISREG(sb.st_mode))) {
	    /*
	     * open file if found
	     */
	    fp = fopen(filepath, "r");
	    if (fp != NULL) break;
	}
    }

    XtFree(helppath);

    /*
     * load the help file if available
     */
    line = 0;
    curptr = &basptr;

    key[0] = '\0';

    length = 0;
    clip_error = FALSE;

    if (fp != NULL) {
	while (fgets(buf, BUFLEN, fp) != NULL) {
	    ++line;
	    switch (buf[0]) {
	    case '#':		/* comment line (ignore) */
		break;
	    case ':':		/* found help key */
		if ((key[0] != '\0') && (length > 0)) {
		    value[length - 1] = '\0';
		    *curptr = XtNew(struct lkp);
		    (*curptr)->key = XtNewString(key);
		    (*curptr)->value = XmStringCreateLtoR(value,
			XmSTRING_ISO8859_1);
		    (*curptr)->nxtptr = NULL;
		    curptr = &((*curptr)->nxtptr);
		}
		value[0] = '\0';
		length = 0;
		clip_error = FALSE;

		/*
		 * proper keys contain letters, '_', and digits (but
		 * may not start with a digit)
		 */
		i = 1;
		while ((i < KEYLEN) && (isalpha(buf[i]) || (buf[i] == '_') ||
		    ((i > 1) && isdigit(buf[i])))) {
		    key[i - 1] = buf[i];
		    ++i;
		}
		key[i - 1] = '\0';
		break;
	    default:		/* normal text */
		/*
		 * ignore any text before first key of if there was
		 * an oversized description
		 */
		if ((!clip_error) && (key[0] != '\0')) {
		    length += strlen(buf);
		    if (length < TXTLEN - 1) {
			strcat(value, buf);
		    } else {
			length -= strlen(buf);
			sprintf(buf, "Improper Help file: %s at line %d\n",
			    filepath, line);
			XtWarning(buf);
			clip_error = TRUE;
		    }
		}
		break;
	    }
	}
	fclose(fp);
	if ((key[0] != '\0') && (length > 0)) {
	    value[length - 1] = '\0';
	    *curptr = XtNew(struct lkp);
	    (*curptr)->key = XtNewString(key);
	    (*curptr)->value = XmStringCreateLtoR(value, XmSTRING_ISO8859_1);
	    (*curptr)->nxtptr = NULL;
	}
	code = 0;
    }
    return code;
}

/***********************************************************************
 *
 * FUNCTION:
 *	lookup_help()
 *
 * INPUTS:
 *	filename	- (char *) name of file containing help
 *
 * OUTPUTS:
 *
 * RETURNS:
 *	value		- (XmString) the help text on success
 *	failMsg		- (XmString) a failure message
 *
 * EXTERNALLY READ:
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 * 	Returns the help string corresponding to the string.
 */

static XmString 
lookup_help(char *key)
{
    struct lkp *curptr;
    XmString value;

    for (curptr = basptr; curptr != NULL; curptr = curptr->nxtptr) {
	if (!(int)strcmp(key, curptr->key)) break;
    }
    if (curptr != NULL) {
	value = curptr->value;
    } else {
	value = failMsg;
    }
    return value;
}

/***********************************************************************
 *
 * FUNCTION:
 *	helpOkCb()
 *
 * INPUTS:
 *
 * OUTPUTS:
 *
 * RETURNS:
 *
 * EXTERNALLY READ:
 *      help
 *
 * EXTERNALLY MODIFIED:
 *
 * DESCRIPTION:
 * 	Unmanage the help shell when the Ok button is pressed.
 */

static void
helpOkCb(Widget w, XtPointer client_data, XtPointer call_data)
{
  XtUnmanageChild(help);
}
