home *** CD-ROM | disk | FTP | other *** search
/ Otherware / Otherware_1_SB_Development.iso / mac / developm / scnote / stdfile.018 / StdFile.c < prev    next >
Encoding:
C/C++ Source or Header  |  1989-04-02  |  61.7 KB  |  1,929 lines

  1. /*------------------------------------------------------------------------------
  2. #
  3. #    Apple Macintosh Developer Technical Support
  4. #
  5. #    Standard File Sample Application
  6. #
  7. #    StdFile
  8. #
  9. #    StdFile.c    -    C Source
  10. #
  11. #    Copyright ⌐ 1989 Apple Computer, Inc.
  12. #    All rights reserved.
  13. #
  14. #    Versions:    
  15. #                1.00                04/89
  16. #
  17. #    Components:
  18. #                StdFile.c            April 1, 1989
  19. #                StdFile.p            April 1, 1989
  20. #                StdFile.h            April 1, 1988
  21. #                StdFile.r            April 1, 1988
  22. #                StdFile.rsrc        April 1, 1988
  23. #                CStdFile.make        April 1, 1989
  24. #                PStdFile.make        April 1, 1988
  25. #
  26.  
  27. OBJECTIVES
  28. ----------
  29. This program attempts to demonstrate the following techniques:
  30.  
  31.     - Normal use of SFGetFile and SFPutFile
  32.     - Normal use of SFPGetFile and SFPPutFile. This includes use of Custom
  33.         Dialogs with handling of extra items through the implementation of a
  34.         DlgHook.
  35.             - First time initialization
  36.             - Extra Simple buttons (Quit/Directory/ThisDir)
  37.             - Radio buttons (file format, types of files to show)
  38.             - Aliasing click on some buttons to clicks on other buttons
  39.             - Regenerating the list of files displayed
  40.     - Creating a full pathname from the reply record (using Working Directories
  41.         or DirID)
  42.     - Selecting a directory (ala MPW's "GetFileName -d")
  43.     - Simple file filter (checks file type)
  44.     - Complex file filter (looking inside the file)
  45.     - Adding and deleting and extra List Manager lists. This is shown for both
  46.         SFGetFile and SFPutFile.
  47.     - Select multiple files using one of two methods
  48.         - Replace SF's list with one of your own
  49.         - Add a second list to the Dialog Box. This method is not shown
  50.             explicitly. Rather, I show how to install and dispose of the actual
  51.             list item. Inserting filenames into the list is left as an exercise
  52.             to you, the Programmer.
  53.     - Setting the starting Directory/Volume
  54.     - Describe pending update event clogging
  55.  
  56.  
  57. NOTES
  58. -----
  59.         - This application assumes existance of HFS. It makes HFS calls and
  60.             accesses HFS data structures without first checking to see if HFS
  61.             exists on this machine.
  62.  
  63. ------------------------------------------------------------------------------*/
  64.  
  65. #include <Types.h>
  66. #include <QuickDraw.h>
  67. #include <ToolUtils.h>
  68. #include <Events.h>
  69. #include <Controls.h>
  70. #include <Windows.h>
  71. #include <Dialogs.h>
  72. #include <Menus.h>
  73. #include <Desk.h>
  74. #include <SegLoad.h>
  75. #include <Files.h>
  76. #include <OSEvents.h>
  77. #include <Traps.h>
  78. #include <Fonts.h>
  79. #include <OSUtils.h>
  80. #include <Resources.h>
  81. #include <Memory.h>
  82. #include <Packages.h>
  83. #include <Lists.h>
  84. #include <StdFile.h>
  85.  
  86. typedef struct StringHolder {
  87.     struct StringHolder    **link;
  88.     Str255    title;
  89. } StringHolder, *StringHolderPtr, **StringHolderHdl;
  90.  
  91.  
  92. #define SFSaveDisk        0x214        /* Negative of current volume refnum [WORD]    */
  93. #define CurDirStore        0x398        /* DirID of current directory [LONG]        */
  94. #define ResLoad            0xA5E        /* Boolean ResLoad setting [WORD]            */
  95. #define TopMapHndl        0xA50        /* 1st map in resource list [Handle]        */
  96.  
  97.  
  98. #define firstTime        -1  /* the first time our hook is called, it is passed a -1 */
  99.  
  100. #define reDrawList        101 /* returning 101 as item number will cause the FILE list
  101.                                 to be recalculated */
  102.  
  103. #define BTNON            1   /* Control value for on */
  104. #define BTNOFF            0   /* Control value for off */
  105.  
  106. #define HWCfgFlags        0xB22    /* Low memory global used to check if A/UX is running */
  107.  
  108.  
  109. Boolean            doneFlag;        /* set to TRUE when time to quit */
  110. SysEnvRec        theWorld;
  111. Boolean            gHaveAUX;        /* set to TRUE if running under A/UX */
  112. EventRecord        myEvent;
  113. WindowPtr        whichWindow;
  114. Boolean            WNEIsImplemented;
  115. SFReply            reply;            /* used in all SF samples */
  116. SFTypeList        typeList;        /* typelist for all SF samples */
  117. OSErr            err;            /* used in all OS calls */
  118. Point            gLocation = {0x40,0x40};
  119. Point            gZeroPt = {0,0};
  120.  
  121. /* for normal SFPPutFile sample */
  122. Boolean            textOnly;
  123. short            MySaveDisk;
  124. Rect            freeItemBox;
  125. Str255            myStr, myStr2;
  126.  
  127. /* for SetDirectory sample */
  128. long            MyCurDir;
  129. Boolean            CurDirValid;
  130.  
  131. /* for MultiFile and ListsNPut samples */
  132. ListHandle        LHandle;
  133.  
  134. /* for PutOptions sample */
  135. short            OptionNumber;
  136. DialogPtr        OptionsDPtr;
  137.  
  138. /* for MultiFile */
  139. StringHolderHdl    firstName;
  140. Boolean            namesChanged;
  141. Boolean            needToUpdate;
  142.  
  143.  
  144.  
  145. /* Define HiWrd and LoWrd macros for efficiency. */
  146.  
  147. #define HiWrd(aLong)    (((aLong) >> 16) & 0xFFFF)
  148. #define LoWrd(aLong)    ((aLong) & 0xFFFF)
  149.  
  150. /* Define TopLeft and BotRight macros for convenience. Notice the implicit */
  151. /* dependency on the ordering of fields within a Rect */
  152.  
  153. #define TopLeft(aRect)    (* (Point *) &(aRect).top)
  154. #define BotRight(aRect)    (* (Point *) &(aRect).bottom)
  155.  
  156. /* _DataInit() is part of the MPW runtime library. This external */
  157. /* reference to it is done so that we can unload its segment, %A5Init. */
  158.  
  159. extern void _DataInit();
  160.  
  161. void Initialize(void);
  162. void EventLoop(void);
  163. void DoCommand(long mResult);
  164. void SetRadioButton(DialogPtr dialog,short item,short state);
  165. void ShowAboutMeDialog(void);
  166. char *pStrcat(char *s,char *t);
  167. char *pStrcpy(char *s,char *t);
  168. char *PathNameFromDirID(long DirID,short vRefNum,char *s);
  169. char *PathNameFromWD(short vRefNum,char *s);
  170. void ShowSelection(char *s);
  171. void ShowCancelled(void);
  172. void doNormalGet(void);
  173. void doNormalPut(void);
  174. void doNormalPGet(void);
  175. void doNormalPPut(void);
  176. void doFileFilter(void);
  177. void doGetDirectory(void);
  178. void doMultiFile(void);
  179. void doListsNPutFile(void);
  180. void doPutOptions(void);
  181. void doIdleUpdates(void);
  182. void doForceDirectory(void);
  183.  
  184.  
  185. /* Let's do it... */
  186.  
  187. main()
  188. {
  189.     UnloadSeg((Ptr) _DataInit);        /* note that _DataInit must not be in Main! */
  190.  
  191.     MaxApplZone();                    /* expand the heap so code segments load at the top */
  192.  
  193.     Initialize();                    /* initialize the program */
  194.  
  195.     EventLoop();                    /* call the main event loop */
  196. }
  197.  
  198. Boolean TrapAvailable(tNumber,tType)
  199.     short        tNumber;
  200.     TrapType    tType;
  201. {
  202.     return NGetTrapAddress(tNumber, tType) != GetTrapAddress(_Unimplemented);
  203. }
  204.  
  205. void Initialize()
  206. {
  207.     Handle        menuBar;
  208.     short        *flagptr;
  209.  
  210.     InitGraf((Ptr) &qd.thePort);
  211.     InitFonts();
  212.     InitWindows();
  213.     InitMenus();
  214.     TEInit();
  215.     InitDialogs(nil);
  216.     InitCursor();
  217.  
  218.     WNEIsImplemented = TrapAvailable(_WaitNextEvent, ToolTrap);
  219.     
  220.     SysEnvirons(curSysEnvVers,&theWorld);
  221.     flagptr = (short *) HWCfgFlags;
  222.     if (*flagptr & (1 << 9))
  223.           gHaveAUX = 1;            /* Do Have A/UX */
  224.     else
  225.         gHaveAUX = 0;            /* Don't have A/UX */
  226.  
  227.     doneFlag = false;
  228.     MySaveDisk = *(short *)SFSaveDisk + 1;    /*so we're sure that they're different*/
  229.  
  230.     menuBar = GetNewMBar(rMenuBar);        /* read menus into menu bar */
  231.     if (menuBar == nil) ExitToShell();
  232.     SetMenuBar(menuBar);                    /* install menus */
  233.     DisposHandle(menuBar);
  234.     AddResMenu(GetMHandle(mApple), 'DRVR');    /* add DA names to Apple menu */
  235.     DrawMenuBar();
  236.  
  237. } /*Initialize*/
  238.  
  239.  
  240. void EventLoop()
  241. {
  242.     Boolean            haveEvent;
  243.     short            deskPart;        /* result from FindWindow */
  244.  
  245.     do {
  246.         if ( WNEIsImplemented )
  247.             haveEvent = WaitNextEvent(everyEvent, &myEvent, 0x7fffffff, nil);
  248.         else {
  249.             SystemTask();
  250.             haveEvent = GetNextEvent(everyEvent, &myEvent);
  251.         }
  252.  
  253.         deskPart = FindWindow(myEvent.where, &whichWindow);
  254.         if ( deskPart != inSysWindow ) InitCursor();
  255.  
  256.         if (haveEvent) {
  257.             switch (myEvent.what) {
  258.                 case mouseDown:
  259.                     switch (deskPart) {
  260.                         case inSysWindow:
  261.                             SystemClick(&myEvent, whichWindow);
  262.                             break;
  263.                         case inMenuBar:
  264.                             DoCommand(MenuSelect(myEvent.where));
  265.                             break;
  266.                     }
  267.                     break;
  268.  
  269.                 case keyDown:
  270.                 case autoKey:
  271.                     if ((myEvent.modifiers&cmdKey) != 0) {
  272.                         DoCommand(MenuKey(myEvent.message&charCodeMask));
  273.                     }
  274.                     break;
  275.             }
  276.         }
  277.     }
  278.     while (!doneFlag);
  279. }
  280.  
  281.  
  282. /************************ Performed the selected command *************************/
  283.  
  284. void DoCommand(mResult)
  285.     long    mResult;
  286. {
  287.     short    theItem;
  288.     short    theMenu;
  289.     Str255    name;
  290.     GrafPtr    oldPort;
  291.  
  292.     theItem = LoWrd(mResult);
  293.     theMenu = HiWrd(mResult);
  294.  
  295.     switch (theMenu) {
  296.  
  297.         case mApple:
  298.             if (theItem == iAboutMe) {
  299.                 ShowAboutMeDialog();
  300.             } else {
  301.                 GetItem(GetMHandle(mApple),theItem,&name);
  302.                 GetPort(&oldPort);
  303.                 OpenDeskAcc(&name);
  304.                 SetPort(oldPort);
  305.             };
  306.             break;
  307.         case mFile:
  308.             switch (theItem) {
  309.                 case iQuit:
  310.                     doneFlag = true;
  311.                     break;
  312.             };
  313.             break;
  314.         case mEdit:
  315.             SystemEdit(theItem-1);
  316.             break;
  317.         case mSFile:
  318.             switch (theItem) {
  319.                 case iNormalGet:        doNormalGet();        break;
  320.                 case iNormalPut:        doNormalPut();        break;
  321.                 case iNormalPGet:        doNormalPGet();        break;
  322.                 case iNormalPPut:        doNormalPPut();        break;
  323.                 case iFileFilter:        doFileFilter();        break;
  324.                 case iGetDirectory:        doGetDirectory();    break;
  325.                 case iMultiFile:        doMultiFile();        break;
  326.                 case iListsNPutFile:    doListsNPutFile();    break;
  327.                 case iPutOptions:        doPutOptions();        break;
  328.                 case iIdleUpdates:        doIdleUpdates();    break;
  329.                 case iForceDirectory:    doForceDirectory();    break;
  330.             };
  331.             break;
  332.     };
  333.     HiliteMenu(0);
  334. };
  335.  
  336.  
  337.  
  338. /** SetRadioButton ************************************************************/
  339. /*
  340. /*    Handy routine for setting the value of a radio button. Given a dialog
  341. /*    pointer, and item number, and a state, this routine will take care of
  342. /*    the rest.
  343. /*
  344. /******************************************************************************/
  345.  
  346. void SetRadioButton(dialog,item,state)
  347.     DialogPtr    dialog;
  348.     short        item;
  349.     short        state;
  350. {
  351.     short        kind;
  352.     Handle        h;
  353.     Rect        r;
  354.  
  355.     GetDItem(dialog,item,&kind,&h,&r);
  356.     SetCtlValue((ControlHandle)h,state);
  357. }
  358.  
  359.  
  360. /** ShowAboutMeDialog *********************************************************/
  361. /*
  362. /*    Shows the obligatory vanity box. Normally shows the standard boring
  363. /*    Developer Technical Support About box, but can be enlivened by holding
  364. /*    the Command key when making the menu selection. Rick Blair says that in the
  365. /*    future, we'll have better ones...
  366. /*
  367. /******************************************************************************/
  368.  
  369. void ShowAboutMeDialog()
  370. {
  371.     if ((myEvent.modifiers & cmdKey) == 0) {
  372.         Alert(rAboutMeDLOG, nil);
  373.     } else {
  374.         Alert(rRealAboutMeDLOG, nil);
  375.     }
  376. }
  377.  
  378.  
  379. /** pStrcat / pStrCpy *********************************************************/
  380. /*
  381. /*    A couple of utility routines. C is thoughtless enough to not really 
  382. /*    support P-strings. In order to perform string copies and concatenations,
  383. /*    I have to use these routines.
  384. /*
  385. /******************************************************************************/
  386.  
  387. char *pStrcat(s,t)
  388.     unsigned char *s, *t;
  389. {
  390.     unsigned char *s2;
  391.     short tLen;
  392.  
  393.     s2 = s + *s;
  394.     *s += (tLen = *t);
  395.     for (++tLen; --tLen; s2[tLen] = t[tLen]);
  396.     return (s);
  397. }
  398.  
  399. char *pStrcpy(s,t)
  400.     unsigned char *s, *t;
  401. {
  402.     short    tLen;
  403.  
  404.     for (tLen = *t + 1; tLen--; s[tLen] = t[tLen]);
  405.     return (s);
  406. }
  407.  
  408.  
  409. /** PathNameFromDirID *********************************************************/
  410. /*
  411. /*    Given a DirID and real vRefnum, this routine will create and return the
  412. /*    full pathname that corresponds to it. It does this by calling PBGetCatInfo
  413. /*    for the given directory, and finding out its name and the DirID of its
  414. /*    parent. It the performs the same operation on the parent, sticking ITS
  415. /*    name onto the beginning of the first directory. This whole process is
  416. /*    carried out until we have processed the root directory (identified with
  417. /*    a DirID of 2.
  418. /*
  419. /*    NOTE: This routine is now A/UX friendly. A/UX likes sub-directories
  420. /*            separated by slashes in a pathname. This routine automatically
  421. /*            uses colons or slashes as separators based on the value of the
  422. /*            global gHasAUX.  This global must be initialized correctly for
  423. /*            this routine to do its thing. However, because of this dependancy
  424. /*            on the idiosyncracies of file systems, generating full pathnames
  425. /*            for other than display purposes is discouraged; it's changed in
  426. /*            the past when A/UX was implemented, and it may change again in
  427. /*            the future it support for other file systems such as ProDOS,
  428. /*            MS-DOS, or OS/2 are added.
  429. /*
  430. /******************************************************************************/
  431.  
  432. char *PathNameFromDirID(DirID, vRefNum, s)
  433.     long    DirID;
  434.     short    vRefNum;
  435.     char    *s;
  436. {
  437.     CInfoPBRec    block;
  438.     Str255        directoryName;
  439.  
  440.     *s = 0;
  441.     block.dirInfo.ioNamePtr = &directoryName;
  442.     block.dirInfo.ioDrParID = DirID;
  443.  
  444.     do {
  445.         block.dirInfo.ioVRefNum = vRefNum;
  446.         block.dirInfo.ioFDirIndex = -1;
  447.         block.dirInfo.ioDrDirID = block.dirInfo.ioDrParID;
  448.  
  449.         err = PBGetCatInfo(&block,false);
  450.         if (gHaveAUX) {
  451.             if (directoryName[1] != '/')
  452.                 /* If this isn't root (i.e. '/'), append a slash ('/') */
  453.                 pStrcat(&directoryName,"\p/");
  454.         } else 
  455.             /* Append a Macintosh style colon (':') */
  456.             pStrcat(&directoryName,"\p:");
  457.         pStrcat(&directoryName,s);
  458.         pStrcpy(s,&directoryName);
  459.     } while (block.dirInfo.ioDrDirID != 2);
  460.  
  461.     return(s);
  462. }
  463.  
  464.  
  465. /** PathNameFromWD ************************************************************/
  466. /*
  467. /*    Given an HFS working directory, this routine returns the full pathname
  468. /*    that corresponds to it. It does this by calling PBGetWDInfo to get the
  469. /*    VRefNum and DirID of the real directory. It then calls PathNameFromDirID,
  470. /*    and returns its result.
  471. /*
  472. /******************************************************************************/
  473.  
  474. char *PathNameFromWD(vRefNum,s)
  475.     long    vRefNum;
  476.     char    *s;
  477. {
  478.  
  479.     WDPBRec    myBlock;
  480.  
  481.     /*
  482.     /* PBGetWDInfo has a bug under A/UX 1.1.  If vRefNum is a real vRefNum
  483.     /* and not a wdRefNum, then it returns garbage.  Since A/UX has only 1
  484.     /* volume (in the Macintosh sense) and only 1 root directory, this can
  485.     /* occur only when a file has been selected in the root directory (/).
  486.     /* So we look for this and hardcode the DirID and vRefNum. */
  487.  
  488.     if (gHaveAUX && (vRefNum == -1))
  489.         return(PathNameFromDirID(2,-1,s));
  490.  
  491.     myBlock.ioNamePtr = nil;
  492.     myBlock.ioVRefNum = vRefNum;
  493.     myBlock.ioWDIndex = 0;
  494.     myBlock.ioWDProcID = 0;
  495.  
  496.     /* Change the Working Directory number in vRefnum into a real vRefnum */
  497.     /* and DirID. The real vRefnum is returned in ioVRefnum, and the real */
  498.     /* DirID is returned in ioWDDirID. */
  499.  
  500.     PBGetWDInfo(&myBlock,false);
  501.  
  502.     return(PathNameFromDirID(myBlock.ioWDDirID,myBlock.ioWDVRefNum,s));
  503. };
  504.  
  505.  
  506. /** ShowSelection *************************************************************/
  507. /*
  508. /*    This routine accepts a string as input, prepends STR#(256,4) - which
  509. /*    should be "The item selected was " - and shows a NoteAlert with the
  510. /*    result.
  511. /*
  512. /******************************************************************************/
  513.  
  514. void ShowSelection(s)
  515.     char *s;
  516. {
  517.     Str255 tStr;
  518.  
  519.     GetIndString(&tStr,rStrMisc,4);
  520.     ParamText(pStrcat(&tStr,s),nil,nil,nil);
  521.     NoteAlert(rShowSelectionDLOG, nil);
  522. };
  523.  
  524.  
  525. /** ShowCancelled *************************************************************/
  526. /*
  527. /*    Shows STR#(256,5) in a NoteAlert. This string says in English "The Cancel
  528. /*    button was pressed."
  529. /*                                                        - Douglas Hofstadter
  530. /*
  531. /******************************************************************************/
  532.  
  533. void ShowCancelled()
  534. {
  535.     Str255    tStr;
  536.  
  537.     GetIndString(&tStr,rStrMisc,5);
  538.     ParamText(&tStr,nil,nil,nil);
  539.     NoteAlert(rShowSelectionDLOG,nil);
  540. };
  541.  
  542.  
  543. /** doNormalGet ***************************************************************/
  544. /*
  545. /*    Simplest form of SFGetFile. This routine puts up a GetFile dialog with
  546. /*    a request to show all files. When this is done, a check is made to see
  547. /*    which of the Save or Cancel buttons were pressed. If the Save button was
  548. /*    pressed, ShowSelection is called to display which file was selected. If the
  549. /*    Cancel button was pressed, a dialog is shown saying so.
  550. /*
  551. /*    NOTE: The second parameter - the prompt string - is ignored in SFGetFile
  552. /*            calls. As noted on page I-523 of Inside Mac, it is there for
  553. /*            historical reasons only.
  554. /*
  555. /******************************************************************************/
  556.  
  557. void doNormalGet()
  558. {
  559.     Str255    s;
  560.  
  561.     SFGetFile(gLocation,            /* location */
  562.         "\pSpace for Rent",            /* vestigial string */
  563.         nil,                        /* fileFilter */
  564.         -1,                            /* numtypes; -1 means all */
  565.         &typeList,                    /* array to types to show */
  566.         nil,                        /* dlgHook */
  567.         &reply);                    /* record for returned values */
  568.  
  569.     if (reply.good)
  570.         ShowSelection(pStrcat(PathNameFromWD(reply.vRefNum,&s),&reply.fName));
  571.     else
  572.         ShowCancelled();
  573. };
  574.  
  575.  
  576. /** doNormalPut ***************************************************************/
  577. /*
  578. /*    Simplest form of SFPutFile. This routine puts up a PutFile dialog with a
  579. /*    prompt and suggested file name.
  580. /*
  581. /******************************************************************************/
  582.  
  583. void doNormalPut()
  584.  
  585. {
  586.     Str255    s;
  587.  
  588.     SFPutFile(gLocation,            /* location */
  589.         "\pSave document as:",        /* prompt string */
  590.         "\pDoug",                    /* original name */
  591.         nil,                        /* dlgHook */
  592.         &reply);                    /* record for returned values */
  593.  
  594.     if (reply.good)
  595.         ShowSelection(pStrcat(PathNameFromWD(reply.vRefNum,&s),reply.fName));
  596.     else
  597.         ShowCancelled();
  598. };
  599.  
  600.  
  601. /** doNormalPGet **************************************************************/
  602. /*
  603. /*    SFPGetFile with Dialog Hook and Simple File Filter. Depending on the value
  604. /*    of the Global variable "textOnly", it shows either TEXT files, or TEXT and
  605. /*    APPL files. The value of "textOnly" is determined by the states of two
  606. /*    radio buttons that are added to the dialog box. Our dlgHook routine is used
  607. /*    to initialize the radio buttons and handle hits on them. When there is a
  608. /*    hit on a radio button, "textOnly" is set to either TRUE or FALSE, and a
  609. /*    special command is sent back to Standard File, telling it to regenerate
  610. /*    the list of file names it is displaying. This sample consists of 3 parts:
  611. /*
  612. /*    doNormalPGet - Called by doCommand. This routine initializes a variable
  613. /*        for our file filter, and then calls SFPGetFile with pointers to
  614. /*        two other routines and a resource number for a special dialog box
  615. /*        with extra items in it.
  616. /*
  617. /*    SimpleFileFilter - Specified in our SFPGetFile call to be called to
  618. /*        specify whether or not a file should be displayed. All TEXT files
  619. /*        are displayed. If the global variable 'textOnly' is FALSE, then
  620. /*        applications (APPL files) are also displayed. NO other files are
  621. /*        displayed.
  622. /*
  623. /*    MySFGetHook - A routine that is called to handle hits on the non-standard
  624. /*        items in our dialog box. These items are:
  625. /*            - 2 radio buttons that affect the setting of the 'textOnly'
  626. /*                variable,
  627. /*            - a Quit button that causes the GetFile dialog box to be put
  628. /*                away (like pressing Cancel), but also causes this demo to be
  629. /*                exitted.
  630. /*
  631. /*        The dlgHook is also used to perform some special initialization
  632. /*        on the items in the dialog box. Standard file does this by calling
  633. /*        this routine with a bogus 'item' number of -1. When we get this
  634. /*        number, we initialize our radio buttons, and change the text in the
  635. /*        Open button.
  636. /*
  637. /*        The radio buttons are used to determine what files will appear in
  638. /*        the files list. It does this by apporpriately setting the 'textOnly'
  639. /*        variable for SimpleFileFilter, and then telling Standard File that
  640. /*        the file list needs to be regenerated by passing back 101 as the
  641. /*        the function result.
  642. /*
  643. /*        The Quit button causes the dialog box to go away by returning the
  644. /*        'getCancel' to Standard File. It also sets the 'doneFlag' variable
  645. /*        to TRUE for our MainEventLoop to notice.
  646. /*
  647. /******************************************************************************/
  648.  
  649. pascal short MySFGetHook(MySFItem, theDialog)
  650.     short        MySFItem;
  651.     DialogPtr    theDialog;
  652. {
  653. #define textButton 11
  654. #define textAppButton 12
  655. #define quitButton 13
  656.  
  657.     Handle        itemToChange;    /* needed for GetDItem and SetCtlValue */
  658.     Rect        itemBox;        /* needed for GetDItem */
  659.     short        itemType;        /* needed for GetDItem */
  660.     Str255        buttonTitle;    /* needed for GetIndString */
  661.  
  662.     /* MySFGetHook is a function that requires that an item number be passed    */
  663.     /* back from it. Normally, this is the same item number that was passed        */
  664.     /* to us, but not necessarily. For instance, clicks on the Quit button        */
  665.     /* get translated into clicks on the Cancel button. We could also return    */
  666.     /* values that cause the file names to be redrawn or have the whole event    */
  667.     /* ignored. However, by default, we'll return what was sent to us.            */
  668.  
  669.     switch (MySFItem) {
  670.         case firstTime:
  671.         /* Before the dialog is drawn, our hook gets called with a -1. */
  672.         /* This gives us the opportunity to change things like Button titles, etc. */
  673.  
  674.         /* Set the textAppButton to OFF, the textButton to ON */
  675.  
  676.             SetRadioButton(theDialog,textAppButton,BTNOFF);
  677.             SetRadioButton(theDialog,textButton,BTNON);
  678.  
  679.         /* Get the Button title from the resource fork. If we can't get the */
  680.         /* resource, we just won't change the open Button's title*/
  681.  
  682.             GetIndString(&buttonTitle,rStrMisc,1);
  683.             if (*buttonTitle != 0) { /* if we really got the resource */
  684.                 GetDItem(theDialog,getOpen,&itemType,&itemToChange,&itemBox);
  685.                 SetCTitle((ControlHandle)itemToChange,&buttonTitle);
  686.             };
  687.             break;
  688.  
  689.         case textButton:
  690.  
  691.         /* Turn the textAppButton OFF, the textButton ON and redraw the list*/
  692.  
  693.             if (!textOnly) {
  694.                 SetRadioButton(theDialog,textAppButton,BTNOFF);
  695.                 SetRadioButton(theDialog,textButton,BTNON);
  696.                 textOnly = true;
  697.                 return(reDrawList); /* we must tell SF to redraw the list */
  698.             };
  699.             break;
  700.  
  701.         case textAppButton:
  702.  
  703.         /* Turn the textButton OFF, the textAppButton ON and redraw the list*/
  704.  
  705.             if (textOnly) {
  706.                 SetRadioButton(theDialog,textButton,BTNOFF);
  707.                 SetRadioButton(theDialog,textAppButton,BTNON);
  708.                 textOnly = false;
  709.                 return(reDrawList); /* we must tell SF to redraw the list */
  710.             };
  711.             break;
  712.  
  713.         case quitButton:
  714.  
  715.         /* Alias clicks on the Quit button to clicks on the Cancel Button. Also, */
  716.         /* set 'doneFlag' to TRUE so that the MainEventLoop terminates. */
  717.  
  718.             doneFlag = true;
  719.             return (getCancel); /* Pass SF back a 'cancel Button' */
  720.  
  721.         default:
  722.             return(MySFItem);
  723.     };
  724. };
  725.  
  726. pascal Boolean SimpleFileFilter(p)
  727.     ParmBlkPtr p;
  728. {
  729.  
  730.     /* If we have a 'TEXT' file, or we have an 'APPL' file and 'textOnly' is */
  731.     /* FALSE, then signify that we should show the file by returning FALSE */
  732.  
  733.     if ((p->fileParam.ioFlFndrInfo.fdType == 'TEXT') ||
  734.         ((!textOnly) && (p->fileParam.ioFlFndrInfo.fdType == 'APPL')))
  735.             return(false);
  736.     else
  737.         return(true);
  738. }
  739.  
  740.  
  741. void doNormalPGet()
  742. {
  743.     textOnly = true;
  744.     SFPGetFile(gLocation,    /* location */
  745.         "\pSpace for Rent",    /* vestigial string */
  746.         SimpleFileFilter,    /* fileFilter */
  747.         -1,                    /* numtypes; -1 means all */
  748.         &typeList,            /* array to types to show */
  749.         MySFGetHook,        /* dlgHook */
  750.         &reply,                /* record for returned values */
  751.         rSFPGetFileDLOG,    /* ID of Custom Dialog */
  752.         nil);                /* ModalDialog filterProc */
  753. };
  754.  
  755. /** doNormalPPut **************************************************************/
  756. /*
  757. /*    Normal SFPPutFile sample. This custom PutFile routine adds several new
  758. /*    to the dialog box. There are 2 radio buttons that determine what format
  759. /*    the user wants to save the information in. There is a Quit buttons that
  760. /*    will cause us to leave this program. And there is a Static Text item that
  761. /*    displays the amount of free space left on the disk. This text item is
  762. /*    updated whenever we detect that the volume we are currently looking at is
  763. /*    not the same as the last volume we looked at. Therefore, we maintain a
  764. /*    variable called MySaveDisk to track this. This sample consists of 3 parts:
  765. /*
  766. /*    doNormalPPut - This routine, called by doCommand, calls SFPPutFile. Note
  767. /*        the extra 'P' in the name. This toolbox call allows us to specify a
  768. /*        customized dialog box, and dlgHook that handles hits on special
  769. /*        items.
  770. /*
  771. /*    MySFPutHook - A routine that is called to handle the non-standard items
  772. /*        in our dialog box. These items are:
  773. /*            - 2 radio buttons that affect the setting of the 'textOnly'
  774. /*                variable,
  775. /*            - a Quit button that causes the GetFile dialog box to be put
  776. /*                away (like pressing Cancel), but also causes this demo to be
  777. /*                exitted,
  778. /*            - a UserItem that displays the amount of free space on the
  779. /*                current volume.
  780. /*
  781. /*        The dlgHook is also used to perform some special initialization
  782. /*        on the items in the dialog box. Standard file does this by calling
  783. /*        this routine with a bogus 'item' number of -1. When we get this
  784. /*        number, we initialize our radio buttons, change the text in the
  785. /*        save button, and prepare the user item by initializing the routine
  786. /*        that will draw it and getting the text that will appear in it.
  787. /*
  788. /*        The radio buttons are used to determine what format the file will be
  789. /*        saved in. They are functionless in this sample, merely changing
  790. /*        their state and setting a global variable for possible use later.
  791. /*
  792. /*        The Quit button causes the dialog box to go away by returning the
  793. /*        'putCancel' to Standard File. It also sets the 'doneFlag' variable
  794. /*        to TRUE for our MainEventLoop to notice.
  795. /*
  796. /*        Finally, this routine performs one other function. When SFPPutFile
  797. /*        is called, we make note of the current volume (the one that will
  798. /*        be displayed in the dialog box). This routine is responsible for
  799. /*        checking to see whenever we change volumes, and to update the
  800. /*        Free Space notice accordingly. It does this by looking at the value
  801. /*        stored at SFSaveDisk. Whenever that value changes, we have changed
  802. /*        volumes and need to make a PBHGetVInfo call to find out how much
  803. /*        room is left on the disk.
  804. /*
  805. /*    DrawFreeSpaceItem - used to draw a user item that displays the amount of
  806. /*        free space left on the disk.
  807. /*
  808. /******************************************************************************/
  809.  
  810. pascal void DrawFreeSpaceItem(theWindow, itemNo)
  811.     WindowPtr    theWindow;
  812.     short        itemNo;
  813. {
  814. #pragma unused (theWindow,itemNo)
  815.     TextBox(myStr + 1, *myStr, &freeItemBox, teJustCenter);
  816. };
  817.  
  818.  
  819. pascal short MySFPutHook(MySFItem, theDialog)
  820.     short        MySFItem;
  821.     DialogPtr    theDialog;
  822. {
  823. #define quitButton 9
  824. #define textButton 10
  825. #define formatButton 11
  826. #define freeSpaceItem 12  /* DITL item number of free space static text */
  827.  
  828.     Handle            itemToChange;    /* needed for GetDItem and SetCtlValue */
  829.     short            itemType;        /* needed for GetDItem */
  830.     Rect            itemBox;        /* needed for GetDItem */
  831.     HParamBlockRec    myHPB;
  832.  
  833.  
  834.  
  835. /* MySFPutHook is a function that requires that an item number be passed    */
  836. /* back from it. Normally, this is the same item number that was passed        */
  837. /* to us, but not necessarily. For instance, clicks on the Quit button        */
  838. /* get translated into clicks on the Cancel button. We could also return    */
  839. /* values that cause the file names to be redrawn or have the whole event    */
  840. /* ignored. However, by default, we'll return what was sent to us.            */
  841.  
  842. /* Before the dialog is drawn, our hook gets called with a -1. */
  843. /* This gives us the opportunity to change things like Button titles, etc. */
  844.  
  845.     if (MySFItem == firstTime) {
  846.  
  847.     /* Set the FormatButton to ON, the textButton to OFF*/
  848.  
  849.         SetRadioButton(theDialog,formatButton,BTNON);
  850.         SetRadioButton(theDialog,textButton,BTNOFF);
  851.         textOnly = false;
  852.  
  853.     /* Get the text for the Save button from the resource fork. */
  854.     /* If we can't get the resource, we just won't change the Button's title*/
  855.  
  856.         GetIndString(&myStr,rStrMisc,1);
  857.         if (*myStr != 0) {            /*if we really got the resource*/
  858.             GetDItem(theDialog,putSave,&itemType,&itemToChange,&itemBox);
  859.             SetCTitle((ControlHandle)itemToChange,&myStr);
  860.         };
  861.  
  862.     /* Set up our routine to draw our user item */
  863.  
  864.         GetDItem(theDialog,freeSpaceItem,&itemType,&itemToChange,&freeItemBox);
  865.         SetDItem(theDialog,freeSpaceItem,itemType,(Handle)DrawFreeSpaceItem,
  866.                  &freeItemBox);
  867.  
  868.     /* Get the text to be used  in our user item. */
  869.  
  870.         GetIndString(&myStr2,rStrMisc,2);
  871.  
  872.     }; /* if MySFItem == firstTime */
  873.  
  874. /* Check to see if we have changed drives. If so, then we need to update */
  875. /* the text in our UserItem. */
  876.  
  877.     if (MySaveDisk != *(short *)SFSaveDisk) {
  878.         /* set up the block for the PBHGetVInfo call*/
  879.         myHPB.volumeParam.ioNamePtr = nil;            /* we don't care about the name */
  880.         myHPB.volumeParam.ioVRefNum = -*(short *)SFSaveDisk;
  881.         myHPB.volumeParam.ioVolIndex = 0;            /* use ioVRefNum only */
  882.  
  883.         err = PBHGetVInfo(&myHPB,false);
  884.  
  885.         if (err == noErr) {
  886.             NumToString(((long)myHPB.volumeParam.ioVFrBlk * myHPB.volumeParam.ioVAlBlkSiz) / 1024,&myStr);
  887.         } else  /* Handle error - we can't get the free space size! */
  888.             pStrcpy(myStr, "\p??");
  889.  
  890.     /* Add the file size to 'K free' (or whatever) */
  891.  
  892.         pStrcat(&myStr,&myStr2);
  893.  
  894.     /* Draw the new freeBlocks in our userItem */
  895.  
  896.         DrawFreeSpaceItem(theDialog,freeSpaceItem);
  897.  
  898.     /* SFSaveDisk changed, so we need to update mySaveDisk*/
  899.  
  900.         MySaveDisk = *(short *)SFSaveDisk;
  901.  
  902.     }; /*IF mySaveDisk != SFSaveDisk^*/
  903.  
  904.     switch(MySFItem) {
  905.  
  906.         case textButton:
  907.  
  908.         /* Turn the FormatButton OFF, the textButton ON */
  909.  
  910.             if (!textOnly) {
  911.                 SetRadioButton(theDialog,formatButton,BTNOFF);
  912.                 SetRadioButton(theDialog,textButton,BTNON);
  913.                 textOnly = true; /* change our flag accordingly */
  914.             };    /*if not textOnly*/
  915.             break;
  916.  
  917.         case formatButton:
  918.  
  919.         /* Turn the textButton OFF, the FormatButton ON */
  920.  
  921.             if (textOnly) {
  922.                 SetRadioButton(theDialog,textButton,BTNOFF);
  923.                 SetRadioButton(theDialog,formatButton,BTNON);
  924.                 textOnly = false; /* change our flag accordingly */
  925.             };    /*if not textOnly*/
  926.             break;
  927.  
  928.         case quitButton:
  929.             doneFlag = true;
  930.             return(putCancel); /* Pass SF back a 'cancel Button' */
  931.  
  932.     };    /* switch */
  933.     return(MySFItem);
  934. };
  935.  
  936.  
  937. void doNormalPPut()
  938. {
  939.     SFPPutFile(gLocation,        /* location */
  940.         "\pSave document as:",    /* prompt string */
  941.         "\pDoug",                /* original name */
  942.         MySFPutHook,            /* dlgHook */
  943.         &reply,                    /* record for returned values */
  944.         rSFPPutFileDLOG,        /* ID of custom Dialog */
  945.         nil);                    /* ModalDialog filterProc */
  946. };
  947.  
  948.  
  949. /** ComplexFileFilter *********************************************************/
  950. /*
  951. /*    Sample of a less trivial file filter. This routine is responsible for
  952. /*    displaying only files that have 'ALRT' resources.  It shows how to
  953. /*    correctly look inside a file for qualifying information, and demonstrates
  954. /*    how to get around a particular quirk when attempting multiple access to
  955. /*    the resource fork.
  956. /*
  957. /*    In order to save time, the first thing we do is turn off resource loading;
  958. /*    we just want to count the number of ALRT resources, and don't care about
  959. /*    the actual data itself. So I save off the current ResLoad setting, and
  960. /*    then set it to FALSE.
  961. /*
  962. /*    I then prepare to open the resource fork of the file. However, I can't
  963. /*    simply call OpenResFile. OpenResFile only works for files that are in
  964. /*    the default directory. Standard file makes HFS calls when it can, and
  965. /*    explicitly uses DirIDs when getting file names and other information.
  966. /*    Therefore, I can't count on the default directory being set to the dir-
  967. /*    ectory that we are examining. In order to call OpenResFile, I have to set
  968. /*    the default directory.
  969. /*
  970. /*    So I take the VRefNum and DirID passed to me, and I create a Working
  971. /*    Directory from them. I save the old default directory, and then set it
  972. /*    to the WD I just created.
  973. /*
  974. /*    So far, so good. I am now ready to open the resource fork of the file,
  975. /*    call Count1Resources (which limits enumeration to the current file only,
  976. /*    excluding other files in the resource chain), and close the file. But
  977. /*    wait! I must make sure that I am not closing a file that I don't really
  978. /*    want to close. For example, one of the files I may be examining may be
  979. /*    myself. If I were to blindly close that file, I would be closing my CODE
  980. /*    resources, removing them from memory. Empirical experience has told me that
  981. /*    this is not good.
  982. /*
  983. /*    So I have to make sure that I don't close my own resource fork. I could
  984. /*    possibly check the refnum that I get back from OpenResFile against one
  985. /*    that I could get by calling CurResFile at the start of the program. If
  986. /*    they match, then I am looking at my own file, and I shouldn't close it. But
  987. /*    this doesn't work always. For instance, I also want to make sure that I
  988. /*    don't close the System Resource file when I point and click my way into
  989. /*    the System Folder. What if there are DA's open? I don't want to close them.
  990. /*    How about the Fonts/DA's file used by Suitcase? What about the DeskTop
  991. /*    file? Basically, I need a way to determine on the fly if a resource file
  992. /*    is already open before I try to open it myself.
  993. /*
  994. /*    Well, there is a way. Within the parameter block passed to me is a field
  995. /*    called ioFlAttrib. It is described on page IV-122 of Inside Mac. As
  996. /*    described there, bit 2 contains the state of the resource fork. If the fork
  997. /*    is open, then the bit is set. All that is necessary for me to do is check
  998. /*    the state of this bit before I open the file. If the bit is clear, then
  999. /*    I need to close the file when I am done.
  1000. /*
  1001. /*    But wait! There's more. This technique - alas! - will not work under
  1002. /*    MultiFinder! Why? Because this bit is set even if another program in
  1003. /*    another partition has the resource fork open. I would test that bit, find
  1004. /*    that the file is open, open the file for myself, count the number of
  1005. /*    resources, and then leave the file open. This now leaves another resource
  1006. /*    file in the resource chain - one that wasn't there before and that
  1007. /*    shouldn't be there now. Another technique is needed.
  1008. /*
  1009. /*    Fortunately, one is at hand. On page I-115 of Inside Mac is a brief
  1010. /*    description of a low-memory variable called TopMapHndl. This contains
  1011. /*    a handle to the resource map of the first resource file in the chain.
  1012. /*    Since a resource file is made the current resource file when OpenResFile
  1013. /*    is called if it is actually being opened, we can test the value of
  1014. /*    TopMapHndl to see if the file was really opened. If the value of TopMapHndl
  1015. /*    changes after OpenResFile, then the resource fork was open for this
  1016. /*    application, and should be closed when we are done with it. If TopMapHndl
  1017. /*    doesn't change, then no new resource maps were added to the chain; no
  1018. /*    new files were opened. Therefore, the file doesn't need to be closed later.
  1019. /*    So far, this solution seems to be the best.
  1020. /*
  1021. /*    After doing all of this, we are almost done. All that needs to be done is
  1022. /*    restore the default directory and the value of ResLoad to their previous
  1023. /*    settings. When we have done so, we can return to Standard File.
  1024. /*
  1025. /******************************************************************************/
  1026.  
  1027. pascal Boolean ComplexFileFilter(p)
  1028.     ParmBlkPtr    p;
  1029. {
  1030.     short            refnum, tRefNum;
  1031.     WDPBRec            WDRec;
  1032.     ParamBlockRec    oldVol, newVol;
  1033.     Boolean            oldResLoad, closeIt;
  1034.     Handle            tHandle;
  1035.     Boolean            returnVal;
  1036.  
  1037.     returnVal = true;        /* Don't show it -- default */
  1038.  
  1039. /* Save the current setting of ResLoad. Then set it to FALSE. */
  1040.  
  1041.     oldResLoad = *(Boolean *)ResLoad;
  1042.     SetResLoad(false);
  1043.  
  1044. /* Save the current default directory/volume. */
  1045.  
  1046.     oldVol.volumeParam.ioNamePtr = nil;
  1047.     PBGetVol(&oldVol,false);
  1048.  
  1049. /* Create a working directory for the directory we are examining. */
  1050.  
  1051.     WDRec.ioNamePtr = nil;
  1052.     WDRec.ioVRefNum = p->fileParam.ioVRefNum;
  1053.     WDRec.ioWDProcID = 'ERIK';
  1054.     WDRec.ioWDDirID = *(long *)CurDirStore;
  1055.  
  1056.     PBOpenWD(&WDRec,false);
  1057.  
  1058. /* Set the Working Directory we just created to be the default. */
  1059.  
  1060.     newVol.volumeParam.ioNamePtr = nil;
  1061.     newVol.volumeParam.ioVRefNum = WDRec.ioVRefNum;
  1062.  
  1063.     PBSetVol(&newVol,false);
  1064.  
  1065. /* Check the current value of TopMapHndl, open the resource file, and */
  1066. /* check it again. If it changed, then note that we should close this */
  1067. /* file later when we are done with it. */
  1068.  
  1069.     tHandle = *(Handle *)TopMapHndl;
  1070.     refnum = OpenResFile(p->fileParam.ioNamePtr);
  1071.     if (tHandle != *(Handle *)TopMapHndl)
  1072.         closeIt = true;
  1073.     else
  1074.         closeIt = false;
  1075.  
  1076. /* If we successfully opened the file, then count the number of ALRT    */
  1077. /* resources in it. Call UseResFile to make sure that the file we just    */
  1078. /* 'opened' is the current one. We have to do this, as OpenResFile will    */
  1079. /* not make the file the current one if it is already open. So save the    */
  1080. /* current resFile, (possibly) change it to the one we want, read the    */
  1081. /* number of ALRT resources in it, and then set the current resource    */
  1082. /* file back to what it was. Finally, if we need to close the file we    */
  1083. /* opened, do so. */
  1084.  
  1085.     if (ResError() == noErr) {
  1086.         tRefNum = CurResFile();
  1087.         UseResFile(refnum);
  1088.         if (Count1Resources('ALRT') > 0) returnVal = false;
  1089.         UseResFile(tRefNum);
  1090.         if (closeIt) CloseResFile(refnum);
  1091.     };
  1092.  
  1093. /* All done! Reset the default directory and ResLoad, and then blow. */
  1094.  
  1095.     PBSetVol(&oldVol,false);
  1096.     PBCloseWD(&WDRec,false);
  1097.     SetResLoad(oldResLoad);
  1098.  
  1099.     return(returnVal);
  1100. };
  1101.  
  1102. void doFileFilter()
  1103. {
  1104.     SFGetFile(gLocation,    /* location */
  1105.         "\pSpace for Rent",    /* vestigial string */
  1106.         ComplexFileFilter,    /* fileFilter */
  1107.         -1,                    /* numtypes; -1 means all */
  1108.         &typeList,            /* array to types to show */
  1109.         nil,                /* dlgHook */
  1110.         &reply);            /* record for returned values */
  1111. };
  1112.  
  1113.  
  1114. /** doGetDirectory ************************************************************/
  1115. /*
  1116. /*    Shows how to modify SFGetFile to allow you to select a directory. This
  1117. /*    mimics the "GetFileName -d" function of MPW. As a matter of fact, the
  1118. /*    DLOG and DITL used in this sample are taken directly out of MPW.
  1119. /*
  1120. /*    There are 2 major additions in the dialog used in this sample:
  1121. /*        - a Simple button that lets one select the directory that is
  1122. /*            currently highlighted in the list of directories,
  1123. /*        - a Simple button at the top of the dialog that lets the user select
  1124. /*            the directory that we are currently *IN*.
  1125. /*
  1126. /*    No new techniques are really used in this sample. Hits on the two simple
  1127. /*    buttons are handled by a dlgHook called MyGetDirHook. Depending on
  1128. /*    which button is hit, we set the global variable MyCurDir to either
  1129. /*    reply.fType (for the currently highlighted directory) or CurDirStore^ (for
  1130. /*    the directory that we are currently in). We then simulate a hit on the
  1131. /*    Cancel button so that Standard File will return to our application.
  1132. /*
  1133. /*    However, when we get back to our application, there is no way for it to
  1134. /*    determine if we simulated a click on the Cancel Button, or if the user
  1135. /*    actually clicked on it; reply.good will be FALSE in either case. So we also
  1136. /*    set the value of global variable CurDirValid to TRUE if we only simulated
  1137. /*    a click on Cancel.
  1138. /*
  1139. /*    We also use the "Item== -1" feature to set up a prompt string and init-
  1140. /*    alize the value of CurDirValid to FALSE.
  1141. /*
  1142. /******************************************************************************/
  1143. pascal short MyGetDirHook(item, dPtr)
  1144.     short        item;
  1145.     DialogPtr    dPtr;
  1146. {
  1147. /* Equates for the itmes that I've added */
  1148. #define getDirButton 11
  1149. #define getDirNowButton 12
  1150. #define getDirMessage 13
  1151.  
  1152.     Str255    messageTitle;
  1153.     short    kind;
  1154.     Handle    h;
  1155.     Rect    r;
  1156.  
  1157.     switch (item) {
  1158.         case firstTime:
  1159.  
  1160.             /* Read in the prompt string from the resource fork, and initialize */
  1161.             /* CurDirValid to FALSE. */
  1162.  
  1163.             GetIndString(&messageTitle,rStrMisc,3);
  1164.             GetDItem(dPtr,getDirMessage,&kind,&h,&r);
  1165.             SetIText(h,&messageTitle);
  1166.             CurDirValid = false;
  1167.             break;
  1168.         case getDirButton:
  1169.             if (reply.fType != 0) {
  1170.                 MyCurDir = reply.fType;
  1171.                 CurDirValid = true;
  1172.                 return(getCancel);
  1173.             };
  1174.             break;
  1175.         case getDirNowButton:
  1176.             MyCurDir = *(long *)CurDirStore;
  1177.             CurDirValid = true;
  1178.             return(getCancel);
  1179.     };
  1180.     return(item);    /* By default, return the item passed to us. */
  1181. };
  1182.  
  1183.  
  1184. pascal Boolean FoldersOnly(p)
  1185.     ParmBlkPtr p;
  1186. {
  1187.     /* Normally, folders are ALWAYS shown, and aren't even passed to        */
  1188.     /* this file filter for judgement. Under such circumstances, it is        */
  1189.     /* only necessary to blindly return TRUE (allow no files whatsoever).    */
  1190.     /* However, Standard File is not documented in such a manner, and        */
  1191.     /* this feature may not be TRUE in the future. Therefore, we DO check    */
  1192.     /* to see if the entry passed to us describes a file or a directory.    */
  1193.  
  1194.     if ((p->fileParam.ioFlAttrib & 0x10) != 0)
  1195.         return(false);
  1196.     return(true);
  1197. };
  1198.  
  1199.  
  1200. void doGetDirectory()
  1201. {
  1202.     Str255    s;
  1203.  
  1204.     SFPGetFile(gLocation,    /* location */
  1205.         "\pSpace for Rent",    /* vestigial string */
  1206.         FoldersOnly,        /* fileFilter */
  1207.         -1,                    /* numtypes; -1 means all */
  1208.         &typeList,            /* array to types to show */
  1209.         MyGetDirHook,        /* dlgHook */
  1210.         &reply,                /* record for returned values */
  1211.         rGetDirectoryDLOG,
  1212.         nil);
  1213.  
  1214.     if (CurDirValid)
  1215.         ShowSelection(PathNameFromDirID(MyCurDir,-*(short *)SFSaveDisk,&s));
  1216. };
  1217.  
  1218.  
  1219. /** MultiFile *****************************************************************/
  1220. /*
  1221. /*    WARNING! The following is an experiment that failed! I was playing around
  1222. /*            with different ways for implementing a facility for multiple
  1223. /*            file handling. This was one of them. As with all experiments that
  1224. /*            fail, it's pretty ugly. The following should NOT be used as a
  1225. /*            sample on how to do things right, but as a lesson on how NOT to
  1226. /*            do things.
  1227. /*
  1228. /*    This sample is an attempt to implement the facilty to let the user choose
  1229. /*    more than one file at once. It does this by remembering all of the files
  1230. /*    passed to the fileFilter, and using those names to create a new, application
  1231. /*    governed filename list that will replace the one displayed by Standard File.
  1232. /*    However, this experiment seems doomed to failure, for the following reasons:
  1233. /*
  1234. /*        - Icons are not displayed next to the names of files. The resulting
  1235. /*            presentation is more disconcerting that I thought.
  1236. /*        - The current directory button (the one displayed above the list of
  1237. /*            filenames) disappears. This seems due to the fact that that pop-up
  1238. /*            menu-like item is not actually a dialog item, and is somehow attached
  1239. /*            to the list of filenames handled by Standard File. Since I move that
  1240. /*            list off of the window so that I can display my own, the current
  1241. /*            directory button seems to go with it.
  1242. /*        - Updating the list is difficult. The normal sequence of events would
  1243. /*            desirably be: 1) have the fileFilter create a linked list of names
  1244. /*            to appear in the dialog, 2) insert those names into the List Manager
  1245. /*            list, and 3) display the list. However, by following this outine,
  1246. /*            the list does not get updated, as the update region for that area
  1247. /*            is empty. So, at some time, we need to invalidate the area
  1248. /*            that holds our list. But there is no convenient place in which to
  1249. /*            do that. We can't call InvalRect within our fileFilter routine, as
  1250. /*            we don't have the DialogPtr at the time (needed to get the bounding
  1251. /*            rectangle of the list item). Neither can we call InvalRect within
  1252. /*            step 2, as we are between BeginUpdate/EndUpdates, and calling
  1253. /*            InvalRect would be useless.
  1254. /*        - We can't display directories. They are not offered to the judgement
  1255. /*            of our fileFilter, and hence cannot be added to our list.
  1256. /*        - Key presses would have do be handled manually by our dlgHook.
  1257. /*
  1258. /*    Our thesis is implemented as described, with all of the above limitations.
  1259. /*    What follows is a description of the routines used:
  1260. /*
  1261. /*    ScoopNames - The fileFilter routine that is responsible for remembering
  1262. /*        the names of all the files passed to it by Standard file. The names
  1263. /*        are stored in a singly linked list of handles. Each handle contains
  1264. /*        a Str255 to hold the name, and room for the handle for the next name.
  1265. /*        The root of the list is stored in the global variable 'firstName'.
  1266. /*        The list is used by SetNames to set the data for the list cells.
  1267. /*
  1268. /*    MultiFileHook - Creates the list and installs it as a user item. Also,
  1269. /*        we check on NULL events to see if our list needs to be updated. If
  1270. /*        so, we call InvalRect to force it to be redrawn. Finally, this
  1271. /*        routine disposes of the list if Open or Cancel are pressed.
  1272. /*
  1273. /*    MFModalFilter - Calls LClick for MouseDowns on the list.
  1274. /*
  1275. /*    MFDrawList - Redraws the list. If there are new names to be displayed, then
  1276. /*        they are inserted into the cells. Then LUpdate is called to redraw
  1277. /*        the list.
  1278. /*
  1279. /*    SetNames - Called by MFDrawList when FirstName is != NIL, indicating that
  1280. /*        there are new names to be displayed. We first get a count of the
  1281. /*        number of names in the linked list, and the List Manager list is
  1282. /*        adjusted accordingly. Then, the linked list is traversed, with each
  1283. /*        name being inserted into its appropriate cell. The nodes of the
  1284. /*        linked list are disposed of along the way. When everything is done,
  1285. /*        the List Manager list is appropriately set up, and the linked list is
  1286. /*        disposed of, with FirstName== NIL.
  1287. /*
  1288. /******************************************************************************/
  1289.  
  1290.  
  1291. void SetNames()
  1292. {
  1293.     StringHolderHdl    nextName;
  1294.     short            count;
  1295.     short            delta;
  1296.     Cell            theCell;
  1297.  
  1298.     nextName = firstName;
  1299.     count = 0;
  1300.     if (nextName != nil) {
  1301.         do {
  1302.             count++;
  1303.             nextName = (*nextName)->link;
  1304.         } while (nextName != nil);
  1305.     };
  1306.     
  1307.     delta = count - (*LHandle)->dataBounds.bottom;
  1308.     if (delta < 0)            /* need to remove this many cells */
  1309.         LDelRow(-delta,0,LHandle);
  1310.     else if (delta > 0)        /* need to add this many cells */
  1311.         LAddRow(delta,(*LHandle)->dataBounds.bottom,LHandle);
  1312.         
  1313.     theCell.h = 0;
  1314.     theCell.v = count-1;
  1315.     do {
  1316.         HLock((Handle)firstName);
  1317.         LSetCell((*firstName)->title+1, *((*firstName)->title), theCell, LHandle);
  1318.         HUnlock((Handle)firstName);
  1319.         nextName = (*firstName)->link;
  1320.         DisposHandle((Handle)firstName);
  1321.         firstName = nextName;
  1322.         theCell.v--;
  1323.     } while (firstName != nil);
  1324.     namesChanged = false;
  1325. };
  1326.  
  1327.  
  1328. pascal void MFDrawList(theWindow, item)
  1329.     WindowPtr    theWindow;
  1330.     short        item;
  1331. {
  1332. #define ListItem 11
  1333.  
  1334.     short    kind;
  1335.     Handle    h;
  1336.     Rect    r;
  1337.  
  1338.     if (item == ListItem) {
  1339.         GetDItem(theWindow,ListItem,&kind,&h,&r);
  1340.         EraseRect(&r);
  1341.         FrameRect(&r);
  1342.         LUpdate(theWindow->visRgn,LHandle);
  1343.         if (namesChanged)
  1344.             SetNames();
  1345.     };
  1346. };
  1347.  
  1348. pascal Boolean MFModalFilter(theDialog, theEvent, itemHit)
  1349.     DialogPtr    theDialog;
  1350.     EventRecord    *theEvent;
  1351.     short        *itemHit;
  1352. {
  1353.  
  1354. #pragma unused (theDialog, itemHit)
  1355.  
  1356.     Boolean    doubleClick;
  1357.     Point    localPt;
  1358.     
  1359.     if (theEvent->what == mouseDown) {
  1360.         localPt = theEvent->where;
  1361.         GlobalToLocal(&localPt);
  1362.         doubleClick = LClick(localPt,theEvent->modifiers,LHandle);
  1363.         /* should check for double clicks here */
  1364.     };
  1365.     return(false);
  1366. };
  1367.  
  1368.  
  1369. pascal short MultiFileHook(item, dPtr)
  1370.     short        item;
  1371.     DialogPtr    dPtr;
  1372. {
  1373. #define ListItem 11
  1374.  
  1375.     short    kind;
  1376.     Handle    h;
  1377.     Rect    r;
  1378.     Rect    rView, dataBounds;
  1379.  
  1380.     switch (item) {
  1381.         case firstTime:
  1382.             GetDItem(dPtr,ListItem,&kind,&h,&r);
  1383.             SetDItem(dPtr,ListItem,kind,(Handle)MFDrawList,&r);
  1384.             rView = r;
  1385.             rView.right -= 15;
  1386.             InsetRect(&rView,1,1);
  1387.             dataBounds.top = 0;
  1388.             dataBounds.left = 0;
  1389.             dataBounds.bottom = 0;
  1390.             dataBounds.right = 1;
  1391.             LHandle = LNew(&rView,    /* position in window */
  1392.                 &dataBounds,        /* initial size of array */
  1393.                 gZeroPt,            /* cell size (0 = default) */
  1394.                 0L,                    /* resource ID of LDEF */
  1395.                 dPtr,                /* window pointer */
  1396.                 true,                /* drawit */
  1397.                 false,                /* has grow */
  1398.                 false,                /* scrollHoriz */
  1399.                 true);                /* scrollVert */
  1400.             break;
  1401.             
  1402.         case 100:
  1403.             if (needToUpdate) {
  1404.                 GetDItem(dPtr,ListItem,&kind,&h,&r);
  1405.                 InvalRect(&r);
  1406.                 needToUpdate = false;
  1407.             };
  1408.             break;
  1409.             
  1410.         case getOpen:
  1411.         case getCancel:
  1412.             LDispose(LHandle);    /* Our linked list of names was already disposed */
  1413.                                 /* of in SetNames, so nothing else to do here... */
  1414.             break;
  1415.     };
  1416.     return(item);
  1417. };
  1418.  
  1419. pascal Boolean ScoopNames(pb)
  1420.     ParmBlkPtr    pb;
  1421. {
  1422.     StringHolderHdl    nextName;
  1423.     
  1424.     nextName = (StringHolderHdl)NewHandle(sizeof(StringHolder));
  1425.     HLock((Handle)nextName);
  1426.     pStrcpy(&(*nextName)->title, pb->fileParam.ioNamePtr);
  1427.     (*nextName)->link = firstName;
  1428.     firstName = nextName;
  1429.     HUnlock((Handle)nextName);
  1430.     namesChanged = true;
  1431.     needToUpdate = true;
  1432.  
  1433.     return(false); /*show everything*/
  1434. };
  1435.  
  1436. void doMultiFile()
  1437. {
  1438.     firstName = nil;
  1439.     namesChanged = false;
  1440.     SFPGetFile(gLocation,    /* location */
  1441.         "\pSpace for Rent",    /* vestigial string */
  1442.         ScoopNames,            /* fileFilter */
  1443.         -1,                    /* numtypes; -1 means all */
  1444.         &typeList,            /* array to types to show */
  1445.         MultiFileHook,        /* dlgHook */
  1446.         &reply,                /* record for returned values */
  1447.         rMultiFileDLOG,        /* ID of Custom Dialog */
  1448.         MFModalFilter);        /* ModalDialog filterProc */
  1449. };
  1450.  
  1451.  
  1452. /** doListsNPutFile ***********************************************************/
  1453. /*
  1454. /*    This sample demonstrates putting a List Manager list into an SFPPutFile
  1455. /*    dialog, describes a problem that can occur by doing so, and shows one
  1456. /*    solution to the problem.
  1457. /*
  1458. /*    Putting another list into a Standard File dialog can be useful for a number
  1459. /*    of reasons. One of them was shown above, where we attempted to replace
  1460. /*    Standard File's list with one of our own. However, the failure we met with
  1461. /*    there forces us to look for another solution. This solution could possibly
  1462. /*    take place in the form of TWO lists: one supplied by Standard file, and the
  1463. /*    other handled by us to hold the multiple file names.
  1464. /*
  1465. /*    While the actual logistics of putting the selected filenames into the list
  1466. /*    as they are selected is left to you, the Programmer, I do show how to
  1467. /*    create and handle the list.
  1468. /*
  1469. /*    While *I* can't think of a reason for wanting to put a second list into
  1470. /*    a PutFile dialog, it is possible that you may wish to. However, there is
  1471. /*    a subtle problem that arises if you do: that of determining when to
  1472. /*    dispose of the list. In the MultiFile example above, we were perfectly
  1473. /*    justified in disposing of the list when the user clicked on Open or
  1474. /*    Cancel. However, in the following example, if were to dispose of the list
  1475. /*    when the user clicked on Save, we may hit a snag. It is possible for the
  1476. /*    user to specify the name of a file that alreay exists. When that happens,
  1477. /*    s/he is presented with another dialog that asks if they are sure of what
  1478. /*    they are doing. At that point, the user could press Cancel, and return us
  1479. /*    to the PutFile dialog, SANS our 2nd list! Neither can we defer disposing
  1480. /*    of list until Standard File is done and returns to our application, as the
  1481. /*    Dialog box we were using is gone, and the List Manager try to perform an
  1482. /*    InvalRect on a non-existant window when it erases its list.
  1483. /*
  1484. /*    There are two solutions to this problem:
  1485. /*
  1486. /*        1) Implement the list as a Custom Control. Then, when it somes time to
  1487. /*            close the dialog box, the custom control will get called with a
  1488. /*            dispCntl message. It can take that opportunity to call LDispose.
  1489. /*
  1490. /*            This technique is not presented here, as it is more appropriate
  1491. /*            for a Custom Control Sample program.  However, it is the suggested
  1492. /*            way to proceed.
  1493. /*
  1494. /*        2) Ensure that the confirmation dialog box never comes up under Standard
  1495. /*            File's control. By calling it ourself, we know what the user chooses.
  1496. /*            If the user presses OK, we delete the offending file, dispose of our
  1497. /*            list, and return to Standard File. If the user presses Cancel, we
  1498. /*            change the click on the Save button into a NULL event. This is the
  1499. /*            algorithm we use below.
  1500. /*
  1501. /*            There is a major disadvantage with this approach, however. With it,
  1502. /*            we cannot implement a safe saving procedure. Normally, the best way
  1503. /*            to save a file (disk space permitting), is to save the data to a
  1504. /*            temporary file, delete the original, and then rename the temporary
  1505. /*            file to that of the original. However, with approach #2, the file
  1506. /*            is deleted well before it is safe to do so.
  1507. /*
  1508. /*            This routine *could* be reworked to avoid this problem. For instance,
  1509. /*            instead of being deleted, the offending file could be renamed to
  1510. /*            something else or moved to another directory.
  1511. /*
  1512. /******************************************************************************/
  1513.  
  1514. pascal void LNPFDrawList(theWindow, item)
  1515.     WindowPtr    theWindow;
  1516.     short        item;
  1517. {
  1518. #define ListItem 9
  1519.  
  1520.     short    kind;
  1521.     Handle    h;
  1522.     Rect    r;
  1523.  
  1524.     if (item == ListItem) {
  1525.         GetDItem(theWindow,ListItem,&kind,&h,&r);
  1526.         FrameRect(&r);
  1527.         LUpdate(theWindow->visRgn,LHandle);
  1528.     };
  1529. };
  1530.  
  1531. pascal Boolean LNPFModalFilter(theDialog, theEvent, itemHit)
  1532.     DialogPtr    theDialog;
  1533.     EventRecord    *theEvent;
  1534.     short        *itemHit;
  1535. {
  1536. #pragma unused(theDialog, itemHit)
  1537.     Point    localPt;
  1538.  
  1539.     if (theEvent->what == mouseDown) {
  1540.         localPt = theEvent->where;
  1541.         GlobalToLocal(&localPt);
  1542.         LClick(localPt,theEvent->modifiers,LHandle);
  1543.     };
  1544.     return(false);
  1545. };
  1546.  
  1547.  
  1548. pascal short ListsNPutFileHook(item, dPtr)
  1549.     short        item;
  1550.     DialogPtr    dPtr;
  1551. {
  1552. #define ListItem 9
  1553. #define ExistingFileALRT -3996
  1554.  
  1555.     short        kind;
  1556.     Handle        h;
  1557.     Rect        r;
  1558.     Rect        rView, dataBounds;
  1559.     Str255        Entry;
  1560.     Cell        theCell;
  1561.     short        choice;
  1562.     FInfo        fndrInfo;
  1563.     WDPBRec        WDRec;
  1564.     AlertTHndl    AlertHandle;
  1565.     Point        dLoc;
  1566.     Rect        *tPtr;
  1567.  
  1568.     switch (item) {
  1569.         case firstTime:
  1570.             GetDItem(dPtr,ListItem,&kind,&h,&r);
  1571.             SetDItem(dPtr,ListItem,kind,(Handle)LNPFDrawList,&r);
  1572.             rView = r;
  1573.             rView.right = rView.right - 15;
  1574.             InsetRect(&rView,1,1);
  1575.             dataBounds.top = 0;
  1576.             dataBounds.left = 0;
  1577.             dataBounds.bottom = 0;
  1578.             dataBounds.right = 1;
  1579.             LHandle = LNew(&rView,    /* position in window */
  1580.                 &dataBounds,        /* initial size of array */
  1581.                 gZeroPt,            /* cell size (0== default) */
  1582.                 0L,                    /* resource ID of LDEF */
  1583.                 dPtr,                /* window pointer */
  1584.                 true,                /* drawit */
  1585.                 false,                /* has grow */
  1586.                 false,                /* scrollHoriz */
  1587.                 true);                /* scrollVert */
  1588.             theCell = gZeroPt;
  1589.             do {
  1590.                 GetIndString(&Entry,rStrList,theCell.v + 1);
  1591.                 if (*Entry != 0) {
  1592.                     LAddRow(1,(*LHandle)->dataBounds.bottom,LHandle);
  1593.                     LSetCell(Entry+1,*Entry,theCell,LHandle);
  1594.                 };
  1595.                 theCell.v += 1;
  1596.             } while (*Entry != 0);
  1597.             break;
  1598.  
  1599.         case putSave:
  1600.             WDRec.ioNamePtr = nil;
  1601.             WDRec.ioVRefNum = - *(short *)SFSaveDisk;
  1602.             WDRec.ioWDProcID = 'ERIK';
  1603.             WDRec.ioWDDirID = *(long *)CurDirStore;
  1604.             PBOpenWD(&WDRec,false);
  1605.  
  1606.             err = GetFInfo(&reply.fName,WDRec.ioVRefNum,&fndrInfo);
  1607.             if (err == noErr) {
  1608.                 ParamText(&reply.fName,nil,nil,nil);
  1609.  
  1610.                 /* Before bringing up the Alert that asks for confirmation, we        */
  1611.                 /* have to relocate it. To start with, the window is located at        */
  1612.                 /* 0,0 in the Window Manager port. To move the Alert Window, read    */
  1613.                 /* the resource into memory and change the boundsRect field so        */
  1614.                 /* that its topleft is at 12,100 within Standard File's dialog.        */
  1615.     
  1616.                 AlertHandle = (AlertTHndl)GetResource('ALRT',ExistingFileALRT);
  1617.                 dLoc = TopLeft(dPtr->portRect);        /* get global location of SF's */
  1618.                 LocalToGlobal(&dLoc);                /* dialog box. */
  1619.                 tPtr = &((*AlertHandle)->boundsRect);
  1620.                 tPtr->right        -= tPtr->left;    /* get width and height of the Alert */
  1621.                 tPtr->bottom    -= tPtr->top;    /* into botRight. */
  1622.                 tPtr->left        = dLoc.h + 12;    /* Change Alert.TopLeft to SF.Dlog.TopLeft */
  1623.                 tPtr->top        = dLoc.v + 100;    /* plus 12,100. */
  1624.                 tPtr->right        += tPtr->left;    /* Adjust Alert.BotRight accordingly. */
  1625.                 tPtr->bottom    += tPtr->top;
  1626.                 choice = Alert(ExistingFileALRT,nil);
  1627.                 if (choice == cancel) {  /* the OK button is in the Cancel slot (item #2) */
  1628.                     FSDelete(&reply.fName,WDRec.ioVRefNum);
  1629.                     LDispose(LHandle);
  1630.                 } else {
  1631.                     return(100);    /* Change "Save" into null event */
  1632.                 };
  1633.             }
  1634.             break;
  1635.         case putCancel:
  1636.             LDispose(LHandle);
  1637.             break;
  1638.     }; /* switch */
  1639.     return(item);
  1640. };
  1641.  
  1642.  
  1643. void doListsNPutFile()
  1644. {
  1645.     short    vRefNum;
  1646.  
  1647.     GetVol(nil,&vRefNum);
  1648.     Create("\pDoug",vRefNum,'KAAR','APPL');
  1649.  
  1650.     SFPPutFile(gLocation,        /* location */
  1651.         "\pSave document as:",    /* prompt string */
  1652.         "\pDoug",                /* original name */
  1653.         ListsNPutFileHook,        /* dlgHook */
  1654.         &reply,                    /* record for returned values */
  1655.         rListsNPutFileDLOG,        /* ID of custom Dialog */
  1656.         LNPFModalFilter);        /* ModalDialog filterProc */
  1657. };
  1658.  
  1659.  
  1660. /** doPutOptions **************************************************************/
  1661. /*
  1662. /*    Apple's suggested options box - A modest proposal.
  1663. /*
  1664. /*    With the proliferation of applications and different file types, many
  1665. /*    developers are allowing the user to select from various file formats they
  1666. /*    want to save their data as. In an effort to maintain consistancy among
  1667. /*    all applications in this respect, we are presenting a sample interface for
  1668. /*    letting the user select the file type they want.
  1669. /*
  1670. /*    This is done by adding 2 items to the PutFile dialog box. One is a text
  1671. /*    item that displays the file format that the file will be saved as. The
  1672. /*    second is an "Options╔" button that brings up another dialog box. This
  1673. /*    dialog box conatins a series of radio buttons next to the names of the
  1674. /*    various file formats supported by the application. Also in the second
  1675. /*    dialog box are OK and Cancel buttons. After a selection has been made
  1676. /*    by the user, the text in the PutFile dialog is updated accordingly.
  1677. /*
  1678. /******************************************************************************/
  1679.  
  1680. void SetFormatString(number, theDialog)
  1681.     short        number;
  1682.     DialogPtr    theDialog;
  1683. {
  1684. #pragma unused(number)
  1685. #define FormatString 10
  1686.     short    kind;
  1687.     Handle    h;
  1688.     Rect    r;
  1689.     Str255    title;
  1690.  
  1691.     GetDItem(OptionsDPtr,OptionNumber,&kind,&h,&r);
  1692.     GetCTitle((ControlHandle)h,&title);
  1693.     GetDItem(theDialog,FormatString,&kind,&h,&r);
  1694.     SetIText(h,&title);
  1695. };
  1696.  
  1697.  
  1698. pascal DrawFrame(theWindow, itemNo)
  1699.     WindowPtr    theWindow;
  1700.     short        itemNo;
  1701. {
  1702.     short        kind;
  1703.     Handle        h;
  1704.     Rect        r;
  1705.     PenState    ps;
  1706.  
  1707.     GetDItem(theWindow,itemNo,&kind,&h,&r);
  1708.     GetPenState(&ps);
  1709.     PenSize(2,2);
  1710.     FrameRect(&r);
  1711.     SetPenState(&ps);
  1712. };
  1713.  
  1714. void doOptionsDialog(item)
  1715.     short    *item;
  1716. {
  1717.     short    newItem;
  1718.     short    itemHit;
  1719.  
  1720.     newItem = OptionNumber;
  1721.     SelectWindow(OptionsDPtr);
  1722.     ShowWindow(OptionsDPtr);
  1723.     do {
  1724.         ModalDialog(nil,&itemHit);
  1725.         if ((itemHit != newItem) && (itemHit > 2)) {
  1726.             SetRadioButton(OptionsDPtr,newItem,BTNOFF);
  1727.             SetRadioButton(OptionsDPtr,itemHit,BTNON);
  1728.             newItem = itemHit;
  1729.         }
  1730.     } while ((itemHit != ok) && (itemHit != cancel));
  1731.     HideWindow(OptionsDPtr);
  1732.     if (itemHit == ok)
  1733.         *item = newItem;
  1734. };
  1735.  
  1736. pascal short PutOptionsHook(item, theDialog)
  1737.     short        item;
  1738.     DialogPtr    theDialog;
  1739. {
  1740. #define FirstRadioButton 3
  1741. #define OptionsButton 9
  1742. #define FrameItem 10
  1743.  
  1744.     short    kind;
  1745.     Handle    h;
  1746.     Rect    r;
  1747.  
  1748.     switch (item) {
  1749.         case firstTime:
  1750.             OptionNumber = FirstRadioButton;
  1751.             SetRadioButton(OptionsDPtr,OptionNumber,BTNON);
  1752.             SetFormatString(OptionNumber,theDialog);
  1753.             GetDItem(OptionsDPtr,FrameItem,&kind,&h,&r);
  1754.             SetDItem(OptionsDPtr,FrameItem,kind,(Handle)DrawFrame,&r);
  1755.             break;
  1756.         case OptionsButton:
  1757.             doOptionsDialog(&OptionNumber);
  1758.             SetFormatString(OptionNumber,theDialog);
  1759.             break;
  1760.     };
  1761.     return(item);
  1762. };
  1763.  
  1764.  
  1765. void doPutOptions()
  1766. {
  1767.     OptionsDPtr = GetNewDialog(rOptionsSubDLOG,nil,(WindowPtr) -1L);
  1768.     SFPPutFile(gLocation,        /* location */
  1769.         "\pSave document as:",    /* prompt string */
  1770.         "\pDoug",                /* original name */
  1771.         PutOptionsHook,            /* dlgHook */
  1772.         &reply,                    /* record for returned values */
  1773.         rOptionsDLOG,            /* ID of custom Dialog */
  1774.         nil);                    /* ModalDialog filterProc */
  1775.     DisposDialog(OptionsDPtr);
  1776. };
  1777.  
  1778. /** doIdleUpdates *************************************************************/
  1779. /*
  1780. /*    There is a problem that Standard File has with updates pending in
  1781. /*    background windows in the current application partition.
  1782. /*
  1783. /*    Standard File calls ModalDialog at the heart of its Main Event Loop.
  1784. /*    ModalDialog calls GetNextEvent, and then calls a filterProc internal
  1785. /*    to Standard File. This filterProc performs some processing on the event,
  1786. /*    (like handling hits on the filename list, the Current Directory button, and
  1787. /*    the Disk Name Icon), and then calls the filterProc specified in SFPxxxFile
  1788. /*    calls.
  1789. /*
  1790. /*    Another of the things that the internal filterProc does is look for NULL
  1791. /*    events. When one is found, the filterProc returns 100 as the "itemHit".
  1792. /*    This forces ModalDialog to return to SF, which in turn can now call
  1793. /*    the dlgHook with the bogus 100 item number, indicating that idle time
  1794. /*    processing can be performed.
  1795. /*
  1796. /*    The problem occurs when there are updates pending in windows open in
  1797. /*    the current application's partition. These updates will 'clog' the event
  1798. /*    queue. When ModalDialog calls GetNextEvent, it will get the update
  1799. /*    event. However, there is no way for ModalDialog to respond to it. It
  1800. /*    therefore passes the event off to the filterProc, which is SF's internal
  1801. /*    one. This filterProc doesn't know how to handle it either. Normally,
  1802. /*    the event would be ignored at this point, and the update event would be
  1803. /*    unresolved. Control returns back to ModalDialog, which calls GetNextEvent
  1804. /*    again, and gets the same udpate event back. NULL events will not get
  1805. /*    returned by GetNextEvent, and, hence, the dlgHook will never get called
  1806. /*    with itemHi==100.
  1807. /*
  1808. /*    This situation can be solved by providing your own filterProc
  1809. /*    procedure to be called after SF's internal filterProc. It will be this
  1810. /*    routine's responsibility to check for update events, and handle them
  1811. /*    appropriately. Usually, this would mean that the update procedure
  1812. /*    within the application would be called, and the update could be cleared.
  1813. /*    However, the filterProc could also just handle update events in the same
  1814. /*    way that Standard File's filterProc handles NULL events. This is done
  1815. /*    by returning 100 in the ItemHit parameter and TRUE as the function result.
  1816. /*    This is the approach taken by the sample below.
  1817. /*
  1818. /******************************************************************************/
  1819.  
  1820.  
  1821. /* This is our dlgHook routine. All it does is wait around for NULL events    */
  1822. /* and draws the current time when it gets one. Note that this routine will    */
  1823. /* NOT get called with ite==100 if we did not have the filter procedure        */
  1824. /* below. */
  1825.  
  1826. pascal short IdleTimeHook(item, dlg)
  1827.     short        item;
  1828.     DialogPtr    dlg;
  1829. {
  1830. #pragma unused(dlg)
  1831.     unsigned long    dateTime;
  1832.     Str255    result;
  1833.  
  1834.     if (item == 100) {
  1835.         TextMode(srcCopy);
  1836.         MoveTo(250,15);
  1837.         GetDateTime(&dateTime);
  1838.         IUTimeString(dateTime, /* wantSecond == */ true, &result);
  1839.         pStrcat(&result,"\p  "); /*pad with spaces to erase longer strings*/
  1840.         DrawString(&result);
  1841.     };
  1842.     return(item);
  1843. };
  1844.  
  1845. pascal Boolean filter(dlg, evt, itemHit)
  1846.     DialogPtr    dlg;
  1847.     EventRecord    *evt;
  1848.     short        *itemHit;
  1849. {
  1850.     /* If we get an update event for a window other than the dialog box, change */
  1851.     /* it to a NULL event, and tell ModalDialog that we handled it. */
  1852.  
  1853.     if ((evt->what == updateEvt) && (evt->message != dlg)) {
  1854.         *itemHit = 100;
  1855.         return(true);
  1856.     };
  1857.     return(false);
  1858. };
  1859.  
  1860. void doIdleUpdates()
  1861. {
  1862.     Rect        r;
  1863.     WindowPtr    tPtr;
  1864.  
  1865.     /* Create a window with a non-empty update region. */
  1866.  
  1867.     r.top = 40; r.left = 10; r.bottom = 340; r.right = 500;
  1868.  
  1869.     tPtr = NewWindow(nil,        /* Window storage */
  1870.         &r,                        /* bounding rectangle */
  1871.         "\pUn-updated Window",
  1872.         true,                    /* is visible */
  1873.         documentProc,            /* procID */
  1874.         (WindowPtr)-1L,            /* bring it up on top */
  1875.         true,                    /* has goaway */
  1876.         0L);                    /* refcon */
  1877.  
  1878.     SFPGetFile(gLocation,        /* location */
  1879.         "\pSpace for Rent",        /* vestigial string */
  1880.         nil,                    /* fileFilter */
  1881.         -1,                        /* numtypes; -1 means all */
  1882.         &typeList,                /* array to types to show */
  1883.         IdleTimeHook,            /* dlgHook */
  1884.         &reply,                    /* record for returned values */
  1885.         -4000,                    /* ID of Normal Dialog */
  1886.         filter);                /* ModalDialog filterProc */
  1887.  
  1888.     DisposeWindow(tPtr);
  1889. };
  1890.  
  1891.  
  1892. /** doForceDirectory **********************************************************/
  1893. /*
  1894. /*    This is a quick sample that shows how to set the initial directory that
  1895. /*    Standard File comes up with. Basically, this is done by storing apporpriate
  1896. /*    values into SFSaveDisk and CurDirStore. In this example, I force the
  1897. /*    directory to be the Blessed Folder as specified by SysEnvirons.
  1898. /*
  1899. /******************************************************************************/
  1900.  
  1901.  
  1902. void doForceDirectory()
  1903. {
  1904.     WDPBRec    pb;
  1905.     Str255    s;
  1906.  
  1907.     pb.ioNamePtr = nil;
  1908.     pb.ioVRefNum = theWorld.sysVRefNum;
  1909.     pb.ioWDIndex = 0;
  1910.     pb.ioWDProcID = 0;
  1911.     PBGetWDInfo(&pb,false);
  1912.  
  1913.     *(long *)CurDirStore = pb.ioWDDirID;
  1914.     *(short *)SFSaveDisk = -pb.ioWDVRefNum;
  1915.  
  1916.     SFGetFile(gLocation,        /* location */
  1917.         "\pSpace for Rent",        /* vestigial string */
  1918.         nil,                    /* fileFilter */
  1919.         -1,                        /* numtypes; -1 means all */
  1920.         &typeList,                /* array to types to show */
  1921.         nil,                    /* dlgHook */
  1922.         &reply);                /* record for returned values */
  1923.  
  1924.     if (reply.good )
  1925.         ShowSelection(pStrcat(PathNameFromWD(reply.vRefNum,&s),reply.fName));
  1926.     else
  1927.         ShowCancelled();
  1928. };
  1929.