/** EggUtils.c

	Utility routines that depend on CoDE or Egg stuff, so they couldn't go
	into DaveUtils.

**/

#include <math.h>
#include <BEEcmd.h>
#include <Packages.h>		//	IUMagString

#include "CDirTPanel.h"
#include "CPaneTPanel.h"
#include "EGG_Strs.h"
#include "BEE_GeometryGlue.h"
#include "DaveUtils.h"
#include "CoSAErr.h"
#include "CPaneTimeEntry.h"
#include "CNumText.h"
#include "CEggApp.h"
#include "CommandGlue.h"
#include "CDirProject.h"
#include "CPanoProjOutline.h"
#include "CComposition.h"
#include "CLayerSelection.h"
#include "CWindowPlus.h"
#include "CDirProjItem.h"
#include "CPanoProjComp.h"
#include "EggCommands.h"
#include "EggPrefs.h"
#include "CEffects.h"
#include "CDirDLOG.h"
#include "CDirProjLayer.h"
#include "CPanoProjLayer.h"		//	for access to i_clayer->i_layerH
#include "RenderSettings.h"
#include "CECOutlinePano.h"
#include "OutputModule.h"
#include "CLayer.h"
#include "CustomMenu.h"
#include "PixelAspectRatio.h"
#include "TBUtilities.h"
#include "CBartenderPlus.h"


#include "EggUtils.h"

static BEE_Dsf		S_last_dsf;
static short		S_last_zoom;


/** FindMenu

	This is a not so nice situation, regarding menus
	that might or might not be installed in the menubar.
	
	Policy should probably be revised.

**/
MenuHandle	FindMenu(short menuID, Boolean genevaNine)
{
	MenuHandle	theMenu = NULL;

	theMenu = GetMHandle(menuID);
	
	if (theMenu == NULL) {

		FailNILRes(theMenu = GetMenu(menuID));
		
		//	$$$$$$$$$$$		
		if ((**theMenu).menuProc == NULL)
			FailNILRes(theMenu = GetMenu(menuID));
		
		/*
			no longer need to do this, since we're using the
			Flex popups instead
			
		if (genevaNine) {
			short	font, size, style;
			
			GetFontStyleSheet(FontStyle_SMALL, &font, &size, &style);
			
			InstallCustomMDEF(theMenu, font, size, style);
		}
		*/
	}
	
	U_ASSERT((**theMenu).menuProc != NULL);
	
	return theMenu;
}


long
PopUpSmallFixedWidthMenuSelect(MenuRef menu, short fixed_width0, short top, short left, short popUpItem)
{
	short	font, size, style;
	Point	loc;
	
	GetFontStyleSheet(FontStyle_SMALL, &font, &size, &style);
	
	loc.h = left;
	loc.v = top;
	
	return DoFlexSizePopMenu(DH(menu)->menuID, menu, loc, fixed_width0, popUpItem, font, size, style);
}


long
PopUpSmallMenuSelect(MenuRef menu, short top, short left, short popUpItem)
{
	return PopUpSmallFixedWidthMenuSelect(menu, 0, top, left, popUpItem);
}




typedef struct {
	short		size;
	short		style;
	u_char		name[64];
} FontStyleSheetRec, *FontStyleSheetPtr, **FontStyleSheetH;


typedef struct {
	FontStyleSheet	sheet;
	short			font;
	short			size;
	short			style;
} CachedStyle;


/** GetFontStyleSheet

	This whole thing would probably be simpler to be a single resource
	that's an array of the styles.  You could then load & lock hi the
	resource once at the beginning and just index into it quickly --
	none of this caching stuff, it would all be cached.
	
	Ah, hindsight.
	
	Actually, it would probably be easier to localize if we leave the
	separate resources (since they have useful names), and just read
	in all from 128..X into our own array of CachedStyle.

**/
void		GetFontStyleSheet(FontStyleSheet sheet,				/* >> */
					short *font0, short *size0, short *style0)	/* << */
{
	Handle			 	fssH;
	FontStyleSheetPtr	fssP;
	short				id = 128 + sheet;
	static CachedStyle	S_last_style = { FontStyle_NONE, 0, 0, 0 };
	
	if (sheet != S_last_style.sheet) {
		
		fssH = GetResource('FntS', id);
		
		if (fssH && DH(fssH)) {
			HLock(fssH);
			
			fssP = DH((FontStyleSheetH)fssH);
			
			if (fssP->name[0])
				GetFNum(fssP->name, &S_last_style.font);
			else
				S_last_style.font = 0;
			
			S_last_style.size = fssP->size;
			S_last_style.style = fssP->style;

			HUnlock(fssH);		// leave floating around in the heap for faster access later
		} else {
			U_REPORT(0, U_Sev_WARNING, (U_Fmt_CUSTOM, STR(403), (int)id));
			
			S_last_style.font = 0;
			S_last_style.size = 0;
			S_last_style.style = 0;
		}

		// even if we get an error, we do this so we don't keep getting errors
		S_last_style.sheet = sheet;
	}
		
	if (font0)
		*font0 = S_last_style.font;
	
	if (size0)
		*size0 = S_last_style.size;
	
	if (style0)
		*style0 = S_last_style.style;
}


void SetFontSizeStyleFromSheet(FontStyleSheet sheet)
{
	short		font, size, style;

	GetFontStyleSheet(sheet, &font, &size, &style);
	
	TextFont(font);
	TextSize(size);
	TextFace(style);
}

void			SaveFontInfo(FEE_FontInfoRec *fontRec)
{
	fontRec->txFont	= qd.thePort->txFont;
	fontRec->txFace	= qd.thePort->txFace;
	fontRec->txMode	= qd.thePort->txMode;
	fontRec->txSize	= qd.thePort->txSize;
}

void			RestoreFontInfo(FEE_FontInfoRec *fontRec)
{
	TextFont(fontRec->txFont);
	TextFace(fontRec->txFace);
	TextMode(fontRec->txFace);
	TextSize(fontRec->txSize);
}

/*
	puts dashes next to menu items in a group
	itemFlags is 32 bits, each bit representing an item
	bit 0 set means nothing, and is ignored
	bit 1 set means put a dash next to menu item number 1 etc.
	
	Also, if there is only one item that needs a dash, 
	then it gets a radio button instead
*/
void		NewDashItems(MenuHandle theMenu, int firstItem, int lastItem, long itemFlags)
{
	int			theItem;
	char		dashMark, checkMark;

	GetMenuCheckMarkChar(FEE_MenuCheckMark_DASH, FontStyle_SYS, &dashMark);
	GetMenuCheckMarkChar(FEE_MenuCheckMark_CHECK, FontStyle_SYS, &checkMark);
	
	//	this tests whether there is only one item to be dashed
	//	if so, we want to radio button it instead
	if ((itemFlags & (itemFlags - 1)) == 0) {
		short	buttonItem;
		
		buttonItem = 0;
		itemFlags = itemFlags >> 1;
		
		do {
			buttonItem++;
			itemFlags = itemFlags >> 1;
		} while (itemFlags != 0);
		
		RadioButtonItem(theMenu, firstItem, lastItem, buttonItem, checkMark);
	} else {		
		if (firstItem == 0)
			firstItem = 1;
		
		if (lastItem == 0)
			lastItem = CountMItems(theMenu);

		for (theItem = firstItem; theItem <= lastItem; ++theItem)
		{
			if (((itemFlags >> theItem) & 0x01) != 0)
				SetItemMark(theMenu, theItem, dashMark);
			else
				SetItemMark(theMenu, theItem, (char)noMark);
		}
	}
}


void SetLastDSF(BEE_Dsf dsf)
{
	S_last_dsf = dsf;
}


void SetLastZoom(short zoom)
{
	S_last_zoom = zoom;
}


short GetDefaultZoom(void)
{
	if (S_last_zoom)
		return S_last_zoom;
	else
		return 1;
}


/** GetDefaultDsf

	

**/
BEE_Dsf GetDefaultDsf(void)
{
	BEE_Dsf		dsf, default_dsf;
	
	// $$$ read default_dsf from PREF, which can be zero
	default_dsf.x = default_dsf.y = 0;

	if (default_dsf.x && default_dsf.y)
		return default_dsf;
	else if (S_last_dsf.x && S_last_dsf.y)
		return S_last_dsf;
	
	dsf.x = dsf.y = 1;
	
	return dsf;
}


#define	kDropFramesPerTenMin		17982
#define	kDropFramesPerMin			1798


static TimeFormatInfo		S_tformat;


static void
ExamineTimeInfo(const TimeFormatInfo *tfi, M_Fixed underlying_fps0,
					Boolean *do_timecode,
					Boolean *do_dropframe,			// will be one or the
					Boolean *do_nondropframe,		//  other or neither, but not both
					TDB_TimeRecord *frame_step)
{
	*do_nondropframe = *do_dropframe = *do_timecode = FALSE;

	if (tfi->use_timecode || underlying_fps0 == 0) {
		long		modulo = tfi->timecode_framemax;

		*do_timecode = TRUE;
		
		frame_step->value = 1;
		frame_step->scale = modulo;
		
		if (modulo == 30 && IS_2997_FPS(underlying_fps0)) {

			// for non-drop we divide by 29.97 and display every #
			// for drop we divide by 29.97 too, but the displayed #'s skip
			
			frame_step->value = 100;
			frame_step->scale = 2997;

			if (tfi->nondrop30) {
				// non-drop -- we're no longer tied to real time
				//	(since we'll divide the time by 29.97, and then
				//	multiply the framecount by 30 to get seconds)
				*do_nondropframe = TRUE;
			} else {
				// drop -- use that wacky drop-frame stuff
				*do_dropframe = TRUE;
			}
		}
	} else {
		FCE(TDB_FrameRate2Duration(underlying_fps0, frame_step));
	}
}


/*
#ifdef NO_NEGATIVE_TIMECODE

	// we no longer use this negative timecode as 9:59:29:29 thing for three
	//	reasons:	1- stressed the time stuff with overflows
	//				2- didn't make sense for negative time velocities
	//				3- people didn't like it much

#define NEG_TIME_BASE_SECONDS		(10L * 60 * 60)		// 10 hours

#define NEG_NONDROP_TIME_BASE_NUM	108000000			// 10:00:00:00nd
#define NEG_NONDROP_TIME_BASE_DEN	2997

#define NONDROP_COMMON_FACTOR		27		// store bases in reduced form for faster compares

typedef struct {
	TDB_TimeRecord		base;
	TDB_TimeRecord		base_half;
} NegBaseInfo;

enum {
	kDropAndNormalBase,			// for drop-timecode and normal timecode
	kNonDropBase				// for nondrop-timecode
};

static const NegBaseInfo S_neg_base[2] = {
	{	
		{	NEG_TIME_BASE_SECONDS,		1 },
		{	NEG_TIME_BASE_SECONDS/2,	1 }
	},
	{ 	
		{	NEG_NONDROP_TIME_BASE_NUM/NONDROP_COMMON_FACTOR,		NEG_NONDROP_TIME_BASE_DEN/NONDROP_COMMON_FACTOR },
		{	NEG_NONDROP_TIME_BASE_NUM/(NONDROP_COMMON_FACTOR*2),	NEG_NONDROP_TIME_BASE_DEN/NONDROP_COMMON_FACTOR }
	}
};

#endif
*/

/** TimeToComponents

Old comments:
	New way always does a floor() type rounding, so that timecode matches that in
	movies (missing a frame always rounds backwards, at least in QT).
	
	But perhaps we should round ceil() for duration, so only a zero duration would
	be reported as zero.  But then displaying start/end/duration would not show
	end == duration when start == 0 and end & duration are not exact multiples of
	the display framerate...
	
	Compromise: we'll round to nearest for duration.

New comments:

	Using TDB routines to do the counting.
	Times: always does floor()-type (i.e. truncate to -inf)
	Durations: truncate away from zero

**/
void			TimeToComponents(const TDB_TimeRecord *tr1,
								const TimeFormatInfo *tfi0,		// if NULL, uses Std
								TimeFormatType format_type,
								M_Fixed underlying_fps0,
								TimeComponents *tc)			/* << */
{
	TDB_TimeRecord		tr = *tr1;
	Boolean				timecode, dropframe, nondropframe;
	TDB_TimeRecord		frame_step;
	long				units;
	
	U_MEMCLR(sizeof(*tc), tc);

	if (tfi0 == NULL)
		tfi0 = &S_tformat;
	
	ExamineTimeInfo(tfi0, underlying_fps0, &timecode, &dropframe, &nondropframe, &frame_step);

	if (timecode) {
		long		modulo = tfi0->timecode_framemax;

		if (tc->negative = (tr.value < 0)) {	/*=*/
			#ifdef NO_NEGATIVE_TIMECODE
				FCE(TDB_AddTime(&S_neg_base[nondropframe ? kNonDropBase : kDropAndNormalBase].base, &tr, 0, &tr));
			#else
				tr.value = -tr.value;
			#endif
		}
	
		// round away from zero when doing Duration, towards -inf for Time
		FCE(TDB_CountFrames(&tr, &frame_step, (format_type == kFormatDuration || tc->negative), &units));
		
		if (dropframe) {			// adjust frame count so we skip :00 and :01 of every
									// minute except minutes divisible by 10
			long			offset, remain;
			
			offset = (units / kDropFramesPerTenMin) * 18;	// 18 frames per 10 minutes
			remain = units % kDropFramesPerTenMin;			// how much left over?
			if (remain >= 1800) {							// first minute is special
				offset += 2;
				remain -= 1800;
			} else {
				remain = 0;
			}
			
			offset += (remain / kDropFramesPerMin) * 2;		// 2 frames per minute

			units += offset;
		}

		tc->frames = units % modulo;
		units = (units - tc->frames) / modulo;
		
		tc->secs = units % 60;
		units = (units - tc->secs) / 60;

		tc->mins = units % 60;
		units = (units - tc->mins) / 60;
		
		tc->hours = units % 24;
	} else {
		Boolean		negated = FALSE;
		Boolean		round_up;
		
		if (tr.value < 0) {
			negated = TRUE;
			tr.value = -tr.value;
		}
			
		// truncate away from zero when doing Duration, truncate towards -inf for Time
		round_up = (format_type == kFormatDuration) || negated;
		
		// countframes only deals with positives
		FCE(TDB_CountFrames(&tr, &frame_step, round_up, &tc->frames));
		
		if (negated)
			tc->frames = -tc->frames;

		if (format_type == kFormatTime) {
			tc->frames += tfi0->starting_frame;
		}
		
		if (tfi0->frames_per_foot) {
			tc->feet = tc->frames / tfi0->frames_per_foot;
			tc->frames -= tc->feet * tfi0->frames_per_foot; 
		}
	}
}


/** ComponentsToTime

	warning: make sure frames_per_foot is filled out!

	support for two types of negative components: tc.negative or
		hours being > (S_neg_time_base/2)
		
		i.e. > 5 hours

**/
void			ComponentsToTime(const TimeComponents *tc,
								const TimeFormatInfo *tfi0,		// if NULL, uses Std
								TimeFormatType format_type,
								M_Fixed underlying_fps0,
								TDB_TimeRecord *tr)
{
	TDB_TimeRecord		frame_step;
	Boolean				timecode, dropframe, nondropframe;
	M_Ratio				frames;
	
	if (tfi0 == NULL)
		tfi0 = &S_tformat;
	
	ExamineTimeInfo(tfi0, underlying_fps0, &timecode, &dropframe, &nondropframe, &frame_step);

	if (timecode) {
		frames.num = tc->frames + (tc->secs + (tc->mins + (tc->hours * 60)) * 60L) * tfi0->timecode_framemax;

		if (dropframe) {
			long 		offset;
			
			offset = (frames.num / 18000) * 18;				// 18 frames per 10 minutes
			offset += ((frames.num % 18000) / 1800) * 2;		// 2 frames per minute
			
			frames.num -= offset;
		}
		
		if (tc->negative)
			frames.num = -frames.num;

		frames.den = 1;
		
		FCE(TDB_MulTime(&frame_step, &frames, 0, tr));
		
		#ifdef NO_NEGATIVE_TIMECODE
			long				compare;

			FCE(TDB_CompareTime(tr, &S_neg_base[nondropframe ? kNonDropBase : kDropAndNormalBase].base_half,
								&compare));
			
			if (compare > 0) {
				// make it negative by subtracting off the negative time base
				FCE(TDB_SubTime(tr, &S_neg_base[nondropframe ? kNonDropBase : kDropAndNormalBase].base, 0, tr));
			}
		#endif
		
	} else {
		frames.num = tc->frames + tc->feet * tfi0->frames_per_foot;
		frames.den = 1;
		
		if (format_type == kFormatTime)
			frames.num -= tfi0->starting_frame;

		FCE(TDB_FrameRate2Duration(underlying_fps0, &frame_step));

		FCE(TDB_MulTime(&frame_step, &frames, 0, tr));
	}
}


void			SetStdTimeFormat(const TimeFormatInfo *tfi)
{
	S_tformat = *tfi;
}


void			GetStdTimeFormat(TimeFormatInfo *tfi)
{
	*tfi = S_tformat;
}

				// $$$ check this

/** GeneralFormatTime

**/
char *
GeneralFormatTime(const TDB_TimeRecord *tr,
								const TimeFormatInfo *tfi,
								TimeFormatType format_type,
								M_Fixed underlying_fps0,
								char *timeString)
{
	char				sign[2];
	TimeComponents		tc;
	char				sep = ':';
	const char			*suffix = "";
	
	TimeToComponents(tr, tfi, format_type, underlying_fps0, &tc);

	if (tfi->use_timecode || underlying_fps0 == 0) {
		#ifdef NO_NEGATIVE_TIMECODE
			sign[0] = 0;
		#else
			if (tc.negative) {
				sign[0] = '-';
				sign[1] = 0;
			} else {
				sign[0] = 0;
			}
		#endif
		
		if (tfi->timecode_framemax == 30) {
			if (IS_2997_FPS(underlying_fps0)) {
				if (tfi->nondrop30) {
					suffix = STR(353);		// "nd"
				} else {
					sep = ';';				// dropframe sep
				}
			}
		}
		
		sprintf(timeString, "%s%1.1ld%c%1.2ld%c%1.2ld%c%1.2ld%s", sign,
							tc.hours, sep, tc.mins, sep, tc.secs, sep, tc.frames, suffix);
	} else if (tfi->frames_per_foot) {
		sprintf(timeString, "%04ld+%02ld", tc.feet, tc.frames);
	} else {
		sprintf(timeString, "%05ld", tc.frames);
	}

	return timeString;
}



/** FormatTime
**/
char *			FormatTime(const TDB_TimeRecord *tr, M_Fixed underlying_fps0, char *str)
{
	return GeneralFormatTime(tr, &S_tformat, kFormatTime, underlying_fps0, str);
}


char *			FormatDuration(const TDB_TimeRecord *tr, M_Fixed underlying_fps0, char *str)
{
	return GeneralFormatTime(tr, &S_tformat, kFormatDuration, underlying_fps0, str);
}


M_Fixed		GetItemUnderlyingFPS(BEE_ItemH itemH)
{
	M_Fixed		fps = 0;
	
	switch (BEE_Item_TYPE(itemH)) {

		case BEE_ItemType_COMP: {
			fps = BEE_Comp_FRAMERATE(itemH);
			break;
		}
		
		case BEE_ItemType_SOLID: {
			BEE_LayerH		layerH;
			
			U_ASSERT(LIST_NUM_ITEMS(BEE_Item_DEPENDANTS(itemH)) == 1);
			
			// get a solid's layer by following back up its dependants list
			FCE(LIST_Get4Item(BEE_Item_DEPENDANTS(itemH), LIST_Index_START,
							(long*)&layerH));

			U_ASSERT(layerH);
			
			fps = BEE_Comp_FRAMERATE(BEE_Layer_PARENT_COMP(layerH));
			break;
		}
		
		case BEE_ItemType_FOOTAGE: {
			fps = PIN_SeqSpec_DISPLAY_FPS(BEE_Footage_PIN_SEQ(itemH));
			break;
		}
	}
	
	return fps;
}


M_Fixed		GetLayerUnderlyingFPS(BEE_LayerH layerH, NIM_LayerOriginType origin)
{
	M_Fixed		fps;

	if (origin == NIM_LayerOrigin_COMP) {
		fps = GetItemUnderlyingFPS(BEE_Layer_PARENT_COMP(layerH));
	} else {
		fps = GetItemUnderlyingFPS(BEE_Layer_SOURCE_ITEM(layerH));
		if (fps == 0) {
			// if source is a still or has no framerate for some reason,
			//	use the comp's
			fps = GetItemUnderlyingFPS(BEE_Layer_PARENT_COMP(layerH));
		}
	}
	
	return fps;
}


char *
FormatItemTime(const TDB_TimeRecord *tr, BEE_ItemH itemH, char *str)
{
	return FormatTime(tr, GetItemUnderlyingFPS(itemH), str);
}


char *
FormatItemDuration(const TDB_TimeRecord *tr, BEE_ItemH itemH, char *str)
{
	return FormatDuration(tr, GetItemUnderlyingFPS(itemH), str);
}


// note: prototype in EggPrefs.h
void
PrefToTimeFormatInfo(TimeDisplayPref tdp, long starting_frame,
						TimeFormatInfo *tfi)
{
	U_ASSERT(tdp.nondrop30 == 0 || tdp.nondrop30 == 1);		// in case old fixed data was in there
	
	U_MEMCLR(sizeof(*tfi), tfi);

	tfi->use_timecode = (tdp.time_display_format == kTimeDisplayFormatTimecode);
	tfi->nondrop30 = tdp.nondrop30 != 0;
	tfi->timecode_framemax = tdp.framemax;
	tfi->frames_per_foot = (tdp.time_display_format == kTimeDisplayFormatFeetFrames) ?
							tdp.frames_per_foot : 0;
	tfi->starting_frame = (starting_frame < 0) ? 0 : starting_frame;
}


static	Err		PIN_SeqSpecModFlagIsSet(
	PIN_SeqSpecH	pinoutH, 
	PIN_ModuleFlags	flag, 
	Boolean			*set)
{
	Err				err = Err_NONE;
	PIN_ModuleInfo	mod_info;
	
	*set = FALSE;
	
	err = PIN_GetModuleInfo(-1, 
		PIN_SeqSpec_MODULE_SIG(pinoutH), &mod_info);
	
	if (!err) {
		*set = (mod_info.flags & flag) != 0;
	}

	return err;
}


FEE_ItemIconType		GetBEEItemIcon(BEE_ItemH itemH)
{
	FEE_ItemIconType	iconNum = FEE_ItemIcon_UNKNOWN;
	BEE_ItemInfo			info;
	
	RCE(BEE_GetItemInfo(itemH, &info));

	switch (info.type) {
	
		case BEE_ItemType_FOOTAGE: {
			PIN_SeqSpecH		seqSpecH = BEE_Footage_PIN_SEQ(itemH);
			
			if (DH(seqSpecH)->filenames) {
				iconNum = FEE_ItemIcon_SEQUENCE;
			} else if (info.duration.value == 0) {
				Boolean		isIllustrator = FALSE;
				
				//	we are a pict
				
				FCE(PIN_SeqSpecModFlagIsSet(
					seqSpecH, PIN_MFlag_CAN_TRANSFORM, &isIllustrator));
				
				if (isIllustrator) {
					iconNum = FEE_ItemIcon_VECTOR;
				} else {
					iconNum = FEE_ItemIcon_PICT;
				}
			} else {
				//	we are a movie or somesuch
				if ((info.flags & BEE_ItemFlag_AUDIO) != 0) {

					if ((info.flags & BEE_ItemFlag_VIDEO) != 0) {
						//	video and audio
						iconNum = FEE_ItemIcon_AV;
					} else {
						//	audio only
						iconNum = FEE_ItemIcon_AUDIO;
					}
				} else {
					//	video only
					iconNum = FEE_ItemIcon_VIDEO;
				}
			}
			break;
		}

		case BEE_ItemType_COMP:			iconNum = FEE_ItemIcon_COMP;	break;
		case BEE_ItemType_SOLID:		iconNum = FEE_ItemIcon_SOLID;	break;
		case BEE_ItemType_RENDER:		iconNum = FEE_ItemIcon_RENDER;	break;
		case BEE_ItemType_FOLDER:		iconNum = FEE_ItemIcon_FOLDER;	break;
	}
	
	return(iconNum);
}

//	***  WARNING  ***

//	This is NOT the mirror routine to DsfToMenuItem
//	this handles the MakeMovie res dialog, which has
//	current settings in it
short		MenuItemToDSF(short menuItem)
{
	switch (menuItem) 
	{
		case 1: return 0;
		case 3: return 1;
		case 4: return 2;
		case 5: return 3;
		case 6: return 4;
		default: return -1;		// custom
	}
}

short		DsfToMenuItem(BEE_Dsf dsf)
{
	short		menuItem;

	if (dsf.x == dsf.y) {
		switch (dsf.x) {
			case 1:		menuItem = kResHigh;	break;
			case 2:		menuItem = kResMedium;	break;
			case 3:		menuItem = kResThird;	break;
			case 4:		menuItem = kResLow;		break;
			default:	menuItem = kResCustom;	break;
		}
	} else {
		menuItem = kResCustom;
	}
	
	return(menuItem);
}


long		DsfMenuItemToCommand(short menuItem)
{
	long		resCommand;
	
	switch (menuItem)
	{
		case kResHigh:		resCommand = cmdHighResolution;		break;
		case kResMedium:	resCommand = cmdMedResolution;		break;
		case kResThird:		resCommand = cmdResolutionThird;	break;
		case kResLow:		resCommand = cmdLowResolution;		break;
		default:			resCommand = cmdCustomResolution;	break;
	}
	
	return(resCommand);
}

Boolean		DoCoolTimeDialog(
	const char		*title0, 
	Boolean			allowNeg, 
	TDB_TimeRecord	*minTime0, 
	TDB_TimeRecord	*maxTime0, 
	TDB_TimeRecord	*timeRec, 
	M_Fixed			underlying_fps0
) {
	CDirDLOG		*d = NULL;
	Boolean			okay = FALSE, done = FALSE;
	CPaneTimeEntry	*pteTR;
	
	TRY {
		d = new(CDirDLOG);
		d->IDirDLOG(153, NULL);
		
		if (title0) {
			Str255	theString;
			
			d->GetWindow()->SetTitle(CopyC2P(title0, theString));
		}
		
		pteTR 	= d->GetTimeEntry(3);
		
		pteTR->SetAllowNegative(allowNeg);
		pteTR->SetUnderlyingFPS(underlying_fps0);
		pteTR->SetTime(timeRec);
		
		if (minTime0)
			pteTR->SetMinTime(minTime0);

		if (maxTime0)
			pteTR->SetMaxTime(maxTime0);

		d->BeginDialog();
		
		do {
			okay = (d->DoModalDialog(cmdOK) == cmdOK);
			
			if (okay) {
				if (minTime0 && maxTime0)
					done = !pteTR->ConformToBounds();
				else
				 	done = TRUE;
				 
				if (!done) {
					d->EndDialog(cmdNull, FALSE);
					SysBeep(1);
				}
			} else
				done = TRUE;
			
		} while (!done);
		
		if (okay) {
			pteTR->GetTime(timeRec);
		}
		
		ForgetObject(d);
		
	} CATCH {
		ForgetObject(d);
	} ENDTRY;

	return(okay);
}

Boolean		GetTimeDialog(
	const char		*dialogTitle0, 
	TDB_TimeRecord	*tr, 
	M_Fixed			underlying_fps0
) {
	return DoCoolTimeDialog(dialogTitle0, TRUE, NULL, NULL, tr, underlying_fps0);
}

/******************************************************************************/

void			DoGotoTime(BEE_ItemH theItem)
{
	BEE_LayerH	theLayer	= NULL;
	CPanoProjLayer			*layer_pano;
	
	if (gEgg->i_time_panel->i_timePane->InSourceMode(&layer_pano)) {
		theLayer	= layer_pano->i_clayer->i_layerH;
		U_ASSERT(theLayer);

		theItem		= BEE_Layer_PARENT_COMP(theLayer);
	}
	
	U_ASSERT(theItem.item);
	
	gEgg->i_time_panel->i_timePane->GoToTimeDialog(theItem, theLayer);
}

void			CaptureWindowState(void)
{
	CDirProjItem	*itemDir;
	BEE_ItemH		itemH;
	BEE_LayerH		theLayer;
	GrafPtr			savePort;
	
	GetPort(&savePort);
	
	itemH.item = NULL;
	FCE(BEE_GetNextItem(itemH, &itemH));	//	root
	FCE(BEE_GetNextItem(itemH, &itemH));	//	first item
	
	while (itemH.item) {

		//	then if the window is open, set the hi bit and
		//	remember the window position
		if (itemDir = GetCItem(itemH)->i_dir.item) { /*=*/

			//	zoom, window pos on screen, scrollbars
			itemDir->StoreItemData();
			FEE_WindowPos(itemH.item).window_open	= TRUE;	
		}
		
		if (BEE_Item_TYPE(itemH) == BEE_ItemType_COMP) {
			
			FEE_FOR_EACH_LAYER(itemH, theLayer) {
				//	then if the window is open, set the hi bit and
				//	to indicate it is open
				if (itemDir = GetCLayer(theLayer)->i_dir.layer) { /*=*/
					
					//	zoom, window pos on screen, scrollbars
					itemDir->StoreItemData();
					FEE_WindowPos(theLayer).window_open	= TRUE;	
				}
				
				//	save all the effects info into the layerH
				GetCEffects(theLayer)->SaveEffectsInfoInLayer();
			} FEE_END_EACH_LAYER(itemH);
		}

		FCE(BEE_GetNextItem(itemH, &itemH));	//	next item
	}
	
	SetPort(savePort);
	CView::ForceNextPrepare();
}

void			RestoreWindowState(CDirProject *theSupervisor)
{
	BEE_ItemH		itemH;
	BEE_LayerH		theLayer;
	GrafPtr			savePort;
	
	GetPort(&savePort);
	
	itemH.item = NULL;
	FCE(BEE_GetNextItem(itemH, &itemH));	//	root
	FCE(BEE_GetNextItem(itemH, &itemH));	//	first item
	
	while (itemH.item) {
		FEE_WindowDressing			myDress;
		Boolean						openComp = FALSE, openTL = FALSE;
		short						modifierKeys = 0;

		if (FEE_WindowPos(itemH.item).window_open) {	
			//	note some projects that were saved with windows closed
			//	will come in with windows open.  blow it off and save again,
			//	and it will be fixed
			FEE_WindowPos(itemH.item).spare			= 0;
			FEE_WindowPos(itemH.item).window_open	= FALSE;
			if (!BEE_Item_MISSING(itemH))
				openComp = TRUE;
		}
		
		myDress = FEE_GetCompDress(itemH);
		
		if (myDress.seq != NULL) {
			
			if ((**(myDress.seq)).useCompState) {
				openTL = openComp;
			} else {
				if (!(**(myDress.seq)).sequencerClosed) {
					openTL = TRUE;
				}
			}
		}
		
		if (openComp ^ openTL) {
			if (openComp) {
				modifierKeys = kCompModifierKey;
			} else {
				modifierKeys = kTLModifierKey;
			}
		}
				
		if (openComp || openTL) {
			gEgg->i_projectDir->i_projectPano->OpenItemThruSelection(itemH, modifierKeys);
		}
		
		if (BEE_Item_TYPE(itemH) == BEE_ItemType_COMP) {
			CComposition				*ccomp	= GetCComp(itemH);
			NIM_EffectWindStateRecH		ewsrH;

			FEE_FOR_EACH_LAYER(itemH, theLayer) {

				if (FEE_WindowPos(theLayer).window_open) {
					//	note some projects that were saved with windows closed
					//	will come in with windows open.  blow it off and save again,
					//	and it will be fixed
					FEE_WindowPos(theLayer).spare		= 0;
					FEE_WindowPos(theLayer).window_open = FALSE;
					ccomp->i_selection->SelectLayer(theLayer, 0);
					ccomp->i_selection->OpenSelection();
				}
				
				ewsrH = NIM_Layer_WIND_STATE_GET(theLayer);
				if (ewsrH && *ewsrH) {
					if ((**ewsrH).effectWind.windowOpen) {
						GetCEffects(theLayer)->OpenItemStuff();
					}
				}
			} FEE_END_EACH_LAYER(itemH);
		}
		
		FCE(BEE_GetNextItem(itemH, &itemH));	//	next item
	}
	
	SetPort(savePort);
	CView::ForceNextPrepare();
}

static	Boolean
RenderSettingsNameUnique(char *testName, void *data)
{
	Err						err = Err_NONE;
	FEE_RenderSettingsList	renderSettingsList = GetPrefRSList();
	FEE_RenderSettingsRec	*settings;
	FEE_RenderSettingsIndex	index;
	long					result = 0;
	Boolean					unique = TRUE;
	
	FEE_LIST_FOR_EACH(renderSettingsList, index, settings, err) {

		result = IUMagString(
			testName, 
			settings->names.settings, 
			strlen(testName), 
			strlen(settings->names.settings)
		);
		
		if (result == 0) {
			unique	= FALSE;
			err		= BEE_Err_BREAK_FROM_LOOP;
		}
	} FEE_LIST_END_EACH(renderSettingsList, err);
	
	if (err != BEE_Err_BREAK_FROM_LOOP)
		FCE(err);
	
	return(unique);
}

static	Boolean
OutputModuleNameUnique(char *testName, void *data)
{
	Err						err = Err_NONE;
	FEE_OutputModuleList	outputModuleList = GetPrefOMList();
	FEE_OutputModuleSpec	*module;
	FEE_OutputModuleIndex	index;
	long					result = 0;
	Boolean					unique = TRUE;
	
	FEE_LIST_FOR_EACH(outputModuleList, index, module, err) {

		result = IUMagString(
			testName, 
			module->names.module, 
			strlen(testName), 
			strlen(module->names.module)
		);
		
		if (result == 0) {
			unique	= FALSE;
			err		= BEE_Err_BREAK_FROM_LOOP;
		}
	} FEE_LIST_END_EACH(outputModuleList, err);
	
	if (err != BEE_Err_BREAK_FROM_LOOP)
		FCE(err);
	
	return(unique);
}

static Boolean		
LayerNameUnique(char *testName, BEE_ItemH comp)
{
	LIST			theList;			// list of LayerH's
	LIST_Index		item, totalItems;
	BEE_LayerH		theLayer;
	BEE_LayerInfo	layerInfo;
	
	theList = BEE_Comp_LAYERS_LIST(comp);
	
	FCE(LIST_GetNumItems(theList, &totalItems));
	
	for (item = totalItems - 1; item >= 0; item--) {
		FCE(LIST_Get4Item(theList, item, (long *)&theLayer));
		FCE(BEE_GetLayerInfo(theLayer, &layerInfo));

		if (IUCompCString(layerInfo.source_name, testName) == 0)
			return(FALSE);
	}
	
	return(TRUE);
}

typedef	struct {
	char		*testName;
	void		*data;
	Boolean		unique;
} FEE_ItemUniqueNameData;

static	Err 	ItemUniqueNameCB(
	FEE_ItemUniqueNameData	*uniqueData, 
	BEE_ItemH				itemH, 
	BEE_ItemH				in_folder, 
	LIST_Index				index
) {
	Err						err			= Err_NONE;
	BEE_ItemInfo			itemInfo;

	err = BEE_GetItemInfo(itemH, &itemInfo);
	
	if (!err) {
		if (IUCompCString(itemInfo.name, uniqueData->testName) == 0) {
			uniqueData->unique = FALSE;
			err = BEE_Err_BREAK_FROM_LOOP;
		}
	}

	return err;
}


static Boolean		
ItemNameUnique(char *testName, void *data)
{
	FEE_ItemUniqueNameData		uniqueData;
	Err							err = Err_NONE;

	uniqueData.testName	= testName;
	uniqueData.data		= data;
	uniqueData.unique	= TRUE;

	err = RCE(BEE_ForEachItem(
		NULL, (BEE_ItemFunc)ItemUniqueNameCB,	
		&uniqueData, BEE_ForEach_INCLUDE_FOLDERS)
	);

	if (err != BEE_Err_BREAK_FROM_LOOP)
		FCE(err);
	
	return uniqueData.unique;
}

void		
MakeUniqueName(	EGG_UniqueNameType type, 		//		>>	what type (comp, solid etc)
				void *data, 					//		>>	any necessary data
				char *name)						//		<>	default name in, unique name out
{
	Boolean		done;
	long		number;
	char		unique[256];
	
	number = 1;
	
	do {
		sprintf(unique, "%s %ld", name, number++);
		
		switch (type) {
		
			case kUniqueItemName:
				done = ItemNameUnique(unique, data);
				break;
			
			case kUniqueLayerName:
				done = LayerNameUnique(unique, *(BEE_ItemH *)&data);
				break;
			
			case kUniqueRenderSettingsName: {
				done = RenderSettingsNameUnique(unique, data);
				break;
			}
			
			case kUniqueOutputModuleName: {
				done = OutputModuleNameUnique(unique, data);
				break;
			}
		}
	} while (!done);
	
	ICE(U_TruncateString(BEE_MAX_ITEM_NAME_LEN, unique));
	strcpy(name, unique);
}

void		NullBeeCommand(void)
{
	DoCommandL(BEE_Cmd_SET_LAYER_END_LOOPING, NULL, 0);	
}



#if 0
enum	{		// KFC menu items
	kValueConstant = 1, kValueSeparator, kValueLinear, kValueLinearTangentWeld, 
	kValueSmooth, kValueCatmullRom, kValueSpline, kValueHold
};
typedef	short	FEE_kfc_MenuItems;

TDB_Interp
ConvertMenuIndexToTDBVary(FEE_kfc_MenuItems index)
{
	TDB_Interp	interp;
	
	if (index <= kValueSeparator || index > kValueHold)
		interp = TDB_Interp_NONE;
	else
		interp = index - 2;
	
	return(interp);
}


FEE_kfc_MenuItems
ConvertTDBVaryToMenuIndex(TDB_Interp interp)
{
	FEE_kfc_MenuItems	index;
	
	if (interp > TDB_Interp_NONE && interp <= TDB_Interp_HOLD)
		index = interp + 2;
	else
		index = kValueConstant;
	
	return(index);
}
#endif


char	*GetDepthString(PIN_AlphaType alphaType, short depth, char *buf)
{
	MatteInfo		matteInfo;

	switch (alphaType) {
		default:
		case PIN_Alpha_NONE:		matteInfo = kNoMatteInfo;	break;
		case PIN_Alpha_IGNORE:		matteInfo = kIgnored;		break;
		case PIN_Alpha_PREMUL:		matteInfo = kMatted;		break;
		case PIN_Alpha_STRAIGHT:	matteInfo = kUnmatted;		break;
	}
	
	ConvertDepthToString(depth, matteInfo, buf);
	
	return(buf);
}

void
ConvertDepthToString(short depth, MatteInfo matte_info, char *str)
{
	short	str_i = 0;			// 0 is empty string
	
	switch (depth) {
		case 32+1:
		case 1:		str_i = 38;	break;
		case 2:		str_i = 39;	break;
		case 4:		str_i = 40;	break;
		case 8:		str_i = 41;	break;
		case 15:
		case 16:	str_i = 42;	break;
		case 24:	str_i = 43;	break;
		case 32:	str_i = 44;	break;
		case 32+2:	str_i = 81; break;
		case 32+4:	str_i = 82; break;
		case 32+8:	str_i = 83; break;
	}
	
	strcpy(str, STR(str_i));
	
	if (depth == 32 && matte_info) {
		strcat(str, STR((matte_info == kMatted) ? 45 :
						((matte_info == kIgnored) ? 123 : 46)));
	}
}


/** GetItemInfoStrings

	Fills in inf if non-NULL.  itemH required if inf NULL.
	
		typedef struct {
			BEE_ItemType		type;
			char				name[BEE_MAX_ITEM_NAME_LEN+1];
			char				type_name[BEE_MAX_TYPE_NAME_LEN+1];
			short				width, height;						// in pixels
			TDB_TimeRecord		duration;
			short				usage_count;
			BEE_ItemFlags		flags;
			long				file_size;							// 0 for non-file
			M_Fixed				framerate;							// 0 for none
			SND_SampleInfo		sound_depth;
			short				color_depth;
			PIN_AlphaLabel		alpha_label;
			PIN_InterlaceLabel	interlace_label;
			BEE_FieldRender		fields;								// simpler version of interlace
			char				comment[PIN_MAX_MESSAGE_LEN+1];		// e.g. "compressed w/Digital Film"
			M_Ratio				hsf;								// horizontal stretch factor
			BEE_Dsf				dsf;								// for comp only
			short				dsf_width, dsf_height;				// for comp only
		} BEE_ItemInfo;

	Pass along the pinH0 if you want to check for Motion-detect deinterlace and
	3:2 Pulldown removal.

**/
void
GetItemInfoStrings(BEE_ItemH itemH, PIN_SeqSpecH pinH0, BEE_ItemInfo *inf, ItemInfoStrs *strs)
{
	BEE_ItemInfo		local_info;
	char				buf[256];
	char				extra[32];
	char				long_dimensions[128];
	
	if (inf == NULL) {
		U_ASSERT(itemH.item);
		inf = &local_info;
	}
	
	if (itemH.item)
		FCE(BEE_GetItemInfo(itemH, inf));
	
	sprintf(long_dimensions, STR(3), inf->width, inf->height);

	if (inf->type == BEE_ItemType_COMP) {
		if (inf->dsf_width != inf->width || inf->dsf_height != inf->height) {
			sprintf(extra, STR(122), inf->dsf_width, inf->dsf_height);
			strcat(long_dimensions, extra);
		}
	}
	
	if (inf->hsf.num != inf->hsf.den) {
		PixelAspectRatioToString(inf->hsf, buf);
		strcat(long_dimensions, ", ");
		strcat(long_dimensions, buf);
	}
	
	FCE(U_TruncateString(31, long_dimensions));
	strcpy(strs->dimensions, long_dimensions);
	
	strs->pulldown[0] = 0;
	
	if (inf->duration.value) {
		sprintf(strs->duration, STR(4), FormatDuration(&inf->duration, inf->framerate, buf));
		sprintf(strs->framerate, STR(100), (float)M_FIX_2_FLOAT(inf->framerate));
		
		if (pinH0) {
			const char			*phase;
			
			if (PIN_SeqSpec_HAS_PD(pinH0)) {
				switch (PIN_SeqSpec_PULLDOWN(pinH0)) {
					case PIN_Phase_WSSWW:	phase = STR(535); break;
					case PIN_Phase_SSWWW:	phase = STR(536); break;
					case PIN_Phase_SWWWS:	phase = STR(537); break;
					case PIN_Phase_WWWSS:	phase = STR(538); break;
					case PIN_Phase_WWSSW:	phase = STR(539); break;
					default:				phase = "?"; break;
				}
				
				sprintf(strs->pulldown, STR(540), phase);		// "3:2 Removal (%s)"
			}
		}
	} else {
		strs->duration[0] = 0;							// no duration/framerate to report
		strs->framerate[0] = 0;
	}
	
	GetDepthString(
		inf->alpha_label.alpha, 
		inf->color_depth, 
		strs->color_depth
	);

	// watch out for float vs. double here $$$ and above
	FCE(SND_GetInfoString(&inf->sound_depth, strs->audio));

	{
		Boolean		md = (pinH0 && PIN_SeqSpec_MOTION_DEINT(pinH0));
		
		switch (inf->fields) {
			case BEE_Field_NONE:
				strs->deinterlace[0] = 0;
				break;
			
			case BEE_Field_UPPER_FIELD_FIRST:
				strcpy(strs->deinterlace, STR(md ? 541 : 154));
				break;
			
			case BEE_Field_LOWER_FIELD_FIRST:
				strcpy(strs->deinterlace, STR(md ? 542 : 155));
				break;
		}
	}
}


/*
defunct
void
GetItemInfoStringsFromCompSettings(const BEE_CompSettings *cs, BEE_Dsf dsf, ItemInfoStrs *strs)
{
	BEE_ItemInfo		info;
	
	info.type = BEE_ItemType_COMP;
	strcpy(info.name, cs->name);
	info.type_name[0] = 0;
	info.width = cs->width;
	info.height = cs->height;
	
	info.duration = cs->duration;
	info.usage_count = 0;			// some of these are wrong, but unused by GetItemInfoStrings
	info.flags = 0;					// $$$
	info.file_size = 0;
	info.framerate = cs->framerate;	
	info.color_depth = 32;		//	$$$$$$$$$$$$$$$$$$$$$	PIN outspec thingie
	//	info.sc = cs->mdb.sc;
	
	if (info.color_depth > 32)
		info.color_depth -= 32;		// get rid of gray-scale adjustment
	
	if (info.color_depth > 24) {
		info.alpha_label.alpha = G_premul_out ? PIN_Alpha_PREMUL : PIN_Alpha_STRAIGHT;
	} else {
		info.alpha_label.alpha = PIN_Alpha_NONE;
	}
	
	info.dsf = dsf;
	
	FCE(BEE_AdjustForDSF(info.width, info.height, dsf,
							&info.dsf_width, &info.dsf_height));
	
	{
		BEE_ItemH		itemH = { NULL };
		
		GetItemInfoStrings(itemH, &info, strs);
	}
}
*/


long	S_timeLinkage;

void	SetGlobalTimeLinkage(long linkage)
{
	S_timeLinkage = linkage;
}

void	SetStreamToNotVary(BEE_LayerH layerH, BEE_StrmType streamType)
{
	FCE(BEE_CmdSetStreamVary(layerH, streamType, NULL, FALSE));
}

Boolean		RealtimeUpdate(void)
{
	return check_key_down(option_key);
}

Boolean		DelayedRealtimeUpdate(void)
{
	static	long		S_recent_update = 0;
			Boolean		realtime = FALSE;
	
	if (RealtimeUpdate()) {
		if (S_recent_update) {
			if (TickCount() - S_recent_update > 6) {
				realtime = TRUE;
			}
		} else {
			S_recent_update = TickCount();
		}
	} else {
		S_recent_update = 0;
	}
	
	return realtime;
}

/** GetStreamTemporalDimension
**/
short
GetStreamTemporalDimension(TDB_StreamH streamH)
{
	short		dimension;
	
	if (TDB_Stream_IS_SPATIAL(streamH)) {
		dimension = 1;		// only one speed tangent
	} else {
		dimension = TDB_Stream_DIMENSION(streamH);
		if (dimension > BEE_MAX_STREAM_DIM)
			dimension = BEE_MAX_STREAM_DIM;
	}
	
	return dimension;
}


void	GetNudgeValue(long cmd, long modifiers, short *dx, short *dy)
{
	*dx = *dy = 0;
	
	switch (cmd) {
		case cmdNudgeUp:		*dy = -1; 	break;
		
		case cmdNudgeRotCCW:
		case cmdNudgeLeft:		*dx = -1;	break;
		
		case cmdNudgeDown:		*dy = 1;	break;
		
		case cmdNudgeRotCW:
		case cmdNudgeRight:		*dx = 1;	break;
		
		case cmdNudgeScaleSmaller:	*dx = *dy = -1;	break;
		
		case cmdNudgeScaleLarger:	*dx = *dy = 1;	break;
	}
	
	if (modifiers & shiftKey) {
		*dx *= 10;
		*dy *= 10;
	}
}

Boolean		StreamIsPointControl(BEE_LayerH theLayer, BEE_StrmType streamType)
{
	Boolean		isPoint;
	
	if (BEE_StrmType_EFFECT(streamType)) {
		isPoint = GetEffectParamType(theLayer, streamType) == PF_Param_POINT;
	} else {
		isPoint = streamType == BEE_Strm_ANCHORPOINT || streamType == BEE_Strm_POSITION;
	}
	
	return(isPoint);
}


void	SeekItemToTime(BEE_ItemH item, const TDB_TimeRecord *timeRec)
{
	Boolean		groupStarted = FALSE;
	
	TRY {
		if (S_timeLinkage) {
			FCE(BEE_StartGroup(NULL, BEE_Cmd_SET_ITEM_TIME));
			groupStarted = TRUE;
		}
			
		FCE(BEE_CmdSeekItemToTime(item, timeRec));
		
		if (S_timeLinkage) {
			FCE(BEE_CmdSynchronizeRelatedItems(item, TRUE, TRUE));
			groupStarted = FALSE;
			FCE(BEE_EndGroup());
		}
	} CATCH {
		if (groupStarted)
			ICE(BEE_EndGroup());
	} ENDTRY;
}


/** SetValue2Digits
**/
void SetValue2Digits(CNumText *t, short v)
{
	char	buf[16];
	
	sprintf(buf, "%02d", v);
	t->SetTextCString(buf);
}


#define kPINOptionsResType		'PINo'


/** LongToString
**/
static char *
LongToString(long v, char *str)
{
	short i;
	
	*((long*)str) = v;
	str[4] = 0;
	
	for (i=0; i<4; i++) {
		if (((u_char)str[i]) < 32)			// remove NULLs
			str[i] += '@';					// shift into printable range
	}

	return str;
}

/** GetPINOptionsPref

	Might be getting default from prefs file or app.
	
**/
PIN_OptionsInfoH
GetPINOptionsPref(PIN_ModuleSignature sig)
{
	short				cur_resfile;
	PIN_OptionsInfoH	optH;
	char				name[16];
	
	cur_resfile = CurResFile();
	
	UseResFile(FILE_INQ_RESREFNUM(PREF_INQ_PREFS_FILESPEC()));
	
	LongToString(sig, name);
	
	optH = (PIN_OptionsInfoH)GetNamedResource(kPINOptionsResType, CtoPstr(name));
	
	if (optH)
		DetachResource((Handle)optH);

	UseResFile(cur_resfile);
	
	return optH;
}


/** DisposePINOptionsPref
**/
void
DisposePINOptionsPref(PIN_OptionsInfoH *optionsH)
{
	U_FREE_HANDLE(*optionsH);		// sets to NULL after
}

static	short	S_fileResRefNum;

void	SetAppResFileRefNum(short resRefNum)
{
	S_fileResRefNum = resRefNum;
}

short	GetAppResFileRefNum(void)
{
	return S_fileResRefNum;
}

void	UseAppResFile(void)
{
	UseResFile(S_fileResRefNum);
}

/** SavePINOptionsPref

	Only saves to prefs file.

**/
void
SavePINOptionsPref(PIN_ModuleSignature sig, PIN_OptionsInfoH optionsH)
{
	short				cur_resfile;
	PIN_OptionsInfoH	optH;
	char				name[16];
	u_char				pname[16], dummy[256];
	ResType				type;
	short				id = 128;
	
	if (PREF_INQ_MODE() == PREF_Mode_PREF_FILE && optionsH) {
		cur_resfile = CurResFile();

		LongToString(sig, name);

		UseResFile(FILE_INQ_RESREFNUM(PREF_INQ_PREFS_FILESPEC()));

		// get old id if there was one, otherwise make a new one
		optH = (PIN_OptionsInfoH)GetNamedResource(kPINOptionsResType, CopyC2P(name, pname));
		if (optH) {
			GetResInfo((Handle)optH, &id, &type, dummy);
			ReleaseResource((Handle)optH);
		} else {
			id = Unique1ID(kPINOptionsResType);
		}
		
		FCE(RI_AddResource((Handle)optionsH, kPINOptionsResType, name, id,
					PREF_INQ_PREFS_FILESPEC(), TRUE));		// will overwrite
		
		UseResFile(cur_resfile);
	}
}


/** StorePINOptions
**/
void
StorePINOptions(PIN_OutSpecH pinoutH)
{
	PIN_OptionsInfoH	optionsH;

	FCE(PIN_GetOutputOptions(pinoutH, &optionsH));
	
	SavePINOptionsPref(PIN_OutSpec_MODULE_SIG(pinoutH), optionsH);
	
	DisposePINOptionsPref(&optionsH);
}

#ifdef ARCH_MAC
 #pragma parameter __D0 GetRegisterA5()
	static long GetRegisterA5(void)
 		ONEWORDINLINE(0x200D);				// move.l		a5,d0
#endif

/** GetMemoryString
**/
void GetMemoryString(Boolean purge_all, char *str)
{
	char		buf2[128], buf3[128];
	long		free_space, ignore;

	if (purge_all) {
		free_space = MaxMem(&ignore);					// compacts mem
	} else {
		free_space = MaxBlock();
	}

	FormatBytes(free_space, buf2);
	
#ifdef ARCH_MAC
	// old way: (doesn't take stack & globals size into consideration)
	// free_space = GetApplLimit() - (char*)ApplicationZone();

	#define	kJumpTableSize		19000			// approximate size of above-a5 space usage

	free_space = GetRegisterA5();

	free_space += kJumpTableSize - (long)ApplicationZone();

#else
	// $$$ fix this to be right for PPC
	free_space = GetApplLimit() - (char*)ApplicationZone();
#endif
	
	// note that FormatBytes considers one Megabyte to be 1024K (which is correct)
	//	but that can mislead users when they set their partition to 4000K and expect to
	//	see 4M free...
	FormatBytes(free_space, buf3);
	
	ICE(U_SubString(str, 32, STR(69), buf2, buf3));	// assuming size of str buf 32 chars
}

extern BEE_ItemH	G_check_file_on_resume;

static Err
RAEE(short stage, Err err)
{
	if (err) {
		U_SYSERR_S(U_LOC(1, stage), U_Sev_PROBLEM, err, STR(197));
	}
	
	return err;
}

void	ShowFrameFileName(BEE_ItemH itemH, const TDB_TimeRecord *tr)
{
	FILE_FlatLocSpec	floc;

	if (BEE_Item_TYPE(itemH) == BEE_ItemType_FOOTAGE) {
		
		FCE(PIN_GetFileAtTime(BEE_Footage_PIN_SEQ(itemH), tr, &floc));
		p2cstr(floc.flat_name);
		gEgg->DisplayInfoMessage((char *)floc.flat_name, NULL);
	}
}


/** CouldEditFileExternally

**/
Boolean
CouldEditFileExternally(BEE_ItemH itemH)
{
	Boolean		could_edit = FALSE;

	// $$$ should do a host of checks!

	if (itemH.item && BEE_Item_TYPE(itemH) == BEE_ItemType_FOOTAGE) {
		could_edit = TRUE;
	}
	
	return could_edit;
}

void
EditFileExternally(BEE_ItemH itemH, const TDB_TimeRecord *tr)
{
	FILE_FlatLocSpec	floc;
	FILE_SpecH			fileH = NULL;
	FILE_FileType		type, creator;
	PIN_SeqSpecH		pinH;
	FSSpec				fsspec;
	char				name[128];
	
	TRY {
		if (BEE_Item_TYPE(itemH) == BEE_ItemType_FOOTAGE) {
			pinH = BEE_Footage_PIN_SEQ(itemH);
			
			FCE(PIN_GetFileAtTime(pinH, tr, &floc));
			
			if (floc.loc.loc_spec != -1) {
				FCE(FILE_New(&floc.loc, NULL, &fileH));
				
				FCE(FILE_InqFileType(fileH, &type, &creator));
				
				if (creator == EGG_SIGNATURE) {
					ICE(U_Beep());
				} else {
					// WARNING: will only work if FILE returns an FSSpec (HierSpec) here!
					FCE(FILE_InqBestLoc(fileH, &floc));
					ICE(FILE_Dispose(fileH));
					fileH = NULL;
					
					U_ASSERT(floc.loc.loc_spec == FILE_L_HIER);
					
					fsspec.vRefNum = floc.loc.u.hier_spec.vRefNum;
					fsspec.parID = floc.loc.u.hier_spec.parentDirID;
					U_Pstrcpy(fsspec.name, floc.loc.u.hier_spec.name);
					
					gEgg->DisplayInfoMessage(STR(200), CopyP2C(fsspec.name, name));
					
					#if 1
					
					FCE(RAEE(77, OpenExternalDocument(&fsspec)));
					
					G_check_file_on_resume = itemH;

					#else
					
					// (I couldn't get this to work, and it wouldn't launch the app anyway)
					FCE(RAEE(1, AECreateDesc(typeApplSignature, &creator, sizeof(creator), &target)));
					
					FCE(RAEE(2, AECreateAppleEvent(kCoreEventClass, kAEOpenDocuments, &target,
										kAutoGenerateReturnID, kAnyTransactionID, &aevent)));
					
					FCE(RAEE(3, AECreateList(NULL, 0, FALSE, &doc_list)));
					
					FCE(RAEE(4, AEPutPtr(&doc_list, 1, typeFSS, &fsspec, sizeof(fsspec))));
					
					FCE(RAEE(5, AEPutParamDesc(&aevent, keyDirectObject, &doc_list)));
					
					FCE(RAEE(6, AESend(&aevent, &reply, kAENoReply+kAEAlwaysInteract,
										kAENormalPriority, kAEDefaultTimeout, NULL, NULL)));
					
					FCE(RAEE(7, AEDisposeDesc(&doc_list)));
					FCE(RAEE(8, AEDisposeDesc(&aevent)));
					FCE(RAEE(9, AEDisposeDesc(&target)));
					
					#endif
				}
				
			}
		}
	} CATCH {
		if (fileH)
			ICE(FILE_Dispose(fileH));

	} ENDTRY;

}

void	RefreshCompWindows(
	BEE_ItemH	compItemH, 
	Boolean		compToo, 
	short		flags
) {
	Err					err			= Err_NONE;
	LIST				layerList;
	LIST_Index			layerIndex;
	BEE_LayerH			*curLayer;
	CPanorama			*itemPano = GetCComp(compItemH)->i_pano.item;
	
	if (compToo && itemPano) {
		itemPano->Prepare();
		itemPano->Refresh();
	}
	
	layerList = BEE_Comp_LAYERS_LIST(compItemH);

	FEE_LIST_FOR_EACH(layerList, layerIndex, curLayer, err) {
	
		if ((kRefreshAllOpenLayers & flags) != 0) {
			itemPano = GetCLayer(*curLayer)->i_pano.item;
			
			if (itemPano) {
				itemPano->Prepare();
				itemPano->Refresh();
			}
		}
		
		if ((kRefreshAllEffects & flags) != 0) {
			itemPano = GetCEffects(*curLayer)->i_ecoPano;
			
			if (itemPano) {
				itemPano->Prepare();
				itemPano->Refresh();
			}
		}

	} FEE_LIST_END_EACH(layerList, err);
	
	FCE(err);
}

void		RefreshEffectsWindowsOfComp(BEE_ItemH compItemH, Boolean compToo)
{
	RefreshCompWindows(compItemH, compToo, kRefreshAllEffects);
}

void		RefreshLayerWindowsOfComp(BEE_ItemH compItemH, Boolean compToo)
{
	RefreshCompWindows(compItemH, compToo, kRefreshAllOpenLayers);
}


void	StepBack(BEE_ItemH compItemH, TDB_TimeRecord *timeRec)
{
	TDB_TimeRecord		fudge;
	
	fudge.value = 1;
	fudge.scale = 300;
	
	// in case we're just past the frame
	FCE(TDB_SubTime(timeRec, &fudge, 0, timeRec));
	FCE(BEE_GetItemStepTime(compItemH, BEE_Step_BACKWARD, timeRec, timeRec));
}

char		*SecsToDate(u_long secs, char *buf)
{
	#define		inter1		(**(Intl1Hndl)internationalH)
	char		buf2[128];
	Handle		internationalH = (Handle)IUGetIntl(1);
	UInt8		oldsup = inter1.suppressDay;

	//	in realtime, like 'Tues, Dec 1, 3:45 PM'
	
	inter1.suppressDay = supYear;	//	don't report year
	IUDatePString(secs, abbrevDate, (u_char *)buf, internationalH);
	inter1.suppressDay = oldsup;	//	must put it back

	internationalH = (Handle)IUGetIntl(0);
	IUTimePString(secs, false, (u_char *)buf2, internationalH);
	
	p2cstr((u_char *)buf);
	p2cstr((u_char *)buf2);
	
	strcat(buf, buf2);
	
	return buf;
}

/** FormatSeconds
**/
char	*FormatSeconds(long secs, char *buf)
{
	long mins;
	long hrs;

	mins = (secs / 60) % 60;
	hrs = (secs / 3600);
	
	secs = secs % 60;
	
	if (hrs == 0) {
		if (mins == 0) {
			sprintf(buf, STR(96), secs);
		} else if (secs == 0) {
			sprintf(buf, STR(99), mins);
			if (mins == 1)
				buf[strlen(buf)-1] = 0;			// hack: truncate 's'
		} else {
			sprintf(buf, STR(97), mins, secs);
		}
	} else {
		sprintf(buf, STR(98), hrs, mins);
	}

	return buf;
}


Boolean			GeneralAlert(short alert_id, const char *string)
{
	Str255		pstring;
	
	CopyC2P(string, pstring);
	ParamText(pstring, NULL, NULL, NULL);
	PositionDialog('ALRT', alert_id);
	InitCursor();

	return CautionAlert(alert_id, ALERT_FILTER) == ok;
}

Boolean			OkCancelAlert(const char *string)
{
	return GeneralAlert(400, string);
}


void	GetMenuCheckMarkChar(
	FEE_MenuCheckMarkType	markType, 
	FontStyleSheet			styleSheet, 
	char					*returnChar)
{
	switch (styleSheet) {
		
		default:
		case FontStyle_SYS: {
			*returnChar = STR(736 + markType - (long)FEE_MenuCheckMark_CHECK)[0];
			break;
		}
		
		case FontStyle_SMALL: {
			*returnChar = STR(739 + markType - (long)FEE_MenuCheckMark_CHECK)[0];
			break;
		}
	}
}

static Err
CountFunc(void *refcon, BEE_ItemH itemH, BEE_ItemH in_folder, LIST_Index index)
{
	long	*countp = (long *)refcon;
	
	(*countp)++;

	return Err_NONE;
}


long CountDeepFolderItems(BEE_ItemH folderH)
{
	long	count = 0;
	
	FCE(BEE_ForEachItem(folderH.folder, CountFunc, &count, BEE_ForEach_INCLUDE_FOLDERS));
	
	return count;
}


long		GetMenuWidth(MenuHandle theMenu, FontStyleSheet styleSheet);
long		GetMenuWidth(MenuHandle theMenu, FontStyleSheet styleSheet)
{
	long		maxWidth = 0;
	
	if (theMenu != NULL) {
		long				items, curItem, width;
		Str255				theString;
		NIM_FontState		saveFont;
		
		GetFontState(&saveFont);
		SetFontSizeStyleFromSheet(styleSheet);
		
		items = CountMItems(theMenu);
		for (curItem = 1; curItem <= items; ++curItem) {
			GetMenuItemText(theMenu, curItem, theString);
			width = StringWidth(theString) + 28;
			if (width > maxWidth) {
				maxWidth = width;
			}
		}

		SetFontState(&saveFont);
	}
	
	return maxWidth;
}

/* --------------------------- localizable keytables ------------------------------ */

#define OVERRIDE_KEYTABLE_RSRC_TYPE		'KyTb'

typedef HANDLE_TYPE(KeyCmd)		KeyCmdH;

typedef struct {
	FILE_SpecH		keytable_fileH;
	KeyCmdH			override_keytables[kNumTableEntries];
	Boolean			keytable_written[kNumTableEntries];
} KeyTableInfo;

static KeyTableInfo		S_kti;

static void
WriteTableToResource(const KeyCmd *key_table, KeyTableID unique_id, FILE_SpecH fileH)
{
	const KeyCmd 	*scan;
	u_long			table_size;
	Handle			tabH;

	scan = key_table;
	
	while (scan++->cmd)		// leaves scan pointing past end of table
		;

	table_size = (char*)scan - (char*)key_table;
	
	tabH = U_ALLOCN_HANDLE_CLR(0, char, table_size, "keytable");
	if (tabH) {
		
		U_MEMCPY(key_table, table_size, U_LOCK_DH(tabH));
		
		ICE(RI_AddResource(tabH, OVERRIDE_KEYTABLE_RSRC_TYPE, NULL,
							unique_id, fileH, FALSE));
		U_FREE_HANDLE(tabH);
	}
}


/** NewAddTableShortcutsToMenus

	Note: repeated code from NewTranslateKeyToCommand

**/
void
NewAddTableShortcutsToMenus(KeyTableID unique_id,
				const KeyCmd *table)
{
	long		table_index = unique_id - kFirstKeyTableID;
	
	U_ASSERT(table_index >= 0 && table_index < kNumTableEntries);

	if (S_kti.override_keytables[table_index]) {
		KeyCmd *override_kc;
		
		override_kc = U_LOCK_DH(S_kti.override_keytables[table_index]);
		
		gBartenderPlus->AddTableShortcutsToMenus(override_kc);
		
		U_UNLOCK_RH(S_kti.override_keytables[table_index]);
		
	} else {
		gBartenderPlus->AddTableShortcutsToMenus(table);
	}
}


long
NewTranslateKeyToCommand(KeyTableID unique_id,
				const KeyCmd *kc,
				const EventRecord *evt)
{
	long		cmd;
	long		table_index = unique_id - kFirstKeyTableID;
	
	U_ASSERT(table_index >= 0 && table_index < kNumTableEntries);

	if (S_kti.override_keytables[table_index]) {
		KeyCmd *override_kc;
		
		override_kc = U_LOCK_DH(S_kti.override_keytables[table_index]);
		cmd = TranslateKeyToCommand(override_kc, evt);
		U_UNLOCK_RH(S_kti.override_keytables[table_index]);
		
	} else {
		if (S_kti.keytable_fileH && !S_kti.keytable_written[table_index]) {
			Boolean		all_written = TRUE;
			short		i;
			
			SysBeep(1);
			WriteTableToResource(kc, unique_id, S_kti.keytable_fileH);
			S_kti.keytable_written[table_index] = TRUE;
			
			for (i=0; i<kNumTableEntries; i++) {
				if (S_kti.keytable_written[i] == FALSE) {
					all_written = FALSE;
					break;
				}
			}
			
			if (all_written) {
				ICE(U_REPORT_S(0, U_Sev_INFO, "All keytables written.  Woohoo!"));
				ICE(FILE_Dispose(S_kti.keytable_fileH));		// closes for us
				U_MEMCLR(sizeof(S_kti), &S_kti);
			}
		}
		
		cmd = TranslateKeyToCommand(kc, evt);
	}

	return cmd;
}


void
SetupOverrideKeytables(void)
{
	short id;
	Handle	h;
	for (id = kFirstKeyTableID; id <= kLastKeyTableID; id++) {
	
		// not an error if it's not there
		h = GetResource(OVERRIDE_KEYTABLE_RSRC_TYPE, id);
		
		if (h) {
			HNoPurge(h);
		}
		
		S_kti.override_keytables[id-kFirstKeyTableID] = (KeyCmdH)h;
	}
}


/** SetupWriteKeytablesToResources

	Call this to setup the writing of the keytables to resources.
	You should then press keys in every window that has a keytable
	associated with it, which will force its bindings into
	a resource in the chosen file.

**/
void
SetupWriteKeytablesToResources(void)
{
	FILE_FlatLocSpec		floc;
	Boolean					create = TRUE;
	Boolean					okay;
	
	if (S_kti.keytable_fileH == NULL) {
		FCE(FILE_CreateLocPutInterBasic("Keytables.rsrc", "Keytables, whereĠd you want Ġem?",
				NULL, &okay, &floc));
		
		if (okay) {
			FCE(FILE_New(&floc.loc, NULL, &S_kti.keytable_fileH));
	
			FCE(FILE_Create(FILE_C_ALLOW_OVERWRITE, 'RSRC', 'Doug', S_kti.keytable_fileH));
			
			FCE(FILE_OpenResFile(FILE_M_READWRITE, &create, S_kti.keytable_fileH));
		}
	}
}

		
void		InstallExtraXFerModes(void)
{
	u_char		buf[256];
	short		menu_id, item_no;
	MenuHandle	menu_handle;
	
	gBartenderPlus->FindMenuItem(
		cmdXferLuminescentPremul, &menu_id, 
		&menu_handle, &item_no);
	
	if (item_no == NOTHING) {
		menu_handle = gBartender->FindMacMenu(168);
		item_no		= CountMItems(menu_handle);
		
		//	note: STR(726) is 'Luminescent Premul'
		//	note: STR(778) is 'Alpha Add'

		//	add separator and 2 optional modes to menubar
		gBartenderPlus->InsertFlexMenuCmd(
			cmdXferHiddenSeparator, 
			C_MENU_SEPARATOR, 168, item_no);

		gBartenderPlus->InsertFlexMenuCmd(
			cmdXferLuminescentPremul, 
			STR(726), 168, item_no + 1);
		
		gBartenderPlus->InsertFlexMenuCmd(
			cmdXferAplhaAdd, 
			STR(778), 168, item_no + 2);
		
		//	add separator and 2 optional modes to TL menu
		menu_handle = FindMenu(217, TRUE);
		AppendMenu(menu_handle, P_MENU_SEPARATOR);
		AppendMenu(menu_handle, CopyC2P(STR(726), buf));
		AppendMenu(menu_handle, CopyC2P(STR(778), buf));
	}
}

short		DoPopupMenu(MenuHandle theMenu, short menuItem, Point thePoint)
{
	char	mark;
	short	newItem = 0;	//	0 == no item selected, or same as last time
	long	result;
	
	GetMenuCheckMarkChar(FEE_MenuCheckMark_CHECK, FontStyle_SMALL, &mark);

	RadioButtonItem(theMenu, 0, 0, menuItem, mark);

	InsertMenu(theMenu, hierMenu);
	result = PopUpSmallMenuSelect(theMenu, thePoint.v, thePoint.h, menuItem);
	DeleteMenu((**theMenu).menuID);
	
	if (
		HiShort(result) != 0 && LoShort(result) != menuItem
	) {
		newItem = LoShort(result);
	}
	
	return newItem;
}

