/*
	File:		PackageMgrPatches.c

	Contains:	Routines which patch the Package Manager traps.

	Written by:	Erich Ringewald

	Copyright:	© 1986-1992 by Apple Computer, Inc., all rights reserved.

	Change History (most recent first):
	 
		 <3>	 3/31/92	DCL		Include StandardFile. Because it was removed from Packages.h
		 <0>	 x/xx/86	ELR		New Today.

*/ 
 
#include <types.h>
#include <memory.h>
#include <quickdraw.h>
#include <fonts.h>
#include <events.h>
#include <dialogs.h>
#include <desk.h>
#include <resources.h>
#include <osutils.h>
#include <packages.h>
#include <standardfile.h>
#include <retrace.h>
#include <menus.h>
#include <windows.h>
#include <files.h>
#include <toolutils.h>
#include <segload.h>
#include <AppleEventsInternal.h>

#include "Glue.h"
#include "Lomem.h"
#include "Data.h"
#include "SysMisc.h"
#include "Patches.h"
#include "Puppet.h"

/************************************************************************************
 * Patch to SFGetFile/SFPGetFile (PACK3 selectors) for puppet string Open.  This patch
 * is reached after we have told the application that there was a mousedown in the menu
 * bar, and then that the user selected the "OpenÉ" item from the menu. The application
 * calls PACK3, which we patch to come here.  Instead of the usual modal dialog, we
 * just return a specification for the file being puppet Open'd.  Of course, we have to
 * go through the normal hoops, like checking the type list and calling the file filter.
 * NOTE: This only works with the older-style SFGetFiles that employ a Working Directory.
 * The newer FSSpec-based ones are not supported, since callers using them should also
 * be AppleEvent-aware, and therefore never require puppet-stringing.
 ************************************************************************************/

/* Some function prototypes that should be in (yet another) header file */
OSErr			FileIDToAppFile(AETFDescriptor *, AppFile *, short *, long *);
void			NextListParam(AETFDescriptor **);

/* Function prototypes for the application routines called by the SFGetFile handler. */
typedef pascal short (*DialogHookPtr) (short item, Ptr theDialog);
typedef pascal Boolean (*FileFilterFncPtr) (FileParam *paramBlockPtr);

/* We truncate the file names we hand back, since they are generated by puppet master. */
#define MAX_SF_FNAME_LEN	(63)

/* InTypeList.  Check whether the puppet file is in the typelist given to SFGetFile. */
Boolean
InTypeList(long fType, SFTypeList *pSFTypeList, short numTypes)
	{
	/* count of zero means all types are OK */
	if (numTypes == (-1))
		return(true);
	
	/* traverse the array until type matches or the count is exhausted */
	while (--numTypes >= 0)
		if ((*pSFTypeList)[numTypes] == fType)
			return(true);
	
	/* Ze count he eez tired */
	return(false);
	}

/* AcceptedByFileFilter. Call the filter, and return whether the file is OK. */
short
AcceptedByFileFilter(AppFile *pActiveDoc, Ptr fileFilterFnc)
	{
	CInfoPBRec				myCInfoPBRec;
	unsigned long			olda5;
	Boolean					retval;

	if (fileFilterFnc == nil)
		return true;
	
	/* Get catalog info required by the filter */
	myCInfoPBRec.dirInfo.ioNamePtr = &pActiveDoc->fName;
	myCInfoPBRec.dirInfo.ioVRefNum = pActiveDoc->vRefNum;
	myCInfoPBRec.dirInfo.ioFDirIndex = 0;
	myCInfoPBRec.dirInfo.ioDrDirID = 0;
	if (PBGetCatInfo(&myCInfoPBRec, SyncHFS) != noErr)
		return(false);

	/* Filter returns whether should be filtered! Our return value has inverse sense. */
	olda5 = CurrentA5SimpleSetup();		
	retval = (CALL_FNC_PTR(FileFilterFncPtr, fileFilterFnc, (&myCInfoPBRec)) == false);
	A5SimpleRestore(olda5);
	
	return(retval);
	}

/* CopyWDToCurProc.  Given an existing Working Directory refnum, open a new WD for the
 * same directory.  The new copy is needed because a) the puppet master might close the
 * original, and b) WDs are tagged by HFS to the process that opened them, and
 * closes them when the process goes away.
 */
void
CopyWDToCurProc(short wdRefNumSrc, WDPBRec *pWDPB)
	{	
	pWDPB->ioVRefNum = wdRefNumSrc;
	pWDPB->ioWDIndex = 0;
	pWDPB->ioWDProcID = 0;
	pWDPB->ioNamePtr = nil;
	pWDPB->ioCompletion = nil;
	PBGetWDInfo(pWDPB, SyncHFS);

	pWDPB->ioVRefNum = pWDPB->ioWDVRefNum;	
	pWDPB->ioWDProcID = 0;
	pWDPB->ioNamePtr = nil;
	pWDPB->ioCompletion = nil;
	(void) PBOpenWD(pWDPB, SyncHFS);
	}
	
/* IsForcedOpen.  The main patch to SFGetFile.  If the call to SFGetFile is to get a
 * puppet file opened, set up the puppet file specification.  Returns whether this
 * was done.
 */
Boolean
IsForcedOpen(Ptr fileFilterFnc, short numTypes, Ptr dlgHook, SFTypeList *pSFTypeList, SFReply *pSFReply, short dlgID)
	{
	AppFile				*pActiveDoc;
	EventListPtr		activeInstrsPtr;
	EventListHdl		activeInstrsHdl;
	EventRecord			*pActiveEvent;
	Boolean				isForcedOpen;
	PuppetVars			*myPuppetVarsPtr;
	MFmsgBlkPtr			msgBlk;
	WDPBRec				wdpbRec;
	AppFile				appFile;
	short				theVol;
	long				theDir;
	long				strLengthPlus1;
	DialogPtr			pDialog;
	unsigned long		olda5;

	isForcedOpen = false;
	olda5 = ProcessMgrA5SimpleSetup();
	
	if (InstrsQueueIsEmpty() == false)
		if ((activeInstrsHdl = GetActiveInstrsHdl()) != nil)
			{
			HLock(activeInstrsHdl);
			
			activeInstrsPtr = *activeInstrsHdl;
			
			/* Since we really look at the instruction preceding the current one */
			assert((unsigned long)activeInstrsPtr->offset >= (SIZEOF_EVENTLIST_HDR + sizeof(EventRecord)));
			pActiveEvent = ((Ptr) activeInstrsPtr) + (unsigned long)activeInstrsPtr->offset;
			pActiveEvent--;

			if (*((char *)&pActiveEvent->what + 1) == app4Evt && (*((char *)&pActiveEvent->message) == instrOpen))
				{
				isForcedOpen = true;

				/* Make pActiveDoc->vRefNum a valid WD for the current process.  Copy
				 * it from current WD, or open it from translation vrefnum/dirid.
				 */
				myPuppetVarsPtr = &pCurrentProcess->p_puppetvars;
				if ((msgBlk = myPuppetVarsPtr->puppetEppcMsgBlk) == nil)
					{
					pActiveDoc = ((Ptr) activeInstrsPtr) + (pActiveEvent->message & LO3BYTES);
					CopyWDToCurProc(pActiveDoc->vRefNum, &wdpbRec);
					}
				else
					{					
					pActiveDoc = nil;
					if (FileIDToAppFile((AETFDescriptor *) (msgBlk->addrOfMsg), &appFile, &theVol, &theDir) == noErr)
						{
						NextListParam((AETFDescriptor **) &msgBlk->addrOfMsg);
						wdpbRec.ioNamePtr = nil;
						wdpbRec.ioVRefNum = theVol;	
						wdpbRec.ioWDProcID = 0;
						wdpbRec.ioWDDirID = theDir;
						wdpbRec.ioCompletion = nil;
						(void) PBOpenWD(&wdpbRec, SyncHFS);
						appFile.vRefNum = wdpbRec.ioVRefNum;
						pActiveDoc = &appFile;
						}
					}
				pSFReply->vRefNum = wdpbRec.ioVRefNum;
				
				if ( (pActiveDoc != nil)
					&&  (InTypeList(pActiveDoc->fType, pSFTypeList, numTypes) && AcceptedByFileFilter(pActiveDoc, fileFilterFnc)) )
					{					
					/* Call word dialog hook to clear r/o flag */
					if (dlgHook != nil)
						{						
						/* Fake out word <= 3.01 to not make the doc r/o by giving it an extra init call */
						pDialog = GetNewDialog(dlgID, nil, (Ptr)(-1));
						if (pDialog)
							{
							(void) CurrentA5SimpleSetup();
							CALL_FNC_PTR(DialogHookPtr, dlgHook, (-1, pDialog));
							(void) ProcessMgrA5SimpleSetup();
							DisposDialog(pDialog);
							}
						}
					pSFReply->good = true;
					pSFReply->fType = pActiveDoc->fType;
					pSFReply->version = pActiveDoc->versNum;
					if ((strLengthPlus1 = Length(&(pActiveDoc->fName)) + 1) > (MAX_SF_FNAME_LEN + 1))
						strLengthPlus1 = MAX_SF_FNAME_LEN + 1;
					BlockMove(&pActiveDoc->fName, &pSFReply->fName, strLengthPlus1);
					}
				else
					{
					/* Tell sawdust-head that the PACK 3 failed */
					pSFReply->good = false;

					/* Shouldn't cancel, and needn't close WD, if the file translation failed */
					if (pActiveDoc != nil)
						{
						/* Close the WD, if we opened it */
						if (wdpbRec.ioWDCreated != 0)
							PBCloseWD(&wdpbRec, SyncHFS);
						
						/* Force back to offending instruction, then cancel */
						activeInstrsPtr->offset -= sizeof(EventRecord);
						CancelSwitchWithError();
						}
					}
				}
			HUnlock(activeInstrsHdl);
			}

	A5SimpleRestore(olda5);	
	return(isForcedOpen);
	}
