/*
 * xman - X window system manual page display program.
 *
 * $XConsortium: misc.c,v 1.29 91/09/03 18:21:49 dave Exp $
 *
 * Copyright 1987, 1988 Massachusetts Institute of Technology
 *
 * 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.
 *
 * Author:    Chris D. Peterson, MIT Project Athena
 * Created:   October 27, 1987
 */

#include "globals.h"
#include "vendor.h"
#include <X11/Xos.h> 		/* sys/types.h and unistd.h included in here */
#include <sys/stat.h>
#include <errno.h>

#ifdef MOTIF
#include <Xm/MessageB.h>
#else
#endif

#include <X11/Xaw/Dialog.h>
#include <X11/Shell.h>

static FILE * Uncompress();
static Boolean UncompressNamed(), UncompressUnformatted();
extern int errno;		/* error codes. */

/*	Function Name: PopupWarning
 *	Description: This function pops upa warning message.
 *	Arguments: string - the specific warning string.
 *	Returns: none
 */

extern Widget initial_widget, top;
static Widget warnShell, warnDialog;

void 
PopdownWarning(w, client, call)
     Widget w; XtPointer client, call;
{
#ifdef MOTIF
  XtDestroyWidget( w );
#ifdef DEBUG
  printf( "XtDestroyWidget\n" );
#endif
#else
  XtPopdown((Widget)client);
#endif
}

void
PopupWarning(man_globals, string)
ManpageGlobals * man_globals;
char * string;
{
  int n;
  Arg wargs[3];
  Dimension topX, topY;
  char buffer[BUFSIZ];
  Widget positionto;
  Boolean hasPosition;

  sprintf( buffer, "Xman Warning: %s", string);
  if ( !man_globals ) {
    fprintf( stderr, "%s\n", buffer );
    return;
  }

  hasPosition = FALSE;
  if (man_globals->This_Manpage)
    positionto = man_globals->This_Manpage;
  else
    positionto = top;
  if (top)
  {
    n=0;
    XtSetArg(wargs[n], XtNx, &topX); n++;
    XtSetArg(wargs[n], XtNy, &topY); n++;
    XtGetValues(top, wargs, n);
    hasPosition = TRUE;
  }

  if (man_globals != NULL) 
    ChangeLabel(man_globals->label, buffer);
  if (man_globals->label == NULL) {
#ifdef MOTIF
    warnDialog = XmCreateWarningDialog( top, "warnDialog", NULL, 0 );
    XtAddCallback( warnDialog, XmNokCallback, PopdownWarning, NULL );
    {
      XmString xmsMsg = XmStringCreateSimple( buffer );
      XtVaSetValues( warnDialog,
        XmNmessageString, xmsMsg,
	XmNnoResize, True,
        NULL );
      XmStringFree( xmsMsg );

      XtUnmanageChild( XmMessageBoxGetChild( warnDialog, XmDIALOG_CANCEL_BUTTON ) );
      XtUnmanageChild( XmMessageBoxGetChild( warnDialog, XmDIALOG_HELP_BUTTON ) );
    }
    XtManageChild( warnDialog );
#else
    n=0;
    if (hasPosition)
    {
      XtSetArg(wargs[n], XtNx, topX); n++;
      XtSetArg(wargs[n], XtNy, topY); n++;
    }

    XtSetArg(wargs[n], XtNtransientFor, top); n++;
    warnShell = XtCreatePopupShell("warnShell", transientShellWidgetClass, 
				   initial_widget, wargs, n);
    XtSetArg(wargs[0], XtNlabel, buffer); 
    warnDialog = XtCreateManagedWidget("warnDialog", dialogWidgetClass, 
				       warnShell, wargs, 1);
    XawDialogAddButton(warnDialog, "dismiss", PopdownWarning, 
		       (XtPointer)warnShell);
    XtRealizeWidget(warnShell);
    Popup(warnShell, XtGrabNone);
#endif
  }
}


/*      THIS ROUTINE IS OBSOLETE...
 *	Function Name: PrintWarning
 *	Description: This function prints a warning message to stderr.
 *	Arguments: string - the specific warning string.
 *	Returns: none
 */

void
PrintWarning(man_globals, string)
ManpageGlobals * man_globals;
char * string;
{
  char buffer[BUFSIZ];

  sprintf( buffer, "Xman Warning: %s", string);

  if (man_globals != NULL) 
    ChangeLabel(man_globals->label, buffer);

  fprintf(stderr, "%s\n", buffer);
}

/*	Function Name: PrintError
 *	Description: This Function prints an error message and exits.
 *	Arguments: string - the specific message.
 *	Returns: none. - exits tho.
 */

void
PrintError(string)
char * string;
{
  fprintf(stderr,"Xman Error: %s\n",string);
  exit(1);
}

/*	Function Name: OpenFile
 *	Description: Assignes a file to the manpage.
 *	Arguments: man_globals - global structure.
 *                 file        - the file pointer.
 *	Returns: none
 */

void
OpenFile(man_globals, file)
ManpageGlobals * man_globals;
FILE * file;
{
  Arg arglist[1];
  Cardinal num_args = 0;

  XtSetArg(arglist[num_args], XtNfile, file); num_args++;
  XtSetValues(man_globals->manpagewidgets.manpage, arglist, num_args);
}


/*	Function Name: FindManualFile
 *	Description: Opens the manual page file given the entry information.
 *	Arguments: man_globals - the globals info for this manpage.
 *                 section_num - section number of the man page.
 *                 entry_num   - entry number of the man page.
 *	Returns: fp - the file pointer
 *
 * NOTES:
 *
 * If there is a uncompressed section it will look there for uncompresed 
 * manual pages first and then for individually comressed file in the 
 * uncompressed section.
 * 
 * If there is a compressed directory then it will also look there for 
 * the manual pages.
 *
 * If both of these fail then it will attempt to format the manual page.
 */

FILE *
FindManualFile(man_globals, section_num, entry_num)
ManpageGlobals * man_globals;
int section_num, entry_num;
{
  FILE * file;
  char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], *temp;
  char filename[BUFSIZ];
  char * entry = manual[section_num].entries[entry_num];
  int len_cat = strlen(CAT);
#ifdef sgi
  int lenpage;
#endif

  temp = CreateManpageName(entry);
  sprintf(man_globals->manpage_title, "The current manual page is: %s.", temp);
  XtFree(temp);
  
  ParseEntry(entry, path, section, page);

/*
 * Look for uncompressed files first.
 */

  sprintf(filename, "%s/%s%s/%s", path, CAT, section + len_cat, page);
  if ( (file = fopen(filename,"r")) != NULL)
    return(file);

/*
 * Then for compressed files in an uncompressed directory.
 */

  sprintf(filename, "%s/%s%s/%s.%s", path, CAT, 
	  section + len_cat, page, COMPRESSION_EXTENSION);
  if ( (file = Uncompress(man_globals, filename)) != NULL) 
    return(file);

#ifdef sgi
/*
 * Then for compressed files in an uncompressed SUBDIRS.
 */

  sprintf(filename, "%s/%s/%s.%s", path,
	  section, page, COMPRESSION_EXTENSION);
  if ( (file = Uncompress(man_globals, filename)) != NULL) 
    return(file);
#endif

#ifdef sgi
   /*
    * Eliminate the COMPRESSION_EXTENSION and then any trailing
    * ".n" where n is a digit.
    * Re-add the COMPRESSION_EXTENSION, and try again.
    */
   temp = &filename[strlen(filename)-strlen(COMPRESSION_EXTENSION)-1];
   *temp = '\0';        /* eliminate cmpress extn */
   if ( *(temp - 2) == '.' && isdigit(*(temp - 1)) )
        strcpy(temp - 1, COMPRESSION_EXTENSION);
        if ( (file = Uncompress(man_globals, filename)) != NULL)
            return(file);
#endif /* sgi */


#ifndef sgi
/*
 * And lastly files in a compressed directory.
 *
 * The directory is not actually compressed it is just named man#.Z
 * and all files in it are compressed without the .Z extension.
 * HP does it this way (really :-).
 */

  sprintf(filename, "%s/%s%s.%s/%s", path, CAT, section + len_cat,
	  COMPRESSION_EXTENSION, page);
  if ( (file = Uncompress(man_globals, filename)) != NULL)
    return(file);
#endif

/*
 * We did not find any preformatted manual pages, try to format it.
 */

  return(Format(man_globals, entry));
}

/*	Function Namecompress
 *	Description: This function will attempt to find a compressed man
 *                   page and uncompress it.
 *	Arguments: man_globals - the psuedo global info.
 *                 filename - name of file to uncompress.
 *	Returns:; a pointer to the file or NULL.
 */

static FILE *
Uncompress(man_globals, filename)
ManpageGlobals * man_globals;
char * filename;
{
  char tmpfile[BUFSIZ], error_buf[BUFSIZ];
  FILE * file;

  if ( !UncompressNamed(man_globals, filename, tmpfile) )
    return(NULL);

  else if ((file = fopen(tmpfile, "r")) == NULL) {  
    sprintf(error_buf, "Something went wrong in retrieving the %s",
	    "uncompressed manual page try cleaning up /tmp.");
    PopupWarning(man_globals, error_buf);
  }

  unlink(tmpfile);		/* remove name in tree, it will remain
				   until we close the fd, however. */
  return(file);
}

/*	Function Name: UncompressNamed
 *	Description: This function will attempt to find a compressed man
 *                   page and uncompress it.
 *	Arguments: man_globals - the psuedo global info.
 *                 filename - name of file to uncompress.
 * RETURNED        output - the file name output (must be an allocated string).
 *	Returns:; TRUE if the file was found.
 */

static Boolean
UncompressNamed(man_globals, filename, output)
ManpageGlobals * man_globals;
char * filename, * output;
{
  char tmp[BUFSIZ], cmdbuf[BUFSIZ], error_buf[BUFSIZ];
  struct stat junk;

  if (stat(filename, &junk) != 0) { /* Check for existance of the file. */
    if (errno != ENOENT) {
      sprintf(error_buf, "Error while stating file %s, errno = %d",
	      filename, errno);
      PopupWarning(man_globals, error_buf);
    }
    return(FALSE);
  }

/*
 * Using stdin is necessary to fool zcat since we cannot guarentee
 * the .Z extension.
 */

  strcpy(tmp, MANTEMP);		/* get a temp file. */
  (void) mktemp(tmp);
  strcpy(output, tmp);

  sprintf(cmdbuf, UNCOMPRESS_FORMAT, filename, output);
  if(system(cmdbuf) == 0) 	/* execute search. */
    return(TRUE);

  sprintf(error_buf, "Error while uncompressing, command was: %s", cmdbuf);
  PopupWarning(man_globals, error_buf);
  return(FALSE);
}

/*	Function Name: Format
 *	Description: This funtion formats the manual pages and interfaces
 *                   with the user.
 *	Arguments: man_globals - the psuedo globals
 *                 file - the file pointer to use and return
 *                 entry - the current entry struct.
 *                 current_box - The current directory being displayed. 
 *	Returns: none.
 */

/* ARGSUSED */

FILE *
Format(man_globals, entry)
ManpageGlobals * man_globals; 
char * entry;
{
  FILE * file;
  Widget manpage = man_globals->manpagewidgets.manpage;
  char cmdbuf[BUFSIZ], tmp[BUFSIZ], filename[BUFSIZ], error_buf[BUFSIZ];
  char path[BUFSIZ];
  XEvent event;
  Position x,y;			/* location to pop up the
				   "would you like to save" widget. */

#ifdef MOTIF
  XtManageChild( man_globals->standby );
  XmUpdateDisplay( man_globals->standby );
#else
  Popup(XtParent(man_globals->standby), XtGrabExclusive);
  while ( !XCheckTypedWindowEvent(XtDisplay(man_globals->standby), 
				  XtWindow(man_globals->standby), 
				  Expose, &event) );
  XtDispatchEvent( &event );
  XFlush(XtDisplay(man_globals->standby));
#endif

  if ( !UncompressUnformatted(man_globals, entry, filename) ) {
    /* We Really could not find it, this should never happen, yea right. */
    sprintf(error_buf, "Could not open manual page, %s", entry);
    PopupWarning(man_globals, error_buf);
#ifdef MOTIF
    XtUnmanageChild(man_globals->standby);
#else
    XtPopdown( XtParent(man_globals->standby) );
#endif
    return(NULL);
  }

  strcpy(tmp,MANTEMP);		          /* Get a temp file. */
  (void) mktemp(tmp);
  strcpy(man_globals->tmpfile, tmp);
  ParseEntry(entry, path, NULL, NULL);

  sprintf(cmdbuf,"cd %s ; %s %s %s > %s %s", path, TBL,
	  filename, FORMAT, man_globals->tmpfile, "2> /dev/null");

  if(system(cmdbuf) != 0) {	/* execute search. */
    sprintf(error_buf,
	    "Something went wrong trying to run the command: %s", cmdbuf);
    PopupWarning(man_globals, error_buf);
    file = NULL;
  }
  else {
    if ((file = fopen(man_globals->tmpfile,"r")) == NULL) {  
      sprintf(error_buf, "Something went wrong in retrieving the %s",
	      "temp file, try cleaning up /tmp");
      PopupWarning(man_globals, error_buf);
    }
    else {

#ifdef MOTIF
      XtUnmanageChild(man_globals->standby);
#else
      XtPopdown( XtParent(man_globals->standby) );
#endif
  
      if ( (man_globals->save == NULL) ||
	   (man_globals->manpagewidgets.manpage == NULL) ) 
	unlink(man_globals->tmpfile);
      else {
	char * ptr, catdir[BUFSIZ];

	/*
	 * If the catdir is writeable then ask the user if he/she wants to
	 * write the man page to it. 
	 */

	strcpy(catdir, man_globals->save_file);
	if ( (ptr = rindex(catdir, '/')) != NULL) {
	  *ptr = '\0';

	  if ( access(catdir, W_OK) != 0 )
	    unlink(man_globals->tmpfile);
	  else {
#ifdef MOTIF
            XtManageChild( man_globals->save ); /* pop up save? dialog */
#else
	    x = (Position) Width(man_globals->manpagewidgets.manpage)/2;
	    y = (Position) Height(man_globals->manpagewidgets.manpage)/2;
	    XtTranslateCoords(manpage, x, y, &x, &y);
	    PositionCenter( man_globals->save, (int) x, (int) y, 0, 0, 0, 0);
	    XtPopup( man_globals->save, XtGrabExclusive);
#endif
	  }
	}
	else 
	  unlink(man_globals->tmpfile);
      }
    }
  }

  if (man_globals->compress)	/* If the original was compressed
				   then this is a tempory file. */
    unlink(filename);
  
  return(file);
}

/*	Function Name: UncompressUnformatted
 *	Description: Finds an uncompressed unformatted manual page.
 *	Arguments: man_globals - the psuedo global structure.
 *                 entry - the manual page entry.
 * RETURNED        filename - location to put the name of the file.
 *	Returns: TRUE if the file was found.
 */

static Boolean
UncompressUnformatted(man_globals, entry, filename)
ManpageGlobals * man_globals;
char * entry, * filename;
{
  char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], input[BUFSIZ];
  int len_cat = strlen(CAT), len_man = strlen(MAN);

  ParseEntry(entry, path, section, page);

/*
 * Look for uncompressed file first.
 */

  sprintf(filename, "%s/%s%s/%s", path, MAN, section + len_man, page);
  if ( access( filename, R_OK ) == 0 ) {
    man_globals->compress = FALSE;
    sprintf(man_globals->save_file, "%s/%s%s/%s", path,
	    CAT, section + len_cat, page);
    return(TRUE);
  }

/*
 * Then for compressed files in an uncompressed directory.
 */

  sprintf(input, "%s.%s", filename, COMPRESSION_EXTENSION);
  if ( UncompressNamed(man_globals, input, filename) ) {
    man_globals->compress = TRUE;
    sprintf(man_globals->save_file, "%s/%s%s/%s.%s", path,
	    CAT, section + len_cat, page, COMPRESSION_EXTENSION);
    return(TRUE);
  }
/*
 * And lastly files in a compressed directory.
 */

  sprintf(input, "%s/%s%s.%s/%s", path, 
	  MAN, section + len_man, COMPRESSION_EXTENSION, page);
  if ( UncompressNamed(man_globals, input, filename) ) {
    man_globals->compress = TRUE;
    sprintf(man_globals->save_file, "%s/%s%s.%s/%s", path, 
	    CAT, section + len_cat, COMPRESSION_EXTENSION, page);
    return(TRUE);
  }
  return(FALSE);
}
  
/*	Function Name: AddCursor
 *	Description: This function adds the cursor to the window.
 *	Arguments: w - the widget to add the cursor to.
 *                 cursor - the cursor to add to this widget.
 *	Returns: none
 */

void
AddCursor(w,cursor)
Widget w;
Cursor cursor;
{
  XColor colors[2];
  Arg args[10];
  Cardinal num_args = 0;
  Colormap c_map;
  
  if (!XtIsRealized(w)) {
    PopupWarning(NULL, "Widget is not realized, no cursor added.\n");
    return;
  }

  XtSetArg( args[num_args], XtNcolormap, &c_map); num_args++;
  XtGetValues( w, args, num_args);

  colors[0].pixel = resources.cursors.fg_color;
  colors[1].pixel = resources.cursors.bg_color;

  XQueryColors (XtDisplay(w), c_map, colors, 2);
  XRecolorCursor(XtDisplay(w), cursor, colors, colors+1);
  XDefineCursor(XtDisplay(w),XtWindow(w),cursor);
}

/*	Function Name: ChangeLabel
 *	Description: This function changes the label field of the
 *                   given widget to the string in str.
 *	Arguments: w - the widget.
 *                 str - the string to change the label to.
 *	Returns: none
 */

void
ChangeLabel(w,str)
Widget w;
char * str;
{
  Arg arglist[3];		/* An argument list. */

#ifdef MOTIF
  XmString xms = XmStringCreateSimple( str );
  XtSetArg(arglist[0], XmNlabelString, xms);
#else
  XtSetArg(arglist[0], XtNlabel, str);

/* shouldn't really have to do this. */
  XtSetArg(arglist[1], XtNwidth, 0);
  XtSetArg(arglist[2], XtNheight, 0);
#endif
  if (w == NULL) return;

  XtSetValues(w, arglist, (Cardinal) 1);
#ifdef MOTIF
  XmStringFree( xms );
#endif
}

/*
 * In an ideal world this would be part of the XToolkit, and I would not
 * have to do it, but such is life sometimes.  Perhaps in X11R3.
 */

/*	Function Name: PositionCenter
 *	Description: This fuction positions the given widgets center
 *                   in the following location.
 *	Arguments: widget - the widget widget to postion
 *                 x,y - The location for the center of the widget
 *                 above - number of pixels above center to locate this widget
 *                 left - number of pixels left of center to locate this widget
 *                 h_space, v_space - how close to get to the edges of the
 *                                    parent window.
 *	Returns: none
 *      Note:  This should only be used with a popup widget that has override
 *             redirect set.
 */

void
PositionCenter(widget,x,y,above,left,v_space,h_space)
Widget widget;
int x,y,above,left;
int h_space,v_space;
{
  Arg wargs[2];
  int x_temp,y_temp;		/* location of the new window. */
  int parent_height,parent_width; /* Height and width of the parent widget or
				   the root window if it has no parent. */

  x_temp = x - left - Width(widget) / 2 + BorderWidth(widget);
  y_temp = y - above -  Height(widget) / 2 + BorderWidth(widget);

  parent_height = HeightOfScreen(XtScreen(widget));
  parent_width = WidthOfScreen(XtScreen(widget));

/*
 * Check to make sure that all edges are within the viewable part of the
 * root window, and if not then force them to be.
 */

  if (x_temp < h_space) 
    x_temp = v_space;
  if (y_temp < v_space)
    (y_temp = 2);

  if ( y_temp + Height(widget) + v_space > parent_height )
      y_temp = parent_height - Height(widget) - v_space; 

  if ( x_temp + Width(widget) + h_space > parent_width )
      x_temp = parent_width - Width(widget) - h_space; 

  XtSetArg(wargs[0], XtNx, x_temp); 
  XtSetArg(wargs[1], XtNy, y_temp); 
  XtSetValues(widget, wargs, 2);
}  

/*	Function Name: ParseEntry(entry, path, sect, page)
 *	Description: Parses the manual pages entry filenames.
 *	Arguments: str - the full path name.
 *                 path - the path name.      RETURNED
 *                 sect - the section name.   RETURNED
 *                 page - the page name.      RETURNED
 *	Returns: none.
 */

void
ParseEntry(entry, path, sect, page)
char *entry, *path, *page, *sect;
{
  char *c, temp[BUFSIZ];

  strcpy(temp, entry);

  c = rindex(temp, '/');
  if (c == NULL) 
    PrintError("index failure in ParseEntry.");
  *c++ = '\0';
  if (page != NULL)
    strcpy(page, c);

  c = rindex(temp, '/');
  if (c == NULL) 
    PrintError("index failure in ParseEntry.");
  *c++ = '\0';
  if (sect != NULL)
    strcpy(sect, c);

  if (path != NULL)
    strcpy(path, temp);
}

/*      Function Name: GetGlobals
 *      Description: Gets the psuedo globals associated with the
 *                   manpage associated with this widget.
 *      Arguments: w - a widget in the manpage.
 *      Returns: the psuedo globals.
 *      Notes: initial_widget is a globals variable.
 *             manglobals_context is a global variable.
 */

ManpageGlobals *
GetGlobals(w)
Widget w;
{
  Widget temp;
  caddr_t data;

  while ( (temp = XtParent(w)) != initial_widget && (temp != NULL))
    w = temp;

  if (temp == NULL) 
    XtAppError(XtWidgetToApplicationContext(w), 
	       "Xman: Could not locate widget in tree, exiting");

  if (XFindContext(XtDisplay(w), XtWindow(w),
		   manglobals_context, &data) != XCSUCCESS)
    XtAppError(XtWidgetToApplicationContext(w), 
	       "Xman: Could not find global data, exiting");

  return( (ManpageGlobals *) data);
}
  
/*      Function Name: SaveGlobals
 *      Description: Saves the psuedo globals on the widget passed
 *                   to this function, although GetGlobals assumes that
 *                   the data is associated with the popup child of topBox.
 *      Arguments: w - the widget to associate the data with.
 *                 globals - data to associate with this widget.
 *      Returns: none.
 *      Notes: WIDGET MUST BE REALIZED.
 *             manglobals_context is a global variable.
 */

void
SaveGlobals(w, globals)
Widget w;
ManpageGlobals * globals;
{
  if (XSaveContext(XtDisplay(w), XtWindow(w), manglobals_context,
		   (caddr_t) globals) != XCSUCCESS)
    XtAppError(XtWidgetToApplicationContext(w), 
	       "Xman: Could not save global data, are you out of memory?");
}

/*      Function Name: RemoveGlobals
 *      Description: Removes the psuedo globals from the widget passed
 *                   to this function.
 *      Arguments: w - the widget to remove the data from.
 *      Returns: none.
 *      Notes: WIDGET MUST BE REALIZED.
 *             manglobals_context is a global variable.
 */

void
RemoveGlobals(w)
Widget w;
{
  if (XDeleteContext(XtDisplay(w), XtWindow(w), 
		     manglobals_context) != XCSUCCESS)
    XtAppError(XtWidgetToApplicationContext(w), 
	       "Xman: Could not remove global data?");
}
