Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
sources/PresInfoWindow.c
/* |
File: PresInfoWindow.c |
Copyright: © 2000-2001 by Apple Computer, Inc., all rights reserved. |
*/ |
#include <ConditionalMacros.h> |
#include <Resources.h> |
#include <TextUtils.h> |
#include <string.h> |
#include <QuicktimeComponents.h> |
#include <MoviesFormat.h> |
#include <Movies.h> |
#include <QuickTimeStreaming.h> |
#include <QTStreamingComponents.h> |
#include "QTSSampleCodeUtils.h" |
#include "WindowSupport.h" |
#include "presinfowindow.h" |
#include "UsherBroadcast.h" |
#include "UsherCommands.h" |
// --------------------------------------------------------------------------- |
// D E F I N I T I O N S |
// --------------------------------------------------------------------------- |
struct PresInfoWindowRecord { |
StandardWindowRecord standard; |
UsherBroadcast *broadcast; |
WindowPtr parentWindow; |
QTSPresentation qtsPresentation; |
QTSStatHelper statHelperAll; |
QTSStatHelper statHelperShort; |
Handle ditlHandle; |
ControlHandle vScroll; |
ControlHandle hScroll; |
long lastUpdateTime; // in ticks |
long updateInterval; // in ticks |
SInt32 dirtyFlags; |
SInt32 drawingCount; |
GrafPtr oldPort; |
short labelFont; |
short labelFontSize; |
short font; |
short fontSize; |
FontInfo fontInfo; |
Rect boundsGutterInfo; |
Rect textItemGutterInfo; |
short lineHeight; |
}; |
typedef struct PresInfoWindowRecord PresInfoWindowRecord, *PresInfoWindPeek; |
#define kPresInfoWindDefaultUpdateInterval 60 // in ticks |
//#define kPresInfoWindDefaultLabelFont systemFont |
#define kPresInfoWindDefaultLabelFont applFont |
#define kPresInfoWindDefaultLabelFontSize 9 |
#define kPresInfoWindDefaultFont applFont |
#define kPresInfoWindDefaultFontSize 9 |
// top, left, bottom, right |
#define kPresInfoWindDefaultBoundsGutterInfo {2,5,2,5} |
//#define kPresInfoWindDefaultTextItemGutterInfo {2,2,2,2} |
#define kPresInfoWindDefaultTextItemGutterInfo {2,0,0,0} |
enum { |
kPresInfoWindDrawingFlag_Erase = 0x00000001 |
}; |
enum { |
kPresInfoWindDirtyFlag_Stats = 0x00000001 |
}; |
// --------------------------------------------------------------------------- |
// P R O T O T Y P E S |
// --------------------------------------------------------------------------- |
static void PresInfoWindPriv_SetupFontInfo(PresInfoWindowRecord *inWindData); |
static void PresInfoWindPriv_Dispose(PresInfoWindowRecord *inWindData); |
static Boolean PresInfoWindPriv_GetWindData(WindowPtr inWindow, PresInfoWindowRecord **outWindData); |
static void PresInfoWindPriv_SetBroadcast(PresInfoWindowRecord *inWindData, UsherBroadcast *inBroadcast); |
static void PresInfoWindPriv_GetReadyToDraw(PresInfoWindowRecord *inWindData); |
static void PresInfoWindPriv_FinishedDrawing(PresInfoWindowRecord *inWindData); |
static void PresInfoWindPriv_DrawStats(PresInfoWindowRecord *inWindData, QTSStatHelper inStatHelper); |
static void PresInfoWindPriv_UpdateIfNecessary(PresInfoWindowRecord *inWindData); |
static void PresInfoWindPriv_Update(PresInfoWindowRecord *inWindData); |
// --------------------------------------------------------------------------- |
// PresInfoWind_New |
// --------------------------------------------------------------------------- |
OSErr PresInfoWind_New(WindowPtr *outWindow) |
{ |
OSErr err = noErr; |
WindowPtr window = NULL; |
Rect windowBounds = {100,100,400,400}; |
PresInfoWindowRecord *windData = NULL; |
Str255 tempString; |
DialogTHndl dlogHandle = NULL; |
Rect defaultBoundsGutter = kPresInfoWindDefaultBoundsGutterInfo; |
Rect defaultTextItemGutter = kPresInfoWindDefaultTextItemGutterInfo; |
dlogHandle = (DialogTHndl)GetResource('DLOG', rDLOG_PresInfoWindow); |
if (dlogHandle == NULL) { |
DEBUGF(("PresInfoWind_New - null dlog resource")); |
EXITERR( err = memFullErr ); |
} |
windowBounds = (**dlogHandle).boundsRect; |
#if TARGET_API_MAC_CARBON |
EXITIFERR( err = CreateNewWindow( kDocumentWindowClass, |
kWindowCloseBoxAttribute /* + kWindowResizableAttribute, um */, |
&windowBounds, |
&window ) ); |
#else |
window = NewCWindow(nil, &windowBounds, (StringPtr)"", |
false, documentProc, (WindowPtr)kFirstWindowOfClass, true, 0L); |
#endif |
if (window == NULL) { |
DEBUGF(("PresInfoWind_New - null window")); |
EXITERR( err = memFullErr ); |
} |
windData = (PresInfoWindowRecord*)QTSNewPtrClear(sizeof(*windData)); |
EXITIFERR( err = MemError() ); |
WindowSupport_InitStandardRecord(&windData->standard, kSignature_PresInfoWind, window); |
SetWRefCon(window, (long)windData); |
windData->updateInterval = kPresInfoWindDefaultUpdateInterval; |
windData->labelFont = kPresInfoWindDefaultLabelFont; |
windData->labelFontSize = kPresInfoWindDefaultLabelFontSize; |
windData->font = kPresInfoWindDefaultFont; |
windData->fontSize = kPresInfoWindDefaultFontSize; |
windData->boundsGutterInfo = defaultBoundsGutter; |
windData->textItemGutterInfo = defaultTextItemGutter; |
PresInfoWindPriv_SetupFontInfo(windData); |
windData->ditlHandle = GetResource('DITL', rDITL_PresInfoWindow); |
if (windData->ditlHandle == NULL) { |
DEBUGF(("PresInfoWind_New - null ditlhandle")); |
EXITERR( err = memFullErr ); |
} |
DetachResource(windData->ditlHandle); |
GetIndString(tempString, rStringList_PresInfoWindow, kPresInfoWindString_DefaultName); |
SetWTitle(window, tempString); |
//ShowWindow(window); |
*outWindow = window; |
exit: |
if (err != noErr) { |
if (windData != NULL) { |
PresInfoWind_Close(window); |
} else { |
} |
} |
return err; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWindPriv_SetupFontInfo |
// --------------------------------------------------------------------------- |
static void PresInfoWindPriv_SetupFontInfo(PresInfoWindowRecord *inWindData) |
{ |
GrafPtr oldPort; |
GetPort(&oldPort); |
TextFont(inWindData->font); |
TextSize(inWindData->fontSize); |
GetFontInfo(&inWindData->fontInfo); |
inWindData->lineHeight = inWindData->fontInfo.ascent + inWindData->fontInfo.descent + inWindData->fontInfo.leading |
+ inWindData->textItemGutterInfo.top + inWindData->textItemGutterInfo.bottom; |
MacSetPort(oldPort); |
} |
// --------------------------------------------------------------------------- |
// PresInfoWind_Close |
// --------------------------------------------------------------------------- |
void PresInfoWind_Close(WindowPtr inWindow) |
{ |
PresInfoWindowRecord *windData = NULL; |
if (inWindow != NULL) { |
if (!PresInfoWindPriv_GetWindData(inWindow, &windData)) { |
DEBUGF(("PresInfoWind_Close $%.8x - couldnt get wind data", inWindow)); |
goto exit; |
} |
if (windData->statHelperAll != 0) { |
QTSDisposeStatHelper(windData->statHelperAll); |
} |
if (windData->statHelperShort != 0) { |
QTSDisposeStatHelper(windData->statHelperShort); |
} |
if (windData->parentWindow != NULL) { |
WindowSupport_HandleMessage(windData->parentWindow, kMessage_WindowClosing, windData->standard.window); |
} |
PresInfoWindPriv_Dispose(windData); |
} |
DisposeWindow(inWindow); |
exit: |
return; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWindPriv_Dispose |
// --------------------------------------------------------------------------- |
static void PresInfoWindPriv_Dispose(PresInfoWindowRecord *inWindData) |
{ |
if (inWindData != NULL) { |
if ((inWindData->broadcast != NULL) && (inWindData->standard.window != NULL)) { |
// tell the broadcast we went away... |
UsherBroadcast_RemoveWindow(inWindData->broadcast, inWindData->standard.window); |
} |
DisposeHandle(inWindData->ditlHandle); |
DisposePtr((Ptr)inWindData); |
} |
} |
// --------------------------------------------------------------------------- |
// PresInfoWindPriv_GetWindData |
// --------------------------------------------------------------------------- |
static Boolean PresInfoWindPriv_GetWindData(WindowPtr inWindow, PresInfoWindowRecord **outWindData) |
{ |
*outWindData = NULL; |
if (inWindow != NULL) { |
*outWindData = (PresInfoWindowRecord*)GetWRefCon(inWindow); |
if (*outWindData != NULL) { |
if ((*outWindData)->standard.signature != kSignature_PresInfoWind) { |
DEBUGF(("PresInfoWind_GetWindData $%.8x $%.8x bad signature '%.4s'", inWindow, *outWindData, &(*outWindData)->standard.signature)); |
*outWindData = NULL; |
} |
} |
} |
return (*outWindData != NULL); |
} |
#pragma mark - |
// --------------------------------------------------------------------------- |
// PresInfoWind_Idle |
// --------------------------------------------------------------------------- |
void PresInfoWind_Idle(WindowPtr inWindow) |
{ |
PresInfoWindowRecord *windData; |
if (!PresInfoWindPriv_GetWindData(inWindow, &windData)) { |
DEBUGF(("PresInfoWind_Idle $%.8x - couldnt get wind data", inWindow)); |
goto exit; |
} |
PresInfoWindPriv_UpdateIfNecessary(windData); |
if (windData->dirtyFlags != 0) { |
PresInfoWind_Draw(inWindow); |
} |
exit: |
return; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWind_GetMenuCommand |
// --------------------------------------------------------------------------- |
long PresInfoWind_GetMenuCommand(WindowPtr inWindow, long inMenuResult, void **outCommandParams) |
{ |
#pragma unused(outCommandParams) |
PresInfoWindowRecord *windData; |
short menuID, menuItem; |
SInt32 command = kCommand_Nothing; |
if (!PresInfoWindPriv_GetWindData(inWindow, &windData)) { |
DEBUGF(("PresInfoWind_GetMenuCommand $%.8x - couldnt get wind data", inWindow)); |
goto exit; |
} |
menuID = (short)MYHIWORD(inMenuResult); |
menuItem = (short)MYLOWORD(inMenuResult); |
switch (menuID) { |
default: |
break; |
} |
if (command == kCommand_Nothing) { |
if (windData->broadcast != 0) { |
command = UsherBroadcast_GetMenuCommand(windData->broadcast, inMenuResult, outCommandParams); |
} |
} |
exit: |
return command; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWind_DoCommand |
// --------------------------------------------------------------------------- |
OSErr PresInfoWind_DoCommand(WindowPtr inWindow, long inCommand, void *inCommandParams) |
{ |
#pragma unused(inCommandParams) |
PresInfoWindowRecord *windData; |
OSErr err = noErr; |
if (!PresInfoWindPriv_GetWindData(inWindow, &windData)) { |
DEBUGF(("PresInfoWind_DoCommand $%.8x - couldnt get wind data", inWindow)); |
EXITERR( err = kErr_AppUnhandledCommand ); |
} |
switch (inCommand) { |
default: |
if (windData->broadcast != 0) { |
err = UsherBroadcast_DoCommand(windData->broadcast, inCommand, inCommandParams); |
} else { |
err = kErr_AppUnhandledCommand; |
} |
break; |
} |
exit: |
return err; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWind_GrowWindow |
// --------------------------------------------------------------------------- |
void PresInfoWind_GrowWindow(WindowPtr inWindow, EventRecord *inEvent) |
{ |
PresInfoWindowRecord *windData; |
GrafPtr oldPort; |
Rect oldWindowRect; |
Rect newWindowRect; |
Rect itemRect; |
Rect bbox; |
long result; |
if (!PresInfoWindPriv_GetWindData(inWindow, &windData)) { |
DEBUGF(("UserInfoWind_GrowWindow $%.8x - couldnt get wind data", inWindow)); |
goto exit; |
} |
GetPort(&oldPort); |
SetPortWindowPort(inWindow); |
#define kPresInfoWindMinWidth 250 |
#define kPresInfoWindMinHeight 150 |
#define kPresinfoWindMaxWidth 30000 |
#define kPresInfoWindMaxHeight 30000 |
bbox.left = kPresInfoWindMinWidth; |
bbox.top = kPresInfoWindMinHeight; |
bbox.right = kPresinfoWindMaxWidth; |
bbox.bottom = kPresInfoWindMaxHeight; |
result = GrowWindow(inWindow, inEvent->where, &bbox); |
if (result != 0) { |
if ((MYLOWORD(result) < 0) || (MYHIWORD(result) < 0)) { |
DEBUGF(("grow result $%.8x", result)); |
goto exit; |
} |
// resize the window, including the stats area |
CopyPortBounds(GetWindowPort(inWindow), &oldWindowRect); |
SizeWindow(inWindow, (short)MYLOWORD(result), (short)MYHIWORD(result), true); |
CopyPortBounds(GetWindowPort(inWindow), &newWindowRect); |
DITLGetItemRect(windData->ditlHandle, rPresInfoWindDITLItem_InfoArea, &itemRect); |
itemRect.right += (newWindowRect.right - newWindowRect.left - (oldWindowRect.right - oldWindowRect.left)); |
itemRect.bottom += (newWindowRect.bottom - newWindowRect.top - (oldWindowRect.bottom - oldWindowRect.top)); |
DITLSetItemRect(windData->ditlHandle, rPresInfoWindDITLItem_InfoArea, &itemRect); |
InvalWindowRect(inWindow, &itemRect); |
} |
exit: |
MacSetPort(oldPort); |
return; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWind_DoContentClick |
// --------------------------------------------------------------------------- |
void PresInfoWind_DoContentClick(WindowPtr inWindow, EventRecord *inEvent) |
{ |
#pragma unused(inEvent) |
PresInfoWindowRecord *windData; |
if (!PresInfoWindPriv_GetWindData(inWindow, &windData)) { |
DEBUGF(("PresInfoWind_DoContentClick $%.8x - couldnt get wind data", inWindow)); |
goto exit; |
} |
exit: |
return; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWind_ActivateWindow |
// --------------------------------------------------------------------------- |
void PresInfoWind_ActivateWindow(WindowPtr inWindow, Boolean inBecomingActive) |
{ |
#pragma unused(inBecomingActive) |
PresInfoWindowRecord *windData; |
Rect windowRect; |
if (!PresInfoWindPriv_GetWindData(inWindow, &windData)) { |
DEBUGF(("PresInfoWind_ActivateWindow $%.8x - couldnt get wind data", inWindow)); |
goto exit; |
} |
CopyPortBounds(GetWindowPort(inWindow), &windowRect); |
InvalWindowRect(inWindow, &windowRect); |
// draw any stuff that should be different in active/inactive mode |
//PresInfoWind_Draw(inWindow); |
exit: |
return; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWind_HandleMessage |
// --------------------------------------------------------------------------- |
OSErr PresInfoWind_HandleMessage(WindowPtr inWindow, long inMessage, void *inMessageParams) |
{ |
#pragma unused(inMessageParams) |
PresInfoWindowRecord *windData; |
OSErr err = noErr; |
if (!PresInfoWindPriv_GetWindData(inWindow, &windData)) { |
DEBUGF(("PresInfoWind_HandleMessage $%.8x - couldnt get wind data", inWindow)); |
EXITERR( err = qtsBadSelectorErr ); |
} |
switch (inMessage) { |
case kMessage_SetUsherBroadcast: |
PresInfoWindPriv_SetBroadcast(windData, (UsherBroadcast*)inMessageParams); |
break; |
case kMessage_SetParentWindow: |
windData->parentWindow = (WindowPtr)inMessageParams; |
break; |
default: |
err = qtsBadSelectorErr; |
break; |
} |
exit: |
return err; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWindPriv_SetBroadcast |
// --------------------------------------------------------------------------- |
static void PresInfoWindPriv_SetBroadcast(PresInfoWindowRecord *inWindData, UsherBroadcast *inBroadcast) |
{ |
OSErr err = noErr; |
OSErr tempErr; |
Str255 pname; |
UsherBroadcastStringParams stringParams; |
inWindData->broadcast = inBroadcast; |
if (inWindData->broadcast != 0) { |
memset(&stringParams, 0, sizeof(stringParams)); |
stringParams.ioString = (char*)pname; |
stringParams.inMaxLength = sizeof(pname)-1; |
tempErr = UsherBroadcast_DoCommand(inWindData->broadcast, kCommand_GetBroadcastName, &stringParams); |
if (tempErr == noErr) { |
if (stringParams.returnedStringLength > 0) { |
//@@@ use resources here |
strcat((char*)pname, " Info"); |
CopyCToPStr((char*)pname, pname); |
SetWTitle(inWindData->standard.window, pname); |
} |
} |
UsherBroadcast_AddWindow(inBroadcast, inWindData->standard.window); |
inWindData->qtsPresentation = UsherBroadcast_GetQTSPresentation(inWindData->broadcast); |
if (inWindData->qtsPresentation != kQTSInvalidPresentation) { |
EXITIFERR( err = QTSNewStatHelper(inWindData->qtsPresentation, kQTSAllStreams, |
kQTSAllStatisticsType, 0L, &inWindData->statHelperAll) ); |
EXITIFERR( err = QTSNewStatHelper(inWindData->qtsPresentation, kQTSAllStreams, |
kQTSShortStatisticsType, 0L, &inWindData->statHelperShort) ); |
} |
} |
exit: |
return; |
} |
#pragma mark - |
// --------------------------------------------------------------------------- |
// PresInfoWind_Draw |
// --------------------------------------------------------------------------- |
void PresInfoWind_Draw(WindowPtr inWindow) |
{ |
PresInfoWindowRecord *windData; |
Rect tempRect; |
if (!PresInfoWindPriv_GetWindData(inWindow, &windData)) { |
DEBUGF(("PresInfoWind_Draw $%.8x - couldnt get wind data", inWindow)); |
goto exit; |
} |
PresInfoWindPriv_GetReadyToDraw(windData); |
CopyPortBounds(GetWindowPort(inWindow), &tempRect); |
EraseRect(&tempRect); |
DrawGrowIcon(inWindow); |
if (windData->statHelperAll != 0) { |
PresInfoWindPriv_DrawStats(windData, windData->statHelperAll); |
} |
PresInfoWindPriv_FinishedDrawing(windData); |
exit: |
return; |
} |
// --------------------------------------------------------------------------- |
// PresInfoWindPriv_GetReadyToDraw |
// --------------------------------------------------------------------------- |
static void PresInfoWindPriv_GetReadyToDraw(PresInfoWindowRecord *inWindData) |
{ |
if (inWindData->drawingCount < 0) { |
DEBUGF(("PresInfoWindPriv_GetReadyToDraw $%.8x-drawingcount<0 $%.8x", inWindData, inWindData->drawingCount)); |
} |
// if we're reentrant, we won't save the old port...i guess |
if (inWindData->drawingCount == 0) { |
GetPort(&inWindData->oldPort); |
} |
++inWindData->drawingCount; |
SetPortWindowPort(inWindData->standard.window); |
TextFont(inWindData->font); |
TextSize(inWindData->fontSize); |
} |
// --------------------------------------------------------------------------- |
// PresInfoWindPriv_FinishedDrawing |
// --------------------------------------------------------------------------- |
static void PresInfoWindPriv_FinishedDrawing(PresInfoWindowRecord *inWindData) |
{ |
--inWindData->drawingCount; |
if (inWindData->drawingCount == 0) { |
MacSetPort(inWindData->oldPort); |
} |
} |
// --------------------------------------------------------------------------- |
// PresInfoWindPriv_DrawStats |
// --------------------------------------------------------------------------- |
static void PresInfoWindPriv_DrawStats(PresInfoWindowRecord *inWindData, QTSStatHelper inStatHelper) |
{ |
#define kSHStringLength 255 |
OSErr err = noErr; |
QTSStatHelperNextParams nextParams; |
Rect bounds; |
Rect lineBounds; |
Point current; |
char nameString[kSHStringLength+1]; |
char dataString[kSHStringLength+1]; |
char unitsString[kSHStringLength+1]; |
short width; |
short unitsWidth; |
DITLGetItemRect(inWindData->ditlHandle, rPresInfoWindDITLItem_InfoArea, &bounds); |
MacFrameRect(&bounds); |
bounds.left += inWindData->boundsGutterInfo.left; |
bounds.right -= inWindData->boundsGutterInfo.right; |
bounds.top += inWindData->boundsGutterInfo.top; |
bounds.bottom -= inWindData->boundsGutterInfo.bottom; |
memset(&nextParams, 0, sizeof(nextParams)); |
nextParams.maxStatNameLength = sizeof(nameString)-1; |
nextParams.returnedStatName = nameString; |
nextParams.maxStatStringLength = sizeof(dataString)-1; |
nextParams.returnedStatString = dataString; |
nextParams.maxStatUnitLength = sizeof(unitsString)-1; |
nextParams.returnedStatUnit = unitsString; |
current.h = bounds.left; |
current.v = bounds.top + inWindData->lineHeight; |
EXITIFERR( err = QTSStatHelperResetIter(inStatHelper) ); |
while (QTSStatHelperNext(inStatHelper, &nextParams)) { |
//@@@ should do something smarter when the label+data is too long for the line |
MoveTo(current.h, current.v); |
lineBounds.left = current.h; |
lineBounds.right = bounds.right; |
lineBounds.top = current.v - inWindData->lineHeight; |
lineBounds.bottom = current.v; |
// name |
MacDrawText((unsigned char*)nameString, 0, (short)strlen(nameString)); |
#define kDataAndUnitsSpacing 3 |
// stat units |
unitsWidth = 0; |
if (unitsString[0] != '\0') { |
unitsWidth = TextWidth(unitsString, 0, (short)strlen(unitsString)); |
MoveTo((short)(lineBounds.right - unitsWidth), current.v); |
MacDrawText((unsigned char*)unitsString, 0, (short)strlen(unitsString)); |
unitsWidth += kDataAndUnitsSpacing; |
} |
// stat data |
width = TextWidth(dataString, 0, (short)strlen(dataString)); |
MoveTo((short)(lineBounds.right - width - unitsWidth), current.v); |
MacDrawText((unsigned char*)dataString, 0, (short)strlen(dataString)); |
current.v += inWindData->lineHeight; |
if (current.v > bounds.bottom) { |
break; |
} |
} |
inWindData->dirtyFlags = 0; |
exit: |
return; |
} |
#pragma mark - |
// --------------------------------------------------------------------------- |
// PresInfoWindPriv_UpdateIfNecessary |
// --------------------------------------------------------------------------- |
static void PresInfoWindPriv_UpdateIfNecessary(PresInfoWindowRecord *inWindData) |
{ |
if (inWindData->lastUpdateTime + inWindData->updateInterval < (SInt32)TickCount()) { |
PresInfoWindPriv_Update(inWindData); |
inWindData->lastUpdateTime = TickCount(); |
inWindData->dirtyFlags |= kPresInfoWindDirtyFlag_Stats; |
} |
} |
// --------------------------------------------------------------------------- |
// PresInfoWindPriv_Update |
// --------------------------------------------------------------------------- |
static void PresInfoWindPriv_Update(PresInfoWindowRecord *inWindData) |
{ |
if (inWindData->statHelperAll != 0) { |
QTSStatHelperGetStats(inWindData->statHelperAll); |
} |
if (inWindData->statHelperShort != 0) { |
QTSStatHelperGetStats(inWindData->statHelperShort); |
} |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14