home *** CD-ROM | disk | FTP | other *** search
/ Otherware / Otherware_1_SB_Development.iso / mac / developm / scnote / stdfile.018 / StdFile.p < prev    next >
Encoding:
Text File  |  1989-04-02  |  65.3 KB  |  1,913 lines

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