/* $Header: /afs/athena.mit.edu/astaff/project/atdev/src/help/RCS/Help.c,v 3.3 93/10/18 17:14:30 dot Exp $ */

/*******************************************************************
  Copyright (C) 1990 by the Massachusetts Institute of Technology

   Export of this software from the United States of America is assumed
   to require a specific license from the United States Government.
   It is the responsibility of any person or organization contemplating
   export to obtain such a license before exporting.

WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
distribute this software and its documentation for any purpose and
without fee is hereby granted, provided that the above copyright
notice appear in all copies and that both that copyright notice and
this permission notice appear in supporting documentation, and that
the name of M.I.T. not be used in advertising or publicity pertaining
to distribution of the software without specific, written prior
permission.  M.I.T. makes no representations about the suitability of
this software for any purpose.  It is provided "as is" without express
or implied warranty.

***************************************************************** */

#include <stdio.h>
#include <strings.h>
#include <sys/types.h>    
#include <sys/stat.h>
#include <ctype.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <Xm/Form.h>
#include <Xm/PanedW.h>
#include <Xm/ScrolledW.h>    
#include <Xm/Text.h>
#include <Xm/List.h>
#include <Xm/Label.h>    
#include <Xm/PushB.h>

#ifdef _AtDevelopment_
#include "FontFamily.h"
#include "HelpP.h"
#else
#include <At/FontFamily.h>
#include <At/HelpP.h>
#endif

/**********************************************************************
**                           AtHelp Resources                        **
**********************************************************************/

#define offset(field) XtOffset(AtHelpWidget, field)    
static XtResource resources[] = {
  {XtNfilename, XtCFilename, XtRString, sizeof(String), 
     offset(help.filename), XtRString, NULL},
  {XtNfontFamily, XtCFontFamily, XtRString, sizeof(String),
     offset(help.fontFamily), XtRString, "schoolbook"},
  {XtNfontSize, XtCFontSize, XtRFontSize, sizeof(int),
     offset(help.fontSize), XtRImmediate, (caddr_t)AtFontNORMAL},
  {XtNtitle, XtCTitle, XtRString, sizeof(String),
     offset(help.title), XtRString, "Online Help"},
  {XtNdoneLabel, XtCDoneLabel, XtRString, sizeof(String),
     offset(help.doneLabel), XtRString, "Done"},
  {XtNdoneCallback, XtCDoneCallback, XtRCallback, sizeof(String),
     offset(help.doneCallback), XtRImmediate, (caddr_t) NULL},
  {XtNtocHeading, XtCTocHeading, XtRString, sizeof(String),
     offset(help.tocHeading), XtRString, "Help Topics"},
  {XtNindexHeading, XtCIndexHeading, XtRString, sizeof(String),
     offset(help.indexHeading), XtRString, "Keywords"},
};
#undef offset
    
/**********************************************************************
**             Forward function prototype declarations               **
**********************************************************************/

static void Initialize(AtHelpWidget, AtHelpWidget);
static void Destroy(AtHelpWidget);
static Boolean SetValues(AtHelpWidget, AtHelpWidget, AtHelpWidget);
static void Resize(AtHelpWidget);
static void CalcSize(AtHelpWidget, Dimension *, Dimension *);
static XtGeometryResult GeometryManager(Widget, XtWidgetGeometry *,
					XtWidgetGeometry *); 
static void freestrings(AtHelpWidget);
static void doenv(AtHelpWidget, int, char *, int, int);
static void ReadFile(AtHelpWidget);
static void GotoSection(Widget, AtHelpWidget, XmListCallbackStruct *);
static void GotoCitation(Widget, AtHelpWidget, XmListCallbackStruct *);
static void DoneCallback(Widget, AtHelpWidget, caddr_t);

int issame(char *, char *);

/**********************************************************************
**                       The AtHelp Class Record                     **
**********************************************************************/

#define superclass (&compositeClassRec)

AtHelpClassRec atHelpClassRec = {
 { /******* CoreClassPart *******/
    /* superclass           */  (WidgetClass) superclass,
    /* class_name           */  "AtHelp",
    /* widget_size          */  sizeof(AtHelpRec),
    /* class_initialize     */  NULL,
    /* class_part_initialize*/  NULL,
    /* class_inited         */  FALSE,
    /* initialize           */  (XtInitProc)Initialize,
    /* initialize_hook      */  NULL,
    /* realize              */  XtInheritRealize,
    /* actions              */  NULL,
    /* num_actions          */  0,
    /* resources            */  resources,
    /* num_resources        */  XtNumber(resources),
    /* xrm_class            */  NULLQUARK,
    /* compress_motion      */  TRUE,
    /* compress_exposure    */  TRUE,
    /* compress_enterleave  */  TRUE,
    /* visible_interest     */  FALSE,
    /* destroy              */  (XtWidgetProc)Destroy,
    /* resize               */  Resize,
    /* expose               */  NULL,
    /* set_values           */  (XtSetValuesFunc) SetValues,
    /* set_values_hook      */  NULL,
    /* set_values_almost    */  XtInheritSetValuesAlmost,
    /* get_values_hook      */  NULL,
    /* accept_focus         */  NULL,
    /* version              */  XtVersion,
    /* callback_offsets     */  NULL,
    /* tm_table             */  NULL,
    /* query_geometry       */  NULL,
    /* display_accelerator  */  NULL,
    /* extension            */  NULL
  },
  { /**** CompositeClassPart ****/
    /* geometry_handler     */  GeometryManager,
    /* change_managed       */  XtInheritChangeManaged,
    /* insert_child         */  XtInheritInsertChild,
    /* delete_child         */  XtInheritDeleteChild,
    /* extension            */  NULL
  },
    
};    

WidgetClass atHelpWidgetClass = (WidgetClass) &atHelpClassRec;

/********************************************************************
**                    Instance Initialize Method                   **
********************************************************************/

static void Initialize(AtHelpWidget request, AtHelpWidget new)
{
    Arg al[20];
    int ac;
    XmString tempXmString;
    XmFontList tempfl;
	
    new->help.fontFamily = XtNewString(new->help.fontFamily);
    new->help.ff = AtFontFamilyGet(XtDisplay(new), new->help.fontFamily);
    new->help.font = AtFontFetch(new->help.ff,AtFontPLAIN,new->help.fontSize);

    new->help.text = NULL;
    new->help.sectionNames = NULL;
    new->help.sectionOffsets = NULL;
    new->help.sections = NULL;
    new->help.numSections = new->help.maxSections = 0;
    new->help.citeNames = NULL;
    new->help.citeOffsets = NULL;
    new->help.citations = NULL;
    new->help.numCites = new->help.maxSections = 0;

    /* create the title (XmLabel) widget */
    ac = 0;
    tempXmString = XmStringCreateLtoR(new->help.title,
				      XmSTRING_DEFAULT_CHARSET);
    tempfl = XmFontListCreate(AtFontFetch(new->help.ff, AtFontBOLD,
					  AtFontBigger(new->help.fontSize)),
			      XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNlabelString, tempXmString); ac++;
    XtSetArg(al[ac], XmNfontList, tempfl); ac++;
    new->help.titlew = XmCreateLabel((Widget)new, "title", al, ac);
    XtManageChild(new->help.titlew); 
    XmStringFree(tempXmString);
    XmFontListFree(tempfl);

    /* create the done (XmPushButton) widget */
    ac = 0;
    tempXmString = XmStringCreateLtoR(new->help.doneLabel,
				      XmSTRING_DEFAULT_CHARSET);
    tempfl = XmFontListCreate(new->help.font, XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNlabelString, tempXmString); ac++;
    XtSetArg(al[ac], XmNfontList, tempfl);  ac++;
    new->help.donew = XmCreatePushButton((Widget)new, "done", al, ac);
    XtManageChild(new->help.donew);
    XtAddCallback(new->help.donew, XmNactivateCallback, DoneCallback, 
		  (caddr_t) new);
    XmStringFree(tempXmString);
    XmFontListFree(tempfl);

    /* create the table of contents title (XmLabel) widget */
    ac = 0;
    tempXmString = XmStringCreateLtoR(new->help.tocHeading,
				      XmSTRING_DEFAULT_CHARSET);
    tempfl = XmFontListCreate(AtFontFetch(new->help.ff, AtFontBOLD,
					  new->help.fontSize),
			      XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNfontList, tempfl); ac++;
    XtSetArg(al[ac], XmNlabelString, tempXmString); ac++;
    new->help.toclabel=XmCreateLabel((Widget)new,"toclabel",al,ac);
    XtManageChild(new->help.toclabel);
    XmStringFree(tempXmString);

    /* create the index title (XmLabel) widget */
    ac = 1;
    tempXmString = XmStringCreateLtoR(new->help.indexHeading,
				      XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNlabelString, tempXmString); ac++;
    new->help.indexlabel=XmCreateLabel((Widget)new,"indexlabel",al,ac);
    XtManageChild(new->help.indexlabel);
    XmStringFree(tempXmString);
    XmFontListFree(tempfl);

    /* create the scrolled window widget for the index */
    ac = 0;
    XtSetArg (al[ac],XmNscrollingPolicy,(XtArgVal)XmAPPLICATION_DEFINED); ac++;
    XtSetArg (al[ac], XmNvisualPolicy, (XtArgVal )XmVARIABLE); ac++;
    XtSetArg (al[ac], XmNscrollBarDisplayPolicy, (XtArgVal )XmSTATIC); ac++;
    XtSetArg (al[ac], XmNshadowThickness, (XtArgVal ) 0); ac++;
    new->help.indexsw = XmCreateScrolledWindow((Widget)new,"indexsw",al,ac);
    XtManageChild(new->help.indexsw); 

    /* create the index list (XmList) widget */
    ac = 0;
    tempfl = XmFontListCreate(new->help.font, XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNlistMarginHeight, 3); ac++;
    XtSetArg(al[ac], XmNlistMarginWidth, 3); ac++;
    XtSetArg(al[ac], XmNfontList, tempfl); ac++;
    XtSetArg(al[ac], XmNselectionPolicy, XmSINGLE_SELECT); ac++;
    new->help.index = XmCreateList(new->help.indexsw, "index", al, ac);
    XtManageChild(new->help.index);

    /* create the paned window widget for the toc and displayer */
    ac = 0;
    new->help.paned = XmCreatePanedWindow((Widget)new, "paned", al, ac);
    XtManageChild(new->help.paned); 

    /* create the scrolled window widget for the table of contents */
    ac = 0;
    XtSetArg(al[ac], XmNpaneMinimum, 30); ac++;
    XtSetArg(al[ac], XmNskipAdjust, True); ac++;
    XtSetArg(al[ac], XmNscrollingPolicy,(XtArgVal)XmAPPLICATION_DEFINED); ac++;
    XtSetArg(al[ac], XmNvisualPolicy, (XtArgVal )XmVARIABLE); ac++;
    XtSetArg(al[ac], XmNscrollBarDisplayPolicy, (XtArgVal )XmSTATIC); ac++;
    XtSetArg(al[ac], XmNshadowThickness, (XtArgVal ) 0); ac++;
    new->help.tocsw = XmCreateScrolledWindow(new->help.paned,"tocsw",al,ac);
    XtManageChild(new->help.tocsw);

    /* create the list (XmList) widget for the table of contents */
    ac = 0;
    XtSetArg(al[ac], XmNlistMarginHeight, 3); ac++;
    XtSetArg(al[ac], XmNlistMarginWidth, 3); ac++;
    XtSetArg(al[ac], XmNfontList, tempfl); ac++;
    XtSetArg(al[ac], XmNvisibleItemCount, 5); ac++;
    XtSetArg(al[ac], XmNselectionPolicy, XmSINGLE_SELECT); ac++;
    new->help.toc = XmCreateList(new->help.tocsw, "toc", al, ac);
    XtManageChild(new->help.toc);

    /* create the scrolled window widget for the displayer */
    ac = 0;
    XtSetArg(al[ac], XmNpaneMinimum, 100); ac++;
    XtSetArg(al[ac], XmNscrollingPolicy,(XtArgVal)XmAPPLICATION_DEFINED); ac++;
    XtSetArg(al[ac], XmNvisualPolicy, (XtArgVal )XmVARIABLE); ac++;
    XtSetArg(al[ac], XmNscrollBarDisplayPolicy, (XtArgVal )XmSTATIC); ac++;
    XtSetArg(al[ac], XmNshadowThickness, (XtArgVal ) 0); ac++;
    new->help.displayersw = XmCreateScrolledWindow(new->help.paned,
						   "displayersw",al,ac);
    XtManageChild(new->help.displayersw);

    /* create the displayer (XmText) widget */
    ac = 0;
    XtSetArg(al[ac], XmNeditMode, XmMULTI_LINE_EDIT); ac++;
    XtSetArg(al[ac], XmNeditable, False); ac++;
    XtSetArg(al[ac], XmNmarginHeight, 3); ac++;
    XtSetArg(al[ac], XmNmarginWidth, 3); ac++;
    XtSetArg(al[ac], XmNwordWrap, True); ac++;
    XtSetArg(al[ac], XmNscrollHorizontal, False); ac++;
    XtSetArg(al[ac], XmNfontList, tempfl); ac++;
    XtSetArg(al[ac], XmNrows, 15); ac++;
    XtSetArg(al[ac], XmNcolumns, 60); ac++;
    new->help.displayer=XmCreateText(new->help.displayersw,"displayer",al,ac);
    XtManageChild(new->help.displayer);
    XmFontListFree(tempfl);

    XtAddCallback(new->help.toc, XmNsingleSelectionCallback,
		  GotoSection, (caddr_t)new);
    XtAddCallback(new->help.index, XmNsingleSelectionCallback,
		  GotoCitation, (caddr_t)new);

    if (new->help.filename != NULL) {
	new->help.filename = XtNewString(new->help.filename);
	ReadFile(new);
    }

    if ((request->core.width == 0) || (request->core.height == 0))
	CalcSize(new, &new->core.width, &new->core.height);

    Resize(new);
}

/********************************************************************
**                            Destroy Method                       **
********************************************************************/

static void Destroy(AtHelpWidget w)
{
  freestrings(w);
  XtFree(w->help.fontFamily);
  XtFree(w->help.filename);
  AtFontFamilyRelease(w->help.ff);
}

/********************************************************************
**                           Resize Method                         **
********************************************************************/

#define MAX(x, y) ((x) >= (y) ? (x) : (y))

static void Resize(AtHelpWidget w)
{
    XtWidgetGeometry title, done, toclabel, indexlabel, index, paned;
    int x1, x2, x3, y1, y2, w1, w2, w3, h1, dx, dy;

    XtQueryGeometry(w->help.titlew, NULL, &title);
    XtQueryGeometry(w->help.donew, NULL, &done);
    XtQueryGeometry(w->help.toclabel, NULL, &toclabel);
    XtQueryGeometry(w->help.indexlabel, NULL, &indexlabel);
    XtQueryGeometry(w->help.indexsw, NULL, &index);
    XtQueryGeometry(w->help.paned, NULL, &paned);

    dx = done.width;
    dy = done.height;

    w1 = MAX(index.width, indexlabel.width);
    w2 = w->core.width - w1;
    w3 = indexlabel.height;
    x1 = w2;
    x2 = (w2-toclabel.width)/2;
    x3 = x1 + (w1-indexlabel.width)/2;

    if (w3 < dy)
      w3 = dy;

    y1 = title.height;
    y2 = y1 + w3;
    h1 = w->core.height - y2;

    XtConfigureWidget(w->help.titlew, 0, 0, w->core.width, title.height, 0);
/***  the positioned '3' should not be a fixed value  ***/
    XtConfigureWidget(w->help.donew, 3, y1, dx, dy, 0);
    XtConfigureWidget(w->help.toclabel, x2, y1, toclabel.width, w3, 0);
    XtConfigureWidget(w->help.indexlabel, x3, y1, indexlabel.width, w3, 0);
    XtConfigureWidget(w->help.paned, 0, y2, w2, h1, 0);
    XtConfigureWidget(w->help.indexsw, x1, y2, w1, h1, 0);
}

/**********************************************************************/

static void CalcSize(AtHelpWidget w, Dimension *width, Dimension *height)
{
    XtWidgetGeometry title, done, indexlabel, paned;
    XtWidgetGeometry indexsw, tocsw, displayersw;
    int d;

    XtQueryGeometry(w->help.titlew, NULL, &title);
    XtQueryGeometry(w->help.donew, NULL, &done);
    XtQueryGeometry(w->help.indexlabel, NULL, &indexlabel);
    XtQueryGeometry(w->help.paned, NULL, &paned);
    XtQueryGeometry(w->help.indexsw, NULL, &indexsw);
    /* XXX this doesn't work, because Motif panedW doesn't have a
       query geometry proc, and doesn't initialize its size correctly.
       So fake it out ourselves */
    XtQueryGeometry(w->help.tocsw, NULL, &tocsw);
    XtQueryGeometry(w->help.displayersw, NULL, &displayersw);
    
    d = MAX(done.height, title.height);
    *width = displayersw.width + MAX(indexsw.width, indexlabel.width) + 5;
    *height = title.height + MAX(d, indexlabel.height) +
	tocsw.height + displayersw.height + 13;
}

/**********************************************************************/

static XtGeometryResult GeometryManager(w, request, reply)
    Widget w;
    XtWidgetGeometry *request;
    XtWidgetGeometry *reply;	/* RETURN */
{
    return XtGeometryNo;
}

/********************************************************************
**                         SetValues Method                        **
********************************************************************/

#define Changed(field) (current->help.field != new->help.field)
#define StringChanged(field) (strcmp(new->help.field, current->help.field))
static Boolean SetValues(AtHelpWidget current, AtHelpWidget request,
			 AtHelpWidget new)
{
  Arg al[5];
  int ac;
  Boolean fontchanged = False, schanged, resize = False;
  XmString tempXmString;
  XmFontList tempfl;

  if (strcasecmp(new->help.fontFamily, current->help.fontFamily))  {
    XtFree(current->help.fontFamily);
    new->help.fontFamily = XtNewString(new->help.fontFamily);
    new->help.ff = AtFontFamilyGet(XtDisplay(new), new->help.fontFamily);
    new->help.font = AtFontFetch(new->help.ff,AtFontPLAIN, new->help.fontSize);
    fontchanged = True;
    resize = True;
  }

  if (Changed(fontSize))  {
    fontchanged = True;
    resize = True;
  }

  /*** check for changes to the title label widget ***/
  schanged = False;
  ac = 0;
  if (fontchanged)  {
    tempfl = XmFontListCreate(AtFontFetch(new->help.ff, AtFontBOLD,
					  AtFontBigger(new->help.fontSize)),
			      XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNfontList, tempfl);  ac++;
  }
  if (StringChanged(title))  {
    tempXmString = XmStringCreateLtoR(new->help.title, 
				      XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNlabelString, tempXmString);  ac++;
    schanged = True;
    resize = True;
  }
  if (schanged || fontchanged)  {
    XtSetValues(new->help.titlew, al, ac);
    if (schanged)
      XmStringFree(tempXmString);
    if (fontchanged)
      XmFontListFree(tempfl);
  }

  /*** check for changes to the tocHeading label widget ***/
  schanged = False;
  ac = 0;
  if (fontchanged)  {
    tempfl = XmFontListCreate(AtFontFetch(new->help.ff, AtFontBOLD,
					  new->help.fontSize),
			      XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNfontList, tempfl);  ac++;
  }
  if (StringChanged(tocHeading))  {
    tempXmString = XmStringCreateLtoR(new->help.tocHeading,
				      XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNlabelString, tempXmString);  ac++;
    schanged = True;
    resize = True;
  }
  if (schanged || fontchanged)  {
    XtSetValues(new->help.toclabel, al, ac);
    if (schanged)
      XmStringFree(tempXmString);
  }

  /*** check for changes to the indexHeading widget ***/
  /*** this depends on al[0] still being valid from above ***/
  schanged = False;
  ac = 0;
  if (fontchanged)
    ac = 1;
  if (StringChanged(indexHeading))  {
    tempXmString = XmStringCreateLtoR(new->help.indexHeading,
				      XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNlabelString, tempXmString);  ac++;
    schanged = True;
    resize = True;
  }
  if (schanged || fontchanged)  {
    XtSetValues(new->help.indexlabel, al, ac);
    if (schanged)
      XmStringFree(tempXmString);
    if (fontchanged)
      XmFontListFree(tempfl);
  }

  /*** check for changes to the done button widget ***/
  schanged = False;
  ac = 0;
  if (fontchanged)  {
    tempfl = XmFontListCreate(new->help.font, XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNfontList, tempfl);  ac++;
  }
  if (StringChanged(doneLabel))  {
    tempXmString = XmStringCreateLtoR(new->help.doneLabel, 
				      XmSTRING_DEFAULT_CHARSET);
    XtSetArg(al[ac], XmNlabelString, tempXmString);  ac++;
    schanged = True;
    resize = True;
  }
  if (schanged || fontchanged)  {
    XtSetValues(new->help.donew, al, ac);
    if (schanged)
      XmStringFree(tempXmString);
  }

  /*** check for changes to the toc list widget, the index list widget, ***/
  /*** and the displayer text widget.  This depends on al[0] being      ***/
  /*** valid from above.                                                ***/
  if (fontchanged)  {
    XtSetValues(new->help.index, al, 1);
    XtSetValues(new->help.toc, al, 1);
    XtSetValues(new->help.displayer, al, 1);
    XmFontListFree(tempfl);
  }

  if (StringChanged(filename))  {
    XtFree(current->help.filename);
    if (new->help.filename != NULL)  {
       new->help.filename = XtNewString(new->help.filename);
       ReadFile(new);
     }
    else {
      /* clear the widget */
      XmTextSetString(new->help.displayer, NULL);

      XtSetArg(al[0], XmNitems, NULL);
      XtSetArg(al[1], XmNitemCount, 0);
      XtSetValues(new->help.toc, al, 2);
      
      XtSetArg(al[0], XmNitems, NULL);
      XtSetArg(al[1], XmNitemCount, 0);
      XtSetValues(new->help.index, al, 2);
    }
  }
  if (resize)
    Resize(new);

  return False;
}

/**********************************************************************/
/**********************************************************************/
/*                           Support Routines                         */
/**********************************************************************/
/**********************************************************************/

static void freestrings(AtHelpWidget w)
{
    int i;
    
    XtFree(w->help.text);
    XtFree(w->help.sectionOffsets);
    XtFree(w->help.citeOffsets);

    for(i=0; i<w->help.numSections; i++) {
	XtFree(w->help.sectionNames[i]);
	XmStringFree(w->help.sections[i]);
    }
    XtFree(w->help.sectionNames);
    XtFree(w->help.sections);

    for(i=0; i<w->help.numCites; i++) {
	XtFree(w->help.citeNames[i]);
	XmStringFree(w->help.citations[i]);
    }
    XtFree(w->help.citeNames);
    XtFree(w->help.citations);
    
    w->help.numSections = w->help.numCites = 0;
    w->help.sectionNum = 0;
    w->help.subsectionNum = 0;
    w->help.subsubsectionNum = 0;
}    

/**********************************************************************/

static void doenv(AtHelpWidget w, int env, char *arg, int len, int offset)
{
  int i;
  switch(env) {
  case 1:
  case 2:
  case 3:
    i = w->help.numSections;
    if (i == w->help.maxSections) {
      w->help.maxSections *= 2;
      w->help.sectionNames = (char **)XtRealloc(w->help.sectionNames,
						w->help.maxSections*sizeof(char *));
      w->help.sectionOffsets = (int*)XtRealloc(w->help.sectionOffsets,
					       w->help.maxSections*sizeof(int));
      w->help.sections = (XmString*)XtRealloc(w->help.sections,
					      w->help.maxSections*sizeof(XmString));
    }
    w->help.sectionNames[i] = XtMalloc((env-1)*4 + len + 1);
    strncpy(w->help.sectionNames[i], "               ", (env-1)*4); 
    strncpy(w->help.sectionNames[i]+(env-1)*4, arg, len);
    *(w->help.sectionNames[i] + (env-1)*4 + len) = '\0';
    w->help.sectionOffsets[i] = offset;
    w->help.sections[i] = XmStringCreate(w->help.sectionNames[i],
					 XmSTRING_DEFAULT_CHARSET);
    w->help.numSections++;
    break;
  case 4:
  case 5:
    i = w->help.numCites;
    if (i == w->help.maxCites) {
      w->help.maxCites *= 2;
      w->help.citeNames = (char **)XtRealloc(w->help.citeNames,
					     w->help.maxCites*sizeof(char *));
      w->help.citeOffsets = (int*)XtRealloc(w->help.citeOffsets,
					    w->help.maxCites*sizeof(int));
      w->help.citations = (XmString*)XtRealloc(w->help.citations,
					       w->help.maxCites * sizeof(XmString));
    }
    w->help.citeNames[i] = strncpy(XtMalloc(len+1), arg, len);
    w->help.citeNames[i][len] = '\0';
    w->help.citeOffsets[i] = offset;
    w->help.citations[i] = XmStringCreate(w->help.citeNames[i],
					  XmSTRING_DEFAULT_CHARSET);
    w->help.numCites++;
    break;
  default:
    break;
  }
}

/**********************************************************************/

static void ReadFile(AtHelpWidget w)
{
    FILE *f;
    struct stat fileinfo;		/* file information from fstat. */
    char *s;
    int len;
    register char *i, *j;
    char *mark, *mark2, *mark3;
    int env, env_verbatim = 0;
    Arg al[5];
    
    if (w->help.filename == NULL) return;

    /* open the file, check its length, malloc enough space, and read it in */
    f = fopen(w->help.filename, "r");
    if (f == NULL) {
	XtWarning("AtHelp: Can't open file.");
	return;
    }
    if (fstat(fileno(f), &fileinfo)) {
	XtWarning("AtHelp: Failure in fstat.");
	fclose(f);
	return;
    }
    len = fileinfo.st_size;
    s = XtMalloc(fileinfo.st_size + 1);
    if (fread(s, sizeof(char), len, f) == 0) {
	XtWarning("AtHelp: Failure in fread.");
	fclose(f);
	return;
    }
    fclose(f);
    *(s + len) = '\0';

    /* free up old stuff from last file */
    freestrings(w);

    /* allocate new space for this file */
    w->help.maxSections = 10;
    w->help.sectionNames = (char**)XtMalloc(w->help.maxSections*sizeof(char*));
    w->help.sectionOffsets = (int*)XtMalloc(w->help.maxSections * sizeof(int));
    w->help.sections=(XmString*)XtMalloc(w->help.maxSections*sizeof(XmString));
    w->help.maxCites = 10;
    w->help.citeNames = (char **)XtMalloc(w->help.maxCites * sizeof(char *));
    w->help.citeOffsets = (int *) XtMalloc(w->help.maxCites * sizeof(int));
    w->help.citations = (XmString*)XtMalloc(w->help.maxCites*sizeof(XmString));

    /*
     * go parse the contents of the file, looking for environments we
     * recognize, and filtering out environments and commands we don't
     * recognize.
     */
    w->help.text = XtMalloc(len + 1);
    for(i = s, j = w->help.text; *i; i++) {
      if ((*i == '{')  || (*i == '}')) 
	continue;
      else if (*i == '\n')  {
	/* if in a verbatim environment, put every return in */
	if (env_verbatim > 0)  {
	  *j++ = '\n';
	  continue;
	}
	else  {
	  if (*(i-1) != '\n')  {
	    *j++ = ' '; 
	    continue;
	  }
	  else if (*(j-1) != '\n') 
	    *j++ = '\n';
	  *j++ = *i;
	}
      }
      else if (*i != '\\') 
	*j++ = *i;
      else {
	i++;
	if (*i == '\\')  {
	  *j++ = '\\';
	  i++; 
	  continue;
	}
	if (!isalpha(*i)) 
	  continue;
	/* otherwise, we've found an environment or command */
	mark = i;
	while(isalnum((int)*i)) i++;
	mark2 = i;
	while(isspace((int)*i)) i++;
	if (*i != '{') /* its a command, not an env.  Ignore it. */
	  continue;
	else { /* its an environment.  If we don't understand, ignore */
	  int len = mark2 - mark;
	  env = 0;
	  if (strncasecmp(mark, "section", len ) == 0) env = 1;
	  else if (strncasecmp(mark, "subsection", len) == 0) env = 2;
	  else if (strncasecmp(mark, "subsubsection", len) == 0) env = 3;
	  else if (strncasecmp(mark, "index", len) == 0) env = 4;
	  else if (strncasecmp(mark, "cite", len) == 0) env = 5;
	  else if (strncasecmp(mark, "begin", len) == 0) env = 6;
	  else if (strncasecmp(mark, "end", len) == 0) env = 7;
	  if (env == 0) continue; /* ignore if not known */
		
	  mark3 = ++i;
	  while (*i != '}') i++;
	  doenv(w, env, mark3, i - mark3, j-w->help.text);
	  if ((env >= 1) && (env <= 3))  {
	    strncpy(j, mark3, i-mark3);
	    j += i-mark3;
	    *j++ = '\n';
	  }
	  if (env == 6)  /** beginning an environment **/
	    if (strncasecmp(mark3, "verbatim", i - mark3) == 0)  {
	      env_verbatim++;
	    }
	  if (env == 7) /** ending an environment **/
	    if (strncasecmp(mark3, "verbatim", i - mark3) == 0)  {
	      env_verbatim--;
	    }
	}
      }
    }
    /* free up the string alloc'ed to hold the unparsed file */
    XtFree(s);

    /* set the approprate stuff for the appropriate widgets */
    XmTextSetString(w->help.displayer, w->help.text);

    XtSetArg(al[0], XmNitems, w->help.sections);
    XtSetArg(al[1], XmNitemCount, w->help.numSections);
    XtSetValues(w->help.toc, al, 2);

    XtSetArg(al[0], XmNitems, w->help.citations);
    XtSetArg(al[1], XmNitemCount, w->help.numCites);
    XtSetValues(w->help.index, al, 2);
}
    
/**********************************************************************/

static void GotoSection(Widget w, AtHelpWidget hw, XmListCallbackStruct *data)
{
    int offset;

    offset = hw->help.sectionOffsets[data->item_position-1];
    XmTextSetTopCharacter(hw->help.displayer, offset);
}

/**********************************************************************/

static void GotoCitation(Widget w, AtHelpWidget hw, XmListCallbackStruct *data)
{
    int offset;

    offset = hw->help.citeOffsets[data->item_position-1];
    XmTextSetTopCharacter(hw->help.displayer, offset);
}

/**********************************************************************/

static void DoneCallback(Widget w, AtHelpWidget hw, caddr_t call_data)

{
  XtCallCallbacks(hw, XtNdoneCallback, NULL);
}

/**********************************************************************
**                           Public Functions                        **
**********************************************************************/

void AtHelpShowSection(AtHelpWidget w, char *section_name)

{
  int i;

  for (i = 0; i < w->help.numSections; i++)
    if (!issame(w->help.sectionNames[i], section_name))  {
      XmListSelectPos(w->help.toc, i+1, False);
      XmTextSetTopCharacter(w->help.displayer, w->help.sectionOffsets[i]);
      break;
    }
}

void AtHelpShowKeyword(AtHelpWidget w, char *keyword)

{
  int i;

  for (i = 0; i < w->help.numCites; i++)
    if (!strcmp(w->help.citeNames[i], keyword))  {
      XmListSelectPos(w->help.index, i+1, False);
      XmTextSetTopCharacter(w->help.displayer, w->help.citeOffsets[i]);
      break;
    }
}

/**********************************************************************/
/**********************************************************************/

int issame(char *cs, char *ct)

{
  while (*cs == ' ')
    cs++;

  return strcmp(cs, ct);
}
