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, Ill present my solution to the problem, in the form of a MultiFile Select dialog.
MPWs 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 Commandos 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.
Programmers 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. Multifiles 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. Multifiles 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 youve 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 isnt running under MFS. If so, makewd simply returns the current volume refnum.
2. The current volume isnt 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 arent 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 isnt 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 cant 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 Ive 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. Ive tried setting the lOnlyOne flag in the list record and then selecting the single cell, but it doesnt work. Im 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 its 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 wds 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 cant 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 doesnt always mean that the selection has already changed. An example of this problem is the Eject button. The current disk wont be ejected until after Ive seen the hit on the button; therefore, the selection hasnt 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 files volume under both MFS and HFS; it is never a working directory refnum. Dirid is the directory id of the files 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 files 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
MultiFiles 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 Ive 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 doesnt 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 doesnt 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 Ill 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 Im finished. If you are creating lots of WDs, as I am, you should also check to see that PBOpenWD succeeded. Theres 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 programs 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. Ive 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 doesnt 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 doesnt 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. Ive 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>
#includemfile.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 apps 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
INCLUDETraps.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
*/
#includemfile.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 dont 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>
#includemfpriv.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 files version*/
/* the files 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 wouldnt 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 youve 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>
#includemfpriv.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 files version*/
/* the files 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 SFs 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 cant 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 weve 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 theres no selection in */
/* SFs list, false if there is */
static Boolean noselect()
{
return((reply.fType == 0) &&
(reply.fName.length == 0));
}
/* returns true if a folder is selected in */
/* SFs list, false if not*/
static Boolean isfolder()
{
return((reply.fType > 0) &&
(reply.fName.length == 0));
}
/* returns true if a file is selected in */
/* SFs 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 */
/* wds 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 doesnt use dirids, 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 its 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*/
/* SFs file list selection*/
if (item >= 1000) {
chklist = true;
return(item);
}
switch (item) {
case INITMSG:
initdlg(dlg);
return(INITMSG);
/* if were not over a file, return */
/* FOLDMSG since were 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 doesnt 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 theres 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 cant get mem for file list, */
/* pretend there arent 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 */
/* its 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 */
/* cant 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 Files list changes. I check to */
/* see what the current selection is in SFs */
/* list and set the title of my Add button */
/* appropriately.*/
static void chknmlist(dlg)
DialogPtr dlg;
{
Rect btnrect;
GrafPtroldport;
Cell thecell;
Booleaninmine;
/* if theres 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 its active. */
/* if the selection is a file, make sure that the */
/*title of the Add button is Add and that its */
/*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 SFs 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 its 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 */
/* theres 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 were on*/
/* a file that isnt in my list, or if*/
/* were 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 cant
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);