/*
 *  xvbrowse.c  -  visual schnauzer routines
 * 
 *  includes:
 *      void CreateBrowse(char *, char *, char *, char *, char *);
 *      void OpenBrowse();
 *      void HideBrowseWindows();
 *      void UnHideBrowseWindows();
 *      void SetBrowseCursor(Cursor);
 *      void KillBrowseWindows();
 *      int  BrowseCheckEvent(evt, int *retval, int *done);
 *      int  BrowseDelWin(Window);
 *      void SetBrowStr(char *);
 *      void RegenBrowseIcons();
 *
 */

/* Copyright Notice
 * ================
 * Copyright 1989, 1990, 1991, 1992, 1993 by John Bradley
 * 
 * Permission to use, copy, and distribute XV in its entirety, for 
 * non-commercial purposes, is hereby granted without fee, provided that
 * this license information and copyright notice appear in all copies.
 * 
 * Note that distributing XV 'bundled' in with ANY product is considered
 * to be a 'commercial purpose'.
 *
 * Also note that any copies of XV that are distributed MUST be built
 * and/or configured to be in their 'unregistered copy' mode, so that it
 * is made obvious to the user that XV is shareware, and that they should
 * consider donating, or at least reading this License Info.
 * 
 * The software may be modified for your own purposes, but modified
 * versions may NOT be distributed without prior consent of the author.
 * 
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the author be held liable for any damages
 * arising from the use of this software.
 * 
 * If you would like to do something with XV that this copyright
 * prohibits (such as distributing it with a commercial product, 
 * using portions of the source in some other program, etc.), please
 * contact the author (preferably via email).  Arrangements can
 * probably be worked out.
 *
 * XV is shareware for PERSONAL USE only.  You may use XV for your own
 * amusement, and if you find it nifty, useful, generally cool, or of
 * some value to you, your non-deductable donation would be greatly
 * appreciated.  $25 is the suggested donation, though, of course,
 * larger donations are quite welcome.  Folks who donate $25 or more
 * can receive a Real Nice bound copy of the XV manual for no extra
 * charge.
 * 
 * Commercial, government, and institutional users MUST register their
 * copies of XV, for the exceedingly REASONABLE price of just $25 per
 * workstation/X terminal.  Site licenses are available for those who
 * wish to run XV on a large number of machines.  Contact the author
 * for more details.
 *
 * The author may be contacted via:
 *    US Mail:  John Bradley
 *              1053 Floyd Terrace
 *              Bryn Mawr, PA  19010
 *
 *    Phone:    (215) 898-8813
 *    EMail:    bradley@cis.upenn.edu
 */


#define NEEDSDIR
#include "xv.h"


/* load up built-in icons */
#include "bitmaps/br_file.xbm"
#include "bitmaps/br_dir.xbm"
#include "bitmaps/br_exe.xbm"
#include "bitmaps/br_chr.xbm"
#include "bitmaps/br_blk.xbm"
#include "bitmaps/br_sock.xbm"
#include "bitmaps/br_fifo.xbm"
#include "bitmaps/br_error.xbm"
#include "bitmaps/br_unknown.xbm"
#include "bitmaps/br_compress.xbm"

#include "bitmaps/br_gif.xbm"
#include "bitmaps/br_pm.xbm"
#include "bitmaps/br_pbm.xbm"
#include "bitmaps/br_xbm.xbm"
#include "bitmaps/br_sunras.xbm"
#include "bitmaps/br_bmp.xbm"
#include "bitmaps/br_utahrle.xbm"
#include "bitmaps/br_iris.xbm"
#include "bitmaps/br_pcx.xbm"
#include "bitmaps/br_jfif.xbm"
#include "bitmaps/br_tiff.xbm"
#include "bitmaps/br_pds.xbm"
#include "bitmaps/br_ps.xbm"


#include "bitmaps/fcurs.xbm"
#include "bitmaps/fccurs.xbm"
#include "bitmaps/fcursm.xbm"



/* FileTypes */
#define BF_HAVEIMG  -1   /* should use 'pimage' and 'ximage' fields */
#define BF_FILE      0
#define BF_DIR       1
#define BF_EXE       2
#define BF_CHR       3
#define BF_BLK       4
#define BF_SOCK      5
#define BF_FIFO      6
#define BF_ERROR     7
#define BF_UNKNOWN   8
#define BF_GIF       9
#define BF_PM       10
#define BF_PBM      11
#define BF_XBM      12
#define BF_SUNRAS   13
#define BF_BMP      14
#define BF_UTAHRLE  15
#define BF_IRIS     16
#define BF_PCX      17
#define BF_JFIF     18
#define BF_TIFF     19
#define BF_PDS      20
#define BF_COMPRESS 21
#define BF_PS       22
#define BF_MAX      23    /* # of built-in icons */

#define ISLOADABLE(ftyp) (ftyp!=BF_DIR  && ftyp!=BF_CHR && ftyp!=BF_BLK && \
			  ftyp!=BF_SOCK && ftyp!=BF_FIFO) 

#define DEF_BROWWIDE 615   /* default size of window */
#define DEF_BROWHIGH 356

#define SCROLLVERT  8      /* height of scroll region at top/bottom of iconw */
#define PAGEVERT    40     /* during rect drag, if further than this, page */

#define ICON_ONLY 2        /* if 'lit' field ==, invert icon only, not title */
#define TEMP_LIT  3        /* temporarily selected, normally off */
#define TEMP_LIT1 4        /* temporarily selected, normally on */

#define TOPMARGIN 30       /* from top of window to top of iconwindow */
#define BOTMARGIN 58       /* room for a row of buttons and a line of text */
#define LRMARGINS 5        /* left and right margins */

#define ISIZE_WIDE   80    /* maximum size of an icon */
#define ISIZE_HIGH   60

#define ISPACE_WIDE (ISIZE_WIDE+16)   /* icon spacing */
#define ISPACE_TOP  4                 /* dist btwn top of ISPACE and ISIZE */
#define ISPACE_TTOP 4                 /* dist btwn bot of icon and title */
#define ISPACE_HIGH (ISIZE_HIGH+ISPACE_TOP+ISPACE_TTOP+16+4)

#define DBLCLICKTIME 300  /* milliseconds */

/* button/menu indicies */
#define BR_DELETE   0
#define BR_MKDIR    1
#define BR_RENAME   2
#define BR_RESCAN   3
#define BR_NEWWIN   4
#define BR_UPDATE   5
#define BR_GENICON  6
#define BR_SELALL   7
#define BR_TEXTVIEW 8
#define BR_QUIT     9
#define BR_CLOSE    10
#define BR_NBUTTS   11

#define BUTTW 80
#define BUTTH 24

static char *cmdMList[] = { "Delete file(s)",
			    "New directory",
			    "Rename file",
			    "reScan directory",
			    "open new Window",
			    "Update icons",
			    "Generate icon(s)",
			    "select All files",
			    "Text view",
			    "Quit xv",
			    "Close window"
			    };


#define MAXDEEP 30     /* maximum directory depth */

#define BOGUSPATH "NO SUCH PATH"

/* convex is a bit weird.  Or, at least, has been in the past... */
#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


typedef struct { char   *name;    /* name of file */
		 char   *imginfo; /* info on the real image */
                 int     ftype;   /* BF_EXE, BF_DIR, BF_FILE, etc... */
		 byte   *pimage;  /* normal, 8-bit-per image */
		 XImage *ximage;  /* X version of pimage */
		 int     w,h;     /* size of icon */
		 int     lit;     /* true if 'selected' */
	       } BFIL;

/* data needed per schnauzer window */
typedef struct {  Window win, iconW;
		  int    vis, wasvis;

		  int    wide, high;
		  int    iwWide, iwHigh;
		  int    numWide, numHigh, visHigh;

		  SCRL   scrl;
		  BUTT   but[BR_NBUTTS];
		  MBUTT  dirMB, cmdMB;
		  char   dispstr[256];
		  int    numbutshown;

		  int    numlit;
		  BFIL  *bfList;
		  int    bfLen;
		  int    lastIconClicked;
		  unsigned long lastClickTime;

		  int    ndirs;
		  char  *mblist[MAXDEEP];
		  char   path[MAXPATHLEN+2];   /* '/' terminated */
		} BROWINFO;


static Cursor   movecurs, copycurs;
static BROWINFO binfo[MAXBRWIN];
static Pixmap   bfIcons[BF_MAX];
static int      hasBeenSized = 0;
static int      haveWindows  = 0;

static unsigned long browfg, browbg, browhi, browlo;

#ifdef __STDC__
static void closeBrowse     (BROWINFO *);
static int  brChkEvent      (BROWINFO *, XEvent *);
static void resizeBrowse    (BROWINFO *, int, int);
static void setBrowStr      (BROWINFO *, char *);
static void doCmd           (BROWINFO *, int);
static void drawBrow        (BROWINFO *);
static void drawNumfiles    (BROWINFO *);
static void eraseNumfiles   (BROWINFO *, int);
static void drawBrowStr     (BROWINFO *);
static void changedNumLit   (BROWINFO *, int, int);
static void setSelInfoStr   (BROWINFO *, int);
static void exposeIconWin   (BROWINFO *, int, int, int, int);
static void drawIconWin     (int, SCRL *);
static void eraseIcon       (BROWINFO *, int);
static void eraseIconTitle  (BROWINFO *, int);
static void drawIcon        (BROWINFO *, int);
static void makeIconVisible (BROWINFO *, int);
static void clickBrow       (BROWINFO *, int, int);
static int  clickIconWin    (BROWINFO *, int, int, unsigned long, int);
static void doubleClick     (BROWINFO *, int);
static int  mouseInWhichIcon(BROWINFO *, int, int);
static void invertSelRect   (BROWINFO *, int, int, int, int);
static void keyIconWin      (BROWINFO *, XKeyEvent *);
static void browKey         (BROWINFO *, int);
static void browAlpha       (BROWINFO *, int);
static void changedBrDirMB  (BROWINFO *, int);
static int  cdBrow          (BROWINFO *);
static void scanDir         (BROWINFO *);
static void copyDirInfo     (BROWINFO *, BROWINFO *);
static void endScan         (BROWINFO *, int);
static void scanFile        (BROWINFO *, BFIL *, char *);
static void sortBFList      (BROWINFO *);
static int  bfnamCmp        (BFIL *, BFIL *);
static void rescanDir       (BROWINFO *);
static int  namcmp          (char **, char **);
static void freeBfList      (BROWINFO *br);
static char **getDirEntries (char *, int *);
static void computeScrlVals (BROWINFO *, int *, int *);
static void genSelectedIcons(BROWINFO *);
static void genIcon         (BROWINFO *, BFIL *);
static void loadThumbFile   (BROWINFO *, BFIL *);
static void writeThumbFile  (BROWINFO *, BFIL *, byte *, int, int, char *);
static void updateIcons     (BROWINFO *);

static void drawTemp        (BROWINFO *, int, int);
static void clearTemp       (BROWINFO *);

static void doTextCmd       (BROWINFO *);
static void doRenameCmd     (BROWINFO *);
static void doMkdirCmd      (BROWINFO *);

static void doDeleteCmd     (BROWINFO *);
static void rm_file         (BROWINFO *, char *);
static void rm_dir          (BROWINFO *, char *);
static void rm_dir1         (BROWINFO *);

static void dragFiles       (BROWINFO *, BROWINFO *, char *, char *, char *, 
			     char **, int, int);
static int  moveFile        (char *, char *);
static int  copyFile        (char *, char *);
static void cp              (void);
static void cp_dir          (void);
static void cp_file         (struct stat *, int);
static void cp_special      (struct stat *, int);
static void cp_fifo         (struct stat *, int);

static int  stat2bf         (int);
#else

static void closeBrowse();
static int  brChkEvent();
static void resizeBrowse(), setBrowStr();
static void doCmd();
static void drawBrow(), drawNumfiles(), eraseNumfiles();
static void drawBrowStr(), changedNumLit(), setSelInfoStr();
static void exposeIconWin(), drawIconWin(), eraseIcon(), eraseIconTitle();
static void drawIcon();
static void makeIconVisible(), clickBrow();
static void doubleClick();
static int  clickIconWin(), mouseInWhichIcon(), cdBrow(); 
static void keyIconWin(), browKey(), browAlpha();
static void invertSelRect(), changedBrDirMB();
static void copyDirInfo(), scanDir(), scanFile(), endScan();
static void sortBFList(), computeScrlVals();
static int  bfnamCmp(), namcmp();
static void rescanDir(), freeBfList();
static char **getDirEntries();
static void genSelectedIcons(), genIcon();
static void loadThumbFile(), writeThumbFile();
static void updateIcons();

static void drawTemp(), clearTemp(), doTextCmd(), doRenameCmd(), doMkdirCmd();

static void doDeleteCmd(), rm_file(), rm_dir(), rm_dir1();
static void dragFiles();
static int  moveFile(), copyFile();
static void cp(), cp_dir(), cp_file(), cp_special(), cp_fifo();
static int  stat2bf();
#endif



/***************************************************************/
void CreateBrowse(geom, fgstr, bgstr, histr, lostr)
     char *geom, *fgstr, *bgstr, *histr, *lostr;
{
  int                   i;
  XSizeHints            hints;
  XSetWindowAttributes  xswa;
  BROWINFO             *br;
  XColor                ecdef, cursfg, cursbg;
  Pixmap                mcpix, ccpix, fcmpix;
  int                   gx, gy, gw, gh, gset, gx1, gy1;
  char                  wgeom[64];


  /* map color spec strings into browCmap, if we're in browPerfect mode */
  if (browPerfect && browCmap) {
    browfg = 0;  browbg = 255;     /* black text on white bg, by default */
    if (fgstr && XParseColor(theDisp, browCmap, fgstr, &ecdef)) {
      browfg = browcols[((ecdef.red   >>  8) & 0xe0) |
			((ecdef.green >> 11) & 0x1c) |
			((ecdef.blue  >> 14) & 0x03)];
    }

    if (bgstr && XParseColor(theDisp, browCmap, bgstr, &ecdef)) {
      browbg = browcols[((ecdef.red   >>  8) & 0xe0) |
			((ecdef.green >> 11) & 0x1c) |
			((ecdef.blue  >> 14) & 0x03)];
    }

    browhi = browbg;  browlo = browfg;
    if (histr && XParseColor(theDisp, browCmap, histr, &ecdef)) {
      browhi = browcols[((ecdef.red   >>  8) & 0xe0) |
			((ecdef.green >> 11) & 0x1c) |
			((ecdef.blue  >> 14) & 0x03)];
    }

    if (lostr && XParseColor(theDisp, browCmap, lostr, &ecdef)) {
      browlo = browcols[((ecdef.red   >>  8) & 0xe0) |
			((ecdef.green >> 11) & 0x1c) |
			((ecdef.blue  >> 14) & 0x03)];
    }
  }
  else {
    browfg = infofg;  browbg = infobg;  browhi = hicol;  browlo = locol;
  }



  gset = XParseGeometry(geom, &gx, &gy, &gw, &gh);

  /* creates *all* schnauzer windows at once */

  for (i=0; i<MAXBRWIN; i++) binfo[i].win = (Window) NULL;

  for (i=0; i<MAXBRWIN; i++) {
    char wname[64];

    /* create a slightly offset geometry, so the windows stack nicely */
    if ((gset & XValue) && (gset & YValue)) {
      if (gset & XNegative) gx1 = gx - i * 20;
                       else gx1 = gx + i * 20;

      if (gset & YNegative) gy1 = gy - i * 20;
	               else gy1 = gy + i * 20;

      if ((gset & WidthValue) && (gset & HeightValue)) 
	sprintf(wgeom, "%dx%d%s%d%s%d", gw, gh, 
		(gset & XNegative) ? "-" : "+", abs(gx1),
		(gset & YNegative) ? "-" : "+", abs(gy1));
      else
	sprintf(wgeom, "%s%d%s%d",
		(gset & XNegative) ? "-" : "+", abs(gx1),
		(gset & YNegative) ? "-" : "+", abs(gy1));
    }
    else strcpy(wgeom, geom);

    br = &binfo[i];

    if (i) sprintf(wname, "xv visual schnauzer (%d)", i);
      else sprintf(wname, "xv visual schnauzer");

    br->win = CreateWindow(wname, "XVschnauze", wgeom,
			   DEF_BROWWIDE, DEF_BROWHIGH, browfg, browbg, 1);
    if (!br->win) FatalError("can't create schnauzer window!");

    haveWindows = 1;
    br->vis = br->wasvis = 0;

    if (browPerfect && browCmap) {
      xswa.colormap = browCmap;
      XChangeWindowAttributes(theDisp, br->win, CWColormap, &xswa);
    }

    if (ctrlColor) XSetWindowBackground(theDisp, br->win, browlo);
              else XSetWindowBackgroundPixmap(theDisp, br->win, grayTile);

    /* note: everything is sized and positioned in ResizeBrowse() */

    br->iconW = XCreateSimpleWindow(theDisp, br->win, 1,1, 100,100, 
				     1,browfg,browbg);
    if (!br->iconW) FatalError("can't create schnauzer icon window!");

    SCCreate(&(br->scrl), br->win, 0,0, 1,100, 0,0,0,0, 
	     browfg, browbg, browhi, browlo, drawIconWin);


    if (XGetNormalHints(theDisp, br->win, &hints)) {
      hints.min_width  = 325 + 96;
       hints.min_height = 180;
      hints.flags |= PMinSize;
      XSetNormalHints(theDisp, br->win, &hints);
    }

#ifdef BACKING_STORE
    xswa.backing_store = WhenMapped;
    XChangeWindowAttributes(theDisp, br->iconW, CWBackingStore, &xswa);
#endif

    XSelectInput(theDisp, br->iconW, ExposureMask | ButtonPressMask);



    BTCreate(&(br->but[BR_DELETE]), br->win, 0,0,BUTTW,BUTTH,
	     "Delete",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_MKDIR]), br->win, 0,0,BUTTW,BUTTH,
	     "New Dir",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_RENAME]), br->win, 0,0,BUTTW,BUTTH,
	     "Rename",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_RESCAN]), br->win, 0,0,BUTTW,BUTTH,
	     "ReScan",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_NEWWIN]), br->win, 0,0,BUTTW,BUTTH,
	     "Open Win",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_UPDATE]), br->win, 0,0,BUTTW,BUTTH,
	     "Update",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_GENICON]),br->win, 0,0,BUTTW,BUTTH,
	     "GenIcon",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_SELALL]), br->win, 0,0,BUTTW,BUTTH,
	     "Select All",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_TEXTVIEW]), br->win, 0,0,BUTTW,BUTTH,
	     "Text view",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_QUIT]),  br->win, 0,0,BUTTW,BUTTH,
	     "Quit xv",browfg,browbg,browhi,browlo);
    BTCreate(&(br->but[BR_CLOSE]),  br->win, 0,0,BUTTW,BUTTH,
	     "Close",browfg,browbg,browhi,browlo);

    XMapSubwindows(theDisp, br->win);

    MBCreate(&(br->dirMB), br->win, 0,0,100,19, NULL,NULL,0,
	     browfg,browbg,browhi,browlo);

    MBCreate(&(br->cmdMB), br->win, 0,0,160,19, "Misc. Commands",
	     cmdMList, BR_NBUTTS, browfg,browbg,browhi,browlo);

    br->numbutshown  = 0;
    br->numlit       = 0;
    br->bfList       = NULL;
    br->bfLen        = 0;
    br->dispstr[0]   = '\0';
    br->ndirs        = 0;
    sprintf(br->path, BOGUSPATH);

    br->lastIconClicked = -1;
    br->lastClickTime = 0;
  }


  /* create built-in icon pixmaps */
  bfIcons[BF_FILE] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_file_bits, br_file_width, br_file_height, 1, 0, 1);

  bfIcons[BF_DIR] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_dir_bits, br_dir_width, br_dir_height, 1, 0, 1);

  bfIcons[BF_EXE] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_exe_bits, br_exe_width, br_exe_height, 1, 0, 1);

  bfIcons[BF_CHR] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_chr_bits, br_chr_width, br_chr_height, 1, 0, 1);

  bfIcons[BF_BLK] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_blk_bits, br_blk_width, br_blk_height, 1, 0, 1);

  bfIcons[BF_SOCK] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_sock_bits, br_sock_width, br_sock_height, 1, 0, 1);

  bfIcons[BF_FIFO] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_fifo_bits, br_fifo_width, br_fifo_height, 1, 0, 1);

  bfIcons[BF_ERROR] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_error_bits, br_error_width, br_error_height,1,0,1);

  bfIcons[BF_UNKNOWN] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_unknown_bits,br_unknown_width,br_unknown_height,
						    1,0,1);

  bfIcons[BF_GIF] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_gif_bits,br_gif_width,br_gif_height,
						    1,0,1);

  bfIcons[BF_PM] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_pm_bits,br_pm_width,br_pm_height,
						    1,0,1);

  bfIcons[BF_PBM] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_pbm_bits,br_pbm_width,br_pbm_height,
						    1,0,1);

  bfIcons[BF_XBM] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_xbm_bits,br_xbm_width,br_xbm_height,
						    1,0,1);

  bfIcons[BF_SUNRAS] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_sunras_bits,br_sunras_width,br_sunras_height,
						    1,0,1);

  bfIcons[BF_BMP] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_bmp_bits,br_bmp_width,br_bmp_height,
						    1,0,1);

  bfIcons[BF_UTAHRLE] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_utahrle_bits,br_utahrle_width,br_utahrle_height,
						    1,0,1);

  bfIcons[BF_IRIS] = XCreatePixmapFromBitmapData(theDisp, br->win, 
                        br_iris_bits,br_iris_width,br_iris_height,
						    1,0,1);

  bfIcons[BF_PCX] = XCreatePixmapFromBitmapData(theDisp, br->win, 
                        br_pcx_bits,br_pcx_width,br_pcx_height,
						    1,0,1);

  bfIcons[BF_JFIF] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_jfif_bits,br_jfif_width,br_jfif_height,
						    1,0,1);

  bfIcons[BF_TIFF] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_tiff_bits,br_tiff_width,br_tiff_height,
						    1,0,1);

  bfIcons[BF_PDS] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_pds_bits,br_pds_width,br_pds_height,
						    1,0,1);

  bfIcons[BF_COMPRESS] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_compress_bits,br_compress_width,
			br_compress_height, 1,0,1);

  bfIcons[BF_PS] = XCreatePixmapFromBitmapData(theDisp, br->win, 
			br_ps_bits,br_ps_width,
			br_ps_height, 1,0,1);


  /* check that they all got built */
  for (i=0; i<BF_MAX && bfIcons[i]; i++);
  if (i<BF_MAX) 
    FatalError("unable to create all built-in icons for schnauzer");

  for (i=0; i<MAXBRWIN; i++) {
    resizeBrowse(&binfo[i], DEF_BROWWIDE, DEF_BROWHIGH);

    XSelectInput(theDisp, binfo[i].win, ExposureMask | ButtonPressMask | 
		 KeyPressMask | StructureNotifyMask);
  }


  /* create movecurs and copycurs cursors */
  mcpix = XCreatePixmapFromBitmapData(theDisp, rootW, filecurs_bits,
	     filecurs_width, filecurs_height, 1, 0, 1);
  ccpix = XCreatePixmapFromBitmapData(theDisp, rootW, fileccurs_bits,
	     fileccurs_width, fileccurs_height, 1, 0, 1);
  fcmpix = XCreatePixmapFromBitmapData(theDisp, rootW, filecursm_bits,
	     filecursm_width, filecursm_height, 1, 0, 1);

  if (mcpix && ccpix && fcmpix) {
    cursfg.red = cursfg.green = cursfg.blue = 0;
    cursbg.red = cursbg.green = cursbg.blue = 0xffff;

    movecurs = XCreatePixmapCursor(theDisp,mcpix,fcmpix,&cursfg,&cursbg,13,13);
    copycurs = XCreatePixmapCursor(theDisp,ccpix,fcmpix,&cursfg,&cursbg,13,13);
    if (!movecurs || !copycurs) 
      FatalError("unable to create schnauzer cursors...");
  }

  if (mcpix)  XFreePixmap(theDisp, mcpix);
  if (ccpix)  XFreePixmap(theDisp, ccpix);
  if (fcmpix) XFreePixmap(theDisp, fcmpix);
    

  hasBeenSized = 1;  /* we can now start looking at browse events */
}


/***************************************************************/
void OpenBrowse()
{
  /* opens up a single browser window */
  int i,j;
  BROWINFO *br;
  char     path[MAXPATHLEN+1];

  /* find next browser to be opened */
  for (i=0; i<MAXBRWIN; i++) {
    br = &binfo[i];
    if (!br->vis) break;
  }
  if (i==MAXBRWIN) return;  /* full up: shouldn't happen */

j=i;
  anyBrowUp = 1;
  XMapRaised(theDisp, br->win);
  br->vis = 1;

  freeBfList(br);

  /* see if some browser is pointing to the same path as CWD.  If so,
     copy the info, rather than reading the directory */
  GETWD(path);
  if (path[strlen(path)-1] != '/') strcat(path,"/");   /* add trailing '/' */

  for (i=0; i<MAXBRWIN; i++) {
    if (strcmp(binfo[i].path,path)==0) break;
  }

  if (i<MAXBRWIN && &binfo[i] != br) copyDirInfo(&binfo[i], br);
  else scanDir(br);

  SCSetVal(&(br->scrl), 0);


  /* see if that was the last one */
  for (i=0; i<MAXBRWIN; i++) {
    if (!binfo[i].vis) break;
  }
  if (i==MAXBRWIN) {        /* can't open any more */
    BTSetActive(&but[BVBROWSE], 0);
    for (i=0; i<MAXBRWIN; i++) {
      BTSetActive(&(binfo[i].but[BR_NEWWIN]), 0);
      binfo[i].cmdMB.dim[BR_NEWWIN] = 1;
    }
  }


  changedNumLit(br, -1, 0);
}


/***************************************************************/
static void closeBrowse(br)
     BROWINFO *br;
{
  int i;

  WaitCursor();

  /* closes a specified browse window */
  XUnmapWindow(theDisp, br->win);
  br->vis = 0;

  for (i=0; i<MAXBRWIN; i++) {
    if (binfo[i].vis) break;
  }
  if (i==MAXBRWIN) anyBrowUp = 0;

  /* free all info for this browse window */
  freeBfList(br);
  sprintf(br->path, BOGUSPATH);
  
  /* turn on 'open new window' command doodads */
  BTSetActive(&but[BVBROWSE], 1);
  for (i=0; i<MAXBRWIN; i++) {
    BTSetActive(&(binfo[i].but[BR_NEWWIN]), 1);
    binfo[i].cmdMB.dim[BR_NEWWIN] = 0;
  }

  SetCursors(-1);
}


/***************************************************************/
void HideBrowseWindows()
{
  int i;

  for (i=0; i<MAXBRWIN; i++) {
    if (binfo[i].vis) {
      XUnmapWindow(theDisp, binfo[i].win);
      binfo[i].wasvis = 1;
      binfo[i].vis = 0;
    }
  }
}


/***************************************************************/
void UnHideBrowseWindows()
{
  int i;

  for (i=0; i<MAXBRWIN; i++) {
    if (binfo[i].wasvis) {
      XMapRaised(theDisp, binfo[i].win);
      binfo[i].wasvis = 0;
      binfo[i].vis = 0;
    }
  }
}


/***************************************************************/
void SetBrowseCursor(c)
     Cursor c;
{
  int i;

  for (i=0; i<MAXBRWIN; i++) {
    if (haveWindows && binfo[i].win) XDefineCursor(theDisp, binfo[i].win, c);
  }
}


/***************************************************************/
void KillBrowseWindows()
{
  int i;

  for (i=0; i<MAXBRWIN; i++) {
    if (haveWindows && binfo[i].win) XDestroyWindow(theDisp, binfo[i].win);
  }
}


static int *event_retP, *event_doneP;

/***************************************************************/
int BrowseCheckEvent(xev, retP, doneP)
     XEvent *xev;
     int *retP, *doneP;
{
  int i;

  event_retP  = retP;     /* so don't have to pass these all over the place */
  event_doneP = doneP;

  for (i=0; i<MAXBRWIN; i++) {
    if (brChkEvent(&binfo[i], xev)) break;
  }

  if (i<MAXBRWIN) return 1;
  return 0;
}


/***************************************************************/
static int brChkEvent(br, xev)
     BROWINFO *br;
     XEvent *xev;
{
  /* checks event to see if it's a browse-window related thing.  If it
     is, it eats the event and returns '1', otherwise '0'. */

  int    i, rv;
  char buf[1024];

  rv = 1;

  if (!hasBeenSized) return 0;  /* ignore evrythng until we get 1st Resize */

  if (xev->type == Expose) {
    int x,y,w,h;
    XExposeEvent *e = (XExposeEvent *) xev;
    x = e->x;  y = e->y;  w = e->width;  h = e->height;

    /* throw away excess redraws for 'dumb' windows */
    if (e->count > 0 && (e->window == br->scrl.win)) {}

    else if (e->window == br->scrl.win) SCRedraw(&(br->scrl));

    else if (e->window == br->win || e->window == br->iconW) { /* smart wins */
      /* group individual expose rects into a single expose region */
      int           count;
      Region        reg;
      XRectangle    rect;
      XEvent        evt;

      XSync(theDisp, False);

      xvbcopy((char *) e, (char *) &evt, sizeof(XEvent));
      reg = XCreateRegion();
      count = 0;
      do {
	rect.x      = evt.xexpose.x;
	rect.y      = evt.xexpose.y;
	rect.width  = evt.xexpose.width;
	rect.height = evt.xexpose.height;

	XUnionRectWithRegion(&rect, reg, reg);
	count++;
      } while (XCheckWindowEvent(theDisp, evt.xexpose.window,
				 ExposureMask, &evt));

      XClipBox(reg, &rect);  /* bounding box of region */
      XSetRegion(theDisp, theGC, reg);

      if (DEBUG) {
	fprintf(stderr,"win = %lx, br->win = %lx, iconW = %lx\n",
		e->window, br->win, br->iconW);
	fprintf(stderr,"grouped %d expose events into %d,%d %dx%d rect\n",
		count, rect.x, rect.y, rect.width, rect.height);
      }
      
      if      (e->window == br->win)   drawBrow(br);

      else if (e->window == br->iconW)
	exposeIconWin(br, rect.x, rect.y, rect.width, rect.height);

      XSetClipMask(theDisp, theGC, None);
      XDestroyRegion(reg);
    }

    else rv = 0;
  }


  else if (xev->type == ButtonPress) {
    XButtonEvent *e = (XButtonEvent *) xev;
    int i,x,y;
    x = e->x;  y = e->y;

    if (e->button == Button1) {
      if      (e->window == br->win)      clickBrow(br,x,y);
      else if (e->window == br->scrl.win) SCTrack(&(br->scrl),x,y);
      else if (e->window == br->iconW) {
	i = clickIconWin(br, x,y,(unsigned long) e->time, 
			 (e->state&ControlMask) || (e->state&ShiftMask));

      }
      else rv = 0;
    }
    else rv = 0;
  }


  else if (xev->type == KeyPress) {
    XKeyEvent *e = (XKeyEvent *) xev;
    if (e->window == br->win) keyIconWin(br, e);
    else rv = 0;
  }


  else if (xev->type == ConfigureNotify) {
    XConfigureEvent *e = (XConfigureEvent *) xev;

    if (e->window == br->win) {
      if (DEBUG) fprintf(stderr,"browW got a configure event (%dx%d)\n",
			 e->width, e->height);

      if (br->wide != e->width || br->high != e->height) {
	if (DEBUG) fprintf(stderr,"Forcing a redraw!  (from configure)\n");
	XClearArea(theDisp, br->win, 0, 0, e->width, e->height, True);
	resizeBrowse(br, e->width, e->height);
      }
    }
    else rv = 0;
  }

  else rv = 0;

  return rv;
}


/***************************************************************/
int BrowseDelWin(win)
     Window win;
{
  /* got a delete window request.  see if the window is a browser window,
     and close accordingly.  Return 1 if event was eaten */

  int i;

  for (i=0; i<MAXBRWIN; i++) {
    if (binfo[i].win == win) {
      closeBrowse(&binfo[i]);
      return 1;
    }
  }

  return 0;
}


/***************************************************************/
static void resizeBrowse(br,w,h)
     BROWINFO *br;
     int w,h;
{
  XSizeHints hints;
  int        i, minv, maxv, curv, page, maxh;

  if (br->wide == w && br->high == h) return;  /* no change in size */

  if (XGetNormalHints(theDisp, br->win, &hints)) {
    hints.width  = w;
    hints.height = h;
    hints.flags |= USSize;
    XSetNormalHints(theDisp, br->win, &hints);
  }

  br->wide = w;  br->high = h;
  br->iwWide = br->wide - (2*LRMARGINS) - (br->scrl.tsize+1) - 2;
  maxh = br->high - (TOPMARGIN + BOTMARGIN);

  br->iwHigh = (maxh / ISPACE_HIGH) * ISPACE_HIGH;
  if (br->iwHigh < ISPACE_HIGH) br->iwHigh = ISPACE_HIGH;

  XMoveResizeWindow(theDisp, br->iconW, LRMARGINS, TOPMARGIN, 
		    br->iwWide, br->iwHigh);


  /* move the buttons around */
  br->numbutshown = (br->wide / (BUTTW + 5));
  RANGE(br->numbutshown, 1, BR_NBUTTS);
  br->numbutshown--;

  for (i=0; i<BR_NBUTTS; i++) {
    /* 'close' always goes on right-most edge */

    if (i<br->numbutshown) 
      br->but[i].x = br->wide - (1+br->numbutshown-i) * (BUTTW+5);
    else if (i==BR_CLOSE)
      br->but[i].x = br->wide - (BUTTW+5);
    else 
      br->but[i].x = br->wide + 10;    /* offscreen */

    br->but[i].y = br->high - BUTTH - 5;
  }

  br->dirMB.x = br->wide/2 - br->dirMB.w/2;
  br->dirMB.x = br->dirMB.x - br->cmdMB.w/2 + (StringWidth("123 files") + 6)/2;

  br->dirMB.y = 5;

  br->cmdMB.x = br->wide - LRMARGINS - br->cmdMB.w - 2;
  br->cmdMB.y = 5;

  br->numWide = br->iwWide / ISPACE_WIDE;
  br->visHigh = br->iwHigh / ISPACE_HIGH;

  /* compute minv,maxv,curv,page values based on new current size */
  computeScrlVals(br, &maxv, &page);
  if (br->scrl.val>maxv) br->scrl.val = maxv;
  
  SCChange(&br->scrl, LRMARGINS+br->iwWide+1, TOPMARGIN, 
	   1, br->iwHigh, 0, maxv, br->scrl.val, page);
}



/***************************************************************/
void SetBrowStr(str)
     char *str;
{
  /* put string in *all* browse windows */
  int i;

  for (i=0; i<MAXBRWIN; i++) 
    setBrowStr(&binfo[i], str);
}


/***************************************************************/
static void setBrowStr(br, str)
     BROWINFO *br;
     char *str;
{
  strncpy(br->dispstr, str, 256);
  drawBrowStr(br);
  XFlush(theDisp);
}


/***************************************************************/
void RegenBrowseIcons()
{
  /* called whenever colormaps have changed, and icons need to be rebuilt */

  int i, j, iconcount;
  BFIL     *bf;
  BROWINFO *br;

  for (j=0; j<MAXBRWIN; j++) {
    br = &binfo[j];

    iconcount = 0;

    for (i=0; i<br->bfLen; i++) {
      bf = &(br->bfList[i]);

      drawTemp(br, i, br->bfLen);

      if (bf->pimage && bf->ftype == BF_HAVEIMG) {
	xvDestroyImage(bf->ximage);
	bf->ximage = Pic8ToXImage(bf->pimage, bf->w, bf->h,
				  browcols, browR, browG, browB);
	iconcount++;
      }

      if ((i         && (i        %100)==0) ||
	  (iconcount && (iconcount% 20)==0)) {  /* mention progress */

	char tmp[64];

	sprintf(tmp, "Re-coloring icons:  processed %d out of %d...", 
		i+1, br->bfLen);
	setBrowStr(br, tmp);
      }
    }

    clearTemp(br);
    setBrowStr(br, "");
    drawIconWin(0, &(br->scrl));  /* redraw new icons */
  }
}


/***************************************************************/
void BRDeletedFile(name)
     char *name;
{
  /* called when file 'name' has been deleted.  If any of the browsers
     were showing the directory that the file was in, does a rescan() */
  
  int  i;
  char buf[MAXPATHLEN + 2], *tmp;

  strcpy(buf, name);
  tmp = BaseName(buf);
  *tmp = '\0';     /* truncate after last '/' */
  
  for (i=0; i<MAXBRWIN; i++) {
    if (strcmp(binfo[i].path, buf)==0) rescanDir(&binfo[i]);
  }
}


/***************************************************************/
void BRCreatedFile(name)
     char *name;
{
  BRDeletedFile(name);
}


/**************************************************************/
/***                    INTERNAL FUNCTIONS                  ***/
/**************************************************************/


/***************************************************************/
static void doCmd(br, cmd)
     BROWINFO *br;
     int cmd;
{
  switch (cmd) {
  case BR_DELETE:  doDeleteCmd(br);
                   break;

  case BR_MKDIR:   doMkdirCmd(br);
                   break;

  case BR_RENAME:  doRenameCmd(br);
                   break;

  case BR_RESCAN:  rescanDir(br);
                   break;

  case BR_NEWWIN:  cdBrow(br);     /* try to open current dir */
                   OpenBrowse();
                   break;

  case BR_UPDATE:  rescanDir(br);
                   updateIcons(br);
                   break;

  case BR_GENICON: genSelectedIcons(br);  break;

  case BR_SELALL:  {
                     int i;  char buf[128];

		     for (i=0; i<br->bfLen; i++) 
		       br->bfList[i].lit = 1;
		     br->numlit = br->bfLen;

		     /* EXCEPT for parent directory */
		     if (br->bfLen>0 && strcmp(br->bfList[0].name,"..")==0) {
		       br->numlit--;  br->bfList[0].lit = 0;
		     }

		     changedNumLit(br, -1, 0);
		     drawIconWin(0, &(br->scrl));
		   }
                   break;

  case BR_TEXTVIEW: doTextCmd(br);       break;
 
  case BR_QUIT:     Quit(0);             break;

  case BR_CLOSE:    closeBrowse(br);     break;
  }
}



/***************************************************************/
static void drawBrow(br)
     BROWINFO *br;
{
  int i;

  drawNumfiles(br);
  drawBrowStr(br);

  for (i=0; i<BR_NBUTTS; i++) {
    if (i<br->numbutshown || i == BR_CLOSE) BTRedraw(&(br->but[i]));
  }

  MBRedraw(&(br->dirMB));
  MBRedraw(&(br->cmdMB));
}


/***************************************************************/
static void drawNumfiles(br)
     BROWINFO *br;
{
  char foo[40];
  int  y;

  y = br->dirMB.y;

  if (br->bfLen != 1) sprintf(foo, "%d files", br->bfLen);
  else strcpy(foo, "1 file");
    
  XSetForeground(theDisp, theGC, browbg);
  XFillRectangle(theDisp,br->win, theGC, 5+1,y+1,StringWidth(foo)+6,
		 br->dirMB.h-1);

  XSetForeground(theDisp,theGC,browfg);
  XDrawRectangle(theDisp,br->win, theGC, 5,y,StringWidth(foo)+7,
		 br->dirMB.h);

  Draw3dRect(br->win, 5+1,y+1,StringWidth(foo)+5,br->dirMB.h-2, R3D_IN, 2,
	     browhi, browlo, browbg);

  XSetForeground(theDisp,theGC,browfg);
  XDrawString(theDisp, br->win, theGC, 5+3, 
	      CENTERY(mfinfo, y + br->dirMB.h/2), foo, strlen(foo));
}


/***************************************************************/
static void eraseNumfiles(br,nf)
     BROWINFO *br;
     int nf;
{
  char foo[40];

  if (nf != 1) sprintf(foo,"%d files",nf);
  else strcpy(foo,"1 file");
    
  XClearArea(theDisp,br->win, 5, br->dirMB.y,
	     StringWidth(foo)+8, br->dirMB.h+1, False);
}


/***************************************************************/
static void drawBrowStr(br)
     BROWINFO *br;
{
  int y;

  y = br->high - (BUTTH+10) - (CHIGH + 6);

  XSetForeground(theDisp, theGC, browbg);
  XFillRectangle(theDisp, br->win, theGC, 0, y+3, br->wide, CHIGH+1);

  XSetForeground(theDisp, theGC, browfg);
  XDrawLine(theDisp, br->win, theGC, 0, y,  br->wide, y);
  XDrawLine(theDisp, br->win, theGC, 0, y+CHIGH+4, br->wide, y+CHIGH+4);

  XSetForeground(theDisp, theGC, (ctrlColor) ? browlo : browbg);
  XDrawLine(theDisp, br->win, theGC, 0, y+1,       br->wide, y+1);
  XDrawLine(theDisp, br->win, theGC, 0, y+CHIGH+5, br->wide, y+CHIGH+5);

  XSetForeground(theDisp, theGC, (ctrlColor) ? browhi : browfg);
  XDrawLine(theDisp, br->win, theGC, 0, y+2, br->wide, y+2);
  XDrawLine(theDisp, br->win, theGC, 0, y+CHIGH+6, br->wide, y+CHIGH+6);

  XSetForeground(theDisp, theGC, browfg);
  XDrawString(theDisp, br->win, theGC, 5, y+ASCENT+3, 
	      br->dispstr, strlen(br->dispstr));
}


/***************************************************************/
static void changedNumLit(br, sel, nostr)
     BROWINFO *br;
     int       sel, nostr;
{
  int i, allowtext;

  if (!nostr) setSelInfoStr(br, sel);
  BTSetActive(&br->but[BR_DELETE],  br->numlit>0);
  br->cmdMB.dim[BR_DELETE] = !(br->numlit>0);

  BTSetActive(&br->but[BR_RENAME],  br->numlit==1);
  br->cmdMB.dim[BR_RENAME] = !(br->numlit==1);

  BTSetActive(&br->but[BR_GENICON], br->numlit>0);
  br->cmdMB.dim[BR_GENICON] = !(br->numlit>0);

  /* turn on 'text view' cmd if exactly one non-dir is lit */
  allowtext = 0;
  if (br->numlit == 1) {
    for (i=0; i<br->bfLen && !br->bfList[i].lit; i++);
    if (i<br->bfLen && br->bfList[i].ftype != BF_DIR) allowtext = 1;
  }
  BTSetActive(&br->but[BR_TEXTVIEW], allowtext);
  br->cmdMB.dim[BR_TEXTVIEW] = !allowtext;
}


/***************************************************************/
static void setSelInfoStr(br, sel)
     BROWINFO *br;
     int       sel;
{
  /* sets the '# files selected' string in the brow window appropriately */

  /* criteria:  
   *    if no files are lit, display ''
   *    if 1 file is lit, pretend it was selected, fall through...
   *    if 1 or more files are lit
   *       if >1 files are lit, and clicked on nothing, disp '# files selected'
   *       otherwise, display info on selected file
   */

  int  i;
  char buf[256], buf1[256];
  BFIL *bf;


  /* default case if no special info */
  if (br->numlit>0) sprintf(buf, "%d file%s selected", br->numlit,
			    (br->numlit>1) ? "s" : "");
  else buf[0] = '\0';


  if (br->numlit > 0) {                      /* one or more files lit */
    if (br->numlit == 1) {                   /* pretend it was selected */
      for (i=0; i<br->bfLen && !br->bfList[i].lit; i++);
      sel = i;
    }

    if (sel >= 0 && sel < br->bfLen) {  /* display info on selected file */
      bf = &(br->bfList[sel]);

      if (bf->lit) {
	if (br->numlit>1) sprintf(buf1,"  (%d files selected)", br->numlit);
	else buf1[0] = '\0';

	if (bf->ftype==BF_HAVEIMG && bf->imginfo) {
	  sprintf(buf,"%s:  %s", bf->name, bf->imginfo);
	  strcat(buf, buf1);
	}

	else if (bf->ftype != BF_DIR) {     /* no info.  display file size */
	  struct stat st;
	      
	  sprintf(buf, "%s%s", br->path, bf->name);  /* build filename */
	  if (stat(buf, &st) == 0) {
	    sprintf(buf, "%s:  %ld bytes", bf->name, st.st_size);
	    strcat(buf, buf1);
	  }
	}
      }
    }
  }

  setBrowStr(br, buf);
}


/***************************************************************/
static void exposeIconWin(br, x,y,w,h)
     BROWINFO *br;
     int x,y,w,h;
{
  int        i, j, cnt;

  if (!hasBeenSized || !br->bfList || !br->bfLen) return;

  /* figure out what icons intersect the clip rect, and only redraw those,
     as drawing entirely clipped-out images *still* requires transmitting
     the image */

  cnt = (1 + br->visHigh) * br->numWide;  /* # of icons to check */

  for (i=0, j=br->scrl.val * br->numWide;  i<cnt;  i++,j++) {
    int ix,iy,iw,ih;

    /* figure out if this icon region is in the clip region */

    ix = (i%br->numWide) * ISPACE_WIDE;   /* x,y = top-left of icon region */
    iy = (i/br->numWide) * ISPACE_HIGH;
    iw = ISPACE_WIDE;
    ih = ISPACE_HIGH;

    if ((ix+iw >= x) && (ix < x+w) && (iy+ih >= y) && (iy < y+h)) {
      if (j>=0 && j < br->bfLen) drawIcon(br,j);
    }
  }
      
  Draw3dRect(br->iconW, 0, 0, br->iwWide-1, br->iwHigh-1, R3D_IN, 2,
	     browhi, browlo, browbg);
}


/***************************************************************/
static void drawIconWin(delta, sptr)
     int delta;
     SCRL *sptr;
{
  int   i,index, x,y, ix,iy, num;
  BFIL     *bf;
  BROWINFO *br;
  char  tmpstr[64], *nstr;

  /* figure out BROWINFO pointer from SCRL pointer */
  for (i=0; i<MAXBRWIN; i++) {
    if (&(binfo[i].scrl) == sptr) break;
  }
  if (i==MAXBRWIN) return;   /* didn't find one */

  br = &binfo[i];

  /* make sure we've been sized.  Necessary, as creating/modifying the
     scrollbar calls this routine directly, rather than through 
     BrowseCheckEvent() */

  if (!hasBeenSized) return;

  XSetForeground(theDisp, theGC, browfg);
  XSetBackground(theDisp, theGC, browbg);

  if (br->bfList && br->bfLen) {
    num = br->visHigh * br->numWide;

    for (i=0, index=br->scrl.val * br->numWide;  i<num;  i++,index++) {
      int x,y,w,h;

      /* erase old icon + string */
      x = (i%br->numWide) * ISPACE_WIDE;   /* x,y = top-left of icon region */
      y = (i/br->numWide) * ISPACE_HIGH;
      w = ISPACE_WIDE;
      h = ISPACE_HIGH;

      XSetForeground(theDisp, theGC, browbg);
      if (ctrlColor) {  /* keep from erasing borders */
	if (x<2) x = 2;
	if (y<2) y = 2;
	if (x+w > br->iwWide-4) w = (br->iwWide-4)-x + 2;
	if (y+h > br->iwHigh-4) h = (br->iwHigh-4)-y + 2;
      }
      XFillRectangle(theDisp, br->iconW, theGC, x, y, ISPACE_WIDE, h);
	
      if (index>=0 && index < br->bfLen) drawIcon(br, index);
    }
  }

  Draw3dRect(br->iconW, 0, 0, br->iwWide-1, br->iwHigh-1, R3D_IN, 2,
	     browhi, browlo, browbg);
}



/***************************************/
static void drawIcon(br, num)
     BROWINFO *br;
     int       num;
{
  int i,x,y,ix,iy,sw,sh,sx,sy;
  BFIL *bf;
  char  tmpstr[64], *nstr, *str;


  if (num<0 || num >= br->bfLen) return;
  bf = &(br->bfList[num]);

  i = num - (br->scrl.val * br->numWide);
  if (i<0 || i>(br->visHigh+1)*br->numWide) return; /* not visible */

  if (bf->lit) {
    XSetForeground(theDisp, theGC, browbg);
    XSetBackground(theDisp, theGC, browfg);
  }
  else {
    XSetForeground(theDisp, theGC, browfg);
    XSetBackground(theDisp, theGC, browbg);
  }

  x = (i%br->numWide) * ISPACE_WIDE;   /* x,y = top-left of icon region */
  y = (i/br->numWide) * ISPACE_HIGH;

  /* draw the icon */
  ix = x + ISPACE_WIDE/2 - bf->w/2;          /* center align */
  iy = y + ISPACE_TOP + ISIZE_HIGH - bf->h;  /* bottom align */


  if (bf->ftype >= 0 && bf->ftype <BF_MAX) {  /* built-in icon */
    XCopyPlane(theDisp, bfIcons[bf->ftype], br->iconW, theGC,
	       0, 0, bf->w, bf->h, ix, iy, 1);
  }

  else if (bf->ftype == BF_HAVEIMG && bf->ximage) {
    XPutImage(theDisp, br->iconW, theGC, bf->ximage, 0,0, ix,iy, bf->w, bf->h);
  }

  else {  /* shouldn't happen */
    XDrawRectangle(theDisp, br->iconW, theGC, ix, iy, bf->w, bf->h);
  }


  str = bf->name;
  if (!strcmp(str,"..")) str = "<parent>";


  /* decide if the title is too big, and shorten if neccesary */
  if (StringWidth(str) > ISPACE_WIDE-6) {
    int dotpos; 
    strncpy(tmpstr,str,56);
    dotpos = strlen(tmpstr);
    strcat(tmpstr,"...");

    while(StringWidth(tmpstr) > ISPACE_WIDE-6 && dotpos>0) {
      /* change last non-dot char in tmpstr to a dot, and lop off
	 last dot */
	    
      dotpos--;
      tmpstr[dotpos] = '.';
      tmpstr[dotpos+3] = '\0';
    }
    
    nstr = tmpstr;
  }
  else nstr = str;
  

  /* draw the title */
  sw = StringWidth(nstr);
  sh = CHIGH;

  sx = x + ISPACE_WIDE/2 - sw/2 - 2;
  sy = y + ISPACE_TOP + ISIZE_HIGH + ISPACE_TTOP - 1;

  XSetForeground(theDisp, theGC,
		 (bf->lit && bf->lit!=ICON_ONLY) ? browfg : browbg);
  XFillRectangle(theDisp, br->iconW, theGC, sx, sy, sw + 4, sh + 2);

  XSetForeground(theDisp, theGC,
		 (bf->lit && bf->lit!=ICON_ONLY) ? browbg : browfg);
  CenterString(br->iconW, nstr, x + ISPACE_WIDE/2, 
	       y + ISPACE_TOP + ISIZE_HIGH + ISPACE_TTOP + CHIGH/2);
}


/***************************************/
static void eraseIcon(br, num)
     BROWINFO *br;
     int       num;
{
  /* note: doesn't erase the icon's title, just the icon itself */

  int i,x,y,ix,iy,w,h;
  BFIL *bf;
  char  tmpstr[64], *nstr;

  if (num<0 || num >= br->bfLen) return;
  bf = &(br->bfList[num]);

  i = num - (br->scrl.val * br->numWide);

  XSetForeground(theDisp, theGC, browbg);

  x = (i % br->numWide) * ISPACE_WIDE;   /* x,y = top-left of icon region */
  y = (i / br->numWide) * ISPACE_HIGH;
  w = bf->w;
  h = bf->h;

  ix = x + ISPACE_WIDE/2 - w/2;          /* center align */
  iy = y + ISPACE_TOP + ISIZE_HIGH - h;  /* bottom align */

  if (ctrlColor) { /* keep from erasing borders */
    if (ix<2) ix = 2;
    if (iy<2) iy = 2;
    if (ix+w > br->iwWide-2) w = (br->iwWide-2) - ix;
    if (iy+h > br->iwHigh-2) h = (br->iwHigh-2) - iy;
  }

  XFillRectangle(theDisp, br->iconW, theGC, ix, iy, w+1, h+1);
}


/***************************************/
static void eraseIconTitle(br, num)
     BROWINFO *br;
     int       num;
{
  /* note: doesn't erase the icon, just the icon's title */

  int i,x,y;

  if (num<0 || num >= br->bfLen) return;
  i = num - (br->scrl.val * br->numWide);

  x = (i % br->numWide) * ISPACE_WIDE;   /* x,y = top-left of icon region */
  y = (i / br->numWide) * ISPACE_HIGH;

  XSetForeground(theDisp, theGC, browbg);
  XFillRectangle(theDisp, br->iconW, theGC, 
		 x, y + ISPACE_TOP + ISIZE_HIGH + ISPACE_TTOP - 1,
		 ISPACE_WIDE, LINEHIGH);

  if (ctrlColor) 
    Draw3dRect(br->iconW, 0, 0, br->iwWide-1, br->iwHigh-1, R3D_IN, 2,
	       browhi, browlo, browbg);
}



/***************************************/
static void makeIconVisible(br, num)
     BROWINFO *br;
     int       num;
{
  int sval, first, numvis;

  /* if icon #i isn't visible, adjust scrollbar so it *is* */

  sval = br->scrl.val;
  first = sval * br->numWide;
  numvis = br->visHigh * br->numWide;

  while (num<first) { sval--;  first = sval * br->numWide; }
  if (num>= (first+numvis)) {
    /* get #num into top row, to reduce future scrolling */
    while (num>=(first+br->numWide) && sval<br->scrl.max) {
      sval++;  first = sval * br->numWide;
    }
  }
  SCSetVal(&br->scrl, sval);
}



/***************************************************************/
static void clickBrow(br, x,y)
     BROWINFO *br;
     int x,y;
{
  int   i;
  BUTT *bp;

  for (i=0; i<BR_NBUTTS; i++) {
    bp = &(br->but[i]);
    if (PTINRECT(x,y,bp->x,bp->y,bp->w,bp->h)) break;
  }

  if (i<BR_NBUTTS) {
    if (BTTrack(bp)) doCmd(br, i);
    return;
  }

  if (MBClick(&(br->dirMB), x,y)) {
    i = MBTrack(&(br->dirMB));
    if (i >= 0) changedBrDirMB(br, i);
    return;
  }

  if (MBClick(&(br->cmdMB), x,y)) {
    i = MBTrack(&(br->cmdMB));
    if (i >= 0) doCmd(br, i);
    return;
  }

  return;
}


/***************************************************************/
static int clickIconWin(br, mx, my, mtime, multi)
     BROWINFO *br;
     int mx,my,multi;
     unsigned long mtime;
{
  /* returns '-1' normally, returns an index into bfList[] if the user
     double-clicks an icon */

  int       i,j, base, num, x,y,ix,iy, rv, sel, cpymode;
  BROWINFO *destBr;
  BFIL     *bf;
  char      buf[256], *destFolderName;

  rv = -1;     /* default return value */
  if (!br->bfList || !br->bfLen) return rv;

  destBr = br;  destFolderName = ".";

  sel = mouseInWhichIcon(br, mx, my);


  if (sel == -1) {  /* clicked on nothing */
    if (!multi) {   /* deselect all */
      for (i=0; i<br->bfLen; i++) {
	if (br->bfList[i].lit) { br->bfList[i].lit = 0;  drawIcon(br,i); }
	br->numlit = 0;
      }
    }

    changedNumLit(br, sel, 0);
    br->lastIconClicked = -1;  
  }


  else if (multi) {      /* clicked on something, while 'multi' key down */
    bf = &(br->bfList[sel]);

    bf->lit = !bf->lit;
    br->lastIconClicked = -1;

    if (!bf->lit) br->numlit--;
             else br->numlit++;

    drawIcon(br, sel);

    changedNumLit(br, sel, 0);
  }


  else {   /* clicked on something, and not in 'multi' mode */
    /* if there are some lit, and we *didn't* click one of them, turn
       all others off */
    if (br->numlit && !br->bfList[sel].lit) {
      for (i=0, bf=br->bfList; i<br->bfLen; i++,bf++) {
	if (bf->lit && i!=sel) {
	  bf->lit = 0;
	  drawIcon(br, i);
	  br->numlit--;
	}
      }
    }


    bf = &br->bfList[sel];

    if (!bf->lit) {
      bf->lit = 1;
      drawIcon(br, sel);
      br->numlit = 1;
    }


    changedNumLit(br, sel, 0);

    
    /* see if we've double-clicked something */
    if (sel==br->lastIconClicked && mtime-br->lastClickTime < DBLCLICKTIME) {
      int k;
      br->lastIconClicked = -1;    /* YES */

      doubleClick(br, sel);
      return rv;
    }

    else {
      br->lastIconClicked = sel;
      br->lastClickTime = mtime;
    }
  }


  changedNumLit(br, -1, 0);



  {    /* track mouse until button1 is released */

    Window       rW, win, cW;
    int          x, y, rootx, rooty, iwx, iwy;
    unsigned int mask, prevmask;
    Cursor       curs;
    int          samepos, oldx, oldy, oldbrnum, destic, origsval, first;
    int          hasrect, rx, ry, rw, rh;

    rx = ry = rw = rh = 0;
    first = 1;  hasrect = 0;  cpymode = 0;
    origsval = br->scrl.val;

    if ( (sel>=0 && !multi) || sel==-1) {  
      /* clicked on an icon, or clicked on nothing... */

      while (!XQueryPointer(theDisp, rootW, &rW, &cW, &rootx, &rooty, 
			    &x,&y,&mask));
      if (mask & Button1Mask) {  /* still held down */

	if (sel == -1) curs = tcross;
	else if ((mask & ControlMask) || (mask & ShiftMask)) {
	  curs = copycurs;  cpymode = 1;
	}
	else curs = movecurs;

	/* change cursors */
	for (i=0; i<MAXBRWIN; i++) 
	  XDefineCursor(theDisp,binfo[i].iconW, curs);

	samepos = oldx = oldy = oldbrnum = 0;
	prevmask = mask;

	while (1) {  /* wait for button 1 to be released */
	  while (!XQueryPointer(theDisp,rootW,&rW,&win,&rootx,&rooty,
				&x,&y,&mask));
	  if (!(mask & Button1Mask)) break;

	  if (sel>=0) {  /* see if changed copy/move status (and cursor) */
	    int cmod;
	    
	    cmod = (mask&ControlMask || mask&ShiftMask) ? 1 : 0;

	    if (cmod != cpymode) {
	      curs = (cmod) ? copycurs : movecurs;
	      	for (i=0; i<MAXBRWIN; i++) 
		  XDefineCursor(theDisp,binfo[i].iconW, curs);
	    }
	    cpymode = cmod;  prevmask = mask;
	  }

	      

	  XTranslateCoordinates(theDisp, rW, br->iconW, rootx,rooty, 
				&iwx,&iwy, &cW);

	  /* find deepest child that the mouse is in */
	  while (win!=None) {
	    XTranslateCoordinates(theDisp, rW, win, rootx, rooty, &x, &y, &cW);
	    if (cW == None) break;
	    else win = cW;
	  }

	  for (i=0; i<MAXBRWIN && win!=binfo[i].iconW; i++);

	  /* if it's in any icon window, and we're doing icon-dragging
	     OR we're doing a rectangle-drag */

	  if (i==MAXBRWIN) { destBr=(BROWINFO *) NULL; destFolderName = ""; }

	  if (i<MAXBRWIN || sel == -1) {  
	    if (i<MAXBRWIN) destBr = &binfo[i];
	    if (sel == -1)  destBr = br;

	    /* AUTO-SCROLLING:  scroll any icon window if we're doing an 
	       icon-drag.  Only scroll the original window if we're doing 
	       a rect drag */

	    if (sel>=0 && (oldx!=x || oldy!=y || oldbrnum!=i)) {  /* moved */
	      samepos = 0;  oldx = x;  oldy = y;  oldbrnum = i;
	    }
	    else {
	      int scamt = 0;
	      if (sel == -1) {   /* rectangle dragging */
		if (iwy < SCROLLVERT)                scamt = -1;
		if (iwy < -PAGEVERT)                 scamt = -destBr->visHigh;
		if (iwy > destBr->iwHigh-SCROLLVERT) scamt = 1;
		if (iwy > destBr->iwHigh+PAGEVERT)   scamt = destBr->visHigh;
	      }
	      else {             /* file dragging */
		if (y >= 0 && y < SCROLLVERT) scamt = -1;
		if (y <= destBr->iwHigh && y > destBr->iwHigh-SCROLLVERT)
		  scamt = 1;
	      }

	      if ((scamt < 0 && destBr->scrl.val > 0) ||
		  (scamt > 0 && destBr->scrl.val < destBr->scrl.max)) {

		if (hasrect) invertSelRect(br,rx,ry,rw,rh);
		hasrect = 0;

		SCSetVal(&(destBr->scrl), destBr->scrl.val + scamt);
	      }
	    }

	    
	    /* if we clicked on an icon (originally), and therefore are
	       showing the 'move files' cursor, see if the cursor is within
	       the icon region of any folders.  If so, light up *the icon
	       only* of the selected folder, by setting it's 'lit' field to
	       ICON_ONLY, and clearing any other folders that might have
	       that set (only one dest folder can be lit at a time) */

	    if (sel>=0) {
	      destic = mouseInWhichIcon(destBr, x, y);

	      bf = (destic>=0) ? &(destBr->bfList[destic]) : (BFIL *) NULL;
	      if (!bf || (bf && bf->ftype == BF_DIR && bf->lit!=ICON_ONLY)) {
		for (i=0; i<destBr->bfLen; i++) {      /* clear prev dest */
		  if (destBr->bfList[i].lit == ICON_ONLY) {
		    destBr->bfList[i].lit = 0;
		    drawIcon(destBr, i);
		  }
		}
		destFolderName = ".";
	      }

	      if (bf && bf->ftype == BF_DIR && !bf->lit) {
		bf->lit = ICON_ONLY;
		drawIcon(destBr, destic);
		destFolderName = bf->name;
	      }
	    }

	    /* Dragging a selection rectangle. */

	    else { 
	      static int prevx, prevy, prevcnt;
	      int        origy, top, left, wide, high, cnt;

	      if (first) { prevx = mx;  prevy = my;  first=0;  prevcnt = -1; }

	      /* set x,y to iconW coordinate system, clipped... */
	      x = iwx;  y = iwy;
	      RANGE(x, 0, br->iwWide-1);
	      RANGE(y, 0, br->iwHigh-1);

	      if (x != prevx || y != prevy || !hasrect) {   /* cursor moved */
		origy = my - (br->scrl.val - origsval) * ISPACE_HIGH;

		if (hasrect) invertSelRect(br, rx, ry, rw, rh);  /* turn off */

		rx  = (mx    < x) ? mx    : x;
		ry  = (origy < y) ? origy : y;
		rw  = abs(mx - x);  
		rh  = abs(origy - y);

		/* figure out which icons need to be lit/unlit.  Only
		   redraw those that have changed state */
		
		for (i=0,cnt=0, bf=br->bfList; i<br->bfLen; i++,bf++) {
		  int ix, iy, isin, light;

		  ix = ((i%br->numWide) * ISPACE_WIDE) 
		                  + ISPACE_WIDE/2 - bf->w/2;
		  iy = ((i/br->numWide) * ISPACE_HIGH) 
		                  + ISPACE_TOP + ISIZE_HIGH - bf->h;

		  iy = iy - br->scrl.val * ISPACE_HIGH;

		  /* is the icon rectangle of this beastie inside the
		     dragging rectangle ? */

		  isin =  (ix+bf->w >= rx && ix < rx+rw &&
			   iy+bf->h >= ry && iy < ry+rh);

		  if (isin) {
		    if (bf->lit==0) {
		      bf->lit = TEMP_LIT;  drawIcon(br, i);
		    }
		    else if (bf->lit==1) {
		      bf->lit = TEMP_LIT1; drawIcon(br, i);
		    }
		  }
		  else {
		    if (bf->lit == TEMP_LIT) {
		      bf->lit = 0;  drawIcon(br, i);
		    }
		    else if (bf->lit == TEMP_LIT1) {
		      bf->lit = 1;  drawIcon(br, i);
		    }
		  }

		  if (bf->lit) cnt++;
		}

		invertSelRect(br, rx, ry, rw, rh);  /* turn on */
		hasrect = 1;

		if (cnt != prevcnt) {
		  if (cnt) sprintf(buf, "%d file%s selected", cnt,
				   (cnt>1) ? "s" : "");
		  else buf[0]='\0';
		  setBrowStr(br, buf);
		  prevcnt = cnt;
		}

		prevx = x;  prevy = y;
	      }
	    }
	  }

	  else {      /* NOT in an icon window... */
	    /* turn off ALL ICON_ONLY icons in ALL icon windows... */
	    for (i=0; i<MAXBRWIN; i++) {
	      for (j=0, bf=binfo[i].bfList; j<binfo[i].bfLen; j++,bf++) {
		if (bf->lit == ICON_ONLY) {
		  bf->lit = 0;
		  drawIcon(&binfo[i], j);
		}
	      }
	    }
	  }
	}

	/* RELEASED BUTTON:  back to normal arrow cursor */
	for (i=0; i<MAXBRWIN; i++) 
	  XDefineCursor(theDisp, binfo[i].iconW, None);

	if (sel == -1) {  /* was dragging rectangle */
	  if (hasrect) invertSelRect(br, rx, ry, rw, rh);

	  br->numlit = 0;
	  for (i=0, bf=br->bfList; i<br->bfLen; i++,bf++) {
	    if (!multi && bf->lit == 1) { bf->lit = 0;  drawIcon(br, i); }

	    if (bf->lit == TEMP_LIT || bf->lit == TEMP_LIT1) {
	      bf->lit = 1;  drawIcon(br, i);
	    }
	    
	    if (bf->lit) br->numlit++;
	  }

	  changedNumLit(br, -1, 0);
	}
      }
    }
  }


  /* if doing a copy or a move, do the thing to the files */
  if (sel >= 0) {
    char *destFolder;

    if (DEBUG) {
      fprintf(stderr,"---------------\n");
      fprintf(stderr,"Source  Dir: '%s'\n", br->path);
      fprintf(stderr,"Dest    Dir: '%s'\n", destBr ? destBr->path : "<null>");
      fprintf(stderr,"Dest Folder: '%s'\n", destFolderName);
    }

    
    if (!br->numlit) {
      if (DEBUG) fprintf(stderr, "no selected files.  Nothing to do!\n");
    }

    else if (!destBr || strlen(destFolderName) == 0) {
      if (DEBUG) fprintf(stderr, "no destination.  Nothing to do!\n");
    }

    else if (strcmp(destFolderName,".")     == 0 && 
	     strcmp(br->path, destBr->path) == 0) {
      if (DEBUG) fprintf(stderr,"source == destination.  Nothing to do!\n");
    }

    else {    /* have to do some copying/moving */
      char **nlist;  int ncnt;

      if (DEBUG) fprintf(stderr,"Files to %s:  ", cpymode ? "copy" : "move");

      nlist = (char **) malloc(br->numlit * sizeof(char *));
      if (!nlist) FatalError("clickIconWin: couldn't malloc nlist");

      /* copy names to nlist */
      for (i=ncnt=0, bf=br->bfList;  i<br->bfLen;  i++,bf++) {
	if (bf->lit == 1 && ncnt < br->numlit) {
	  nlist[ncnt] = (char *) malloc(strlen(bf->name)+1);
	  if (!nlist[ncnt]) FatalError("out of memory building namelist");

	  strcpy(nlist[ncnt], bf->name);
	  ncnt++;
	  if (DEBUG) fprintf(stderr, "%s  ", bf->name);
	}
      }
      if (DEBUG) fprintf(stderr,"\n\n");
 
#ifdef VMS
      /*
       * For VMS, our directory file names are identifed by the 
       * special filename extension, ".DIR".  Unfortunately, this 
       * needs to be stripped before we ever actually use the name 
       * in a copy command... :(     RLD 26-FEB-1993
       */

      *rindex ( destFolderName, '.' ) = '\0';
#endif


      dragFiles(br, destBr, br->path, destBr->path, destFolderName, nlist, 
		ncnt, cpymode);

      /* free namelist */
      for (i=0; i<ncnt; i++) if (nlist[i]) free(nlist[i]);
      free(nlist);
    }


    if (destBr) {   /* turn off any ICON_ONLY folders */
      for (i=0, bf=destBr->bfList;  i<destBr->bfLen;  i++,bf++) {
	if (bf->lit == ICON_ONLY) {
	  bf->lit = 0;
	  drawIcon(destBr, i);
	}
      }
    }
  }      /* end of 'tracking' sub-function */

  return rv;
}

/*******************************************/
static void doubleClick(br, sel)
     BROWINFO *br;
     int       sel;
{
  int i, j, k;
  char buf[512];
  BFIL *bf;

  /* called to 'open' icon #sel, which could be a file or a dir */

  /* if sel == -1, then called via RETURN key.  just use first lit item
     as thing that was double clicked on */

  if (sel < 0) {
    for (sel=0; sel<br->bfLen && br->bfList[sel].lit==0; sel++);
  }

  j = 0;
  k = numnames;
  if (sel < 0 || sel >= br->bfLen) return;   /* no selection */

  /* if this isn't a dir, copy it, and all other selected non-dir
     filenames into the ctrl list */
  if (ISLOADABLE(br->bfList[sel].ftype)) {
    AddFNameToCtrlList(br->path, br->bfList[sel].name);
    j++;
  }

  for (i=0, bf=br->bfList; i<br->bfLen; i++,bf++) {
    if (bf->lit && ISLOADABLE(bf->ftype) && i!=sel) {
      AddFNameToCtrlList(br->path, bf->name);
      j++;
    }
  }

  /* clear all 'other' selections */
  for (i=0, bf=br->bfList; i<br->bfLen; i++,bf++) {
    if (bf->lit && i!=sel) {
      bf->lit = 0;
      drawIcon(br, i);
    }
  }
  br->numlit = 1;
  changedNumLit(br, sel, 0);


  if (j && k<numnames) {   /* stuff was *actually* added to the list */
    curname = nList.selected = k;
    ChangedCtrlList();  /* added something to list */
  }


  
  /* double-clicked something.  We should do something about it */
  if (br->bfList[sel].ftype == BF_DIR) {  /* try to cd */
    sprintf(buf, "%s%s", br->path, br->bfList[sel].name);
    if (chdir(buf)) {
      char str[512];
      sprintf(str,"Unable to cd to '%s'\n", br->bfList[sel].name);
      setBrowStr(br, str);
      XBell(theDisp, 50);
    }
    else {
      scanDir(br);
      SCSetVal(&(br->scrl), 0);  /* reset to top on a chdir */
    }
  }

  else {  /* not a directory.  Try to open it as a file */
    int j;

    sprintf(buf, "%s%s", br->path, br->bfList[sel].name);

    /* if name is already in namelist, make it the current selection */
    for (j=0;  j<numnames && strcmp(namelist[j],buf);  j++);

    if (j<numnames) {
      curname = nList.selected = j;
      ChangedCtrlList();
      *event_retP = THISNEXT;
    }
    else { *event_retP = LOADPIC;  SetDirFName(buf);  }
    
    *event_doneP = 1;     /* make MainLoop load image */
  }
}

/*******************************************/
static int mouseInWhichIcon(br, mx, my)
     BROWINFO *br;
     int       mx, my;
{
  /* mx,my are mouse position in iconW coordinates.  Returns '-1' if the
     mouse is not in any icon */

  int i, x, y, ix, iy, sel, base, num;
  BFIL *bf;

  /* figure out what was clicked... */
  base = br->scrl.val   * br->numWide;
  num = (1+br->visHigh) * br->numWide;

  for (i=0, sel=base; i<num; i++,sel++) {
    if (sel>=0 && sel<br->bfLen) {
      bf = &(br->bfList[sel]);

      x = (i%br->numWide) * ISPACE_WIDE;  /* x,y=top-left of icon region */
      y = (i/br->numWide) * ISPACE_HIGH;

      ix = x + ISPACE_WIDE/2 - bf->w/2;          /* center align */
      iy = y + ISPACE_TOP + ISIZE_HIGH - bf->h;  /* bottom align */

      if (PTINRECT(mx,my, ix, iy, bf->w, bf->h)) break;
    }
  }

  if (i==num) return -1;
  return sel;
}


/*******************************************/
static void invertSelRect(br, x, y, w, h)
     BROWINFO *br;
     int       x,y,w,h;
{
  if (w>1 && h>1) {
    XSetState(theDisp,theGC, browfg, browbg, GXinvert, browfg^browbg);
    XDrawRectangle(theDisp, br->iconW, theGC, x,y,w,h);
    XSetState(theDisp,theGC, browfg, browbg, GXcopy, AllPlanes);
  }
}


/* constants used in browKey(), below */

#define BR_LEFT   0
#define BR_RIGHT  1
#define BR_UP     2
#define BR_DOWN   3
#define BR_PAGEUP 4
#define BR_PAGEDN 5
#define BR_HOME   6
#define BR_END    7


/***************************************************************/
static void keyIconWin(br, kevt)
     BROWINFO *br;
     XKeyEvent *kevt;
{
  char buf[128];
  KeySym ks;
  int stlen, shift, dealt;

  stlen = XLookupString(kevt, buf, 128, &ks, (XComposeStatus *) NULL);
  shift = kevt->state & ShiftMask;
  dealt = 1;  

  /* check for arrow keys, Home, End, PgUp, PgDown, etc. */
  if      (ks==XK_Left  || ks==XK_KP_4 || ks==XK_F30)
    browKey(br,BR_LEFT);

  else if (ks==XK_Right || ks==XK_KP_6 || ks==XK_F32) 
    browKey(br,BR_RIGHT);

  else if (ks==XK_Home  || (ks==XK_Prior && shift))   browKey(br,BR_HOME);
  else if (ks==XK_End   || (ks==XK_Next  && shift))   browKey(br,BR_END);
  else if (ks==XK_Prior || (ks==XK_Up    && shift))   browKey(br,BR_PAGEUP);
  else if (ks==XK_Next  || (ks==XK_Down  && shift))   browKey(br,BR_PAGEDN);
  else if (ks==XK_Up    || ks==XK_KP_8 || ks==XK_F28) browKey(br,BR_UP);
  else if (ks==XK_Down  || ks==XK_KP_2 || ks==XK_F34) browKey(br,BR_DOWN);
  else dealt = 0;

  /* fake tab and backtab to be same as 'space' and 'backspace' */
  if (ks==XK_Tab &&  shift) { buf[0] = '\010';  stlen = 1; }
  if (ks==XK_Tab && !shift) { buf[0] = ' ';  stlen = 1; }

  if (dealt || !stlen) return;

  /* keyboard equivalents */
  switch (buf[0]) {
  case '\004': doCmd(br, BR_DELETE);   break;      /* ^D = Delete Files */
  case '\016': doCmd(br, BR_MKDIR);    break;      /* ^N = New Directory */
  case '\022': doCmd(br, BR_RENAME);   break;      /* ^R = Rename */
  case '\023': doCmd(br, BR_RESCAN);   break;      /* ^S = reScan */
  case '\027': doCmd(br, BR_NEWWIN);   break;      /* ^W = open new Window */
  case '\025': doCmd(br, BR_UPDATE);   break;      /* ^U = Update icons */
  case '\007': doCmd(br, BR_GENICON);  break;      /* ^G = Generate icons */
  case '\001': doCmd(br, BR_SELALL);   break;      /* ^A = select All */
  case '\024': doCmd(br, BR_TEXTVIEW); break;      /* ^T = Textview */
  case '\021': doCmd(br, BR_QUIT);     break;      /* ^Q = Quit xv */

  case '\003': FakeButtonPress(&but[BCMTVIEW]); break;  /* ^C */

  case '\033': doCmd(br, BR_CLOSE);   break;      /* ESC = Close window */

  case '\r':
  case '\n':   doubleClick(br, -1);   break;      /* RETURN = load selected */

  case ' ':
  case '\010':
  case '\177':   /* SPACE = load next, BS/DEL = load prev */
    if (br->bfLen && br->numlit == 1) {
      int i, viewsel;
      char fname[MAXPATHLEN];

      for (i=0; i<br->bfLen && !br->bfList[i].lit; i++);  /* find lit one */

      sprintf(fname, "%s%s", br->path, br->bfList[i].name);
      viewsel = !(strcmp(fname, fullfname));

      if (viewsel) {
	if (buf[0]==' ') browKey(br, BR_RIGHT);
	            else browKey(br, BR_LEFT);
      }

      if (!br->bfList[i].lit || !viewsel) {   /* changed selection */
	for (i=0; i<br->bfLen && !br->bfList[i].lit; i++);  /* find it */
	if (br->bfList[i].ftype != BF_DIR) 
	  doubleClick(br, -1);
      }
    }
    break;


  default:  /* unknown character.  Take it as an alpha accelerator */
    browAlpha(br, buf[0]);
    break;
  }

}


/***************************************************/
static void browKey(br, key)
     BROWINFO *br;
     int key;
{
  int i,j;

  if (!br->bfLen) return;

  /* an arrow key (or something like that) was pressed in icon window.
     change selection/scrollbar accordingly */

  /* handle easy keys */
  if (key == BR_PAGEUP) SCSetVal(&br->scrl, br->scrl.val - br->scrl.page);
  if (key == BR_PAGEDN) SCSetVal(&br->scrl, br->scrl.val + br->scrl.page);
  if (key == BR_HOME)   SCSetVal(&br->scrl, br->scrl.min);
  if (key == BR_END)    SCSetVal(&br->scrl, br->scrl.max);

  /* handle up/down/left/right keys
   *
   * if precisely *one* item is lit, than the up/down/left/right keys move
   * the selection.  
   *
   * if NO items are lit, then left/right select the first/last fully-displayed
   * icon, and up/down simply scroll window up or down, without selecting 
   * anything
   *
   * if more than one item is lit, up/down/left/right keys BEEP
   */

  if (key==BR_UP || key==BR_DOWN || key==BR_LEFT || key==BR_RIGHT) {

    if (br->numlit > 1) XBell(theDisp, 50);
    else if (br->numlit == 1) {
      /* find it */
      for (i=0; i<br->bfLen && !br->bfList[i].lit; i++);

      /* if it's not visible, lose it */
      if ((i <   br->scrl.val * br->numWide) ||
	  (i >= (br->scrl.val + br->visHigh) * br->numWide)) {
	br->numlit = 0;
	br->bfList[i].lit = 0;
	drawIcon(br, i);
      }
      else {
	/* make it visible */
	makeIconVisible(br, i);

	j = i;

	if (key == BR_UP)    j = i - br->numWide;
	if (key == BR_DOWN)  j = i + br->numWide;
	if (key == BR_LEFT)  j = i - 1;
	if (key == BR_RIGHT) j = i + 1;
	
	if (j >= 0 && j < br->bfLen) {  
	  br->bfList[i].lit = 0;
	  br->bfList[j].lit = 1;
	  makeIconVisible(br,j);
	  drawIcon(br,i);
	  drawIcon(br,j);
	  setSelInfoStr(br, j);
	}
      }
    }

  
    if (br->numlit == 0) {   /* no current selection */
      if (key == BR_UP)   SCSetVal(&br->scrl, br->scrl.val - 1);
      if (key == BR_DOWN) SCSetVal(&br->scrl, br->scrl.val + 1);
      if (key == BR_LEFT || key == BR_RIGHT) {
	if (key == BR_LEFT)  i = (br->scrl.val+br->visHigh) * br->numWide - 1;
	                else i = (br->scrl.val * br->numWide);
	RANGE(i, 0, br->bfLen-1);
	br->bfList[i].lit = 1;
	br->numlit = 1;
	changedNumLit(br, i, 0);
	drawIcon(br, i);
      }
    }
  }
}



/***************************************************/
static void browAlpha(br, ch)
     BROWINFO *br;
     int ch;
{
  /* find first 'plain' file that is lexically >= than the given ch */

  int i,j;

  if (!br->bfLen) return;
  if (ch <= ' ' || ch > '\177')  return;    /* ignore 'funny' keys */

  for (i=0; i<br->bfLen && br->bfList[i].ftype==BF_DIR; i++);
  if (i==br->bfLen) return;    /* only directories in this dir */

  for ( ; i<br->bfLen; i++) {
    if (br->bfList[i].name[0] >= ch) break;
  }

  if (i==br->bfLen) i--;

  for (j=0; j<br->bfLen; j++) {
    if (br->bfList[j].lit) {
      br->bfList[j].lit = 0;
      drawIcon(br, j);
    }
  }

  br->bfList[i].lit = 1;
  drawIcon(br, i);
  br->numlit = 1;

  makeIconVisible(br, i);
  changedNumLit(br, i, 0);
}



/***************************************************/
static void changedBrDirMB(br, sel)
     BROWINFO *br;
     int sel;
{

  if (sel != 0) {   /* changed directories */
    char tmppath[MAXPATHLEN+1];
    int  i;

    /* build 'tmppath' */
    tmppath[0] = '\0';
    for (i = br->ndirs-1; i>=sel; i--)
      strcat(tmppath, br->mblist[i]);

    if (tmppath[0] == '\0') {
      /* special case:  if cd to '/', fix path (it's currently "") */
#ifdef apollo    /*** Apollo DomainOS uses // as the network root ***/
      strcpy(tmppath,"//");
#else
      strcpy(tmppath,"/");
#endif
    }
#ifdef VMS
    else {
      /*
       *  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).
       *
       *  End 'tmppath' by changing trailing '/' (of dir name) to a '\0'
       */
      *rindex ( tmppath, '/') = '\0';
      if ( ((br->ndirs-sel) == 2) && (strlen(tmppath) > 1) ) 
	strcat ( tmppath, "/000000" ); /* add root dir for device */
      else if  ((br->ndirs-sel) == 1 )
	strcpy ( tmppath, "/device_list_root/000000" );  /* fake top level */
    }
#endif

    if (chdir(tmppath)) {
      char str[512];
      sprintf(str,"Unable to cd to '%s'\n", tmppath);
      MBRedraw(&(br->dirMB));
      setBrowStr(br,str);
      XBell(theDisp, 50);
    }
    else {
      scanDir(br);
      SCSetVal(&br->scrl, 0);  /* reset to top of window on a chdir */
    }
  }
}



/***************************************************************/
static int cdBrow(br)
     BROWINFO *br;
{
  /* returns non-zero on failure */

  int rv;

  /* temporarily excise trailing '/' char from br->path */
  if (strlen(br->path)>2 && br->path[strlen(br->path)-1] == '/')
    br->path[strlen(br->path)-1] = '\0';

  rv = chdir(br->path);
  if (rv) {
    char str[512];
    sprintf(str, "Unable to cd to '%s'\n", br->path);
    setBrowStr(br, str);
    XBell(theDisp, 50);
  }

  strcat(br->path, "/");   /* put trailing '/' back on */
  return rv;
}


/***************************************************************/
static void copyDirInfo(srcbr, dstbr)
     BROWINFO *srcbr, *dstbr;
{
  /* copies br info from an already existing browser window 
     (ie, one that is already showing the same directory) */

  int i, oldnum, maxv, page;

  oldnum = dstbr->bfLen;
  dstbr->lastIconClicked = -1;
  setBrowStr(dstbr,"");

  /* copy mblist */
  dstbr->ndirs = srcbr->ndirs;
  for (i=0;  i<dstbr->ndirs;  i++) {
    dstbr->mblist[i] = (char *) malloc(strlen(srcbr->mblist[i]) + 1);
    if (!dstbr->mblist[i]) FatalError("unable to malloc brMBlist[]");
    strcpy(dstbr->mblist[i], srcbr->mblist[i]);
  }

  dstbr->dirMB.list  = srcbr->mblist;
  dstbr->dirMB.nlist = srcbr->ndirs;

  XClearArea(theDisp, dstbr->dirMB.win, dstbr->dirMB.x, dstbr->dirMB.y, 
	     dstbr->dirMB.w+3, dstbr->dirMB.h+3, False);

  i = StringWidth(dstbr->mblist[0]) + 10;
  dstbr->dirMB.x = dstbr->dirMB.x + dstbr->dirMB.w/2 - i/2;
  dstbr->dirMB.w = i;
  MBRedraw(&dstbr->dirMB);

  strcpy(dstbr->path, srcbr->path);

  WaitCursor();
  freeBfList(dstbr);     /* just to be safe */

  /* copy the bfList info */
  dstbr->numlit = 0;
  dstbr->bfLen = srcbr->bfLen;

  dstbr->bfList = (BFIL *) calloc(dstbr->bfLen, sizeof(BFIL));
  if (!dstbr->bfList) FatalError("can't create bfList!");

  for (i=0; i<dstbr->bfLen; i++) {
    BFIL *sbf, *dbf;

    if ((i&0x03) == 0) drawTemp(dstbr, i, dstbr->bfLen);
    if ((i & 0x3f) == 0) WaitCursor();
    
    sbf = &(srcbr->bfList[i]);
    dbf = &(dstbr->bfList[i]);

    if (sbf->name) {
      dbf->name = (char *) malloc(strlen(sbf->name) + 1);
      if (!dbf->name) FatalError("ran out of memory for dbf->name");
      strcpy(dbf->name, sbf->name);
    }
    else dbf->name = (char *) NULL;

    if (sbf->imginfo) {
      dbf->imginfo = (char *) malloc(strlen(sbf->imginfo) + 1);
      if (!dbf->imginfo) FatalError("ran out of memory for dbf->imginfo");
      strcpy(dbf->imginfo, sbf->imginfo);
    }
    else dbf->imginfo = (char *) NULL;

    dbf->ftype = sbf->ftype;
    dbf->lit = 0;
    dbf->w   = sbf->w;
    dbf->h   = sbf->h;

    if (sbf->pimage) {
      dbf->pimage = (byte *) malloc(dbf->w * dbf->h);
      if (!dbf->pimage) FatalError("ran out of memory for dbf->pimage");
      xvbcopy((char *) sbf->pimage, (char *) dbf->pimage, (dbf->w * dbf->h));
    }
    else dbf->pimage = (byte *) NULL;

    if (sbf->ximage) {
      dbf->ximage = (XImage *) malloc(sizeof(XImage));
      if (!dbf->ximage) FatalError("ran out of memory for dbf->ximage");
      xvbcopy((char *) sbf->ximage, (char *) dbf->ximage, sizeof(XImage));

      if (sbf->ximage->data) {
	dbf->ximage->data = (char *) malloc(dbf->ximage->height * 
					    dbf->ximage->bytes_per_line);
	if (!dbf->ximage->data) FatalError("ran out of memory for ximg data");
	xvbcopy((char *) sbf->ximage->data, (char *) dbf->ximage->data, 
	      dbf->ximage->height * dbf->ximage->bytes_per_line);
      }
    }
    else dbf->ximage = (XImage *) NULL;

  }

  clearTemp(dstbr);

  /* misc setup (similar to endScan(), but without unnecessary stuff) */
  eraseNumfiles(dstbr, oldnum);
  drawNumfiles(dstbr);
  computeScrlVals(dstbr, &maxv, &page);
  if (dstbr->scrl.val > maxv) dstbr->scrl.val = maxv;

  XClearArea(theDisp, dstbr->iconW, 0, 0, dstbr->iwWide, dstbr->iwHigh, True);
  SCSetRange(&dstbr->scrl, 0, maxv, dstbr->scrl.val, page);

  SetCursors(-1);
}

    
  

/***************************************************************/
static void scanDir(br)
     BROWINFO *br;
{
  /* loads contents of current working directory into BFIL structures...
   * and also loads up the MB list
   *
   * note:  when actually doing the code, in addition to stat'ing files, we
   * might want to try reading the first couple of bytes out of them, to see
   * what magicno they have, and putting up an appropriate icon for different
   * types of standard files.  Make this mechanism fairly clean and easily
   * extensible, as different machines will have different types of files,
   * and it's reasonable to expect folks to want to add their own bitmaps
   */

  int   i,j,k,oldbflen;
  BFIL *bf;

  DIR           *dirp;
  char          *dbeg, *dend;
  char          *dirnames[MAXDEEP];
  static char    path[MAXPATHLEN + 2] = { '\0' };

#ifdef NODIRENT
  struct direct *dp;
#else
  struct dirent *dp;
#endif


  br->lastIconClicked = -1;  /* turn off possibility of seeing a dblclick */
  setBrowStr(br,"");


  /********************************************************************/
  /*** LOAD UP the brdirMB information to reflect the new directory ***/
  /********************************************************************/


  
  GETWD(path);
  for (i=0; i<br->ndirs; i++) free(br->mblist[i]);  /* clear old dir names */

  if (path[strlen(path)-1] != '/') strcat(path,"/");   /* add trailing '/' */

  /* path will be something like: "/u3/bradley/src/weiner/whatever/" */

  dbeg = dend = path;
  for (i=0; i<MAXDEEP && dend; i++) {
    dend = (char *) strchr(dbeg,'/');  /* find next '/' char */

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

    dirnames[i] = dbeg;
    dbeg = dend+1;
  }
  br->ndirs = i-1;


  /* build brMBlist */
  for (i = br->ndirs-1,j=0; i>=0; i--,j++) {
    int stlen = (i<(br->ndirs-1)) ? dirnames[i+1] - dirnames[i] 
                                  : strlen(dirnames[i]);

    br->mblist[j] = (char *) malloc(stlen+1);
    if (!br->mblist[j]) FatalError("unable to malloc brMBlist[]");

    strncpy(br->mblist[j], dirnames[i], stlen);
    br->mblist[j][stlen] = '\0';
  }
    

  /* refresh the brdirMB button */
  br->dirMB.list  = br->mblist;
  br->dirMB.nlist = br->ndirs;

  XClearArea(theDisp, br->dirMB.win, br->dirMB.x, br->dirMB.y, 
	     br->dirMB.w+3, br->dirMB.h+3, False);

  i = StringWidth(br->mblist[0]) + 10;
  br->dirMB.x = br->dirMB.x + br->dirMB.w/2 - i/2;
  br->dirMB.w = i;
  MBRedraw(&br->dirMB);

  strcpy(br->path, path);   /* will have a trailing '/' character */


  /********************************************************************/
  /*** read the directory   (load up bfList)                        ***/
  /********************************************************************/


  WaitCursor();

  oldbflen = br->bfLen;

  freeBfList(br);   /* free all memory currently used by bfList structure */

  /* count how many files are in the list */

  dirp = opendir(".");
  if (!dirp) {  
    setBrowStr(br, "Couldn't read current directory.");
    SetCursors(-1);
    return;
  }

  while ( (dp = readdir(dirp)) != NULL) {
    if (strcmp(dp->d_name, ".") && 
	strcmp(dp->d_name, THUMBDIR)) br->bfLen++;
    if ((br->bfLen & 0x3f) == 0) WaitCursor();
  }


  if (br->bfLen) {
    int readcount, iconcount, statcount;

    br->bfList = (BFIL *) calloc(br->bfLen, sizeof(BFIL));

    if (!br->bfList) FatalError("can't create bfList! (malloc failed)\n");

    rewinddir(dirp);   /* back to beginning of directory */


    /* get info for each file in directory */

    readcount = 0;  iconcount = 0;  statcount = 0;
    for (i=0, bf=br->bfList; i<br->bfLen; i++,bf++) {

      drawTemp(br, i, br->bfLen);

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

      /* get next directory entry that isn't '.' or THUMBDIR 
         (or '..' if we're at the top level directory */

      do { dp = readdir(dirp); }
      while (dp && (strcmp(dp->d_name, ".")==0 || 
		    strcmp(dp->d_name, THUMBDIR)==0 ||
		    (br->ndirs==1 && strcmp(dp->d_name,"..")==0)));
      
      if (!dp) { br->bfLen = i;  break; }   /* dir got shorter... */


      scanFile(br, bf, dp->d_name);


      statcount++;
      if (bf->ftype == BF_HAVEIMG) iconcount++;
      if (bf->ftype == BF_FILE)    readcount++;

      if ((statcount && (statcount%100)==0) ||
	  (iconcount && (iconcount% 20)==0) ||
	  (readcount && (readcount% 20)==0)) {   /* mention progress */

	char tmp[64];

	sprintf(tmp, "Processed %d out of %d...", i+1, br->bfLen);
	setBrowStr(br, tmp);
      }
    }

    clearTemp(br);
  }

  closedir(dirp);

  endScan(br, oldbflen);
}


/***************************************************************/
static void endScan(br, oldnum)
     BROWINFO *br;
     int       oldnum;
{
  /* called at end of scanDir() and rescanDir() */

  int maxv, page;
  int w,h;

  setBrowStr(br,"");
  sortBFList(br);

  eraseNumfiles(br, oldnum);
  drawNumfiles(br);

  computeScrlVals(br, &maxv, &page);
  if (br->scrl.val>maxv) br->scrl.val = maxv;

  /* have to clear window as # of icons may have changed */
  w = br->iwWide;  h = br->iwHigh;
  if (ctrlColor) { w -= 4;  h -= 4; }
  if (w<1) w = 1;
  if (h<1) h = 1;

  XClearArea(theDisp, br->iconW, (ctrlColor) ? 2 : 0,
	                      (ctrlColor) ? 2 : 0, w, h, False);

  SCSetRange(&br->scrl, 0, maxv, br->scrl.val, page);
  
  SetCursors(-1);
}


/***************************************************************/
static void scanFile(br, bf, name)
     BROWINFO *br;
     BFIL *bf;
     char *name;
{
  /* given a pointer to an empty BFIL structure, and a filename,
     loads up the BFIL structure appropriately */

  struct stat    st;

  /* copy name */
  bf->name = (char *) malloc(strlen(name) + 1);

  if (!bf->name) FatalError("ran out of memory for bf->name");
  strcpy(bf->name, name);

  /* default icon values.  (in case 'stat' doesn't work) */
  bf->ftype  = BF_FILE;
  bf->w = br_file_width;  bf->h = br_file_height;
  bf->pimage = (byte *) NULL;
  bf->ximage = (XImage *) NULL;
  bf->lit    = 0;
	

  if (stat(bf->name, &st)==0) {
    bf->ftype = stat2bf(st.st_mode);
    if (bf->ftype == BF_FILE && (st.st_mode & 0111)) bf->ftype = BF_EXE;

    switch (bf->ftype) {
    case BF_DIR:  bf->w = br_dir_width;   bf->h = br_dir_height;   break;
    case BF_CHR:  bf->w = br_chr_width;   bf->h = br_chr_height;   break;
    case BF_BLK:  bf->w = br_blk_width;   bf->h = br_blk_height;   break;
    case BF_SOCK: bf->w = br_sock_width;  bf->h = br_sock_height;  break;
    case BF_FIFO: bf->w = br_fifo_width;  bf->h = br_fifo_height;  break;
    case BF_EXE:  bf->w = br_exe_width;   bf->h = br_exe_height;   break;
    }
  }


  loadThumbFile(br, bf);


  if (bf->ftype == BF_FILE || bf->ftype == BF_EXE) {
    /* if it's a regular file, with no thumbnail, try to determine what
       type of file it is */

    int filetype;

    filetype = ReadFileType(bf->name);

    switch (filetype) {
    case RFT_GIF:      bf->ftype = BF_GIF;      break;
    case RFT_PM:       bf->ftype = BF_PM;       break;
    case RFT_PBM:      bf->ftype = BF_PBM;      break;
    case RFT_XBM:      bf->ftype = BF_XBM;      break;
    case RFT_SUNRAS:   bf->ftype = BF_SUNRAS;   break;
    case RFT_BMP:      bf->ftype = BF_BMP;      break;
    case RFT_UTAHRLE:  bf->ftype = BF_UTAHRLE;  break;
    case RFT_IRIS:     bf->ftype = BF_IRIS;     break;
    case RFT_PCX:      bf->ftype = BF_PCX;      break;
    case RFT_JFIF:     bf->ftype = BF_JFIF;     break;
    case RFT_TIFF:     bf->ftype = BF_TIFF;     break;
    case RFT_PDSVICAR: bf->ftype = BF_PDS;      break;
    case RFT_COMPRESS: bf->ftype = BF_COMPRESS; break;
    case RFT_PS:       bf->ftype = BF_PS;       break;
    }
  }
}



/***************************************************************/
static unsigned long bfcompares;

static void sortBFList(br)
     BROWINFO *br;
{
  bfcompares = 0;
  qsort((char *) br->bfList, br->bfLen, sizeof(BFIL), bfnamCmp);
}


static int bfnamCmp(b1, b2)
     BFIL *b1, *b2;
{
  bfcompares++;
  if ((bfcompares & 0x7f)==0) WaitCursor();

  /* sort critera:  directories first, in alphabetical order, 
     followed by everything else, in alphabetical order */
  
  if ((b1->ftype == BF_DIR && b2->ftype == BF_DIR) ||
      (b1->ftype != BF_DIR && b2->ftype != BF_DIR)) 
    return strcmp(b1->name, b2->name);

  else if (b1->ftype == BF_DIR && b2->ftype != BF_DIR) return -1;
  else return 1;
}


/***************************************************************/
static void rescanDir(br)
     BROWINFO *br;
{
  /* chdir to br->path
   * build two name-lists, one holding the names of all files in the bfList,
   * and the second holding the names of all files in this directory
   * (ignore . and .. and THUMBDIR in both)
   *
   * sort the two namelists in pure-alpha order.
   * for each item in the first list, see if it has an entry in the second
   *   list.  If it does, remove the entry from *both* lists
   *
   * once that's done, we'll have a list of files that have been deleted
   *     (are in bfList, but not in directory)
   * and a list of files that have been created
   *     (aren't in bfList, are in directory)
   *
   * malloc a new temp bfList that has room for (bfLen - #del'd + #created)
   * entries, copy all entries from the old bfList (that aren't on the
   * deleted list) into the new list.  for each entry on the created list,
   * copy it to the new bfList and load its icon (not that it's likely to
   * have one, in which case fall back to the generic file-type icon).
   *
   * free data used by any remaining (deleted) entries in the old bfList
   *
   * call sortBfList to put the new bflist into the right order
   */

  int    i, j, bflen, dirlen, dnum, cmpval, newlen, n, oldlen;
  char **bfnames, **dirnames;
  BFIL  *newbflist, *bf;

  if (cdBrow(br)) return;

  WaitCursor();

  /* build 'bfnames' array */
  bflen = oldlen = br->bfLen;   bfnames = (char **) NULL;
  if (bflen) {
    bfnames = (char **) malloc(bflen * sizeof(char *));
    if (!bfnames) FatalError("couldn't alloc bfnames in rescanDir()");
    for (i=0; i<bflen; i++) {
      bfnames[i] = (char *) malloc(strlen(br->bfList[i].name) + 1);
      if (!bfnames[i]) FatalError("couldn't alloc bfnames in rescanDir()");
      
      strcpy(bfnames[i], br->bfList[i].name);
    }
  }

  WaitCursor();

  dirnames = getDirEntries(".", &dirlen);

  WaitCursor();

  /* note, either (or both) dirnames/bfnames can be NULL, in which case
     their respective 'len's will be zero */
  
  /* sort the two name lists */
  if (bflen)  qsort((char *) bfnames,  bflen,  sizeof(char *), namcmp);
  if (dirlen) qsort((char *) dirnames, dirlen, sizeof(char *), namcmp);

  /* run through the bflist, and delete entries common to both lists */
  for (i=0, dnum=0; i<bflen && dnum<dirlen; i++) {
    cmpval = strcmp(bfnames[i], dirnames[dnum]);
    if      (cmpval < 0) continue;
    else if (cmpval > 0) {       /* advance dnum, and try again */
      dnum++;
      i--;
    }
    else /* cmpval == 0 */ {     /* remove from both lists */
      free(bfnames[i]);  free(dirnames[dnum]);
      bfnames[i] = dirnames[dnum] = (char *) NULL;
      dnum++;
    }
  }


  WaitCursor();

  /* compress the lists, removing NULL entries, . .. and THUMBDIR */

  for (i=j=0; i<bflen; i++) {
    if (bfnames[i] && strcmp(bfnames[i],".") && strcmp(bfnames[i],"..") &&
	strcmp(bfnames[i],THUMBDIR)) {
      bfnames[j++] = bfnames[i];
    }
  }
  bflen = j;
  

  for (i=j=0; i<dirlen; i++) {
    if (dirnames[i] && strcmp(dirnames[i],".") && strcmp(dirnames[i],"..") &&
	strcmp(dirnames[i],THUMBDIR)) {
      dirnames[j++] = dirnames[i];
    }
  }
  dirlen = j;
  

  if (DEBUG) {
    fprintf(stderr,"%d files seem to have gone away:  ", bflen);
    for (i=0; i<bflen; i++) 
      fprintf(stderr,"%s ", bfnames[i]);
    fprintf(stderr,"\n\n");

    fprintf(stderr,"%d files seem to have appeared:  ", dirlen);
    for (i=0; i<dirlen; i++) 
      fprintf(stderr,"%s ", dirnames[i]);
    fprintf(stderr,"\n\n");
  }
   
  
  /* create a new bfList */
  newlen = br->bfLen - bflen + dirlen;  /* oldlen - #del'd + #created */
  if (newlen>0) {
    newbflist = (BFIL *) calloc(newlen, sizeof(BFIL));
    if (!newbflist) FatalError("couldn't malloc newbflist in rescanDir()");

    /* copy all entries from old bflist that aren't on deleted list into new */
    for (i=n=0, bf=br->bfList;  i<br->bfLen && n<newlen; i++, bf++) {
      for (j=0; j<bflen; j++) {
	if (strcmp(bf->name, bfnames[j])==0) break;
      }
      if (j == bflen) {   /* not in del list.  copy to new list */
	xvbcopy((char *) bf, (char *) &(newbflist[n++]),  sizeof(BFIL));
      }
      else {              /* in deleted list.  free all data for this entry */
	if (bf->name)    free(bf->name);
	if (bf->imginfo) free(bf->imginfo);
	if (bf->pimage)  free(bf->pimage);
	if (bf->ximage)  xvDestroyImage(bf->ximage);
      }
    }


    /* add all entries in the 'created' list */
    for (i=0; i<dirlen && n<newlen; i++) {
      scanFile(br, &newbflist[n++], dirnames[i]);
    }


    if (br->bfList) free(br->bfList);

    br->bfList = newbflist;
    br->bfLen  = (n < newlen) ? n : newlen;
  }
  else freeBfList(br);      /* dir is now empty */

  WaitCursor();


  /* free memory still in use by bfnames and dirnames arrays */
  for (i=0; i<bflen; i++)  { if (bfnames[i])  free(bfnames[i]); }
  for (i=0; i<dirlen; i++) { if (dirnames[i]) free(dirnames[i]); }
  if (bfnames) free(bfnames);
  if (dirnames) free(dirnames);

  endScan(br, oldlen);
}

/***************************************************************/
static void freeBfList(br)
     BROWINFO *br;
{
  int   i;
  BFIL *bf;

  if (br->bfList) {
    for (i=0, bf=br->bfList; i<br->bfLen; i++,bf++) {
      if ((i & 0x3f) == 0) WaitCursor();

      if (bf->name)    free(bf->name);
      if (bf->imginfo) free(bf->imginfo);
      if (bf->pimage)  free(bf->pimage);
      if (bf->ximage)  xvDestroyImage(bf->ximage);
    }
    
    free(br->bfList);
  }

  br->bfList = (BFIL *) NULL;
  br->bfLen  = 0;
  br->numlit = 0;
}


static int namcmp(s1, s2)
     char **s1, **s2;
{
  return strcmp(*s1,*s2);
}

/***************************************************************/
static char **getDirEntries(dir, lenP)
     char *dir;
     int  *lenP;
{
  /* loads up all directory entries into an array.  This *isn't* a great 
     way to do it, but I can't count on 'scandir()' existing on 
     every system.  Returns 'NULL' on failure, or pointer to array of
     'lenP' strings on success.  '.' and '..' ARE included in list */

  int    i, dirlen;
  DIR   *dirp;
  char **names;
#ifdef NODIRENT
  struct direct *dp;
#else
  struct dirent *dp;
#endif


  dirp = opendir(dir);
  if (!dirp) {
    SetISTR(ISTR_WARNING, "%s: %s", dir, ERRSTR(errno));
    *lenP = 0;
    return (char **) NULL;
  }


  /* count # of entries in dir */
  for (dirlen=0;  (dp = readdir(dirp)) != NULL;  dirlen++);
  if (!dirlen) {
    closedir(dirp);  
    *lenP = dirlen;
    return (char **) NULL; 
  }


  /* load up the entries, now that we know how many to make */
  names = (char **) malloc(dirlen * sizeof(char *));
  if (!names) FatalError("malloc failure in getDirEntries()");


  rewinddir(dirp);
  for (i=0; i<dirlen; i++) {
    dp = readdir(dirp);
    if (!dp) break;          /* dir got shorter.  can it do that?!? */

    names[i] = (char *) malloc(strlen(dp->d_name) + 1);
    if (!names[i]) FatalError("malloc failure in getDirEntries()");

    strcpy(names[i], dp->d_name);
  }
  
  if (i<dirlen) dirlen = i;     /* dir got shorter... */

  closedir(dirp);

  *lenP = dirlen;
  return names;
}



/***************************************************************/
static void computeScrlVals(br, max, page)
     BROWINFO *br;
     int *max, *page;
{
  /* called whenever bfList or size of icon window has changed */

  if (br->numWide<1) br->numWide = 1;    /* safety */

  br->numHigh = (br->bfLen + br->numWide-1) / br->numWide;  /* # icons high */

  *page = br->visHigh;
  *max  = br->numHigh - *page;
}






/***************************************************************/
static void genSelectedIcons(br)
     BROWINFO *br;
{
  int i, sval, first, numvis, cnt;

  setBrowStr(br, "");

  if (!br->bfList || !br->bfLen) return;

  if (cdBrow(br)) return;

  WaitCursor();

  for (i=cnt=0; i<br->bfLen; i++) {
    if (br->bfList[i].lit) {
      if (br->numlit) drawTemp(br, cnt, br->numlit);
      cnt++;
      makeIconVisible(br, i);
      eraseIcon(br, i);
      genIcon(br, &(br->bfList[i]));
      br->bfList[i].lit = 0;
      drawIcon(br, i);
    }
  }

  if (br->numlit) clearTemp(br);
  br->numlit = 0;
  changedNumLit(br, -1, 1);

  SetCursors(-1);
}


/***************************************************************/
static void genIcon(br, bf)
     BROWINFO *br;
     BFIL *bf;
{
  /* given a BFIL entry, load up the file.
   * if we succeeded in loading up the file, 
   *      generate an aspect-correct 8-bit image using brow Cmap
   * otherwise
   *      replace this icon with the BF_UNKNOWN, or BF_ERR icons
   */

  PICINFO pinfo;
  int     i, filetype;
  double  wexpand,hexpand;
  int     iwide, ihigh;
  byte   *icon24, *icon8;
  char    str[256], str1[256], *readname, uncompname[128];
  char    basefname[128];


  if (!bf || !bf->name || bf->name[0] == '\0') return;   /* shouldnt happen */
  str[0] = '\0';
  basefname[0] = '\0';
  pinfo.pic = (byte *) NULL;
  pinfo.comment = (char *) NULL;
  readname = bf->name;

  /* skip all 'special' files */
  if (!ISLOADABLE(bf->ftype)) return;

  filetype = ReadFileType(bf->name);

  if (filetype == RFT_COMPRESS) {
#ifndef VMS
    if (UncompressFile(bf->name, uncompname)) {
#else
    /* chop off trailing '.Z' from friendly displayed basefname, if any */
    strcpy (basefname, bf->name);
    *rindex (basefname, '.') = '\0';
    if (UncompressFile(basefname, uncompname)) {
#endif
      filetype = ReadFileType(uncompname);
      readname = uncompname;
    }
    else {
      sprintf(str, "Couldn't uncompress file '%s'", bf->name);
      setBrowStr(br, str);
      bf->ftype = BF_ERROR;
    }
  }

  /* get rid of comments.  don't need 'em */
  if (pinfo.comment) free(pinfo.comment);  pinfo.comment = (char *) NULL;

  if (filetype == RFT_ERROR) { 
    sprintf(str,"Couldn't open file '%s'", bf->name);
    setBrowStr(br, str);
    bf->ftype = BF_ERROR;
  }

  else if (filetype == RFT_UNKNOWN) {
    /* if it *was* an 'exe', leave it that way */
    if (bf->ftype != BF_EXE) bf->ftype = BF_UNKNOWN;
  }

  else {
    /* otherwise it's a known filetype... do the *hard* part now... */

    i = ReadPicFile(readname, filetype, &pinfo, 1);
    KillPageFiles(pinfo.pagebname, pinfo.numpages);

    if (!i) bf->ftype = BF_ERROR;

    if (i && (pinfo.w<=0 || pinfo.h<=0)) {        /* bogus size */
      bf->ftype = BF_ERROR;
      free(pinfo.pic); pinfo.pic = (byte *) NULL;
    }
  }

  /* get rid of comment, as we don't need it */
  if (pinfo.comment) { 
    free(pinfo.comment);  pinfo.comment = (char *) NULL;
  }

  /* if we made an uncompressed file, we can rm it now */
  if (readname != bf->name) unlink(readname);


  /* at this point either BF_ERROR, BF_UNKNOWN, BF_EXE or pic */

  if (!pinfo.pic) {
    if (bf->ftype == BF_EXE) return;  /* don't write thumbfiles for exe's */

    bf->w = br_file_width;  bf->h = br_file_height;
    writeThumbFile(br, bf, NULL, 0, 0, NULL);   /* BF_ERROR, BF_UNKNOWN */
    return;
  }

  /* at this point, we have a pic, so it must be an image file */

  /* compute size of icon  (iwide,ihigh) */
  
  wexpand = (double) pinfo.w / (double) ISIZE_WIDE;
  hexpand = (double) pinfo.h / (double) ISIZE_HIGH;

  if (wexpand >= 1.0 || hexpand >= 1.0) {   /* don't expand small icons */
    if (wexpand>hexpand) {
      iwide = (int) (pinfo.w / wexpand + 0.5);
      ihigh = (int) (pinfo.h / wexpand + 0.5);
    }
    else {
      iwide = (int) (pinfo.w / hexpand + 0.5);
      ihigh = (int) (pinfo.h / hexpand + 0.5);
    }
  }
  else {  /* smaller than ISIZE.  Leave it that way. */
    iwide = pinfo.w;  ihigh = pinfo.h;
  }


  /* generate icon */
  icon24 = Smooth24(pinfo.pic, pinfo.type==PIC24, pinfo.w, pinfo.h, 
		    iwide, ihigh, pinfo.r,pinfo.g,pinfo.b);
  if (!icon24) { bf->ftype = BF_FILE;  free(pinfo.pic); return; }

  sprintf(str, "%dx%d ", pinfo.w, pinfo.h);
  switch (filetype) {
  case RFT_GIF:      if (strstr(pinfo.shrtInfo, "GIF89")) 
                       strcat(str,"GIF89 file");
                     else
		       strcat(str,"GIF87 file");
                     break;

  case RFT_PM:       strcat(str,"PM file");               break;

  case RFT_PBM:      if (strstr(pinfo.fullInfo, "raw")) strcat(str,"Raw ");
                     else strcat(str,"Ascii ");
    
                     for (i=0; i<3 && strlen(pinfo.fullInfo)>3; i++) {
		       str1[0] = pinfo.fullInfo[i];  str1[1] = '\0';
		       strcat(str, str1);
		     }
         
                     strcat(str," file");
                     break;

  case RFT_XBM:      strcat(str,"X11 bitmap file");       break;
  case RFT_SUNRAS:   strcat(str,"Sun rasterfile");        break;
  case RFT_BMP:      strcat(str,"BMP file");              break;
  case RFT_UTAHRLE:  strcat(str,"Utah RLE file");         break;
  case RFT_IRIS:     strcat(str,"Iris RGB file");         break;
  case RFT_PCX:      strcat(str,"PCX file");              break;
  case RFT_JFIF:     strcat(str,"JPEG file");             break;
  case RFT_TIFF:     strcat(str,"TIFF file");             break;
  case RFT_PDSVICAR: strcat(str,"PDS/VICAR file");        break;
  case RFT_PS:       strcat(str,"PostScript file");       break;
  default:           strcat(str,"file of unknown type");  break;
  }
  
  
  /* find out length of original file */
  {  FILE *fp;
     long  filesize;
     char  buf[64];
     
     fp = fopen(bf->name, "r");
     if (fp) {
       fseek(fp, 0L, 2);
       filesize = ftell(fp);
       fclose(fp);
       
       sprintf(buf,"  (%ld bytes)", filesize);
       strcat(str, buf);
     }
   }


  sprintf(str1, "%s:  %s", bf->name, str);
  setBrowStr(br, str1);

  /* dither 24-bit icon into 8-bit icon (using 3/3/2 cmap) */
  icon8 = DoColorDither(icon24, NULL, iwide, ihigh, NULL, NULL, NULL,
			browR, browG, browB, 256);
  if (!icon8) { bf->ftype = BF_FILE;  free(icon24); free(pinfo.pic); return; }

  writeThumbFile(br, bf, icon8, iwide, ihigh, str);

  /* have to make a *copy* of str */
  if (strlen(str)) {
    bf->imginfo = (char *) malloc(strlen(str)+1);
    if (bf->imginfo) strcpy(bf->imginfo, str);
  }
  else bf->imginfo = (char *) NULL;
  
  bf->pimage  = icon8;
  bf->w       = iwide;
  bf->h       = ihigh;
  bf->ftype   = BF_HAVEIMG;
  
  bf->ximage = Pic8ToXImage(icon8, iwide, ihigh, browcols,
			    browR, browG, browB);
  
  free(icon24);
  free(pinfo.pic);
}



    


/*
 *  THUMBNAIL FILE FORMAT:
 *
 * <magic number 'P7 332' >
 * <comment identifying version of XV that wrote this file>
 * <comment identifying type & size of the full-size image>
 * <OPTIONAL comment identifying this as a BUILT-IN icon, in which case
 *    there is no width,height,maxval info, nor any 8-bit data>
 * <comment signifying end of comments>
 * <width, height, and maxval of this file >
 * <raw binary 8-bit data, in 2/2/2 Truecolor format>
 *
 * Example:
 *    P7 332
 *    #XVVERSION:Version 2.28  Rev: 9/26/92
 *    #IMGINFO:512x440 Color JPEG
 *    #END_OF_COMMENTS
 *    48 40 255
 *    <binary data>
 *
 * alternately:
 *    P7 332
 *    #XVVERSION:Version 2.28 Rev: 9/26/92
 *    #BUILTIN:UNKNOWN
 *    #IMGINFO:
 *    #END_OF_COMMENTS
 */



/***************************************************************/
static void loadThumbFile(br, bf)
     BROWINFO *br;
     BFIL *bf;
{
  /* determine if bf has an associated thumbnail file.  If so, load it up,
     and create the ximage, and such */

  FILE *fp;
  char  thFname[512];
  char  buf[256], *st, *info;
  int   w,h,mv,i,builtin;
  byte *icon8;

  info = NULL;  icon8 = NULL;  builtin = 0;

  sprintf(thFname, "%s%s/%s", br->path, THUMBDIR, bf->name);

  fp = fopen(thFname, "r");
  if (!fp) return;            /* nope, it doesn't have one */

  /* read in the file */
  if (!fgets(buf, 256, fp)) goto errexit;

  if (strncmp(buf, "P7 332", 6)) goto errexit;


  /* read comments until we see '#END_OF_COMMENTS', or hit EOF */
  while (1) {
    if (!fgets(buf, 256, fp)) goto errexit;
    
    if      (!strncmp(buf, "#END_OF_COMMENTS", strlen("#END_OF_COMMENTS")))
      break;

    else if (!strncmp(buf, "#XVVERSION:", strlen("#XVVERSION:"))) {
      /* probably should check for compatibility, or something... */
    }

    else if (!strncmp(buf, "#BUILTIN:", strlen("#BUILTIN:"))) {
      builtin = 1;
      st = (char *) strchr(buf, ':') + 1;
      if (strcmp(st, "ERROR")==0)   bf->ftype = BF_ERROR;
      else bf->ftype = BF_UNKNOWN;
    }

    else if (!strncmp(buf, "#IMGINFO:", strlen("#IMGINFO:"))) {
      st = (char *) strchr(buf, ':') + 1;
      info = (char *) malloc(strlen(st) + 1);
      if (info) strcpy(info, st);
    }
  }


  if (builtin) {
    bf->imginfo = info;
    fclose(fp);
    return;
  }



  /* read width, height, maxval */
  if (!fgets(buf, 256, fp) || sscanf(buf, "%d %d %d", &w, &h, &mv) != 3) 
    goto errexit;


  if (w>ISIZE_WIDE || h>ISIZE_HIGH || w<1 || h<1 || mv != 255) {
    sprintf(buf,"Bogus thumbnail file for '%s'.  Skipping.", bf->name);
    setBrowStr(br, buf);
    goto errexit;
  }


  /* read binary data */
  icon8 = (byte *) malloc(w * h);
  if (!icon8) goto errexit;

  i = fread(icon8, 1, w*h, fp);
  if (i != w*h) goto errexit;

  if (icon8) {
    bf->pimage  = icon8;
    bf->w       = w;
    bf->h       = h;
    bf->ftype   = BF_HAVEIMG;
    bf->imginfo = info;
    
    bf->ximage = Pic8ToXImage(icon8, w, h, browcols, browR, browG, browB);
  }
  else {
    if (info) free(info);
  }
  
  fclose(fp);
  return;


 errexit:
  fclose(fp);
  if (info) free(info);
  if (icon8) free(icon8);
}


  
/***************************************************************/
static void writeThumbFile(br, bf, icon8, w, h, info)
     BROWINFO *br;
     BFIL *bf;
     byte *icon8;
     int   w,h;
     char *info;
{
  FILE *fp;
  char  thFname[512], buf[256];
  int   i;


  /* make the THUMBDIR directory.  No harm if it's already created */
  sprintf(thFname, "%s%s", br->path, THUMBDIR);
  mkdir(thFname, 0755);  /* ignore failures, as it fails if dir exists */

  sprintf(thFname, "%s%s/%s", br->path, THUMBDIR, bf->name);

  fp = fopen(thFname, "w");
  if (!fp) {
    sprintf(buf, "Can't create thumbnail file '%s':  %s", thFname, 
	    ERRSTR(errno));
    setBrowStr(br, buf);
    return;            /* can't write... */
  }


  /* write the file */
  fprintf(fp, "P7 332\n");
  fprintf(fp, "#XVVERSION:%s\n", REVDATE);

  if (icon8) {
    fprintf(fp, "#IMGINFO:%s\n", (info) ? info : "");
  }
  else {
    fprintf(fp, "#BUILTIN:");
    switch (bf->ftype) {
    case BF_ERROR:   fprintf(fp,"ERROR\n");    break;
    case BF_UNKNOWN: fprintf(fp,"UNKNOWN\n");  break;
    default:         fprintf(fp,"UNKNOWN\n");  break;
    }

    fprintf(fp, "#IMGINFO:%s\n", (info) ? info : "");
  }

  fprintf(fp, "#END_OF_COMMENTS\n");

  if (icon8) {
    fprintf(fp, "%d %d %d\n", w, h, 255);

    /* write the raw data */
    fwrite(icon8, 1, w*h, fp);
  }

  if (ferror(fp)) {  /* error occurred */
    fclose(fp);
    unlink(thFname);  /* delete it */
    sprintf(buf, "Can't write thumbnail file '%s':  %s", thFname,
	    ERRSTR(errno));
    setBrowStr(br, buf);
    return;            /* can't write... */
  }
  
  fclose(fp);
}


/***************************************************************/
static void updateIcons(br)
     BROWINFO *br;
{
  /* for each file in the bfList, see if it has an icon file.
   *    if it doesn't, generate one
   *    if it does, check the dates.  If the pic file is newer, regen icon
   *
   * for each file in the current directory's thumbnail directory,
   *   see if there's a corresponding pic file.  If not, delete the
   *   icon file
   */

  int            i, iconsBuilt, iconsKilled, statcount;
  char           tmpstr[128];
  BFIL          *bf;
  DIR           *dirp;
#ifdef NODIRENT
  struct direct *dp;
#else
  struct dirent *dp;
#endif


  iconsBuilt = iconsKilled = statcount = 0;


  /* make the THUMBDIR directory.  No harm if it's already there... */
  mkdir(THUMBDIR, 0755);


  /* okay, we're in the right directory.  run through the bfList, and look
     for corresponding thumbnail files */

  WaitCursor();

  for (i=0, bf=br->bfList; i<br->bfLen; i++, bf++) {
    if (bf->ftype <= BF_FILE || bf->ftype >= BF_ERROR || bf->ftype==BF_EXE) {

      /* ie, not a 'special' file */

      int  s1, s2;
      char thfname[256];
      struct stat filest, thumbst;

      drawTemp(br, i, br->bfLen);

      s1 = stat(bf->name, &filest);

      /* see if this file has an associated thumbnail file */
      sprintf(thfname, "%s/%s", THUMBDIR, bf->name);
      s2 = stat(thfname, &thumbst);

      if (s1 || s2 || filest.st_mtime > thumbst.st_mtime) {
	/* either stat'ing the file or the thumbfile failed, or 
	   both stat's succeeded and the thumb is older than the pic */

	/* free info in 'bf', as it'll get overwritten in genIcon() */
	if (bf->imginfo) free          (bf->imginfo);
	if (bf->pimage)  free          (bf->pimage);
	if (bf->ximage)  xvDestroyImage(bf->ximage);

	bf->imginfo = (char *)   NULL;
	bf->pimage  = (byte *)   NULL;
	bf->ximage  = (XImage *) NULL;

	makeIconVisible(br, i);
	eraseIcon(br, i);
	genIcon(br, bf);
	drawIcon(br, i);

	if (bf->ftype != BF_EXE) {
	  iconsBuilt++;
	  if (DEBUG) 
	    fprintf(stderr,"icon made:fname='%s' thfname='%s' %d,%d,%ld,%ld\n",
		    bf->name, thfname, s1,s2,filest.st_mtime,thumbst.st_mtime);
	}
      }
    }
    statcount++;

    if ((statcount % 30)==0) WaitCursor();

    if ((statcount  && (statcount % 100)==0) ||
	(iconsBuilt && (iconsBuilt % 20)==0)) {

      sprintf(tmpstr, "Processed %d out of %d...", i+1, br->bfLen);
      setBrowStr(br, tmpstr);
    }
  }

  clearTemp(br);



  /* search the THUMBDIR directory, looking for thumbfiles that don't have
     corresponding pic files.  Delete those. */

  setBrowStr(br, "Scanning for excess icon files...");

  statcount = 0;
  dirp = opendir(THUMBDIR);
  if (dirp) {
    while ( (dp = readdir(dirp)) != NULL) {
      char thfname[256];
      struct stat filest, thumbst;

      /* stat this directory entry to make sure it's a plain file */
      sprintf(thfname, "%s/%s", THUMBDIR, dp->d_name);
      if (stat(thfname, &thumbst)==0) {  /* success */
	int tmp;
	tmp  = stat2bf(thumbst.st_mode);

	if (tmp == BF_FILE) {  /* a plain file */
	  /* see if this thumbfile has an associated pic file */
	  if (stat(dp->d_name, &filest)) {  /* failed!: guess it doesn't */
	    if (unlink(thfname)==0) iconsKilled++;
	  }
	}
      }
      statcount++;
      
      if ((statcount % 30)==0) WaitCursor();
    }
    closedir(dirp);
  }

  SetCursors(-1);

  sprintf(tmpstr, "Update finished:  %d icon%s created, %d icon%s deleted.",
	  iconsBuilt,  (iconsBuilt ==1) ? "" : "s",
	  iconsKilled, (iconsKilled==1) ? "" : "s");
  setBrowStr(br, tmpstr);

  drawIconWin(0, &(br->scrl));   /* redraw icon window */
}


/*******************************************/
static void drawTemp(br, cnt, maxcnt)
     BROWINFO *br;
     int       cnt, maxcnt;
{
  if (maxcnt<1) return;   /* none of that naughty ol' divide by zero stuff */

  DrawTempGauge(br->win, 5, br->dirMB.y, br->dirMB.x-10, br->dirMB.h,
		(double) cnt / (double) maxcnt,
		browfg, browbg, browhi, browlo);
}
 
static void clearTemp(br)
  BROWINFO *br;
{
  XClearArea(theDisp, br->win, 5, br->dirMB.y, 
	     br->dirMB.x-10+1, br->dirMB.h + 1, True);
}




/*******************************************/
static void doTextCmd(br)
     BROWINFO *br;
{
  int i;

  if (!br->bfLen || !br->bfList || !br->numlit) return;

  for (i=0; i<br->bfLen && !br->bfList[i].lit; i++);
  if (i==br->bfLen) return;    /* shouldn't happen */

  if (cdBrow(br)) return;
  TextView(br->bfList[i].name);
}


/*******************************************/
static void doRenameCmd(br)
     BROWINFO *br;
{
  /* called when one (and *only* one!) item is lit in the current br.
     pops up a 'what do you want to rename it to' box, and attempts to
     do the trick... */

  int  i, num;
  char buf[128], txt[256], *origname, txt1[256];
  static char *labels[] = { "\nOk", "\033Cancel" };
  struct stat st;

  if (cdBrow(br)) return;

  /* find the selected file */
  for (i=0; i<br->bfLen && !br->bfList[i].lit; i++);
  if (i==br->bfLen) return;    /* shouldn't happen */
  
  origname = br->bfList[i].name;   num = i;

  if (strcmp(origname, "..")==0) {
    sprintf(buf,"Sorry, you can't rename the parent directory, %s",
	    "for semi-obvious reasons.");
    ErrPopUp(buf, "\nRight.");
    return;
  }

  sprintf(txt, "Enter a new name for the %s '%s':", 
	  (br->bfList[i].ftype==BF_DIR) ? "directory" : "file",
	  origname);

  strcpy(buf, origname);
  i = GetStrPopUp(txt, labels, 2, buf, 128, "/ |\'\"<>,", 0);
  if (i) return;     /* cancelled */


  if (strcmp(origname, buf)==0) return;


  /* see if the desired file exists, and attempt to do the rename if
     it doesn't.  On success, free bfList[].name, realloc with the new name,
     and redraw that icon.  If any other br's are pointed to same dir,
     do rescan's in them */

  /* this will also pick up mucking around with '.' and '..' */
  if (stat(buf, &st) == 0) {   /* successful stat:  new name already exists */
    sprintf(txt,"Sorry, a file or directory named '%s' already exists.",buf);
    ErrPopUp(txt, "\nOh!");
    return;
  }

  /* try to rename the file */
  if (rename(origname, buf) < 0) {
    sprintf(txt, "Error renaming '%s' to '%s':  %s",
	    origname, buf, ERRSTR(errno));
    ErrPopUp(txt, "\nSo what!");
    return;
  }


  /* try to rename it's thumbnail file, if any.  Ignore errors */
  sprintf(txt,  "%s/%s", THUMBDIR, origname);
  sprintf(txt1, "%s/%s", THUMBDIR, buf);
  rename(txt, txt1);



  free(br->bfList[num].name);
  br->bfList[num].name = (char *) malloc(strlen(buf) + 1);
  if (br->bfList[num].name) strcpy(br->bfList[num].name, buf);
                       else FatalError("out of memory in doRenameCmd");

  eraseIconTitle(br, num);
  drawIcon(br, num);

  for (i=0; i<MAXBRWIN; i++) {
    if (&binfo[i] != br && strcmp(binfo[i].path, br->path)==0) 
      rescanDir(&binfo[i]);
  }

  DIRCreatedFile(br->path);
}




/*******************************************/
static void doMkdirCmd(br)
     BROWINFO *br;
{
  /* called at any time (doesn't have anything to do with current selection)
     pops up a 'what do you want to call it' box, and attempts to
     do the trick... */

  int          i;
  char         buf[128], txt[256];
  static char *labels[] = { "\nOk", "\033Cancel" };
  struct stat  st;

  if (cdBrow(br)) return;

  buf[0] = '\0';
  i = GetStrPopUp("Enter name for new directory:", labels, 2, 
		  buf, 128, "/ |\'\"<>,", 0);
  if (i) return;     /* cancelled */

  if (strlen(buf)==0) return;    /* no name entered */

  /* make sure they haven't tried to create '.' or '..' (can't be filtered) */
  /* see if the file exists already, complain and abort if it does */

  if (strcmp(buf,".")==0 || strcmp(buf,"..")==0 ||
      stat(buf, &st)==0) {
    sprintf(txt,"Sorry, a file or directory named '%s' already exists.",buf);
    ErrPopUp(txt, "\nZoinks!");
    return;
  }


  /* if it doesn't, do the mkdir().  On success, need to do a rescan of
     cwd, and any other br's pointing at same directory */
  if (mkdir(buf, 0755) < 0) {
    sprintf(txt, "Error creating directory '%s':  %s", buf, ERRSTR(errno));
    ErrPopUp(txt, "\nEat me!");
    return;
  }


  /* rescan current br, and all other br's pointing to same directory */
  for (i=0; i<MAXBRWIN; i++) {
    if (strcmp(binfo[i].path, br->path)==0) 
      rescanDir(&binfo[i]);
  }

  DIRCreatedFile(br->path);
}





/*******************************************/
static void doDeleteCmd(br)
     BROWINFO *br;
{
  /* if '..' is lit, turn it off and mention that you can't delete it.
   *
   * count # of lit files and lit directories.  Prompt with an
   * appropriate 'Are you sure?' box
   *
   * if we're proceeding, delete the non-dir files, and their thumbnail
   *    buddies, if any (by calling rm_file() )
   * call 'rm_dir()' for each of the directories
   */

  BFIL  *bf;
  int    i, j, numdirs, numfiles, slen, firstdel;
  char   buf[512];
  static char *yesno[]  = { "\004Delete", "\033Cancel" };

  if (!br->bfLen || !br->bfList || !br->numlit) return;

  if (cdBrow(br)) return;     /* can't cd to this directory.  screw it! */

  if (br->bfList[0].lit && strcmp(br->bfList[0].name,"..")==0) {
    br->numlit--;
    br->bfList[0].lit = 0;
    changedNumLit(br, -1, 0);

    sprintf(buf,"Sorry, but you can't delete the parent directory, %s",
	    "for semi-obvious reasons.");
    ErrPopUp(buf, "\nRight.");
    if (!br->numlit) {               /* turned off only lit file.  return */
      drawIcon(br, 0);
      return;
    }
  }

  numdirs = numfiles = 0;  firstdel = -1;
  for (i=0, bf=br->bfList; i<br->bfLen; i++,bf++) {
    if (bf->lit) {
      if (firstdel == -1) firstdel = i;
      if (bf->ftype == BF_DIR) numdirs++;
      else numfiles++;
    }
  }


  /* if any plain files are being toasted, bring up the low-key 
     confirmation box */

  if (numfiles) {
    sprintf(buf,"Delete file%s:  ", numfiles>1 ? "s" : "");
    slen = strlen(buf);

    for (i=0, bf=br->bfList;  i<br->bfLen;  i++,bf++) {
      if (bf->lit && bf->ftype != BF_DIR) {
	if ( (slen + strlen(bf->name) + 1) > 256) {
	  strcat(buf,"...");
	  break;
	}
	else {
	  strcat(buf, bf->name);   slen += strlen(bf->name);
	  strcat(buf, " ");        slen++;
	}
      }
    }

    i = PopUp(buf, yesno, 2);
    if (i) return;              /* cancelled */
  }
     

  /* if any directories are being toasted, bring up the are you REALLY sure
     confirmation box */

  if (numdirs) {
    sprintf(buf,"Recursively delete director%s:  ", numdirs>1 ? "ies" : "y");
    slen = strlen(buf);

    for (i=0, bf=br->bfList;  i<br->bfLen;  i++,bf++) {
      if (bf->lit && bf->ftype == BF_DIR) {
	if ( (slen + strlen(bf->name) + 1) > 256) {
	  strcat(buf,"...");
	  break;
	}
	else {
	  strcat(buf, bf->name);   slen += strlen(bf->name);
	  strcat(buf, " ");        slen++;
	}
      }
    }

    i = PopUp(buf, yesno, 2);
    if (i) return;              /* cancelled */
  }


  /* okay, at this point they've been warned.  do the deletion */
  
  for (i=0, bf=br->bfList;  i<br->bfLen;  i++,bf++) {
    if (bf->lit) {
      if (bf->ftype == BF_DIR) rm_dir (br, bf->name);
                          else rm_file(br, bf->name);
    }
  }

  /* rescan br, as it's probably changed */
  rescanDir(br);

  /* recompute br->numlit */
  for (i=br->numlit=0; i<br->bfLen; i++) {
    if (br->bfList[i].lit) br->numlit++;
  }
  changedNumLit(br, -1, 0);


  /* if deleted a single file, select the icon that's in the same position */
  if (numfiles + numdirs == 1) {
    if (!br->bfList[firstdel].lit) {
      br->bfList[firstdel].lit = 1;
      br->numlit++;
      drawIcon(br, firstdel);
      makeIconVisible(br, firstdel);
      changedNumLit(br, firstdel, 0);
    }
  }

  /* rescan other br's that are looking at this directory */
  for (i=0; i<MAXBRWIN; i++) {
    if (&binfo[i] != br && strcmp(binfo[i].path, br->path)==0) 
      rescanDir(&binfo[i]);
  }

  DIRDeletedFile(br->path);
}


/*******************************************/
static void rm_file(br, name)
     BROWINFO *br;
     char *name;
{
  /* unlinks specified file.  br only needed to display potential err msg */

  int   i;
  char buf[512], buf1[512], *tmp;

  if (DEBUG) fprintf(stderr,"rm %s", name);

  i = unlink(name);
  if (i) {
    sprintf(buf, "rm %s: %s", name, ERRSTR(errno));
    setBrowStr(br, buf);
  }

  /* try to delete a thumbnail file, as well.  ignore errors */
  strcpy(buf1, name);          /* tmp1 = leading path of name */
  tmp = rindex(buf1, '/');
  if (!tmp) strcpy(buf1,".");
  else *tmp = '\0';
  
  sprintf(buf, "%s/%s/%s", buf1, THUMBDIR, BaseName(name));
  if (DEBUG) fprintf(stderr,"   (%s)\n", buf);

  unlink(buf);
}

static char rmdirPath[MAXPATHLEN+1];

/*******************************************/
static void rm_dir(br, dname)
     BROWINFO *br;
     char *dname;
{
  /* called to remove top-level dir.  All subdirs are handled by rm_dir1() */

  strcpy(rmdirPath, dname);
  rm_dir1(br);
}

static void rm_dir1(br)
     BROWINFO *br;
{
  /* recursively delete this directory, and all things under it */
  
  int    i, dirlen, longpath, oldpathlen;
  char **names, *name, buf[512];
  struct stat st;

  if (DEBUG) fprintf(stderr,"rm %s\n", rmdirPath);

  longpath = 0;
  oldpathlen = strlen(rmdirPath);

  /* delete all plain files under this directory */
  names = getDirEntries(rmdirPath, &dirlen);

  if (names && dirlen) {
    /* we've got the names of all files & dirs in this directory.  rm the
       non-subdirectories first */

    for (i=0; i<dirlen; i++) {
      name = names[i];

      /* skip . and .. (not that we should ever see them... */
      if (name[0] == '.' && (name[1]=='\0' || 
			     (name[1]=='.' && name[2]=='\0'))) goto done;

      if (strlen(name) + oldpathlen >= (MAXPATHLEN-3)) {
	longpath = 1;
	goto done;
      }

      strcat(rmdirPath, "/");
      strcat(rmdirPath, name);

      if (stat(rmdirPath, &st) < 0) {
	sprintf(buf, "%s: %s", name, ERRSTR(errno));
	setBrowStr(br, buf);
	rmdirPath[oldpathlen] = '\0';
	goto done;
      }
	
      if (stat2bf(st.st_mode) == BF_DIR) {  /* skip, for now */
	rmdirPath[oldpathlen] = '\0';
	continue;   /* don't remove from list */
      }

      rm_file(br, rmdirPath);
      rmdirPath[oldpathlen] = '\0';
      
    done:     /* remove name from list */
      free(name);
      names[i] = (char *) NULL;
    }


    /* rm subdirectories, the only things left in the list */
    for (i=0; i<dirlen; i++) {
      if (!names[i]) continue;    /* loop until next name found */
      name = names[i];

      if (strlen(name) + oldpathlen >= (MAXPATHLEN-3)) {
	longpath = 1;
	continue;
      }

      strcat(rmdirPath, "/");
      strcat(rmdirPath, name);

      rm_dir1(br);     /* RECURSE! */

      rmdirPath[oldpathlen] = '\0';
    }

    /* empty rest of namelist */
    for (i=0; i<dirlen; i++) {
      if (names[i]) free(names[i]);
    }
    free(names);
  }

  if (longpath) setBrowStr(br, "Path too long error!");

  i = rmdir(rmdirPath);
  if (i < 0) {
    sprintf(buf, "rm %s: %s", rmdirPath, ERRSTR(errno));
    setBrowStr(br, buf);
  }
}




static int overwrite;
#define OWRT_ASK    0
#define OWRT_NOASK  1
#define OWRT_CANCEL 2


/*******************************************/
static void dragFiles(srcBr, dstBr, srcpath, dstpath, dstdir, 
		      names, nlen, cpymode)
     BROWINFO *srcBr, *dstBr;
     char     *srcpath, *dstpath, *dstdir, **names;
     int       nlen, cpymode;
{
  /* move or copy file(s) and their associated thumbnail files.  
     srcpath and dstpath will have trailing '/'s.  dstdir is name of
     folder in dstpath (or "." or "..") to write to.  names is an nlen
     long array of strings (the simple filenames of the files to move)
     if 'cpymode' copy files, otherwise move them */

  int  i, j, k, dothumbs, fail;
  char dstp[MAXPATHLEN + 1];
  char src[MAXPATHLEN+1], dst[MAXPATHLEN+1];
  char buf[128];
  struct stat st;


  /* build real destination dir */
  strcpy(dstp, dstpath);

  /* note dstpath of "/" and dstdir of ".." will never happen, as there's
     no parent folder shown when in the root directory */

  if (strcmp(dstdir,"..")==0) {  /* lop off last pathname component */
    for (i=strlen(dstp)-2;  i>0 && dstp[i]!='/';  i--);
    i++;
    dstp[i] = '\0';
  }
  else if (strcmp(dstdir,".")!=0) sprintf(dstp, "%s%s/", dstpath, dstdir);



  /* if there is a thumbnail directory in 'srcpath', make one for dstpath */
  sprintf(src,"%s%s", srcpath, THUMBDIR);
  dothumbs = 0;
  if (stat(srcpath, &st)==0) {
    sprintf(dst,"%s%s", dstp, THUMBDIR);
    mkdir(dst, 0755);
    dothumbs = 1;
  }


  overwrite = OWRT_ASK;

  if (nlen>1) {
    if (cpymode) setBrowStr(srcBr, "Copying files...");
            else setBrowStr(srcBr, "Moving files...");
  }

  for (i=fail=0; i<nlen; i++) {
    WaitCursor();
    /* progress report */
    if (nlen>1) drawTemp(srcBr, i, nlen);

    if (strcmp(names[i], "..")==0) continue;  /* don't move parent */

    sprintf(src,"%s%s", srcpath, names[i]);
    sprintf(dst,"%s%s", dstp,    names[i]);

    if (cpymode) j = copyFile(src,dst);
            else j = moveFile(src,dst);

    if (overwrite == OWRT_CANCEL) break;         /* abort move */
    if (j==1) fail++;

    if (dothumbs && j==0) {
      sprintf(src,"%s%s/%s", srcpath, THUMBDIR, names[i]);
      sprintf(dst,"%s%s/%s", dstp,    THUMBDIR, names[i]);

      /* delete destination thumbfile to avoid 'overwrite' warnings */
      unlink(dst);

      if (cpymode) j = copyFile(src,dst);
              else j = moveFile(src,dst);
    }
  }

  if (nlen>1) clearTemp(srcBr);


  /* update icon windows appropriately */
  for (i=0; i<MAXBRWIN; i++) {
    if (strcmp(binfo[i].path, srcpath)==0 ||
	strcmp(binfo[i].path, dstp)==0) {
      rescanDir(&binfo[i]);
    }
  }

  /* update directory window (load/save) */
  sprintf(src, "%sbozo", srcpath);
  DIRDeletedFile(src);
  sprintf(src, "%sbozo", dstp);
  DIRCreatedFile(src);

  if (dothumbs) {
    sprintf(src, "%s%s/bozo", srcpath, THUMBDIR);
    DIRDeletedFile(src);
    sprintf(src, "%s%s/bozo", dstp, THUMBDIR);
    DIRCreatedFile(src);
  }


  /* clear all files in the destination folder */
  for (i=0; i<dstBr->bfLen; i++) { 
    dstBr->bfList[i].lit = 0;
  }
  dstBr->numlit = 0;

  /* light all named files in the destination folder */
  for (i=0; i<nlen; i++) {
    char *name;  BFIL *bf;
    name = names[i];
    for (j=0, bf=dstBr->bfList; 
	 j<dstBr->bfLen && strcmp(name, bf->name)!=0; j++, bf++);
    if (j<dstBr->bfLen) { 
      bf->lit = 1;  dstBr->numlit++; 
    }
  }

  /* scroll so first lit file is visible */
  for (i=0; i<dstBr->bfLen && !dstBr->bfList[i].lit; i++);
  if (i<dstBr->bfLen) makeIconVisible(dstBr, i);

  drawIconWin(0, &(dstBr->scrl));    /* redraw dst window */
  changedNumLit(dstBr, -1, 0);


  if (fail) sprintf(buf, "Some files were not %s because of errors.", 
		    cpymode ? "copied" : "moved");

  else if (nlen>1) sprintf(buf, "%d files %s", nlen, 
			   (cpymode) ? "copied" : "moved");
  else sprintf(buf,"");
  setBrowStr(srcBr, buf);

  SetCursors(-1);
}



/*************************************************/
static int moveFile(src,dst)
     char *src, *dst;
{
  /* essentially the same as the 'mv' command.  src and dst are full
     pathnames.  It's semi-quiet about errors.  a non-existant src file is
     *not* considered an error (as we don't check for thumbfiles to exist
     before calling this function).  Returns '1' on error, '0' if ok.
     Returns '-1' on 'skip this'

     One bit of noise:  if destination file exists, pop up a Overwrite?
     warning box.  */

  int         i, srcdir, dstdir;
  struct stat st;
  char        buf[512];
  static char  *owbuts[4]  = { "\nOk", "dDon't ask", "nNo", "\033Cancel" };

  if (DEBUG) fprintf(stderr,"moveFile %s %s\n", src, dst);

  if (stat(src, &st)) return 0;    /* src doesn't exist, it would seem */
  srcdir = (stat2bf(st.st_mode) == BF_DIR);

  /* see if destination exists */
  if (stat(dst, &st)==0) {
    dstdir = (stat2bf(st.st_mode) == BF_DIR);

    if (overwrite==OWRT_ASK) {
      sprintf(buf, "%s '%s' exists.\n\nOverwrite?", 
	      dstdir ? "Directory" : "File", dst);
      i = PopUp(buf, owbuts, 4);

      if      (i==1) overwrite = OWRT_NOASK;
      else if (i==2) return -1;
      else if (i==3) { overwrite = OWRT_CANCEL;  return 1; }
    }

    if (dstdir) {
#ifndef VMS  /* we don't delete directories in VMS */
      sprintf(buf, "rm -rf %s", dst);
      if (system(buf)) {     /* okay, so it's cheating... */
	SetISTR(ISTR_WARNING, "Unable to remove directory %s", dst);
	return 1;
      }
#endif /* VMS */
    }
    else if (unlink(dst)) {
      SetISTR(ISTR_WARNING, "unlink %s: %s", dst, ERRSTR(errno));
      return 1;
    }
  }

  
  if (!rename(src, dst)) return 0;   /* Ok */
  if (errno != EXDEV) return 1;      /* failure, of some sort */

  /* We're crossing filesystem boundaries.  Copy the file and rm the
     original */

  i = copyFile(src, dst);
  if (i == 0) {    /* copied okay, kill the original */
    if (srcdir) {
#ifndef VMS   /* we don't delete directories in VMS */
      sprintf(buf, "rm -rf %s", src);
      if (system(buf)) {     /* okay, so it's cheating... */
	SetISTR(ISTR_WARNING, "Unable to remove directory %s", dst);
	return 1;
      }
#endif /* VMS */
    }
    else if (unlink(src)) {
      SetISTR(ISTR_WARNING, "unlink %s: %s", src, ERRSTR(errno));
      return 1;
    }
  }

  return i;
}


/* needed to recursively copy directories */
static int  userMask, copyerr;
static char cpSrcPath[MAXPATHLEN], cpDstPath[MAXPATHLEN];


/*************************************************/
static int copyFile(src,dst)
     char *src, *dst;
{
  /* src and dst are full
     pathnames.  It's semi-quiet about errors.  a non-existant src file is
     *not* considered an error (as we don't check for thumbfiles to exist
     before calling this function).  Returns '1' on error, '0' if ok.

     Will be called with full filenames of source and destination, as in:
     src="/usr/bozo/foobie" and dst="/somewhere/else/foobie" */

  /* possible cases:  source is either a file or a directory, or doesn't exist,
     destination is either a file, a directory, or doesn't exist.

     if source doesn't exist, nothing to do.  
     if source is a file:
        if dest is a file, popup 'overwriting' question, delete file if ok
	if dest is a dir,  popup 'overwriting dir' question, delete dir if ok
	   fall through:  if dest doesn't exist, copy the file
     if source is a dir:
        if dest is a file, popup 'overwriting' question, delete file if ok
	if dest is a dir,  popup 'overwriting dir' question, delete dir if ok
	   fall through:  if dest doesn't exist, copy the directory, recurs */


  int         i, dstExists, srcdir, dstdir;
  struct stat srcSt, dstSt;
  char        buf[1024];
  static char *owdiff[3] = { "\nOk", "nNo", "\033Cancel" };
  static char *owsame[4] = { "\nOk", "dDon't Ask", "nNo", "\033Cancel" };

  if (DEBUG) fprintf(stderr,"copyFile %s %s\n", src, dst);

  if (stat(src,&srcSt)) return 0;  /* source doesn't exist, it would seem */

  dstExists = (stat(dst, &dstSt)==0);

  if (dstExists) {   /* ask about overwriting... */
    srcdir = (stat2bf(srcSt.st_mode) == BF_DIR);
    dstdir = (stat2bf(dstSt.st_mode) == BF_DIR);

    sprintf(buf, "%s '%s' already exists.  Replace it with %s '%s'?",
	    (dstdir) ? "Directory" : "File", dst,
	    (srcdir) ? "contents of directory" : "file", src);

    if (srcdir == dstdir) {
      if (overwrite==OWRT_ASK) {
	i = PopUp(buf, owsame, 4);
	if (i==1) overwrite = OWRT_NOASK;
	if (i==2) return -1;
	else if (i==3) { overwrite = OWRT_CANCEL;  return 1; }
      }
    }
    else {     /* one's a dir, the other's a file.  *ALWAYS* ask! */
      i = PopUp(buf, owdiff, 3);
      if (i==1) return -1;
      else if (i==2) { overwrite = OWRT_CANCEL;  return 1; }
    }


    /* it's okay...  rm the destination */
    if (dstdir) {
      if (rmdir(dst)) return 1;   /* failed to remove */
    }
    else {
      if (unlink(dst)) return 1;  /* failed to remove */
    }

    dstExists = 0;
  }


  /* destination doesn't exist no more, if it ever did... */
  userMask = umask(0);  /* grab the umask */
  umask(userMask);      /* put it back... */
  

  strcpy(cpSrcPath, src);
  strcpy(cpDstPath, dst);

  copyerr = 0;
  cp();

  return (copyerr>0) ? 1 : 0;
}


/* The following cp() and cp_* functions are derived from the source
   to 'cp' from the BSD sources */

/*
 * Copyright (c) 1988 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * David Hitz of Auspex Systems Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */


/************************/
static void cp()
/************************/
{
  /* copies 'thing' at cpSrcPath to cpDstPath.  Since this function is
     called recursively by cp_dir, there are *no* guarantees that either file
     exists or not */

  int         i, havedst;
  struct stat srcSt, dstSt;

  if (stat(cpSrcPath, &srcSt)) {   /* src doesn't exist, usefully... */
    SetISTR(ISTR_WARNING, "%s: %s", cpSrcPath, ERRSTR(errno));
    copyerr++;
    return;
  }

  if (stat(cpDstPath, &dstSt)) {  /* dst doesn't exist.  not really a prob */
    havedst = 0;
  }
  else {
    if (srcSt.st_dev == dstSt.st_dev && srcSt.st_ino == dstSt.st_ino) {
      return;         /* identical files:  nothing to do */
    }
    havedst = 1;
  }


  switch(stat2bf(srcSt.st_mode)) {   /* determine how to copy, by filetype */

    /* NOTE:  There is no S_IFLNK case here, since we're using 'stat()' and
     * not lstat().  As such we'll never see symbolic links, only that which
     * they point to
     */

  case BF_DIR:  if (!havedst) {  /* create destination directory */
    if (mkdir(cpDstPath, srcSt.st_mode | 0700) < 0) {
      SetISTR(ISTR_WARNING,"%s: %s",cpDstPath,ERRSTR(errno));
      copyerr++;
      return;
    }
  }
  else {
    if (stat2bf(dstSt.st_mode) != BF_DIR) {
      SetISTR(ISTR_WARNING,"%s: not a directory", cpDstPath);
      copyerr++;
      return;
    }
  }
    
    cp_dir();
    if (!havedst) chmod(cpDstPath, srcSt.st_mode);
    
    break;
    

  case BF_CHR:
  case BF_BLK:   cp_special(&srcSt, havedst);    break;

  case BF_FIFO:  cp_fifo(&srcSt, havedst);       break;

  case BF_SOCK:  SetISTR(ISTR_WARNING,"Socket file '%s' not copied.",
			 cpSrcPath);
                 copyerr++;
                 break;

  default:       cp_file(&srcSt, havedst);
  }

  return;
}


/********************/
static void cp_dir()
/********************/
{
  int    i, dirlen, oldsrclen, olddstlen, longpath;
  char **names, *name;
  struct stat  srcSt, dstSt;


  /* src and dst directories both exists now.  copy entries */

  if (DEBUG) fprintf(stderr,"cp_dir:   src='%s',  dst='%s'\n", 
		     cpSrcPath, cpDstPath);

  longpath  = 0;
  oldsrclen = strlen(cpSrcPath);
  olddstlen = strlen(cpDstPath);

  names = getDirEntries(cpSrcPath, &dirlen);
  if (!names || !dirlen) return;      /* nothing to copy */


  /* now, we've got a list of entry names.  Copy the non-subdirs first, for
     performance reasons... */

  for (i=0; i<dirlen && overwrite!=OWRT_CANCEL; i++) {
    name = names[i];
    if (name[0] == '.' && (name[1]=='\0' || 
			   (name[1]=='.' && name[2]=='\0'))) goto done;
    
    /* add name to src and dst paths */
    if ((strlen(name) + oldsrclen >= (MAXPATHLEN-3)) ||
	(strlen(name) + olddstlen >= (MAXPATHLEN-3)))   {
      copyerr++;   /* path too long */
      longpath = 1;
      goto done;
    }

    strcat(cpSrcPath, "/");
    strcat(cpSrcPath, name);

    if (stat(cpSrcPath, &srcSt) < 0) {
      SetISTR(ISTR_WARNING,"%s: %s",cpSrcPath,ERRSTR(errno));
      copyerr++;
      cpSrcPath[oldsrclen] = '\0';
      goto done;
    }
     
    if (stat2bf(srcSt.st_mode) == BF_DIR) {
      cpSrcPath[oldsrclen] = '\0';
      continue;                     /* don't remove from list, just skip */
    }
     
    strcat(cpDstPath, "/");
    strcat(cpDstPath, name);
    cp();                         /* RECURSE */

    cpSrcPath[oldsrclen] = '\0';
    cpDstPath[olddstlen] = '\0';

  done:                           /* remove name from list */
    free(name);
    names[i] = (char *) NULL;
  }


  /* copy subdirectories, which are the only things left in the list */
  for (i=0; i<dirlen && overwrite!=OWRT_CANCEL; i++) {
    if (!names[i]) continue;    /* loop until next name found */
    name = names[i];

    /* add name to src and dst paths */
    if ((strlen(name) + oldsrclen >= (MAXPATHLEN-3)) ||
	(strlen(name) + olddstlen >= (MAXPATHLEN-3)))   {
      copyerr++;
      longpath = 1;
      continue;
    }

    strcat(cpSrcPath, "/");
    strcat(cpSrcPath, name);

    strcat(cpDstPath, "/");
    strcat(cpDstPath, name);

    cp();                        /* RECURSE */
    
    cpSrcPath[oldsrclen] = '\0';
    cpDstPath[olddstlen] = '\0';
  }

  /* free all memory still in use */
  for (i=0; i<dirlen; i++) {
    if (names[i]) free(names[i]);
  }
  free(names);

  if (longpath) SetISTR(ISTR_WARNING, "Path too long error!");
}


/*****************************/
static void cp_file(st, exists)
     struct stat *st;
     int exists;
/*****************************/
{
  register int srcFd, dstFd, rcount, wcount, i;
  char         str[512], buf[8192];
  static char  *owbuts[4] = { "\nOk", "dDon't Ask", "nNo", "\033Cancel" };

  if (DEBUG) fprintf(stderr,"cp_file:  src='%s',  dst='%s'\n", 
		     cpSrcPath, cpDstPath);

  if ((srcFd = open(cpSrcPath, O_RDONLY, 0)) == -1) {
    SetISTR(ISTR_WARNING, "%s: %s", cpSrcPath, ERRSTR(errno));
    copyerr++;
    return;
  }

  if (exists) {
    if (overwrite==OWRT_ASK) {
      sprintf(buf, "File '%s' exists.\n\nOverwrite?", cpDstPath);
      i = PopUp(buf, owbuts, 4);

      if      (i==1) overwrite = OWRT_NOASK;
      else if (i==2) return;
      else if (i==3) { overwrite = OWRT_CANCEL;  return; }
    }
    dstFd = open(cpDstPath, O_WRONLY|O_TRUNC, 0);
  }
  else
    dstFd = open(cpDstPath, O_WRONLY|O_CREAT|O_TRUNC,
		 (st->st_mode & 0777) & (~userMask));

  if (dstFd == -1) {
    SetISTR(ISTR_WARNING, "%s: %s", cpDstPath, ERRSTR(errno));
    copyerr++;
    return;
  }

  WaitCursor();

  /* copy the file contents */
  while ((rcount = read(srcFd, buf, 8192)) > 0) {
    wcount = write(dstFd, buf, rcount);
    if (rcount != wcount || wcount == -1) {
      SetISTR(ISTR_WARNING, "%s: %s", cpDstPath, ERRSTR(errno));
      copyerr++;
      break;
    }
  }
  if (rcount < 0) {
    SetISTR(ISTR_WARNING, "%s: %s", cpSrcPath, ERRSTR(errno));
    copyerr++;
  }

  close(srcFd);
  if (close(dstFd)) {
    SetISTR(ISTR_WARNING, "%s: %s", cpDstPath, ERRSTR(errno));
    copyerr++;
  }
}



/*********************************/
static void cp_special(st, exists)
     struct stat *st;
     int exists;
/*********************************/
{
  if (DEBUG) fprintf(stderr,"cp_spec:  src='%s',  dst='%s'\n", 
		     cpSrcPath, cpDstPath);

  if (exists && unlink(cpDstPath)) {
    SetISTR(ISTR_WARNING, "unlink %s: %s", cpDstPath, ERRSTR(errno));
    copyerr++;
    return;
  }

#ifndef VMS  /* VMS doesn't have a mknod command */
  if (mknod(cpDstPath, st->st_mode, st->st_rdev)) {
    SetISTR(ISTR_WARNING, "mknod %s: %s", cpDstPath, ERRSTR(errno));
    copyerr++;
    return;
  }
#endif

}


/*********************************/
static void cp_fifo(st, exists)
     struct stat *st;
     int exists;
/*********************************/
{
  if (DEBUG) fprintf(stderr,"cp_fifo:  src='%s',  dst='%s'\n", 
		     cpSrcPath, cpDstPath);

#ifdef S_IFIFO
  if (exists && unlink(cpDstPath)) {
    SetISTR(ISTR_WARNING, "unlink %s: %s", cpDstPath, ERRSTR(errno));
    copyerr++;
    return;
  }

  if (mkfifo(cpDstPath, st->st_mode)) {
    SetISTR(ISTR_WARNING, "mkfifo %s: %s", cpDstPath, ERRSTR(errno));
    copyerr++;
    return;
  }
#endif
}



  
/*********************************/
static int stat2bf(stmode)
     int stmode;
{
  /* given the 'st.st_mode' field from a successful stat(), returns 
     BF_FILE, BF_DIR, BF_BLK, BF_CHR, BF_FIFO, or BF_SOCK.  Does *NOT*
     return BF_EXE */

  int rv, ftype;

  rv = BF_FILE;

#if defined(_POSIX_SOURCE)
  ftype = stmode;

  if      (S_ISDIR(ftype))  rv = BF_DIR;
  else if (S_ISCHR(ftype))  rv = BF_CHR;
  else if (S_ISBLK(ftype))  rv = BF_BLK;

# ifdef S_ISFIFO
  else if (S_ISFIFO(ftype)) rv = BF_FIFO;
# endif

  /* Hack: S_IFSOCK is not POSIX and there's no macro for this test */
  else if ((ftype & 0xc000) == 0xc000) rv = BF_SOCK;

#else /* not _POSIX_SOURCE */

  ftype = stmode & S_IFMT;  /* mask off uninteresting bits */
	
  if      (ftype == S_IFDIR) rv = BF_DIR;
  else if (ftype == S_IFCHR) rv = BF_CHR;
  else if (ftype == S_IFBLK) rv = BF_BLK;

# ifdef S_IFSOCK
  else if (ftype == S_IFSOCK) rv = BF_SOCK;
#  endif

# ifdef S_IFIFO
  else if (ftype == S_IFIFO) rv = BF_FIFO;
# endif

#endif  /* _POSIX_SOURCE */

  return rv;
}
