TweetFollow Us on Twitter

MultiFile dialog
Volume Number:7
Issue Number:5
Column Tag:C Forum

Related Info: List Manager Standard File Dialog Manager
File Mgr (PBxxx)

MultiFile Dialog

By Eric Schlegel, Castleton, NY

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

History

[Eric Schlegel attends Dartmouth College in Hanover, NH, and he has been programming for the past five years with most of his work in MPW C and Pascal.]

The Standard File Package is one of the most useful parts of the Macintosh Toolbox. It allows a programmer to easily get a filename from a user, and it presents a simple, intuitive interface with which a user can select a file. A key aspect of the package, however, is that it is designed to select only a single file. It is not designed to select multiple files, which can be a problem for some applications - in particular, mine. In this article, I’ll present my solution to the problem, in the form of a MultiFile Select dialog.

MPW’s Commando dialogs were the original model for the MultiFile Select dialog. MPW is a command-line environment, so that to open a file, you type something like “open file1.c file2.c file3.c.” Typing many long filenames is tiring and error-prone. To remedy this problem, Apple introduced in MPW 2.0 the Commando interface, which is used as a front end to the command line. In particular, the Commando interface has built into it a multifile select dialog just like mine. Now you can select multiple files using Commando and Commando will automatically generate the commands to, say, open the files.

This interface was just what I needed for my own work, and so I set out to replicate Commando’s multifile interface for my own program. The result is the subject of this article.

What the User Sees

The MultiFile Select dialog appears to the user to be very similar to the standard SFGetFile dialog (see Figure 1). The user can still scroll through a list of files, move into and out of folders, change drives, etc. There are two major differences from the standard dialog:

1. The Open button has been renamed Done.

2. Beneath the standard list of files is another file list; to the right of this list are two buttons, Add/Open and Remove.

Figure 1. The Dialog Box

The user adds a file to the lower list box by selecting a file in the upper box and clicking on the Add button. Double-clicking or pressing Return or Enter will also add the file to the list. If a folder is selected in the upper box, the Add button is renamed Open, and clicking on it will simply open the folder. Files can be removed from the lower box by selecting them and clicking on the Remove button. Once a list of files has been collected, the user clicks on the Done button and the dialog closes.

Technical Background

The key to adding your own controls to a Standard File dialog is the dlgHook parameter of the Standard File calls. This parameter, if not null, should be the address of a function with the following declaration:

{1}

FUNCTION MyDlg (item: INTEGER; theDialog: DialogPtr) : INTEGER;

Standard File uses ModalDialog to run the Standard File dialog. After ModalDialog returns, Standard File calls your dlgHook function, passing it the item number returned by ModalDialog and the DialogPtr for the Standard File dialog. After handling the event, the dlgHook function returns an item number to Standard File. You can add your own controls to the Standard File dialog and handle hits on them with your dlgHook function.

In addition to passing you item numbers from ModalDialog, Standard File also passes your dlgHook several fake numbers. These fake numbers include:

-1 Passed to you only once when the dialog is being initialized. When you get this event, you should do any initialization you need, such as changing the name of a button in the standard dialog.

100 A null event. Nothing has happened.

101 Return this event from your dlgHook function to make Standard File redraw the file list. This is useful if, say, you have radio buttons controlling what type of files are displayed. When the file type changes, return 101 to display a new list of files.

102 Generated by a click in the current directory button. It causes the directory menu to be pulled down and tracked.

103 Generated by a double-click on a folder or a click on the Open button while a folder is selected. It causes the folder to be opened.

1000+ ? Generated by a keyDown event. “?” is the ASCII code for the key that was struck. 1065, for example, would indicate that the user had hit the ‘A’ key, which has a code of 65.

For some applications even the dlgHook may not be enough to support your modifications to standard file. This is when SFPGetFile and SFPPutFile come to the rescue. These calls behave exactly like SFGetFile and SFPutFile except that they allow you to use your own dialog resource and, more importantly, install a filter proc that will be passed to ModalDialog. This filter proc has the same declaration as any other ModalDialog filter proc:

{2}

FUNCTION MyFilter (theDialog: DialogPtr; VAR theEvent: EventRecord; VAR 
itemHit: INTEGER) : BOOLEAN;

Using your filter proc you can examine and modify events before Standard File even has a chance to look at them.

Programmer’s Interface

The file mfile.h is a header file for MultiFile; mfile.c contains the actual code. Mfile.c first has #defines and global variables. The only interesting items are FSFCBLen, SFSaveDisk, and CurDirStore, which are #defines to allow access to the low-memory globals of the same names. FSFCBLen is -1 if MFS is running, or greater than 0 if HFS is running. SFSaveDisk contains the negative of the vrefnum of the volume currently displayed by Standard File. CurDirStore is only used under HFS; it contains the dirID of the directory currently displayed by Standard File.

The interface to the MultiFile dialog uses five procedures: get_tl, multifile, numfiles, getfile, and freemf. Calling multifile is similar to calling SFGetFile except that you cannot use your own dlgHook function. Multifile’s first argument is a point indicating where to place the top left corner of the dialog; this argument is passed directly through to SFPGetFile. Get_tl will calculate this point for you so that the dialog is nicely centered on the screen. Multifile’s second argument is a C string which is used as a prompt for the lower file list box. The third, fourth, and fifth arguments are a filefilter proc, the number of types in your type list, and the type list itself. These three parameters are passed directly to SFPGetFile, so that you can customize which files are displayed in the upper file list.

Multifile itself is trivial. It first makes a copy of your prompt string so that the string can be accessed by other procedures. It then reads in the strings “Add” and “Open” from the resource fork, initializes some variables, and calls SFPGetFile. The rest of the work is done by Standard File, and by the dlg_hook and filter functions which I pass to SFPGetFile.

Numfiles returns the number of files collected. This number is stored in the global variable nfiles after the dialog closes.

Getfile takes two arguments, a file number and a pointer to an FSTRUCT struct, and returns in the struct the indicated file. The file number is one-based and may range from one through the number returned by numfiles. An FSTRUCT is defined as follows:

/* 3 */

typedef struct fstruct {
 OSType ftype;
 short  vrefnum;
 short  fver;
 String(63) fname;
}  FSTRUCT;

An FSTRUCT is the same as an ordinary SFReply record except that it does not have a field to indicate whether the data is valid. Instead, multifile returns a Boolean result of true if the user clicked on the Done button and false if the user clicked on the Cancel button. Ftype is the file type. Vrefnum is a true volume refnum if you are using MFS, or a working directory refnum if you are using HFS. Fver is the file version. Fname is a Pascal string containing the file name.

Freemf deallocates memory used by multifile to hold the list of selected files. You should call freemf once you’ve retrieved all the files with getfile.

Utility Routines

The dlg_hook and filter functions use a collection of utility routines. Most of these are simple; the only interesting ones are makewd, mkcell, inlist, and sel1cell. Makewd creates a new working directory for the current volume and directory using PBOpenWD, and returns the wdrefnum. Before using PBOpenWD, however, makewd must check that:

1. Multifile isn’t running under MFS. If so, makewd simply returns the current volume refnum.

2. The current volume isn’t an MFS volume. If so, makewd again returns the current vrefnum.

Mkcell takes information about a file and fills in an MFCELL struct for the file. Mkcell also has to watch that we aren’t running under MFS; if we are, mkcell always fills in 0 for the current directory ID. The HFS dirID comes from the global CurDirStore, which isn’t used under MFS and probably contains trash. Mkcell always uses 0 to maintain a constant value for the dirID under MFS.

Multifile passes its own SFReply record to SFPGetFile. Inlist returns true if the file currently described by that record is in my list, and if it is, the cell that the file is in. I use inlist to avoid adding a file to my list more than once. The tricky part about inlist is its use of LSearch to look for the file. LSearch matches the data it is looking for against the entire cell; in other words, if you are looking for “cde”, you will only find cells with “cde” in them. You will not find cells with “abcde”, “cdefg”, or “abcdefg”. To me, this means that I can’t just look for the name of the current file. Instead, I have to set up an entire MFCELL struct with all fields filled in and look for a matching cell.

Sel1cell takes a cell in my file list and ensures that that cell alone is selected. The best way I’ve found to do this is simply by looking for selected cells in my list and deselecting them as I find them, and then selecting the specified cell. I’ve tried setting the lOnlyOne flag in the list record and then selecting the single cell, but it doesn’t work. I’m still trying to think of a better way to do this.

DlgHook Function

I use my dlgHook function to handle hits on my three added buttons, to receive fake item numbers from Standard File, and to intercept hits on some of the standard items in the Standard File dialog. Initdlg is called when the dlgHook function receives -1 as the item number. Initdlg installs your prompt string in my extra staticText item, installs a userproc to draw my file list and the border around the Add/Open button, and calls the List Manager to create a new list. The only tricky part about initdlg is leaving room for the vertical scroll bar when creating the new list.

When I get a hit on the Done or Cancel buttons I call disposdlg to clean up my part of the dialog. Disposdlg first saves the number of files selected in the variable nfiles. At this point, all data about the files is stored in my file list. I have to copy this information to a safe place before I dispose of the list. Disposdlg allocates memory in the form of an array of FSTRUCTs, one struct for each file. It then copies the information from my file list into the newly allocated memory, and finally disposes of the list. Once disposdlg returns, my dlgHook functions sets the variable good to true or false, depending on whether Done or Cancel was hit. Multifile returns the value of good when it finishes.

A hit on the Remove button is easy to handle. I use the List Manager to delete any selected files in the lower list box.

A hit on the Add/Open button is a bit more complicated. I first check to see if the current selection is a file or a folder. If it’s a folder, I return 103 from the dlgHook function to make Standard File open the folder. Otherwise, I call do_add to add the file to my file list box. Do_add first uses inlist to check that the file is not already in the list; if it is, do_add returns without doing anything. If the file is not in the list, do_add calls makewd to open a working directory for the file. Makewd returns the wdrefnum if it succeeds, or 0 if too many working directories are already open. (The current limit on open wd’s is 40.) If makewd returns 0, do_add uses wdalert to display an alert informing the user. Otherwise, do_add calls addfile to add the file to my list. Do_add then disables the Add button, since the selected file can’t be added to the list again, and enables the remove button, since the new file is now selected in my file list.

My dlgHook function also has to watch out for hits on items in the original Standard File dialog. The procedure chknmlist ensures that the title of the Add/Open button always corresponds to the selection in the upper file list. If the selection is a file, the button should read Add; if the file is already in my file list, the Add button should be inactive and the file should be selected in my list as well as the original list box. If the selection is a folder, the button should read Open. Virtually any hit on the original Standard File dialog can change the selection, and so whenever a hit occurs I set a flag variable, chklist, to indicate that chknmlist should be called to examine the selection.

Setting a flag is necessary because a Standard File hit doesn’t always mean that the selection has already changed. An example of this problem is the Eject button. The current disk won’t be ejected until after I’ve seen the hit on the button; therefore, the selection hasn’t yet changed at the time I get the hit. Instead of checking the selection immediately I just set chklist to indicate that it needs to be checked. The next time my ModalDialog filterProc is called, it looks at chklist and calls chknmlist if chklist is true.

The filterProc was not originally used for this purpose. In the original version of MultiFile I looked at chklist whenever my dlgHook got a null event from Standard File. This approach also works fine except for one small problem: if any window behind the MultiFile dialog needs updating, Standard File will constantly receive update events for that window and never get any null events. Your dlgHook, in turn, will never get any null events either. In my case this meant that the selection would be changed but the Add/Open button would never be updated. It was some consolation to me that Apple was also bitten by this bug; see Tech Note 99.

FilterProc

My filterProc, filter, has few but important uses. Each time it is called it calls chknmlist if chklist is true. On mouseDown events, it calls do_mousedown; do_mousedown in turn calls LClick to track the mouse in my file list. On keyDown and autoKey events it calls do_keydown. Do_keydown looks for Return or Enter and flashes the Add button if it sees either one.

The List Box

The useritem in the MultiFile dialog is a placeholder for my file list box. The list box is implemented using the List Manager and a custom list definition function. The list is 2 columns wide and has one row for each file. Each cell in the first column is of type MFCELL; this type is defined in the file mfpriv.h.

/* 4 */

typedef struct mfcell {
 String(63) fname;
 OSType ftype;
 short  fver;
 short  vrefnum;
 short  dirid;
} MFCELL;

Fname, ftype, and fver are the filename, file type, and file version, respectively. Vrefnum is the volume refnum of the file’s volume under both MFS and HFS; it is never a working directory refnum. Dirid is the directory id of the file’s directory under HFS; under MFS it is unused and is set to 0. The second column of the list contains a working directory refnum for the file’s directory, or a copy of vrefnum under MFS.

I use a custom list definition with code in the files mfldef.a and mfldef.c. Mfldef.a is an Assembly header for the actual LDEF in mfldef.c. The file mfldef.make is an MPW Make file to build the LDEF. The custom list definition is trivial. It first checks which column the cell it is drawing is in. If the cell is in the first column, it draws the filename; if the cell is in the second column, it returns without drawing, since the cell only contains a wdrefnum.

Mousedowns in the list are caught by my filterProc function, which calls LClick when it sees a mouseDown.

Resources

MultiFile’s resources are in the file mfile.r in MPW Rez format. I use my own dialog resource and pass the resource id to SFPGetFile. My dialog is based on the standard SFGetFile dialog. The ‘DLOG’ resource is somewhat taller to make room for my own controls. The ‘DITL’ resource has the ten standard SFGetFile items in it and five new items added at the end. The first three items are my Add, Remove, and Done buttons; the next is a userItem placeholder for my file list; and the last is a staticText item for the prompt string.

Mfile.r also contains an ‘ALRT’ resource and its corresponding ‘DITL’ resource to inform the user that no more working directories can be opened. Finally, I have a ‘STR#’ resource with the two strings “Add” and “Open” for the title of the Add/Open button and a Rez include statement to read my custom LDEF into the resource fork.

Button Problems

The first version of MultiFile did not have the Done button; instead, I simply renamed the Open button to Done when I initialized the dialog. This approach, however, has a subtle problem. Standard File coerces a double-click on a file into a fake hit on item 1, the original Open button, and a double-click on folder into a fake item 103. At the same time, Standard File returns a click on the original Open button as an item 1 or 103, depending on whether a file or a folder is hilighted.

The problem is that for me, a double-click on an file or folder and a click on the original Open button have entirely different meanings. A double-click is a signal to add a file to the file list or to open a folder. A click on the Open button is a signal to end the dialog, since I’ve renamed the Open button to Done. (Paul Snively faced a similar problem while writing his Set Paths DA; he explains his solution in the February 1987 MacTutor.)

My fix to this problem is to remove the Open button from the scene. In my dialog, the original Open button is repositioned outside of the dialog box so that the user cannot possibly click on it. I replace the original Open with my own Done button. In this way, if I ever get a hit on the original Open button, I know that the user has double-clicked on a file; if I get a fake item 103, I know that the user has double-clicked on a folder; and I only get hits on my Done button when the user clicks on it.

Standard File Obscurities

There are lots of obscure features of Standard File which were very useful in writing MultiFile. Some of these are:

When your dlgHook is called, the SFReply struct that you pass to Standard File has been filled in for the current selection. If both the file type and the filename length are equal to 0, there is no selection. If the file type is greater than 0 and the filename length equals 0, a folder is selected. The file type is the dirID of the folder. If the file type is greater than 0 and the filename length is greater than 0, a file is selected.

The disk name text item is item #4 in the item list for the dialog, and hits on it are returned to your dlgHook function. This item doesn’t have a corresponding constant in IM I as do many of the other items in the dialog.

Trapping disk-insertion events is tricky. When a disk is inserted, Standard File redisplays the file list to show the new disk, but it doesn’t tell you about it. Standard File does, however, generate an update event to redisplay the file list. I detect the update event because I have a userproc to draw my file list. Whenever the userproc is called, I set chklist to true so that I’ll examine the new selection and fix the Add/Open button.

You also have to watch out for disk-ejection using Cmd-Shift-1 or 2. The update technique works in this case as well.

Under HFS, hits on the scroll bar for the upper file list are returned as hits on item #7, the file list item. Item #8, which was the scroll bar under MFS, is no longer used.

Generic file system tip: I have to create a working directory for each file under HFS so that the caller of MultiFile can open the files. I use a procID of ‘ERIK’ when creating WDs so that the Finder will close them for me when I’m finished. If you are creating lots of WDs, as I am, you should also check to see that PBOpenWD succeeded. There’s currently a limit of 40 WDs open at any one time.

Demonstration Program

The file mfdemo.c contains source for a small program to demonstrate MultiFile. The program first puts up a standard SFGetFile dialog for you to select an application. The demo then calls multifile so that you can select a list of documents. When multifile returns, the demo launches the application and passes it the document list. Mfdemo.a is a assembly interface to the Launch trap, mfdemo.r is the resource file for the demo, and mfdemo.make is the demo program’s makefile.

Compilers and Portability

MultiFile was compiled using MPW C version 2.0. MPW is different from several other Mac C compilers in that it treats ints as 32 bits instead of 16. I’ve tried to keep MultiFile, and other C code, portable to 16-bit int C compilers using these three rules:

1. If a variable is to be accessed from Pascal and the Pascal declaration requires an INTEGER, use a short int. Shorts will always be 16 bits.

2. If a variable is to be accessed from Pascal and the Pascal declaration requires a LONGINT, use a long int. Longs will always be 32 bits.

3. The length of anything not covered by rules 1 and 2 probably doesn’t matter. Use an int, but be careful not to make assumptions about its length.

Variables that are passed by address to ROM routines are the primary area where you have watch for length problems. For example, the declaration of ModalDialog is:

{5}

PROCEDURE ModalDialog (filterProc: ProcPtr; VAR itemHit: INTEGER);

In calling this from C, always declare itemHit as a short, not an int. One of the worst headaches in porting code written for 16-bit int compilers to MPW C is ensuring that the code doesn’t use ints for variables that have to be 16 bits. Always using shorts for 16-bit vars and longs for 32-bit vars helps immensely.

MPW C also does automatic conversion between Pascal and C strings. For example, I use GetIndString to get the strings “Add” and “Open” from the resource fork. I pass GetIndString a 256-char array, and GetIndString fetches a pstring and converts it to a cstring before stuffing it into the array. The behavior of other C compilers may differ.

Sources

The internal workings of the Standard File package are one of the more confusing aspects of the Macintosh. I’ve found four important sources of information on this subject: the Standard File chapters in Volumes 1 and 4 of IM, and Tech Notes 47 and 80. Volume 1 of IM gives the interfaces to SFGetFile and SFPutFile, and also their more powerful relatives, SFPGetFile and SFPPutFile. Volume 4 details the differences between MFS and HFS Standard File. Tech Note 47 explains how to add your own radio buttons to a Standard File dialog, and 80 gives some hints about the low memory globals SFSaveDisk and CurDirStore.

Listing:  mfdemo.make

# File mfdemo.make
# Makefile for mfdemo
#
# Copyright © Eric Schlegel 1987, 1988


#   File:       mfdemo.make
#   Target:     mfdemo
#   Sources:    mfdemo.r mfdemo.a mfdemo.c mfile.c
#   Created:    Monday, December 28, 1987 9:02:38 PM

mfdemo.a.o ƒ mfdemo.a
 Asm mfdemo.a
mfdemo.c.o ƒ mfdemo.c
 C -g mfdemo.c
mfile.c.o ƒ mfile.c
 C -g mfile.c
mfdemo ƒƒ mfdemo.r mfile.r mfldef
 Rez -append mfdemo.r -o mfdemo
mfdemo ƒƒ mfdemo.a.o mfdemo.c.o mfile.c.o
 Link -t APPL -c ‘????’ 
 mfdemo.a.o 
 mfdemo.c.o 
 mfile.c.o 
 “{Libraries}”Interface.o 
 “{CLibraries}”CRuntime.o 
 “{CLibraries}”StdCLib.o 
 “{CLibraries}”CInterface.o 
 -o mfdemo
Listing:  mfdemo.c

/*
** File mfdemo.c
** Source file for demonstration of
** MultiFile Select dialog
**
** Copyright © Eric Schlegel 1987, 1988
*/


#include<types.h>
#include<quickdraw.h>
#include<fonts.h>
#include<events.h>
#include<windows.h>
#include<menus.h>
#include<textedit.h>
#include<dialogs.h>
#include<packages.h>
#include<memory.h>
#include<segload.h>
#include<files.h>
#include“mfile.h”

#define AppParmHandle(*((long *)(0xaec)))

typedef struct parmhead {
 short  msg;
 short  count;
} PARMHEAD;

/* launch struct. See TN126 */
typedef struct lstruct {
 StringPtrpfName;
 short  param;
 short  LC;
 long   extBlockLen;
 short  fFlags;
 long   launchFlags;
} LSTRUCT;

pascal short dolaunch(lsp)
 LSTRUCT*lsp;
 extern;
 
/* SFReply rec for SFGetFile for app */
SFReply appreply;
LSTRUCT ls;

main()
{
 Booleangetapp();
 void   getdocs();
 void   launchit();
 
 EventRecordev;
 int    ch;
 
 InitGraf(&qd.thePort);
 InitFonts();
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs(NULL);
 
 if (!getapp())
 ExitToShell();
 
 getdocs();
 
 launchit();
 
 /* we get here if under MultiFinder */
 /* wait for a cmd-Q */
 while (1)
 if (GetNextEvent(everyEvent, &ev))
 if ((ev.what == keyDown) || (ev.what == autoKey)) {
 ch = ev.message & charCodeMask;
 if (((ch == ‘q’) || (ch == ‘Q’)) &&
 (ev.modifiers & cmdKey))
 break;
 }
 
 ExitToShell();
}

/* use SFGetFile to get an application to    */
/* launch. returns true if user clicks OK    */
/* and false if user clicks Cancel */
Boolean getapp()
{
 Point  where;
 SFTypeList types;
 
 SetPt(&where, 50, 50);
 types[0] = ‘APPL’;
 SFGetFile(&where, “”, NULL, 1, types, NULL, &appreply);
 return(appreply.good);
}

/* get the documents with multifile()*/
void getdocs()
{
 pascal Boolean  docfilt();
 void   mkdoclist();
 
 Point  where;
 SFTypeList types;
 
 get_tl(&where);
 if (multifile(&where, “Select documents:”, docfilt, -1, types))
 mkdoclist();
}

/* filefilter function for selecting documents     */
pascal Boolean docfilt(pb)
ParmBlkPtrpb;
{
 /* always show folders; also show noninvisible documents      */
 if (pb->fileParam.ioFlAttrib & 0x10)
 return(true);
 else
 return((pb->fileParam.ioFlFndrInfo.fdFlags & fInvisible) ||
 (pb->fileParam.ioFlFndrInfo.fdType == ‘APPL’));
}

/* set up the application parameters */
void mkdoclist()
{
 void   newlist();
 void   addfile();
 
 int    nfiles;
 int    i;
 FSTRUCTfstrct;
 
 newlist();
 
 nfiles = numfiles();
 for (i = 1; i <= nfiles; i++) {
 getfile(i, &fstrct);
 addfile(&fstrct);
 }
}

/* creates a new application parameters list */
void newlist()
{
 Handle parms;
 THz    oldzone;
 
 /* if AppParmHandle already has a valid handle,   */
 /* just resize it to the starting size.                 */
 if (parms = (Handle)AppParmHandle) {
 HUnlock(parms);
 SetHandleSize(parms, 4);
 return;
 }
 
 /* allocate memory in sysheap so it */
 /* stays around after we quit*/
 oldzone = GetZone();
 SetZone(SystemZone());
 
 /* make sure we get the memory  */
 if (parms = NewHandle(4)) {
 ((PARMHEAD *)(*parms))->msg = appOpen;
 ((PARMHEAD *)(*parms))->count = 0;
 }
 
 SetZone(oldzone);
 
 AppParmHandle = (long)parms;
}

/* adds file described by fstrct to app params list      */
void addfile(fstrct)
FSTRUCT *fstrct;
{
 Handle parms;
 long   oldsize;
 int    fsize;
 char   *aptr;
 
 /* return if parms is null */
 if (!(parms = (Handle)AppParmHandle))
 return;
 
 /* get length of filename; if length of     fname */
 /* plus length byte is odd, make it even          */
 fsize = fstrct->fname.length;
 fsize++;
 if (fsize & 0x01)
 fsize++;
 
 /* grow parms to hold new file. */
 /* 8 bytes for vrefnum, type,*/
 /* and version. */
 oldsize = GetHandleSize(parms);
 SetHandleSize(parms, oldsize + 8 + fsize);
 
 /* return if not enough mem*/
 if (MemError())
 return;
 
 /* stuff in the file info*/
 aptr = (char *)((long)*parms + oldsize);
 *((short *)aptr)++ = fstrct->vrefnum;
 *((long *)aptr)++ = fstrct->ftype;
 *((short *)aptr)++ = fstrct->fver;
 BlockMove(&fstrct->fname, aptr, fsize);
 
 /* increment file count  */
 ((PARMHEAD *)(*parms))->count++;
}

/* launches application described by appreply      */
void launchit()
{
 char   fname[64];
 HParamBlockRec  hpb;
 
 /* copy app name to fname. SFReply.fName is a Str63     */
 BlockMove(&appreply.fName, fname, 64);
 
 hpb.fileParam.ioCompletion = 0L;
 hpb.fileParam.ioNamePtr = fname;
 hpb.fileParam.ioVRefNum = appreply.vRefNum;
 hpb.fileParam.ioFDirIndex = 0;
 hpb.fileParam.ioDirID = 0L;
 PBHGetFInfo(&hpb, false);
 
 /* set current volume to app’s directory    */
 SetVol(NULL, appreply.vRefNum);
 
 /* set up launch struct. param=0 indicates  */
 /* no alternate screen or sound buffers           */
 ls.pfName = fname;
 ls.param = 0;
 ls.LC = ‘LC’;
 ls.extBlockLen = 6;
 ls.fFlags = hpb.fileParam.ioFlFndrInfo.fdFlags;
 ls.launchFlags = 0x40000000;
 
 /* ignore the error code after launch */
 (void)dolaunch(&ls);
}
Listing:  mfdemo.a

; File mfdemo.a
; Asm interface to _Launch trap for mfdemo.c
;
; Copyright © Eric Schlegel 1987, 1988


 INCLUDE‘Traps.a’
 
dolaunchPROCEXPORT
 
 move.l (a7)+,a3
 move.l (a7)+,a0
 _Launch
 move.w d0,(a7)
 jmp    (a3)
 
 ENDPROC
 
 END
Listing:  mfdemo.r

/*
** File mfdemo.r
** Resource file for mfdemo.c
**
** Copyright © 1987, 1988 Eric Schlegel
*/

#include“mfile.r”

/* this is the standard Rez SIZE type definition   */
/* with some extra bit definitions.*/
/*------------SIZE • Switcher Size Information----------*/
type ‘SIZE’ {
 boolean  dontSaveScreen,
 saveScreen;
 boolean  ignoreSuspendResumeEvents,
 acceptSuspendResumeEvents;
 booleanenableOptionSwitch,
 disableOptionSwitch;
 booleancannotBackground,
 canBackground;
 booleannotMultiFinderAware,
 MultiFinderAware;
 booleannotOnlyBackground,
 onlyBackground;
 unsigned bitstring[10] = 0; 
 unsigned longint; /* size - 32k   */
 unsigned longint; /* min size - 32k */
};

resource ‘SIZE’ (-1) {
 dontSaveScreen,
 acceptSuspendResumeEvents,
 disableOptionSwitch,
 cannotBackground,
 MultiFinderAware,
 notOnlyBackground,
 65536, /* 64K preferred, min */
 65536
};
Listing: mpriv.h

/*
** File mfpriv.h
** Private header file for mfile.c and mfldef.c.
**
** Copyright © Eric Schlegel 1987, 1988
*/


/* private declaration of an MFCELL shared by      */
/* mfile.c and mfldef.c   */
/* fname : pstring filename */
/* ftype : file type */
/* fver : file version    */
/* vrefnum : always a volume refnum*/
/* dirid : directory id under HFS, 0 under MFS           */

typedef struct mfcell {
 String(63) fname;
 OSType ftype;
 short  fver;
 short  vrefnum;
 short  dirid;
} MFCELL;
Listing: mfldef.make

#
#File mfldef.make
#Makefile for custom LDEF for MultiFile dialog.
#
#Copyright © Eric Schlegel 1987, 1988
#


#   File:       mfldef.make
#   Target:     mfldef
#   Sources:    mfldef.a mfldef.c
#   Created:    Sunday, August 30, 1987 9:22:13 PM

mfldef.a.o ƒ mfldef.a
 Asm mfldef.a
mfldef.c.o ƒ mfpriv.h mfldef.c
 C mfldef.c -g
mfldef ƒƒ mfldef.a.o mfldef.c.o
 Link -t ‘????’ -c ‘????’ -rt LDEF=128 
 mfldef.a.o 
 mfldef.c.o 
 “{CLibraries}”CInterface.o 
 -o mfldef
Listing:  mfldef.a

; File mfldef.a
; Asm header for LDEF in file mfldef.c

; Copyright © Eric Schlegel 1987, 1988

 STRING ASIS; so we don’t get a length byte in front of
 ; the ‘LDEF’
 
LDEFEnt MAINEXPORT ; the main entry point

 IMPORT mfldef   ; name of the C function that is
 ;the ldef
 
LDEFHeader; header for the ldef
 BRA.S  @0; branch around the header to  real code
 DC.W 0 ; flags word
 DC.B ‘LDEF’; type
 DC.W   128 ; resource id
 DC.W 1 ; version
 
@0 ; dummy label - header branches to here
 JMP  mfldef; jump to the entry point of the ldef
 
 END
Listing:  mfldef.c

/*
** File mfldef.c
** Source file for custom LDEF for MultiFile dialog.
**
** Copyright © Eric Schlegel 1987, 1988
*/


#include<types.h>
#include<quickdraw.h>
#include<lists.h>
#include“mfpriv.h”

pascal void Debugger()
 extern 0xA9FF;

pascal void mfldef(lmsg, lselect, lrect, lcell, loffset, ldatalen, lhandle)
short   lmsg;
Boolean lselect;
Rect    *lrect;
Cell    lcell;
short   loffset;
short   ldatalen;
ListHandlelhandle;
{
 void doinit();
 void dodraw();
 void dohilite();
 
 switch (lmsg) {
 case lInitMsg:
 doinit(lhandle);
 break;
 case lDrawMsg:
 /* only column 0 needs to be drawn. */
 if (!lcell.h)
 dodraw(lselect, lrect,
 loffset, lhandle);
 break;
 case lHiliteMsg:
 /* ditto as above */
 if (!lcell.h)
 dohilite(lrect);
 break;
 }
}

void doinit(lhandle)
ListHandlelhandle;
{
 FontInfo fntinfo;
 
 GetFontInfo(&fntinfo);
 SetPt(&((*lhandle)->indent), 4,
 fntinfo.ascent);
}

void dodraw(lselect, lrect, loffset, lhandle)
Boolean lselect;
Rect    *lrect;
short   loffset;
ListHandlelhandle;
{
 MFCELL *data;
 short  strlen;
 
 HLock((*lhandle)->cells);
 
 data = *((*lhandle)->cells) + loffset;
 
 PenNormal();
 MoveTo(lrect->left + (*lhandle)->indent.h,
 lrect->top + (*lhandle)->indent.v);
 DrawText(data->fname.text, 0, data->fname.length);
 
 HUnlock((*lhandle)->cells);
 
 if (lselect)
 InvertRect(lrect);
}

void dohilite(lrect)
Rect  *lrect;
{
 InvertRect(lrect);
}
Listing:  mfile.h

/*
** File mfile.h
** Header file for MultiFile dialog.
**
** Copyright © Eric Schlegel 1987, 1988
*/


/* an FSTRUCT is:*/
/* the file type */
/* a vrefnum (or wdrefnum) for the file*/
/* the file’s version*/
/* the file’s name in Pascal form  */
typedef struct fstruct {
 OSType ftype;
 short  vrefnum;
 short  fver;
 String(63) fname;
} FSTRUCT;

extern void get_tl();
/* args:
 Point  *where;
 Pass in a pointer to a point; get_tl will figure out
 for you where the top left corner of the multifile dlg
 should be so that it is centered half-way across the 
 screen and one-third down, and return that point. You
 can then pass in the point as the “where” argument of
 multifile().
 
   return val:
   None.
*/

extern Boolean multifile();
/* args:
 Point  *where;
 The top left corner of the multifile dialog.
 
 char   *prompt;
 A prompt string to label the file list. For example, 
 “Files to process:”. The string should be at most
 255 chars long, plus the ending null.
 
 ProcPtrfilefilt;
 Your file filter function.
 
 short  numtypes;
 How many types are in the typelist.
 
 SFTypeList *typelist;
 A list of types to display.
 
   return val:
   Multifile returns true if the user clicked in the Done button,
 or false if the user clicked in the cancel button.
*/

extern int  numfiles();
/* args:
 None.
 
   return val:
 Numfiles returns the number of files that the user collected.
 This number will be valid even if the user exited by clicking
 on Cancel, but in that case you usually wouldn’t want to
 retrieve the files anyway.
*/

extern void getfile();
/* args:
 short  fileno;
 The number of the file to retrieve. The first
 file is number 1.
 
 struct fstruct  *fstrct;
 A pointer to an fstruct. The fields of the 
 fstruct will be filled with info about the
 selected file.
 
  return val:
   None.
*/

extern void freemf();
/* args:
 None.
 
   return val:
   None.
 
   Call this once you’ve retrieved all the file names using
 getfile().
*/
Listing: mfile.c

/*
** File mfile.c
** Source file for MultiFile dialog.
**
** Copyright © Eric Schlegel 1987, 1988
*/


#include<Types.h>
#include<Resources.h>
#include<memory.h>
#include<QuickDraw.h>
#include<Fonts.h>
#include<Windows.h>
#include<Menus.h>
#include<TextEdit.h>
#include<Dialogs.h>
#include<Events.h>
#include<Lists.h>
#include<Packages.h>
#include<Controls.h>
#include<Files.h>
#include<Strings.h>
#include<osutils.h>
#include<string.h>
#include“mfpriv.h”


/* msg to dlgHook to init itself */
#define INITMSG  (-1)
/* msg saying nothing happened*/
#define NULLMSG  (100)
/* msg saying hit in cur dir popup */
#define DIRMSG   (102)
/* msg to open a folder */
#define FOLDMSG  (103)
/* item number of disk name */
#define GETDISK  (4)
/* “Done” button item number*/
#define DONE_BTN (11)
/* “Add” button item number */
#define ADD_BTN  (12)
/* “Remove” button item number*/
#define RM_BTN   (13)
/* my file list item number */
#define LST_ITEM (14)
/* prompt stattext item */
#define PROMPT_ITEM(15)

/* size of a scroll bar   */
#define SCRLSIZE (15)

/* res id of ldef*/
#define LDEFID   (128)
/* res id of working dir alert*/
#define WDALRTID (3999)
/* res id of multifile dialog */
#define MFDLGID  (4000)
/* res id of str# resource*/
#define MFSTRSID (512)

/* constants for hilitebtn*/
#define ACTIVE   (0)
#define INACTIVE (255)

/* charcode for the Enter key */
#define ENTER    (0x03)

/* -1 if mfs, >0 if hfs */
#define FSFCBLen (*((short *)(0x3f6)))
/* -current vRefNum*/
#define SFSaveDisk (*((short *)(0x214)))
/* current dirID */
#define CurDirStore(*((long *)(0x398)))


/* an FSTRUCT is:*/
/* the file type */
/* a vrefnum (or wdrefnum) for the file*/
/* the file’s version*/
/* the file’s name in Pascal form  */
typedef struct fstruct {
 OSType ftype;
 short  vrefnum;
 short  fver;
 String(63) fname;
} FSTRUCT;


/* handle to an array of FSTRUCTs  */
typedef FSTRUCT  (**FHNDL)[32767];


/* number of files */
static intnfiles;
/* handle to array of files */
static FHNDLfiles;
/* the multifile dialog */
static DialogPtr mfdlg;
/* my file name list */
static ListHandlelist;
/* dummy reply struct for SFPGetFile */
static SFReply   reply;
/* my internal copy of the prompt  */
static char myprompt[256];
/* true if Add btn, false if Open btn*/
static Boolean   is_add;
/* true if we should check SF’s selection    */
static Boolean   chklist;
/* true if user hit done, false if cancel    */
static Boolean   good;
/* “Open” */
static char openstr[256];
/* “Add”*/
static char addstr[256];


/* center r in the screen 1/hfract */
/* across and 1/vfract down */
static void center(r, hfract, vfract)
Rect  *r;
inthfract;
intvfract;
{
 int    h;
 int    v;
 
 h = qd.screenBits.bounds.right -
 qd.screenBits.bounds.left;
 v = qd.screenBits.bounds.bottom -
 qd.screenBits.bounds.top;
 
 h = h - r->right + r->left;
 v = v - r->bottom + r->top;
 
 h = h / hfract;
 v = v / vfract;
 
 SetRect(r, h, v, h + r->right - r->left,
 v + r->bottom - r->top);
}

/* calculate the top left corner of the dialog     */
void get_tl(where)
Point *where;
{
 DialogTHndldlgres;
 Rect   drect;
 
 /* use default vals for classic mac */
 /* screen if we can’t get dialog  */
 if (!(dlgres = GetResource(‘DLOG’, MFDLGID))) {
 where->h = 82;
 where->v = 33;
 return;
 }
 
 drect = (*dlgres)->boundsRect;
 center(&drect, 2, 3);
 /* keep the top of the dialog at least*/
 /* 10 pixels below the menu bar.  */
 if (drect.top < 30)
 drect.top = 30;
 
 where->h = drect.left;
 where->v = drect.top;
}

/* start up the multifile dialog */
Boolean multifile(where, prompt, filefilt, numtypes, typelist)
Point   *where;
char    *prompt;
ProcPtr filefilt;
intnumtypes;
SFTypeList*typelist;
{
 pascal short  dlg_hook();
 pascal Boolean  filter();
 
 /* make a copy of the prompt */
 /* so initdlg() can access it*/
 strcpy(myprompt, prompt);
 
 GetIndString (openstr, MFSTRSID, 1);
 if (strlen(openstr) == 0)
 strcpy(openstr, “Open”);
 
 GetIndString (addstr, MFSTRSID, 2);
 if (strlen(addstr) == 0)
 strcpy(addstr, “Add”);
 
 is_add = true;
 chklist = true;
 
 SFPGetFile(where, ‘’, filefilt, numtypes,
 typelist, dlg_hook, &reply, MFDLGID, filter);
 
 return(good);
}

/* return number of files collected*/
int numfiles()
{
 return(nfiles);
}

/* return file #fileno in fstrct */
void getfile(fileno, fstrct)
intfileno;/* fileno is 1-based*/
FSTRUCT *fstrct;
{
 BlockMove(&(**files)[fileno-1], fstrct,
 sizeof(FSTRUCT));
}

/* dispose of files memory*/
void freemf()
{
 /* make sure we’ve got a valid handle */
 if (files)
 DisposHandle(files);
}

/********************************
 utility routines
********************************/
/* flashes the button item #itemno */
/* in dlg for 4 ticks*/
static void flashbtn(dlg, itemno)
DialogPtr dlg;
intitemno;
{
 short  itemtype;
 Handle item;
 Rect   box;
 long   finalticks;
 
 GetDItem(dlg, itemno, &itemtype, &item,
 &box);
 
 HiliteControl(item, inButton);
 Delay(4, &finalticks);
 HiliteControl(item, 0);
}

/* returns the item rect of item #itemno in dlg    */
static void getitemr(dlg, itemno, r)
DialogPtr dlg;
intitemno;
Rect    *r;
{
 short  itemtype;
 Handle item;
 
 GetDItem(dlg, itemno, &itemtype, &item, r);
}

/* calls ValidRect for the rect enclosing    */
/* item #itemno in dlg. */
static void validitem(dlg, itemno)
DialogPtr dlg;
intitemno;
{
 Rect   itemr;
 GrafPtroldport;
 
 /* make sure the port is correct  */
 /* before doing the ValidRect */
 GetPort(&oldport);
 SetPort(dlg);
 
 getitemr(dlg, itemno, &itemr);
 ValidRect(&itemr);
 
 SetPort(oldport);
}

/* returns the hiliting of the button*/
/* item #itemno in dlg.   */
static int gethilite(dlg, itemno)
DialogPtr dlg;
intitemno;
{
 short  itemtype;
 Handle item;
 Rect box;
 
 GetDItem(dlg, itemno, &itemtype,
 &item, &box);
 return((*(ControlHandle)item)->contrlHilite);
}

/* sets the hiliting of the button item*/
/* #itemno in dlg to state. */
static void hilitebtn(dlg, itemno, state)
DialogPtr dlg;
intitemno;
intstate;
{
 short  itemtype;
 Handle item;
 Rect box;
 
 GetDItem(dlg, itemno, &itemtype,
 &item, &box);
 HiliteControl(item, state);
}

/* sets the title of the Add button to title.      */
static void set_title(dlg, title)
DialogPtr dlg;
char    *title;
{
 short  itemtype;
 Handle item;
 Rect box;
 
 GetDItem(dlg, ADD_BTN, &itemtype,
 &item, &box);
 SetCTitle(item, title);
}

/* the tests used in noselect(), isfolder(), */
/* and isfile() come from “C Workshop” in    */
/* the April ’86 MacTutor.*/

/* returns true if there’s no selection in   */
/* SF’s list, false if there is    */
static Boolean noselect()
{
 return((reply.fType == 0) &&
 (reply.fName.length == 0));
}

/* returns true if a folder is selected in   */
/* SF’s list, false if not*/
static Boolean isfolder()
{
 return((reply.fType > 0) &&
 (reply.fName.length == 0));
}

/* returns true if a file is selected in     */
/* SF’s list, false if not*/
static Boolean isfile()
{
 return((reply.fType > 0) &&
 (reply.fName.length > 0));
}

/* if mfs, simply returns the current vrefnum;     */
/* if hfs, makes the current dir a working               */
/* directory, and returns the wdrefnum, or 0 if    */
/* the PBOpenWD  failed because too many           */
/* wd’s were already open.*/
static short makewd()
{
 WDPBRecwdpb;
 HParamBlockRec  hpb;
 
 /* if running MFS */
 if (FSFCBLen == -1)
 return(-SFSaveDisk);
 
 /* running HFS; check for an MFS  */
 /* volume; return vrefnum if so   */
 hpb.volumeParam.ioCompletion = NULL;
 hpb.volumeParam.ioNamePtr = NULL;
 hpb.volumeParam.ioVRefNum = -SFSaveDisk;
 hpb.volumeParam.ioVolIndex = NULL;
 PBHGetVInfo(&hpb, false);
 if (hpb.volumeParam.ioVSigWord == 0xd2d7)
 return(-SFSaveDisk);
 
 /* make a new working directory */
 wdpb.ioCompletion = NULL;
 wdpb.ioNamePtr = NULL;
 wdpb.ioVRefNum = -SFSaveDisk;
 wdpb.ioWDProcID = ‘ERIK’;
 wdpb.ioWDDirID = CurDirStore;
 
 if (!PBOpenWD(&wdpb, false))
 return(wdpb.ioVRefNum);
 else
 return(0);
}

/* fill in an mfcell with data about a file  */
static void mkcell(thecell, fname, ftype, fver, vrefnum, dirid)
MFCELL  *thecell;
String(63)*fname;
OSType  ftype;
short   fver;
short   vrefnum;
long    dirid;
{
 int    namelen;
 
 thecell->ftype = ftype;
 thecell->fver = fver;
 thecell->vrefnum = vrefnum;
 
 /* MFS doesn’t use dirid’s, so  */
 /* just use 0 instead    */
 if (FSFCBLen > 0)
 thecell->dirid = dirid;
 else
 thecell->dirid = 0;
 
 namelen = fname->length;
 BlockMove(fname, &thecell->fname, namelen + 1);
 
 /* fill rest of fname with spaces */
 memset(thecell->fname.text + namelen, ‘ ‘,
 63 - namelen);
}

/* returns true if the file currently in the */
/* reply rec is already in my list, false if */
/* not. Also returns in thecell the cell           */
/* that the file is in, if it’s in the list.             */
static Boolean inlist(thecell)
Cell  *thecell;
{
 MFCELL thefile;
 
 /* build a cell with info about the current file  */
 mkcell(&thefile, &reply.fName, reply.fType,
 reply.version, -SFSaveDisk, CurDirStore);
 
 /* look for the data*/
 SetPt(thecell, 0, 0);
 return(LSearch(&thefile, sizeof(MFCELL),
 NULL, thecell, list));
}

/* unselects any selected cells, selects     */
/* thecell, and scrolls to it.*/
static void sel1cell(thecell)
Cell  *thecell;
{
 Cell acell;
 
 /* unselect all selected cells. There must  */
 /* be a better way to do this.    */
 SetPt (&acell, 0, 0);
 while (LGetSelect(true, &acell, list)) {
 LSetSelect(false, &acell, list);
 acell.v += 1;
 }
 
 LSetSelect(true, thecell, list);
 LAutoScroll(list);
}

/* adds fname to my list  */
static void addfile(fname, ftype, fver, vrefnum, dirid, wdrefnum)
String(63)*fname;
OSType  ftype;
short   fver;
short   vrefnum;
long    dirid;
short   wdrefnum;
{
 Cell   thecell;
 MFCELL newfile;
 
 /* add a new row at the bottom of the list  */
 LDoDraw(false, list);
 thecell.h = 0;
 thecell.v = LAddRow(1, (*list)->dataBounds.bottom,
 list);
 LDoDraw(true, list);
 
 /* build the new cell  */
 mkcell(&newfile, fname, ftype, fver,
 vrefnum, dirid);
 
 /* add the new cell data */
 LSetCell(&newfile, sizeof(MFCELL),
 &thecell, list);
 thecell.h = 1;
 LSetCell(&wdrefnum, sizeof(short),
 &thecell, list);
 
 /* select the new cell */
 thecell.h = 0;
 sel1cell(&thecell);
}

/* remove file in thecell */
static void rmfile(thecell)
Cell  *thecell;
{
 LDelRow(1, thecell->v, list);
}

/****************************************
 dlog hook stuff
****************************************/
static pascal short dlg_hook(item, dlg)
short   item;
DialogPtr dlg;
{
 void initdlg();
 void disposdlg();
 void chknmlist();
 void do_add();
 void do_rm();
 
 /* if a key is pressed, check*/
 /* SF’s file list selection*/
 if (item >= 1000) {
 chklist = true;
 return(item);
 }
 
 switch (item) {
 case INITMSG:
 initdlg(dlg);
 return(INITMSG);
 
 /* if we’re not over a file, return */
 /* FOLDMSG since we’re over a folder*/
 /* and check the list next time   */
 case ADD_BTN:
 if (isfile()) {
 do_add(dlg);
 return(ADD_BTN);
 } else {
 chklist = true;
 return(FOLDMSG);
 }
 
 case RM_BTN:
 do_rm(dlg);
 return(RM_BTN);
 
 /* return getCancel here to make sure */
 /* that SF doesn’t make a wd for a file     */
 case DONE_BTN:
 disposdlg();
 good = true;
 return(getCancel);
 
 /* item=getOpen on a double-click */
 /* or a press of Return or Enter  */
 case getOpen:
 do_add(dlg);
 return(NULLMSG);
 
 case getCancel:
 disposdlg();
 good = false;
 return(getCancel);
 
 case getNmList:
 chknmlist(dlg);
 return(getNmList);
 
 /* for all of these, we want to   */
 /* check the file list next time and*/
 /* return the same item number    */
 case DIRMSG:
 case FOLDMSG:
 case GETDISK:
 case getEject:
 case getDrive:
 chklist = true;
 return(item);
 
 default:
 return(item);
 }
}

/* init the dialog */
static void initdlg(dlg)
DialogPtr dlg;
{
 pascal voiduserdraw();
 
 short  itemtype;
 Handle item;
 Rect   box;
 Rect   bounds;
 Point  csize;
 
 /* save dialogptr */
 mfdlg = dlg;
 
 /* set the prompt text */
 GetDItem(dlg, PROMPT_ITEM, &itemtype,
 &item, &box);
 SetIText(item, myprompt);
 
 /* get info about file list item  */
 /*  and install user proc*/
 GetDItem(dlg, LST_ITEM, &itemtype,
 &item, &box);
 SetDItem(dlg, LST_ITEM, itemtype, userdraw,
 &box);
 
 /* make file list */
 box.right -= SCRLSIZE;
 SetRect(&bounds, 0, 0, 2, 0);
 SetPt (&csize, box.right - box.left, 16);
 list = LNew(&box, &bounds, &csize, LDEFID, dlg,
 true, false, false, true);
 
 /* start with the Remove btn inactive,*/
 /* since there’s nothing to remove*/
 hilitebtn(dlg, RM_BTN, INACTIVE);
}

/* copy list info to files, then dispose of list   */
static void disposdlg()
{
 Cell   thecell;
 MFCELL data;
 short  size;
 short  wdrefnum;
 int    i;
 
 nfiles = (*list)->dataBounds.bottom;
 
 /* if we can’t get mem for file list, */
 /* pretend there aren’t any files */
 if (!(files = NewHandle(nfiles * sizeof(FSTRUCT))))
 nfiles = 0;
 
 /* copy info from list into files */
 for (i = 0; i < nfiles; i++) {
 /* get mfcell stuff from column 0 */
 thecell.h = 0;
 thecell.v = i;
 size = sizeof(MFCELL);
 LGetCell(&data, &size, &thecell, list);
 
 /* get wdrefnum from column 1*/
 thecell.h = 1;
 size = sizeof(short);
 LGetCell(&wdrefnum, &size, &thecell, list);
 
 (**files)[i].ftype = data.ftype;
 (**files)[i].vrefnum = wdrefnum;
 (**files)[i].fver = data.fver;
 BlockMove(&data.fname, &(**files)[i].fname,
 64);
 }
 
 LDispose(list);
}

/* userproc to draw my file list */
/* and outline Add button */
static pascal void userdraw(wp, item)
WindowPtr wp;
short   item;
{
 Rect   box;
 
 chklist = true;
 
 LUpdate(wp->visRgn, list);
 
 /* frame file list*/
 getitemr(wp, LST_ITEM, &box);
 box.right -= SCRLSIZE;
 InsetRect(&box, -1, -1);
 PenSize(1, 1);
 FrameRect(&box);
 
 /* outline Add button  */
 getitemr(wp, ADD_BTN, &box);
 InsetRect(&box, -4, -4);
 PenNormal();
 PenSize(3, 3);
 FrameRoundRect(&box, 16, 16);
}

/* handle hits on the Add button */
static void do_add(dlg)
DialogPtr dlg;
{
 void   wdalert();
 
 short  wdrefnum;
 Cell   thecell;
 
 if (!inlist(&thecell)) {
 if (wdrefnum = makewd()) {
 addfile(&reply.fName, reply.fType,
 reply.version, -SFSaveDisk, CurDirStore,
 wdrefnum);
 hilitebtn(dlg, ADD_BTN, INACTIVE);
 hilitebtn(dlg, RM_BTN, ACTIVE);
 } else
 wdalert(dlg);
 }
}

/* positions the wd alert so that  */
/* it’s just above and inside the  */
/* sf dlg, and then displays it.   */
static void wdalert(dlg)
DialogPtr dlg;   /* the sfgetfile dlg*/
{
 int    width;
 int    height;
 AlertTHndl alrtres;
 Point  botleft;
 GrafPtroldport;
 
 /* init botleft in local coords */
 botleft.h = 0;
 botleft.v = dlg->portRect.bottom;
 
 /* convert botleft to globals*/
 GetPort(&oldport);
 SetPort(dlg);
 LocalToGlobal(&botleft);
 SetPort(oldport);
 
 /* set the alert portrect. If we  */
 /* can’t get the resource, bag  */
 /* the alert and just beep.*/
 if (alrtres = GetResource(‘ALRT’, WDALRTID)) {
 HNoPurge(alrtres);
 width = (*alrtres)->boundsRect.right -
 (*alrtres)->boundsRect.left;
 height = (*alrtres)->boundsRect.bottom - 
 (*alrtres)->boundsRect.top;
 (*alrtres)->boundsRect.top = botleft.v - height
 - 10;
 (*alrtres)->boundsRect.left = botleft.h
 + 10;
 (*alrtres)->boundsRect.bottom = botleft.v 
 - 10;
 (*alrtres)->boundsRect.right = botleft.h + width
 + 10;
 
 Alert(WDALRTID, NULL);
 } else
 SysBeep(5);
}

/* handle hits on the Remove button*/
static void do_rm(dlg)
DialogPtr dlg;
{
 void chknmlist();
 
 Cell thecell;
 
 SetPt(&thecell, 0, 0);
 while (LGetSelect(true, &thecell, list)) {
 rmfile(&thecell);
 SetPt(&thecell, 0, 0);
 }
 
 hilitebtn(dlg, RM_BTN, INACTIVE);
 chknmlist(dlg);
}

/* chknmlist is called after the selection in      */
/* Standard File’s list changes. I  check to */
/* see what the current selection is in SF’s */
/* list and set the title of my Add button               */
/* appropriately.*/
static void chknmlist(dlg)
DialogPtr dlg;
{
 Rect   btnrect;
 GrafPtroldport;
 Cell   thecell; 
 Booleaninmine;  
 
 /* if there’s no selection, unhilite the add button     */
 /* if the selection is a folder, change the title of    */
 /*     the Add button to Open and make sure       */
 /*that it’s active. */
 /* if the selection is a file, make sure that the       */
 /*title of the Add button is Add and that it’s    */
 /*active.*/
 if (noselect())
 hilitebtn(dlg, ADD_BTN, INACTIVE);
 else if (isfolder()) {
 if (gethilite(dlg, ADD_BTN) == INACTIVE)
 hilitebtn(dlg, ADD_BTN, ACTIVE);
 
 if (is_add) {
 set_title(dlg, openstr);
 
 /* changing the title generates an*/
 /* unnecessary update event. This */
 /* gets rid of it.*/
 validitem(dlg, ADD_BTN);
 
 is_add = false;
 }
 } else if (isfile()) {
 /* if the selected file in SF’s list is also      */
 /* in my list, select the corresponding           */
 /* cell in my list and hilite the rm button */
 if ((inmine = inlist(&thecell)) == true) {
 sel1cell(&thecell);
 hilitebtn(dlg, RM_BTN, ACTIVE);
 }
 
 /* if the file is in the list, make sure that     */
 /* the Add button is inactive; otherwise,   */
 /* make sure that it’s active.    */
 if ((inmine) &&
 (gethilite(dlg, ADD_BTN) == ACTIVE))
 hilitebtn(dlg, ADD_BTN, INACTIVE);
 else if ((!inmine) &&
 (gethilite(dlg, ADD_BTN) == INACTIVE))
 hilitebtn(dlg, ADD_BTN, ACTIVE);
 
 if (!is_add) {
 set_title(dlg, addstr);
 
 /* bag the update event */
 validitem(dlg, ADD_BTN);
 
 is_add = true;
 }
 }
}

/**********************************
 filter stuff
**********************************/
static pascal Boolean filter(dlg, event, itemhit)
DialogPtr dlg;
EventRecord *event;
short   *itemhit;
{
 void   do_keydown();
 void   do_mousedown();
 
 if (chklist) {
 chknmlist(mfdlg);
 chklist = false;
 }
 
 switch (event->what) {
 case mouseDown:
 do_mousedown(dlg, event);
 return(false);
 case keyDown:
 case autoKey:
 do_keydown(dlg, event);
 return(false);
 default:
 return(false);
 }
}

/* handle mouseDowns and  */
/* track mouse if in list box */
static void do_mousedown(dlg, event)
DialogPtr dlg;
EventRecord *event;
{
 Rect   listr;
 Point  locpt;
 GrafPtroldport;
 Cell   thecell;
 
 GetPort(&oldport);
 SetPort(dlg);
 
 getitemr(dlg, LST_ITEM, &listr);
 
 locpt = event->where;
 GlobalToLocal(&locpt);
 if (PtInRect(&locpt, &listr)) {
 LClick(&locpt, event->modifiers, list);
 
 /* hilite the remove button if  */
 /* there’s a selection   */
 SetPt(&thecell, 0, 0);
 if (LGetSelect(true, &thecell, list))
 hilitebtn(dlg, RM_BTN, ACTIVE);
 else
 hilitebtn(dlg, RM_BTN, INACTIVE);
 }
 
 SetPort(oldport);
}

/* flash Add/Open button if */
/* Return or Enter pressed*/
static void do_keydown(dlg, event)
DialogPtr dlg;
EventRecord *event;
{
 int    c;
 Cell thecell;
 
 /* only flash Add button if we’re on*/
 /* a file that isn’t in my list, or if*/
 /* we’re on a folder.    */
 c = event->message & charCodeMask;
 if ((c == ‘\n’) || (c == ENTER))
 if ((isfile() && !inlist(&thecell)) || isfolder())
 flashbtn(dlg, ADD_BTN);
}

Listing:  mfile.r

/*
** File mfile.r
** Resource file for MultiFile dialog.
**
** Copyright © Eric Schlegel 1987, 1988
*/


#include “types.r”

resource ‘DITL’ (4000) {
 {
 /* [1] */
 {2210, 256, 2228, 336},
 Button {
 enabled,
 “Add”
 },
 /* [2] */
 {0, 571, 80, 589},
 Button {
 enabled,
 “Hidden”
 },
 /* [3] */
 {163, 256, 181, 336},
 Button {
 enabled,
 “Cancel”
 },
 /* [4] */
 {39, 232, 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, 246},
 UserItem {
 enabled
 },
 /* [9] */
 {124, 252, 125, 340},
 UserItem {
 disabled
 },
 /* [10] */
 {0, 532, 101, 628},
 StaticText {
 disabled,
 “”
 },
 /* [11] */
 {138, 256, 156, 336},
 Button {
 enabled,
 “Done”
 },
 /* [12] */
 {225, 256, 243, 336},
 Button {
 enabled,
 “Add”
 },
 /* [13] */
 {251, 256, 269, 336},
 Button {
 enabled,
 “Remove”
 },
 /* [14] */
 {215, 13, 279, 229},
 UserItem {
 enabled
 },
 /* [15] */
 {195, 16, 211, 225},
 StaticText {
 disabled,
 “”
 }
 }
};

resource ‘DLOG’ (4000, purgeable) {
 {0, 0, 285, 348},
 dBoxProc,
 invisible,
 noGoAway,
 0x0,
 4000,
 “”
};

resource ‘DITL’ (3999) {
 {
 /* [1] */
 {64, 108, 82, 188},
 Button {
 enabled,
 “OK”
 },
 /* [2] */
 {10, 10, 57, 220},
 StaticText {
 disabled,
 “Due to a file system “
 “limitation, you can’t “
 “add any more files to “
 “the list.”
 }
 }
};

resource ‘ALRT’ (3999, purgeable) {
 {40, 40, 130, 270},
 3999,
 {
 OK, visible, sound1,
 OK, visible, sound1,
 OK, visible, sound1,
 OK, visible, sound1
 }
};

resource ‘STR#’ (512, purgeable) {
 {
 “Open”,
 “Add”
 }
};

include “mfldef” ‘LDEF’ (128) as ‘LDEF’ (128, “MultiFile”);

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »

Price Scanner via MacPrices.net

Boost Mobile will sell you an iPhone 11 for $...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering an iPhone 11 for $149.99 when purchased with their $40 Unlimited service plan (12GB of premium data). No trade-in is required... Read more
Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more
Every model of Apple’s 13-inch M3 MacBook Air...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13″ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9″ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more
Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more

Jobs Board

Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.