Documentation Archive Developer
Search

ADC Home > Reference Library > Technical Notes > Legacy Documents > User Experience >

Legacy Documentclose button

Important: This document is part of the Legacy section of the ADC Reference Library. This information should not be used for new development.

Current information on this Reference Library topic can be found here:

Standard File Customization

CONTENTS

This note contains an example program that demonstrates how SFPGetFile can be customized using the dialog hook and file filter functions.

[Oct 01 1995]






Introduction

SFPGetFile's dialog hook function and file filter function enable you to customize SFPGetFile's behavior to fit the needs of your application. This technical note consists primarily of a short example program that

1) changes the title of the Open button to 'MyOpen',

2) adds two radio buttons so that the user can choose to display either text files or text files and applications,

3) adds a quit button to the SFPGetFile dialog.

All this is done in a way so as to provide compatibility with the Macintosh File System (MFS), the Hierarchical File System (HFS) and (hopefully) future systems. If you have any questions as you read, the complete source of the demo program and the resource compiler input file is provided at the end of this technical note.

Basically, we need to do three things: add our extra controls to the resource compiler input file, write a dialog hook function, and write a file filter function.

Back to top

Modifying the Resource Compiler Input File

First we need to define a dialog in our resource file. It will be DLOG #128:

    CONST myDLOGID = 128;

and its Rez description is:

    resource 'DLOG' (128, purgeable) {
        {0, 0, 200, 349},
        dBoxProc, invisible, noGoAway,
        0x0,
        128,
        "MyGF"
    };

The above coordinates (0 0 200 349) are from the standard Standard File dialog. If you need to change the size of the dialog to accommodate new controls, change these coordinates. Next we need to add a DITL in our resource file that is the same as the standard HFS DITL #-4000 except for one item. We need to change the left coordinate of UserItem #4, or part of the dialog will be hidden if we're running under MFS:

    /* [4] */
    /* left coordinate changed from 232 to 252 so program will
       work on MFS */
    {39, 252, 59, 347},
    UserItem {
         disabled
    };

None of the other items of the DITL should be changed, so that your program will remain as compatible as possible with different versions of Standard File. Finally, we need to add three items to this DITL, two radio buttons and one button (to serve as a quit button)

    /* [11] textButton */
    {1, 14, 20, 142},
    RadioButton {
        enabled,
        "Text files only"
    };
    /* [12] textAppButton */
    {19, 14, 38, 176},
    RadioButton {
        enabled,
        "Text and applications"
    };
    /* [13] quitButton */
    {6, 256, 24, 336},
    Button {
        enabled,
        "Quit"
    }

Because we've added three items, we need also need to change the item count for the DITL from 10 to 13. We also include the following in our resource file:

resource 'STR#' (256) {
    {/* array StringArray: 1 elements */
        /* [1] */
        "MyOpen"
    }
};

That's all there is to modify in the resource file.

Back to top

The Dialog Hook

We will be calling SFPGetFile as follows:

    SFPGetFile (wher, '', @SFFileFilter, NumFileTypes, MyFileTypes, @MySFHook,
reply, myDLOGID,nil);

Notice that we're passing @MySFHook to Standard File. This is the address of our dialog hook routine. Our dialog hook is declared as:

    FUNCTION MySFHook(MySFitem: INTEGER; theDialog: DialogPtr):INTEGER;

A dialog hook routine allows us to see every item hit before standard file acts on it. This allows us to handle controls that aren't in the standard SFPGetFile's DITL or to handle standard controls in non-standard ways. The dialog hook in this example consists of a case statement with MySFitem as the case selector. Before SFPGetFile displays its dialog, it calls our dialog hook, passing it a -1 as MySFitem. This gives us a chance to initialize our controls. Here we will set the textAppButton to off and the textButton to on:

    GetDItem(theDialog,textAppButton,itemType,itemToChange,itemBox);
     SetCtlValue(controlHandle(itemToChange),btnOff);
     GetDItem(theDialog,textButton,itemType,itemToChange,itemBox);
     SetCtlValue(controlHandle(itemToChange),btnOn);
 

and we can also change the title of an existing control. Here's how we might change the title of the Open button using a string that we get from a resource file:

    GetIndString(buttonTitle,256,1);
     If buttonTitle <> '' then Begin { if we really got the resource}
         GetDItem(theDialog,getOpen,itemType,itemToChange,itemBox);
         SetCtitle(controlHandle(itemToChange),buttonTitle);
    End; {if} {if we didn't get the resource, don't change the title}

Upon completion of our routine that handles the -1, we return a -1 to standard file:

MySFHook:= MySFItem; {pass back the same item we were sent}

We now have a SFPGetFile dialog displayed that has a quit button and two radio buttons (the textOnly button is on, the TextApp button is off). In addition, the standard Open button has been renamed to MyOpen (or whatever STR is the first string in STR# 256). This was all done before SFPGetFile displayed the dialog. Once our hook is exited, SFPGetFile displays the dialog and calls ModalDialog.

When the user clicks on an item in the dialog, our hook is called again. We can then take appropriate actions, such as highlighting the textButton and un-highlighting the textAppButton if the user clicks on the textButton. At this time, we can also update a global variable (textOnly) that we will use in our file filter function to tell us which files to display. Notice that we can redisplay the file list by returning a 101 as the result of MySFHook. (Standard File for Systems newer than 4.3 will also read the low memory globals, CurDirStore and SFSaveDisk, and switch directories when necessary if a 101 is returned as the result. Thus, you can point Standard File to a new directory, or a new disk.) For example, when the textButton is hit we turn the textAppButton off, turn the textButton on, update the global variable textOnly, and tell SFPGetFile to redisplay the list of files the user can choose from:

    if not textOnly then Begin  {if textOnly was turned off, turn it on now}
         GetDItem(theDialog,textAppButton,itemType,itemToChange,itemBox);
         SetCtlValue(controlHandle(itemToChange),btnOff);
         GetDItem(theDialog,textButton,itemType,itemToChange,itemBox);
         SetCtlValue(controlHandle(itemToChange),btnOn);
         textOnly:=TRUE;     {toggle our global variable for use in the filter}
         MySFHook:= reDrawList;{101}       {we must tell SF to redraw the list}
     End;  {if not textOnly}

If our quit button is hit, we can pass SFPGetFile back the cancelbutton:

    MySFHook:= getCancel;

If one of SFPGetFile's standard items is hit, it is very important to pass that item back to SFPGetFile:

    MySFHook:= MySFItem; {pass back the same item we were sent}

Back to top

The File Filter

Remember, we called SFPGetFile as follows:

    SFPGetFile (wher, '', @SFFileFilter, NumFileTypes,
                 MyFileTypes, @MySFHook, reply,myDLOGID,nil);

Notice that we're passing @SFFileFilter to SFPGetFile. This is the address of our file filter routine. A file filter is declared as:

    FUNCTION SFFileFilter (p: ParmBlkPtr): BOOLEAN;

A file filter routine allows us to control which files SFPGetFile will display for the user. Our file filter is called for every file (of the type(s) specified in the typelist) on an MFS disk, or for every file (of the type(s) specified in the typelist) in the current directory on an HFS disk. In addition, SFPGetFile displays HFS folders for us automatically. Our file filter selects which files should appear in the dialog by returning FALSE for every file that should be shown and TRUE for every file that shouldn't.

For example, using our global variable textOnly (which we set in our dialog hook, remember?):

    FUNCTION SFFileFilter(p:parmBlkPtr):boolean;

    Begin {SFFileFilter}
        SFFileFilter:= TRUE;                        {Don't show it -- default}

        if textOnly then
           if p^.ioFlFndrInfo.fdType = 'TEXT' then
               SFFileFilter:= FALSE                      {Show TEXT files only}
           else Begin
           End  {dummy else}
       else
           if (p^.ioFlFndrInfo.fdType = 'TEXT') or
                    (p^.ioFlFndrInfo.fdType = 'APPL') then
                         SFFileFilter:= FALSE;       { show TEXT or APPL files}
    End;  {SFFileFilter}

SFPGetFile calls the file filter after it has called our dialog hook. Please remember that the filter is passed every file of the types specified in the typelist (MyFileTypes). If you want your application to be able to choose from all files, pass SFPGetFile a -1 as numTypes. For information about parameters to SFPGetFile that haven't been discussed in this technical note, see the Standard File Package chapter of Inside Macintosh.

That's all there is to it!! Now that you know how to modify SFPGetFile to suit your needs, please don't rush off and load up the dialog window with all kinds of controls and text. Please make sure that you adhere to Macintosh interface standards. Similar techniques can be used with SFGetFile, SFPutFile and SFPPutFile.

The complete source of the demo program and of the resource compiler input file follows:

Back to top

MPW Pascal Source

{$R-}

{Jim Friedlander    Macintosh Technical Support 9/30/85}

program SFGetDemo;

USES
     MemTypes,
     QuickDraw,
     OSIntf,
     ToolIntf,
     PackIntf;
{$D+}

CONST
  myDLOGID = 128; {ID of our dialog for use with SFPGetFile}

VAR
  wher: Point; { where to display dialog }
  reply: SFReply; { reply record }
  textOnly: BOOLEAN; { tells us which files are currently being displayed}
  myFileTypes: SFTypeList; { we won't actually use this }
  NumFileTypes: integer;


{----------------------------------------------------------------------------}
FUNCTION MySFHook(MySFitem:integer; theDialog:DialogPtr): integer;

CONST
  textButton        = 11;      {DITL item number of textButton}
  textAppButton    = 12;      {DITL item number of textAppButton}
  quitButton        = 13;      {DITL item number of quitButton}


  stayInSF         =  0;        {if we want to stay in SF after getting an
  Open hit, we can pass back a 0 from our hook (not used inthis example) }
  firstTime        = -1;        {the first time our hook is called, it is
                    passed a -1}


{The following line is the key to the whole routine -- the magic 101!!}
  reDrawList     = 101;    {returning 101 as item number will cause
                     the file list to be recalculated}
  btnOn        = 1;        {control value for on}
  btnOff         = 0;        {control value for off}

VAR
  itemToChange: Handle; {needed for GetDItem and SetCtlValue}
  itemBox:Rect;            {needed for GetDItem}
  itemType:integer;                {needed for GetDItem}
  buttonTitle: Str255;           {needed for GetIndString}

Begin {MySFHook}
  case MySFItem of

    firstTime: Begin { before the dialog is drawn, our hook gets
                     called with a -1 (firstTime) as the item so
                    we can change things like button titles,
                    etc. }

{Here we will set the textAppButton to OFF, the textButton to ON}
        GetDItem(theDialog,textAppButton,itemType,itemToChange,itemBox);
        SetCtlValue(controlHandle(itemToChange),btnOff);
        GetDItem(theDialog,textButton,itemType,itemToChange,itemBox);
        SetCtlValue(controlHandle(itemToChange),btnOn);

        GetIndString(buttonTitle,256,1); {get the button title from a resource file}
        If buttonTitle <> '' then Begin        { if we got the resource}
            GetDItem(theDialog,getOpen,itemType,itemToChange,itemBox);
                            {get handle to open button}
            SetCtitle(controlHandle(itemToChange),buttonTitle);
        End; {if}                {if we can't get the resource, we
                        just won't change the open button's
                        title}
        MySFHook:= MySFItem;        {pass back the same item we were
                         sent}
    End;  {firstTime}


{Here we will turn the textAppButton OFF, the textButton ON and redraw the list}
    textButton: Begin
        if not textOnly then Begin
            GetDItem(theDialog,textAppButton,itemType,itemToChange,itemBox);
            SetCtlValue(controlHandle(itemToChange),btnOff);
            GetDItem(theDialog,textButton,itemType,itemToChange,itemBox);
            SetCtlValue(controlHandle(itemToChange),btnOn);
            textOnly:=TRUE;
            MySFHook:= reDrawList;    {we must tell SF to redraw the list}
        End;  {if not textOnly}
    End;  {textOnlyButton}

{Here we turn the textButton OFF, the textAppButton ON and redraw the list}
    textAppButton: Begin
        if textOnly then Begin
            GetDItem(theDialog,TextButton,itemType,itemToChange,itemBox);
            SetCtlValue(controlHandle(itemToChange),BtnOff);
            GetDItem(theDialog,TextAppButton,itemType,itemToChange,itemBox);
            SetCtlValue(controlHandle(itemToChange),BtnOn);
            TextOnly:=FALSE;
            MySFHook:= reDrawList;    {we must tell SF to redraw the list}
          End;  {if not textOnly}
        End;  {textAppButton}

    quitButton:  MySFHook:= getCancel;    {Pass SF back a 'cancel button'}

{!!!!very important !!!! We pass SF's 'standard' hits back to SF}
    otherwise  Begin
                  MySFHook:= MySFItem;    { the item hit was one of SF's
                        standard items... }
    End;  {otherwise}            { so just pass it back}
  End;  {case}
End;  {MySFHook}

{-----------------------------------------------------------------------------}

FUNCTION SFFileFilter(p:parmBlkPtr):boolean; {general strategy -- check value
                           of global vartextOnly to see
                           which files to display}

Begin {SFFileFilter}
  SFFileFilter:= TRUE; {Don't show it -- default}

  if textOnly then
      if p^.ioFlFndrInfo.fdType = 'TEXT' then
          SFFileFilter:= FALSE        {Show it}
      else Begin
      End  {dummy else}
  else
      if (p^.ioFlFndrInfo.fdType = 'TEXT') or (p^.ioFlFndrInfo.fdType = 'APPL') then
          SFFileFilter:= FALSE;        {Show it}
End;  {SFFileFilter}


{-----------------------------------------------------------------------------}

Begin {main program}
   InitGraf (@thePort);
   InitFonts;
   InitWindows;
   TEInit;
   InitDialogs (nil);

   wher.h:=80;
   wher.v:=90;
   NumFileTypes:= -1;          {Display all files}

{ we don't need to initialize MyFileTypes, because we want to get a chance to filter
every file on the disk in SFFileFilter - we will decide what to show and what not to.
If you want to filter just certain types of files by name, you would set up MyFileTypes
and NumFileTypes accordingly}

   repeat
    {each time SFPGetFile is called, display will be text-only files}
       textOnly:= TRUE;

       SFPGetFile (wher, '', @SFFileFilter, NumFileTypes, MyFileTypes,
       @MySFHook,reply,myDLOGID,nil);

   until reply.good = FALSE;
{until a cancel button hit ( or a Quit button -- thanks to our dialog hook ) }
End.

Back to top

MPW C Source

#include <Types.h>
#include <Quickdraw.h>
#include <Resources.h>
#include <Fonts.h>
#include <Windows.h>
#include <Menus.h>
#include <TextEdit.h>
#include <Events.h>
#include <Dialogs.h>
#include <Packages.h>
#include <Files.h>
#include <Controls.h>
#include <ToolUtils.h>
#define    textButton    11     /*DITL item number of textButton*/

#define    textAppButton    12    /*DITL item number of textAppButton*/

#define    quitButton    13    /*DITL item number of quitButton*/

#define    stayInSF    0    /*if we want to stay in SF after getting an
                    Open hit, we can pass back a 0 from our
                    hook  (not used in this example) */

#define    firstTime    -1    /* the first time our hook is called, it is
                    passed a -1*/


#define    reDrawList    101    /* This line is the key to the whole
                    routine the magic 101!! returning 101 as
                    item number will cause the file list to be
                    recalculated*/

#define    btnOn        1    /*control value for on*/

#define    btnOff        0    /*control value for off*/

#define     myDLOGID    128    /*resource ID of our DLOG for SFPGetFile*/

Boolean        textOnly; /* tells us which files are currently being displayed*/

main()
{    /*main program*/

        pascal short MySFHook();
        pascal Boolean flFilter();

        Point      wher;            /* where to display dialog*/
        SFReply      reply;                /* reply record */
        SFTypeList    myFileTypes;          /* we won't use this */
        short int      NumFileTypes = -1;

    InitGraf(&qd.thePort);
    InitFonts();
    FlushEvents(everyEvent, 0);
    InitWindows();
    TEInit();
    InitDialogs(nil);
    InitCursor();



   wher.h=80;
   wher.v=90;

/* we don't need to initialize MyFileTypes, because we want to get a chance to
ilter every file on  the disk in flFilter - we will decide what to show and what
not to. if you want to filter just certain types of files by name, you would set
up MyFileTypes and NumFileTypes accordingly*/

   do {
    textOnly= true;     /*each time SFPGetFile is called, initial
                    display will be text-only files*/

       SFPGetFile(&wher,"",flFilter, NumFileTypes,myFileTypes,
            MySFHook, &reply,myDLOGID,nil);

   } while (reply.good);    /*until we get a cancel button hit
                 (or a Quit button in this case ) */
} /* main */


pascal short MySFHook(short MySFItem,DialogPtr theDialog)
{

Handle  itemToChange;            /*needed for GetDItem and SetCtlValue*/
Rect    itemBox;                /*needed for GetDItem*/
short    itemType;                   /*needed for GetDItem*/
char    buttonTitle[256];        /*needed for GetIndString*/

  switch (MySFItem)
  {
    case firstTime:
        /* before the dialog is drawn, our hook gets called with a -1
         (firstTime)... as the item so we can change things like button
         titles, etc. Here we set the textAppButton to OFF, the
         textButton to ON*/
        GetDItem(theDialog,textAppButton,&itemType,&itemToChange,&itemBox);
        SetCtlValue(itemToChange,btnOff);
        GetDItem(theDialog,textButton,&itemType,&itemToChange,&itemBox);
        SetCtlValue(itemToChange,btnOn);

        /*get the button title from a resource file*/
        GetIndString((char *)buttonTitle,256,1);
        if (buttonTitle[0] != 0)    /* check the length of the p-string
                        to see if we got the resource*/
        {

    /*get a handle to the open button*/
    GetDItem(theDialog,getOpen,&itemType,&itemToChange,&itemBox);
            SetCTitle(itemToChange,buttonTitle);
          } /*if we can't get the resource, we just
                      won't change the open button's title*/

        return MySFItem; /*pass back the same item we were sent*/
        break;

/*Here we turn the textAppButton OFF, the textButton ON and redraw the list*/
    case textButton:
        if (!textOnly) {
GetDItem(theDialog,textAppButton,&itemType,&itemToChange,&itemBox);
           SetCtlValue(itemToChange,btnOff);
GetDItem(theDialog,textButton,&itemType,&itemToChange,&itemBox);
                SetCtlValue(itemToChange,btnOn);
                textOnly=true;
                    return(reDrawList);    /*must tell SF to redraw the list*/
               }                  /*if !textOnly*/
        return MySFItem;
        break;             /*Here we will turn the textButton
                        OFF, the textAppButton ON and redraw
                        the list*/
    case textAppButton:
        if (textOnly)
            {
GetDItem(theDialog,textButton,&itemType,&itemToChange,&itemBox);
                    SetCtlValue(itemToChange,btnOff);
GetDItem(theDialog,textAppButton,&itemType,&itemToChange,&itemBox);
                    SetCtlValue(itemToChange,btnOn);
                    textOnly=false;
                    return(reDrawList);    /* must tell SF to redraw the list*/
           }  /*if not textOnly*/
        return MySFItem;     /*pass back the same item we were sent*/
        break;

    case quitButton:
        return(getCancel); /*Pass SF back a 'cancel button'*/

/*!!!very important !!!! We must pass SF's 'standard' item hits back to SF*/
    default:
        return(MySFItem); /* the item was one of SF's standard items... */
  }  /*switch*/
      return(MySFItem); /* return what we got */
}  /*MySFHook*/


pascal Boolean flFilter(pb)
FileParam    *pb;

{

/* is this gross or what??? */
return((textOnly) ? ((pb->ioFlFndrInfo.fdType) != 'TEXT') :
    ((pb->ioFlFndrInfo.fdType) != 'TEXT') &&
    ((pb->ioFlFndrInfo.fdType) != 'APPL'));
}  /*flFilter*/

Back to top

Rez Input File

#include "types.r"

resource 'STR#' (256) {
    {
        "MyOpen"
    }
};

resource 'DLOG' (128, purgeable) {
    {0, 0, 200, 349},
    dBoxProc,
    invisible,
    noGoAway,
    0x0,
    128,
    "MyGF"
};

resource 'DITL' (128, purgeable) {
    {
        /* [1] */
        {138, 256, 156, 336},
        Button { enabled, "Open" };
        /* [2] */
        {1152, 59, 1232, 77},
        Button { enabled, "Hidden" };
        /* [3] */
        {163, 256, 181, 336},
        Button { enabled, "Cancel" };
        /* [4] */
        {39, 252, 59, 347},
        UserItem { disabled };
        /* [5] */
        {68, 256, 86, 336},
        Button { enabled, "Eject" };
        /* [6] */
        {93, 256, 111, 336},
        Button { enabled, "Drive" };
        /* [7] */
        {39, 12, 185, 230},
        UserItem { enabled };
        /* [8] */
        {39, 229, 185, 245},
        UserItem { enabled };
        /* [9] */
        {124, 252, 125, 340},
        UserItem { disabled };
        /* [10] */
        {1044, 20, 1145, 116},
        StaticText { disabled, "" };
        /* [11] */
        {1, 14, 20, 142},
        RadioButton { enabled, "Text files only" };
        /* [12] */
        {19, 14, 38, 176},
        RadioButton { enabled, "Text and applications" };
        /* [13] */
        {6, 256, 24, 336},
        Button { enabled, "Quit" }
    }
};

Back to top

References

The Standard File Package

Back to top

Downloadables

Acrobat gif

Acrobat version of this Note (68K)

Download


Back to top