home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Snippets / StandardGetFolder / StandardGetFolder source / StandardGetFolder.c < prev    next >
Encoding:
Text File  |  1993-08-22  |  26.2 KB  |  746 lines  |  [TEXT/KAHL]

  1. /*************************************************************************************************
  2.  
  3. StandardGetFolder.c -- Copyright Chris Larson, 1993 -- All rights reserved.
  4.                        Based partly upon StandardGetFolder example by Steve Falkenburg (MacDTS)
  5.                        and partly on the code in Inside Macintosh: Files.
  6.                        
  7.                        Dialog box layouts taken from Inside Macintosh: Files.
  8.                        
  9.   To Do:    * Better method of replacing Pop-up directory hierarchy help strings.
  10.             * Don’t show the temporary items folder(s).
  11.             * Allow selection of folder with only write privileges.
  12.  
  13.     Bug Reports/Comments to cklarson@engr.ucdavis.edu
  14.  
  15.     Version 1.0
  16.  
  17. *************************************************************************************************/
  18.  
  19. // ----------
  20. // Define this symbol to exclude various unneeded glue routines.
  21. // ----------
  22.  
  23. #ifdef SystemSevenOrLater
  24. #undef SystemSevenOrLater
  25. #endif
  26. #define SystemSevenOrLater 1
  27.  
  28. // ----------
  29. // Include Files
  30. // ----------
  31.  
  32. #include "StandardGetFolder.h"
  33. #include "SGF.h"
  34.  
  35. // ----------
  36. // Global Variables
  37. // ----------
  38.  
  39. static StringHandle                gLeftString;        // Left portion of the select button title.
  40. static StringHandle                gRightString;        // Right portion of the select button title.
  41. static StringHandle                gDesktopName;        // Replacement name for the desktop folder.
  42. static short                    gDeskVRefNum;        // Volume reference number of the desktop folder.
  43. static long                        gDeskDirID;            // Directory ID of the desktop folder.
  44. static short                    gSaveSFSaveDisk;    // Temporarily holds the value of SFSaveDisk.
  45. static long                        gSaveCurDirStore;    // Temporarily holds the value of CurDirStore.
  46. static char                        gSelectKey;            // Holds the character code of the select key.
  47. static Str255                    gPreviousName;        // Holds the name last used in the button title.
  48. static StandardFolderReplyPtr    gFolderReply;        // Pointer to the standard folder reply.
  49. static WindowFunc                gWindowProc;        // Application window event handler function.
  50. static long                        gTrapAddress;        // Holds the true address of _GetResource.
  51. static RgnHandle                gSaveRgn;            // Temporarily saves the main dialog’s ClipRgn.
  52. static Boolean                    gSelectButtonHit;    // Indicates that select button was just hit.
  53. static Boolean                    gCompareNames;        // Flag indicating the need to reexamine the
  54.                                                     //    current title of the select button.
  55.  
  56. /****************************************************************************************************
  57.  
  58. StandardGetFolder -- The 'main' routine
  59.  
  60. ****************************************************************************************************/
  61.  
  62. OSErr StandardGetFolder (Str255 prompt, StandardFolderReplyPtr reply, ProcPtr windowProc)
  63. {    
  64.     StringHandle         selectKeyString;
  65.     Point                location = {-1,-1};
  66.     StandardFileReply    newReply;
  67.  
  68.     // ----------
  69.     // Initialize some variables.
  70.     // ----------
  71.  
  72.     reply->sfGood = false;
  73.     gWindowProc = windowProc;
  74.     gCompareNames = true;
  75.     gSelectButtonHit = false;
  76.     gFolderReply = reply;
  77.     gSaveRgn = NULL;
  78.     newReply.sfIsVolume = 0;
  79.     newReply.sfIsFolder = 0;
  80.     newReply.sfFlags = 0;
  81.  
  82.     // ----------
  83.     // Find and store the information about the desktop folder. Assume an error by FindFolder implies
  84.     // that the desktop folder does not exist.
  85.     // ----------
  86.  
  87.     if (FindFolder(kOnSystemDisk,kDesktopFolderType,kDontCreateFolder,&gDeskVRefNum,
  88.             &gDeskDirID) != noErr)
  89.         gDeskVRefNum = kNullVol;
  90.  
  91.     // ----------
  92.     // Load and store the select key character code. If an error occurs, return the error code.
  93.     // ----------
  94.  
  95.     if ((selectKeyString = GetString(rSelectKeyStringID)) != NULL)
  96.         {
  97.         gSelectKey = (*selectKeyString)[1];
  98.         ReleaseResource ((Handle)selectKeyString);
  99.         }
  100.     else
  101.         return (ResErr);
  102.  
  103.     // ----------
  104.     // Load the replacement title for the desktop folder. If an error occurs, return the error code.
  105.     // ----------
  106.  
  107.     if ((gDesktopName = GetString(rDesktopNameStringID)) == NULL)
  108.         return (ResErr);
  109.  
  110.     // ----------
  111.     // Load the wrapper strings for the select button title. If an error occurs, return the error
  112.     // code.
  113.     // ----------
  114.  
  115.     if ((gLeftString = GetString(rLeftStringID)) == NULL)
  116.         return (ResErr);
  117.  
  118.     if ((gRightString = GetString(rRightStringID)) == NULL)
  119.         return (ResErr);
  120.  
  121.     // ----------
  122.     // Lock the strings. This is necessary since StringWidth() may move memory and I pass it
  123.     // dereferenced handles. The strings are locked after all are successfully loaded to prevent
  124.     // orphaning a locked block high in the heap if an error were to occur while loading the second
  125.     // or third string.
  126.     // ----------
  127.  
  128.     HLockHi ((Handle)gDesktopName);
  129.     HLockHi ((Handle)gLeftString);
  130.     HLockHi ((Handle)gRightString);
  131.  
  132.     // ----------
  133.     // If we were given a prompt, store it in gPreviousName (on entry to CustomGetFile, there will be
  134.     // no previous name so this space is usable).
  135.     // ----------
  136.  
  137.     if (prompt)
  138.         PStringCopy (gPreviousName,prompt);
  139.     else
  140.         *gPreviousName = 0;
  141.  
  142.     // ----------
  143.     // Install the patch on _GetResource to allow our replacement help strings for the pop-up
  144.     // directory hierarchy to be used (** If anyone knows of a better way to replace these strings I
  145.     // would appreciate hearing about it **).
  146.     // ----------
  147.  
  148.     gTrapAddress = GetToolTrapAddress(_GetResource);
  149.     SetToolTrapAddress((long)Patch,_GetResource);
  150.  
  151.     // ----------
  152.     // Call CustomGetFile with the appropriate parameters.
  153.     // ----------
  154.  
  155.     CustomGetFile((ProcPtr)&GetFolderFileFilterYD,kAllFiles,NULL,&newReply,rGetFolderDialogID,
  156.                     location,(ProcPtr)&GetFolderDialogHookYD,(ProcPtr)&GetFolderModalFilterYD,
  157.                     NULL,NULL,&newReply);
  158.  
  159.     // ----------
  160.     // Remove the trap patch immediately after exiting CustomGetFile.
  161.     // ----------
  162.  
  163.     SetToolTrapAddress(gTrapAddress,_GetResource);
  164.  
  165.     // ----------
  166.     // Get rid of the strings.
  167.     // ----------
  168.  
  169.     ReleaseResource((Handle)gDesktopName);
  170.     ReleaseResource((Handle)gLeftString);
  171.     ReleaseResource((Handle)gRightString);
  172.  
  173.     // ----------
  174.     // Everything’s OK, so return noErr.
  175.     // ----------
  176.  
  177.     return (noErr);
  178. }
  179.  
  180. /****************************************************************************************************
  181.  
  182. FileFilter -- Return false if the file is to be displayed (i.e. its directory bit is set and its
  183.               invisible bit is not set).
  184.  
  185. ****************************************************************************************************/
  186.  
  187. pascal Boolean GetFolderFileFilterYD (CInfoPBPtr paramBlock, StandardFileReply *myData)
  188. {
  189.     return(!((((paramBlock->hFileInfo).ioFlAttrib)&kIsFolderFlag)&&
  190.             (!(((paramBlock->dirInfo).ioDrUsrWds.frFlags)&kInvisibleFlag))));
  191. }
  192.  
  193. /****************************************************************************************************
  194.  
  195. ModalFilter -- Handle update and activate events for standard file dialogs, pass update and activate
  196.                events for application windows on to the application (via the function pointer passed
  197.                to StandardGetFolder) if a routine is given. Also, convert Command - Select Key to a
  198.                press of the select button and flag mouse clicks within the file list to cause the
  199.                select button to correct the name it displays.
  200.                
  201. ****************************************************************************************************/
  202.  
  203. pascal Boolean GetFolderModalFilterYD (DialogPtr theDialog, EventRecord *theEvent, short *itemHit,
  204.                                         StandardFileReply *myData)
  205. {
  206.     Boolean        result = false;        // Initialize to indicate that the event was not handled.
  207.     short        itemType;
  208.     Handle        itemHandle;
  209.     Rect        itemRect;
  210.     Point        localPoint;
  211.     GrafPtr        savePort;
  212.  
  213.     // ----------
  214.     // Start with activate events. These must be processed no matter which dialog is in front, so this
  215.     // block comes before the check to see which dialog is in front. Note that no matter to what
  216.     // degree the activate event is handled, the modal filter must return false (as per IM: Macintosh
  217.     // Toolbox Essentials).
  218.     // ----------
  219.  
  220.     if (theEvent->what == activateEvt)
  221.         {
  222.         
  223.         // ----------
  224.         // If the main dialog is the target of the activate event, the select button must be either
  225.         // activated or dimmed according to whether the dialog is being activated or deactivated.
  226.         // ----------
  227.  
  228.         if (((WindowPeek)(theEvent->message))->refCon == sfMainDialogRefCon)
  229.             {
  230.             GetDItem ((DialogPtr)(theEvent->message),sfItemSelectFolderButton,&itemType,&itemHandle,
  231.                     &itemRect);
  232.             if ((theEvent->modifiers)&activeFlag)
  233.             
  234.                 // ----------
  235.                 // We are being activated, so un-dim the select button.
  236.                 // ----------
  237.  
  238.                 HiliteControl((ControlHandle)itemHandle,kNormal);
  239.             else
  240.                 {
  241.                 
  242.                 // ----------
  243.                 // The selection cannot change while the dialog is deactivated, so flag the select
  244.                 // button title as not needing examination.
  245.                 // ----------
  246.  
  247.                 gCompareNames = false;
  248.  
  249.                 // ----------
  250.                 // If the main dialog is being deactivated and the select button was just hit,
  251.                 // an error must have ocurred while trying to open the selected item. Therefore,
  252.                 // clear the select button press, restore the main dialog’s clip region and allow
  253.                 // standard file to display the error dialog. After the user dismisses the error
  254.                 // dialog, processing will continue as if the select button was never pressed.
  255.                 // ----------
  256.  
  257.                 if (gSelectButtonHit)
  258.                     {
  259.                     gSelectButtonHit = false;
  260.                     GetPort(&savePort);
  261.                     SetPort((DialogPtr)(theEvent->message));
  262.                     SetClip(gSaveRgn);
  263.                     DisposeRgn(gSaveRgn);
  264.                     gSaveRgn = NULL;
  265.                     SetPort(savePort);
  266.                     }
  267.  
  268.                 // ----------
  269.                 // We are being deactivated, so dim the select button.
  270.                 // ----------
  271.  
  272.                 HiliteControl((ControlHandle)itemHandle,kInactive);
  273.                 }
  274.             }
  275.  
  276.         // ----------
  277.         // If the main dialog is not the target of the activate event, check to see if the error
  278.         // dialog is the target. If the error dialog is the target, I have nothing to do. If the
  279.         // error dialog is not the target, then the target must be an application window (since the
  280.         // error dialog and the main dialog are the only two dialogs StandardGetFolder will ever
  281.         // present) so call the application’s window event handler.
  282.         // ----------
  283.  
  284.         else if (((WindowPeek)(theEvent->message))->refCon != sfErrorDialogRefCon)
  285.             CallAppWindowFunction(theEvent);
  286.         }
  287.  
  288.     // ----------
  289.     // Now Update events. Much like activate events, these must be handled no matter which dialog is
  290.     // in front and the modal filter must return false (again, as per IM: Mac Toolbox Essentials).
  291.     // ----------
  292.  
  293.     if (theEvent->what == updateEvt)
  294.         {
  295.         
  296.         // ----------
  297.         // If the target of the update event is the dialog currently in front, standard file will
  298.         // handle the event correctly, so return false immediately.
  299.         // ----------
  300.  
  301.         if ((DialogPtr)(theEvent->message) == theDialog)
  302.             return (false);
  303.         
  304.         // ----------
  305.         // If the target of the update event is not the frontmost dialog, check to see if the target
  306.         // is the main dialog. Since standard file will not update its own dialog boxes correctly if
  307.         // they are not the frontmost window (Grrrr…), I must update the dialog myself. Note that the
  308.         // only case where one standard file dialog is obscured by another is when the main dialog is
  309.         // behind the error dialog. Thus this case is detectable simply by checking to see if the
  310.         // target is the main dialog. (If the main dialog were the front dialog, the previous check
  311.         // would have returned false, so implicit in this is that the main dialog is not in front.)
  312.         // ----------
  313.  
  314.         if (((WindowPeek)(theEvent->message))->refCon == sfMainDialogRefCon)
  315.             {
  316.             BeginUpdate ((DialogPtr)(theEvent->message));
  317.             UpdateDialog((DialogPtr)(theEvent->message),((WindowPtr)(theEvent->message))->visRgn);
  318.             EndUpdate((DialogPtr)(theEvent->message));
  319.             return (false);
  320.             }
  321.         
  322.         // ----------
  323.         // If the target is neither the frontmost dialog nor the main dialog, it must be a window
  324.         // belonging to the application, so call the application’s window event handler.
  325.         // ----------
  326.  
  327.         CallAppWindowFunction(theEvent);
  328.         }
  329.  
  330.     // ----------
  331.     // The remaining checks are only relevant if the main dialog is the frontmost, so return if the
  332.     // frontmost dialog is not the main dialog.
  333.     // ----------
  334.  
  335.     if (GetWRefCon((WindowPtr)theDialog) != sfMainDialogRefCon)
  336.         return (false);
  337.  
  338.     // ----------
  339.     // Check to see if the select key was pressed while the command key was held down. If so, fake a
  340.     // press of the select button and return true, indicating that the event was handled.
  341.     // ----------
  342.  
  343.     if ((theEvent->what == keyDown)||(theEvent->what == autoKey))
  344.         {
  345.         if (((theEvent->modifiers)&cmdKey)&&(((theEvent->message)&charCodeMask) == gSelectKey))
  346.             {
  347.             *itemHit = sfItemSelectFolderButton;
  348.             HitButton (theDialog,sfItemSelectFolderButton);
  349.             result = true;
  350.             }
  351.         }
  352.  
  353.     // ----------
  354.     // Since pressing the mouse in an empty area of the file list changes the file selection (changes
  355.     // it to no selection) but is passed to the dialog hook as a null event (Grrrr… part 2) I must
  356.     // flag all clicks within the file list to cause examination of the select button title.
  357.     // ----------
  358.  
  359.     if (theEvent->what == mouseDown)
  360.         {
  361.         localPoint = theEvent->where;
  362.         GlobalToLocal (&localPoint);
  363.         GetDItem (theDialog,sfItemFileListUser,&itemType,&itemHandle,&itemRect);
  364.         if (PtInRect(localPoint,&itemRect))
  365.             gCompareNames = true;
  366.         }
  367.  
  368.     return (result);
  369. }
  370.  
  371. /****************************************************************************************************
  372.  
  373. DialogHook -- Set the select button title as appropriate, manage presses of the select button.
  374.  
  375. ****************************************************************************************************/
  376.  
  377. pascal short GetFolderDialogHookYD (short itemHit, DialogPtr theDialog, StandardFileReply *myData)
  378. {    
  379.     CInfoPBRec    paramBlock;
  380.     Str255        dirName;
  381.     short        itemType;
  382.     Handle        itemHandle;
  383.     Rect        itemRect;
  384.     GrafPtr        savePort;
  385.     RgnHandle    nullRgn;
  386.  
  387.     // ----------
  388.     // The dialog hook should only run if the main dialog is in front, so exit immediately if that is
  389.     // not the case.
  390.     // ----------
  391.  
  392.     if (GetWRefCon((WindowPtr)theDialog) != sfMainDialogRefCon)
  393.         return (itemHit);
  394.  
  395.     // ----------
  396.     // Repair what I consider to be a bug in standard file: if the file list is displaying the desktop
  397.     // and the last thing that was selected (whether it is still selected or there is no selection) is
  398.     // a volume other than the startup volume, the lo-mem globals hold the information describing the
  399.     // root directory of the last selected volume _not_ the information describing the desktop folder,
  400.     // which is the displayed directory. This bug can cause incorrect behavior: when a standard file
  401.     // dialog is dismissed and immediately recalled, the second dialog should open displaying the same
  402.     // directory as the first did when dismissed. Try this on your favorite standard file application:
  403.     // call up the dialog, insert a floppy, go to the desktop and highlight the floppy. Then cancel
  404.     // the dialog and immediately call it up again, *bam* it opens displaying the root directory of
  405.     // the floppy _not_ the desktop (as it should). Thus, in an attempt to remedy this behavior (and
  406.     // to make my module work correctly, since it relies on the values of the lo-mem globals) I reset
  407.     // the globals to describe the desktop whenever the desktop button is dimmed.
  408.     // ----------
  409.     
  410.     GetDItem (theDialog,sfItemDesktopButton,&itemType,&itemHandle,&itemRect);
  411.     if ((**((ControlHandle)itemHandle)).contrlHilite == kInactive)
  412.         {
  413.         SFSaveDisk = -gDeskVRefNum;
  414.         CurDirStore = gDeskDirID;
  415.         }
  416.     
  417.     // ----------
  418.     // If the select button was just pressed (and not cleared by the modal filter), the selected
  419.     // item was opened without an error, so it is a valid selection. Clear the select button press
  420.     // (so that the DisposeRgn() call is only made once), release the saved region, fill in the
  421.     // reply record, restore the contents of the lo-mem globals, and fake a press of the cancel
  422.     // button.
  423.     // ----------
  424.     
  425.     if (gSelectButtonHit)
  426.         {
  427.         gSelectButtonHit = false;
  428.         DisposeRgn(gSaveRgn);
  429.         gFolderReply->sfVRefNum = -SFSaveDisk;
  430.         gFolderReply->sfDirID = CurDirStore;
  431.         gFolderReply->sfGood = true;
  432.         
  433.         SFSaveDisk = gSaveSFSaveDisk;
  434.         CurDirStore = gSaveCurDirStore;
  435.         
  436.         return (sfItemCancelButton);
  437.         }
  438.     
  439.     // ----------
  440.     // Process events normally.
  441.     // ----------
  442.  
  443.     switch (itemHit)
  444.         {
  445.         
  446.         // ----------
  447.         // If the select button was pressed, force the use of standard file’s error checking.
  448.         // By instructing standard file to open the selected item, I can make use of the built-in
  449.         // error mechanism of the standard file package and therefore achieve two goals: avoid
  450.         // having to write one myself, and maintain absolute consistency over error messages.
  451.         // This method does involve a little trickery however: since I force standard file to open
  452.         // the selected item, the appearance of the main dialog would change in response to the
  453.         // new directory information and the lo-memory globals will be changed to describe the
  454.         // just-opened directory. No problem. To ensure that the display of the main dialog does not
  455.         // change I save off its clip region (in case an error occurs and it must be restored) and
  456.         // set the clip region to the empty region -- no drawing will occur. To make sure that the
  457.         // lo-mem globals will describe the last seen directory (not necessarily the selected one)
  458.         // upon exit of this routine (so that the next standard file dialog will open in the
  459.         // directory last displayed by this one, barring munging by SuperBoomerang, etc.), I save 
  460.         // off and restore their values.
  461.         // ----------
  462.  
  463.         case sfItemSelectFolderButton:
  464.  
  465.             gSelectButtonHit = true;
  466.             GetPort(&savePort);
  467.             SetPort(theDialog);
  468.             
  469.             gSaveRgn = NewRgn();
  470.             nullRgn = NewRgn();
  471.             
  472.             GetClip(gSaveRgn);
  473.             SetClip(nullRgn);
  474.             
  475.             DisposeRgn(nullRgn);
  476.             
  477.             SetPort(savePort);
  478.  
  479.             gSaveSFSaveDisk = SFSaveDisk;
  480.             gSaveCurDirStore = CurDirStore;
  481.  
  482.             // ----------
  483.             // If the selected item is a folder, volume, or alias, open it. If there is no selection,
  484.             // the selected directory is already displayed, so do nothing.
  485.             // ----------
  486.  
  487.             if ( (myData->sfIsFolder) || (myData->sfIsVolume) )
  488.                 return (sfHookOpenFolder);
  489.             else if ( (myData->sfFlags) & kIsAliasFlag)
  490.                 return (sfHookOpenAlias);
  491.             else
  492.                 return (sfHookNullEvent);
  493.  
  494.         // ----------
  495.         // If this is the first time through, set the prompt to the given text, if any was given.
  496.         // ----------
  497.  
  498.         case sfHookFirstCall:
  499.             if (*gPreviousName != 0)
  500.                 {
  501.                 GetDItem(theDialog,sfItemPromptStatText,&itemType,&itemHandle,&itemRect);
  502.                 SetIText(itemHandle,gPreviousName);
  503.                 *gPreviousName = 0;
  504.                 }
  505.  
  506.         // ----------
  507.         // If this is a null event and the select button title does not need to be examined, exit the
  508.         // switch statement.
  509.         // ----------
  510.  
  511.         case sfHookNullEvent:
  512.             if (!gCompareNames)
  513.                 break;
  514.  
  515.         // ----------
  516.         // Otherwise, the select button title needs to be examined (either the event is not a null
  517.         // event or we have flagged the title as needing examination).
  518.         // ----------
  519.  
  520.         default:
  521.         
  522.             // ----------
  523.             // If the selection is an alias, a folder, or a volume, use the name of the selected
  524.             // item.
  525.             // ----------
  526.  
  527.             if ((myData->sfIsFolder)||(myData->sfIsVolume)||((myData->sfFlags)&kIsAliasFlag))
  528.                 PStringCopy (dirName,(myData->sfFile).name);
  529.         
  530.             // ----------
  531.             // Otherwise, use the name of the currently displayed directory.
  532.             // ----------
  533.             
  534.             else
  535.                 {
  536.                 
  537.                 // ----------
  538.                 // If the currently displayed directory is the desktop, substitute our string for the
  539.                 // name of the desktop folder.
  540.                 // ----------
  541.     
  542.                 if ((gDeskVRefNum == -SFSaveDisk)&&(gDeskDirID == CurDirStore))
  543.                     PStringCopy(dirName,*gDesktopName);
  544.                 
  545.                 // ----------
  546.                 // Otherwise, query the volume catalog to get the name of the currently displayed
  547.                 // directory.
  548.                 // ----------
  549.  
  550.                 else
  551.                     {
  552.                     paramBlock.dirInfo.ioCompletion = NULL;
  553.                     paramBlock.dirInfo.ioNamePtr = dirName;
  554.                     paramBlock.dirInfo.ioFDirIndex = -1;
  555.                     paramBlock.dirInfo.ioVRefNum = -SFSaveDisk;
  556.                     paramBlock.dirInfo.ioDrDirID = CurDirStore;
  557.                     
  558.                     if (PBGetCatInfoSync(¶mBlock) != noErr)
  559.                         return(itemHit);
  560.                     }
  561.                 }
  562.  
  563.             // ----------
  564.             // Compare the current name with the name last placed into the select button. If they
  565.             // differ, set the select button title to reflect the current name. EqualString() is
  566.             // used to perform the name comparison since it follows the comparison rules of the
  567.             // Macintosh file system.
  568.             // ----------
  569.  
  570.             if (EqualString(dirName,gPreviousName,false,false) == false)
  571.                 {
  572.                 GetDItem(theDialog,sfItemSelectFolderButton,&itemType,&itemHandle,&itemRect);
  573.                 SetButtonTitle((ControlHandle)itemHandle,dirName,&itemRect,theDialog);
  574.                 }
  575.  
  576.             // ----------
  577.             // If this was not a null event, flag the select button title as needing examination
  578.             // (since the selection can change after a call to the dialog hook with a non-null event
  579.             // this will force those cases to be caught).
  580.             // ----------
  581.  
  582.             gCompareNames = (itemHit != sfHookNullEvent);
  583.             
  584.             // ----------
  585.             // If a press of the open button (meaning open a file -- opens associated with aliases or
  586.             // directories are mapped to pseudo-items) happens to creep through (yes, it’s possible:
  587.             // it can happen if return or enter is pressed soon after inserting a floppy when there is
  588.             // a folder or alias highlighted prior to inserting the floppy) invalidate it by mapping it
  589.             // to a null event.
  590.             // ----------
  591.             
  592.             if (itemHit == sfItemOpenButton)
  593.                 itemHit = sfHookNullEvent;
  594.         }
  595.  
  596.     return (itemHit);
  597. }
  598.  
  599. /****************************************************************************************************
  600.  
  601. CallAppWindowFunction -- Passes an event to the application’s window function, if one was given. If
  602.                          one was not given, the event is mapped to a null event.
  603.  
  604. ****************************************************************************************************/
  605.  
  606. void CallAppWindowFunction (EventRecord *theEvent)
  607. {
  608.     GrafPtr    savePort;
  609.     
  610.     if (gWindowProc != NULL)
  611.             {
  612.             
  613.             // ----------
  614.             // Save and restore the current port in case the application leaves the port set to an
  615.             // application window.
  616.             // ----------
  617.  
  618.             GetPort(&savePort);
  619.             (*gWindowProc)(theEvent);
  620.             SetPort(savePort);
  621.             }
  622.     else 
  623.         theEvent->what = nullEvent;
  624. }
  625.  
  626. /****************************************************************************************************
  627.  
  628. SetButtonTitle -- Sets the given button's title to the concatenation (gLeftString + newTitle +
  629.                   gRightString) where the newTitle string will be end-truncated to fit the
  630.                   entire concatenation within the button's rectangle. End-truncation is used to
  631.                   match the behavior of the pop-up menu atop the main standard file dialog.
  632.  
  633. ****************************************************************************************************/
  634.  
  635. void SetButtonTitle (ControlHandle theButton, Str255 newTitle, Rect *buttonRect, DialogPtr theDialog)
  636. {
  637.     Str255        buttonTitle;
  638.     short        width;
  639.  
  640.     // ----------
  641.     // Place the name we are changing to into our comparison buffer
  642.     // ----------
  643.  
  644.     PStringCopy (gPreviousName,newTitle);
  645.  
  646.     // ----------
  647.     // Set width to the width of the select button less the lengths of the wrapper strings and less
  648.     // a tad more (the title offset) to provide a little leeway.
  649.     // ----------
  650.  
  651.     width = buttonRect->right - buttonRect->left - StringWidth(*gLeftString) -
  652.             StringWidth (*gRightString) - kTitleOffset;
  653.  
  654.     // ----------
  655.     // End-truncate the string
  656.     // ----------
  657.  
  658.     TruncString(width,newTitle,smTruncEnd);
  659.     
  660.     // ----------
  661.     // Only set a new title if the truncated filename has at least one character (yes it is possible
  662.     // that this routine gets passed a null filename -- one instance in which it happens is right
  663.     // after inserting a floppy, for one pass, standard file thinks the floppy is a volume with the
  664.     // null string for a title).
  665.     // ----------
  666.     
  667.     if (*newTitle != 0)
  668.         {
  669.  
  670.         // ----------
  671.         // Perform the concatenation.
  672.         // ----------
  673.     
  674.         PStringCopy (buttonTitle,*gLeftString);
  675.         PStringCat (buttonTitle,newTitle);
  676.         PStringCat (buttonTitle,*gRightString);
  677.     
  678.         // ----------
  679.         // Set the button’s title (note that SetCTitle causes the control to be drawn and then causes
  680.         // the control’s rectangle to be marked invalid so immediately after changing the title, I
  681.         // validate the rectangle to prevent the control from being drawn twice.
  682.         // ----------
  683.     
  684.         SetCTitle (theButton,buttonTitle);
  685.         SetPort (theDialog);
  686.         ValidRect (buttonRect);
  687.         }
  688. }
  689.  
  690. /****************************************************************************************************
  691.  
  692. HitButton -- Fake a button press by inverting the button for a given time interval.
  693.  
  694. ****************************************************************************************************/
  695.  
  696. void HitButton (DialogPtr theDialog, short itemNumber)
  697. {
  698.     short    itemType;
  699.     Handle    buttonHandle;
  700.     Rect    buttonRect;
  701.     long    ignored;
  702.  
  703.     GetDItem (theDialog,itemNumber,&itemType,&buttonHandle,&buttonRect);
  704.     HiliteControl ((ControlHandle)buttonHandle,kInverted);
  705.     Delay (kHitButtonDelay,&ignored);
  706.     HiliteControl ((ControlHandle)buttonHandle,kNormal);
  707. }
  708.  
  709. /****************************************************************************************************
  710.  
  711. Patch -- Allows the use of our replacement help strings for the pop-up directory list. In order to
  712.          replace these help strings, _GetResource must be patched (** once again, if you know of a
  713.          better way I’d love to hear it **). This patch is installed immediately before the call to
  714.          CustomGetFile and removed immediately after. It simply watches for attempts to get a handle
  715.          to the default help strings and alters the call to get the replacement strings.
  716.  
  717. ****************************************************************************************************/
  718.  
  719. pascal Handle Patch (ResType type, short id)
  720. {    
  721.     // ----------
  722.     // If the default string resource is being requested, alter the request to return our replacement
  723.     // strings.
  724.     // ----------
  725.  
  726.     if ((type == 'STR#')&&(id == kExistingPopUpStrings))
  727.         id = kNewPopUpStrings;
  728.  
  729.     // ----------
  730.     // When we are called, A5 may not be valid. Since patching a trap from an application only effects
  731.     // the application, we can assume that CurrentA5 will hold the address of our A5 world. We need
  732.     // access to the A5 world to get at the correct address of _GetResource, since it is stored as a
  733.     // global variable and global variables are accessed as offsets from A5.
  734.     // ----------
  735.  
  736.     asm 68000
  737.         {
  738.         move.l    a5,-(a7)        ; push the value of a5
  739.         move.l    CurrentA5,a5    ; load the address of our a5 world
  740.         move.l    gTrapAddress,a0    ; load the address of _GetResource into a0
  741.         move.l    (a7)+,a5        ; restore the value of a5
  742.         unlk    a6                ; remove the a6 link
  743.         jmp        (a0)            ; jump to _GetResoruce
  744.         }
  745. }
  746.