/*	SFGetFolder.c - taken from Apple's snippets
	Modified to make it a standalone unit.

	StandardGetFolder example

	Steve Falkenburg -- MacDTS
	
	This sample uses the new System 7 CustomGetFile routine
	to provide a StandardGetFolder call to be used by applications
	when the user needs to select a folder instead of a file.
	
	It's written in Think C, but should work in MPW if a few #include
	files are added.
	
	The style of the dialog box is taken from the Human Interface note
	on folder selection.
	
	SJF		5/2/92		added check for empty filename and call to MakeFSSpec
	SJF		5/2/92		added check for refcon in filter and hook routines
	SJF		10/30/91	original coding
	
*/

#include <string.h>
#include <GestaltEqu.h>
#include <Folders.h>
#include <Traps.h>
#include <Script.h>
#include <Aliases.h>
#include "SFGetFolder.h"
//#include "trapavail.h"




/* prototypes */

	/* only one available to the outside world */

	/* internal prototypes */
void	InitStuff(void);
pascal	short MyDlgHook(short item, DialogPtr theDlg, Ptr userData);
pascal	Boolean MyModalFilter(DialogPtr theDlg, EventRecord *ev, short *itemHit, Ptr myData);
void	HitButton(DialogPtr theDlg, short item);
pascal	Boolean FilterAllFiles(CInfoPBPtr pb,  Ptr myDataPtr);
void	SetSelectButtonName(StringPtr selName, Boolean hilited, DialogPtr theDlg);
Boolean	SameFile(FSSpec *file1, FSSpec *file2);
Boolean	GetFSSpecPartialName(FSSpec *file, StringPtr fName);
OSErr	GetDeskFolderSpec(FSSpec *fSpec, short vRefNum);
OSErr	MakeCanonFSSpec(FSSpec *fSpec);
Boolean	ShouldHiliteSelect(FSSpec *fSpec);
void	ConcatPStrings(Str255 first, ConstStr255Param second);
#define	CPString(s, d) BlockMove(s, d, s[0] + 1L)

/* typedefs */

typedef struct {
	StandardFileReply *replyPtr;
	FSSpec oldSelection;
} SFData,  *SFDataPtr;

/* constants */

#include "SFGetFolder.const"

#define	kCanSelectDesktop	true
#define	kSelectStrRsrc		1128
#define kDefaultSelectString "\pSelect"
#define	kDefaultDeskString	"\pDesktop"
#define	kSelectKey			's'

/* globals */

static	Boolean gHasFindFolder;
static	FSSpec gDeskFolderSpec;
static	Str255 gSelectString;
static	Str255 gDesktopFName;
static	Boolean gInitialized = false;


/* initialize managers */

void InitStuff(void)
{
	OSErr err;
	long gResponse;
	Handle strHndl;
	
	gInitialized = true;
/*** For now,  we assume that FindFolder is always there! ***/
#ifdef OLD_WAY
	gHasFindFolder = false;
	if (TrapAvailable(_GestaltDispatch)) {
		err = Gestalt(gestaltFindFolderAttr, &gResponse);
		if (err==noErr)
			gHasFindFolder = (gResponse && (1<<gestaltFindFolderPresent));
	}

#else
	gHasFindFolder = true;	
#endif

	strHndl = Get1Resource('STR ', kSelectStrRsrc);
	if (ResError()!=noErr || !strHndl || !*strHndl)
		BlockMove(kDefaultSelectString, gSelectString, kDefaultSelectString[0]+1);
	else {
		BlockMove(*strHndl, &gSelectString, (long)((unsigned char *)(*strHndl)[0]+1));
		ReleaseResource(strHndl);
	}

	strHndl = Get1Resource('STR ', kDeskStrRsrc);
	if (ResError()!=noErr || !strHndl || !*strHndl)
		BlockMove(kDefaultDeskString, gDesktopFName, kDefaultSelectString[0]+1);
	else {
		BlockMove(*strHndl, &gDesktopFName, (long)((unsigned char *)(*strHndl)[0]+1));
		ReleaseResource(strHndl);
	}
}

/* get full path */
Boolean GetFullPath(FSSpec fSpec, Str255 *fullPath)
{
CInfoPBRec pb;
CInfoPBRec	myPB;					/* parameter block for PBGetCatInfo */
Str255		dirName;				/* a directory name */
OSErr		myErr;
short		len;

#define isFolder(X) ((X.hFileInfo.ioFlAttrib & 0x10) ? true : false)
// another way of doing this macro is 	(pb.hFileInfo.ioFlAttrib & (1<<4))



	CPString(fSpec.name, *fullPath);		/* initialize full pathname */

	pb.hFileInfo.ioFDirIndex = 0;			/* this HAS to be 0! */
	pb.hFileInfo.ioDirID = fSpec.parID;
	pb.hFileInfo.ioVRefNum = fSpec.vRefNum;
	pb.hFileInfo.ioNamePtr = fSpec.name;
	myErr = PBGetCatInfo(& pb, false);

	if (isFolder(pb))						/* file is a directory */
		ConcatPStrings(*fullPath, "\p:");

	/* What if we already have what we need...? */
	if (pb.dirInfo.ioDrDirID == fsRtDirID) {

		return true;		/* then we are done */
	}

	else {

		myPB.dirInfo.ioNamePtr = dirName;
		myPB.dirInfo.ioVRefNum = fSpec.vRefNum;	/* indicate target volume */
		myPB.dirInfo.ioDrParID = fSpec.parID;	/* initialize parent directory ID */
		myPB.dirInfo.ioFDirIndex = -1;			/* get info about a directory */
	
		/* Get name of each parent directory, up to root directory. */
		len = (*fullPath)[0];
	
		do {
	
			myPB.dirInfo.ioDrDirID = myPB.dirInfo.ioDrParID;
			myErr = PBGetCatInfo(&myPB, FALSE);
			if (myErr == noErr) {
				ConcatPStrings(dirName, "\p:");
				len += dirName[0];
				ConcatPStrings(dirName, *fullPath);
				CPString(dirName, *fullPath);
			}
	
		} while  ((myErr == noErr) && (myPB.dirInfo.ioDrDirID != fsRtDirID));
	
		return  (len == (*fullPath)[0]);
		/* True/False depending on whether the whole path was built or not */
	}
}



/* do getfile */

Boolean SFGetFolder(FSSpec *fSpec, Str255 prompt, Str255 newFolder)
{
	Point where = {-1, -1};
	SFReply reply;
	DialogPtr theDialog;
	short item;
	StandardFileReply sfReply;
	SFData sfUserData;
	OSErr err;
	Boolean targetIsFolder, wasAliased;
	
	if (!gInitialized) InitStuff();
	
	/* initialize user data area */
	sfUserData.replyPtr = &sfReply;
	sfUserData.oldSelection.vRefNum = -9999;	/* init to ridiculous value */

	ParamText(prompt, "\p", "\p", "\p");
	CustomGetFile((FileFilterYDProcPtr)FilterAllFiles,  -1,  nil,  &sfReply, 
		kSFDlg,  where,  (DlgHookYDProcPtr)MyDlgHook,  (ModalFilterYDProcPtr)MyModalFilter, 
		nil,  nil,  &sfUserData);
	
	if (sfReply.sfGood) {
		err = ResolveAliasFile(&sfReply.sfFile, true, &targetIsFolder, &wasAliased);
		if (err!=noErr)
			return false;
	}
	
	err = FSMakeFSSpec(sfReply.sfFile.vRefNum, sfReply.sfFile.parID,
					   sfReply.sfFile.name, fSpec);
	if (err!=noErr)
		return false;
	
	return sfReply.sfGood;
}


/*	this dialog hook checks the contents of the additional edit fields
	when the user selects a file.  The focus of the dialog is changed if one
	of the fields is out of range.
*/

pascal short MyDlgHook(short item, DialogPtr theDlg, Ptr userData)
{
	SFDataPtr sfUserData;
	Boolean hiliteButton;
	FSSpec curSpec;
	OSType refCon;
	
	refCon = GetWRefCon(theDlg);
	if (refCon!=sfMainDialogRefCon)
		return item;
		
	sfUserData = (SFDataPtr) userData;
	
	if (item==sfHookFirstCall || item==sfHookLastCall)
		return item;
	
	if (item==sfItemVolumeUser) {
		sfUserData->replyPtr->sfFile.name[0] = '\0';
		sfUserData->replyPtr->sfFile.parID = 2;
		sfUserData->replyPtr->sfIsFolder = false;
		sfUserData->replyPtr->sfIsVolume = false;
		sfUserData->replyPtr->sfFlags = 0;
		item = sfHookChangeSelection;
	}
		
	if (!SameFile(&sfUserData->replyPtr->sfFile, &sfUserData->oldSelection)) {
		BlockMove(&sfUserData->replyPtr->sfFile, &curSpec, sizeof(FSSpec));
		MakeCanonFSSpec(&curSpec);
		
		if (curSpec.vRefNum!=sfUserData->oldSelection.vRefNum)
			GetDeskFolderSpec(&gDeskFolderSpec, curSpec.vRefNum);	
		SetSelectButtonName(curSpec.name, ShouldHiliteSelect(&curSpec), theDlg);
		
		BlockMove(&sfUserData->replyPtr->sfFile, &sfUserData->oldSelection, sizeof(FSSpec));
	}
	
	if (item==kSelectItem)
		item = sfItemOpenButton;
		
	return item;
}


pascal Boolean MyModalFilter(DialogPtr theDlg, EventRecord *ev, short *itemHit, Ptr myData)
{
	Boolean evHandled;
	char keyPressed;
	OSType refCon;
	
	refCon = GetWRefCon(theDlg);
	if (refCon!=sfMainDialogRefCon)
		return false;
		
	evHandled = false;
	
	switch (ev->what) {
		case keyDown:
		case autoKey:
			keyPressed = ev->message & charCodeMask;
			if ((ev->modifiers & cmdKey) != 0) {
				switch (keyPressed) {
					case kSelectKey:
						HitButton(theDlg, kSelectItem);
						*itemHit = kSelectItem;
						evHandled = true;
						break;
				}
			}
			break;
	}
	
	return evHandled;
}


void HitButton(DialogPtr theDlg, short item)
{
	short iType;
	ControlHandle iHndl;
	Rect iRect;
	long fTicks;
	
	GetDItem(theDlg, item, &iType, (Handle *)&iHndl, &iRect);
	HiliteControl(iHndl, inButton);
	Delay(5, &fTicks);
	HiliteControl(iHndl, 0);
}


pascal Boolean FilterAllFiles(CInfoPBPtr pb,  Ptr myDataPtr)
{
	if (pb->hFileInfo.ioFlAttrib & (1<<4))	/* file is a directory */
		return false;

	return true;
}


void SetSelectButtonName(StringPtr selName, Boolean hilited, DialogPtr theDlg)
{
	ControlHandle selectButton;
	short iType;
	Handle iHndl;
	Rect iRect;
	Str255 storeName, tempLenStr, tempSelName;
	short btnWidth;
	
	BlockMove(selName, tempSelName, selName[0]+1);
	GetDItem(theDlg, kSelectItem, &iType, &iHndl, &iRect);
	
	/* truncate select name to fit in button */
	
	btnWidth = iRect.right - iRect.left;
	BlockMove(gSelectString, tempLenStr, gSelectString[0]+1);
	p2cstr(tempLenStr);
	strcat((char *)tempLenStr, " ÒÓ  ");
	c2pstr((char *)tempLenStr);
	btnWidth -= StringWidth(tempLenStr);
	TruncString(btnWidth, tempSelName, smTruncMiddle);
	
	BlockMove(gSelectString, storeName, gSelectString[0]+1);
	p2cstr(storeName);
	p2cstr(tempSelName);
	strcat((char *)storeName, " Ò");
	strcat((char *)storeName, (char *)tempSelName);
	strcat((char *)storeName, "Ó");
	
	c2pstr((char *)storeName);
	c2pstr((char *)tempSelName);
	SetCTitle((ControlHandle)iHndl, storeName);
	
	SetDItem(theDlg, kSelectItem, iType, iHndl, &iRect);

	if (hilited)
		HiliteControl((ControlHandle)iHndl, 0);
	else
		HiliteControl((ControlHandle)iHndl, 255);		
}


Boolean SameFile(FSSpec *file1, FSSpec *file2)
{
	if (file1->vRefNum != file2->vRefNum)
		return false;
	if (file1->parID != file2->parID)
		return false;
	if (!EqualString(file1->name, file2->name, false, true))
		return false;
	
	return true;
}


OSErr GetDeskFolderSpec(FSSpec *fSpec, short vRefNum)
{
	DirInfo infoPB;
	OSErr err;
	
	if (!gHasFindFolder) {
		fSpec->vRefNum = -9999;
		return -1;
	}
	
	fSpec->name[0] = '\0';
	err = FindFolder(vRefNum, kDesktopFolderType, kDontCreateFolder, 
						&fSpec->vRefNum, &fSpec->parID);
	if (err!=noErr)
		return err;
	
	return MakeCanonFSSpec(fSpec);
}


Boolean ShouldHiliteSelect(FSSpec *fSpec)
{
	if (SameFile(fSpec, &gDeskFolderSpec)) {
		BlockMove(gDesktopFName, fSpec->name, gDesktopFName[0]+1);
		return kCanSelectDesktop;
	}
	else
		return true;
}


OSErr MakeCanonFSSpec(FSSpec *fSpec)
{
	DirInfo infoPB;
	OSErr err;

	if (fSpec->name[0] != '\0')
		return noErr;
		
	infoPB.ioNamePtr = fSpec->name;
	infoPB.ioVRefNum = fSpec->vRefNum;
	infoPB.ioDrDirID = fSpec->parID;
	infoPB.ioFDirIndex = -1;
	err = PBGetCatInfo((CInfoPBPtr)&infoPB, false);
	fSpec->parID = infoPB.ioDrParID;
	
	return err;
}

/******************************************************************************
 ConcatPStrings
 
		Concatenate two Pascal strings by attaching the second string on
		the end of the first string.
		(stolen from TBUtilities.cp in TCL)
 ******************************************************************************/

static void	ConcatPStrings(
	Str255		first,
	ConstStr255Param second)
{
    
    short charsToCopy;
    
    /* Truncate if concatenated string would be longer than 255 chars.  */
#define Min(x,y) ((x) < (y) ? (x) : (y))

    charsToCopy = Min(second[0], 255 - first[0]);
    BlockMove(second + 1, first + first[0] + 1, (long) charsToCopy);
    first[0] += charsToCopy;
}

