
/* 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 - 6 June 1994
 */

/*
 * Copyright 1991 - 1994,  Andrzej Stochniol, London, UK
 *
 * ASEDIT text editor, both binary and source (hereafter, Software) is
 * copyrighted by Andrzej Stochniol (hereafter, AS) and ownership remains
 * with AS.
 *
 * AS grants you (hereafter, Licensee) a license to use the Software
 * for academic, research and internal business purposes only, without a
 * fee.  Licensee may distribute the binary and source code (if released)
 * to third parties provided that the copyright notice and this statement
 * appears on all copies and that no charge is associated with such copies.
 *
 * Licensee may make derivative works.  However, if Licensee distributes
 * any derivative work based on or derived from the Software, then
 * Licensee will:
 * (1) notify AS regarding its distribution of the derivative work, and
 * (2) clearly notify users that such derivative work is a modified version
 *      and not the original ASEDIT distributed by AS.
 *
 * Any Licensee wishing to make commercial use of the Software should
 * contact AS to negotiate an appropriate license for such commercial use.
 * Commercial use includes:
 * (1) integration of all or part of the source code into a product for sale
 *     or license by or on behalf of Licensee to third parties, or 
 * (2) distribution of the binary code or source code to third parties that
 *     need it to utilize a commercial product sold or licensed by or on 
 *     behalf of Licensee.
 *
 * A. STOCHNIOL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS
 * SOFTWARE FOR ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR
 * IMPLIED WARRANTY.  IN NO EVENT SHALL A. STOCHNIOL BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * By using or copying this Software, Licensee agrees to abide by the
 * copyright law and all other applicable laws, and the terms of this
 * license.
 * AS shall have the right to terminate this license immediately by
 * written notice upon Licensee's breach of, or non-compliance with, any
 * of its terms.  Licensee may be held legally responsible for any
 * copyright infringement that is caused or encouraged by Licensee's
 * failure to abide by the terms of this license.
 *
 *
 * 	Andrzej Stochniol	(A.Stochniol@ic.ac.uk)
 * 	30 Hatch Road
 * 	London SW16 4PN
 * 	UK
 */


#include <stdio.h>
#include <string.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 <Xm/XmP.h>     /* needed for declaration of _XmSelectColorDefault (prcatically needed only since Motif 1.2x) */

#if defined(vax11c) || defined(__DECC)
#   define MAXPATHLEN 256
#else
#   include <sys/param.h>		/* for MAXPATHLEN declaration */
#endif /* vax11c */

#include "asedit.h"             /* for the definition of aseditWindowStruct  */


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

/* default help file name */

static char *help_name_def = "asedit.hlp";


/* declarations & data 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) */
    int  data;		/* client data for the callback routine */
} ActionAreaItem;



hypertext_topic htopic;
hypertext_help  hth = {False, 0, 0, NULL};


Widget 	hypertext_text, follow_help_button, back_help_button;



/* specific definitions & data for going back in the help window */

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


BackHelpStack bhelp_stack = {0, 0};




/* procedure prototypes */
#ifdef _NO_PROTO
void TurnOffSashTraversal ( );
static void SaveBackHelpStep ( );
static void HelpBackCB ( );
static Widget CreateHelpScrolledText ( );
static void flush_backhelp_stack ( );
static void pop_backhelp_stack   ( );
static void push_backhelp_stack  ( );

#else  /* _NO_PROTO */
void TurnOffSashTraversal(Widget pane);
static void SaveBackHelpStep(hypertext_topic *topic);
static void HelpBackCB (Widget w, XtPointer client_data, XtPointer call_data);
static Widget CreateHelpScrolledText (Widget parent);
static void flush_backhelp_stack(BackHelpStack *s);
static void pop_backhelp_stack (BackHelpStack *s, BackHelpPtr *el);
static void push_backhelp_stack(BackHelpStack *s, BackHelpPtr el);

#endif


#ifdef _NO_PROTO
char *createFullHelpName(helpDir, helpName, helpDirFallback, helpNameFallback)
    char *helpDir;
    char *helpName;
    char *helpDirFallback;
    char *helpNameFallback;
#else  /* _NO_PROTO */

char *createFullHelpName(char *helpDir, char *helpName, char *helpDirFallback, char *helpNameFallback)
#endif
{

    char help_file_name[MAXPATHLEN];            /* full help file name */
    char *fullName=NULL;
    int  len;

    /* create full help file name (including path); user specified help directory
       takes precedence over the one set during installation */

    if(!helpDir) strcpy(help_file_name, helpDirFallback); /* helpDir (X resource) was NULL */
    else         strcpy(help_file_name, helpDir);

#if !defined(vax11c) && !defined(__DECC)
    if(help_file_name[strlen(help_file_name) - 1] != '/') strcat(help_file_name,"/"); /* add '/' if needed */
#endif /* !vax11c */
    if(!helpName) strcat(help_file_name, helpNameFallback); /* helpName (X resource) was NULL */
    else          strcat(help_file_name, helpName);

    len = strlen(help_file_name);

    fullName = (char *) XtMalloc(len+1);

    if(fullName)
	strcpy(fullName, help_file_name);

    return fullName;

}   /* createFullHelpName */
   


#ifdef _NO_PROTO
static void get_hypertext_topic(topic_id, hth, topic)
    long topic_id;
     hypertext_help *hth;
     hypertext_topic *topic;
#else  /* _NO_PROTO */
static 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];



    if(!hth->file_name)
    {
	/* first call; create the full help file name (including path) */
	hth->file_name = createFullHelpName(lstr.helpDir, lstr.helpName, help_dir, help_name_def);
    }

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


    if ( (in = fopen(hth->file_name,"rb")) == NULL )
    {
	sprintf(topic->buf, lstr.help_err_fopen, hth->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,	lstr.help_err_finc, hth->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 */


	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,
		lstr.help_no_info	/* language specific: "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 */
#if defined(vax11c) || defined(__DECC)
    topic->buf[l++] = '\0';
#else
    topic->buf[l++] = NULL;
#endif  /* vax11c */
    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  */


#ifdef _NO_PROTO
static void show_hypertext_links(text, htopic)
     Widget text;
     hypertext_topic *htopic;
#else  /* _NO_PROTO */
static void show_hypertext_links(Widget text, hypertext_topic *htopic)
#endif
{
    int i;
    XmTextPosition left, right;
    
    /* make hypertext highlighting if in an appropriate place */
    for( i=0; i < htopic->links; i++)
    {
	left = (XmTextPosition)htopic->link_pos[i];
	right =  (XmTextPosition) (htopic->link_pos[i] + htopic->link_len[i]);
	/**** underline the whole - NOT USED
	   XmTextSetHighlight(text, (XmTextPosition)htopic->link_pos[i], 
		(XmTextPosition) (htopic->link_pos[i] + htopic->link_len[i]), 
		XmHIGHLIGHT_SECONDARY_SELECTED);
	****/

	/* highlight the brackets only but do that in the reverse */
	XmTextSetHighlight(text, left-1,  left, XmHIGHLIGHT_SELECTED);
	XmTextSetHighlight(text, right,  right+1, XmHIGHLIGHT_SELECTED);

    }

}

/* a workaround procedure for Motif 1.1 (to switch off the highlighting) */

#ifdef _NO_PROTO
static void hide_hypertext_links(text, htopic)
     Widget text;
     hypertext_topic *htopic;
#else  /* _NO_PROTO */
static void hide_hypertext_links(Widget text, hypertext_topic *htopic)
#endif
{
    int i;
    XmTextPosition left, right;
    
    /* switch off hypertext highlighting if in an appropriate place 
	(needed only for Motif before Motif 1.2 !!!- buggy old Motif) */
    for( i=0; i < htopic->links; i++)
    {
	left = (XmTextPosition)htopic->link_pos[i];
	right =  (XmTextPosition) (htopic->link_pos[i] + htopic->link_len[i]);

	/* restore the default bracket's colour  */
	XmTextSetHighlight(text, left-1,  left, XmHIGHLIGHT_NORMAL);
	XmTextSetHighlight(text, right,  right+1, XmHIGHLIGHT_NORMAL);

    }

}   /* hide_hypertext_links */



/*****************************  HelpCB  *************************************
**
**		Process help callbacks
*/
#ifdef _NO_PROTO
void HelpCB (w, client_data, call_data)
    Widget w;
    XtPointer client_data;
    XtPointer call_data;
#else  /* _NO_PROTO */
void HelpCB (Widget w, XtPointer client_data, XtPointer call_data)
#endif
{
    int id                  = get_id_from_asdat(client_data);
    aseditWindowStruct *win = get_win_from_asdat(client_data);

    TurnWatchCursor(True);
    if(help_dialog == NULL)	/* first create the help dialog */
    {
	/* we create just one help dialog (common for all asedit windows); it reduces
	   clutter on the display and reduces memory requirements for multiple
	   editing windows session;
	*/
	help_dialog =  CreateHyperTextHelp(toplevel, "help window", NULL, 0);
    }

    /* 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 && (win->replace_dialog != NULL) &&
		XtIsManaged(win->new_text))  id += 200;


    if(xmUseVersion < 1002)     /* work around a bug in Motif before 1.2 */
        hide_hypertext_links(hypertext_text, &htopic);
    get_hypertext_topic(id, &hth, &htopic);
    XmTextSetString(hypertext_text, (char *)htopic.buf);  /* add the file string to the text widget */
    show_hypertext_links(hypertext_text, &htopic);
    /* set the sensitivity of the Follow button */
    XtSetSensitive(follow_help_button, False);

    asManageDialog (help_dialog);

    TurnWatchCursor(False);

} /* 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
static void HelpTextCB (w, client_data, call_data)
    Widget w;
    XtPointer client_data;
    XtPointer call_data;
#else  /* _NO_PROTO */
static void HelpTextCB (Widget w, XtPointer client_data, XtPointer 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 */

    int id                  = get_id_from_asdat(client_data);
    aseditWindowStruct *win = get_win_from_asdat(client_data);



    reason = txt_data->reason;


    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 widget
	   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 */
		if(xmUseVersion < 1002)     /* work around a bug in Motif before 1.2 */ 
			hide_hypertext_links(hypertext_text, &htopic);
		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 */
		show_hypertext_links(hypertext_text, &htopic);
		/* set the sensitivity of the Follow button */
		if(xmUseVersion >= 1002) /* in SGI IRIX5, 
			Motif 1.2.2 you can't set the current
                        default button to insensitive, so first traverse to the next 
			button for each Motif 1.2 implementation !!!*/
                    XmProcessTraversal(follow_help_button, XmTRAVERSE_NEXT);

		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, 
			 CurrentTime);   /* *USE* the CurrentTime */
			/**** asedit 1.1x   timestamp_move); ***/
		/* above we use CurrentTime because on AIX 3.2.3e the old selections are not
		   properly turned off; the CurrentTime solve the problem; we might possibly
		   get rid off timestamp_move altogether (1.09.1993)
		*/
		setting_selection = False;

		break;
	      }
	    }
	    break;
    }


} /* HelpTextCB*/





#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++;

#if (XmVersion > 1000)
	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, mk_asdat(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.
	     */
#if (XmVersion == 1000)
	    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);

#if (XmVersion == 1000)
	    /* 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)
 */
#if (XtSpecificationRelease > 3)

#ifdef _NO_PROTO
static void arm_and_activate_button(text_w, client_data, cbs)
    Widget text_w;
    XtPointer  client_data;
    XmAnyCallbackStruct *cbs;
#else  /* _NO_PROTO */
static void arm_and_activate_button(Widget text_w, XtPointer  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,     0		},
	{ "Back", 	HelpBackCB, 	0		},
	{ "Index", 	HelpCB, 	HTEXT_INDEX	},
	{ "Close",  	DialogCancelCB, HTEXT_HELP   	},
	{ "Help",   	HelpCB,         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++;
    XtSetArg (al[ac], XmNdefaultPosition, False); 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);

#if (XmVersion == 1000)
    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)
    ***/

#if (XmVersion == 1000)
    XtAddCallback (hypertext_text, XmNactivateCallback, 
		(XtCallbackProc)HelpTextCB, mk_asdat(0));
#else
    XtAddCallback (hypertext_text, XmNactivateCallback, 
		(XtCallbackProc)arm_and_activate_button, (XtPointer)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
    */
#if (XmVersion == 1000)
    XtAddCallback (pane ,XmNhelpCallback,  
		(XtCallbackProc)HelpCB,   mk_asdat(HTEXT_HELP) );
#else
    XtAddCallback (pane ,XmNhelpCallback, 
		(XtCallbackProc)arm_and_activate_button, (XtPointer)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, (XtPointer)index_button);
#endif

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

    {
	Dimension width, height;
	/* initially position the dialog in the middle of the screen */
	
	ac = 0;
        XtSetArg(al[ac], XmNwidth,  &width);    ac++;
        XtSetArg(al[ac], XmNheight, &height);   ac++;
        XtGetValues (pane, al, ac);
	ac = 0;
        XtSetArg(al[ac], XmNx, (int)(WidthOfScreen(XtScreen(toplevel))-width)/2);  ac++;
        XtSetArg(al[ac], XmNy, (int)(HeightOfScreen(XtScreen(toplevel))-height)/2);  ac++;
        XtSetValues (help_dialog, al, ac);
    }

    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		*/
    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, mk_asdat(0));

    if(lstr.useOldColorSetup)
    {
	Pixel	   foreground, background, background1;	/* used for a proper set up of mono screens */

	/* 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, 0, &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);
	}
    }   /* useOldColorSetup */

    return(text);

} /* CreateHelpScrolledText */


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

#include <Xm/SashP.h>

#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.
    */

#if (XmVersion > 1000)
    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); ac++;
	    XtSetValues(children[n_children], al, ac);
	}
    }
#endif

}   /* TurnOffSashTraversal */




#ifdef _NO_PROTO
static void push_backhelp_stack(s, el)
    BackHelpStack *s;
    BackHelpPtr el;
#else  /* _NO_PROTO */
static 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
static void pop_backhelp_stack(s, el)
    BackHelpStack *s;
    BackHelpPtr *el;
#else  /* _NO_PROTO */
static 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
static void flush_backhelp_stack(s)
    BackHelpStack *s;
#else  /* _NO_PROTO */
static 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
static void reset_backhelp()
#else  /* _NO_PROTO */
static void reset_backhelp(void)
#endif
{
    /* resets backhelp flushing the bhelp stack */

    flush_backhelp_stack(&bhelp_stack);

}   /* reset_backhelp */

#ifdef _NO_PROTO
static void SaveBackHelpStep(topic)
    hypertext_topic *topic;
#else  /* _NO_PROTO */
static 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
static void HelpBackCB (w, client_data, call_data)
    Widget w;
    XtPointer client_data;
    XtPointer call_data;
#else  /* _NO_PROTO */
static void HelpBackCB (Widget w, XtPointer client_data, XtPointer call_data)
#endif
{

    BackHelpPtr el=NULL;
    long position =0L;

    int id                  = get_id_from_asdat(client_data);
    aseditWindowStruct *win = get_win_from_asdat(client_data);


    /* 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... */
    {
       if(xmUseVersion >= 1002)	/* in SGI IRIX5, Motif 1.2.2 you can't set the current
			default button to insensitive, so first traverse to the next button
		  	for each Motif 1.2 implementation !!!*/
                XmProcessTraversal(back_help_button, XmTRAVERSE_NEXT);
        

	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 .... */

    if(xmUseVersion < 1002) 	/* work around a bug in Motif before 1.2 */
    	hide_hypertext_links(hypertext_text, &htopic);
    get_hypertext_topic(el->id, &hth, &htopic);
    XmTextSetString(hypertext_text, (char *)htopic.buf);  /* add the file string to the text widget */
    show_hypertext_links(hypertext_text, &htopic);
    /* 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);

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

} /* HelpBackCB */



