//====START_GENERATED_PROLOG======================================
//
//
//   COMPONENT_NAME: odutils
//
//   CLASSES: none
//
//   ORIGINS: 82,27
//
//
//   (C) COPYRIGHT International Business Machines Corp. 1995,1996
//   All Rights Reserved
//   Licensed Materials - Property of IBM
//   US Government Users Restricted Rights - Use, duplication or
//   disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
//   	
//   IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
//   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
//   PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, INDIRECT OR
//   CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
//   USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
//   OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
//   OR PERFORMANCE OF THIS SOFTWARE.
//
//====END_GENERATED_PROLOG========================================
//
// @(#) 1.4 com/src/utils/DlogUtil.cpp, odutils, od96nt, odnt9712d 7/15/96 17:57:39 [ 3/21/97 16:49:45 ]
/*
	File:		DlogUtil.cpp

	Contains:	implementation of dialog utility functions

	Owned by:	Tantek ‚elik

	Copyright:	© 1993 - 1995 by Apple Computer, Inc., all rights reserved.

*/

#ifndef _PLATFORM_WIN32_

#ifndef _EXCEPT_
#include <Except.h>
#endif

#ifndef SOM_Module_OpenDoc_Errors_defined
#include <ErrorDef.xh>
#endif

#ifndef _DLOGUTIL_
#include <DlogUtil.h>
#endif

#ifndef _PLFMDEF_
#include <PlfmDef.h>
#endif

#ifndef _ODUTILS_
#include <ODUtils.h>
#endif

#ifndef SOM_ODMenuBar_xh
#include <MenuBar.xh>
#endif

#ifndef SOM_ODDispatcher_xh
#include <Disptch.xh>
#endif

#ifndef SOM_ODSession_xh
#include <ODSessn.xh>
#endif

#ifndef SOM_ODWindowState_xh
#include <WinStat.xh>
#endif

#ifndef SOM_ODClipboard_xh
#include <Clipbd.xh>
#endif

#ifndef _ODMEMORY_
#include <ODMemory.h>
#endif

#ifndef __GESTALTEQU__
#include <GestaltEqu.h>
#endif

#ifndef __TOOLUTILS__
#include <ToolUtils.h>
#endif

#ifndef _PASCLSTR_
#include <PasclStr.h>
#endif

#ifndef __DIALOGS__
#include <Dialogs.h>
#endif

#ifndef __TEXTEDIT__
#include <TextEdit.h>
#endif

#ifndef __PALETTES__
#include <Palettes.h>
#endif

#ifndef _MEMMGR_
#include <MemMgr.h>
#endif

#ifndef _ODDEBUG_
#include <ODDebug.h>
#endif

#ifndef _USERSRCM_
#include <UseRsrcM.h>
#endif

#ifndef SOM_ODNameSpaceManager_xh
#include <NmSpcMg.xh>
#endif

#ifndef SOM_Module_OpenDoc_Commands_defined
#include <CmdDefs.xh>
#endif

#ifndef SOM_ODStorageSystem_xh
#include <ODStor.xh>
#endif

#ifndef SOM_ODPlatformTypeList_xh
#include <PfTypLs.xh>
#endif

#ifndef _TEMPOBJ_
#include "TempObj.h"
#endif

// Types
struct _MenuItemInfo
{
	MenuHandle	menu;
	short		item;
	Str255		text;
	short		cmdChar;
	short		iconID;
	short		markChar;
	Style		textStyle;
};
typedef struct _MenuItemInfo MenuItemInfo;

struct _DialogBoxInfo
{
	MenuItemInfo	redo;
	// Can put other things in here later on
	// like supporting movable modals, etc.
};
typedef struct _DialogBoxInfo DialogBoxInfo;

// Key codes
// const char kEscapeKey		= 0x1B;
// const char kUpArrowKey		= 0x1E;
// const char kDownArrowKey	= 0x1F;
const char kPageUpKey		= 0x0B;
const char kPageDownKey		= 0x0C;
const char kHomeKey			= 0x01;
const char kEndKey			= 0x04;

// Control settings
const short kControlInactive = 255;
const short kControlActive = 0;

// Dialog item assumptions
const short kOKButton = 1;
const short kCancelButton = 2;

// Memory requirements
const size_t kODMinFreeSpaceToShowDialog = 4 * 1024;
	
static Environment		*sEv;
static ODSession		*sSession;
static ModalFilterUPP	sFilter = kODNULL;
static ModalFilterUPP	sButtonKeyFilter = kODNULL;
static ModalFilterUPP	sArrowKeyFilter = kODNULL;
static UserItemUPP		sOutlineDrawProc = kODNULL;
static short			sCmdKeyStrResID = 0;	
static DialogScriptData*
						sDlogScriptData = kODNULL;	
	// Res ID of STR# that contains cmd-keys for buttons

static DialogBoxInfo* sDialogInfo = kODNULL;

// Private functions
static void SaveAndDeleteMenuItem( MenuHandle menu, short item, MenuItemInfo *savedItem );
static void RestoreMenuItem( MenuItemInfo *savedItem );


//==============================================================================
// Preflighting Dialogs and Alerts
//==============================================================================

//------------------------------------------------------------------------------
// Centering
//------------------------------------------------------------------------------

static ODBoolean
GetFrontWindowBounds( Rect &bounds )
{
	WindowPtr wp;
	{
		TempODWindow win = sSession->GetWindowState(sEv)->AcquireActiveWindow(sEv);
		wp = win ? win->GetPlatformWindow(sEv) : FrontWindow();
	}
	
	if( wp ) {
		bounds = wp->portRect;
		Rect *mapBounds;
		if( wp->portBits.rowBytes & 0xC000 )			// Color port
			mapBounds= &(**((CGrafPtr)wp)->portPixMap).bounds;
		else
			mapBounds= &wp->portBits.bounds;
		OffsetRect(&bounds,-mapBounds->left,-mapBounds->top);
		return kODTrue;
	} else {
		bounds = ODQDGlobals.screenBits.bounds;		// No win, use main screen
		bounds.top += GetMBarHeight();
		return kODFalse;
	}
}


static void
GetWindowsMainScreen( const Rect &windowBounds, Rect &screenBounds )
{
	long area=0;
	
	GDHandle gdh;
	Rect devBounds,sectBounds;
	long maxArea=0;
	
	for( gdh = GetDeviceList(); gdh; gdh = GetNextDevice(gdh) ) {
		devBounds = (**gdh).gdRect;
		if( gdh == GetMainDevice() )
			devBounds.top += GetMBarHeight();
		if( SectRect(&devBounds,&windowBounds,&sectBounds) ) {
			area = (sectBounds.right-sectBounds.left) * (long)(sectBounds.bottom-sectBounds.top);
			if( area > maxArea ) {
				maxArea = area;					// Best screen so far
				screenBounds = devBounds;
				if( gdh==GetMainDevice() )
					screenBounds.right -= 30;	// Leave room for Finder disk icons
			}
		}
	}
	if( area==0 ) {
		screenBounds = ODQDGlobals.screenBits.bounds;		// Default: use main screen
		screenBounds.top += GetMBarHeight();
	}
}


static void
CenterDialog( short &centerFlag, Rect &bounds )
{
	/* Do centering manually. Why? Even though the Dialog Manager handles
	   automatic centering, it calls FrontWindow to find the parent window.
	   This will return an incorrect window (a floater) if there are floating
	   windows. We have to reproduce the centering effect but using the
	   actual frontmost non-floating window. */
	const short noAutoCenter					= 0x0000;
	const short centerParentWindowScreen		= 0x680A;		// From Types.r
	const short alertPositionParentWindowScreen = 0x700A;
	const short centerParentWindow				= 0xA80A;
	const short alertPositionParentWindow		= 0xB00A;
	// We don't do anything for other flags.
	
	if( centerFlag == noAutoCenter )
		return;
	
	Rect centerBounds;
	if( GetFrontWindowBounds(centerBounds)
			&& (centerFlag==centerParentWindowScreen ||
				centerFlag==alertPositionParentWindowScreen) ) {
		Rect screenBounds;
		GetWindowsMainScreen(centerBounds,screenBounds);
		centerBounds = screenBounds;
	}
	
	Point pos;
	pos.h = ((centerBounds.right-centerBounds.left)-(bounds.right-bounds.left)) >>1;
	pos.v = ((centerBounds.bottom-centerBounds.top)-(bounds.bottom-bounds.top));
	if( centerFlag==centerParentWindowScreen || centerFlag==centerParentWindow )
		pos.v /= 2;
	else if( centerFlag==alertPositionParentWindowScreen || centerFlag==alertPositionParentWindow )
		pos.v /= 3;
	else
		return;							// Ignore other flags
		
	pos.h += centerBounds.left;
	pos.v += centerBounds.top;
	OffsetRect(&bounds, pos.h-bounds.left, pos.v-bounds.top);
	centerFlag = noAutoCenter;			// It's already centered now
}


//------------------------------------------------------------------------------
// PreflightDialog
//------------------------------------------------------------------------------

static Handle
PreflightDialog( ODBoolean isDLOG, short id )
{
	Handle rsrc;
	ODBoolean ok;
	
	if( isDLOG ) {
		// Load DLOG and DITL:
		rsrc = Get1Resource('DLOG', id);
		DialogTemplate** dlogTemplate = (DialogTemplate**)rsrc;
		ok = dlogTemplate && Get1Resource('DITL', (*dlogTemplate)->itemsID);
		if( ok ) {
			// Get the centering flag & adjust dialog centering:
			HLock(rsrc);
			size_t centerFlag = (size_t) &(**dlogTemplate).title;
			centerFlag += *(unsigned char*)centerFlag +1;	// skip title
			centerFlag += (centerFlag & 1);					// word align
			CenterDialog(*(short*)centerFlag, (**dlogTemplate).boundsRect);
			HUnlock(rsrc);
		}
	} else {
		// Load ALRT and DITL:
		rsrc = Get1Resource('ALRT', id);
		AlertTemplate** alrtTemplate = (AlertTemplate**)rsrc;
		ok = alrtTemplate && Get1Resource('DITL', (*alrtTemplate)->itemsID);
		if( ok ) {
			// Get the centering flag & adjust alert centering:
			HLock(rsrc);
			short *centerFlag = (short*)( (size_t)*alrtTemplate + sizeof(AlertTemplate) );
			CenterDialog(*centerFlag,(**alrtTemplate).boundsRect);
			HUnlock(rsrc);
		}
	}
	
	if( !ok ) {
#if ODDebug
		OSErr err = ResError();
		if( err != noErr && err != resNotFound )
			WARN("Could not load dialog/alert %hd: error %hd",id,err);
#endif
		SysBeep(2);							// At least give some indication
		rsrc = kODNULL;
	} else {
		// Bail if space is really, really low. Don't even beep, this might cause
		// a large 'snd' to be loaded.
		if( !ODHaveFreeSpace(kODMinFreeSpaceToShowDialog,kODMinFreeSpaceToShowDialog,
							 kODTrue) ) {
			WARN("Whoa, not enough memory to show a dialog/alert!");
			ReleaseResource(rsrc);
			rsrc = kODNULL;
		}
	}
	
	return rsrc;
}


//==============================================================================
// Displaying Dialogs and Alerts
//==============================================================================

DialogPtr
ODGetNewDialog( Environment *ev, short resID, ODSession *session, ODBoolean defaultButtons )
{
	ASSERT(session!=kODNULL, kODErrIllegalNullInput);

	sEv = ev ?ev :somGetGlobalEnvironment();
	sSession = session;

	Handle dlogRsrc = PreflightDialog(kODTrue,resID);
	if( ! dlogRsrc )
		return kODNULL;

	DialogPtr dlog = ::GetNewDialog(resID,kODNULL,(WindowPtr)-1L);
	if( dlog ) {
		if( defaultButtons ) {
			::SetDialogDefaultItem(dlog,kStdOkItemIndex);
			::SetDialogCancelItem(dlog,kStdCancelItemIndex);
		}
		::SetDialogTracksCursor(dlog,kODTrue);
		
		sCmdKeyStrResID = 0;	// Don't use command-key lookup unless told to
		
		InitCursor();			// Show arrow
	}
	
	ReleaseResource(dlogRsrc);
	
	return dlog;
}

ODSShort
ShowAlert(Environment *ev, ODSShort alertID, ModalFilterUPP modalFilter, ODSession *session)
{
	ASSERT(session != kODNULL, kODErrIllegalNullInput);

	CUsingLibraryResources r;

	SetCursor(&(ODQDGlobals.arrow));

	sEv = ev ?ev :somGetGlobalEnvironment();
	sSession = session;

	Handle alrtRsrc = PreflightDialog(kODFalse,alertID);
	if( ! alrtRsrc )
		return cancel;
	
	// Success at last!

	short alertReturn = Alert(alertID, modalFilter);
	
	ReleaseResource(alrtRsrc);

	return alertReturn;
}

pascal Boolean
ODDialogFilterProc( DialogPtr dp, EventRecord *event, short *item )
{
	ODBoolean dispatch = kODFalse;
	
	if( event->what==updateEvt || event->what==activateEvt ) {
		if( (WindowPtr)event->message != dp )	// Dispatch updates/activates of other windows
			dispatch = kODTrue;
			
	} else if( event->what==nullEvent || event->what==osEvt )	// Ditto null/suspend/resume
		dispatch = kODTrue;
	
	else if( event->what==mouseDown ) {
		WindowPtr wp;
		if( ::FindWindow(event->where,&wp)==inDrag && wp==dp ) {	// Dragging me!
			Rect bounds = ODQDGlobals.screenBits.bounds;
			DragWindow(dp,event->where,&bounds);
			event->what = nullEvent;
		}
		
	} else if( event->what==keyDown ) {
		// Cmd-A means select all if there is editable text:
		short curTextItem = ((DialogPeek)dp)->editField + 1;
		char key = event->message & charCodeMask;
		if( curTextItem>0 && (key=='a' || key=='A') && (event->modifiers & cmdKey) )
			SelectDialogItemText(dp, curTextItem, 0, 32767);
#ifdef TO_BE_DELETED
		else
		{
			long scriptNum = GetScriptManagerVariable( smKeyScript );
			long curScript = FontToScript(dp->txFont);
			if ( curScript != scriptNum )
			{
//				DebugStr( "\pChanging system font;g" );
				short myFont = GetScriptVariable( scriptNum, smScriptAppFond );
				WindowPtr savePort;
				GetPort(&savePort);
				SetPort(dp);
				TextFont( myFont );
				SetPort(savePort);
			}
		}
#endif /* TO_BE_DELETED */
	}
	
	if( dispatch ) {
		TRY{
			ODDispatcher* dispatcher = sSession->GetDispatcher(sEv);
			if (dispatcher)
				dispatcher->Dispatch(sEv, (ODEventData*)event);
		}CATCH_ALL{
		}ENDTRY
	}
	
	// Forward other events to the standard filter-proc:	
	ModalFilterUPP proc;
	OSErr err= GetStdFilterProc(&proc);
	if( err==noErr )
		return CallModalFilterProc(proc, dp,event,item);	// Call through to std filter-proc
	else {
		WARN("Couldn't get std filter proc!");
		return false;
	}
}


ModalFilterUPP
GetODDialogFilter( )
{
	if( !sFilter )
		sFilter = NewModalFilterProc(&ODDialogFilterProc);
	return sFilter;
}


//==============================================================================
// Filter Procs & Such
//==============================================================================

static void
ActivateControl(DialogPtr dialog, short item, Boolean isActivate)
{
	short			itemType;
	Rect			itemRect;
	ControlHandle	cntrlHandle;

	GetDialogItem(dialog, item, &itemType, (Handle*) &cntrlHandle, &itemRect);
	ASSERT_CONTROL_ITEM(itemType);
	
	CUsingLibraryResources r;

	if ( isActivate )
	{
		HiliteControl(cntrlHandle, GetControlReference(cntrlHandle));
	}
	else
	{
		SetControlReference(cntrlHandle, (**cntrlHandle).contrlHilite);
		HiliteControl(cntrlHandle, kControlInactive);
	}
}

static void
ActivateAllControls(DialogPtr dialog, Boolean isActivate)
{
	short			itemType;
	Rect			itemRect;
	ControlHandle	cntrlHandle;
	short			item;

	for (item = 1; item <= CountDITL(dialog); ++item)
	{
		GetDialogItem(dialog, item, &itemType, (Handle*) &cntrlHandle, &itemRect);
		itemType &= ~kItemDisableBit;
		if ( itemType>=ctrlItem && itemType<=ctrlItem+resCtrl )
			ActivateControl(dialog, item, isActivate);
	}
}


pascal Boolean ODArrowKeyFilterProc( DialogPtr dlg, ODEventData* event,
		short* itemHit )
{
	Boolean result;
	if ( event->what == keyDown || event->what == autoKey )
	{
		result = kODTrue;
		char key = event->message & charCodeMask;
		ODBoolean cmdKeyDown = (event->modifiers & cmdKey) != 0;
		ODBoolean optionKeyDown = (event->modifiers & optionKey) != 0;
		switch ( key )
		{
			case kUpArrowKey:
				*itemHit = cmdKeyDown ? optionKeyDown ? kODHomeArrowItem :
						kODPageUpArrowItem : kODUpArrowItem;
				break;
			case kDownArrowKey:
				*itemHit = cmdKeyDown ? optionKeyDown ? kODEndArrowItem :
						kODPageDownArrowItem : kODDownArrowItem;
				break;

			case kPageUpKey:
				*itemHit = kODPageUpArrowItem;
				break;
			case kPageDownKey:
				*itemHit = kODPageDownArrowItem;
				break;
			case kHomeKey:
				*itemHit = kODHomeArrowItem;
				break;
			case kEndKey:
				*itemHit = kODEndArrowItem;
				break;

			default:
				result = kODFalse;
		}
	}
	else
		result = kODFalse;
	return result || ODButtonKeyFilterProc( dlg, event, itemHit );
}


ModalFilterUPP
GetODArrowKeyFilterProc( )
{
	if( !sArrowKeyFilter )
		sArrowKeyFilter = NewModalFilterProc(&ODArrowKeyFilterProc);
	return sArrowKeyFilter;
}

// DMc: this debugging code disables use of enter to mean 'OK' when this var is set:
// #if ODDebug
// int gODButtonKeyNoEnterOkay = 0; // set to non-zero to disable enter for okay
// #endif

pascal Boolean
ODButtonKeyFilterProc(DialogPtr dialog, EventRecord *event, short *itemHit)
{	
	Rect			itemRect;
	short			itemType;
	Handle			itemHandle;
	short			myItemHit = 0;

	const short		kEscKeyCode = 0x35;	// Virtual key code for the escape key

	if ( event->what == keyDown )
	{
		char key = event->message & charCodeMask;
		switch ( key )
		{
			case kReturnKey:
			case kEnterKey:
				GetDialogItem(dialog, kOKButton, &itemType, &itemHandle, &itemRect);
				ASSERT_CONTROL_ITEM(itemType);

// DMc: this debugging code disables use of enter to mean 'OK' when a var is set:
// #if ODDebug
// 				if ( !gODButtonKeyNoEnterOkay || key != kEnterKey )
// 				{
// 					if ( (**(ControlHandle)itemHandle).contrlHilite == kControlActive )
// 						myItemHit = kOKButton;
// 				}
// #else
				if ( (**(ControlHandle)itemHandle).contrlHilite == kControlActive )
					myItemHit = kOKButton;
// #endif /*ODDebug*/
					
				break;
			
			case kEscapeKey:
				// Ensure that the escape key, not the clear key, was pressed
				if ( (event->message & keyCodeMask) == (kEscKeyCode << 8) )
				{
					GetDialogItem(dialog, kCancelButton, &itemType, &itemHandle, &itemRect);
					if ( (itemType & ~kItemDisableBit) == (ctrlItem+btnCtrl) )
						myItemHit = kCancelButton;
				}
				break;

			case '.':		// cmd-period means cancel
				if ( event->modifiers & cmdKey )
				{
					GetDialogItem(dialog, kCancelButton, &itemType, &itemHandle, &itemRect);
					if ( (itemType & ~kItemDisableBit) == (ctrlItem+btnCtrl) )
						myItemHit = kCancelButton;
				}
				break;

			default:
				if ( (sCmdKeyStrResID != 0)  && (event->modifiers & cmdKey) )
				{
					// Attempt to match the key to a key found in the STR# resource
					Handle stringsHandle = GetResource( 'STR#', sCmdKeyStrResID );
					if ( stringsHandle )
					{
						short 	i;
						short	numStrings = **(short**)stringsHandle;
						Str255	cmdKeyCode;
						for ( i = 1; i <= numStrings; ++i )
						{
						  GetIndString( cmdKeyCode, sCmdKeyStrResID, i );
						  if ( cmdKeyCode[0] && cmdKeyCode[1] == key )
						  {
							GetDialogItem(dialog, i, &itemType, &itemHandle, &itemRect);
							ASSERT_CONTROL_ITEM(itemType);
							if ( (**(ControlHandle)itemHandle).contrlHilite == kControlActive )
							{
							  myItemHit = i;	// Cool--record the item number that is hit
							  break;
							}
						  }
						}
					} 
				}
				break;
		}

		if ( myItemHit != 0 )	// need to hilite a button
		{
			FlashButtonItem( dialog, myItemHit );
			*itemHit = myItemHit;
			return kODTrue;
		}
	
	}
	else if ( event->what == activateEvt )
	{
		Boolean isActivate = ((event->modifiers & activeFlag) != 0 );
		ActivateAllControls(dialog, isActivate);
	}

	// we only get here if we'd otherwise be returning false; all successes
	// exit at "return kODTrue" above
	return ODDialogFilterProc(dialog,event,itemHit);
}


ModalFilterUPP
GetODButtonKeyFilterProc( )
{
	if( !sButtonKeyFilter )
		sButtonKeyFilter = NewModalFilterProc(&ODButtonKeyFilterProc);
	return sButtonKeyFilter;
}


void
ODUseCommandKeyStringsResource( short resID )
{
	sCmdKeyStrResID = resID;
}


//==============================================================================
// Updating the Menu Bar
//==============================================================================

void ODDialogBegin( Environment* ev, ODSession* session, 
			ODMenuBar* currentMenuBar, DialogPtr dialog )
{
	ODUnused(dialog);	// Later on, we can use this to support movable modals
	
	if ( sEv == kODNULL )
		sEv = ev;

	if ( sSession == kODNULL )
		sSession = session;

	ASSERT(currentMenuBar!=kODNULL,kODErrIllegalNullInput);
	ASSERT(sSession!=kODNULL,kODErrIllegalNullInput);
	
	// Cleanup if needed
	if ( sDialogInfo )
		ODDisposePtr(sDialogInfo);

	// Allocate a new menu info record
	sDialogInfo = (DialogBoxInfo*)ODNewPtrClear(sizeof(DialogBoxInfo),kDefaultHeapID);

	if ( sDialogInfo )
	{
		ODMenuID		editMenuID = 0;
		ODPlatformMenu	editMenu = kODNULL;
		short			redoItem;

		// Delete the Redo menu item so ModalDialog will handle the Edit menu right.
		currentMenuBar->GetMenuAndItem(sEv, kODCommandRedo, &editMenuID, &redoItem);
		editMenu = currentMenuBar->GetMenu(sEv, editMenuID );
		if ( editMenu )
			SaveAndDeleteMenuItem(editMenu, redoItem, &sDialogInfo->redo);
		else
		{
			ODDisposePtr(sDialogInfo);
			sDialogInfo = kODNULL;
		}
	}
	
	// Lastly, export the OD clipboard in case the user pastes into the dialog
	ODPlatformTypeList* types = session->GetStorageSystem(ev)->CreatePlatformTypeList(ev, kODNULL);
	types->AddLast(sEv, 'TEXT');
	types->AddLast(sEv, 'PICT');
	sSession->GetClipboard(sEv)->SetPlatformClipboard(sEv, types);
	ODDeleteObject(types);
}


void ODDialogEnd()
{
	// Restore the Redo menu item deleted by DialogBegin
	if ( sDialogInfo )
	{
		RestoreMenuItem(&sDialogInfo->redo);
		ODDisposePtr(sDialogInfo);
		sDialogInfo = kODNULL;		// Don't allow to be called again
	}
}


void SaveAndDeleteMenuItem( MenuHandle menu, short item, MenuItemInfo *savedItem )
{
	savedItem->menu = menu;
	savedItem->item = item;
	GetMenuItemText(menu, item, savedItem->text);
	GetItemCmd(menu, item, &savedItem->cmdChar);
	GetItemIcon(menu, item, &savedItem->iconID);
	GetItemMark(menu, item, &savedItem->markChar);
	GetItemStyle(menu, item, &savedItem->textStyle);
	DeleteMenuItem(menu, item);
	InvalMenuBar();
}


void RestoreMenuItem( MenuItemInfo *savedItem )
{
	InsertMenuItem(savedItem->menu, savedItem->text, savedItem->item -1);
		// Still set item text in case of non-meta meta-characters.
	SetMenuItemText(savedItem->menu, savedItem->item, savedItem->text);
	SetItemCmd(savedItem->menu, savedItem->item, savedItem->cmdChar);
	SetItemIcon(savedItem->menu, savedItem->item, savedItem->iconID);
	SetItemMark(savedItem->menu, savedItem->item, savedItem->markChar);
	SetItemStyle(savedItem->menu, savedItem->item, savedItem->textStyle);
	InvalMenuBar();
}


pascal void
ODOutlineDefaultButtonDrawProc(DialogPtr theDialog, short theItem)
{
	Rect 		itemRect;
	Handle		itemHandle;
	short		itemKind;

	WindowPtr	buttonWindow;
	WindowPtr	savePort;
	PenState	savePen;
	short		buttonOval;
	Boolean		isColorPort;

	const short kColorPort = 0xC000;

	GetDialogItem(theDialog, kOKButton, &itemKind, &itemHandle, &itemRect);
	ASSERT_CONTROL_ITEM(itemKind);

	GetPort(&savePort);
	buttonWindow = (**(ControlHandle)itemHandle).contrlOwner;
	SetPort(buttonWindow);
	GetPenState(&savePen);
	PenNormal();

	InsetRect(&itemRect, -4, -4);
	FrameRoundRect(&itemRect, 16, 16);
	buttonOval = ((itemRect.bottom - itemRect.top)/2)+2;

	isColorPort = ((((CGrafPtr)buttonWindow)->portVersion & kColorPort) == kColorPort);
	
	if ( (**(ControlHandle)itemHandle).contrlHilite == kControlInactive )
	{
		// Button is inactive, so outline with gray

		RGBColor	fgSaveColor;
		RGBColor	fgNewColor;
		RGBColor	bgColor;
		Boolean		newGray = false;

		if ( isColorPort )
		{
			GetBackColor(&bgColor);
			GetForeColor(&fgSaveColor);
			fgNewColor = fgSaveColor;
			
			Rect globalRect = itemRect;
			LocalToGlobal((Point *)&(globalRect.top));
			LocalToGlobal((Point *)&(globalRect.bottom));
			GDHandle targetDevice = GetMaxDevice(&globalRect);
			
			newGray = GetGray(targetDevice, &bgColor, &fgNewColor);
		}

		if ( newGray )
			RGBForeColor(&fgNewColor);
		else
#ifdef THINK_CPLUS
			PenPat(ODQDGlobals.gray);
#else
			PenPat(&ODQDGlobals.gray);
#endif

		PenSize(3, 3);
		FrameRoundRect(&itemRect, buttonOval, buttonOval);
		
		if ( isColorPort )
			RGBForeColor(&fgSaveColor);
	}
	else
	{
		// Button is active, so outline with black
#ifdef THINK_CPLUS
		PenPat(ODQDGlobals.black);
#else
		PenPat(&ODQDGlobals.black);
#endif
		PenSize(3, 3);
		FrameRoundRect(&itemRect, buttonOval, buttonOval);
	}

	SetPenState(&savePen);
	SetPort(savePort);
}


UserItemUPP
GetODOutlineDefaultButtonDrawProc( )
{
	if( !sOutlineDrawProc )
		sOutlineDrawProc = NewUserItemProc(&ODOutlineDefaultButtonDrawProc);
	return sOutlineDrawProc;
}


void
ODUseDialogScriptData( DialogScriptData* dsd, DialogPtr dialog )
{
	sDlogScriptData = dsd;
	dsd->SetDialog( dialog );
}

pascal ODBoolean CheckKeyScriptChangeFilterProc( DialogPtr dialog,
		EventRecord *event, short *itemHit)
{
	TEHandle teh = ((DialogPeek)dialog)->textH;
	DialogScriptData* dsd = sDlogScriptData;
	WASSERT( dsd );

	if ( dsd->Script() == ksmUninited )
		dsd->SetScriptAndLockKeyboard();

	if ( event->what == keyDown )
	{
		if ( !dsd->ScriptChanged() )
		{
			ODScriptCode newScript = GetScriptManagerVariable(smKeyScript);
			ODScriptCode fontScript = FontToScript((*teh)->txFont);
			WASSERT( fontScript == dsd->Script() );
			if ( fontScript != newScript )
			{
				short newFont = GetScriptVariable( newScript, smScriptAppFond );
				(*teh)->txFont = newFont;
//				TextFont(newFont);

				dsd->SetScriptAndLockKeyboard( FontToScript(newFont) );
			}
		}
	}

	// if this is the first time in this edit item, redraw in case
	// we've switched fonts/scripts.
	short currentItem = ((DialogPeek)dialog)->editField;
	if ( dsd->ScriptChanged() && !dsd->ItemRedrawn(currentItem) )
	{
		dsd->SetRedrawn(currentItem);
		short itemKind;
		Handle ignoreH;
		Rect ignoreR;
		GetDialogItem( dialog, currentItem+1, &itemKind, &ignoreH, &ignoreR );
		if ( itemKind == editText )
		{
			TECalText( teh );
			Rect viewRect = (*teh)->viewRect;
			TEUpdate( &viewRect, teh );
		}
	}

	// pass to the next filter proc.  This one just changes state, never
	// consuming the event.
	return kODFalse;
}

void EnableOkButton(DialogPtr dlog, ODBoolean enable)
{
	Environment*	ev = somGetGlobalEnvironment();
	short			itemType;
	Handle			itemHandle;
	Rect			itemRect;
	ODBoolean		okEnabled;

	const short kButtonFrameInset = -4;

	GetDialogItem(dlog, kOKButton, &itemType, &itemHandle, &itemRect);	
	ASSERT_CONTROL_ITEM(itemType);
	okEnabled = (**(ControlHandle)itemHandle).contrlHilite == kControlActive;

	// there's nothing to do if the button is as it should be already
	if ( enable != okEnabled )
	{
		HiliteControl((ControlHandle)itemHandle, enable ? kControlActive : kControlInactive);

		// Invalidate the default button frame so it will be redrawn dim
		WindowPtr savePort;
		GetPort(&savePort);
		SetPort(dlog);
		InsetRect(&itemRect, kButtonFrameInset, kButtonFrameInset);
		InvalRect(&itemRect);
		SetPort(savePort);
	}
}

//	DrawGrayBox draws a gray box to be used as a separator.
//	On a ColorQD machine, a true-gray line is attempted.
//  On a B&W machine, a 50% pattern is used.

static pascal void DrawGrayBox(Rect *theBox)
{
	PenState	penState;
	GetPenState(&penState);

	PixPatHandle ppat = kODNULL;
	
	/* check if Color QuickDraw is available */	
	long response;
	if( Gestalt(gestaltQuickdrawFeatures, &response)==noErr
				&& BitTst(&response, 31-gestaltHasColor) ) {
		const RGBColor gray = {0x7FFF,0x7FFF,0x7FFF};
		ppat = NewPixPat();
		if( ppat )
			MakeRGBPat(ppat,&gray);
	}

	if( ppat )
		PenPixPat(ppat);
	else
		PenPat(&ODQDGlobals.gray);
		
	FrameRect(theBox);
	
	SetPenState(&penState);

	if( ppat )
		DisposePixPat(ppat);
}

pascal void DrawGrayBoxItem(DialogPtr theDialog, short theItem)
{
	Rect 	boxRect;
	Handle	scratchHandle;
	short	scratchKind;
	
	GetDialogItem(theDialog, theItem, &scratchKind, &scratchHandle, &boxRect);
	DrawGrayBox(&boxRect);
}

pascal void DrawItemFrame(DialogPtr theDialog, short theItem)
{
	Rect 	frameRect;
	Handle	scratchHandle;
	short	scratchKind;
	
	GetDialogItem(theDialog, theItem, &scratchKind, &scratchHandle, &frameRect);
	FrameRect(&frameRect);
}

////////////////////////////////////////////////////////////////////////////////
// DrawITextInDlogBox
// Given a dialog, a rect and an ODIText*, truncate the text to fit in the
// rect and then draw it in the dialog.  Return result telling whether any
// change was made.
////////////////////////////////////////////////////////////////////////////////

ODBoolean DrawITextInDlogBox( ODIText* itext, const Rect* textRect,
		DialogPtr dlog, ODBoolean tryToTruncate )
{
	WASSERT(dlog);
	WindowPtr savePort;
	GetPort(&savePort);
	SetPort(dlog);

	PenState savedPenState;
	GetPenState(&savedPenState);
	PenNormal();

	short saveFont = dlog->txFont;
	short savedTextMode = dlog->txMode;
	
	ODScriptCode scriptCode = GetITextScriptCode(itext);
	if ( scriptCode != FontToScript(saveFont) )
		TextFont(GetScriptVariable(scriptCode,smScriptAppFond));
	
	char buffer[256];
	ODSShort len = GetITextStringLength(itext);
	if ( len > 255 ) len = 255;
	ODBlockMove( GetITextPtr( itext ), buffer, len );
	short truncResult;
	if ( tryToTruncate )
		truncResult = TruncText( textRect->right - textRect->left - 1,
			buffer, &len, truncEnd);
	TETextBox( buffer, len,	textRect, teFlushDefault );

	TextFont(saveFont);
	TextMode(savedTextMode);
	SetPenState(&savedPenState);
	SetPort(savePort);
	return tryToTruncate? truncResult == 1 : kODFalse;	// 1 means truncation needed and successful
}

////////////////////////////////////////////////////////////////////////////////
// FlashButtonItem
// Given a dialog and an item representing a button, flash that button,
// highlighting it for 8 ticks and then reverting.
////////////////////////////////////////////////////////////////////////////////
void FlashButtonItem( DialogPtr dialog, short itemHit )
{
	Rect itemRect;
	short itemType;
	Handle itemHandle;

	GetDialogItem( dialog, itemHit, &itemType, &itemHandle, &itemRect);
	ASSERT_CONTROL_ITEM(itemType);
	HiliteControl((ControlHandle)itemHandle, kControlButtonPart);
	unsigned long ticks = TickCount() + 8;
	while ( TickCount() < ticks ) ;
	HiliteControl((ControlHandle)itemHandle, kControlActive);	// Turn off hilite
}

////////////////////////////////////////////////////////////////////////////////
// ArrowKeyScrollList
// Given an "item" representing an up or down arrow or any of the other keys
// recognized by ODArrowKeyFilterProc above, a ListHandle indicating the list
// in which scrolling is taking place, pageSize giving the number of entries to
// be skipped by a "page down" command, and the zero-based index of the last
// entry in the list, do the right thing for the key selected.  So far this
// routine is used for lists embedded in dialogs, and it's up to the caller
// to determine which list is meant if there is more than one.
// NOTE that nothing happens currently if no list item is selected.  There needs
// to be a starting point for scrolling.
////////////////////////////////////////////////////////////////////////////////

void ArrowKeyScrollList( ODSShort arrowItem, ListHandle listH,
		ODSShort pageSize, ODSShort lastEntry )
{
	ODSShort limitCell;
	ODSShort delta;
	switch( arrowItem )
	{
		case kODUpArrowItem:
			limitCell = 0;
			delta = -1;
			break;

		case kODDownArrowItem:
			limitCell = lastEntry;
			delta = 1;
			break;

		case kODPageUpArrowItem:
			limitCell = 0;
			delta = 0 - pageSize;
			break;

		case kODPageDownArrowItem:
			limitCell = lastEntry;
			delta = pageSize;
			break;

		case kODHomeArrowItem:
			limitCell = 0;
			delta = 0 - lastEntry;
			break;

		case kODEndArrowItem:
			limitCell = lastEntry;
			delta = lastEntry;
			break;

		default:
			WARN( "unknown arrow key/item" );
			return;
	}

	Cell selectedCell = {0,0};
	if ( !LGetSelect( true, &selectedCell, listH ) 
			|| (selectedCell.v == limitCell) ) 
		return;
	LSetSelect( false, selectedCell, listH );
	selectedCell.v += delta;
	if ( selectedCell.v < 0 )
		selectedCell.v = 0;
	else if ( limitCell > 0 && selectedCell.v > limitCell )
		selectedCell.v = limitCell;
	LSetSelect( true, selectedCell, listH );
	LAutoScroll(listH);
}

//------------------------------------------------------------------------------
// ReplaceIntoString
//------------------------------------------------------------------------------
// Substitutes str0 for all occurances of "^0", and str1 for "^1", in the string
// retrieved from the 'STR ' resource identified by the first parameter.  Either
// str0 or str1 may be null.  Uses the current resource chain to find the 'STR '
// resource.

void ReplaceIntoString(ODSShort strResourceID,
						ConstStr255Param str0,
						ConstStr255Param str1,
						Str255 destString)
{
	StringHandle msgHandle = GetString(strResourceID);
	if ( msgHandle )
	{
		DetachResource((Handle) msgHandle);

		ODHandle textHandle;

		HLock((Handle) msgHandle);
		ODHandle templateText = PStrToText(*msgHandle);
		HUnlock((Handle) msgHandle);

		if ( str0 )
		{
			textHandle = PStrToText(str0);
			if ( textHandle )
			{
				ReplaceText((Handle) templateText, (Handle) textHandle, "\p^0");
				ODDisposeHandle(textHandle);
			}
		}
		
		if ( str1 )
		{
			textHandle = PStrToText(str1);
			if ( textHandle )
			{
				ReplaceText((Handle) templateText, (Handle) textHandle, "\p^1");
				ODDisposeHandle(textHandle);
			}
		}

		TextToPStr(templateText, destString);

		ODDisposeHandle(templateText);
		DisposeHandle((Handle) msgHandle);
	}
	else
		if( ResError() != memFullErr )
		{
			WARN("ReplaceIntoString didn't find STR %hd",strResourceID);
			destString[0] = 0;
		}
}


//--------------------------------------------------------------------
// GetODITextInd
// Gets string # (index) from the 'STR#' resource given by resID 
// into an IText* whose script and language codes are those of 
// the current system script.
//--------------------------------------------------------------------

ODIText* GetODITextInd(short resID, short index)
{
	Str255 pStr = "\p";
	GetIndString(pStr, resID, index);
	ODScriptCode script = FontToScript( GetSysFont() );
	return CreateITextPString(script,
							GetScriptVariable(script, smScriptLang), 
							pStr);
}


//--------------------------------------------------------------------
// GetODIText
// Turns an 'STR ' resource of the given resID into an ODIText* whose
// language and script codes are those of the current system script.
//--------------------------------------------------------------------

ODIText* GetODIText(short resID)
{
	Handle textHandle;
	ODIText* iText = kODNULL;
	
	textHandle = (Handle)GetString(resID);
	
	if ( textHandle != kODNULL )
	{
		// CreateIText copies the values passed into it, so we don't
		// have to worry about the resource handle moving outside of
		// the scope of this function.

		ODScriptCode script = GetScriptManagerVariable(smSysScript);
		ODLangCode lang = GetScriptVariable(script, smScriptLang);
		HLock(textHandle);
		iText = CreateIText( script, lang, (StringPtr)*textHandle );
			
		ReleaseResource(textHandle);
	}
	
	return iText;
}

//------------------------------------------------------------------------------
// SetDialogTextStyle
//------------------------------------------------------------------------------

void SetDialogTextStyle(DialogPtr dlg, short finfResourceID,
		ODScriptCode script)
{
	typedef struct {
		short count;
		short fontNumber;
		short fontStyl;
		short fontSize;
	} FontSpec;

	typedef FontSpec** FontSpecHandle;
	
	FontSpecHandle finfHandle = (FontSpecHandle) GetResource('finf', finfResourceID);

	if ( finfHandle )
	{
		SetPort(dlg);
		
		short font;
		if ( script != smCurrentScript )
			font = GetScriptVariable( script, smScriptAppFond );	// <eeh> smScriptSysFond better?
		else
			font = (**finfHandle).fontNumber;
		
		TextFont(font);
		TextFace((**finfHandle).fontStyl);
		TextSize((**finfHandle).fontSize);
		TEHandle te = ((DialogPeek)dlg)->textH;
		(**te).txFont = font;
		(**te).txSize = (**finfHandle).fontSize;
		(**te).txFace = (**finfHandle).fontStyl;
		TECalText(te);
		ReleaseResource((Handle) finfHandle);
	} else
		if( ResError() != memFullErr )
			WARN("SetDialogTextStyle didn't find 'finf' %hd",finfResourceID);
}

//-------------------------------------------------------------------------------------
// SetPopupItemScript
//-------------------------------------------------------------------------------------
// "scriptID" should be a true script code, not an implicit script code
//
// This routine assumes the popup is drawn using the font of the current port.
//
// Setting the script of a popup menu item has two bad consequences:
// The title and item appear in bold face, and the width of the
// menu is increased.  This is true even if the script specified
// is the same as the default script!  So only specify a script if its different.

void SetPopupItemScript(MenuHandle menu, short itemNum, short scriptID)
{
	const ODSShort kUseSpecificScript = 0x1c;

	ODSLong	savedSMFontForceFlag = GetScriptManagerVariable(smFontForce);
	SetScriptManagerVariable(smFontForce, false);

	// Since the popup is drawn using the window font, compare the argument
	// script to the script of the current graphics port.
	GrafPtr curPort;
	GetPort(&curPort);

	if ( scriptID == smSystemScript )
		scriptID = (short) GetScriptManagerVariable(smSysScript);

	if ( scriptID != FontToScript(curPort->txFont) )
	{
		SetItemCmd(menu, itemNum, kUseSpecificScript);
		SetItemIcon(menu, itemNum, scriptID);
	}
	
	SetScriptManagerVariable(smFontForce, savedSMFontForceFlag);
}


//-------------------------------------------------------------------------------------
// Implementation of DialogScriptData class
//-------------------------------------------------------------------------------------

DialogScriptData::DialogScriptData()
{
	fItemRedrawn = 0L;
	fMustUnlock = kODFalse;
	fScript = ksmUninited;
}

DialogScriptData::~DialogScriptData()
{
	if ( fMustUnlock )
		this->ReleaseLockout();
}

void DialogScriptData::SetScriptAndLockKeyboard()
{
//	this->SetScriptAndLockKeyboard( FontToScript( GetAppFont() ) );
	WASSERT( FontToScript( GetSysFont()) == FontToScript( GetAppFont() ));
	this->SetScriptAndLockKeyboard( FontToScript( GetSysFont() ) );
}

// There are two cases we need to be aware of here when the script passed
// in comes from an existing IText* and isn't Roman.  First, it's possible
// that the keyscript is not yet set to that script (that it's still Roman).
// Second, the dialog may not be in a state to properly display that text.

void DialogScriptData::SetScriptAndLockKeyboard( ODScriptCode script )
{
	if ( script != smRoman )	// lock all changes out: switch to Roman will
	{							// result in non-Roman text being garbage
		if ( GetScriptManagerVariable(smKeyScript) != script )
			KeyScript( script );	// script may not yet have been set

		WASSERT(fDialog);
		TEHandle teh = ((DialogPeek)fDialog)->textH;
		if ( FontToScript( (*teh)->txFont ) != script )
		{
			(*teh)->txFont = GetScriptVariable( script, smScriptSysFond );			
		}

		KeyScript( smKeyDisableKybdSwitch );
		fMustUnlock = kODTrue;
	}
	fScript = script;
}

void DialogScriptData::ReleaseLockout()
{
	if ( fMustUnlock != kODFalse )
	{
		KeyScript( smKeyEnableKybds );
		fMustUnlock = kODFalse;
	}
}

ODBoolean DialogScriptData::ItemRedrawn( short item )
{
	WASSERT( item < kMaxNumItems );
	return (fItemRedrawn & 1L<<(item-1)) != 0;
}

void DialogScriptData::SetRedrawn( short item )
{
	WASSERT( item < kMaxNumItems );
	fItemRedrawn |= 1L<<(item-1);
}

#endif // _PLATFORM_WIN32_
