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.
StandardGetFolder.c
/* |
File: StandardGetFolder.c |
Contains: Routines needed to use CustomGetFile for selecting folders |
Also adds a button to create new folders. |
Written by: Andy Bachorski |
Copyright: Copyright © 1998-1999 by Apple Computer, Inc., All Rights Reserved. |
You may incorporate this Apple sample source code into your program(s) without |
restriction. This Apple sample source code has been provided "AS IS" and the |
responsibility for its operation is yours. You are not permitted to redistribute |
this Apple sample source code as "Apple sample source code" after having made |
changes. If you're going to re-distribute the source, we require that you make |
it clear in the source that the code was descended from Apple sample source |
code, but that you've made changes. |
A Few Notes: |
The routines in this file have been tested, though not exhaustively so. |
They should not be used as the basis of the final code you use in your |
application without further testing. |
The error reporting is fairly complete, but there could be errors encounter |
which deserve a more descriptive message than is displayed here. |
This code assumes the presence of System 7 or later. You should check |
the system version before using these routines in your code. |
The MoreFiles package is used for some of the sanity checking in the routines, |
and is not included with this project. You will need to obtain the |
MoreFiles package (from the developer ftp site) to build an use this project. |
The New Folder and 'the name is already taken' error dialogs are from the |
Standard File package and are not included with this project, but rather are |
loaded directly from the system. If this makes you uncomfortable, you can |
always copy those dialog to your application before you ship. |
Change History (most recent first): |
7/1/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1 |
2/02/99 Andy Bachorski Updated project to CodeWarrior Pro 2 |
Added New Folder button to dialog, along |
with the routinesand changes to other |
routines needed to support the button. |
One routine added is a filter to restrict |
the length of the new folder name and to |
filter out ':' characters, which are |
illegal in folder names. |
*/ |
//****************** Universal Interfaces **************************** |
#include <Aliases.h> |
#include <Controls.h> |
#include <Folders.h> |
#include <Scrap.h> |
#include <StandardFile.h> |
#include <TextUtils.h> |
#include <ToolUtils.h> |
#include "MoreFilesExtras.h" |
//****************** Project Interfaces **************************** |
#include "StandardGetFolder.h" |
//****************** Private Definitions **************************** |
enum { |
kSFGetFolderDlgID = 250, // ID for the CustomFile dialog resource |
kSelectItem = 10, // Item number for select button in this dialog |
kCreateNewFolder = 12, // Item number for new folder button |
// Command-key characters should not be localized, so it is OK to hard-code them |
// directly where they are used. |
kSelectButtonKey = 's', // Command key char for the select button |
kNewFolderButton = 'n' // Command key char for the new folder button |
}; |
enum { |
kSelectButtonStrListID = 250, // String list containing labels for select button |
kSelectStrNum = 1, // 'Select: "^0"' |
kDesktopStrNum = 2, // 'Desktop' |
kSelectNoQuoteStrNum = 3 // 'Select: ^0' |
}; |
enum { |
kErrorStringListID = 251, // String list containing error messages |
errNameAlreadyTakenStr = 1, // Name already taken message |
errGenericIOErrorStr = 2 // Generic I/O error message, for unexpected errors. |
}; |
enum { |
kSFGetFolderNameDlgID = -6046, // These two dialogs are from the StandardFile package |
kSFFolderNameTaken = -6044, // and are loaded from the System file. If this makes you nervous |
// you can always put a copy of them in your application. |
kSFFolderNameTextItem = 3, // Item number of name entry edit field in kSFGetFolderNameDlgID dialog |
kSFNameTakenTextItem = 2 // Item number of error string in kSFFolderNameTaken dialog |
}; |
enum { |
kDontUseQuotes = false, // parameter for SetButtonName |
kUseQuotes = true |
}; |
enum { |
kMaxStringLength = 31, // Length limit for folder names |
kButtonHiliteDelay = 8 // Ticks to delay while flashing buttons for command-key entries. |
}; |
enum { |
kFilterAllTypes = -1, // pass all types to filter proc in StandardFild get dialog |
kBootVolumeVRefNum = -1 |
}; |
//****************** Private Types **************************** |
// This structure contains the data that StandardGetFolder needs to do its thing. |
// It is passed to the standard file hook routine in the refcon when CustomGetFile is called. |
struct UserDataRec { |
DialogPtr dialogPtr; // a pointer to the dialog (for access to items in the dialog). |
StandardFileReply *sfrPtr; // a pointer to the standard file reply record (so the current selection can be inspected). |
FSSpec oldSelectionFSSpec; // a copy of the "previous" file spec from the reply record (to check if the selection has changed). |
long desktopDirID; // the dirID |
short desktopVRefNum; // and vRefNum for the desktop folder of the boot volume (to check if the desktop is selected). |
}; |
typedef struct UserDataRec UserDataRec; |
typedef UserDataRec *UserDataRecPtr; |
//****************** Private Prototypes **************************** |
pascal Boolean SFGetFolderModalDialogFilter( DialogPtr dialogPtr, EventRecord *eventRecPtr, short *item, Ptr dataPtr ); |
pascal short SFGetFolderDialogHook( short item, DialogPtr dialogPtr, Ptr dataPtr ); |
void CreateNewFolder( DialogPtr sfDlgPtr, UserDataRecPtr userDataRecPtr, short *dialogItem ); |
pascal Boolean DialogFilter (DialogPtr inputDialog, EventRecord * dialogEventPtr, short *dialogItemPtr); |
void ShowCreateFolderError( Point dialogPt, OSErr anErr ); |
void SetSelectButtonName( DialogPtr dialogPtr, UserDataRecPtr userDataRecPtr ); |
void SetButtonName( DialogPtr dialogPtr, short buttonID, StringPtr buttonName, Boolean quoteFlag); |
void GetLabelString( StringPtr theStr, short stringNum ); |
void GetErrorString( StringPtr theStr, short stringNum ); |
void CopyPStr( StringPtr src, StringPtr dest ); |
void FlashButton( DialogPtr dialogPtr, short buttonID ); |
void SetButtonEnabled( DialogPtr dialogPtr, short buttonID, Boolean enableButton ); |
Boolean SameFSSpec( FSSpecPtr spec1, FSSpecPtr spec2 ); |
Boolean FilterOutColons( long message ); |
Boolean RestrictTextLength( DialogPtr dialogPtr, EventRecord * theDialogEvent, long maxStringLength, short dialogItem ); |
short DialogHasSelectionRange( DialogPeek inputDialog ); |
Boolean KeyIsEditKey( char theKey ); |
//****************************************************************************** |
// |
// This routine assumes the presence of CustomGetFile, which is available in |
// System 7 and later. You should check for the right system version before |
// calling this routine. |
// |
pascal void StandardGetFolder( FileFilterYDUPP fileFilter, StandardFileReply *sfReply ) |
{ |
Point dialogTopLeft = { -1, -1 }; // Use to center the dialog. |
SFTypeList mySFTypeList = { 0 }; |
UserDataRec userData; |
DlgHookYDUPP dialogHookUPP; |
ModalFilterYDUPP modalFilterUPP; |
OSErr anErr; |
CopyPStr( "\p ", sfReply->sfFile.name ); // Set initial contents of Select. |
userData.sfrPtr = sfReply; // Put point to in the reply record in the UserDataRec for later access. |
// Find the desktop folder, save it in the user data struct. |
anErr = FindFolder( kBootVolumeVRefNum, kDesktopFolderType, kDontCreateFolder, |
&userData.desktopVRefNum, &userData.desktopDirID ); |
if ( anErr != noErr ) // Can't find desktop folder, use values that won't match any real vRefNum/dirID. |
{ |
userData.desktopVRefNum = 0; |
userData.desktopDirID = 0; |
} |
// Display the custom choose folder dialog. |
dialogHookUPP = NewDlgHookYDProc( SFGetFolderDialogHook ); // These should probably global and initialized at app |
modalFilterUPP = NewModalFilterYDProc( SFGetFolderModalDialogFilter ); // startup time if this routine is call more than once. |
CustomGetFile( fileFilter, kFilterAllTypes, mySFTypeList, sfReply, kSFGetFolderDlgID, |
dialogTopLeft, dialogHookUPP, modalFilterUPP, nil, nil, &userData ); |
DisposeRoutineDescriptor( dialogHookUPP ); |
DisposeRoutineDescriptor( modalFilterUPP ); |
// The user has pressed the select button and no error was returned. |
// Do a bit of post processing to ensure a useable result is returned. |
if ( sfReply->sfGood ) |
{ |
// If the selection's name is null, nothing is selected in the |
// dialog list, so return the parent folder as the result. |
if ( sfReply->sfFile.name[0] == '\0' ) |
{ |
FSSpec parentDirFSS; |
anErr = FSMakeFSSpec( sfReply->sfFile.vRefNum, sfReply->sfFile.parID, "\p", &parentDirFSS ); |
if (anErr == noErr) |
{ |
sfReply->sfFile = parentDirFSS; |
} |
else |
{ |
sfReply->sfGood = false; // Can't get a valid FSSpec to return, so set the appropriate condition. |
} |
} |
// Everything still looks good, check what type of result is really being returned. |
if ( sfReply->sfGood ) |
{ |
Boolean folderFlag; |
Boolean wasAliasedFlag; |
// First, resolve alias files so the FSSpec points to the target of the alias. |
anErr = ResolveAliasFile( &sfReply->sfFile, true, &folderFlag, &wasAliasedFlag ); |
if ( anErr != noErr ) // Alias couldn't be resolved, set the failure condition. |
{ |
sfReply->sfGood = false; |
} |
else if ( folderFlag ) // Got a valid FSSpec, make sure the alias resolved to a folder. |
{ |
// Lastly, is the selection a volume? |
if ( sfReply->sfFile.parID == fsRtParID ) // The parID of the root of a disk is always fsRtParID == 1 |
{ |
sfReply->sfIsVolume = true; |
sfReply->sfIsFolder = false; |
} |
else |
{ |
sfReply->sfIsVolume = false; |
sfReply->sfIsFolder = true; |
} |
} |
} |
} |
}//end StandardGetFolder |
//****************************************************************************** |
// |
// Return true (to filter the item) if this item is invisible or a file. |
// |
pascal Boolean OnlyVisibleFoldersCustomFileFilter( CInfoPBPtr myCInfoPBPtr, Ptr dataPtr ) |
{ |
#pragma unused (dataPtr) |
Boolean visibleFlag; |
Boolean folderFlag; |
visibleFlag = ! ( myCInfoPBPtr->hFileInfo.ioFlFndrInfo.fdFlags & kIsInvisible ); |
folderFlag = ( myCInfoPBPtr->hFileInfo.ioFlAttrib & ioDirMask ); |
// Because the semantics of the filter proc are "true means don't show |
// it" we need to invert the result that we return |
return !( visibleFlag && folderFlag ); |
}//end OnlyVisibleFoldersCustomFileFilter |
//****************************************************************************** |
// |
// SFGetFolderModalDialogFilter maps a key to the Select button, and handles |
// flashing of the button when the key is hit |
// |
pascal Boolean SFGetFolderModalDialogFilter( DialogPtr dialogPtr, EventRecord *eventRecPtr, short *item, Ptr dataPtr ) |
{ |
#pragma unused ( dataPtr ) |
Boolean eventHandled = false; |
// It is always a good idea to make certain the proper dialog is showing, |
// since there are extension out there that can muck with things, and also |
// because standard file can nest dialogs but calls the same filter for each. |
if ( ((WindowPeek)dialogPtr)->refCon == sfMainDialogRefCon ) |
{ |
// Check if the select button was hit |
if ( ( eventRecPtr->what == keyDown ) && ( eventRecPtr->modifiers & cmdKey ) ) |
{ |
if ( (eventRecPtr->message & charCodeMask) == kSelectButtonKey ) |
{ |
*item = kSelectItem; |
FlashButton( dialogPtr, kSelectItem ); |
eventHandled = true; |
} |
else if ( (eventRecPtr->message & charCodeMask) == kNewFolderButton ) |
{ |
*item = kCreateNewFolder; |
FlashButton( dialogPtr, kCreateNewFolder ); |
eventHandled = true; |
} |
} |
} |
return eventHandled; |
}//end SFGetFolderModalDialogFilter |
//****************************************************************************** |
// |
// The StandardFile hook routine. It ... |
// maps the select button to Open |
// presents the new folder dialog |
// sets the Select button name |
// |
pascal short SFGetFolderDialogHook( short dialogItem, DialogPtr dialogPtr, Ptr dataPtr ) |
{ |
// Be sure Std File is really showing us the intended dialog, not a nested modal dialog. |
if ( ((WindowPeek)dialogPtr)->refCon == sfMainDialogRefCon ) |
{ |
UserDataRecPtr userDataRecPtr = (UserDataRecPtr)dataPtr; |
// Map the Select button to Open |
switch ( dialogItem ) |
{ |
case kSelectItem : |
{ |
dialogItem = sfItemOpenButton; |
} |
break; |
case sfHookNullEvent : |
if ( SameFSSpec( &userDataRecPtr->sfrPtr->sfFile, &userDataRecPtr->oldSelectionFSSpec ) ) |
{ |
break; |
} |
case sfItemFileListUser : |
{ |
OSErr anErr; |
Boolean enableNewFolderButton = true; |
SInt8 ioACUser; |
// Is the volume locked? |
anErr = CheckVolLock( nil, userDataRecPtr->sfrPtr->sfFile.vRefNum ); |
if ( anErr != noErr ) |
{ |
enableNewFolderButton = false; // It's locked, no need to go further. |
} |
else // Volume not locked, does it have access restrictions? |
{ |
anErr = GetIOACUser( userDataRecPtr->sfrPtr->sfFile.vRefNum, userDataRecPtr->sfrPtr->sfFile.parID, |
userDataRecPtr->sfrPtr->sfFile.name, &ioACUser ); |
if ( anErr == noErr ) |
{ |
if ( ! userHasFullAccess( ioACUser ) ) |
{ |
enableNewFolderButton = false; |
} |
} |
} |
SetButtonEnabled( dialogPtr, kCreateNewFolder, enableNewFolderButton ); |
} |
case sfHookFirstCall : |
case sfHookChangeSelection : |
case sfHookRebuildList : |
SetSelectButtonName( dialogPtr, userDataRecPtr ); |
break; |
case kCreateNewFolder : // The New Folder button has been clicked. Do that new folder dialog 'thang. |
CreateNewFolder( dialogPtr, userDataRecPtr, &dialogItem ); |
break; |
default : // This case is here for debugging purposes only |
// - provides a place to catch unexpected selectors while debugging. |
if ( dialogItem != sfHookNullEvent ) |
{ |
dialogItem = dialogItem; |
} |
break; |
} |
// Save the current selection as the old selection for comparison next time. |
// |
// It's not valid on the first call, though, or if we don't have a |
// name available from standard file. |
if ( dialogItem != sfHookFirstCall || userDataRecPtr->sfrPtr->sfFile.name[0] != '\0') |
{ |
userDataRecPtr->oldSelectionFSSpec = userDataRecPtr->sfrPtr->sfFile; |
} |
else |
{ |
// On first call, empty string won't set the button correctly, so invalidate oldSelection. |
userDataRecPtr->oldSelectionFSSpec.vRefNum = 999; |
userDataRecPtr->oldSelectionFSSpec.parID = 0; |
} |
} |
return dialogItem; |
} |
//****************************************************************************** |
enum { |
kHorzOffsetFromNewFolderButtonPos = 55, |
kVertOffsetFromNewFolderButtonPos = 35 |
}; |
void CreateNewFolder( DialogPtr sfDlgPtr, UserDataRecPtr userDataRecPtr, short *dialogItem ) |
{ |
DialogPtr dialogPtr; |
Point itemPoint; |
OSErr anErr; |
dialogPtr = GetNewDialog( kSFGetFolderNameDlgID, nil, (WindowPtr)(-1) ); // -1 means put the dialog in front of all other windows. |
if ( dialogPtr != nil ) |
{ |
Rect itemRect; |
short itemType; |
Handle itemHandle; |
ModalFilterUPP modalFilterUPP = NewModalFilterProc( DialogFilter ); |
SInt16 hitItem = 0; |
// Set up our user items for various things. |
SetDialogDefaultItem( dialogPtr, ok ); |
SetDialogCancelItem( dialogPtr, cancel ); |
SetDialogTracksCursor( dialogPtr, true ); |
// Get the position of the New Folder button so the new folder dialog can be |
// centered there, which more or less matches the default system behavior. |
GetDialogItem( sfDlgPtr, kCreateNewFolder, &itemType, &itemHandle, &itemRect ); |
itemPoint.h = itemRect.left - kHorzOffsetFromNewFolderButtonPos; |
itemPoint.v = itemRect.top - kVertOffsetFromNewFolderButtonPos; |
LocalToGlobal( &itemPoint ); |
MoveWindow( (WindowPtr)dialogPtr, itemPoint.h, itemPoint.v, true ); |
// Select the default folder name. |
SelectDialogItemText( dialogPtr, kSFFolderNameTextItem, 0, 32 ); |
// Let's see the dialog now. |
ShowWindow( (WindowPtr)dialogPtr ); |
DrawDialog( dialogPtr ); |
// Spin in the dialog 'till one of the buttons is pressed. |
do { |
ModalDialog( modalFilterUPP, &hitItem ); |
} while ( hitItem != ok && hitItem != cancel ); |
if ( hitItem == ok ) |
{ |
Str31 itemStr; |
GetDialogItem( dialogPtr, kSFFolderNameTextItem, &itemType, &itemHandle, &itemRect ); |
GetDialogItemText( itemHandle, itemStr ); |
// Make sure the user has entered a name string to work with. |
if ( itemStr[ 0 ] > '\0' ) |
{ |
long newDirID; |
// Check to see if the item selected is on the desktop. If it is, create the new folder on the desktop. |
if ( userDataRecPtr->sfrPtr->sfFile.parID == fsRtParID ) |
{ |
userDataRecPtr->sfrPtr->sfFile.parID = userDataRecPtr->desktopDirID; |
userDataRecPtr->sfrPtr->sfFile.vRefNum = userDataRecPtr->desktopVRefNum; |
} |
BlockMoveData( itemStr, userDataRecPtr->sfrPtr->sfFile.name, itemStr[ 0 ] + 1 ); |
anErr = FSpDirCreate( &userDataRecPtr->sfrPtr->sfFile, smSystemScript, &newDirID ); |
userDataRecPtr->sfrPtr->sfFile.parID = newDirID; |
if ( anErr == noErr ) |
{ |
*dialogItem = sfHookChangeSelection; // Force a list update |
} |
} |
} |
DisposeDialog( dialogPtr ); |
InitCursor(); // Init to prevent I-beam from hanging around in some circumstances. |
DisposeRoutineDescriptor( modalFilterUPP ); |
} |
if ( anErr != noErr ) |
{ |
ShowCreateFolderError( itemPoint, anErr ); |
} |
return; |
}//end CreateNewFolder |
//****************************************************************************** |
pascal Boolean DialogFilter( DialogPtr inputDialog, EventRecord * dialogEventPtr, short *dialogItemPtr ) |
{ |
Boolean wasAKey; |
Boolean eventWasHandled = false; |
// Get some values here that will be used through-out the filter. |
wasAKey = ( (dialogEventPtr->what == keyDown) || (dialogEventPtr->what == autoKey) ); |
//------------------------------------------------------------------------------ |
// First some filtering on key events. |
//------------------------------------------------------------------------------ |
if ( wasAKey ) |
{ |
eventWasHandled = FilterOutColons( dialogEventPtr->message ); |
if ( eventWasHandled == false ) |
{ |
eventWasHandled = RestrictTextLength( inputDialog, dialogEventPtr, kMaxStringLength, kSFFolderNameTextItem ); |
} |
} |
// One final thing to do is to call the standard dialog filter. |
// You MUST call the standard filter if you want any of the standard dialog |
// behaviors to happen. The OK button border, cursor tracking, and the rest |
// ONLY happen if you call the standard dialog filter. |
// Also, the standard dialog filter is only called if returnValue is still |
// false by the time we get here. |
if ( eventWasHandled == false ) |
{ |
ModalFilterUPP theModalProc; |
OSErr myErr; |
myErr = GetStdFilterProc( &theModalProc ); |
if ( myErr == noErr ) |
{ |
eventWasHandled = CallModalFilterProc( theModalProc, inputDialog, dialogEventPtr, dialogItemPtr ); |
} |
} |
return eventWasHandled; |
}// end DialogFilter |
//****************************************************************************** |
enum { |
kHorzOffsetFromNewFolderDialogPos = 14, |
kVertOffsetFromNewFolderDialogPos = 6 |
}; |
void ShowCreateFolderError( Point dialogPt, OSErr anErr ) |
{ |
DialogPtr dialogPtr; |
Str255 errStr; |
short errStrIndex; |
if ( anErr == dupFNErr ) // That name is already taken |
{ |
errStrIndex = errNameAlreadyTakenStr; |
} |
else // An unexpected error while creating the folder, so give a generic message. |
{ |
errStrIndex = errGenericIOErrorStr; |
} |
dialogPtr = GetNewDialog( kSFFolderNameTaken, nil, (WindowPtr)(-1) ); // -1 means put the dialog in front of all other windows. |
if ( dialogPtr != nil ) |
{ |
Rect itemRect; |
short itemType; |
Handle itemHandle; |
SInt16 hitItem = 0; |
// Set up our user items for various things. |
SetDialogDefaultItem( dialogPtr, ok ); |
SetDialogTracksCursor( dialogPtr, true ); |
// Position the dialog so it's shown in the same location as the name entry dialog was. |
MoveWindow( (WindowPtr)dialogPtr, dialogPt.h - kHorzOffsetFromNewFolderDialogPos, dialogPt.v - kVertOffsetFromNewFolderDialogPos, true ); |
// Put our error message in the dialog's text item. |
GetErrorString( errStr, errStrIndex ); |
GetDialogItem( dialogPtr, kSFNameTakenTextItem, &itemType, &itemHandle, &itemRect ); |
SetDialogItemText( itemHandle, errStr ); |
// Let's see the dialog now. |
ShowWindow( (WindowPtr)dialogPtr ); |
DrawDialog( dialogPtr ); |
ModalDialog( nil, &hitItem ); |
DisposeDialog( dialogPtr ); |
} |
return; |
}//end ShowCreateFolderError |
//****************************************************************************** |
void SetSelectButtonName( DialogPtr dialogPtr, UserDataRecPtr userDataRecPtr ) |
{ |
// Be sure there is a file name selected. |
if ( userDataRecPtr->sfrPtr->sfFile.name[ 0 ] != '\0' ) |
{ |
SetButtonName( dialogPtr, kSelectItem, |
userDataRecPtr->sfrPtr->sfFile.name, kUseQuotes ); |
} |
else // No file name in the reply, so... |
{ |
// Is the desktop selected? |
if ( userDataRecPtr->sfrPtr->sfFile.vRefNum == userDataRecPtr->desktopVRefNum |
&& userDataRecPtr->sfrPtr->sfFile.parID == userDataRecPtr->desktopDirID ) |
{ |
// Set button to "Select Desktop". |
Str63 desktopName; |
GetLabelString( desktopName, kDesktopStrNum ); |
SetButtonName( dialogPtr, kSelectItem, desktopName, kDontUseQuotes ); |
} |
else // Desktop not selected, so there is nothing in the folder selected. |
{ // Get parent directory's name for the Select button. |
// Passing an empty name string to FSMakeFSSpec gets the name of the folder specified by the parID parameter |
FSSpec parentFSS; |
(void) FSMakeFSSpec( userDataRecPtr->sfrPtr->sfFile.vRefNum, |
userDataRecPtr->sfrPtr->sfFile.parID, "\p", &parentFSS ); |
SetButtonName( dialogPtr, kSelectItem, parentFSS.name, kUseQuotes ); |
} |
} |
}//end SetSelectButtonName |
//****************************************************************************** |
// |
// SetButtonName sets the name of the Select button in the SF dialog. |
// |
// To do this, we need to call the Script Manager to truncate the label in the |
// middle to fit the button and to merge the button name with the word Select |
// (possibly followed by quotes). Using the Script Manager avoids all sorts |
// of problems internationally. |
// |
// buttonName is the name to appear following the word Select |
// quoteFlag should be true if the name is to appear in quotes |
// |
void SetButtonName( DialogPtr dialogPtr, short buttonID, StringPtr buttonName, Boolean quoteFlag) |
{ |
short buttonType; |
Handle buttonHandle; |
Rect buttonRect; |
Handle labelHandle; |
Str255 labelStr; |
OSErr anErr; |
// Get the details of the button from the dialog |
GetDialogItem( dialogPtr, buttonID, &buttonType, &buttonHandle, &buttonRect ); |
// Get the string for the select button label, "Select ^0" or "Select Ò^0Ó" |
if ( quoteFlag == kUseQuotes ) |
{ |
GetLabelString( labelStr, kSelectStrNum ); |
} |
else |
{ |
GetLabelString( labelStr, kSelectNoQuoteStrNum ); |
} |
// Make string handles containing the select button label and the |
// file name to be stuffed into the button |
anErr = PtrToHand( &labelStr[1], &labelHandle, labelStr[0] ); |
if ( anErr == noErr ) |
{ |
Handle nameHandle = nil; |
short textWidth; |
// cut out the middle of the file name to fit the button |
// |
// we'll temporarily use labelStr here to hold the modified button name |
// since we don't own the buttonName string storage space |
textWidth = ( buttonRect.right - buttonRect.left ) - StringWidth( labelStr ); |
CopyPStr( buttonName, labelStr ); |
(void) TruncString( textWidth, labelStr, smTruncMiddle ); |
anErr = PtrToHand( &labelStr[1], &nameHandle, labelStr[0] ); |
if ( anErr == noErr ) |
{ |
Str15 keyStr; |
// replace the ^0 in the Select string with the file name |
CopyPStr( "\p^0", keyStr ); |
(void) ReplaceText( labelHandle, nameHandle, keyStr ); |
labelStr[0] = (unsigned char)GetHandleSize( labelHandle ); |
BlockMoveData( *labelHandle, &labelStr[1], labelStr[0] ); |
// now set the control title, and re-validate the area |
// above the control to avoid a needless redraw |
SetControlTitle( (ControlHandle)buttonHandle, labelStr ); |
ValidRect( &buttonRect ); |
DisposeHandle( nameHandle ); |
} |
DisposeHandle( labelHandle ); |
} |
return; |
}//end SetButtonName |
//****************************************************************************** |
void GetLabelString( StringPtr theStr, short stringNum ) |
{ |
GetIndString( theStr, kSelectButtonStrListID, stringNum ); |
} |
//****************************************************************************** |
void GetErrorString( StringPtr theStr, short stringNum ) |
{ |
GetIndString( theStr, kErrorStringListID, stringNum ); |
} |
//****************************************************************************** |
void CopyPStr( StringPtr src, StringPtr dest ) |
{ |
BlockMoveData( src, dest, 1 + src[ 0 ] ); |
} |
//****************************************************************************** |
// |
// FlashButton briefly highlights the dialog button |
// as feedback for key equivalents |
// |
void FlashButton( DialogPtr dialogPtr, short buttonID ) |
{ |
short buttonType; |
Handle buttonHandle; |
Rect buttonRect; |
UInt32 finalTicks; |
GetDialogItem( dialogPtr, buttonID, &buttonType, &buttonHandle, &buttonRect ); |
HiliteControl((ControlHandle) buttonHandle, kControlButtonPart); |
Delay( kButtonHiliteDelay, &finalTicks ); |
HiliteControl((ControlHandle) buttonHandle, 0); |
return; |
}//end FlashButton |
//****************************************************************************** |
// |
void SetButtonEnabled( DialogPtr dialogPtr, short buttonID, Boolean enableButton ) |
{ |
short buttonType; |
Handle buttonHandle; |
Rect buttonRect; |
GetDialogItem( dialogPtr, buttonID, &buttonType, &buttonHandle, &buttonRect ); |
if ( enableButton ) |
{ |
HiliteControl( (ControlHandle)buttonHandle, kControlNoPart ); |
} |
else |
{ |
HiliteControl( (ControlHandle)buttonHandle, kControlInactivePart ); |
} |
return; |
}//end SetButtonEnabled |
//****************************************************************************** |
Boolean SameFSSpec( FSSpecPtr spec1, FSSpecPtr spec2 ) |
{ |
return ( spec1->vRefNum == spec2->vRefNum |
&& spec1->parID == spec2->parID |
&& EqualString( spec1->name, spec2->name, false, false ) ); |
}//end SameFSSpec |
//****************************************************************************** |
// |
// This key filter demonstrates how to disallow certain characters. |
// This particular filter won't allow the entry of numeric characters. |
// Any number entered will be eaten, and a beep generated. |
// |
Boolean FilterOutColons( long message ) |
{ |
char theKeyPressed = message & charCodeMask; |
Boolean keyWasFiltered = false; |
if ( theKeyPressed == ':' ) |
{ |
// Dang, it's a colon, reject it. |
keyWasFiltered = true; |
} |
return keyWasFiltered; |
}// end FilterOutColons |
//************************************************************************************** |
// |
// This key filter restricts the length of text in an edit field. |
// |
// The paste command needs to be handled here, since it can cause the text in the edit |
// field to go over the length limit. |
// This filter also ckecks for and passed through keyboard navigation keys in the case where |
// the edit field is at its maximum length. |
// |
Boolean RestrictTextLength( DialogPtr dialogPtr, EventRecord * theDialogEvent, long maxStringLength, short dialogItem ) |
{ |
Rect itemRect; |
Handle itemHandle; |
Str255 itemStr; |
short itemType; |
short selectionLength; |
char theKeyPressed; |
Boolean keyWasFiltered = false; |
selectionLength = DialogHasSelectionRange( (DialogPeek)dialogPtr ); |
// Get the text from the edit field. |
GetDialogItem( dialogPtr, dialogItem, &itemType, &itemHandle, &itemRect ); |
GetDialogItemText( itemHandle, itemStr ); |
theKeyPressed = theDialogEvent->message & charCodeMask; |
if (theDialogEvent->modifiers & cmdKey) // Was the key pressed a command? |
{ |
if ( (theKeyPressed == 'v') || (theKeyPressed == 'V') ) // Was it a paste command? ( ** NOT LOCALIZED ** ) |
{ |
long offset; |
long scrapLen = GetScrap( nil, 'TEXT', &offset ); |
long newStrLen = itemStr[0] + (scrapLen - selectionLength); |
if ( newStrLen > maxStringLength ) // Yes, will the paste exceed the max length? |
{ |
keyWasFiltered = true; |
} |
} |
} |
else if ( ! KeyIsEditKey( theKeyPressed ) && (itemStr[0] - selectionLength + 1) > maxStringLength ) |
{ |
keyWasFiltered = true; |
} |
return keyWasFiltered; |
}// end RestrictTextLength |
//****************************************************************************** |
short DialogHasSelectionRange( DialogPeek inputDialog ) |
{ |
TEHandle theTERecord; |
short selectionRange; |
theTERecord = inputDialog->textH; |
selectionRange = (*theTERecord)->selEnd - (*theTERecord)->selStart; |
return ( selectionRange ); |
}// end DialogHasSelectionRange |
//****************************************************************************** |
// |
// A little utility to see if the current key is an edit-type key |
// |
// key equates |
enum |
{ |
kEnterKey = 0x03, |
kTabKey = 0x09, |
kReturnKey = 0x0D, |
kBackSpace = 0x08, |
kEscKey = 0x1B, |
kLeftArrow = 0x1C, |
kRightArrow = 0x1D, |
kUpArrow = 0x1E, |
kDownArrow = 0x1F, |
kDeleteKey = 0x7F |
}; |
Boolean KeyIsEditKey( char theKey ) |
{ |
long index; |
Boolean isEditKey = false; |
char editChars[] = { kLeftArrow, kUpArrow, kRightArrow, kDownArrow, kBackSpace, kEscKey, kTabKey, kEnterKey }; |
long editCharCount = sizeof( editChars ) / sizeof( char ); |
for ( index = 0; index < editCharCount; index++ ) |
{ |
if ( theKey == editChars[ index ] ) |
{ |
isEditKey = true; |
} |
} |
return (isEditKey); |
}// end KeyIsEditKey |
//****************************************************************************** |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-03-13