
/* ht_help.c	- hypertext help */
/*
 * ht_help.c	- 	on-line context-sensitive hypertext help
 *		Version for Motif programs (based on a PC version)
 *		last revision - 15.01.1993
 *
 * (C) COPYRIGHT A. Stochniol, 1989 - 1993
 * All Rights Reserved
 *
 * Author:
 * Dr. Andrzej Stochniol
 * Department of Mechanical Engineering
 * Imperial College of Science, Technology and Medicine
 * London, SW7 2BX, UK
 *
 *    E-mail:  A.Stochniol@ic.ac.uk
 *
 *
 *
 */

#ifdef AMIGA
#include "amiga.h"
#include <Xm/SashP.h>
#endif


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <Xm/SelectioB.h>
#include <Xm/Text.h>
#include <Xm/DialogS.h>
#include <Xm/PanedW.h>

#include <Xm/PushB.h>
#include <Xm/Form.h>

#include <sys/param.h>		/* for MAXPATHLEN declaration */



/* external declarations */
extern Widget   new_text;	/* for a temp hack */
extern Widget 	help_dialog;   	/* that widget is returned by CreateHyperTextHelp */
extern Display	*display;	/*  Display		*/

#ifdef _NO_PROTO
extern void DialogCancelCB (/* Widget w, caddr_t client_data, caddr_t call_data */ );
extern void focusCB(/* Widget w, caddr_t data, caddr_t dummy */);
#else  /* _NO_PROTO */
extern void DialogCancelCB (Widget w, caddr_t client_data, caddr_t call_data);
extern void focusCB(Widget w, caddr_t data, caddr_t dummy);
#endif

/* special hypertext link definitions (they should be really define
   in the include file) ***/
#define HTEXT_KEYS              8000
#define HTEXT_INDEX             8001
#define HTEXT_HELP              8002

/* for a temp hack ***/
#define DIALOG_FIND_OR_CHANGE   300000



/* declarations specific for ht_help */

#define	MAX_CROSS_REF	99
#define MAX_TOPIC_LEN	32000
#define MAX_TOPIC_LINKS 1000

typedef struct			/* characterize currently chosen topic & its state*/
{
    long	id;				/* id of the topic 	*/
    char	buf[MAX_TOPIC_LEN+2];             /* text of the topic    */
    int		links;                          /* number of links	*/
    long	link_pos[MAX_TOPIC_LINKS];	/* link positions (starts)*/
    int		link_len[MAX_TOPIC_LINKS];	/* length of link words */
						/* topic current state flags follow ... */
    long	cursor_pos;			/* current cursor position */
    Boolean	link_selected;			/* link ready to select flag */
    int		link_index;			/* link index (when link_selected is True) */
} hypertext_topic;


typedef struct
{
	Boolean initialized;	/* when hypertext initialize = True 	*/
	int	n_topics;       /* number of help topics                */
	int	n_cross_refs;	/* number of cross references		*/
	char 	*file_name;	/* A.S. hypertext help file name 	*/
	long    *topic_ids;	/* pointer to the topic id's table	*/
	long	*topic_adrs;	/* pointer to the topic adresses's table*/
	long	*cross_refs;	/* pointer to the cross references table;
				   [2*i] element shows an adress which should
				   be substituted by [2*i+1] adress;    */
} hypertext_help;

typedef struct {
    char *label;	/* PushButton's Label */
    void (*callback)();	/* pointer to a button callback routine (when activated) */
    caddr_t data;	/* client data for the callback routine */
} ActionAreaItem;


/* set help directory (in a way compatible with the old Imakefile file) */
#ifndef HELPDIR
    char *help_dir = "/usr/lib/X11";
#else
    char *help_dir = HELPDIR;
#endif



hypertext_topic htopic;
hypertext_help  hth = {False, 0, 0, "asedit.hlp"};


Widget 	hypertext_text, follow_help_button, back_help_button;


/* procedure prototypes */
#ifdef _NO_PROTO
void TurnOffSashTraversal(/* Widget pane */);
void SaveBackHelpStep(/* hypertext_topic *topic */);
void HelpBackCB (/* Widget w, caddr_t client_data, caddr_t call_data */);
#else  /* _NO_PROTO */
void TurnOffSashTraversal(Widget pane);
void SaveBackHelpStep(hypertext_topic *topic);
void HelpBackCB (Widget w, caddr_t client_data, caddr_t call_data);
#endif



#ifdef _NO_PROTO
void get_hypertext_topic(topic_id, hth, topic)
    long topic_id;
     hypertext_help *hth;
     hypertext_topic *topic;
#else  /* _NO_PROTO */
void get_hypertext_topic(long topic_id, hypertext_help *hth,
		hypertext_topic *topic)
#endif
{
    long l;
    int i, len;
    int kind=0;		/* okresla typ (kolor) wprowadzanego wyrazu:
			   0-normalny, 1-b.jasny, nazwa komendy,
			   2-innego koloru normalny wyraz */
    char  ch;
    FILE *in;
    /* char Copyright[17] = {"(C) A. Stochniol"}; *** such initialization is not allowed on DEC */
    char *Copyright = "(C) A. Stochniol";

    char Cop_test[17];
    int  links=0, index=0;

    int  nr_wyr = 0, len_wyr[100];	/* old extension for support of colour terms- max. 100 */
    long x_wyr[100];

    char help_file_name[MAXPATHLEN];		/* full help file name */



    /* assign default data for not existing or for an "error topic" */
    topic->id    = 0L;
    topic->links = 0;

    /* create full help file name (including path) */
#ifndef AMIGA
    strcpy(help_file_name, help_dir);    strcat(help_file_name,"/");
#else
    strcpy(help_file_name,"PROGDIR:");
#endif
    strcat(help_file_name,hth->file_name);

    if ( (in = fopen(help_file_name,"rb")) == NULL )
    {
	sprintf(topic->buf,"Help ERROR. Can't open Help file '%s'.",help_file_name );
	return;
    }


    if(!hth->initialized)		/* przeczytanie calego zbioru i ustalenie
				   adresow poczatkow help_informacji */
    {
	long int file_pos0;

	/* sprawdzenie poprawnosci zbioru - odczytanie hasla itp. ***/

	fseek(in,20L,0);	/* od tego miejsca powinno byc inf. Copyright */
	fscanf(in,"%17c",Cop_test);
	if(strncmp(Copyright,Cop_test,16))
	{
	    sprintf(topic->buf,"Help ERROR. Incorrect Help file '%s'.",help_file_name );
	    fclose(in);
	    return;
	}
	fseek(in,40L,0);	/* przeskoczenie hasla */

	/* all addresses are stored in the ASCII form; it is important
	because the same help file might be used on computers with different
	byte ordering schemes (for example: IBM RS/6000, SGI IRIS use big
	endian ordering, but DEC VAX, Intel 80x86 use little endian byte
	ordering). So storing the data in a binary form would be restricted
	to only one, specific byte ordering (additionally we could run into
	problems with sizeof of int and long on different computers!).
	ASCII way of reading follows:
	*/

	fscanf(in,"%d%d",&hth->n_topics, &hth->n_cross_refs);

	hth->topic_ids = (long *)calloc(hth->n_topics,sizeof(long));
	hth->topic_adrs= (long *)calloc(hth->n_topics,sizeof(long));
	hth->cross_refs= (long *)calloc(2*hth->n_cross_refs,sizeof(long));

	for(i=0; i< hth->n_topics;i++) fscanf(in,"%ld%ld",
			&hth->topic_ids[i], &hth->topic_adrs[i]);
	for (i=0; i< hth->n_cross_refs; i++)	fscanf(in,"%ld%ld",
			&hth->cross_refs[2*i], &hth->cross_refs[2*i+1]);

	/* dopasowanie adresow o poczatkowa czesc zbioru .. */
	file_pos0=ftell(in) + 1;  /* za ostatnia liczba byla spacja */


	/*** old binary scheme of reading in the help addresses (see above
		why we are not using such a scheme any more )

	  fread(&hth->n_topics,    sizeof(int),1,in);
	  fread(&hth->n_cross_refs,sizeof(int),1,in);

	  hth->topic_ids = (long *)calloc(hth->n_topics,sizeof(long));
	  hth->topic_adrs= (long *)calloc(hth->n_topics,sizeof(long));
	  hth->cross_refs= (long *)calloc(2*hth->n_cross_refs,sizeof(long));

	  fread(hth->topic_ids, sizeof(long),hth->n_topics,in);
	  fread(hth->topic_adrs,sizeof(long),hth->n_topics,in);

	  fread(hth->cross_refs,sizeof(long),2*hth->n_cross_refs,in);
	  |* dopasowanie adresow o poczatkowa czesc zbioru .. *|
	  file_pos0=ftell(in); |* old_tekst +1; *|  |* za ostatnia liczba byla spacja *|

	*** old binary scheme **/

	for(i=0; i< hth->n_topics; i++) hth->topic_adrs[i] += file_pos0;
    }

    hth->initialized =True;	/* Help file was succesfuly opened and adresses of
				   the help topics have been initialized */


    /* okreslenie numeru help_informacji ... - dostajemy go z zewnatrz */

    /* sprawdzenie czy nie ma cross referencji ..*/
    for(i=0; i<hth->n_cross_refs; i++)
    {
	if(topic_id == hth->cross_refs[2*i])
		{ topic_id=hth->cross_refs[2*i+1]; break; }
    }

    /* find address (position) of the topic */
    index = -1;
    for(i=0; i<hth->n_topics; i++)  { if(topic_id == hth->topic_ids[i]) index=i; }
    if(index < 0)
    {
	sprintf(topic->buf,"SORRY. No help information for this item.");
	fclose(in);
	return;
    }


    topic->id = topic_id;			/* store the retrieved topic id */

    /*   read in a topic into topic buffer (topic->buf)	*/
    links = 0;

    l=kind=len=nr_wyr=0;

    fseek(in, hth->topic_adrs[index] , 0);
    while( l <MAX_TOPIC_LEN && !feof(in))
    {
	switch(ch=getc(in))
	{
	    case 0:	 	/* skip every NULL (it shouldn't happen) */
		break;

	    case 2:
		if(len > 0 && kind == 1)
			topic->link_len[links++]=len;
		if(len > 0 && kind == 2) len_wyr[nr_wyr++]=len;
		len=kind=0;
		break;

	    case 1:   kind=1;	/* poczatek slowa kluczowego (komendy)*/
		topic->link_pos[links] = l;  /* position in the text widget start from 0*/
		break;

	    case 4:	  kind=2;	/* poczatek wyrazenia kolorow.*/
		x_wyr[nr_wyr]= l;
		break;

	    default:
		if(ch == '~') break;
		topic->buf[l++] = ch;
		if(ch != '\n' && kind > 0) len++;
		if(ch == '\n' && len > 0)	/* zamykamy slowo kluczowe (just in case) */
		{
		    if(kind == 1)
			topic->link_len[links++]=len;
		    else if(kind == 2)  len_wyr[nr_wyr++]=len;
		    len=kind=0;
		}
		break;
	} /* switch */
	if(ch == '~') break;
    } /* while */

    /* ewentualne zamkniecie hasla */
    if(len > 0)	/* zamykamy slowo kluczowe (just in case) */
    {
	if(kind == 1)
	topic->link_len[links++]=len;
	else if(kind == 2)  len_wyr[nr_wyr++]=len;
	len=kind=0;
    }

    /* add the NULL to the constructed string */
    topic->buf[l++] = NULL;
    topic->links = links;		/* store the number of links */
    topic->link_selected= False;	/* set initial - not selected */
    topic->link_index	= -1;		/* link index (not set) */
    topic->cursor_pos   = 0L;		/* starting cursor position */


    /* wyroznienie niektorych wyrazow ..*|
     *OLD* for (i=0; i<nr_wyr; i++) change_color(x_wyr[i],y_wyr[i],len_wyr[i],color_wyr); ***/


    fclose(in);
    return;

} /* get_hypertext_topic  */



/*****************************  HelpCB  *************************************
**
**		Process help callbacks
*/
#ifdef _NO_PROTO
void HelpCB (w, client_data, call_data)
    Widget w;
    caddr_t client_data;
    caddr_t call_data;
#else  /* _NO_PROTO */
void HelpCB (Widget w, caddr_t client_data, caddr_t call_data)
#endif
{
    long id = (long)client_data;
    /* if it is not a first call save data from the previous topic for the
       Back Help step;
    */
    if(hth.initialized) SaveBackHelpStep(&htopic);
    /* a special hack for the find and change dialogs help; the top widget is exactly
       the same in both cases so the only way to tell if the call actually comes from the change
       dialog box is to check if the new_text widget is managed or not; there is no easy and
       nice way of doing that
    **/
    if(id == DIALOG_FIND_OR_CHANGE && XtIsManaged(new_text))  id += 200;


    get_hypertext_topic(id, &hth, &htopic);
    XmTextSetString(hypertext_text, (char *)htopic.buf);  /* add the file string to the text widget */
    /* set the sensitivity of the Follow button */
    XtSetSensitive(follow_help_button, False);

    XtManageChild (help_dialog);

} /* HelpCB */




/*****************************  HelpTextCB  *************************************
**
**	Process callbacks from hypertext_text widget.
**	actually only motionVerifyCallback & XmCR_ACTIVATE can happen;
**	additionally this procedure is installed as a callback for follow_help_button
**	activates the link (remember that the callback structure in that case
**      IS NOT a TextVerifyCallbackStruct, the same is true for XmCR_ACTIVATE reason)
*/
#ifdef _NO_PROTO
void HelpTextCB (w, client_data, call_data)
    Widget w;
    caddr_t client_data;
    caddr_t call_data;
#else  /* _NO_PROTO */
void HelpTextCB (Widget w, caddr_t client_data, caddr_t call_data)
#endif
{

    XmTextVerifyCallbackStruct  *txt_data = (XmTextVerifyCallbackStruct *) call_data;
    /* this callback may be currently activated from a button as well so be careful and
	don't use improperly txt_data for the second reason of the callback */
    long pos;			/* in Motif XmTextPosition is defined as long */
    Arg    al[2];
    int    reason;		/* reason for the callback */
    int    i;
    long    link_startPos=0L, link_endPos=0L;
    long   new_htopic_id;

    static Time timestamp_move;
    static Boolean	setting_selection = False;	/* !! special flag for recursive calls */


    reason = txt_data->reason;

#ifdef _AS_DEBUG
    fprintf(stderr,"\nHelpText: %s:\n",
		reason == XmCR_OK ? "OK from control pane" :
		reason == XmCR_ACTIVATE ? "ACTIVATE" :
		reason == XmCR_MODIFYING_TEXT_VALUE ? "MODIFYING_TEXT_VALUE" :
		reason == XmCR_MOVING_INSERT_CURSOR ? "MOVING_INSERT_CURSOR" :
		reason == XmCR_VALUE_CHANGED        ? "VALUE_CHANGED"        : "unknown"
		);
#endif

    switch(reason)
    {
	case XmCR_MODIFYING_TEXT_VALUE:
	    /* should not happen */
	    break;

	case XmCR_VALUE_CHANGED:
	    /* should not happen - the callback structure is illegal */
	    return;

	case XmCR_MOVING_INSERT_CURSOR:
	    if(setting_selection ) return;   /* it was a recursive call during setting
			a selection !!!!! return immediately ****/
	    break;

	/* link may be activated or by double click in the hypertext text widhet
	   or by pressing "Folow" button below that text widget !!!!! */
	case XmCR_OK:
	case XmCR_ACTIVATE:
	    if(htopic.link_selected)
	    {
		/* activate the hypertext link; first calculate a new id using
		   the old one and link_index;
		*/
		if(htopic.id <	  10000000L)
			new_htopic_id = htopic.id * 100L + (long) htopic.link_index;
		else	new_htopic_id = htopic.id  + (long) htopic.link_index + 1L;

		if(hth.initialized) SaveBackHelpStep(&htopic);	/* save data from previous
						topic for the Back Help step */
		get_hypertext_topic(new_htopic_id, &hth, &htopic);
		/* inside get_... htopic.link_selected was already set to False */
		XmTextSetString(hypertext_text, (char *)htopic.buf);  /* add the file string to the text widget */
		/* set the sensitivity of the Follow button */
		XtSetSensitive(follow_help_button, False);
	    }
	    return;

	default:
	    /* an unknown client_data was received and there is no setup to handle this */
	    fprintf(stderr, "Warning: an unknown client_data in text callback\n");
	    fprintf(stderr,"Detected reason = %d \n", reason);
	    return;
    }

    /* we got here - this callback was called from hypertext_text ( == w) and
       txt_data has its all values defined */

    pos = txt_data->newInsert;

    htopic.cursor_pos = pos;		/* store the cursor position */

    switch(reason)
    {
	case XmCR_MODIFYING_TEXT_VALUE:
	    break;

	case XmCR_MOVING_INSERT_CURSOR:
	    /** if we do not allow resetting of the selection we will have
		all the time one link active (after first time selected)
		htopic.link_selected = False;
		*******/
	    /* make hypertext highlighting if in an appropriate place */
	    for( i=0; i < htopic.links; i++ )
	    {
	      /* we accept both positon at the beggining and at the end of the
		 link word (and of course inside it); this gives us a way of
		highlighting link using jumping to a next word in both
		directions */
	      if(pos >= htopic.link_pos[i] &&
		 pos <  (htopic.link_pos[i] + htopic.link_len[i]) )
	      {
		/* we are just over the link word; set a logical link flag and set
		link_startPos, link_endPos; set the link_index */
		htopic.link_selected = True;
		link_startPos = htopic.link_pos[i];
		link_endPos   = htopic.link_pos[i] + htopic.link_len[i];
		htopic.link_index    = i;
		/* set the sensitivity of the follow button */
		XtSetSensitive(follow_help_button, True);

		/* set the timestamp needed to set selection (it is
		   needed for the Search option as well ) */
		/* we have to check if the event is different from NULL (very important on X11R4) */
		if(txt_data->event) timestamp_move = txt_data->event->xkey.time;
		setting_selection = True;
		XmTextSetSelection(hypertext_text, link_startPos, link_endPos, timestamp_move);
		setting_selection = False;

		break;
	      }
	    }
	    break;
    }


} /* HelpTextCB*/


#ifdef _NO_PROTO
static Widget CreateHelpScrolledText (/* Widget parent */);
#else  /* _NO_PROTO */
static Widget CreateHelpScrolledText (Widget parent);
#endif



#define TIGHTNESS 20
/* TIGHTNESS determines how closely the items are placed in the action area.
   The higher the value, the close together the PusButtons appear; the lower the
   value, the further apart they are. The value above (20) was chosen arbitrarily
   and purely for aesthetic reasons.
*/

/*****************************	CreateActionArea	**********************
**	create and return a Composite widget thet contains a number of
**	PushButtons that are evenly distributed horizontally throughout the
**	widget. The actions and number of actions are specified in the actions
**	and num_actions parameters. The default button position is specified as
**	a parameter.
**	Procedure sets the kid widgets table (we might need one or few buttons
**	to be accessible globally, for example to change their sensitivity etc.)
*/
#ifdef _NO_PROTO
Widget CreateActionArea(parent, actions, num_actions, default_button_pos, kid )
    Widget parent;
    ActionAreaItem *actions;
    int num_actions;
    int default_button_pos;
    Widget *kid;
#else  /* _NO_PROTO */
Widget CreateActionArea(Widget parent, ActionAreaItem *actions, int num_actions,
	int default_button_pos, Widget *kid )
#endif
{
    Widget action_area, button;
    Arg al[15];         		/* Arg List */
    register int ac = 0;      		/* Arg Count */

    int i;

    XtSetArg(al[ac], XmNleftOffset, 10); 	ac++;
    XtSetArg(al[ac], XmNrightOffset, 10); 	ac++;
    XtSetArg(al[ac], XmNfractionBase, TIGHTNESS*num_actions - 1); 	ac++;

    action_area= XmCreateForm ( parent, "action_area", al, ac );

    for (i = 0; i < num_actions; i++)
    {
	ac = 0;
	XtSetArg(al[ac], XmNleftAttachment,       i? XmATTACH_POSITION : XmATTACH_FORM); 	ac++;
	XtSetArg(al[ac], XmNleftPosition,         TIGHTNESS*i); 	ac++;
	XtSetArg(al[ac], XmNtopAttachment,        XmATTACH_FORM); 	ac++;
	XtSetArg(al[ac], XmNbottomAttachment,     XmATTACH_FORM); 	ac++;
	XtSetArg(al[ac], XmNrightAttachment,
		i != num_actions-1? XmATTACH_POSITION : XmATTACH_FORM);	ac++;
	XtSetArg(al[ac], XmNrightPosition,        TIGHTNESS*i + (TIGHTNESS-1));	ac++;
    	XtSetArg(al[ac], XmNshowAsDefault,        i == default_button_pos);	ac++;
#ifndef X11R3
	XtSetArg(al[ac], XmNdefaultButtonShadowThickness, 1);	ac++;
#endif

	button = XmCreatePushButton (action_area, actions[i].label, al, ac);
        XtManageChild(button);
	/* store that widget to be accessible in the calling procedure */
	kid[i] = button;

	if (actions[i].callback)
	    XtAddCallback(button, XmNactivateCallback,
		(XtCallbackProc)actions[i].callback, (caddr_t)actions[i].data);
	if (i == default_button_pos) {
	    /* Set the action_area's default button to the first widget
	     * created (or, make the index a parameter to the function
	     * or have it be part of the data structure). Also, set the
	     * pane window constraint for max and min heights so this
	     * particular pane in the PanedWindow is not resizable.
	     */
#ifdef X11R3
	    short int mheight;
#else
	    Dimension mheight;
#endif
	    Dimension h;

	    ac = 0;
	    XtSetArg (al[ac], XmNmarginHeight, &mheight);   ac++;
	    XtGetValues(action_area, al, ac);

	    ac = 0;
	    XtSetArg (al[ac], XmNheight, &h);   ac++;
	    XtGetValues(button, al, ac);

#ifdef X11R3
	    /* do not increase h ... (just a Motif 1.0 bug) */
#else
	    h += 2*mheight;
#endif
	    ac = 0;
	    XtSetArg (al[ac], XmNdefaultButton, button);	ac++;
	    XtSetArg (al[ac], XmNpaneMaximum, h);       ac++;
	    XtSetArg (al[ac], XmNpaneMinimum, h);       ac++;
	    XtSetValues(action_area, al, ac);
	}
    }

    XtManageChild(action_area);

    return action_area;
}


/* When activate action happen in help text widget (Enter press or double click),
   respond by activating the follow_help_button in the action area
   as if the user had selected it. To make the procedure more general the
   button to activate is passed as a client data.
   The following procedure can be used only for X11R4 (XtCallActionProc was
   not available before)
 */
#ifndef X11R3

#ifdef _NO_PROTO
static void arm_and_activate_button(text_w, client_data, cbs)
    Widget text_w;
    caddr_t  client_data;
    XmAnyCallbackStruct *cbs;
#else  /* _NO_PROTO */
static void arm_and_activate_button(Widget text_w, caddr_t  client_data,
		XmAnyCallbackStruct *cbs)
#endif
{
    Widget button = (Widget)client_data;

    /* borrow the "event" field from cbs */
    /* make the button think it got pushed.  This causes
     * appropriate callback to be called, but XtCallActionProc() causes
     * the button appear to be activated as if the user selected it.
     * Call that procedure only when the button is sensitive !!!!
     * Just in case check if there is no NULL widget pass in (Should NEVER
     * happen, unless the client_data was set up incorrectly)
     */
    if(button && XtIsSensitive(button))
	XtCallActionProc(button, "ArmAndActivate", cbs->event, NULL, 0);

} /* arm_and_activate_button */

#endif



/* the following version of CreateHyperTextHelp set the action area labels
   directly (i.e. everything passed with arglist and argcount is ignored !!)
   I left the original declaration only for compatibility reasons with previous
   versions (i.e. CreateHyperTextHelp1 & CreateHyperTextHelp2).
*/

#ifdef _NO_PROTO
Widget CreateHyperTextHelp(parent, name, arglist, argcount )
    Widget parent;
    String name;
    Arg arglist[];
    int argcount;
#else  /* _NO_PROTO */
Widget CreateHyperTextHelp( Widget parent, String name,
		Arg arglist[], int argcount )
#endif
{
    Widget kid[10];       	/* Children created in the action area */
    Arg al[10];                 /* Arg List */
    register int ac = 0;        /* Arg Count */
    Dimension    h_space, v_space;
    Widget pane, help_dialog, apply_button, index_button, action_area, scrolled_window;

    static ActionAreaItem action_items[] = {
	{ "Follow", 	HelpTextCB,     (caddr_t)NULL		},
	{ "Back", 	HelpBackCB, 	(caddr_t)NULL		},
	{ "Index", 	HelpCB, 	(caddr_t)HTEXT_INDEX	},
	{ "Close",  	DialogCancelCB, (caddr_t)HTEXT_HELP   	},
	{ "Help",   	HelpCB,         (caddr_t)HTEXT_HELP	},
    };

    /* following widgets are defined as global:
	hypertext_text          - main hypertext widget (Text widget)
	follow_help_button      - button to activate link (to be made
					  sensitive or not)
	back_help_button	- button to go back one step in help history
    */


    XtSetArg(al[ac], XmNallowShellResize, TRUE); ac++;
    help_dialog = XmCreateDialogShell ( parent, "help_dialog", al, ac );

    /* Create a PanedWindow to manage the stuff in this dialog. This will
     * contain the control area (a ScrolledText widget) and the action area
     * (created by CreateActionArea() using the action_items defined above).
     */

    ac = 0;
    XtSetArg(al[ac], XmNsashWidth, 1);  ac++;
    XtSetArg(al[ac], XmNsashHeight,1);	ac++;
    pane = XmCreatePanedWindow(help_dialog, "pane", al, ac);

    /* create a scrolled text inside paned window  */
    hypertext_text = CreateHelpScrolledText (pane);
    XtManageChild (hypertext_text);



    /* create the action area - default button -> Index (button 2)*/

    action_area = CreateActionArea(pane, action_items, XtNumber(action_items),
			2, kid);

#ifdef X11R3
    XmAddTabGroup(hypertext_text);
    XmAddTabGroup(action_area);
#endif

    /* get the button used to activate the hypertext help link and the button for
       back help steps */
    follow_help_button = kid[0];
    back_help_button =   kid[1];
    index_button     =   kid[2];	/* it is needed only in this procedure */


    /* set the initial sensitivity of the Back button to False */
    XtSetSensitive(back_help_button, False);

    /* the action_area was already fixed to its current height inside CreateActionArea
       -- we never let it resize !! */

    /* ADD the activate callback - this callback is essential for hypertext help */

    /** version 1 & 2 :   XtAddCallback (hypertext_text, XmNactivateCallback, HelpTextCB, NULL);
    *** OLD (ver. 1 & 2)***/
    /* lets register callback which will simulate pressing the follow_help_button;
       the "true" callback that we are really interested is actually
       registered for the button which we "press" by calling arm_and_activate_button
       (in this case it is specified in the action_items);
       use a follow_help_button as a client data for that callback;
       Unfortunately this is ONLY available for X11R4 upwards....
       use the standard callback for X11R3... (without emulating button pressing)
    ***/
#ifdef X11R3
    XtAddCallback (hypertext_text, XmNactivateCallback, 
		(XtCallbackProc)HelpTextCB, NULL);
#else
    XtAddCallback (hypertext_text, XmNactivateCallback, 
		(XtCallbackProc)arm_and_activate_button, (caddr_t)follow_help_button);
#endif


    /* register helpCalback for the pane area;
       it will give us the right callback for both control and action areas
       when F1 (Motif Help) key  is  pressed;
       For X11R4 use a "clever" callback to emulate pressing the Help button in
       the action area
    */
#ifdef X11R3
    XtAddCallback (pane ,XmNhelpCallback,  
		(XtCallbackProc)HelpCB,   (caddr_t)HTEXT_HELP);
#else
    XtAddCallback (pane ,XmNhelpCallback, 
		(XtCallbackProc)arm_and_activate_button, (caddr_t)kid[4]);
#endif

    /**** ??????? HOW to SET ESCAPE key to unmanage this dialog ???? */



    /* set a special callback to set the focus to the index button
       in the action area when the dialog gets its first focus;
    */
    /** the following focusCallback trick does not work for my DEC's !!!
	so we are making conditional compilation for all such calls
	(full discussion - see comments in the asedit.c)

    **/
#ifndef XFOCUS_BUG
    XtAddCallback(hypertext_text, XmNfocusCallback, 
		(XtCallbackProc)focusCB, (caddr_t)index_button);
#endif

    /* turn off traversal on the sash */
    TurnOffSashTraversal(pane);


    return(pane);

}   /* CreateHyperTextHelp */



/*****************************  CreateHelpScrolledText  **************************
**
**	Create Help Scrolled Text widget and set the colour of recessed windows.
*/
#ifdef _NO_PROTO
static Widget CreateHelpScrolledText (parent)
    Widget parent;
#else  /* _NO_PROTO */
static Widget CreateHelpScrolledText (Widget parent)
#endif
{
    Arg		   al[15];		/*  arg list		*/
    register int   ac;			/*  arg count		*/
    Pixel	   foreground, background, background1;	/* used for a proper set up of mono screens */
    Widget	   text;


    /* create scrolled text widget */
    ac = 0;
    XtSetArg (al[ac], XmNresizeWidth, False);  ac++;
    XtSetArg (al[ac], XmNresizeHeight, False);  ac++;
    XtSetArg (al[ac], XmNscrollVertical, True);  ac++;
    XtSetArg (al[ac], XmNscrollHorizontal, False);  ac++;
    XtSetArg (al[ac], XmNeditMode, XmMULTI_LINE_EDIT);  ac++;
    XtSetArg (al[ac], XmNeditable, False);		ac++;
    XtSetArg (al[ac], XmNwordWrap, True);		ac++;

    text = XmCreateScrolledText (parent, "hypertext_text", al, ac);

    /* add motion callback */
    XtAddCallback (text, XmNmotionVerifyCallback,
		(XtCallbackProc)HelpTextCB, NULL);


    /* get the standard colour of the scrolled background */
    ac = 0;
    XtSetArg(al[ac], XmNbackground, &background1);	ac++;
    XtSetArg(al[ac], XmNforeground, &foreground);	ac++;
    XtGetValues(text, al, ac);

    /*	Set colours of a recessed vertical scrollbar
	We are doing that only for screens supporting more than 16
	colours (theoritically it should be done if the colour
	display is available, but practically if we have less than 16
	colours all of them probably have been already used up
	and the nearest available colour which will be assigned
	maybe inappropriate )*/


    if (DefaultDepthOfScreen(XDefaultScreenOfDisplay(display)) > 4)
    {
	Widget 		scrolled_window;
	Widget		vsbar;			/*  ScrollBar		*/
	XrmValue		pixel_data;

	scrolled_window = XtParent(text);
	XtSetArg (al[ac], XmNverticalScrollBar, &vsbar);	ac++;
	XtGetValues (scrolled_window, al, ac);	/* get the scroll bar */

	/* get a colour to be used for the scrollbar and remember it */
	_XmSelectColorDefault (scrolled_window, NULL, &pixel_data);
	background = *((Pixel *) pixel_data.addr);

	/* now check if the background is not by chance identical
	   with the foreground (it happens on mono screens or for a black/white
	   setup); if so do not apply it, but use the standard background1.
	*/
	if(background == foreground)	background = background1;

	XtSetArg (al[0], XmNbackground, background);
	XtSetValues (vsbar, al, 1);
    }

    return(text);

} /* CreateHelpScrolledText */


/* include private Sash header file - see below why */


#ifndef AMIGA
#include <Xm/SashP.h>
#endif

#ifdef _NO_PROTO
void TurnOffSashTraversal(pane)
    Widget pane;
#else  /* _NO_PROTO */
void TurnOffSashTraversal(Widget pane)
#endif
{
    /* procedure turn off XmtraversalOn on all the sashes in a PanedWindow;
       the PanedWindow sashes are widgets that are not described or defined
       publicly (therefore they are not technically supported). Because
       of that we have to include a private header file SashP.h.
       I do not now any other way of setting off the traversal on sashes.
       Probably there isn't any because internals of the PanedWindow widget
       hard-code its Sash widgets' XmNtraversalOn resource to True as they
       are created !
       This procedure works only for X11R4 (XmNchildren & XmNnumchildren are
       not defined in X11R3). On the other hand I do not need this procedure
       in X11R3 because traversal mechanism is set by the user.
    */

#ifndef X11R3
    Widget *children;
    int	    n_children=0;
    Arg		   al[3];
    register int   ac = 0;

    XtSetArg( al[ac], XmNchildren, &children);		ac++;
    XtSetArg( al[ac], XmNnumChildren, &n_children);	ac++;
    XtGetValues( pane, al, ac);

    while(n_children-- > 0)
    {
	if(XmIsSash(children[n_children]))
	{
	    ac = 0;
	    XtSetArg( al[0], XmNtraversalOn, False);
	    XtSetValues(children[n_children], al, 1);
	}
    }
#endif

}   /* TurnOffSashTraversal */


/* size of the back_help stack*/
#define BackHelpSTACK_SIZE		30

typedef struct
{
   long	id;				/* id of the topic 	*/
   long cursor_pos;			/* cursor position when new topic was called */
   Boolean	link_selected;		/* link ready to select flag */
   int		link_index;			/* link index (when link_selected is True)*/
}  BackHelpEl, *BackHelpPtr;


typedef struct
{
   int 		bottom;			/* bootom (head) of the stack */
   int 		top;			/* top (tail) of the stack */
   BackHelpPtr el[BackHelpSTACK_SIZE];	/* pointers to stack elements */
}  BackHelpStack;


#ifdef _NO_PROTO
void flush_backhelp_stack(/*  BackHelpStack *s  */);
void pop_backhelp_stack (/* BackHelpStack *s, BackHelpPtr *el **/);
void push_backhelp_stack(/* BackHelpStack *s, BackHelpPtr el  */);
#else  /* _NO_PROTO */
void flush_backhelp_stack(BackHelpStack *s);
void pop_backhelp_stack (BackHelpStack *s, BackHelpPtr *el);
void push_backhelp_stack(BackHelpStack *s, BackHelpPtr el);
#endif


BackHelpStack bhelp_stack = {0, 0};



#ifdef _NO_PROTO
void push_backhelp_stack(s, el)
    BackHelpStack *s;
    BackHelpPtr el;
#else  /* _NO_PROTO */
void push_backhelp_stack(BackHelpStack *s, BackHelpPtr el)
#endif
{
    /* push an element on the back help stack (on its top); the stack has predefined
       maximum length (BackHelpSTACK_SIZE); if the stack is filled up one element
       from the bottom of the stack is destroyed, address of the bottom is moved
       up so we get one space on the stack. In fact it is not a classic
       stack which I implemented here but this "cyclic" version gives an easy
       way of restoring last BackHelpSTACK_SIZE help topics;
       because of the possibility of moving of the stack bottom the address
       of any element on the stack must be calculated "modulo" BackHelpSTACK_SIZE;
    */

    s->el[s->top] = el;
    s->top = (s->top + 1) % BackHelpSTACK_SIZE;

    /* check if the stack is used up completely; if so destroy the oldest
       element (i.e. from the bottom of the stack) and move the address
       of the bottom
    */
    if(s->bottom == s->top)
    {
	s->bottom = (s->bottom + 1) % BackHelpSTACK_SIZE;
    }
} /* push_backhelp_stack */

#ifdef _NO_PROTO
void pop_backhelp_stack(s, el)
    BackHelpStack *s;
    BackHelpPtr *el;
#else  /* _NO_PROTO */
void pop_backhelp_stack(BackHelpStack *s, BackHelpPtr *el)
#endif
{
    /* pop the element from the top of the backhelp stack s */
    if(s->bottom != s->top)
    {
	s->top --;
	if(s->top < 0) s->top += BackHelpSTACK_SIZE;	/* "cyclic" stack */
	*el = s->el[s->top];				/* moved here to avoid
							   checking twice for sign
							   of s->topp -- */
    }
    else *el = NULL;					/* empty stack */
} /* pop_backhelp_stack */

#ifdef _NO_PROTO
void flush_backhelp_stack(s)
    BackHelpStack *s;
#else  /* _NO_PROTO */
void flush_backhelp_stack(BackHelpStack *s)
#endif
{
    /* flush the backhelp stack pointed by s */
    int i, j, n;
    n = s->top - s->bottom;
    if(n < 0) n += BackHelpSTACK_SIZE;		/* "cyclic" stack */

    for(i=0; i<n; i++)
    {
	j = s->top -i -1;
	if(j < 0) j += BackHelpSTACK_SIZE;
    }
    s->top = s->bottom;
} /* flush_backhelp_stack */

#ifdef _NO_PROTO
void reset_backhelp()
#else  /* _NO_PROTO */
void reset_backhelp(void)
#endif
{
    /* resets backhelp flushing the bhelp stack */

    flush_backhelp_stack(&bhelp_stack);

}   /* reset_backhelp */

#ifdef _NO_PROTO
void SaveBackHelpStep(topic)
    hypertext_topic *topic;
#else  /* _NO_PROTO */
void SaveBackHelpStep(hypertext_topic *topic)
#endif
{

    /* save the important elements of hypertext topic for back help steps */

    BackHelpPtr  undo_el;	/* el for undo operation */

    /* to speed the process of going back we preallocate a buffer for backhelp_history,
       with exactly the same length as BackHelpSTACK_SIZE; this get rid of permanent
       allocation and deallocation of memory for every backhelp action;
    */
    static BackHelpEl backhelp_history[BackHelpSTACK_SIZE];
    static Boolean backhelp_history_initialized = False;
    int i;

    /* when you start backhelp allocate memory for elements (if any) in backhelp_history */
    if(!backhelp_history_initialized)
    {
	backhelp_history_initialized = True;
	for(i=0; i < BackHelpSTACK_SIZE; i++)  ;  /* nothing to allocate in BackHelpEl-ements */
    }


    /* the undo_el is a pointer to an element in backhelp_history, the
       element in backhelp_history has the same index as the top element on the
       bhelp_stack (which has a "cyclic" nature)
    */
    undo_el = &backhelp_history[bhelp_stack.top];

    /* exctract the data from hypertext topic and set the undo_el values */
    undo_el->id			= topic->id;
    undo_el->cursor_pos		= topic->cursor_pos;
    undo_el->link_selected      = topic->link_selected;
    undo_el->link_index		= topic->link_index;

    /* push the undo data on the undo stack ... */
    push_backhelp_stack(&bhelp_stack, undo_el);
    /* set sensitivity to the back_help button */
    XtSetSensitive(back_help_button, True);

} /* SaveBackHelpStep */

/*****************************  HelpBackCB  *************************************
**
**		Process help Back callbacks (makes one step back in hypertext help)
*/
#ifdef _NO_PROTO
void HelpBackCB (w, client_data, call_data)
    Widget w;
    caddr_t client_data;
    caddr_t call_data;
#else  /* _NO_PROTO */
void HelpBackCB (Widget w, caddr_t client_data, caddr_t call_data)
#endif
{

    BackHelpPtr el=NULL;
    long position =0L;

    /* in the current version we always pop one element from the backhelp stack;
       if we would like to implement showing back the topic which was closed
       probably the best idea would be to check if the help is hown or not;
       if not shown - just Manage the dialog; otherwise follow as below ...
    ***/

    pop_backhelp_stack(&bhelp_stack, &el);
    if(bhelp_stack.bottom == bhelp_stack.top)  /* last element was popped... */
    {
	XtSetSensitive(back_help_button,  False);
	XFlush(display);	/* sensitivity must be set before next keys are
				   processed, otherwise with a busy system we
				   could "skip over" the bottom of the stack
				   and all track would be lost !!! */
    }

    if(el == NULL) 	return;	/* should not happen, we are doing this just in
				   case. If it did happen it would mean that
				   the undo/redo was called when the appropriate
				   stack was empty, i.e. the sensitivity of the
				   appropriate button was set improperly (but now
				   would set correctly) */





    /*  finally do the appropriate back help operation .... */

    get_hypertext_topic(el->id, &hth, &htopic);
    XmTextSetString(hypertext_text, (char *)htopic.buf);  /* add the file string to the text widget */
    /* set the sensitivity of the Follow button */
    XtSetSensitive(follow_help_button, False);

    /* now if the link was selected get the link_index to find the position
       of the link word (help keyword) and position there the insertion point;
       the HelpTextCB will highlight the keyword and set sensitivity to the follow
       button
    */
    if(el->link_selected)
    {
	/* just in case check if everything is well (we don't go outside the
           legal range); I do not see when it could happen but ... */
        if(el->link_index < htopic.links)
                position = htopic.link_pos[el->link_index];
        XmTextSetInsertionPosition(hypertext_text, position);
    }

    /* finally position the cursor when it was just before the next Help call happened */
    position = el->cursor_pos;  /* ????? does this destroy the highlighting ???? */
    XmTextSetInsertionPosition(hypertext_text, position);

    XtManageChild (help_dialog);        /* ?? it's probably already managed !! */

} /* HelpBackCB */



