/* 
 * xvdir.c - Directory changin', file i/o dialog box
 *
 * callable functions:
 *
 *   CreateDirW(geom,bwidth)-  creates the dirW window.  Doesn't map it.
 *   DirBox(vis)            -  random processing based on value of 'vis'
 *                             maps/unmaps window, etc.
 *   ClickDirW()            -  handles mouse clicks in DirW
 *   LoadCurrentDirectory() -  loads up current dir information for dirW
 *   DoSave()               -  calls appropriate save routines
 *   SetDirFName()          -  sets the 'load/save-as' filename and default
 *   GetDirFName()          -  gets the 'load/save-as' filename (no path)
 *   SetDirRButt()          -  sets format/color/size rbutts 
 */


/*
 * Copyright 1989, 1990, 1991, 1992 by John Bradley and
 *                       The University of Pennsylvania
 *
 * Permission to use, copy, and distribute for non-commercial purposes,
 * is hereby granted without fee, providing that the above copyright
 * notice appear in all copies and that both the copyright notice and this
 * permission notice appear in supporting documentation. 
 *
 * The software may be modified for your own purposes, but modified versions
 * may not be distributed.
 *
 * This software is provided "as is" without any expressed or implied warranty.
 *
 * The author may be contacted via:
 *    US Mail:   John Bradley
 *               GRASP Lab, Room 301C
 *               3401 Walnut St.  
 *               Philadelphia, PA  19104
 *
 *    Phone:     (215) 898-8813
 *    EMail:     bradley@cis.upenn.edu       
 */


#define NEEDSDIR
#include "xv.h"

#ifndef VMS
#include <pwd.h>       /* for getpwnam() prototype and passwd struct */
#endif


#define NLINES 9                   /* # of lines in list control (keep odd) */
#define LISTW  200

#define BUTTW   60
#define BUTTH   26
#define DDWIDE  (LISTW-80+15)
#define DNAMWIDE 194

#define MAXDEEP 30    /* maximum number of directories in cwd path */
#define MAXFNLEN 256   /* max length of filename being entered */


/* convex c 4.x is fanatically ANSI, and the OS is increasingly fanatically
   POSIX.  The compiler won't define __STDC__ in extended (default) mode,
   and the various include files won't do some things if __STDC__ is
   defined, and won't do others if it isn't.  The compiler people say that
   the use of __STDC__ to detect an ANSI compiler is wrong, and that everyone
   should use __stdc__.  I typically just add -D__STDC__ to the commandline
   and work around any problems it causes.  Here, these don't get defined
   if __STDC__ is defined, because the include files assume that if it is,
   the compiler is being strict.  Sigh   

   Anthony A. Datri  <datri@concave.convex.com> */

#if defined(__convex__) && defined (__STDC__)
#define S_IFMT  _S_IFMT
#define S_IFDIR _S_IFDIR
#define S_IFCHR _S_IFCHR
#define S_IFBLK _S_IFBLK
#endif


#ifdef __STDC__
static void RedrawDList(void);
static void changedDirMB(void);
static int  dnamcmp(char **, char **);
static int  cd_able(char *);
static void setFName(char *);
static void showFName(void);
static int  FNameCdable(void);
static void changeSuffix(void);
#else
static void RedrawDList();
static void changedDirMB();
static int  dnamcmp();
static int  cd_able();
static void setFName();
static void showFName();
static int  FNameCdable();
static void changeSuffix();
#endif


static int    listh;
static char  *fnames[MAXNAMES];
static int    numfnames = 0, ndirs = 0;
static char   path[MAXPATHLEN+1];
static char  *dirs[MAXDEEP];            /* list of directory names */
static char  *dirMBlist[MAXDEEP];       /* list of dir names in right order */
static char  *lastdir;                  /* name of the directory we're in */
static char   filename[MAXFNLEN+100];   /* filename being entered */
static char   deffname[MAXFNLEN+100];   /* default filename */
static char   globname[MAXFNLEN+100];   /* the +100 is for ~ expansion */

static RBUTT *formatRB, *colorRB;
static int    savemode;                 /* if 0 'load box', if 1 'save box' */
static int    curPos, stPos, enPos;     /* filename textedit stuff */
static MBUTT  dirMB;                    /* popup path menu */



/***************************************************/
void CreateDirW(geom)
char *geom;
{
  int y;
  XClassHint classh;

  listh = LINEHIGH * NLINES;

  dirW = CreateWindow("",geom,DIRWIDE, DIRHIGH, infofg, infobg);
  if (!dirW) FatalError("couldn't create 'directory' window!");

  classh.res_name = "xv";
  classh.res_class = "XVdir";
  XSetClassHint(theDisp, dirW, &classh);
  StoreDeleteWindowProp(dirW);

  dnamW = XCreateSimpleWindow(theDisp, dirW, 80, listh+70-ASCENT-4, 
			      DNAMWIDE+6, LINEHIGH+4, 1, infofg, infobg);
  if (!dnamW) FatalError("can't create name window");
  XSelectInput(theDisp, dnamW, ExposureMask);


  LSCreate(&dList, dirW, 10, 14+LINEHIGH, LISTW, listh, NLINES,
	   fnames, numfnames,
	   infofg, infobg, RedrawDList, 1, 0);

  CBCreate(&browseCB, dirW, DIRWIDE/2, dList.y + listh + 4, 
	   "Browse", infofg, infobg);

  CBCreate(&savenormCB, dirW, DIRWIDE/2, dList.y + listh + 4, 
	   "Save at normal size", infofg, infobg);


  BTCreate(&dbut[S_BOK], dirW, 233,   dList.y-13+listh/6, 60, BUTTH, 
	   "Ok", infofg, infobg);
  BTCreate(&dbut[S_BCANC], dirW, 233, dList.y-13+(listh*3)/6, 60, BUTTH, 
	   "Cancel", infofg, infobg);
  BTCreate(&dbut[S_BRESCAN], dirW, 233, dList.y-13+(listh*5)/6, 60, BUTTH, 
	   "Rescan", infofg, infobg);

  y = listh + 100;
  formatRB = RBCreate(NULL, dirW, 26, y, "GIF", infofg, infobg);
  RBCreate(formatRB, dirW, 26, y+18,  "PM", infofg, infobg);
  RBCreate(formatRB, dirW, 26, y+36,  "PBM (raw)", infofg, infobg);
  RBCreate(formatRB, dirW, 26, y+54,  "PBM (ascii)", infofg, infobg);
  RBCreate(formatRB, dirW, 26, y+72,  "X11 Bitmap", infofg, infobg);
  RBCreate(formatRB, dirW, 26, y+90,  "Sun Rasterfile", infofg, infobg);
  RBCreate(formatRB, dirW, 26, y+108, "PostScript", infofg, infobg);
  y = y + 126;

#ifdef HAVE_JPEG
  RBCreate(formatRB, dirW, 26, y, "JPEG", infofg, infobg);
  y += 18;
#endif

#ifdef HAVE_TIFF
  RBCreate(formatRB, dirW, 26, y, "TIFF", infofg, infobg);
  y += 18;
#endif

  y = listh + 100;
  colorRB = RBCreate(NULL, dirW, DIRWIDE/2, y, "Full Color", infofg, infobg);
  RBCreate(colorRB, dirW, DIRWIDE/2, y+18, "Greyscale", infofg, infobg);
  RBCreate(colorRB, dirW, DIRWIDE/2, y+36, "B/W Dithered", infofg, infobg);
  RBCreate(colorRB, dirW, DIRWIDE/2, y+54, "Reduced Color", infofg, infobg);

  SetDirFName("");

  XMapSubwindows(theDisp, dirW);

  /* have to create MBUTTs after XMapSubWindows to keep popup unmapped */
  MBCreate(&dirMB, dirW, 50, 5, DDWIDE,LINEHIGH,NULL,NULL,0,infofg,infobg);

  numfnames = 0;
}
  

/***************************************************/
void DirBox(mode)
int mode;
{
  if (mode) {
    WaitCursor();  LoadCurrentDirectory();  SetCursors(-1);
  }

  if (!mode) XUnmapWindow(theDisp, dirW);  /* close */

  else if (mode == BLOAD) {
    XStoreName(theDisp, dirW, "xv load");
    XSetIconName(theDisp, dirW, "xv load");
    XResizeWindow(theDisp, dirW, DIRWIDE, listh+100);

    /* SetDirFName(""); */
    CenterMapWindow(dirW, dbut[S_BOK].x+30, dbut[S_BOK].y + BUTTH/2,
		    DIRWIDE, listh+100);

    savemode = 0;
  }

  else if (mode == BSAVE) {
    XStoreName(theDisp, dirW, "xv save");
    XSetIconName(theDisp, dirW, "xv save");
    XResizeWindow(theDisp, dirW, DIRWIDE, DIRHIGH);

    CenterMapWindow(dirW, dbut[S_BOK].x+30, dbut[S_BOK].y + BUTTH/2,
		    DIRWIDE, DIRHIGH);

    savemode = 1;
  }

  dirUp = mode;
  BTSetActive(&but[BLOAD], !dirUp);
  BTSetActive(&but[BSAVE], !dirUp);
}


/***************************************************/
void RedrawDirW(x,y,w,h)
int x,y,w,h;
{
  int  i,ypos;
  char foo[30], *str;
  XRectangle xr;

  xr.x = x;  xr.y = y;  xr.width = w;  xr.height = h;
  XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted);

  if (dList.nstr==1) strcpy(foo,"1 file");
                else sprintf(foo,"%d files",dList.nstr);

  ypos = dList.y + dList.h + 5 + ASCENT;
  XSetForeground(theDisp, theGC, infobg);
  XFillRectangle(theDisp, dirW, theGC, 10, ypos-ASCENT, DIRWIDE, CHIGH);
  XSetForeground(theDisp, theGC, infofg);
  XDrawString(theDisp, dirW, theGC, 10, ypos, foo, strlen(foo));

  if (dirUp == BLOAD) str = "Load file:";  
                 else str = "Save file:";
  XDrawString(theDisp, dirW, theGC, 10, dList.h+70, str,strlen(str));

  for (i=0; i<S_NBUTTS; i++) BTRedraw(&dbut[i]);

  MBRedraw(&dirMB);

  if (savemode) {
    RBRedraw(formatRB, -1);
    RBRedraw(colorRB, -1);

    ULineString(dirW, "Format", formatRB->x-16, formatRB->y-3-DESCENT);
    ULineString(dirW, "Colors", colorRB->x-16,  colorRB->y-3-DESCENT);

    CBRedraw(&savenormCB);
  }
  else {
    CBRedraw(&browseCB);
  }

  XSetClipMask(theDisp, theGC, None);
}


/***************************************************/
int ClickDirW(x,y)
int x,y;
{
  BUTT  *bp;
  int    bnum,i;


  /* check the RBUTTS first, since they don't DO anything */
  if ( (bnum=RBClick(formatRB, x,y)) >= 0) { 
    i = RBTrack(formatRB, bnum);

    if (RBWhich(formatRB)==4) {  /* turn all but B/W dithered */
      RBSetActive(colorRB,0,0);
      RBSetActive(colorRB,1,0);
      RBSetActive(colorRB,3,0);
      RBSelect(colorRB,2);
    }
    else {                       /* turn on all but B/W dithered */
      RBSetActive(colorRB,0,1);
      RBSetActive(colorRB,1,1);
      RBSetActive(colorRB,3,1);
    }

    changeSuffix();
    return -1;
  }

  if ( (bnum=RBClick(colorRB, x,y)) >= 0) {
    i=RBTrack(colorRB, bnum);
    changeSuffix();
    return -1;
  }

  /* controls that are only in Load *or* Save mode */
  if (!savemode) {
    if (CBClick(&browseCB, x, y)) CBTrack(&browseCB);
  } 
  else {
    if (CBClick(&savenormCB, x, y)) CBTrack(&savenormCB);
  }


  for (bnum=0; bnum<S_NBUTTS; bnum++) {
    bp = &dbut[bnum];
    if (PTINRECT(x, y, bp->x, bp->y, bp->w, bp->h)) break;
  }

  if (bnum<S_NBUTTS) {   /* found one */
    if (BTTrack(bp)) return (bnum);
  }

  if (MBClick(&dirMB, x, y)) {
    if (MBTrack(&dirMB)) changedDirMB();
  }

  return -1;
}


/***************************************************/
void SelectDir(n)
int n;
{
  /* called when entry #n in the dir list was selected/double-clicked */

  /* if n<0, nothing was double-clicked, but perhaps the selection
     has changed.  Copy the selection to the filename if a) we're in
     the 'load' box, and b) it's not a directory name */

  if (n<0) {
    if (dList.selected>=0 
        /* && !cd_able(dList.str[dList.selected]) */
	/* && !savemode */ )
      setFName(dList.str[dList.selected]+1);
    return;
  }

  /* can just pretend 'enter' was hit on a double click, as the original
     click would've copied the string to filename */

  if (FNameCdable()) setFName(deffname);  /* back to filename after CDing */
  else FakeButtonPress(&dbut[S_BOK]);
}



/***************************************************/
static void changedDirMB()
{
  if (dirMB.selected != 0) {   /* changed directories */
    char tmppath[MAXPATHLEN+1], *trunc_point;

    /* end 'path' by changing trailing '/' (of dir name) to a '\0' */
    trunc_point = (dirs[(ndirs-1)-dirMB.selected + 1] - 1);
    *trunc_point = '\0';

    if (path[0] == '\0') {
      /* special case:  if cd to '/', fix path (it's currently "") */
#if defined(apollo) && !defined(MSDOS)    /*** Apollo DomainOS uses // as the network root ***/
      strcpy(tmppath,"//");
#else
      strcpy(tmppath,"/");
#endif
    }
    else strcpy(tmppath, path);

#ifdef VMS
    /*
     *  The VMS chdir always needs 2 components (device and directory),
     *  so convert "/device" to "/device/000000" and convert
     *  "/" to "/DEVICE_LIST_ROOT/000000" (device_list_root is special
     *  concealed device setup to provide list of available disks).
     */
    if ( ((ndirs-dirMB.selected) == 2) && (strlen(tmppath) > 1) ) 
      strcat ( tmppath, "/000000" ); /* add root dir for device */
    else if  ((ndirs-dirMB.selected) == 1 ) {
      strcpy ( tmppath, "/device_list_root/000000" );  /* fake top level */
    }
#endif
    if (chdir(tmppath)) {
      static char *foo[] = { "\nWhatever" };
      char str[512];
      sprintf(str,"Unable to cd to '%s'\n%s\n", tmppath);
      *trunc_point = '/';  /* restore the path */
      dirMB.selected = 0;
      MBRedraw(&dirMB);
      PopUp(str, foo, 1);
    }
    else 
      LoadCurrentDirectory();   
  }
}


/***************************************************/
static void RedrawDList()
{
  LSRedraw(&dList);
}


/***************************************************/
void LoadCurrentDirectory()
{
  DIR           *dirp;
  int            i, j, ftype;
  struct stat    st;
  char          *dbeg, *dend;
#ifdef DIRENT
  struct dirent *dp;
#else
  struct direct *dp;
#endif


  /* get rid of previous file names */
  for (i=0; i<numfnames; i++) free(fnames[i]);
  numfnames = 0;

  /* get rid of old dirMBlist */
  for (i=0; i<ndirs; i++) free(dirMBlist[i]);
  
  GETWD(path);    /* get current working directory */

  if (path[strlen(path)-1] != '/')
    strcat(path,"/");   /* tack on a trailing '/' to make path consistent */

  /* path will be something like: "/u3/bradley/src/weiner/whatever/" */
  /* parse path into individual directory names */
  dbeg = dend = path;
  for (i=0; i<MAXDEEP && dend; i++) {
    dend = (char *) strchr(dbeg,'/');  /* find next '/' char */

#if defined(apollo) && !defined(MSDOS)
    /** On apollos the path will be something like //machine/users/foo/ **/
    /** handle the initial // **/
    if ((dend == dbeg ) && (dbeg[0] == '/') && (dbeg[1] == '/')) dend += 1;
#endif

    dirs[i] = dbeg;
    dbeg = dend+1;
  }
  ndirs = i-1;


  /* build dirMBlist */
  for (i=ndirs-1,j=0; i>=0; i--,j++) {
    int stlen = (i<(ndirs-1)) ? dirs[i+1] - dirs[i] : strlen(dirs[i]);
    dirMBlist[j] = (char *) malloc(stlen+1);
    if (!dirMBlist[j]) FatalError("unable to malloc dirMBlist[]");

    strncpy(dirMBlist[j], dirs[i], stlen);
    dirMBlist[j][stlen] = '\0';
  }
    

  lastdir = dirs[ndirs-1];
  dirMB.list = dirMBlist;
  dirMB.nlist = ndirs;
  dirMB.selected = 0;
  XClearArea(theDisp, dirMB.win, dirMB.x,dirMB.y, dirMB.w+3,dirMB.h+3, False);
  i = StringWidth(dirMBlist[dirMB.selected]) + 10;
  dirMB.x = dirMB.x + dirMB.w/2 - i/2;
  dirMB.w = i;
  MBRedraw(&dirMB);


  dirp = opendir(".");
  if (!dirp) {
    LSNewData(&dList, fnames, 0);
    RedrawDirW(0,0,DIRWIDE,DIRHIGH);
    return;
  }

  WaitCursor();

  i=0;
  while ( (dp = readdir(dirp)) != NULL) {
    if (strcmp(dp->d_name, ".")==0 || strcmp(dp->d_name, "..")==0) {
      /* skip over '.' and '..' */
    }
    else {

      if (i == MAXNAMES) {
	fprintf(stderr,
		"%s: too many directory entries.  Only saving first %d.\n",
		cmd, MAXNAMES);
	break;
      }

      if ((i&31)==0) WaitCursor();

#ifdef DIRENT
#  ifdef i386
      /* Not 100% sure d_reclen is correct, but it works...  MWS 10/18/90 */
      fnames[i] = (char *) malloc(dp->d_reclen + 2); /* filetype + '\0'*/
#  else
      fnames[i] = (char *) malloc(strlen(dp->d_name) + 3);
#  endif
#else
      fnames[i] = (char *) malloc(dp->d_namlen + 2); /* +2=filetype + '\0'*/
#endif

      if (!fnames[i]) FatalError("malloc error while reading directory");
      strcpy(fnames[i]+1, dp->d_name);

      /* figure out what type of file the beastie is */
      fnames[i][0] = C_REG;   /* default to normal file, if stat fails */

      if (!nostat && (stat(fnames[i]+1, &st)==0)) {
	ftype = st.st_mode & S_IFMT;   /* mask off uninteresting bits */
	if      (ftype == S_IFDIR)  fnames[i][0] = C_DIR;
	else if (ftype == S_IFCHR)  fnames[i][0] = C_CHR;
	else if (ftype == S_IFBLK)  fnames[i][0] = C_BLK;

#ifdef S_IFIFO
	else if (ftype == S_IFIFO)  fnames[i][0] = C_FIFO;
#endif

#ifdef S_IFLNK
	else if (ftype == S_IFLNK)  fnames[i][0] = C_LNK;
#endif

#ifdef S_IFSOCK
        else if (ftype == S_IFSOCK) fnames[i][0] = C_SOCK;
#endif
      }
      else {
	/* fprintf(stderr,"problems 'stat-ing' files\n");*/
	fnames[i][0] = C_REG;
      }
      i++;
    }
  }

  closedir(dirp);

  numfnames = i;

  qsort((char *) fnames, numfnames, sizeof(char *), dnamcmp);

  LSNewData(&dList, fnames, numfnames);
  RedrawDirW(0,0,DIRWIDE,DIRHIGH);
  SetCursors(-1);
}


/***************************************************/
static int cd_able(str)
char *str;
{
  return ((str[0] == C_DIR || str[0] == C_LNK));
}


/***************************************************/
static int dnamcmp(s1,s2)
char **s1, **s2;
{
  /* sort so that directories are at beginning of list */

  /* if both dir/lnk or both NOT dir/lnk, sort on name */

  if ( ( cd_able(*s1) &&  cd_able(*s2)) ||
       (!cd_able(*s1) && !cd_able(*s2)))
    return (strcmp((*s1)+1, (*s2)+1));

  else if (cd_able(*s1)) return -1;  /* s1 is first */
  else return 1;                     /* s2 is first */
}





/***************************************************/
int DirKey(c)
int c;
{
  /* got keypress in dirW.  stick on end of filename */
  int len;

  len = strlen(filename);
  
  if (c>=' ' && c<'\177') {             /* printable characters */
    /* note: only allow 'piped commands' in savemode... */

    /* only allow spaces in 'piped commands', not filenames */
    if (c==' ' && (!ISPIPE(filename[0]) || curPos==0)) return (-1);

    /* only allow vertbars in 'piped commands', not filenames */
    if (c=='|' && curPos!=0 && !ISPIPE(filename[0])) return(-1);

    if (len >= MAXFNLEN-1) return(-1);  /* max length of string */
    bcopy(&filename[curPos], &filename[curPos+1], len-curPos+1);
    filename[curPos]=c;  curPos++;
  }

  else if (c=='\010' || c=='\177') {    /* BS or DEL */
    if (curPos==0) return(-1);          /* at beginning of str */
    bcopy(&filename[curPos], &filename[curPos-1], len-curPos+1);
    curPos--;
  }

  else if (c=='\025') {                 /* ^U: clear entire line */
    filename[0] = '\0';
    curPos = 0;
  }

  else if (c=='\013') {                 /* ^K: clear to end of line */
    filename[curPos] = '\0';
  }

  else if (c=='\001') {                 /* ^A: move to beginning */
    curPos = 0;
  }

  else if (c=='\005') {                 /* ^E: move to end */
    curPos = len;
  }

  else if (c=='\004') {                 /* ^D: delete character at curPos */
    if (curPos==len) return(-1);
    bcopy(&filename[curPos+1], &filename[curPos], len-curPos);
  }

  else if (c=='\002') {                 /* ^B: move backwards char */
    if (curPos==0) return(-1);
    curPos--;
  }

  else if (c=='\006') {                 /* ^F: move forwards char */
    if (curPos==len) return(-1);
    curPos++;
  }

  else if (c=='\012' || c=='\015') {    /* CR or LF */

    /* ENTER handling.  Tricky... */
    if (FNameCdable()) setFName(deffname);  /* back to filename after a CD */
    else FakeButtonPress(&dbut[S_BOK]);
  }

  else if (c=='\033') {                  /* ESC = Cancel */
    FakeButtonPress(&dbut[S_BCANC]);
  }

  else return(-1);                      /* unhandled character */

  showFName();
  return(0);
}


/***************************************************/
void RedrawDNamW()
{
  int cpos;

  /* draw substring filename[stPos:enPos] and cursor */

  XSetForeground(theDisp, theGC, infofg);

  if (stPos>0) {  /* draw a "there's more over here" doowah */
    XDrawLine(theDisp, dnamW, theGC, 0,0,0,LINEHIGH+4);
    XDrawLine(theDisp, dnamW, theGC, 1,0,1,LINEHIGH+4);
    XDrawLine(theDisp, dnamW, theGC, 2,0,2,LINEHIGH+4);
  }

  if (enPos<strlen(filename)) {  /* draw a "there's more over here" doowah */
    XDrawLine(theDisp, dnamW, theGC, DNAMWIDE+5,0,DNAMWIDE+5,LINEHIGH+4);
    XDrawLine(theDisp, dnamW, theGC, DNAMWIDE+4,0,DNAMWIDE+4,LINEHIGH+4);
    XDrawLine(theDisp, dnamW, theGC, DNAMWIDE+3,0,DNAMWIDE+3,LINEHIGH+4);
  }

  XDrawString(theDisp, dnamW, theGC,3,ASCENT+3,filename+stPos, enPos-stPos);

  cpos = XTextWidth(mfinfo, &filename[stPos], curPos-stPos);
  XDrawLine(theDisp, dnamW, theGC, 3+cpos, 2, 3+cpos, 2+CHIGH+1);
  XDrawLine(theDisp, dnamW, theGC, 3+cpos, 2+CHIGH+1, 5+cpos, 2+CHIGH+3);
  XDrawLine(theDisp, dnamW, theGC, 3+cpos, 2+CHIGH+1, 1+cpos, 2+CHIGH+3);
}


/***************************************************/
int DoSave()
{
  FILE *fp;
  byte *thepic, *bwpic, *rp, *gp, *bp;
  int   w, h, rv, fmt, col, nc;

  /* opens file, does appropriate color pre-processing, calls save routine
     based on chosen format.  Returns '0' if successful */

  dbut[S_BOK].lit = 1;  BTRedraw(&dbut[S_BOK]);

  if (savenormCB.val) { thepic = cpic;  w = cWIDE;  h = cHIGH; }
                 else { thepic = epic;  w = eWIDE;  h = eHIGH; }

  col = RBWhich(colorRB);  fmt = RBWhich(formatRB);

  strcpy(globname, filename);
  if (globname[0] == '~') Globify(globname);

  /* handle formats that pop up 'how do you want to save this' boxes */
  if (fmt == F_PS) {   /* PostScript */
    PSSaveParams(globname, col);
    PSDialog(1);                   /* open PSDialog box */
    dbut[S_BOK].lit = 0;  BTRedraw(&dbut[S_BOK]);
    return 0;                      /* always 'succeeds' */
  }

#ifdef HAVE_JPEG
  else if (fmt == F_JPEG) {   /* JPEG */
    JPEGSaveParams(globname, col);
    JPEGDialog(1);                   /* open JPEGDialog box */
    dbut[S_BOK].lit = 0;  BTRedraw(&dbut[S_BOK]);
    return 0;                      /* always 'succeeds' */
  }
#endif

#ifdef HAVE_TIFF
  else if (fmt == F_TIFF) {   /* TIFF */
    TIFFSaveParams(globname, col);
    TIFFDialog(1);                   /* open TIFF Dialog box */
    dbut[S_BOK].lit = 0;  BTRedraw(&dbut[S_BOK]);
    return 0;                      /* always 'succeeds' */
  }
#endif



  WaitCursor();

  bwpic = HandleBWandReduced(col, &nc, &rp, &gp, &bp);
  if (bwpic) thepic = bwpic;


  fp = OpenOutFile(globname);
  if (!fp) {
    if (bwpic) free(bwpic);
    SetCursors(-1);
    dbut[S_BOK].lit = 0;  BTRedraw(&dbut[S_BOK]);
    return -1;
  }

  if (col == F_REDUCED) col = F_FULLCOLOR;
  rv = 0;

  switch (fmt) {
  case F_GIF:
    rv = WriteGIF(fp, thepic, w, h, rp, gp, bp, nc, col);  break;

  case F_PM:
    rv = WritePM (fp, thepic, w, h, rp, gp, bp, nc, col);  break;

  case F_PBMRAW:
    rv = WritePBM(fp, thepic, w, h, rp, gp, bp, nc, col, 1);  break;

  case F_PBMASCII: 
    rv = WritePBM(fp, thepic, w, h, rp, gp, bp, nc, col, 0);  break;

  case F_XBM:
    rv = WriteXBM(fp, thepic, w, h, globname);  break;

  case F_SUNRAS:
    rv = WriteSunRas(fp, thepic, w, h, rp, gp, bp, nc, col,0);  break;
  }

  if (CloseOutFile(fp, globname, rv) == 0) {
    DirBox(0);
    LoadCurrentDirectory();   /* wrote file: rescan directory */
  }

  if (bwpic) free(bwpic);

  SetCursors(-1);
  dbut[S_BOK].lit = 0;  BTRedraw(&dbut[S_BOK]);

  return rv;
}



/***************************************************/
void SetDirFName(st)
char *st;
{
  strncpy(deffname, st, MAXFNLEN-1);
  setFName(st);
}


/***************************************************/
static void setFName(st)
char *st;
{
  strncpy(filename, st, MAXFNLEN-1);
  filename[MAXFNLEN-1] = '\0';  /* make sure it's terminated */
  curPos = strlen(st);
  stPos = 0;  enPos = curPos;

  showFName();
}


/***************************************************/
static void showFName()
{
  int len;

  len = strlen(filename);

  if (curPos<stPos) stPos = curPos;
  if (curPos>enPos) enPos = curPos;

  if (stPos>len) stPos = (len>0) ? len-1 : 0;
  if (enPos>len) enPos = (len>0) ? len-1 : 0;

  /* while substring is shorter than window, inc enPos */

  while (XTextWidth(mfinfo, &filename[stPos], enPos-stPos) < DNAMWIDE
	 && enPos<len) { enPos++; }

  /* while substring is longer than window, dec enpos, unless enpos==curpos,
     in which case, inc stpos */

  while (XTextWidth(mfinfo, &filename[stPos], enPos-stPos) > DNAMWIDE) {
    if (enPos != curPos) enPos--;
    else stPos++;
  }

  XClearWindow(theDisp, dnamW);
  RedrawDNamW();
  BTSetActive(&dbut[S_BOK], strlen(filename)!=0);
}


/***************************************************/
char *GetDirFName()
{
  return (filename);
}


/***************************************************/
void SetDirRButt(group, bnum)
int group, bnum;
{
  if (group == F_COLORS) RBSelect(colorRB, bnum);
  else if (group == F_FORMAT) {
    RBSelect(formatRB, bnum);

    if (RBWhich(formatRB) == F_XBM) { /* turn off all but B/W */
      RBSetActive(colorRB,0,0);
      RBSetActive(colorRB,1,0);
      RBSetActive(colorRB,3,0);
      RBSelect(colorRB,2);
    }

    else {                       /* turn on all but B/W dithered */
      RBSetActive(colorRB,0,1);
      RBSetActive(colorRB,1,1);
      RBSetActive(colorRB,3,1);
    }
  }
}

  

/***************************************/
static void changeSuffix()
{
  /* see if there's a common suffix at the end of the filename.  
     if there is, remember what case it was (all caps or all lower), lop
     it off, and replace it with a new appropriate suffix, in the
     same case */

  int allcaps;
  char *suffix, *sp, *dp, lowsuf[512];

  /* find the last '.' in the filename */
  suffix = (char *) strrchr(filename, '.');
  if (!suffix) return;
  suffix++;  /* point to first letter of the suffix */

  /* check for all-caposity */
  for (sp = suffix, allcaps=1; *sp; sp++) 
    if (islower(*sp)) allcaps = 0;

  /* copy the suffix into an all-lower-case buffer */
  for (sp=suffix, dp=lowsuf; *sp; sp++, dp++) {
    *dp = (isupper(*sp)) ? tolower(*sp) : *sp;
  }
  *dp = '\0';

  /* compare for common suffixes */
  if ((strcmp(lowsuf,"gif" )==0) ||
      (strcmp(lowsuf,"pm"  )==0) ||
      (strcmp(lowsuf,"pbm" )==0) ||
      (strcmp(lowsuf,"pgm" )==0) ||
      (strcmp(lowsuf,"ppm" )==0) ||
      (strcmp(lowsuf,"pnm" )==0) ||
      (strcmp(lowsuf,"bm"  )==0) ||
      (strcmp(lowsuf,"xbm" )==0) ||
      (strcmp(lowsuf,"ras" )==0) ||
      (strcmp(lowsuf,"ps"  )==0) ||
      (strcmp(lowsuf,"eps" )==0) ||
      (strcmp(lowsuf,"jpg" )==0) ||
      (strcmp(lowsuf,"jpeg")==0) ||
      (strcmp(lowsuf,"jfif")==0) ||
      (strcmp(lowsuf,"tif" )==0) ||
      (strcmp(lowsuf,"tiff")==0)) {

    /* found one.  set lowsuf = to the new suffix, and tack on to filename */

    int fmt, col;
    fmt = RBWhich(formatRB);
    col = RBWhich(colorRB);

    switch (fmt) {
    case F_GIF:      strcpy(lowsuf,"gif");  break;
    case F_PM:       strcpy(lowsuf,"pm");   break;
    case F_PBMRAW:
    case F_PBMASCII: if (col == F_FULLCOLOR || col == F_REDUCED) 
                                                  strcpy(lowsuf,"ppm");
                     else if (col == F_GREYSCALE) strcpy(lowsuf,"pgm");
                     else if (col == F_BWDITHER)  strcpy(lowsuf,"pbm");
                     break;

    case F_XBM:      strcpy(lowsuf,"xbm");  break;
    case F_SUNRAS:   strcpy(lowsuf,"ras");  break;
    case F_PS:       strcpy(lowsuf,"ps");   break;

#ifdef HAVE_JPEG
    case F_JPEG:     strcpy(lowsuf,"jpg");  break;
#endif

#ifdef HAVE_TIFF
    case F_TIFF:     strcpy(lowsuf,"tif");  break;
#endif
    }

    if (allcaps) {  /* upper-caseify lowsuf */
      for (sp=lowsuf; *sp; sp++) 
	*sp = (islower(*sp)) ? toupper(*sp) : *sp;
    }

    /* one other case:  if the original suffix started with a single
       capital letter, make the new suffix start with a single cap */
    if (isupper(suffix[0])) lowsuf[0] = toupper(lowsuf[0]);

    strcpy(suffix, lowsuf);   /* tack onto filename */
    SetDirFName(filename);
  }

}
  

/***************************************************/
static int FNameCdable()
{
  /* returns '1' if filename is a directory, and goes there */
  
  char newpath[1024];
  struct stat st;
  int retval = 0;

  newpath[0] = '\0';   /* start out empty */

  if (ISPIPE(filename[0]) || strlen(filename)==0) return 0;

  if (filename[0] == '/' || filename[0] == '~') {  /* absolute path */
    strcpy(newpath, filename);
  }
  else {  /* not an absolute pathname */
    strcpy(newpath,path);
    strcat(newpath,filename);
  }

  if (newpath[0]=='~') {    /* handle globbing */
    Globify(newpath);
  }

#ifdef VMS
  /* Convert names of form "/device.dir" to "/device/000000.DIR"  */
  if ( strrchr ( newpath, '/' ) == newpath ) {
    strcpy ( strrchr ( newpath, '.' ), "/000000.DIR" );
  }
#endif

  if (stat(newpath, &st)==0) {
    int ftype;

    ftype = st.st_mode & S_IFMT;   /* mask off uninteresting bits */

    if (ftype == S_IFDIR) {
#ifdef VMS
      /*
       * remove the .DIR from the path so that false 000000 directories work
       */
      char *dirext;
      dirext = strrchr ( newpath, '/' );
      if ( dirext == NULL ) dirext = newpath; else dirext++;
      dirext = strstr ( dirext, "." );
      *dirext = '\0';
#endif

      if (chdir(newpath)==0) {
	LoadCurrentDirectory();
	retval = 1;
      }
    }
  }
  
  return retval;
}


/**************************************************************************/
int Globify(fname)
  char *fname;
{
  /* expands ~s in file names.  Returns the name inplace 'name'.
     returns 0 if okay, 1 if error occurred (user name not found) */

  struct passwd *entry;
  char *cp, *sp, *up, uname[64], tmp[MAXFNLEN+100];

#if defined(VMS) || defined(__GNUC__)
  return 1;
#else
  if (*fname != '~') return 0; /* doesn't start with a tilde, don't expand */

  /* look for the first '/' after the tilde */
  sp = index(fname,'/');
  if (sp == 0) {               /* no '/' after the tilde */
    sp = fname+strlen(fname);  /* sp = end of string */
  }

  /* uname equals the string between the ~ and the / */
  for (cp=fname+1,up=uname; cp<sp; *up++ = *cp++);
  *up='\0';

  if (*uname=='\0') { /* no name.  substitute ~ with $HOME */
    char *homedir, *getenv();
    homedir = getenv("HOME");  
    if (homedir == NULL) homedir = ".";
    strcpy(tmp,homedir);
    strcat(tmp,sp);
  }

  else {              /* get password entry for uname */
    entry = getpwnam(uname);
    if (entry==0) return 1;       /* name not found */
    strcpy(tmp,entry->pw_dir);
    strcat(tmp,sp);
    endpwent();
  }

  strcpy(fname,tmp);  /* return expanded file name */
  return 0;
#endif  /* !VMS && !GNUC */
}



/* the name of the file actually opened.  (the temp file if we are piping) */
static char outFName[256];  
static int  dopipe;


/***************************************/
FILE *OpenOutFile(filename)
     char *filename;
{
  /* opens file for output.  does various error handling bits.  Returns
     an open file pointer if success, NULL if failure */

  FILE *fp;
  struct stat st;

  if (!filename || filename[0] == '\0') return NULL;
  strcpy(outFName, filename);
  dopipe = 0;

  if (ISPIPE(filename[0])) {   /* do piping */
    /* make up some bogus temp file to put this in */
    sprintf(outFName, "%s/xvXXXXXX", tmpdir);
    mktemp(outFName);
    dopipe = 1;
  }


  /* see if file exists (ie, we're overwriting) */
  if (stat(outFName, &st)==0) {   /* stat succeeded, file must exist */
    static char *foo[] = { "\nOk", "\033Cancel" };
    char str[512];

    sprintf(str,"Overwrite existing file '%s'?", outFName);
    if (PopUp(str, foo, 2)) return NULL;
  }
    

  /* Open file */
  fp = fopen(outFName, WRITE_BINARY);
  if (!fp) {
    static char *foo[] = { "\nBummer!" };
    char  str[512];
    sprintf(str,"Can't write file '%s'\n\n  %s.",outFName,sys_errlist[errno]);
    PopUp(str, foo, 1);
    return NULL;
  }

  return fp;
}
  

/***************************************/
int CloseOutFile(fp, filename, failed)
     FILE *fp;
     char *filename;
     int   failed;
{
  /* close output file, and if piping, deal... Returns '0' if everything OK */

  if (failed) {    /* failure during format-specific output routine */
    static char *foo[] = { "\nBummer!" };
    char  str[512];
    sprintf(str,"Couldn't write file '%s'.", outFName);
    PopUp(str, foo, 1);
    unlink(outFName);   /* couldn't properly write file:  delete it */
    return 1;
  }

    
  if (fclose(fp) == EOF) {
    static char *foo[] = { "\nWeird!" };
    char  str[512];
    sprintf(str,"Can't close file '%s'\n\n  %s.",outFName,sys_errlist[errno]);
    PopUp(str, foo, 1);
    return 1;
  }

  SetISTR(ISTR_INFO,"Successfully wrote '%s'", outFName);
  
  if (dopipe) {
    char cmd[512], str[1024];
    sprintf(cmd, "cat %s |%s", outFName, filename+1);  /* lose pipe char */
    sprintf(str,"Doing command: '%s'", cmd);
    OpenAlert(str);
    if (system(cmd)) {
      static char *foo[] = { "\nThat Sucks!" };
      sprintf(str, "Unable to complete command:\n  %s", cmd);
      CloseAlert();
      PopUp(str, foo, 1);
      unlink(outFName);
      return 1;
    }
    else {
      CloseAlert();
      SetISTR(ISTR_INFO,"Successfully completed command.");
      unlink(outFName);
    }
  }
  return 0;
}

      


static byte rBW[2], gBW[2], bBW[2];

/***************************************/
byte *HandleBWandReduced(color, nc, rpp, gpp, bpp)
     int color, *nc;
     byte **rpp, **gpp, **bpp;
{
  /* given 'color' (the mode selected by colorRB), we may have to dither
     and/or use different colormaps.  Returns 'nc', rpp, gpp, bpp (the
     colormap to use).  Also, if the function returns non-NULL, it generated
     a new (dithered) image to use. */
     
  byte *bwpic = NULL;

  *nc = numcols;  *rpp = r;  *gpp = g;  *bpp = b;
  if (color==F_REDUCED && (ncols>0)) 
    { *rpp = rdisp;  *gpp = gdisp;  *bpp = bdisp; }

  /* if BW or reduced,ncols=0 generate a dithered image */
  if (color==F_BWDITHER || (ncols==0 && color==F_REDUCED) ) {
    /* generate a FSDithered 1-byte per pixel image */
    /* if we're saving as an FSDithered, or we're viewing as FSDithered
       and we're saving 'reduced' */

    byte *thepic;  int w,h;

    if (savenormCB.val) { thepic = cpic;  w = cWIDE;  h = cHIGH; }
                   else { thepic = epic;  w = eWIDE;  h = eHIGH; }

    bwpic = (byte *) malloc(w * h);
    if (!bwpic) FatalError("unable to malloc dithered picture (DoSave)");

    FSDither(thepic, w, h, bwpic);

    /* put a BW colormap */
    rBW[0] = (blkRGB>>16)&0xff;     rBW[1] = (whtRGB>>16)&0xff;
    gBW[0] = (blkRGB>>8)&0xff;      gBW[1] = (whtRGB>>8)&0xff;
    bBW[0] = blkRGB&0xff;           bBW[1] =  whtRGB&0xff;
    *rpp = rBW;  *gpp = gBW;  *bpp = bBW;
    *nc = 2;
  }

  return bwpic;
}



