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
 
//******************************************************************************