/*	ScianFileSystem.c
	manages file system window for scian

	Jim Lyons
	created 4/17/91

	4/28/91 version 2 of files window
	5/1/91  version 3: made file objects
	5/3/91	added format dialog routines
	5/29/91 EMP removed lib headers
	6/25/91 removed icon definitions
	7/22/91 added Read Data Files menu item
	8/8/91	guarded all strcpy and strcat calls
	9/9/91	added help strings
	9/16/91	redesigned file window
	9/19/91 added file info window
	10/12/91 removed Set File Type stuff
	10/28/91 changed interface to File Readers (with EMP)
	1/20/92	changed order of file type identification in ReadDir
	2/5/92	added DropInContents method to file window corral
	4/8/92	made all chdirs temporary, never change dirs on user
	4/17/92	changed Open to use Object Function
	4/23/92 changed Set Format and Show Info to use Object Functions
	5/26/92 EMP fixed OpenParent not to fail at root
	5/26/92 EMP made GetFileWindow public
	6/13/92 EMP changed to use method declarations, not prototypes
	10/15/92 Modified file info window to make two-line directory name
	2/3/93	Fixed bug in file info routine
*/

#include "Scian.h"
#ifdef GETPWNAM
#include <pwd.h>
#endif
#include "ScianStyle.h"
#include "ScianTypes.h"
#include "ScianMethods.h"
#include "ScianArrays.h"
#include "ScianWindows.h"
#include "ScianTextBoxes.h"
#include "ScianSliders.h"
#include "ScianButtons.h"
#include "ScianTitleBoxes.h"
#include "ScianObjWindows.h"
#include "ScianObjFunctions.h"
#include "ScianColors.h"
#include "ScianControls.h"
#include "ScianTimers.h"
#include "ScianLists.h"
#include "ScianIDs.h"
#include "ScianEvents.h"
#include "ScianErrors.h"
#include "ScianDialogs.h"
#include "ScianIcons.h"
#include "ScianFiles.h"
#include "ScianDatasets.h"
#include "ScianScripts.h"
#include "ScianPreferences.h"
#include "ScianFileSystem.h"

/* defs for file window parameters */

#undef TITLEBOXTOP
#define TITLEBOXTOP	14

#define PARENTBTNWID	92
#define PARENTBTNHT	70
#define MINBTNWID	120
#define FILTERBTNWID	208

#define FILESWIDTH	(2*MINORBORDER + 2*SMALLGAP + 3*MINBTNWID)
#define FILESHEIGHT	390


/* defs for file format dialog box */
#define RADIOWID	84
#define BOTPART		(BOTMARGIN + OKBTNHT + INTERSPACE)

#define STICKYALL	(STICKYRIGHT + STICKYLEFT + STICKYBOTTOM + STICKYTOP)

/* defs for file info window */
#define NINFOITEMS	5
#define INFOITEMHT	40
#define INFOITEMWID	110
#define INFONAMEWIDTH	70
#define INFOFIELDWIDTH	200
#define INFOFIELDHEIGHT	(NINFOITEMS*INFOITEMHT)
#define INFOWIDTH	(3*MINORBORDER + INFONAMEWIDTH + INFOFIELDWIDTH)
#define INFOHEIGHT	\
	(MINORBORDER + MAJORBORDER + GAP + 2*INFOFONTSIZE + 2*DEFLINESPACE + BARWIDTH + INFOFIELDHEIGHT)
#define INFOFONT	"Helvetica-Bold"
#define INFOFONTSIZE	12

/* file selector bit mask */
#define MASK(f)		(1<<f)

#define DEFAULTFILTER	(MASK(DIRFILE) | MASK(DATAFILE) | MASK(LOGFILE) | MASK(PALFILE))
#define ALLFILES	(MASK(NFILETYPES) - 1)

/* EMP static method declarations */

static ObjPtr CancelDialog();
static ObjPtr OKDialog();
static ObjPtr DoParent();
static ObjPtr DropInFileWin();
static ObjPtr OpenParent();
static ObjPtr OpenFile();
static ObjPtr OpenFolder();
static ObjPtr NewFilter();
static ObjPtr ShowFiles();


/* static function prototypes */
#ifdef PROTO

static ObjPtr SetFormat(ObjPtr);
static ObjPtr ShowInfo(ObjPtr);
static char *GetFileInfo(char *, char *);
static void FileFormatDialog(FuncTyp);
static ObjPtr OKFormat(int, ObjPtr);
static void ChangeFileType(ObjPtr, int);
static ObjPtr SelFileList(ObjPtr);
static ObjPtr SelDirList(ObjPtr);
static ObjPtr NewFileWindow(WinInfoPtr, char *);
static Bool ReadDirectory(ObjPtr);
static int FileType(char *);

#else

static ObjPtr SetFormat();
static ObjPtr ShowInfo();
static char *GetFileInfo();
static void FileFormatDialog();
static ObjPtr OKFormat();
static void ChangeFileType();
static ObjPtr SelFileList();
static ObjPtr SelDirList();
static ObjPtr NewFileWindow();
static Bool ReadDirectory();
static int FileType();

#endif

/********************************************************* GLOBALS */

int fileIcon[NFILETYPES] = {ICONFOLDER, ICONBINFILE, ICONOBJFILE, ICONTEXTFILE,
				ICONDATAFILE, ICONSCIANDOC, ICONCTABLE, ICONUNKNOWN};
char *fileTypeName[NFILETYPES] = {"Dir", "Bin", "Obj", "Text", "Data", 
			"SciAn Log", "Palette", "Other"};

char dirSave[MAXPATHLEN+1];	/* save original working directory name */
char replySave[MAXPATHLEN+1];	/* keep dialog reply text for reediting */

ObjPtr aList;	/* list for collecting selected files */


/********************************************************** INIT FILE SYSTEM */
void InitFileSystem(void)
{
	/* (fileClass is handled by ScianFiles) */
	/* ...but set the object function here for double clicking per EMP */

	SetVar(fileClass, DOUBLECLICK, NewString(OF_OPEN));

	getwd(dirSave);		/* save original working directory name */
	*replySave = '\0';	/* clear saved path */
}

void KillFileSystem(void)
{
	/* (fileClass is handled by ScianFiles) */

}

/******************************************************* OBJ FUNCTION ROUTINES */
#ifdef PROTO
ObjPtr Collect(ObjPtr theFile)
#else
ObjPtr Collect(theFile)
ObjPtr theFile;
#endif
/* used as method for collecting files to Set Format or Show Info */
{
	PrefixList(aList, theFile);
	return ObjTrue;
}

void SetUpCollect()
/* setup routine for Set Format and Show Info */
{
	aList = NewList();
}

void RestoreDir()
/* restores original directory after opening files */
{
	chdir(dirSave);
}

/******************************************************* DO NEW FILE WINDOW */
void DoNewFileWindow(void)
/* New File Window menu routine */
{
	WinInfoPtr askWindow;

	if (logging)
	{
		Log("Show FilesWindow\n");
		InhibitLogging(true);
	}
	if (*replySave)
	{
		strncpy(tempStr, replySave, TEMPSTRSIZE);
		tempStr[TEMPSTRSIZE] = '\0';
	}
	else if (PrefExists(PREF_DEFDIR))
	{
		strncpy(tempStr, GetPrefString(PREF_DEFDIR), TEMPSTRSIZE);
		tempStr[TEMPSTRSIZE-1] = '\0';
	}
	else getwd(tempStr);
	askWindow = AskUser((WinInfoPtr) NULLOBJ, "Enter directory name:",
		 (FuncTyp) NewFileWindow, tempStr);
	if (askWindow) SetVar((ObjPtr) askWindow, HELPSTRING, NewString("This dialog window \
allows you to enter the name of the directory for which you want to open a new File Window. \
The default directory name, showing when the window first comes up, may be set in the \
Preferences window."));
	if (logging) InhibitLogging(false);
}

static ObjPtr NewFileAgain(WinInfoPtr win, int n)
/* called if error in directory name from New File Window dialog */
{
	if (n == 1) DoNewFileWindow();
	else *replySave = '\0'; /* clear error string */
	return NULLOBJ;
}

#define MAXUSERNAME	20	/*Max # of chars in user name*/

#ifdef PROTO
static ObjPtr NewFileWindow(WinInfoPtr owner, char *replyText)
#else
static ObjPtr NewFileWindow(owner, replyText)
WinInfoPtr owner;
char *replyText;
#endif
/* puts up the New File Window */
{
	char *s, t[MAXPATHLEN + 1];
	WinInfoPtr alert;
	DIR *dirp;

	SKIPBLANKS(replyText);
	if (*replyText == '~')
	{
	    /*See if next character could be a name*/
	    if (isdigit(replyText[1]) || isalpha(replyText[1]))
	    {
		/*Must be some nother user*/
#ifdef GETPWNAM
		int k;
		struct passwd *pwdEntry;
		char userName[MAXUSERNAME + 1];

		for (k = 1;
		     k < MAXUSERNAME && (isdigit(replyText[k]) || isalpha(replyText[k]));
		     ++k)
		{
		    userName[k-1] = replyText[k];
		}
		userName[k-1] = '\0';

		pwdEntry = getpwnam(userName);
		if (pwdEntry)
		{
		    /*Entry's OK*/
		    strcpy(t, pwdEntry -> pw_dir);
		    strcat(t, replyText + k);
		}
		else
		{
		    /* unknown user */
		    strcpy(replySave, replyText); /* save text for user reediting */
		    sprintf(t, "Unknown user, %s.", userName);
		    alert = AlertUser(UIERRORALERT, owner, t,
				NewFileAgain, 2, "Cancel", "Re-enter");
		    if (alert) SetVar((ObjPtr) alert, HELPSTRING, NewString("You have used the \
'~user' construct in a directory name to find another user's home directory.  \
No user with the specified name could be found.  Press \
the Re-enter button to correct the entry, or press the Cancel key to cancel the request."));
		    return ObjTrue;
		}
#else
		/* save text for user reediting */
		strcpy(replySave, replyText);

		alert = AlertUser(UIERRORALERT, owner, "Sorry, this version of SciAn does not \
support the '~user' construct in a directory name.", NewFileAgain,
				2, "Cancel", "Re-enter");
		if (alert) SetVar((ObjPtr) alert, HELPSTRING, NewString("You have used the \
'~user' construct in a directory name to find a user's home directory.  This \
version of SciAn has not been compiled to accept this construct.  Press \
the Re-enter button to correct the entry, or press the Cancel key to cancel the request."));
		return ObjTrue;
#endif
	    }
	    else
	    {
		if (s = getenv("HOME")) /* substitute for squiggle */
		{
		    strcpy(t, s);
		    strcat(t, replyText + 1);
		}
	    }
	}
	else if (*replyText)
	{
		strcpy(t, replyText);
	}
	else
	{
		t[0] = '/';
		t[1] = '\0';
	}

	if (chdir(t) == 0) /* check if valid directory */
	{
		getwd(t);	/* get expanded name back */
		GetFileWindow(t);
		*replySave = '\0'; /* clear error string */
		chdir(dirSave);	/* return to original directory */
		return ObjTrue;
	}
	else /* error */
	{
#define PROMPT1 "Could not open directory "
#define PROMPT2 ". Do you want to enter another directory or cancel the request?"

		/* save text for user reediting */
		strcpy(replySave, replyText);

		/* set up prompt in tempStr */
		strcpy(tempStr, PROMPT1);
		strncat(tempStr, (t ? t : replyText), 
			TEMPSTRSIZE - strlen(PROMPT1) - strlen(PROMPT2) - 1);
		strcat(tempStr, PROMPT2);
		alert = AlertUser(UIERRORALERT, owner, tempStr, NewFileAgain,
				2, "Cancel", "Re-enter");
		if (alert) SetVar((ObjPtr) alert, HELPSTRING, NewString("This dialog window \
came up because the directory entered in the New File Window dialog could not be opened. Press \
the Re-enter button to correct that entry, or press the Cancel key to cancel the request."));
		
	}
	return ObjTrue;
}

/******************************************************* SET FORMAT */
void ProcessSetFormat(void)
{
	ObjPtr corral;
	int fmt, i,n;

	if (!selWinInfo || !(corral = GetVar((ObjPtr) selWinInfo, CORRAL))
		|| ListCount(aList) == 0) return;

	/* now make dialog */
	FileFormatDialog(OKFormat);

	/*** assuming selWinInfo is now the new dialog ... */
	SetVar((ObjPtr) selWinInfo, FILELIST, aList); /* stash file list with dialog */

	if (logging) InhibitLogging(false);
	return;
}

static ObjPtr OKFormat(int fmt, ObjPtr list)
{
	ObjPtr theFile, theRdr, theIcon;
	int i, n = ListCount(list);

	/* fmt is the number of the button that was selected in the format group */
	if (fmt < 0) return ObjFalse; /* no choice made */
	else if (fmt >= ListCount(allFileReaders)) /* choice was "none" */
	{
		for (i=0; i<n; ++i) /* clear FORMAT and READER, leave filetype same */
		{
			theFile = GetListElem(list, i);
			theIcon = GetVar(theFile, REPICON);
			SetVar(theFile, READER, NULLOBJ);
			SetVar(theIcon, FORMAT, NULLOBJ);
			ImInvalid(theIcon);	/* cause redraw */
		}
	}
	else
	{
		for (i=0; i<n; ++i)
		{
			theFile = GetListElem(list, i);
			theRdr = GetListElem(allFileReaders, fmt);
			SetVar(theFile, READER, theRdr);
			ChangeFileType(theFile, DATAFILE);
			SetVar(GetVar(theFile, REPICON), FORMAT, 
			       GetVar(theRdr, NAME));
			theIcon = GetVar(theFile, REPICON);
			ImInvalid(theIcon);	/* cause redraw */
		}
	}
	return ObjTrue;
}

/* ChangeFileType routine is somewhat obsolete since we eliminated the Set File Type command.
	It is used in one place for Set File Format (in OKFormat above) just to change
	a file's type to DATA. I kept the general form in case needed later. JL */
#ifdef PROTO
static void ChangeFileType(ObjPtr theFile, int newType)
#else
static void ChangeFileType(theFile, newType)
ObjPtr theFile;
int newType;
#endif
{
	ObjPtr theIcon, theCorral, fileLists;
	int oldType;

	oldType = GetInt(GetVar(theFile, FILETYPE));
	if (oldType == newType) return;

	theIcon = GetVar(theFile, REPICON);
	theCorral = GetVar(theIcon, PARENT);
	fileLists = GetVar(theCorral, FILELIST);

	/* take file off old type list, add to new type list */
	DeleteFromList(GetListElem(fileLists, oldType), theFile);
	PrefixList(GetListElem(fileLists, newType), theFile);

	/* change the file type in file and icon */
	SetVar(theFile, FILETYPE, NewInt(newType));
	SetVar(theIcon, FILETYPE, NewInt(newType));

	/* change the icon */
	SetVar(theIcon, WHICHICON, NewInt(fileIcon[newType]));

	if (newType == DATAFILE) /* just in case a file was changed from data, then back */
	{
		char *ext, *name;
		ObjPtr theRdr;

		name = GetString(GetVar(theFile, NAME));
		ext = strrchr(name, '.');
		if (ext++ && (theRdr = FindExtensionReader(ext) ))
		{
			/* known data file format */
			SetVar(theFile, READER, theRdr);
			SetVar(theIcon, FORMAT, GetVar(theRdr, NAME));
		}
	}
	else /* clear format in icon and file */
	{
		SetVar(theFile, READER, NULLOBJ);
		SetVar(theIcon, FORMAT, NULLOBJ);
	}
	return;
}

/******************************************************************** SHOW INFO */
void ProcessShowInfo(void)
{
	WinInfoPtr infoWin;
	ObjPtr textbox, thePath, list, theString;
	char *path;
	int i;

	path = GetString(thePath = GetVar(GetListElem(aList, 0), PATH));

	if (chdir(path) != 0)
	{
		ReportError("ProcessShowInfo", "Could not change directory");
		return;
	}

	/* get the info window */
	theString = NewString("File Info");
	infoWin = GetDialog((WinInfoPtr) NULLOBJ, theString, "File Info",
		INFOWIDTH, INFOHEIGHT, INFOWIDTH, INFOHEIGHT, WINDBUF + WINFIXEDSIZE);
	if (textbox = GetVar((ObjPtr) infoWin, REPOBJ)) /* dialog exists */
	{
		ThingListPtr p;
		ObjPtr theFile, field, contents;
		char *fileName;
		int x, itemWid;
	
		/* pop info window */
		PopWindow(infoWin);
		SetVar(textbox, VALUE, thePath);
		ImInvalid(textbox);

		/* put in file info textbox for each file on the list */
		field = GetVar((ObjPtr) infoWin, FIELD);
		SetVar(field, CONTENTS, contents = NewList());
		p = LISTOF(aList);
		x = MINORBORDER;
		SetupFont(INFOFONT, INFOFONTSIZE); /* so we can check filename length */
		while (p)
		{
			theFile = p -> thing;
			fileName = GetString(GetVar(theFile, NAME));
			itemWid = StrWidth(fileName);
			if (itemWid < INFOITEMWID) itemWid = INFOITEMWID;
			textbox = NewTextBox(x, x + itemWid, 
				 -INFOFIELDHEIGHT + MINORBORDER, -MINORBORDER,
				PLAIN, "File Info", GetFileInfo(path, fileName));
			PrefixList(contents, textbox);
			SetVar(textbox, PARENT, field);
			SetTextFont(textbox, INFOFONT);
			SetTextSize(textbox, INFOFONTSIZE);
			SetTextLineSpace(textbox, INFOITEMHT - INFOFONTSIZE);
			p = p -> next;
			x = x + itemWid + 2*MINORBORDER;
		}
		ScrollHome(field);
		RecalcScroll(field);
	}
	else /* create info window */
	{
		ObjPtr field, panel, contents, button;
		ThingListPtr p;
		ObjPtr theFile;
		char *fileName;
		int x, itemWid;

		contents = GetVar((ObjPtr) infoWin, CONTENTS); /* contents of window */
		ContentsExpectWindowSize(infoWin, INFOWIDTH, INFOHEIGHT);

		/* put in help string */
		SetVar((ObjPtr) infoWin, HELPSTRING, NewString("This window shows directory \
information for the selected file(s) in the file window. If more than one file was selected, \
you can use the scrollbar at the bottom of the window to view information for each of them."));

		/* put in panel */
		panel = NewPanel(greyPanelClass, 0, INFOWIDTH, 0, INFOHEIGHT);
		PrefixList(contents, panel);
		SetVar(panel, PARENT, (ObjPtr) infoWin);

		contents = GetVar(panel, CONTENTS); /* contents of panel */

		/* put path textbox at top */
		textbox = NewTextBox(MINORBORDER, INFOWIDTH - MINORBORDER,
				INFOHEIGHT - MAJORBORDER - 2*INFOFONTSIZE - 2*DEFLINESPACE,
				INFOHEIGHT - MAJORBORDER, PLAIN, "Pathname", GetString(thePath));
		SetVar(textbox, PARENT, panel);
		PrefixList(contents, textbox);
		SetTextFont(textbox, INFOFONT);
		SetTextSize(textbox, INFOFONTSIZE);
		SetTextAlign(textbox, RIGHTALIGN);
		SetVar((ObjPtr) infoWin, REPOBJ, textbox); /* ref to by this routine */

		/* put in "fieldnames" textbox */
		textbox = NewTextBox(MINORBORDER, MINORBORDER + INFONAMEWIDTH, 
			MINORBORDER,
			INFOHEIGHT-MAJORBORDER-2*INFOFONTSIZE-2*DEFLINESPACE-GAP-MINORBORDER-1,
			PLAIN, "Fieldnames", 
		  	"Name\nSize\nOwner\nMode\nModified");
		SetVar(textbox, PARENT, panel);
		PrefixList(contents, textbox);
		SetTextFont(textbox, INFOFONT);
		SetTextSize(textbox, INFOFONTSIZE);
		SetTextAlign(textbox, RIGHTALIGN);
		SetTextLineSpace(textbox, INFOITEMHT-INFOFONTSIZE);

		/* put in field for info text boxes */
		field = NewControlField(2*MINORBORDER + INFONAMEWIDTH, INFOWIDTH - MINORBORDER,
			MINORBORDER, MINORBORDER + INFOFIELDHEIGHT + BARWIDTH, 
			"Info Field", OBJECTSFROMTOP + BARBOTTOM);
		SetVar(field, BACKGROUND, NewInt(UIGRAY75));
		SetVar(field, BORDERTYPE, NewInt(1));
		SetVar(field, PARENT, panel);
		PrefixList(contents, field);
		SetVar(field, CONTENTS, contents = NewList());
		SetVar((ObjPtr) infoWin, FIELD, field);

		/* put in file info textbox for each file on the list */
		p = LISTOF(aList);
		x = MINORBORDER;
		SetupFont(INFOFONT, INFOFONTSIZE); /* so we can check filename length */
		while (p)
		{
			theFile = p -> thing;
			fileName = GetString(GetVar(theFile, NAME));
			itemWid = StrWidth(fileName);
			if (itemWid < INFOITEMWID) itemWid = INFOITEMWID;
			textbox = NewTextBox(x, x + itemWid, 
				 -INFOFIELDHEIGHT + MINORBORDER, -MINORBORDER,
				PLAIN, "File Info", GetFileInfo(path, fileName));
			PrefixList(contents, textbox);
			SetVar(textbox, PARENT, field);
			SetTextFont(textbox, INFOFONT);
			SetTextSize(textbox, INFOFONTSIZE);
			SetTextLineSpace(textbox, INFOITEMHT - INFOFONTSIZE);
			p = p -> next;
			x = x + itemWid + 2*MINORBORDER;
		}
		RecalcScroll(field);
	}
	if (logging) InhibitLogging(false);
	chdir(dirSave); /* return to original working directory */
	return;
}

#ifdef PROTO
static char *GetFileInfo(char *path, char *fileName)
#else
static char *GetFileInfo(path, fileName)
char *path, *fileName;
#endif
{
	char *p = tempStr, *s, buf[20];
	struct stat stbuf;
	struct passwd *pwbuf;
	struct tm *tmbuf;
	short mode;

	s = fileName;
	while (*p++ = *s++) ;
	*(p - 1) = '\n';

	/* get file info */
	if (stat(fileName, &stbuf) == -1) /* This happens! (eg, link to a bin, dead link) */
	{
		strcpy(p, "?\n?\n?\n?");
	}
	else
	{
		/* get size in K */
		sprintf(buf,"%dK",(stbuf.st_size + 1023)/1024);
		s = buf;
		while (*p++ = *s++) ;
		*(p - 1) = '\n';

		/* get owner name */
		pwbuf = getpwuid(stbuf.st_uid);
		if (pwbuf)
		{
			s = pwbuf -> pw_name;
		}
		else
		{
			s = "(unavailable)";
		}
		while (*p++ = *s++) ; /* copy name */
		*(p - 1) = '\n';

		/* get mode bits */
		mode = stbuf.st_mode;
		*p++ = mode & S_IRUSR ? 'r' : '-';
		*p++ = mode & S_IWUSR ? 'w' : '-';
		*p++ = mode & S_IXUSR ? 'x' : '-';
		*p++ = ' ';
		*p++ = mode & S_IRGRP ? 'r' : '-';
		*p++ = mode & S_IWGRP ? 'w' : '-';
		*p++ = mode & S_IXGRP ? 'x' : '-';
		*p++ = ' ';
		*p++ = mode & S_IROTH ? 'r' : '-';
		*p++ = mode & S_IWOTH ? 'w' : '-';
		*p++ = mode & S_IXOTH ? 'x' : '-';
		*p++ = '\n';

		/* get modified date */
		tmbuf = localtime(&stbuf.st_mtime);
		sprintf(buf,"%02d/%02d/%02d %02d:%02d\n", tmbuf->tm_mon + 1, tmbuf->tm_mday,
			tmbuf->tm_year, tmbuf->tm_hour, tmbuf->tm_min);
		s = buf;
		while (*p = *s++) ++p;
	}
	return tempStr;
}

/********************************************************************* FILE FORMAT DIALOG
*	Puts up a dialog for selecting data file format.
*	The OKRoutine is called with the selected format and list of files:
*
*		void OKRoutine(int fmt, ObjPtr list)
*/
#ifdef PROTO
static void FileFormatDialog(FuncTyp OKRoutine)
#else
static void FileFormatDialog(OKRoutine)
FuncTyp OKRoutine;
#endif
{
	WinInfoPtr theDialog;
	ObjPtr panel, alertPanel, contents, promptBox, radioBtn, cancelBtn, okBtn;
	ObjPtr thePrompt, radioGroup;
	int row, col, nRows, nCols, totWid, totHt;
	int n, nBtns;

	nBtns = ListCount(allFileReaders) + 1;

	/* first compute sizes from the number of buttons */
	/* number of buttons is one more than number of formats to allow for "none" */
	if (nBtns < 11) nCols = 2, nRows = (nBtns + 1)/2;
	else if (nBtns < 25) nCols = 3, nRows = (nBtns + 2)/3;
	else nCols = 4, nRows = (nBtns + 3)/4;
	totWid = 2*SIDEMARGIN + nCols*RADIOWID + (nCols - 1)*GAP;
	totHt = BOTMARGIN + OKBTNHT + 2*INTERSPACE + nRows*CHECKBOXHEIGHT + (nRows - 1)*GAP
		+ PROMPTTEXTSIZE + TOPMARGIN;

	/* see if dialog exists */
	if (!selWinInfo) return;
	thePrompt = NewString("Set data format to:");
	theDialog = GetDialog(selWinInfo, thePrompt, "Set Data Format",
		totWid, totHt, totWid, totHt, 
		WINDBUF + WINFIXEDSIZE + WINCENTERED + WINNOFRAME);
	if(!theDialog) return;

	if (GetVar((ObjPtr) theDialog, REPOBJ)) /* dialog exists */
	{
		/* pop the dialog */
		PopWindow(theDialog);
	}
	else /* make the dialog box */
	{
		ThingListPtr runner;

		SetVar((ObjPtr) theDialog, REPOBJ, thePrompt); /* mark dialog */
		contents = GetVar((ObjPtr) theDialog, CONTENTS); /* contents of window */
		ContentsExpectWindowSize(theDialog, totWid, totHt);
		SetVar((ObjPtr) theDialog, HELPSTRING, NewString("This window shows \
the data file formats recognized by this version of SciAn. Click on the proper format \
for the selected files and press the OK button. For more information, see the section \
on data file formats in the SciAn User's manual."));

		/* put in panel */
		panel = NewPanel(greyPanelClass, 0, totWid, 0, totHt);
		PrefixList(contents, panel);
		SetVar(panel, PARENT, (ObjPtr) theDialog);

		contents = GetVar(panel, CONTENTS); /* contents of panel */

		/* put in pretty panel */
		alertPanel = NewAlertPanel(0, totWid, 0, totHt, "Fmt Panel");
		PrefixList(contents, alertPanel);
		SetVar(alertPanel, PARENT, panel);

		/* put in prompt */
		promptBox = NewTextBox(SIDEMARGIN, totWid - SIDEMARGIN,
				totHt - TOPMARGIN - PROMPTTEXTSIZE, totHt - TOPMARGIN,
				PLAIN, "Prompt Box", GetString(thePrompt));
		PrefixList(contents, promptBox);
		SetVar(promptBox, PARENT, panel);
		SetTextFont(promptBox, PROMPTTEXTFONT);
		SetTextSize(promptBox, PROMPTTEXTSIZE);
		SetTextColor(promptBox, PROMPTTEXTCOLOR);

		/* put in the bottom buttons */
		cancelBtn = NewButton(totWid - SIDEMARGIN - 2*OKBTNWID - GAP,
				totWid - SIDEMARGIN - OKBTNWID - GAP,
				BOTMARGIN, BOTMARGIN + OKBTNHT, "Cancel");
		PrefixList(contents, cancelBtn);
		SetVar(cancelBtn, PARENT, panel);
		SetMethod(cancelBtn, CHANGEDVALUE, CancelDialog);
		SetVar(cancelBtn, HELPSTRING, NewString("Pressing this button \
will cancel this dialog window without changing the format of the selected file(s)."));

		okBtn = NewButton(totWid - SIDEMARGIN - OKBTNWID,
				totWid - SIDEMARGIN,
				BOTMARGIN, BOTMARGIN + OKBTNHT, "OK");
		PrefixList(contents, okBtn);
		SetVar(okBtn, PARENT, panel);
		SetMethod(okBtn, OKMETHOD, OKRoutine); /* for ref by OKDialog */
		SetMethod(okBtn, CHANGEDVALUE, OKDialog);
		SetVar(okBtn, HELPSTRING, NewString("Pressing this button \
will dismiss this dialog window and set the format of the selected file(s)."));
		SetVar(okBtn, REPOBJ, (ObjPtr) theDialog);

		/* oh, yeah -- guess we need a few radio buttons */
		radioGroup = NewRadioButtonGroup("Data Format");
		PrefixList(contents, radioGroup);
		SetVar(radioGroup, PARENT, panel);
		runner = LISTOF(allFileReaders);
		for (n=0, row=nRows-1; row>=0 && runner; --row)
		{
			for (col = 0; col<nCols && runner; ++col)
			{
				ObjPtr theName;
				theName = GetVar(runner -> thing, NAME);
				radioBtn = NewRadioButton(
					SIDEMARGIN + col*(RADIOWID + GAP),
					SIDEMARGIN + (col+1)*RADIOWID + col*GAP,
					BOTPART + row*(CHECKBOXHEIGHT + GAP),
					BOTPART + (row+1)*CHECKBOXHEIGHT + row*GAP,
					theName ? GetString(theName) : "");
				AddRadioButton(radioGroup, radioBtn);
				runner = runner -> next; 
			}
		}
		/* now add "none" button */
		row = 0; col = nCols - 1;
		radioBtn = NewRadioButton(
			SIDEMARGIN + col*(RADIOWID + GAP),
			SIDEMARGIN + (col+1)*RADIOWID + col*GAP,
			BOTPART + row*(CHECKBOXHEIGHT + GAP),
			BOTPART + (row+1)*CHECKBOXHEIGHT + row*GAP,
			"NONE");
		AddRadioButton(radioGroup, radioBtn);
		
		SetVar(okBtn, DATA, radioGroup); /* ref by OKDialog */
	}
	return;
}

static ObjPtr OKDialog(btn)
ObjPtr btn;
{
	FuncTyp OKRtn;
	ObjPtr list, dialog;
	int val;

	list = GetVar(GetVar(btn, REPOBJ), FILELIST);
	val = GetInt(GetVar(GetVar(btn, DATA), VALUE));
	OKRtn = GetMethod(btn, OKMETHOD);

	DeferClose();
	OKRtn(val, list);
	return ObjTrue;
}

static ObjPtr CancelDialog(btn)
ObjPtr btn;
{
	DeferClose();
	return ObjFalse;
}

/******************************************************************* GET FILE WINDOW */
#ifdef PROTO
void GetFileWindow(char *dirName)
#else
void GetFileWindow(dirName)
char *dirName;
#endif

/* Create/pop Files dialog */
{
	WinInfoPtr fileWin;
	ObjPtr panel, contents, corral, thePath;
	ObjPtr btn, btnGroup;
	int btnWid;

	/* get the dialog window */
	thePath = NewString(dirName);
	fileWin = GetDialog((WinInfoPtr) NULLOBJ, thePath, dirName,
		FILESWIDTH, FILESHEIGHT, SCRWIDTH, SCRHEIGHT, WINDBUF);
	if(!fileWin) return;

	ContentsExpectWindowSize(fileWin, FILESWIDTH, FILESHEIGHT);

	if (GetVar((ObjPtr) fileWin, PATH)) /* dialog exists */
	{
		/* pop files dialog */
		PopWindow(fileWin);
	}
	else /* create file window */
	{
		InhibitLogging(true);
		SetVar((ObjPtr) fileWin, PATH, thePath); /* ref by this routine */
		contents = GetVar((ObjPtr) fileWin, CONTENTS); /* contents of window */

		/* put in help string */
		SetVar((ObjPtr) fileWin, HELPSTRING, NewString("This window shows the files \
and sub-directories located in the directory given by the window's title. The files appear as \
icons corresponding to file type; sub-directories appear as folders. To open a file or \
folder, select its icon and press the Open button, or simply double click on it."));

		/* put in panel */
		panel = NewPanel(greyPanelClass, 0, FILESWIDTH, 0, FILESHEIGHT);
		PrefixList(contents, panel);
		SetVar(panel, PARENT, (ObjPtr) fileWin);
		SetVar(panel, STICKINESS, NewInt(STICKYALL));

		contents = GetVar(panel, CONTENTS); /* contents of panel */

		/* put in icon corral */
		corral = NewIconCorral(NULLOBJ, MINORBORDER, 
				FILESWIDTH - MINORBORDER, TITLEBOXTOP + MINORBORDER + OKBTNHT,
				FILESHEIGHT - (TITLEBOXTOP + MINORBORDER + PARENTBTNHT),
				OBJECTSFROMTOP + BARRIGHT + BARBOTTOM);
		SetVar(corral, STICKINESS, NewInt(STICKYALL));
		PrefixList(contents, corral);
		SetVar(corral, PARENT, panel);
		SetVar(corral, PATH, thePath);
		SetVar(corral, SELECTOR, NewInt(DEFAULTFILTER)); /* file selector bits */
		SetMethod(corral, SHOWFILES, ShowFiles);
		SetMethod(corral, DROPINCONTENTS, DropInFileWin);

		/* put in title box for filter buttons */
		btn = NewTitleBox(MINORBORDER, MINORBORDER + FILTERBTNWID + 2*SMALLGAP,
			FILESHEIGHT - TITLEBOXTOP - PARENTBTNHT, FILESHEIGHT - 4, "Files");
		PrefixList(contents, btn);
		SetVar(btn, PARENT, panel);
		SetVar(btn, STICKINESS, NewInt(STICKYTOP + STICKYLEFT));

		/* put in file filter radio group */
		btnGroup = NewRadioButtonGroup("File Filter");
		SetVar(btnGroup, PARENT, panel);
		PrefixList(contents, btnGroup);
		SetVar(btnGroup, CORRAL, corral);
		SetVar(btnGroup, STICKINESS, NewInt(STICKYTOP + STICKYLEFT));
		SetVar(btnGroup, HELPSTRING, NewString("These buttons determine which file types \
to show in the File Window. (Folders are always shown.) If the top button is checked, only SciAn \
data, log, and palette files are shown; the files must have proper filename \
extensions to be recognized by SciAn. If the bottom button is checked, all files are shown."));

		btn = NewRadioButton(MINORBORDER + SMALLGAP, 
		  MINORBORDER + SMALLGAP + FILTERBTNWID,
		  FILESHEIGHT - TITLEBOXTOP - PARENTBTNHT + SMALLGAP + CHECKBOXHEIGHT + CHECKBOXSPACING,
		  FILESHEIGHT - TITLEBOXTOP - PARENTBTNHT + SMALLGAP + 2*CHECKBOXHEIGHT + CHECKBOXSPACING,
		  "Show SciAn files only");
		AddRadioButton(btnGroup, btn);
		SetVar(btn, STICKINESS, NewInt(STICKYTOP + STICKYLEFT));
		SetVar(btn, SELECTOR, NewInt(DEFAULTFILTER));

		btn = NewRadioButton(MINORBORDER + SMALLGAP, 
			MINORBORDER + SMALLGAP + FILTERBTNWID,
			FILESHEIGHT - TITLEBOXTOP - PARENTBTNHT + SMALLGAP,
			FILESHEIGHT - TITLEBOXTOP - PARENTBTNHT + SMALLGAP + CHECKBOXHEIGHT,
			"Show all files");
		AddRadioButton(btnGroup, btn);
		SetVar(btn, STICKINESS, NewInt(STICKYTOP + STICKYLEFT));
		SetVar(btn, SELECTOR, NewInt(ALLFILES));
		SetValue(btnGroup, NewInt(0)); /* make "Show SciAn files only" the default */
		SetMethod(btnGroup, CHANGEDVALUE, NewFilter);

		/* put in parent button */
		btn = NewIconLabeledButton(FILESWIDTH - MINORBORDER - PARENTBTNWID, 
			FILESWIDTH - MINORBORDER, FILESHEIGHT - TITLEBOXTOP - PARENTBTNHT,
			FILESHEIGHT - TITLEBOXTOP, ICONPARENT, UIWHITE, "Open Parent", BS_PLAIN);
		SetVar(btn, STICKINESS, NewInt(STICKYTOP + STICKYRIGHT));
		SetVar(btn, PARENT, panel);
		SetVar(btn, LABEL, NewString("Open Parent"));
		SetVar(btn, HELPSTRING, NewString("Pressing this button will \
open a new file window at the next higher directory level. The directory of the current window \
will appear as a folder in the new window."));
		PrefixList(contents, btn);
		MakeButtonToggled(btn, true); /* make parent button icon be lit up always */
		SetValue(btn, NewInt(true));
		MakeButtonToggled(btn, false);
		SetVar(btn, PATH, thePath); /* ref by OpenParent */
		SetMethod(btn, CHANGEDVALUE, DoParent);
		SetMethod(btn, OPEN, OpenParent);

		/* put in action buttons */
		btnWid = (FILESWIDTH - 2*MINORBORDER - 2*SMALLGAP)/3;
		btn = NewFunctionButton(fileWin, MINORBORDER, MINORBORDER + btnWid,
				MINORBORDER, MINORBORDER + OKBTNHT, OF_OPEN);
		if (btn)
		{
			SetVar(btn, PARENT, panel);
			PrefixList(contents, btn);
			SetVar(btn, STICKINESS, NewInt(STICKYLEFT + FLOATINGRIGHT));
		}

		btn = NewFunctionButton(fileWin, MINORBORDER + btnWid + SMALLGAP, 
		MINORBORDER + SMALLGAP + 2*btnWid,
				MINORBORDER, MINORBORDER + OKBTNHT,
				OF_SETFORMAT);
		if (btn)
		{
			SetVar(btn, PARENT, panel);
			PrefixList(contents, btn);
			SetVar(btn, STICKINESS, NewInt(FLOATINGRIGHT + FLOATINGLEFT));
		}

		btn = NewFunctionButton(fileWin, FILESWIDTH - MINORBORDER - btnWid, FILESWIDTH - MINORBORDER,
				MINORBORDER, MINORBORDER + OKBTNHT,
				OF_SHOWINFO);
		if (btn)
		{
			SetVar(btn, PARENT, panel);
			PrefixList(contents, btn);
			SetVar(btn, STICKINESS, NewInt(FLOATINGLEFT + STICKYRIGHT));
		}

		/* set up reference */
		SetVar((ObjPtr) fileWin, CORRAL, corral); /* ref by this routine */

		/* now put in the file icons */
		ReadDirectory(corral);
		ShowFiles(corral);
		InhibitLogging(false);
	}
}

#ifdef PROTO
ObjPtr IconHelp(ObjPtr icon, ObjPtr class)
#else
ObjPtr IconHelp(icon, class)
ObjPtr icon, class;
#endif
{
	int type = GetInt(GetVar(icon, FILETYPE));

	switch (type)
	{
		case DIRFILE:
			SetVar(class, HELPSTRING, NewString("This icon represents a sub-directory \
within the directory of this file window. Double clicking on the folder will open a file window \
for the sub-directory."));
			break;

#ifndef RELEASE
		case PALFILE:
			SetVar(class, HELPSTRING, NewString("This icon represents a palette file \
which has been saved by SciAn. For more information, ask Eric."));
			break;
#endif
			
		case DATAFILE:
			SetVar(class, HELPSTRING, NewString("\
This icon represents a data file with a format recognized by this version of SciAn. The \
name of the format is shown under the file name. \
Double clicking on this icon will read the file into the Datasets window."));
			break;

		case OTHFILE:
			SetVar(class, HELPSTRING, NewString("This icon represents a file whose type \
SciAn could not determine from the filename extension. If the file is a data file, you must \
identify the data format before SciAn can read the file. Select the file, press the Set \
Format button, and then select the proper format."));
			break;

		default:
			SetVar(class, HELPSTRING, NewString("This icon represents a file in the \
directory of this file window."));
			break;
	}
	return NULLOBJ;
}

/************************************************************************ READ DIRECTORY 
*	Reads directory of 'path' associated with given corral.
*	Creates file objects and sets parameters for them.
*/
#ifdef PROTO
static Bool ReadDirectory(ObjPtr corral)
#else
static Bool ReadDirectory(corral)
ObjPtr corral;
#endif
{
	struct stat stbuf;
	int fd;
#ifdef DIRENT
	struct dirent *dp;
#else
	struct direct *dp;
#endif
	DIR *dirp;
	ObjPtr thePath, theFile;
	ObjPtr fileLists, sortedLists;
	int i, fileType;
	ObjPtr theRdr;
	char *path, *ext;

	/* this may take a while */
	LongOperation();

	thePath = GetVar(corral, PATH);
	if (thePath) path = GetString(thePath);
	else return false;

	if (chdir(path) != 0)
	{
		ReportError("ReadDirectory", "Error changing directory");
		return false;
	}

	/* open directory */
	if (!(dirp = opendir(path)))
	{
		ReportError("ReadDirectory", "Error opening directory");
		chdir(dirSave);
		return false;
	}

	/* set up master list and file type lists */
	fileLists = NewList();
	for (i=0; i<NFILETYPES; ++i) PrefixList(fileLists, NewList());

	/* read directory */
	while ((dp = readdir(dirp)) != NULL) /***/
	{

		if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) continue;
		stat(dp->d_name, &stbuf);
		if((stbuf.st_mode & S_IFMT) == S_IFLNK) continue; /*** must be a dead link? */
		if ((stbuf.st_mode & S_IFMT) == S_IFSOCK) continue; /* socket file */

		if (*dp->d_name == '.') continue; /* ignore dot files */
		ext = strrchr(dp->d_name, '.'); /* point to filename extension */

		/* make a file object */
		theFile = NewObject(fileClass, 0);
		SetVar(theFile, NAME, NewString(dp->d_name));
		SetVar(theFile, PATH, thePath);

		/* figure out file type */
		if((stbuf.st_mode & S_IFMT) == S_IFDIR)
		{
			/* entry is a directory */
			SetVar(theFile, FILETYPE, NewInt(DIRFILE));
			PrefixList(GetListElem(fileLists, DIRFILE), theFile);
		}
		else
		{
			/* check file extension to identify file type */
			if (ext++) /* skip dot */
			{
				if (strcmp(ext, "o") == 0)
				{
					/* object file */
					SetVar(theFile, FILETYPE, NewInt(OBJFILE));
					PrefixList(GetListElem(fileLists, OBJFILE), theFile);
				}
				else if (strcmp(ext, "txt") == 0
					|| strcmp(ext, "c") == 0
					|| strcmp(ext, "h") == 0
					|| strcmp(ext, "f") == 0 )
				{
					/* text file */
					SetVar(theFile, FILETYPE, NewInt(TEXTFILE));
					PrefixList(GetListElem(fileLists, TEXTFILE), theFile);
				}
				else if ( theRdr = FindExtensionReader(ext) )
				{
					/* known data file extension */
					SetVar(theFile, FILETYPE, NewInt(DATAFILE));
					SetVar(theFile, READER, theRdr);
					PrefixList(GetListElem(fileLists, DATAFILE), theFile);
				}
				else if (strcmp(ext, "log") == 0) /*** what should the ext be? */
				{
					/* script or log file */
					SetVar(theFile, FILETYPE, NewInt(LOGFILE));
					PrefixList(GetListElem(fileLists, LOGFILE), theFile);
				}
#ifndef RELEASE
				else if (strcmp(ext, "pal") == 0)
				{
					/* scian palette file */
					SetVar(theFile, FILETYPE, NewInt(PALFILE));
					PrefixList(GetListElem(fileLists, PALFILE), theFile);
				}
#endif
				else /* other file type */
				{
					SetVar(theFile, FILETYPE, NewInt(OTHFILE));
					PrefixList(GetListElem(fileLists, OTHFILE), theFile);
				}
			}
			else if (stbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
			{
				/* entry is (probably) executable file */
				SetVar(theFile, FILETYPE, NewInt(BINFILE));
				PrefixList(GetListElem(fileLists, BINFILE), theFile);
			}
			else /* no dot, not executable: other file type */
			{
				SetVar(theFile, FILETYPE, NewInt(OTHFILE));
				PrefixList(GetListElem(fileLists, OTHFILE), theFile);
			}
		}
	}
	closedir(dirp);

	/* now sort the lists */
	sortedLists = NewList();
	for (i=0; i<NFILETYPES; ++i)
	  PostfixList(sortedLists, SortListByStringVar(GetListElem(fileLists, i), NAME, false));

	SetVar(corral, FILELIST, sortedLists);
	chdir(dirSave);	/* return to original directory */
	return true;
}

/*********************************************************************** SHOW FILES
*	Puts the icons for the selected file types into the given corral.
*/
static ObjPtr ShowFiles(corral)
ObjPtr corral;
{
	ObjPtr theFile, theIcon, theRdr, iconList;
	ObjPtr fileLists, theType, thePath;
	ObjPtr contents;
	int i, fileSelector, left, right, bottom, top, width;
	ThingListPtr p;
	FuncTyp ReScroll;

	/* this may take a while */
	LongOperation();

	fileSelector = GetInt(GetVar(corral, SELECTOR));
	fileLists = GetVar(corral, FILELIST);
	thePath = GetVar(corral, PATH);

	Get2DIntBounds(corral, &left, &right, &bottom, &top);
	width = right - left;

	SetVar(corral, CONTENTS, contents = NewList());

	/* now drop the selected filetype icons in the corral */
	for (i=0; i<NFILETYPES; ++i)
	{
		if (!(fileSelector & MASK(i))) continue;
		theType = NewInt(i);
		p = LISTOF(GetListElem(fileLists, i));
		while(p)
		{
			theFile = p -> thing;
			if (!(theIcon = GetVar(theFile, REPICON)))
			{
				/* make icon for this file */
				theIcon = NewIcon(0, 0, fileIcon[i], GetString(GetVar(theFile, NAME)));
				SetVar(theFile, REPICON, theIcon);
				SetVar(theIcon, REPOBJ, theFile);
				SetVar(theIcon, PARENT, corral);
				SetVar(theIcon, FILETYPE, theType);
				SetVar(theIcon, PATH, thePath);
				SetMethod(theIcon, MAKE1HELPSTRING, IconHelp);

				if (i == DIRFILE)
				{
					SetMethod(theFile, OPEN, OpenFolder);
				}
				else
				{
					SetMethod(theFile, OPEN, OpenFile);
					SetMethod(theFile, COLLECT, Collect);
				}

				if (theRdr = GetVar(theFile, READER)) /* put format name on icon */
					SetVar(theIcon, FORMAT, GetVar(theRdr, NAME));
			}
			SetVar(theIcon, ICONLOC, NULLOBJ);
			PrefixList(contents, theIcon);
			p = p -> next;
		}
	}
	ScrollHome(corral);
	RecalcScroll(corral);
	return ObjTrue; /* function is used as method */
}

static ObjPtr DropInFileWin(corral, icon, x, y)
ObjPtr corral, icon;
int x,y;
{
#if 0
	ObjPtr theFile, theFormat, theReader;
	int fileType;
	theFile = GetVar(icon, REPOBJ);
	if (theFile && IsFile(theFile)
	{
		fileType = GetInt(GetVar(theFile, FILETYPE));
		if (fileType == DIRFILE)
		{
			DoUniqueTask(ReportCantDoIt);
			return ObjFalse;
		}
		/* ok: to copy file need to make new icon and file objects, copy vars,
			and update fileList, corral contents */
		
	}
#else
	printf("Drop in file window not implemented.\n");
#endif
	return ObjFalse;
}

static ObjPtr DoParent(button)
ObjPtr button;
{
	DeferMessage(button, OPEN);
	return ObjTrue;
}

/* this is called only as a result of a deferred OPEN message to button */
static ObjPtr OpenParent(button)
ObjPtr button;
{
	char *s, dirName[MAXPATHLEN+1];;

	strncpy(dirName, GetString(GetVar(button, PATH)), MAXPATHLEN);
	dirName[MAXPATHLEN] = '\0'; /* ensure termination */
	s = strrchr(dirName, '/');
	if (s)
	{
	    if (s > dirName)
	    {
		*s = '\0';
	    }
	    else
	    {
		*(s + 1) = '\0';
	    }
	    GetFileWindow(dirName);
	}
	return ObjTrue;
}

/*********************************************************************** NEW FILTER */
static ObjPtr NewFilter(b)
ObjPtr b;
{
	ObjPtr corral;
	int selector;

	corral = GetObjectVar("NewFilter", b, CORRAL);
	selector = GetInt(GetVar(b, VALUE)) ? ALLFILES : DEFAULTFILTER;
	SetVar(corral, SELECTOR, NewInt(selector));
	ShowFiles(corral);
	return ObjTrue;
}


/**************************************************************** OPEN FILE */
static ObjPtr OpenFile(theFile)
ObjPtr theFile;
/* opens (reads) one data file; returns ObjTrue if success or ObjFalse if failure */
{
	ObjPtr path, reader;

	reader = GetVar(theFile, READER);
	path = GetStringVar("OpenFile",theFile, PATH);
	if (!reader || !path || chdir(GetString(path)) != 0)
	{
	/***/	printf("error getting %s/%s\n",path,GetString(GetVar(theFile, NAME)));
		return ObjFalse;
	}
	LongOperation();
	ReadFormattedFile(reader, GetString(GetVar(theFile, NAME)));

	/* deselect after reading */
	/*** SelectIcon(GetVar(theFile, REPICON), false); ***/

	/*** take out when ObjFunction changed to call RestoreDir() ***/
	chdir(dirSave); /* change back to original working directory */
	return ObjTrue;
}

/* This routine is called only as a result of a deferred OPEN message */
static ObjPtr OpenFolder(theFile)
ObjPtr theFile;
{
	char *path, *name, dirName[MAXPATHLEN+1];
	DIR *dirp;

	path = GetString(GetVar(theFile, PATH));
	name = GetString(GetVar(theFile, NAME));
	if (strlen(path) + 1 + strlen(name) > MAXPATHLEN)
	{
		/* Oh, no! The dang pathname is too long! */
		ReportError("OpenFolder","Oh, no! The dang pathname is too long!");
		return ObjFalse;
	}
	strcpy(dirName, path);
        if (dirName[strlen(dirName) - 1] != '/')
	{
		strcat(dirName, "/");
	}
	strcat(dirName, name);
	dirName[MAXPATHLEN] = '\0';

	if(dirp = opendir(dirName)) /* good path? */
	{
		closedir(dirp);
		GetFileWindow(dirName);
		return ObjTrue;
	}
	else
	{
		/* Hey! There shouldn't be an error here! */
		ReportError("OpenFolder","Could not open the dang folder!");
		return ObjFalse;
	}
}
