/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
/* $TOG: HelpUtil.c /main/19 1998/04/09 17:43:30 mgreess $ */
/************************************<+>*************************************
 ****************************************************************************
 **
 **   File:        HelpUtil.c
 **
 **   Project:     Rivers Project
 **
 **   Description: 
 ** 
 **
 **  (c) Copyright 1987, 1988, 1989, 1990, 1991, 1992 Hewlett-Packard Company
 **
 **  (c) Copyright 1993, 1994 Hewlett-Packard Company
 **  (c) Copyright 1993, 1994 International Business Machines Corp.
 **  (c) Copyright 1993, 1994 Sun Microsystems, Inc.
 **  (c) Copyright 1993, 1994 Novell, Inc.
 **
 ****************************************************************************
 ************************************<+>*************************************/


#include <sys/param.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>  /* R_OK */
#include <sys/stat.h>

#include <sys/types.h>
#define X_INCLUDE_PWD_H
#define XOS_USE_XT_LOCKING
#include <X11/Xos_r.h>

#include <Xm/Xm.h>
#include <Xm/XmP.h>
#include <Xm/MwmUtil.h>

#include <Xm/MessageB.h>
#include <X11/Shell.h>
#include <X11/Intrinsic.h>
#include <X11/cursorfont.h>

/* private includes */
#include "Access.h"
#include "bufioI.h"
#include "DisplayAreaI.h"
#include "DisplayAreaP.h"

#include <Dt/Help.h>
#include "HelpI.h"
#include "HelpP.h"
#include "StringFuncsI.h"
#include "HelpDialogI.h"
#include "HelpDialogP.h"
#include "HelpUtilI.h"
#include "HelposI.h"
#include "HyperTextI.h"
#include "FormatI.h"
#include "MessagesP.h"
#include "HelpQuickD.h"
#include "SetListI.h"
#include "DestroyI.h"
#include "HelpAccessI.h"
#include "FileUtilsI.h"
#include "HourGlassI.h"
#include "Lock.h"

#include <Dt/DtNlUtils.h>

/******* global variables *******/
char _DtHelpDefaultHelp4HelpVolume[] = "Help4Help";
char _DtHelpDefaultLocationId[] = "_HOMETOPIC";

/**** Help Util Error message Defines ****/

#define UtilMessage0	_DtHelpMsg_0010
#define UtilMessage2	_DtHelpMsg_0011


static void _DtMessageClose(
    Widget w,
    XtPointer client_data,
    XEvent *event);
static void  CloseDefBoxCB(
    Widget w,
    XtPointer client_data,
    XtPointer call_data);

/* Macro for finding a point within a gadget.
 * Its used for item help 
 */
#define  PT_IN_CHILD(X, Y, CHILD) \
	 ((((int)(X)) >= ((int) (CHILD)->core.x)) && \
          (((int)(X)) <= ((int)((CHILD)->core.x + (CHILD)->core.width))) && \
          (((int)(Y)) >= ((int) (CHILD)->core.y)) && \
          (((int)(Y)) <= ((int)((CHILD)->core.y + (CHILD)->core.height))))


/******** useful constants ********/
#define EOS  '\0'
#define DIR_SLASH '/'

#define HUSET 8     /* message catalog set */

/******** static variables ********/


/******** data structures ********/
typedef struct ExecContext 
{
   char * command;
   XtPointer pDisplayArea;
} ExecContext;

/******** The onitem cursor (32x32, xbm format) ********/
#define onitem32_width 32
#define onitem32_height 32
#define onitem32_x_hot 0
#define onitem32_y_hot 0
static unsigned char onitem32_bits[] = {
   0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0x1f, 0xfc, 0xf9, 0xff, 0xe7, 0xf3,
   0xf1, 0xff, 0xfb, 0xef, 0xe1, 0xff, 0xfd, 0xdf, 0xc1, 0xff, 0xfd, 0xdf,
   0x83, 0xff, 0xfe, 0xbf, 0x03, 0xff, 0x7e, 0x7e, 0x03, 0xfe, 0xbe, 0x7d,
   0x03, 0xfc, 0xbe, 0x7d, 0x03, 0xf0, 0xc1, 0x7d, 0x03, 0xe0, 0xff, 0x7e,
   0x07, 0xc0, 0x7f, 0xbf, 0x07, 0x80, 0xbf, 0xbf, 0x07, 0x00, 0xde, 0xdf,
   0x07, 0x00, 0xdc, 0xef, 0x07, 0x00, 0xdf, 0xf7, 0x07, 0x80, 0xdf, 0xfb,
   0x0f, 0xc0, 0xdf, 0xfb, 0x0f, 0xc0, 0xdf, 0xfb, 0x0f, 0x81, 0xdf, 0xfb,
   0xcf, 0x83, 0x3f, 0xfc, 0xef, 0x07, 0xff, 0xff, 0xff, 0x07, 0xff, 0xff,
   0xff, 0x0f, 0x3e, 0xfc, 0xff, 0x0f, 0xde, 0xfb, 0xff, 0x1f, 0xdc, 0xfb,
   0xff, 0x1f, 0xdc, 0xfb, 0xff, 0x3f, 0xd8, 0xfb, 0xff, 0x3f, 0x3c, 0xfc,
   0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

#define onitem32_m_width 32
#define onitem32_m_height 32
#define onitem32_m_x_hot 0
#define onitem32_m_y_hot 0
static unsigned char onitem32_m_bits[] = {
   0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0xe0, 0x03, 0x0f, 0x00, 0xf8, 0x0f,
   0x1f, 0x00, 0xfc, 0x1f, 0x3f, 0x00, 0xfe, 0x3f, 0x7f, 0x00, 0xfe, 0x3f,
   0xfe, 0x00, 0xff, 0x7f, 0xfe, 0x01, 0xff, 0xff, 0xfe, 0x03, 0x7f, 0xfe,
   0xfe, 0x0f, 0x7f, 0xfe, 0xfe, 0x1f, 0x3e, 0xfe, 0xfe, 0x3f, 0x00, 0xff,
   0xfc, 0x7f, 0x80, 0x7f, 0xfc, 0xff, 0xc1, 0x7f, 0xfc, 0xff, 0xe3, 0x3f,
   0xfc, 0xff, 0xe7, 0x1f, 0xfc, 0xff, 0xe3, 0x0f, 0xfc, 0xff, 0xe0, 0x07,
   0xf8, 0x7f, 0xe0, 0x07, 0xf8, 0x7f, 0xe0, 0x07, 0xf8, 0xff, 0xe0, 0x07,
   0xf8, 0xfe, 0xc0, 0x03, 0x38, 0xfc, 0x01, 0x00, 0x18, 0xfc, 0x01, 0x00,
   0x00, 0xf8, 0xc3, 0x03, 0x00, 0xf8, 0xe3, 0x07, 0x00, 0xf0, 0xe7, 0x07,
   0x00, 0xf0, 0xe7, 0x07, 0x00, 0xe0, 0xef, 0x07, 0x00, 0xe0, 0xc7, 0x03,
   0x00, 0xc0, 0x03, 0x00, 0x00, 0x80, 0x00, 0x00};

#if 0 /* XPM format */
static char * onitem32_xpm[] = {
/* width height ncolors cpp [x_hot y_hot] */
"32 32 3 1 0 0",
/* colors */
" 	s iconColor1	m black	c black",
".	s background	m black	c #969696969696",
"X	s iconColor2	m white	c white",
/* pixels */
"  ..............................",
" X ..................XXXXX......",
" XX ...............XX     XX....",
" XXX .............X         X...",
" XXXX ...........X           X..",
" XXXXX ..........X           X..",
". XXXXX ........X             X.",
". XXXXXX .......X      XX      X",
". XXXXXXX ......X     X..X     X",
". XXXXXXXX  ....X     X..X     X",
". XXXXXXXXXX ....XXXXX...X     X",
". XXXXXXXXXXX ..........X      X",
".. XXXXXXXXXXX ........X      X.",
".. XXXXXXXXXXXX  .....X       X.",
".. XXXXXXXXXXXXXX ...X       X..",
".. XXXXXXXXXXXXXXX ..X      X...",
".. XXXXXXXXXXXXX  ...X     X....",
".. XXXXXXXXXXXX .....X    X.....",
"... XXXXXXXXXX ......X    X.....",
"... XXXXXXXXXX ......X    X.....",
"... XXXX XXXXXX .....X    X.....",
"... XX  . XXXXX ......XXXX......",
"... X .... XXXXX ...............",
"...  ..... XXXXX ...............",
"........... XXXXX ....XXXX......",
"........... XXXXX ...X    X.....",
"............ XXXXX ..X    X.....",
"............ XXXXX ..X    X.....",
"............. XXXXX .X    X.....",
"............. XXXX ...XXXX......",
".............. X  ..............",
"............... ................"};
#endif

#define ROOT_USER 0
#define BIN_USER  2
#define SYS_USER  3

#define NO_CONDITION           0
#define MISMATCHING_HOME_DIRS  1

/* ------------------------------------------------------------ *
**
**  Function     trusted
**
**  Purpose      Determines if the passed help volume is a 
**               "trusted"  help volume or not.  We call it 
**               trusted if it meets the following conditions:
**                  1. File Owner is root, bin, or system. 
**                  2. File is NOT writable by group or others.
**                     
**  Returns
**               True   -  if the help volume IS Trusted
**              False   -  if the help volume is NOT Trusted
**
** ------------------------------------------------------------ */
static Boolean trusted (char *hv_path)  /* Full path to the help volume */
{
   struct stat buf;
   Boolean     writable;

   if ( (stat (hv_path, &buf)) == -1)
      return False;


/* S_IWGRP */   /* write group */
/* S_IWOTH */   /* write other */

  /** ---------------------------------------------------------------------- *
   **  The help volume MUST be owned by root, bin, or sys to be trusted.
   ** ---------------------------------------------------------------------- */

   if ( buf.st_uid != ROOT_USER &&
        buf.st_uid != BIN_USER  &&
        buf.st_uid != SYS_USER)
   {
      return False;
   }

  /** ----------------------------------------------------------------------- *
   **  The help volume MUST not be writable by group or others to be trusted.
   ** ----------------------------------------------------------------------- */

   writable = (((buf.st_mode & S_IWGRP) == 0) && (buf.st_mode & S_IWOTH) == 0) ?  
              True : False;

   return writable;

}


/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/
/****       API Error Dialog Support Functions       *****/
/*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/


/************************************************************************
 * Function: _DtMessageClose
 *
 *	Close the error/info message box.
 *
 ************************************************************************/ 
static void _DtMessageClose(
    Widget w,
    XtPointer client_data,
    XEvent *event )
{                        
   /* NOTE: ExecuteContextCB() is dependent on this code */
   if (event->type == UnmapNotify)
   {
      XtRemoveEventHandler (XtParent (client_data), StructureNotifyMask, 
                            True, (XtEventHandler) _DtMessageClose, client_data);

      XtUnmanageChild (client_data);
      XtDestroyWidget (client_data);
   }
}

/************************************************************************
 * Function: ExecuteContextCB
 *
 *	Execute an execution context
 *
 ************************************************************************/ 
static void ExecuteContextCB(
    Widget w,
    XtPointer client_data,
    XtPointer callData )
{
    ExecContext * ec = (ExecContext *) client_data;

    if (ec && ec->command && ec->pDisplayArea)
    {
        _DtHelpExecProcedure(ec->pDisplayArea,ec->command);
        free(ec->command);
    }
    XtFree((char *) ec);

    /* unmap, rather than unmanage and destroy, because of the code
       in _DtMessageClose().  _DtMessageClose() is notified when 
       the widget unmaps and it destroys the widget. */
    XtUnmapWidget(w);       /* w is the message dialog */
}

/*****************************************************************************
 * Function: CreateErrorDialog
 *
 *  Creates an XmMessageDialog with the message and all buttons
 *  except the 'Close' (OK) button unmanaged.
 *  Also adds a callback that destroys the widget when the dialog is closed.
 * 
 *****************************************************************************/
static Widget 
CreateErrorDialog(
    Widget                   parent,
    char *                   message)
{     
   Widget	button;
   Widget	messageDialog;
   Arg		args[10];
   int	        n;
 
   XmString	label_string;
   XmString      ok_string;
   char          *title_string; 
 
   /* Setup the message string and dialog title */
 
   ok_string = XmStringCreateLocalized(((char *)_DTGETMESSAGE
                               (HUSET, 2,"Close")));
   label_string = XmStringCreateLocalized(message);
   title_string = XtNewString((char *)_DTGETMESSAGE
                              (HUSET, 5,"Help Error"));
 
   n = 0;
   XtSetArg (args[n], XmNmessageString, label_string);  n++;
   XtSetArg (args[n], XmNtitle,title_string); n++;
   XtSetArg (args[n], XmNcancelLabelString, ok_string); n++;
   XtSetArg (args[n], XmNdefaultButtonType, XmDIALOG_CANCEL_BUTTON); n++;
   messageDialog = XmCreateErrorDialog (parent, "errorDialog",
 					       args, n);
   XtSetArg(args[0], XmNmwmDecorations,
 		              MWM_DECOR_BORDER | MWM_DECOR_TITLE);
   XtSetArg(args[1], XmNuseAsyncGeometry, True);
   XtSetValues(XtParent(messageDialog), args, 2);
 
   XmStringFree (label_string);
   XmStringFree (ok_string);
   XtFree(title_string);
 
   /* unmanage or define the other buttons */
   button = XmMessageBoxGetChild (messageDialog, XmDIALOG_OK_BUTTON);
   XtUnmanageChild (button);
   button = XmMessageBoxGetChild (messageDialog, XmDIALOG_HELP_BUTTON);
   XtUnmanageChild (button);
 
   /* StructureNotifyMask gets Circulate, Configure, Destroy, 
      Gravity, Map, Reparent, & Unmap events */
   XtAddEventHandler(XtParent(messageDialog),
 	             StructureNotifyMask,True,
                     (XtEventHandler) _DtMessageClose, (XtPointer) messageDialog);
 
   return messageDialog;           /* RETURN */

}

/*****************************************************************************
 * Function: CreateExecErrorDlg
 *
 * 
 * 
 * Called by:
 *****************************************************************************/
static Widget
CreateExecErrorDlg(
      Widget   helpWidget,
      const char *  cmdStr,
      Boolean  invalidAlias,
      _DtHelpCommonHelpStuff * pHelpStuff,
      int        condition,
      char     * current_hd)
{
   DtHelpListStruct *pHelpInfo;
   Widget msgDlg;
   Widget btn;
   char *  msg;
   char *  fullmsg;

   /* handle the error case */
   if (invalidAlias)
   {
      msg = (char *)_DTGETMESSAGE(HUSET, 12,
               "The help volume wanted to execute a command alias.\n"
               "The alias '%s' is not defined.");
   }

   else if (condition == MISMATCHING_HOME_DIRS)
   {
      msg = (char *)_DTGETMESSAGE(HUSET, 14,
"The help volume wanted to execute a command as the root user, but the\n"
"home directory of  \"%s\"  ($HOME) does not match the root\n"
"user's home directory.  This could result in executing unexpected\n"
"commands.\n\n"

"The command is:  \"%s\"\n\n"

"Note:  to avoid this in the future:\n"
"  execute \"su - root\"  rather than \"su root\".\n");
   }

   else
   {
      msg = (char *)_DTGETMESSAGE(HUSET, 13,
             "The help volume wanted to execute a command.\n"
             "For security reasons, automatic command execution is turned off.\n"
             "The command is:  %s");
   }
   fullmsg = (char *) malloc(strlen(msg)+strlen(cmdStr)+30);
   if (fullmsg) 
   {
      if (condition == MISMATCHING_HOME_DIRS)
         sprintf(fullmsg, msg, current_hd, cmdStr);
      else
         sprintf(fullmsg,msg,cmdStr);
   }
   else 
      fullmsg = msg;

   /* create an error dialog, but don't manage it yet */
   msgDlg = CreateErrorDialog(XtParent(helpWidget),fullmsg);
   
   if (msg != fullmsg) free(fullmsg);

   btn = XmMessageBoxGetChild (msgDlg, XmDIALOG_HELP_BUTTON);
   XtManageChild (btn);           /* re-manage the button */

   /* add the HelpOnHelp callback */
   pHelpInfo = _DtHelpListAdd(DtHELP_ExecutionPolicy_STR,
                        helpWidget, pHelpStuff, &pHelpStuff->pHelpListHead);
   XtAddCallback(btn, XmNactivateCallback, _DtHelpCB, (XtPointer) pHelpInfo);

   return msgDlg;
}
 
/*****************************************************************************
 * Function: _DtHelpErrorDialog
 *
 * 
 * 
 * Called by:
 *****************************************************************************/
void _DtHelpErrorDialog(
    Widget                   parent,
    char *                   message)
{     
  Widget messageDialog;

  messageDialog = CreateErrorDialog(parent,message);

  /* Display help window. This used to be before the call 
     to add a StructureNotify event handler */
  XtManageChild (messageDialog);
}


/*****************************************************************************
 * Function: _DtHelpFilterExecCmdStr
 *
 *   Args:
 *    helpWidget:	help widget requesting to exec the command
 *    pDisplayStuff:    ptr to the DisplayWidget stuff of the help widget
 *    commandStr:	command string to execute
 *    ret_cmdStr:       the screened & possibly rewritten command is put here
 *    ret_invalidAlias:  was the command an invalid alias?
 *    ret_execPermitted: if executionPolicy permit exec & ret_cmdStr is valid
 *    ret_queryNeeded:  if executionPolicy requires a query before exec
 *
 * Description:
 *    ret_cmdStr gets memory owned by the calling function; it should be
 *    freed when no longer needed.  The string will be the same as the
 *    commandStr if commandStr was not an alias.  If the commandStr
 *    is an alias and if the alias is defined, the ret_cmdStr will be the
 *    value of the alias.  If the alias isn't defined, the ret_cmdStr will
 *    be the default command if available, or the alias name otherwise.
 * 
 *    ret_invalidAlias will be True if the alias was undefined and
 *    no default command was given.
 *
 *    ret_execPermitted will be True if executionPolicy is DtHELP_EXECUTE_ALL
 *    or DtHELP_EXECUTE_QUERY_xxx and ret_cmdStr is valid
 *
 *    ret_queryNeeded will be True if executionPoilcy is 
 *    DtHELP_EXECUTE_QUERY_ALL or if it is DtHELP_EXECUTE_QUERY_UNALIASED 
 *    and ret_cmdStr did not derive from an alias (i.e. was hardcoded
 *    in the help volume, not retrieved from a resource).
 *
 * Returns:
 *    True:  if execPermitted and a valid command string
 *    False: if if execPermitted is False or invalid command string
 *
 * Comments:
 *    This code is written such that we don't need nor want to know
 *    whether it is a general or quick help widget.
 * 
 * Warning:
 *    command string must be writable; it is written, but left
 *    unchanged when the function exits.
 *
 *****************************************************************************/
Boolean _DtHelpFilterExecCmdStr(
    Widget                   helpWidget,
    unsigned char            executionPolicy,
    const char *             commandStr,
    char * *                 ret_cmdStr,
    Boolean *                ret_invalidAlias,
    Boolean *                ret_execPermitted,
    Boolean *                ret_queryNeeded,
    char *		     hv_path)     /* Path to the Help Volume */
{     
   char * token;
   char * tokenEnd;
   char   tokenEndChar;
   char * aliasCommand = NULL;
   Boolean ret;

#define RN_execAlias "executionAlias"
#define RC_execAlias "ExecutionAlias"
#define ExecAliasCmd "DtHelpExecAlias"

   /* default values */
   *ret_cmdStr = NULL;
   *ret_invalidAlias = False;
   *ret_execPermitted = False;
   *ret_queryNeeded = False;

   if (NULL == commandStr) 
      return False;

  /** ------------------------------------------------------------- *
   **  If the executionPolicy is query all unaliased (query for all
   **  execution links that have no execution alias defined), we 
   **  make an exception:  only query the user for help volumes 
   **  deemed NOT "trusted".
   ** ------------------------------------------------------------- */

   if (DtHELP_EXECUTE_QUERY_UNALIASED == executionPolicy)
   {
      if ( ! (trusted (hv_path)))
         *ret_queryNeeded = True;   /* Query ALL non-trusted help volumes */
   }
   else
      *ret_queryNeeded = (DtHELP_EXECUTE_QUERY_ALL == executionPolicy);

   /* get whether exec permitted */
   if (   DtHELP_EXECUTE_ALL == executionPolicy 
       || DtHELP_EXECUTE_QUERY_UNALIASED == executionPolicy
       || DtHELP_EXECUTE_QUERY_ALL == executionPolicy)
      *ret_execPermitted = True;
   else
      *ret_execPermitted = False;

   /* parse apart the command string, looking for DtHelpExecAlias */
   /* The first call will return true, with the first string */
   token = (char *) commandStr + DtStrspn((char *)commandStr, " \t");
   tokenEnd = token + DtStrcspn(token, " \t");
   tokenEndChar = *tokenEnd;
   if (tokenEnd) *tokenEnd = EOS;

   if ( NULL == token || _DtHelpCeStrCaseCmpLatin1(token, ExecAliasCmd) != 0 )
   {
      /*** the command is not an alias; proceed using execution Policy ***/
   
      *tokenEnd = tokenEndChar;       /* restore the string */
   
      *ret_cmdStr = strdup(commandStr);
      ret = *ret_execPermitted;

      return ret;                             /* RETURN ret */
   }


   /**** It's an alias; get it , look it up, and return it ****/

   *tokenEnd = tokenEndChar;       /* restore the string */

   /* get the next token */
   token = tokenEnd + DtStrspn(tokenEnd, " \t");
   tokenEnd = token + DtStrcspn(token, " \t");
   tokenEndChar = *tokenEnd;
   if (tokenEnd) *tokenEnd = EOS;

   if ( token )
   {
      Display * dpy = XtDisplay(helpWidget);
      XrmDatabase appDb = XrmGetDatabase(dpy);
      XrmValue value;
      String appname, appclass;
      char * reptype;
      char *rsrc_name, *rsrc_class;

      rsrc_name = XtMalloc(200);
      rsrc_class = XtMalloc(200);

      XtGetApplicationNameAndClass(dpy,&appname,&appclass);

      /* query the application's database for the alias command */
      /* build alias resource class and resource */
      /* e.g. App.executionAlias.<alias> */
      sprintf(rsrc_name,"%s.%s.%s",appclass, RN_execAlias,token);
      /* e.g. App.ExecutionAlias.<alias> */
      sprintf(rsrc_class,"%s.%s.%s",appclass, RC_execAlias,token);

      /* Get alias command */
      if (XrmGetResource(appDb,rsrc_name,rsrc_class,&reptype,&value) == True)
         aliasCommand = value.addr;

      /* build alias resource name and resource */
      /* e.g. app.executionAlias.<alias> */
      sprintf(rsrc_name,"%s.%s.%s",appname, RN_execAlias,token);
      /* e.g. app.ExecutionAlias.<alias> */
      sprintf(rsrc_class,"%s.%s.%s",appname, RC_execAlias,token);

      /* Get alias command and override class with instance, if defined */
      if (XrmGetResource(appDb,rsrc_name,rsrc_class,&reptype,&value) == True)
         aliasCommand = value.addr;

      if (rsrc_name) XtFree(rsrc_name);
      if (rsrc_class) XtFree(rsrc_class);
   }  /* if alias token */
   else 
   {
      token = "";
   }

   if (tokenEnd) *tokenEnd = tokenEndChar; /* restore the string */

   /* alias was defined */
   if (aliasCommand) 
   {
      *ret_cmdStr = strdup(aliasCommand);
      /* see if query needed; is not if policy is query_unaliased or all */
      *ret_queryNeeded = !(   DtHELP_EXECUTE_QUERY_UNALIASED == executionPolicy
                           || DtHELP_EXECUTE_ALL == executionPolicy);
      ret = *ret_execPermitted;
   }
   else /* the alias wasn't defined */
   {
      char * aliasToken = token;   /* token currently pts to alias */

      /* look for a default command */
      /* get the next token */
      token = tokenEnd + DtStrspn(tokenEnd, " \t");
      tokenEnd = token + DtStrcspn(token, " \t");

      if (token == tokenEnd)
      {  /* alias wasn't defined and no default command */
         *ret_cmdStr = strdup(aliasToken);
         *ret_invalidAlias = True;
         *ret_queryNeeded = False;  /* no query needed on invalid alias, ever */
         *ret_execPermitted = False; /* can't exec an invalid alias */
         ret = False;
      }
      else 
      {  /* alias wasn't defined but a default command */
         /* query is whatever was determined earlier */
         *ret_cmdStr = strdup(token);
         ret = *ret_execPermitted;
      }
   }

   return ret;                          /* RETURN ret */
}

/*********************************************************************
 * _DtHelpCeWaitAndProcessEvents
 *
 *  Purpose:
 *    _DtHelpCeWaitAndProcessEvents will process events and call
 *    the waitProc until waitProc returns False.   This function
 *    is useful to put up modal dialogs that must be reponded to 
 *    in the midst of executing code that must remain on the call stack.
 *
 *  Warning:
 *    This function should only be used on modal dialogs.
 *
 *********************************************************************/

void
_DtHelpCeWaitAndProcessEvents (
    Widget             w,
    _DtHelpCeWaitProc  waitProc,
    void *             clientData)
{
    Boolean waitFlag;
    XEvent   event;
    XtAppContext app;

    app = XtWidgetToApplicationContext(w);
    do 
    {
#ifndef XTHREADS
        XtAppNextEvent(app,&event);
        XtDispatchEvent(&event);
#else
	XtInputMask mask;

	while (!(mask = XtAppPending(app)))
	  ;   /* Busy waiting - so we don't lose our Lock! */
	
	if (mask & XtIMXEvent)         /* We have an XEvent */
	  {
	    /* Get the XEvent - we know it's there! Note that XtAppNextEvent
	       would also process timers/alternate inputs.
	     */
	    XtAppNextEvent(app, &event);  /* No blocking, since an event is ready */
	    XtDispatchEvent(&event);
	  }
	else   /* Not a XEvent, it's an alternate input/timer event */
	  {
	    XtAppProcessEvent(app, mask); /* No blocking, since an event is ready */
	  }
#endif /* XTHREADS */
        /* check to see if we're done waiting */
        waitFlag = (*waitProc)(w, clientData); 
    } while (waitFlag);
}

/*****************************************************************************
* Function: WaitForBtnActivatedCB
*
* Purpose:
*    Treats the 'clientData' as a pointer to an integer
*    and turns its value into a Boolean
*
* Returns:  *(int *)clientData < 0
*****************************************************************************/
static Boolean 
WaitForBtnActivatedCB(
    Widget w,
    void * clientData)
{
    return (*(int *)clientData < 0);
   /* True=keep waiting; False= wait no longer */
}


typedef struct ModalMsgDlgCBStruct
{
  Widget msgDlg;
  Widget okBtn;
  Widget cancelBtn;
  Widget helpBtn;
  int    activatedBtnId;
} ModalMsgDlgCBStruct;

/*****************************************************************************
* Function: IdentifyActivatedBtnCB
*
* Purpose:
*    Treats the 'clientData' as a pointer to an integer.
*    Waits for the value pointed to by clientData to be >= 0.
*
*****************************************************************************/
static void 
IdentifyActivatedBtnCB(
    Widget w,
    XtPointer clientData,
    XtPointer callData)
{
   ModalMsgDlgCBStruct * pMd = (ModalMsgDlgCBStruct *) clientData;
    
   /* w must be a XmMessageDialog widget */
   if (pMd->okBtn == w) 
      { pMd->activatedBtnId = XmDIALOG_OK_BUTTON; return; /* RETURN */ }
   if (pMd->cancelBtn == w) 
      { pMd->activatedBtnId = XmDIALOG_CANCEL_BUTTON; return; /* RETURN */ }
   if (pMd->helpBtn == w) 
      { pMd->activatedBtnId = XmDIALOG_HELP_BUTTON; return; /* RETURN */ }
   pMd->activatedBtnId = -1;      /* unknown button */
}
    

/*****************************************************************************
 * Function: _DtHelpFilterExecCmd
 *
 *   Args:
 *    helpWidget:	help widget requesting to exec the command
 *    command:		command string to execute
 *    execPolicy:       current policy setting
 *    useQueryDialog:   use a dialog to query user whether to exec, if not allowed
 *    pHelpStuff:       ptr to the HelpStuff structure of the help widget
 *    ret_filteredCmdStr: filtered command string
 * 
 * Returns:
 *    0: no error; filteredCmdStr can be exec'd
 *   -1: error: either internal or executionPolicy denies exec; 
 *       filteredCmdStr is NULL
 *
 * Purpose:
 *    This function filters an execution command string.  This can
 *    occur in several ways.  In all cases, the command string is
 *    supports command alias replacement.  If the final outcome
 *    is that execution is permitted, the returned string is
 *    is the command string to execute.  If execution is not
 *    permitted, the return string is a NULL pointer. 
 *
 *    Filtering of the command occurs as follows.
 *    If executionPolicy permits execution, only alias replacement occurs.
 *    If executionPolicy does restrict execution and a
 *    dialog is requested, then a modal dialog is posted and the
 *    user can decide whether to execute or not.
 *    If a dialog is not requested, the return string is NULL.
 *
 * Comments:
 *    This code is written such that we don't need nor want to know
 *    whether it is a general or quick help widget.
 * 
 * Warning:
 *    command string must be writable; it is written, but left
 *    unchanged whent the function exits.
 *
 *    This operation is synchronous, meaning that, if a dialog is
 *    posted, it is a modal dialog and the function won't return 
 *    until the user selects a button.
 *
 * Called by:
 *****************************************************************************/
int _DtHelpFilterExecCmd(
    Widget        helpWidget,
    const char *  commandStr,
    unsigned char executionPolicy,
    Boolean       useQueryDialog,
    _DtHelpCommonHelpStuff * pHelpStuff,
    char * *      ret_filteredCmdStr,
    char *		     hv_path)
{     
   ModalMsgDlgCBStruct msgDlgCBStruct;
   Boolean goodCmd;
   Boolean invalidAlias;
   Boolean execPermitted;
   Boolean queryNeeded;
   Widget  msgDlg;
   char *  filteredCmdStr = NULL;
   Arg	   args[5];
   int     n;
   XmString labelString;
   XmString labelString2;
   Widget  noexecBtn;
   Widget  execBtn;

   goodCmd = _DtHelpFilterExecCmdStr(helpWidget, executionPolicy, 
                 commandStr, &filteredCmdStr, &invalidAlias, 
                 &execPermitted, &queryNeeded, hv_path);

   /* if permissions allow immediate execution, do so */
   if (execPermitted && False == queryNeeded)
   {
      *ret_filteredCmdStr = filteredCmdStr;
      return 0;                           /* RETURN ok */
   }

   if (False == useQueryDialog)
   {
      *ret_filteredCmdStr = NULL;
      XtFree(filteredCmdStr);
      return -1;                          /* RETURN error */
   }

   /* create the dialog, but don't yet manage it */
   msgDlg =  CreateExecErrorDlg(helpWidget,filteredCmdStr,
                        invalidAlias,pHelpStuff, NO_CONDITION, "");

   /* if a bad alias or no exec permitted, 
      don't need to wait for a response; dlg has close & Help */
   if (False == execPermitted || False == queryNeeded)  
   {
      XtManageChild(msgDlg);                 /* manage modeless dialog */
      *ret_filteredCmdStr = NULL;            /* don't execute */
      XtFree(filteredCmdStr);
      return -1;                            /* RETURN error */
   }

   /* if got this far, query is needed;make the dialog include 
      Execute Anyway and Don't Execute buttons */

   /* give the right title to the buttons */
   labelString = XmStringCreateLocalized(((char *)_DTGETMESSAGE
                   (HUSET, 10,"Execute Anyway")));
   /* give the right title to the Cancel button */
   labelString2 = XmStringCreateLocalized(((char *)_DTGETMESSAGE
                   (HUSET, 11,"Don't Execute")));
   n = 0;
   XtSetArg (args[n], XmNokLabelString, labelString);	n++;
   XtSetArg (args[n], XmNcancelLabelString, labelString2);		n++;
   XtSetArg (args[n], XmNdialogStyle, XmDIALOG_PRIMARY_APPLICATION_MODAL); n++;
   XtSetValues(msgDlg,args,n);
   XmStringFree(labelString);
   XmStringFree(labelString2);

   /* We put an activate callback on the DontExecute and ExecuteAnyway buttons 
      and wait until a button is pressed.  */
   noexecBtn = XmMessageBoxGetChild (msgDlg, XmDIALOG_CANCEL_BUTTON);
   XtAddCallback(noexecBtn, XmNactivateCallback, 
                             IdentifyActivatedBtnCB, (XtPointer) &msgDlgCBStruct);

   execBtn = XmMessageBoxGetChild (msgDlg, XmDIALOG_OK_BUTTON);
   XtManageChild (execBtn);           /* re-manage the button */
   XtAddCallback(execBtn, XmNactivateCallback, 
                             IdentifyActivatedBtnCB, (XtPointer) &msgDlgCBStruct);
   
   /* fill out the CB information structure used by IdentifyActivatedBtnCB */
   msgDlgCBStruct.msgDlg = msgDlg;
   msgDlgCBStruct.okBtn = execBtn;
   msgDlgCBStruct.cancelBtn = noexecBtn;
   msgDlgCBStruct.activatedBtnId = -1;

   /* Display message dialog */
   XtManageChild (msgDlg);

   /*
    * turn on the modal dialog indicator
    */
   _DtHelpTurnOnNoEnter(helpWidget);

   /* wait until 'msgDlgCBStruct.activatedBtnId' has a value >= 0 */
   /* this occurs when the user responds to the msg dialog */
   _DtHelpCeWaitAndProcessEvents(msgDlg, 
              WaitForBtnActivatedCB, &msgDlgCBStruct.activatedBtnId);

   /*
    * turn off the modal dialog indicator
    */
   _DtHelpTurnOffNoEnter(helpWidget);

   /* no need to destroy msgDlg; it has a closeCallback to do that */

   /* act based on which button was activated */
   if (msgDlgCBStruct.activatedBtnId == XmDIALOG_OK_BUTTON)
   {
      *ret_filteredCmdStr = filteredCmdStr;  /* do execute command */
      return 0;                             /* RETURN ok */
   }
   else
   {
      *ret_filteredCmdStr = NULL;            /* don't execute */
      XtFree(filteredCmdStr);
      return -1;                            /* RETURN error */
   }
}

/*****************************************************************************
 * Function: _DtHelpExecFilteredCmd
 *
 *   Args:
 *    helpWidget:	help widget requesting to exec the command
 *    command:		command string to execute
 *    modal:            is the execution modal (sync) or modeless (async)
 *    helpLocationId:	helpOnHelp file location for Help btn in error dialog
 *    pDisplayStuff:    ptr to the DisplayWidget stuff of the help widget
 *    pHelpStuff:	ptr to the CommonHelp stuff of the help widget
 * 
 * Comments:
 *    This code is written such that we don't need nor want to know
 *    whether it is a general or quick help widget.
 * 
 * Warning:
 *    command string must be writable; it is written, but left
 *    unchanged whent the function exits.
 *
 *    At the moment, the helpLocationId is ignored, and the
 *    help location is hardwired to DtHELP_ExecutionPolicy_STR
 *    in CreateExecErrorDialog().
 * Called by:
 *****************************************************************************/
void _DtHelpExecFilteredCmd(
    Widget                   helpWidget,
    char *                   commandStr,
    char *                   helpLocationId,
    _DtHelpDisplayWidgetStuff * pDisplayStuff,
    _DtHelpCommonHelpStuff * pHelpStuff)
{     
   Boolean goodCmd;
   Boolean invalidAlias;
   Boolean execPermitted;
   Boolean queryNeeded;
   char *  filteredCmdStr = NULL;
   ExecContext * execContext;
   XmString labelString;
   XmString labelString2;
   Widget   msgDlg;
   Widget   btn;
   int     n;	
   Arg	   args[5];
   char    *hv_path=NULL;

   uid_t         user;
   char          *home_dir;
   Boolean        diff_home_dirs=False; /* True ==> $HOME is different from */
                                        /* root user's $HOME directory      */
/*
   getpw{uid,nam}_r routines fail on IBM platform when search password info
   via NIS (yellow pages). However, in the case of root, we'll assume that
   the password info is in /etc/passwd. If this is not the case, the
   following code can fail on IBM platform when XTHREADS and XUSE_MTSAFE_API
   are defined.
*/
/*
   _Xgetpwparams pwd_buf;
*/
   struct passwd * pwd_ret;

  /** -------------------------------------------------------------- *
   **  If we're running as the root user
   **   o check if the value of the HOME env var matches 
   **     root's home directory (defined by /etc/passwd).
   **   o If they do not match, then present a dialog 
   **     alerting the user of this, along with the command to 
   **     invoke.
   ** -------------------------------------------------------------- */

   if ( (user=getuid()) == ROOT_USER)
   {
      home_dir = getenv ("HOME");

      if (home_dir != NULL && strlen(home_dir) >= (size_t) 1)
      {
         if (((pwd_ret = _XGetpwuid(user, pwd_buf)) != NULL)
                         && (strcmp(home_dir, pwd_ret->pw_dir)))
         {
            diff_home_dirs = True;
         }
      }
   }

   hv_path = _DtHelpFileLocate(DtHelpVOLUME_TYPE, pDisplayStuff->helpVolume,
                               _DtHelpFileSuffixList, False, R_OK);

   /* The desired and intended behaviour is to use _DtHelpFilterExecCmdStr(), but
      the other code is left here, should a change be wished. */
#if 1
   /* This function runs a filter for policy and alias but posts no dialog  */
   goodCmd=_DtHelpFilterExecCmdStr(helpWidget, 
              pDisplayStuff->executionPolicy, commandStr, 
               &filteredCmdStr, &invalidAlias, &execPermitted, &queryNeeded, hv_path);
#else
    /* This function does an synchronous filter; i.e. the code runs a filter
       for policy and alias, and if policy denies exec and the command is
       valid, then posts a modal dialog and waits for the user to decide
       what to do before returning. */
   goodCmd = _DtHelpFilterExecCmd(helpWidget, commandStr, 
                                  pDisplayStuff->executionPolicy, True, 
                                  pHelpStuff, &filteredCmdStr, hv_path);
   execPermitted = (goodCmd == 0);  /* convert an error int into a Boolean */
   queryNeeded =    
          ((   pDisplayStuff->executionPolicy==DtHELP_EXECUTE_QUERY_ALL)
            || (pDisplayStuff->executionPolicy==DtHELP_EXECUTE_QUERY_UNALIASED))
       && goodCmd;
#endif

   /* if permissions allow immediate execution, do so */
   if (execPermitted && False == queryNeeded  && diff_home_dirs == False)
   {
      (void) _DtHelpExecProcedure (pHelpStuff->pDisplayArea, filteredCmdStr);
      free(filteredCmdStr);
      return;                           /* RETURN */
   }

   /* this traps bad cmds and also use of the synchronous filter call */
   if (NULL == filteredCmdStr) return;       /* RETURN */

   /*** Create a modeless dialog to inform the user of the problem
        and possibly allow them to execute the command anyway. ***/

   /* create the dialog, but don't yet manage it */
   if ( diff_home_dirs == True)
     msgDlg =  CreateExecErrorDlg(helpWidget,filteredCmdStr, invalidAlias,pHelpStuff, 
                                  MISMATCHING_HOME_DIRS, home_dir );
   else
     msgDlg =  CreateExecErrorDlg(helpWidget,filteredCmdStr, invalidAlias,pHelpStuff, 
                                  NO_CONDITION, "");


   /*** setup ExecuteAnyway and Help buttons ***/

   if ( (diff_home_dirs == True)  
                     ||
        (queryNeeded && execPermitted) )
   {
      /* give the right title to the buttons */
      labelString = XmStringCreateLocalized(((char *)_DTGETMESSAGE
                         (HUSET, 10,"Execute Anyway")));
      /* give the right title to the Cancel button */
      labelString2 = XmStringCreateLocalized(((char *)_DTGETMESSAGE
                      (HUSET, 11,"Don't Execute")));
      n = 0;
      XtSetArg (args[n], XmNokLabelString, labelString);	n++;
      XtSetArg (args[n], XmNcancelLabelString, labelString2);	n++;
      XtSetValues(msgDlg,args,n);
      XmStringFree(labelString);
      XmStringFree(labelString2);
   
      btn = XmMessageBoxGetChild (msgDlg, XmDIALOG_OK_BUTTON);
      XtManageChild (btn);           /* re-manage the button */
   
      /* add the ExecuteContextCB() client-data and callback */
      execContext = malloc(sizeof(ExecContext));
      if (execContext) 
      { 
         execContext->command = filteredCmdStr;
         execContext->pDisplayArea = pHelpStuff->pDisplayArea;
         XtAddCallback(btn, XmNactivateCallback, 
                             ExecuteContextCB, (XtPointer) execContext);
      }
      else
      {
         free(filteredCmdStr);
      }
   }  /* cmd wasn't an alias */
   else
   {
      free(filteredCmdStr);
   }

   /* Display message dialog */
   XtManageChild (msgDlg);

   /* RETURN */
}



/*****************************************************************************
 * Function: LocateWidgetId()
 *
 *   
 *
 * Called by: 
 *****************************************************************************/
static Widget  LocateWidgetId(
    Display          *dpy,
    int              screen,
    int             *statusRet,
    Widget           shellWidget,
    Cursor           cursorIn)
{
    static Cursor    DfltOnItemCursor = 0;
    Widget           widget;
    Widget           child;
    CompositeWidget  comp_widget;
    int              status;
    Cursor           cursor;
    XEvent           event;
    int              x,y;
    int              i;
    Window           parent;
    Window           sub;
    Window           target_win;
    int              new_x, new_y;
    int              offset;
    KeySym           keySym;
    Boolean          notDone=TRUE;

 
    /* Make the target cursor */
    if (cursorIn != 0)
        cursor = cursorIn;
    else
#if 0
        cursor = XCreateFontCursor (dpy, XC_question_arrow);
#else
    {
        _DtHelpProcessLock();
        if (0 == DfltOnItemCursor)
        {
            char        *bits;
            char        *maskBits;
            unsigned int width;
            unsigned int height;
            unsigned int xHotspot;
            unsigned int yHotspot;
            Pixmap       pixmap;
            Pixmap       maskPixmap;
            XColor       xcolors[2];
   
            width    = onitem32_width;
            height   = onitem32_height;
            bits     = (char *) onitem32_bits;
            maskBits = (char *) onitem32_m_bits;
            xHotspot = onitem32_x_hot;
            yHotspot = onitem32_y_hot;
    
            pixmap = XCreateBitmapFromData (dpy,
                         RootWindowOfScreen(XtScreen(shellWidget)), bits,
                         width, height);
    
    
            maskPixmap = XCreateBitmapFromData (dpy,
                         RootWindowOfScreen(XtScreen(shellWidget)), maskBits,
                         width, height);
    
            xcolors[0].pixel = BlackPixelOfScreen(ScreenOfDisplay(dpy, screen));
            xcolors[1].pixel = WhitePixelOfScreen(ScreenOfDisplay(dpy, screen));
    
            XQueryColors(dpy,
			 DefaultColormapOfScreen(ScreenOfDisplay(dpy, screen)), 
			 xcolors, 
			 2);
    
            DfltOnItemCursor = XCreatePixmapCursor (dpy, pixmap, maskPixmap,
                                          &(xcolors[0]), &(xcolors[1]),
                                          xHotspot, yHotspot);
            XFreePixmap (dpy, pixmap);
            XFreePixmap (dpy, maskPixmap);
        }  /* if dflt cursor not yet created */
        cursor = DfltOnItemCursor;
        _DtHelpProcessUnlock();
    }  /* if to use the standard cursor */
#endif


    /* Grab the pointer using target cursor, letting it roam all over */
    status = XtGrabPointer (shellWidget, TRUE,
                           ButtonPressMask|ButtonReleaseMask, GrabModeAsync,
                           GrabModeAsync, None, cursor, CurrentTime);
    if (status != GrabSuccess)
      {
        XmeWarning(shellWidget,(char *)_DTGETMESSAGE(HUSET, 3,
              "Internal Error: Could not grab the mouse\nDtHelpReturnSelectedWidget aborted.\n"));
        *statusRet = DtHELP_SELECT_ERROR;
        return(NULL);
      }



    /* Grab the Keyboard so we can catch the ESC button press */
    status = XtGrabKeyboard(shellWidget, False,
                           GrabModeAsync, GrabModeAsync, CurrentTime);
    if (status != GrabSuccess)
      {
        
        XtUngrabPointer (shellWidget, CurrentTime);
        XmeWarning(shellWidget,(char *)_DTGETMESSAGE(HUSET, 4,
        "Internal Error: Could not grab the keyboard\nDtHelpReturnSelectedWidget() aborted.\n"));
        *statusRet = DtHELP_SELECT_ERROR;
        return(NULL);
      }

    /* We are ok so let the user select a window... */
    while (notDone)
    {
      XtAppContext app = XtWidgetToApplicationContext(shellWidget);
      /* allow one more event */
#ifndef XTHREADS
      XtAppNextEvent(app, &event);
#else
      XtInputMask mask;

      while (!(mask = XtAppPending(app)))
	  ;   /* Busy waiting - so we don't lose our Lock! */
	
      if (!(mask & XtIMXEvent)) /* Not a XEvent, it's an alternate input/timer event */
	    XtAppProcessEvent(app, mask); /* No blocking, since an event is ready */
      else   /* We have an XEvent */
	{
	    /* Get the XEvent - we know it's there! Note that XtAppNextEvent
	       would also process timers/alternate inputs.
	     */
	    XtAppNextEvent(app, &event);
#endif /* XTHREADS */
      widget = XtWindowToWidget(dpy, event.xany.window);
      
      switch (event.type) {
            case ButtonPress:
                break;
            case ButtonRelease:
                notDone = FALSE;
                break;
            case KeyPress:
                /* Look for ESC key press and stop if we get one */
                if (event.xkey.state & ShiftMask)
                  offset = 1;
                else
                  offset = 0;
                
                keySym = XLookupKeysym((XKeyEvent *)&event, offset);
                if (keySym == XK_Escape)
		  {
                    XtUngrabKeyboard (shellWidget, CurrentTime);
                    XtUngrabPointer (shellWidget, CurrentTime);
                    *statusRet = DtHELP_SELECT_ABORT;
                    return(NULL);
		  }
            default:
                  XtDispatchEvent(&event);
      }
#ifdef XTHREADS
	}    /* if */
#endif
    } 

    XtUngrabKeyboard (shellWidget, CurrentTime);      /* Done with keyboard */
    XtUngrabPointer (shellWidget, CurrentTime);      /* Done with pointer */

    /* If its null then the user selected some area outside our window(s) */
    if (widget == shellWidget)
      {
        *statusRet = DtHELP_SELECT_INVALID;
        return (NULL);
      }
    if (!XtIsComposite (widget))
      {
        *statusRet = DtHELP_SELECT_VALID;
        return (widget);
      }
    
    /* Get the x and y and parent relative to the current window */
    parent = RootWindow(dpy, screen);
    target_win = XtWindow(widget);
    x = event.xbutton.x_root;
    y = event.xbutton.y_root;

    XTranslateCoordinates(dpy, parent, target_win, x, y,
                             &new_x, &new_y, &sub);
    x = new_x;
    y = new_y;

    comp_widget = (CompositeWidget)widget;

    /*  look for gadgets at this point  */
    for (i = 0; i < comp_widget->composite.num_children; i++) {
        child = comp_widget->composite.children[i];
        /* put in check for only managed widgets here */
         if(XtIsManaged(child))
           if (PT_IN_CHILD (x, y, child))
              {
                *statusRet = DtHELP_SELECT_VALID;
                return (child);
              }
    }

    *statusRet = DtHELP_SELECT_VALID;
    return (widget);
}




/*****************************************************************************
 * Function:	   Boolean RememberDir(String path)
 *
 * Parameters:		path		Specifies the path to check.
 *
 * Return Value:	Boolean		if the path name is good.
 *
 * Description:	Use the directory caching mechanism to improve performance
 *		by remembering the directories that have already been
 *		stat'ed.
 *
 *****************************************************************************/
static	Boolean
RememberDir(String path)
{
    int		 result = 0;
    char        *ptr;
    struct stat  buf;

    if (path == NULL || *path == '\0')
	return False;

    if (_DtHelpCeStrrchr(path, "/", MB_CUR_MAX, &ptr) == 0 && ptr != path)
      {
	*ptr   = '\0';
	result = _DtHelpCeCheckAndCacheDir(path);
	*ptr   = '/';
      }
    if (result == 0 && access(path, R_OK) == 0 &&
				stat(path, &buf) == 0 && S_ISREG(buf.st_mode))
	return True;

    return False;
}

/*****************************************************************************
 * Function:	   Boolean _DtHelpResolvePathname(
 *
 *
 * Parameters:     
 *
 * Return Value:    Boolean.
 *
 *
 * Description: _DtHelpResolvePathname attempts to validate and expand a path
 *              to a Cache Creek help access file.
 *
 *****************************************************************************/
Boolean _DtHelpResolvePathname(
   Widget       widget,
   char * *	io_fileName,
   _DtHelpVolumeHdl * io_volumeHandle,
   char *	sysVolumeSearchPath,
   char *	userVolumeSearchPath)
{
   String           newPath = NULL;

   /* try to locate file and its entry, if present */
   newPath = _DtHelpFileLocate(DtHelpVOLUME_TYPE, *io_fileName,
                                  _DtHelpFileSuffixList,False,R_OK);

   /* If we found a valid file let's do some set up here */

   if (newPath != NULL)       /* We have a valid volume file so open it */
   {
       /* Close the current one if we have one open */
       if (*io_volumeHandle != NULL)
           _DtHelpCloseVolume(*io_volumeHandle);

       /* Open the help volume file and save the handle id */
       if (_DtHelpOpenVolume(newPath,io_volumeHandle) >= 0)
       {
           /* Copy the expanded file location path */
           XtFree(*io_fileName);
           *io_fileName = newPath;

           return(TRUE);
       }
       else
       {
           /* ERROR; leave io_fileName untouched on error
            * We used to set it to null here now we just return what came in
            */
           /* later NOTE: this seems strange, since we have closed the
              old volume, invalidating io_fileName */
           XtFree(newPath);
           XmeWarning(widget,(char*)UtilMessage2); 
           return (FALSE);
       }      
   }
   else                       /* We have a non-valid path */
   {
       /* ERROR; leave io_fileName untouched on error
        * We used to set it to null here now we just return what came in
        */

       XmeWarning(widget,(char*)UtilMessage0);        
       return (FALSE);
   }

}





/*****************************************************************************
 * Function:	   Boolean _DtHelpExpandHelpVolume(DtHelpDialogWidget nw);
 *
 *
 * Parameters:     nw  Specifies the current help dialog widget.
 *
 * Return Value:    Boolean.
 *

 * Description: _DtHelpExpandHelpVolume looks for a $LANG variable in the 
 *              helpAccesFile string and if found, replaces it with the 
 *              current lang variable.
 *
 *****************************************************************************/
Boolean _DtHelpExpandHelpVolume(
   Widget                      w,
   _DtHelpDisplayWidgetStuff * display,
   _DtHelpCommonHelpStuff *    help,
   _DtHelpPrintStuff *         print)
{
  Boolean validTopic = FALSE;
  Boolean validPath  = FALSE;
  char  *topLevelId;

  /* Free the old, and Copy the new volumeHandle to printVolume varialbe */
  if (print->printVolume != NULL)
     XtFree(print->printVolume);

  print->printVolume = XtNewString(display->helpVolume);     

  validPath = _DtHelpResolvePathname((Widget)w,
				&print->printVolume,
                                &display->volumeHandle,
                                help->sysVolumeSearchPath,
                                help->userVolumeSearchPath);

 
  /* Check to see that we resolved our path correctly */
  if (!validPath)
    return (FALSE);			/* RETURN */
  else
    {

       /* The following routine will malloc memory for the topLevelId
        * variable, so we must free our current version first.
        */
       XtFree(help->topLevelId);

       /* Assign our top level topic for this help access file */
       validTopic = _DtHelpCeGetTopTopicId(display->volumeHandle, &topLevelId);

       if (!validTopic)
         {
           /* Bad top level topic */
           help->topLevelId = NULL;
           return(FALSE);
         }
       else   
         {
            /* recall that the topLevelId/File vars are malloc'd */
            help->topLevelId = topLevelId;
            return(TRUE); 
	 }
    }
}


/*****************************************************************************
 * Function:	   char *_DtHelpParseIdString(char * specification);
 *
 *
 * Parameters:     specification  Specifies an author defined help topic.
 *
 * Return Value:    Void.
 *
 * Description:   This function copies the locationId portion of the 
 *                specification and returns it to the calling routine.
 *
 *****************************************************************************/
char *_DtHelpParseIdString(
   char *specification)
{

  char *pAccessFile = NULL;
  char *tmpSpec=NULL;
  char *returnStr=NULL;
  char *strtok_ptr=NULL;

  tmpSpec = XtNewString(specification);

  
  /* First look for a blank in the specification.  This will signify that
   * we have a HelpAccessFile as part of the specification.
   */
  
  /* The first call will return true, with the first string */
  pAccessFile = DtStrtok_r(tmpSpec, " ", &strtok_ptr);
  returnStr = XtNewString(pAccessFile);

  /* The second call will return true only if we have another string */
  pAccessFile = DtStrtok_r(NULL, " ", &strtok_ptr);

  if (pAccessFile != NULL)
    {
      /* We have a helpAccessFile in our specification */
           
      XtFree(returnStr);
   
      returnStr = XtNewString(pAccessFile);
      XtFree(tmpSpec);
      return(returnStr);

    }
  else
    {
      /* We don't have a helpAccessFile as part of the specificaiton
       * so we just return our locationId.
       */
       XtFree(tmpSpec);
       return (returnStr);
     }

}



/*****************************************************************************
 * Function:	   char *_DtHelpParseAccessFile(char * specification);
 *
 *
 * Parameters:     specification  Specifies an author defined help topic.
 *
 * Return Value:    Void.
 *
 * Description:   This function copies the helpAccessFile portion of the 
 *                specification and returns it to the calling routine.
 *
 *****************************************************************************/
char *_DtHelpParseAccessFile(
   char *specification)
{
  char *pAccessFile = NULL;
  char *tmpSpec=NULL;
  char *returnStr=NULL;
  char *strtok_ptr=NULL;

  tmpSpec = XtNewString(specification);

  
  /* First look for a blank in the specification.  This will signify that
   * we have a HelpAccessFile as part of the specification.
   */
  
  /* The first call will return true, with the first string */
  pAccessFile = DtStrtok_r(tmpSpec, " ", &strtok_ptr);
  returnStr = XtNewString(pAccessFile);

  /* The second call will return true only if we have another string */
  pAccessFile = DtStrtok_r(NULL, " ", &strtok_ptr);

  if (pAccessFile != NULL)
    {
      /* We have a helpAccessFile in our specification */
           

      /* If we have an accessFile, but it's not a full path, then we
       * must get the full path from the reg file.
       */
      XtFree(tmpSpec);
      return(returnStr);

    }
  else
    {
      /* We don't have a helpAccessFile as part of the specificaiton
       * so return NULL.
       */
       XtFree(returnStr);
       XtFree(tmpSpec);
       return (NULL);
     }

}








/*****************************************************************************
 * Function:	 DtHelpReturnSelectedWidgetId 
 *
 * Parameters:   parent      Specifies the widget ID to use as the bases of 
 *                           interaction, usually a top level shell.
 *
 *               cursor      Specifies the cursor to be used for the pointer
 *                           during the interaction.  If a value of NULL is 
 *                           used this function will use a default cursor
 *                           value.
 *
 *               widget      This is the return value (e.g. the selected 
 *                           widget).  A value of NULL is returned on error.
 *
 * Return Value:  Status: (-1,0 or 1).
 *
 * Purpose: Allows developers to get the widget ID for any widget in their UI
 *          that the user has selected via the pointer.  This function will
 *          cause the cursor to change and allow a user to select an item in 
 *          the UI.
 *
 *****************************************************************************/
int DtHelpReturnSelectedWidgetId(
    Widget parent,
    Cursor cursor,
    Widget  *widget)
{


 Display   *dpy;
 int       screen;  
 Widget    selectedItem;
 int       status=DtHELP_SELECT_ERROR;
 Screen    *retScr;
 int	   result;
 _DtHelpWidgetToAppContext(parent);

  _DtHelpAppLock(app);
  /* Setup some needed variables */
  dpy = XtDisplay(parent);
  retScr = XtScreen(parent);
 
  screen = XScreenNumberOfScreen(retScr);
 
  /* refresh the display */
  XmUpdateDisplay(parent);

  /* Change the curser to let the user select the desired widget */
  selectedItem = LocateWidgetId(dpy, screen, &status, parent, cursor);
  
  switch (status)
    {
      case DtHELP_SELECT_VALID:
        *widget = selectedItem;
        result = DtHELP_SELECT_VALID;
	break;
     
      case DtHELP_SELECT_ABORT:
        *widget = NULL;
        result = DtHELP_SELECT_ABORT;
	break;

      case DtHELP_SELECT_ERROR:
        *widget = NULL;
        result = DtHELP_SELECT_ERROR;
	break;
 
      case DtHELP_SELECT_INVALID:
      default:
        *widget = NULL;
        result = DtHELP_SELECT_INVALID;
	break;
    }

  _DtHelpAppUnlock(app);
  return result;
}








/*****************************************************************************
 * Function:	    void _DtHelpTopicListAddToHead(
 *                                       char *locationId,
 *                                       int topicType,
 *                                       int maxNodex,
 *                                       DtTopicListStruct *pHead,
 *                                       DtTopicListStruct *pTale,
 *                                       totalNodes) 
 *                            
 *
 * Parameters:  
 *
 * Return Value:    Void.
 *
 * Purpose: 	    Adds an element to the top of the given topicList.
 *
 *****************************************************************************/
void _DtHelpTopicListAddToHead(
    char *locationId,
    XmString topicTitle,
    int topicType,
    int maxNodes,
    char *accessPath,
    DtTopicListStruct **pHead,
    DtTopicListStruct **pTale,
    int *totalNodes,
    int scrollPosition)
{
  DtTopicListStruct *pTemp=NULL;

  /* add the new topic to the top */
  pTemp = (DtTopicListStruct *) XtMalloc((sizeof(DtTopicListStruct)));

  pTemp->pNext            = (*pHead);
  pTemp->pPrevious        = NULL;

  /* Assign the passed in values to our first element */
  pTemp->locationId       = XtNewString(locationId);
  pTemp->topicTitleLbl    = NULL;
  if (topicTitle != NULL)
      pTemp->topicTitleLbl= XmStringCopy(topicTitle);
  pTemp->topicType        = topicType;
  pTemp->helpVolume       = XtNewString(accessPath);
  pTemp->scrollPosition   = scrollPosition; 

  /* Add locationId as first element if pHead = NULL */
  if (*pHead == NULL)
    {
      /* Assign our tale pointer */
      (*pTale) = (*pHead);

      /* Make sure or totalNodes counter is correct, e.g. force it to 1 */
      *totalNodes = 1;
    }
  else 
    {  /* We have a list so add the new topic to the top */
    
     (*pHead)->pPrevious     = pTemp;

     /* Assign our tale pointer only the first time in this block */
     if (*totalNodes == 1)
       (*pTale) = (*pHead);

     /* Re-Assign our head pointer to point to the new head of the list */

     /* Bump our totalNode count */
     *totalNodes = *totalNodes +1;
    }

  /* set the head to the current entry */
  (*pHead) = pTemp;

   /* If we have reached our maxNodes remove a node from the end of our list */
   if (*totalNodes > maxNodes)
     {
       pTemp            = (*pTale);
       (*pTale)         = (*pTale)->pPrevious;
       (*pTale)->pNext  = NULL;
       pTemp->pPrevious = NULL;
               
       /* Free the id String and AccessPath elements */
       XtFree(pTemp->locationId);
       XtFree(pTemp->helpVolume);
       if (pTemp->topicTitleLbl != NULL)
           XmStringFree(pTemp->topicTitleLbl);

       /* Now, free the whole node */
       XtFree((char*)pTemp);

       /* Bump back our total node counter */
       *totalNodes = *totalNodes -1;

     }
}




/*****************************************************************************
 * Function:	    void _DtHelpTopicListDeleteHead(
 *                                       DtTopicListStruct *pHead,
 *                                       DtTopicListStruct *pTale,
 *                                       totalNodes) 
 *                            
 *
 * Parameters:  
 *
 * Return Value:    Void.
 *
 * Purpose: 	    Delets an element from the top of the given topicList.
 *
 *****************************************************************************/
void _DtHelpTopicListDeleteHead(
    DtTopicListStruct **pHead,
    DtTopicListStruct **pTale,
    int *totalNodes)
{
  DtTopicListStruct *pTemp=NULL;

  /* Delete the top node in our topic list */
  if (*pHead != NULL)
    {
      pTemp = (*pHead);
      if(pTemp != (*pTale))        /* (e.g. more than one node in list) */
	{
           (*pHead)            = pTemp->pNext;
           pTemp->pNext        = NULL; 
           (*pHead)->pPrevious = NULL;

           /* Free the id String and accessPath elements */
           XtFree(pTemp->locationId);
           XtFree(pTemp->helpVolume);

           /* Now, free the whole node */
           XtFree((char*)pTemp);

           /* Bump back our total node counter */
           *totalNodes = *totalNodes -1;
         } 
    }
}






/*****************************************************************************
 * Function:	    void _DtHelpMapCB()
 *                   
 *                            
 *
 * Parameters:      client_data is the widget in reference to
 *                  which widget w is placed
 *
 * Return Value:    Void.
 *
 * Purpose: 	    Determins where a new child dialog should be mapped in
 *                  relation to its parent.
 *
 * Algorithm:       1. attempt left or right placement with no overlap
 *                  2. if fails, attempt up or down placement with no overlap
 *                  3. if fails, determines location with least
 *                     amount of overlap, and places there.
 *
 *****************************************************************************/
XtCallbackProc _DtHelpMapCB(
    Widget w,
    XtPointer client_data,
    XtPointer call_data )
{
    Arg         args[2];
    Widget      parent;
    Position    centeredY, bestX, bestY, pX, pY; 
    Dimension   pHeight, myHeight, pWidth, myWidth;
    Dimension   maxX, maxY;
    int	        rhsX, lhsX, topY, botY;   /* needs to be int, not Dimension */
    Display *   display;
    Screen *    screen;
    int         screenNumber;

    parent = (Widget)client_data;
    display = XtDisplay(w);
    screen = XtScreen(w);
    screenNumber = XScreenNumberOfScreen(screen);

    pX = XtX(parent);
    pY = XtY(parent);
    if (pX < 0) pX = 0;
    if (pY < 0) pY = 0;
    pHeight = XtHeight(parent);
    pWidth = XtWidth(parent);
    myHeight = XtHeight(w);
    myWidth = XtWidth(w);
    maxX = XDisplayWidth(display,screenNumber);
    maxY = XDisplayHeight(display,screenNumber);

    /* algorithm 
     * 1. attempt left or right placement with no overlap
     * 2. if fails, attempt up or down placement with no overlap
     * 3. if fails, places on the right in the middle
     */
    
    /* first try left right placement */
    bestY = pY + pHeight/2 - myHeight/2;
    centeredY = bestY;
    rhsX = pX + pWidth;
    lhsX = pX - myWidth - 8;     /* 8: account for border */
    if ( ((int)(rhsX + myWidth)) < ((int) maxX) ) bestX = rhsX;
    else if ( lhsX > 0 ) bestX = lhsX;
    else
    {
        /* then try up down placement */
        bestX = pX + pWidth/2 - myWidth/2;
        botY = pY + pHeight;
        topY = pY - myHeight - 44;     /* 44: account for menu border */
        if ( ((int)(botY + myWidth)) < ((int) maxY) ) bestY = botY;
        else if ( topY > 0 ) bestY = topY;
        else
        {
            /* otherwise, center vertically and on the right */
            bestX = maxX - myWidth;
            bestY = centeredY;
        }
    }

    XtSetArg(args[0], XmNx, bestX);
    XtSetArg(args[1], XmNy,  bestY);
    XtSetValues(w, args, 2);

    return((XtCallbackProc) NULL);

}






/*****************************************************************************
 * Function:      void _DtHelpMapCenteredCB(
 *
 *
 *
 * Parameters:
 *
 * Return Value:    Void.
 *
 * Purpose:       Determins where the center of our help dialog is and sets
 *                  where new child dialog should be mapped such that its centered.
 *
 *****************************************************************************/
XtCallbackProc _DtHelpMapCenteredCB(
    Widget w,
    XtPointer client_data,
    XtPointer call_data )
{
    Arg         args[2];
    Widget      parent;
    Position newX, newY, pY, pX;
    Dimension pHeight, myHeight, pWidth, myWidth;

    parent = (Widget)client_data;

    pX = XtX(parent);
    pY = XtY(parent);
    pHeight = XtHeight(parent);
    pWidth = XtWidth(parent);
    myHeight = XtHeight(w);
    myWidth = XtWidth(w);


    newY = ((Position)(pHeight - myHeight) / 2) + pY;
    newX = ((Position)(pWidth - myWidth) / 2) + pX;

    XtSetArg(args[0], XmNx, newX);
    XtSetArg(args[1], XmNy, newY);
    XtSetValues(w, args, 2);

    return((XtCallbackProc) NULL);

}




/*****************************************************************************
 * Function:	   void _DtHelpDisplayDefinitionBox(
 *                            Widget new,   
 *                            Widget definitionBox,
 *                            char * path,
 *                            char * locationId);
 *       
 * Parameters:   
 *
 * Return Value:    
 *
 * Purpose: 	   This routine will create and post the definition box.
 *                 (e.g. the Quick Help Dialog widget)
 *
 ****************************************************************************/
void _DtHelpDisplayDefinitionBox(
    Widget parent,  
    Widget **definitionBox, 
    char * path,
    char * locationId)
{

  Arg  args[10];
  int    n;
  Widget printWidget, helpWidget, backWidget;
  XmString closeString;
  char *title;

  /* get the title from the main help dialog and use it here for */
  n = 0;
  XtSetArg (args[n], XmNtitle, &(title));  ++n;
  XtGetValues (XtParent(parent), args, n);


  if (*definitionBox == NULL)
    {

      /* Create the QuickHelpDialog widget to use as the definition box */
      closeString = XmStringCreateLocalized(((char *)_DTGETMESSAGE
                              (HUSET, 2,"Close")));
      n =0;
      XtSetArg (args[n], DtNhelpVolume, path);            n++; 
      XtSetArg (args[n], DtNhelpType, DtHELP_TYPE_TOPIC); n++; 
      XtSetArg (args[n], DtNlocationId, locationId);      n++;
      XtSetArg (args[n], DtNcloseLabelString, closeString);  n++;
      XtSetArg (args[n], XmNtitle, title);                n++;
      *definitionBox = 
           (Widget *)DtCreateHelpQuickDialog(parent, "definitionBox", 
                                               args, n);
      XmStringFree(closeString);

      /* Catch the close callback so we can destroy the widget */
      XtAddCallback((Widget)*definitionBox, DtNcloseCallback,
                    CloseDefBoxCB, (XtPointer) NULL);


      /* We do not want a print button for now so we unmap it */     
      printWidget = DtHelpQuickDialogGetChild ((Widget)*definitionBox, 
                                        DtHELP_QUICK_PRINT_BUTTON);
      XtUnmanageChild (printWidget);
  
      /* We do not want a help button for now so we unmap it */     
      helpWidget = DtHelpQuickDialogGetChild ((Widget)*definitionBox, 
                                       DtHELP_QUICK_HELP_BUTTON);
      XtUnmanageChild (helpWidget);


      /* We do not want a BACK button for now so we unmap it */     
      backWidget = DtHelpQuickDialogGetChild ((Widget)*definitionBox, 
                                           DtHELP_QUICK_BACK_BUTTON);
      XtUnmanageChild (backWidget);


  
      /*  Adjust the decorations for the dialog shell of the dialog  */
      n = 0;
      XtSetArg(args[n], XmNmwmFunctions, MWM_FUNC_RESIZE | MWM_FUNC_MOVE); n++;
      XtSetArg (args[n], XmNmwmDecorations, 
                MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_RESIZEH); n++;
      XtSetValues (XtParent(*definitionBox), args, n);
    
      /* Add the popup position callback to our history dialog */
      XtAddCallback (XtParent(*definitionBox), XmNpopupCallback,
                    (XtCallbackProc)_DtHelpMapCenteredCB, (XtPointer)XtParent(parent));



    }
  else
    {
       /* We already have one so lets use it. */

       /* Set the proper title */
       n = 0;
       XtSetArg (args[n], XmNtitle, title);  ++n;
       XtSetValues (XtParent(*definitionBox), args, n);
   
       /* Set the proper contents. */
       n = 0;
       XtSetArg (args[n], DtNhelpType, DtHELP_TYPE_TOPIC); n++; 
       XtSetArg (args[n], DtNhelpVolume, path);             n++; 
       XtSetArg (args[n], DtNlocationId, locationId);       n++;
       XtSetValues ((Widget)*definitionBox, args, n);
  
    }
 

  /* Display the dialog */
  XtManageChild((Widget)*definitionBox);     
  XtMapWidget(XtParent((Widget)*definitionBox));
 
}


/*****************************************************************************
 * Function:	   static void CloseDefBoxCB(
 *                            Widget w,   
 *                            XtPointer client_data,
 *                            XtPointer call_data);
 *       
 * Parameters:   
 *
 * Return Value:    
 *
 * Purpose: 	   This routine closes and destroys the Definition Box
 *                 Dialog Widget that we create.
 *
 ****************************************************************************/
static void  CloseDefBoxCB(
    Widget w,
    XtPointer client_data,
    XtPointer call_data )
{

   XtUnmanageChild(w);


}





/*****************************************************************************
 * Function:	   void _DtHelpDisplayFormatError()
  *       
 * Parameters:   
 *
 * Return Value:    
 *
 * Purpose: 	   This routine generate and display the proper errror 
 *                 message to the display area as well as send the proper 
 *                 error to XmWarning() function.
 *
 ****************************************************************************/
void _DtHelpDisplayFormatError(
    XtPointer displayArea,
    Widget widget,   
    char *userError,
    char *systemError)
{
   XtPointer  topicHandle;

   /* Set the string to the current help dialog */
   (void) _DtHelpFormatAsciiStringDynamic(displayArea, userError, &topicHandle);
         
   /* We ignore the status return here, because if we error out here we are
    * in big trouble because this is an error routine
    */
   
   _DtHelpDisplayAreaSetList (displayArea, topicHandle, FALSE, -1);

   if (systemError != NULL)  
     XmeWarning((Widget)widget, systemError);
  
}

 

/*****************************************************************************
 * Function:	   void _DtHelpCommonHelpInit()
  *       
 * Parameters:   
 *
 * Return Value:    
 *
 * Purpose: 	   This routine inits common help stuff
 *
 ****************************************************************************/
void _DtHelpCommonHelpInit(
    _DtHelpCommonHelpStuff * help)
{
   help->topLevelId          = NULL;
   help->currentHelpFile     = NULL;

   /* for help on help */
   if ( help->helpOnHelpVolume != _DtHelpDefaultHelp4HelpVolume)
       help->helpOnHelpVolume = XtNewString(help->helpOnHelpVolume);
   if ( NULL == help->helpOnHelpVolume ) 
       help->helpOnHelpVolume = (char *)_DtHelpDefaultHelp4HelpVolume;
   help->pHelpListHead = NULL;		/* Help List Pointer */
   help->onHelpDialog = NULL;		/* help on help dialog */
   help->pDisplayArea = NULL;		/* Display widget handle */

   /* get the search paths used by the widget */
   help->userVolumeSearchPath = _DtHelpGetUserSearchPath();
   help->sysVolumeSearchPath = _DtHelpGetSystemSearchPath();
}

 

/*****************************************************************************
 * Function:	   void _DtHelpCommonHelpClean()
  *       
 * Parameters:   
 *
 * Return Value:    
 *
 * Purpose: 	   This routine cleans up common help stuff
 *
 ****************************************************************************/
void _DtHelpCommonHelpClean(
    _DtHelpCommonHelpStuff * help,
    Boolean                 destroy)
{
   free(help->topLevelId);
   XtFree(help->currentHelpFile);

   help->topLevelId          = NULL;
   help->currentHelpFile     = NULL;

   if (destroy)
   {
      if (help->helpOnHelpVolume != _DtHelpDefaultHelp4HelpVolume)
         XtFree(help->helpOnHelpVolume);

      /* Free all the info we saved for our help callbacks */
      _DtHelpListFree(&help->pHelpListHead);

      XtFree(help->userVolumeSearchPath);
      XtFree(help->sysVolumeSearchPath);

      memset(help,0,sizeof(_DtHelpCommonHelpStuff));
   }
   else
   {
      /* Set our display area to a null starting vlaues */
      _DtHelpDisplayAreaClean(help->pDisplayArea);
   }
}

 

/*****************************************************************************
 * Function:	   void _DtHelpSetDlgButtonsWidth
 *       
 * Parameters:   
 *
 * Return Value:    
 *
 * Purpose: 	   This routine cleans up common help stuff
 *
 ****************************************************************************/
void _DtHelpSetButtonPositions(
    Widget     btnList[],
    int        numBtns,
    Dimension  minFormWidth,
    Dimension  btnMargins,
    Dimension  minBetweenBtnSpace)
{ /* position buttons */

      /* This code adds up the sizes of the buttons to go into
         the bottom row and calculates the form position percentages.
         All buttons are the same size, and are able to hold all
         the strings that may be dynamically placed in them.
         All buttons are 5% apart. */
      /* This code is specifically written to handle 3 buttons
         and assumes that the first 3 strings are to the ActionBtn */
   Dimension minWidthWithSpace = 0;
   Dimension borderWidth = 0;
   Dimension sumWidth = 0;
   Dimension leftPos = 0;
   Dimension rightPos = 0;
   Dimension spaceWidth = 0;
   Dimension btnWidth;
   Dimension maxBtnWidth = 0;
   float scale = 0.0;
   XmFontList fontList = NULL;
   int        i;
   int        n;
   Arg        args[5];

   if (numBtns <= 0 || NULL == btnList[0]) return;

   /* get the fontList for the button */
   XtSetArg (args[0], XmNborderWidth, &borderWidth);
   XtSetArg (args[1], XmNfontList, &fontList);
   XtGetValues (btnList[0], args, 2);
   /* assumption: the fontList that is returned is not owned by me; don't free */

   /* cycle through all buttons */
   for (i=0; i<numBtns && NULL!=btnList[i]; i++)
   {
      XmString labelString;

      /* get the label from the button */
      XtSetArg (args[0], XmNlabelString, &labelString);
      XtGetValues (btnList[i], args, 1);

      btnWidth = XmStringWidth(fontList,labelString) + borderWidth + btnMargins;
      if (btnWidth > maxBtnWidth) maxBtnWidth = btnWidth;

      XmStringFree(labelString);
   } /* for calcing widths */
   numBtns = i;  /* number of valid buttons */

   /* calc the space */
   sumWidth = maxBtnWidth * numBtns;
   minWidthWithSpace = sumWidth + minBetweenBtnSpace * (numBtns * 2);
   if (minWidthWithSpace > minWidthWithSpace) minFormWidth = minWidthWithSpace;
   spaceWidth = ((int)(minFormWidth - sumWidth) / (numBtns * 2));

   /* scale pixels to percent */
   scale = 100.0 / (float) minFormWidth;

   /* set the positions of each button */
   leftPos = spaceWidth;
   for ( i=0; i<numBtns; i++ )
   {
      rightPos = leftPos + maxBtnWidth;
   
      n = 0;
      XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION);            n++;
      XtSetArg (args[n], XmNleftPosition, (Dimension) (((float) leftPos)*scale)); n++;
      XtSetArg (args[n], XmNrightAttachment, XmATTACH_POSITION);           n++;
      XtSetArg (args[n], XmNrightPosition,(Dimension) (((float) rightPos)*scale)); n++;
      XtSetValues (btnList[i], args, n);
   
      leftPos = rightPos + spaceWidth + spaceWidth;
   }  /* setup the positions for all buttons */
}  /* _DtHelpSetDlgButtonsWidth */



/*****************************************************************************
 * Function:	   _DtHelpXmFontListGetPropertyMax
 *       
 * Parameters:   
 *     fontList:  an XmFontList
 *     atom:      an XA_xxx value (see Vol 1, chpt 6.2.9)
 *     ret_propertyValue: ptr to long value that will hold the max value
 *
 * Return Value:    
 *     True: got at least one value
 *     False: unable to get any value; ret_propertyValue unchanged
 *
 * Purpose:
 *    This function returns the max value of XGetFontProperty calls 
 *    for each font in the XmFontList
 *    If there is an error, ret_propertyValue is left unchanged.
 *
 ****************************************************************************/
Boolean
_DtHelpXmFontListGetPropertyMax(
        XmFontList fontList,
        Atom atom,
        unsigned long *ret_propertyValue)
{
    Boolean gotValue = False;
    XmFontContext context;
    XmFontListEntry entry;
    XtPointer fontData;

    if (NULL == fontList) return False;           /* RETURN on error */

    /* walk through the fontList entries and add them in */
    XmFontListInitFontContext(&context,fontList);
    for ( entry = XmFontListNextEntry(context);
          NULL != entry;
          entry = XmFontListNextEntry(context) )
    {
       unsigned long value;
       XmFontType type;

       fontData = XmFontListEntryGetFont(entry,&type);
       if (XmFONT_IS_FONT == type)
       {
          XFontStruct * fontStruct;

          /* cast according to type */
          fontStruct = (XFontStruct *) fontData;

          if(XGetFontProperty(fontStruct, atom, &value) == TRUE) 
          {
             if(gotValue == False) /* haven't gotten any prop value yet */
             {
                 *ret_propertyValue = value;
                 gotValue = True;
             }
             else   /* have a good prop value already...get the max one */
             {
                 if(value > *ret_propertyValue)
                     *ret_propertyValue = value;
             }
          } /* if the getproperty call was good */
       }
       else  /* XmFONT_IS_FONTSET */
       {
          XFontSet fontSet;
          XFontStruct **font_list;
          char **name_list;
          int numfont;
          int i;

          /* cast according to type */
          fontSet = (XFontSet) fontData;
 
          numfont=XFontsOfFontSet(fontSet,&font_list,&name_list);
          for(i = 0; i < numfont; i++) 
          {
              if(XGetFontProperty(font_list[i], atom, &value) == TRUE) 
              {
                  if(gotValue == False) /* haven't gotten any prop value yet */
                  {
                      *ret_propertyValue = value;
                      gotValue = True;
                  }
                  else   /* have a good prop value already...get the max one */
                  {
                      if(value > *ret_propertyValue)
                          *ret_propertyValue = value;
                  }
              } /* if the getproperty call was good */
          } /* for all fonts in the font set */
       } /* this entry uses a font set */
    } /* for all font entries in the font list */
    XmFontListFreeFontContext(context);

    return(gotValue);
}
