TweetFollow Us on Twitter

Aliases
Volume Number:9
Issue Number:1
Column Tag:Cover Story

Related Info: Alias Manager Event Manager

Aliases ’R us

Or, how to make the System do the grunt work.

By Tim Swihart, Cupertino, California

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

About the author

Tim Swihart is an accomplished author and programmer. He is the author of numerous programming articles and the co-author of “Programming for System 7” (the first source code-oriented System 7 book not written by Apple, part of Addison-Wesley’s “Macintosh Inside Out” series). Tim is also the author of BNDL Banger - believed to be the first publicly available solution for the hassles of getting the Finder to recognize BNDL-related resource changes.

Aliases are a great part of System 7 - they provide a compact, portable “pointer” to files, folders, and even other volumes. Aliases remember where their target is, even if that target is offline, unmounted, renamed, or restored from a backup.

Aliases are generally transparent to most applications (the Finder and Standard File conspire to hide them from your application). But, perhaps your application can offer users additional functionality by taking further advantage of the flexibility aliases offer. They’re an easy way to keep track of ancillary files that your application needs, but that maybe don’t have to be in either the Preferences folder or in the same folder as your application. Inside Mac uses the example of a custom dictionary for a specific document - aliases allow your application to tie the dictionary and the document together, without requiring the user to keep them in the same place for all eternity.

The paragraphs ahead show how to incorporate aliases within your own application and along the way we’ll create an application (Alias Meister) that simplifies creating and sorting Finder aliases. It’s rare that an application honestly needs to create Finder aliases, but they provide an easy way to demonstrate the concepts shown in this article. So we’ll examine one of those “rare” applications as we forge ahead. Once you've learned the basics of creating and managing aliases, you'll be better prepared for dealing with the Edition Manager (which uses aliases as the link between an edition and the document that published it) and many other parts of the System.

A Word About the Environment

Alias Meister was created under MPW C v.3.2.3 and all of the listings in the article are designed to compile under MPW. A Think C v.5.0.3 compatible version of this application is provided on the disk for this issue. There are minor differences between the two versions, but the resulting applications are functionally equivalent.

A Word About Aliases

There are two common things that come to mind when talking about aliases. Most users immediately think of Finder aliases, but they’re really just a special case of the second type - a generic alias record. Each alias record uniquely identifies one file and is traditionally stored as an ‘alis’ resource. Thus, a Finder alias is simply a small file with one ‘alis’ resource, a special file type, and sometimes a custom icon family.

The custom icon is for the user's benefit and the special file type is for the Finder’s benefit. That leaves with only the ‘alis’ resource, or rather, the alias record - which is exactly what we want to focus on.

Why Use Aliases?

An alias record contains the target file’s ID number, name, parent folder ID, volume type the target lives on (floppy, hard drive, server, etc.), creation date, file and creator types, and, if needed, the information required to mount the volume the target lives on (this is especially important if the target is, or lives on, a shared volume). This provides enough information for the Alias Manager to hunt down the target file under most circumstances. There are times that the target simply can’t be found because all of the information in an alias record has become out of date (backup a file, delete it, restore it to a different location, and rename it - that pretty much invalidates everything in the alias record). Under normal circumstances, aliases are very robust and the Alias Manager will update an alias it’s using if that alias no longer completely describes the target file (as would happen if the target was renamed).

For more information on the Alias Manager’s search algorithms, see “A Brief Tangent” later in this article or read Chapter 4 of "Inside Macintosh: Files" from Addison-Wesley. You’ll also find additional information in “Inside Macintosh: Files” on updating “stale” aliases.

Despite the wealth of information kept in an alias record, its format (with two minor exceptions) is considered off limits. Those two exceptions are the userType field (which can also be used to hold any 4 bytes of information your application desires) and the aliasSize field (which is simply a count of the number of bytes the rest of the alias record occupies). The parent folder ID, volume mounting information, creator type, and other bits of information that help uniquely identify the target are all stashed in the undocumented, variable-length, chunk of data that makes up the bulk of the alias record.

Since most of an alias record’s contents are off limits to applications, why bother to use one? Why not just save an FSSpec record for the target? After all, the format of an FSSpec record is clearly documented- it contains the volume reference number that the target lives on, along with the target's parent folder, and the target’s file name. What more could you ask for?

Stability! FSSpec records are “fragile” and are quickly rendered meaningless between launches of your application. If the target file moves, is renamed, is on an unmounted volume, or is simply on a volume that wasn’t mounted in the same order as it was when the FSSpec was created (such as removable media), the FSSpec is no longer accurate (if any of those three fields change, the FSSpec is “damaged” and repairing it often isn’t possible. So, saving an FSSpec is of no real value.

Aliases, on the other hand, are designed to hold enough information that the target file can be tracked down on demand, even if it’s on a floppy or server that’s not currently online (the Alias Manager will prompt the user to insert the target floppy or will mount the desired server) or if it’s been moved, renamed, or restored from a backup (which tends to alter the file’s ID number).

Still not convinced? Are you sitting there grumbling that File Manager routines don’t actually accept aliases, no matter how wonderful aliases are, so what’s the real point of using them? The Alias Manager routine ResolveAlias easily converts an alias record into an FSSpec (which is what the new System 7 File Manager routines like best). The Alias Manager routine NewAlias converts an FSSpec into an alias record. Together, they make it simple to have the best of both worlds - the flexibility of alias records and the ease-of-use of FSSpec records.

Time For Some Examples

I use a lot of Finder aliases to help keep my productivity up - they let me jump quickly from folder to folder, move files easily between any of the several Macs I use (they all have File Sharing turned on and they all have a collection of aliases to my other Macs), and they help clutter up my desktop (a clean desktop is a sign of too much spare time). I use so many aliases, I’ve started sorting them into various locations - the most heavily used aliases stay on the desktop, the least used are in a folder within my Apple Menu Items folder, and the rest float loose in my Apple Menu Items folder.

Creating a new Finder alias involves several steps: selecting the item’s icon; picking Make Alias from the Finder’s File menu; and moving the resulting alias to where I want it (which is usually a folder that has be opened in yet another step). Along the way, I usually remove the word “alias” from the end of the alias’s name (the italics are enough of a reminder for me). Granted, I could leave aliases to the various destinations on my desktop (saving me the step of opening the desired destination), but I’d still have to create the new Finder aliases and drop them on the destination aliases - that’s still more steps than are really needed.

This whole process is just tedious enough that I wanted to automate it. “Surely,” I thought, “there must be a way to just drop the icons I want aliases for onto something and have that something create the Finder aliases and store them where I want the resulting Finder aliases to be stored.”

Sounds like a job for a tiny little application. In fact, that application could keep track of my desired destination simply be storing an alias to that destination. If I have multiple destinations, I can just make multiple copies of this application and configure each of them to point to a different location. What better way to showcase alias records, simplify creating Finder aliases, and have the basis for an article? Alias Meister is my solution to this problem.

How Do I Use It?

Just drop any icon (files, folders, volumes, Finder aliases - everything works) on top of Alias Meister’s icon and Alias Meister will create a Finder alias for that icon and store the new alias in a predetermined location. One step, dropping the icon you want an alias for onto Alias Meister, is all it takes to do what used to take several. I haven’t done extensive timing comparisons, but the perception is that creating aliases this way is noticeably faster than the old-fashioned way.

What Predetermines Where They Go?

Alias Meister stores an alias to your desired destination. This “destination alias” is stored as an ‘alis’ resource in Alias Meister’s resource fork. When Alias Meister is first created, it has no ‘alis’ resource (there’s no clean way to create a universally meaningful alias record, so none is included in the initial copy of the application). Then where does Alias Meister send the Finder aliases it creates since there is no “desired destination” hanging around? If Alias Meister can’t find an ‘alis’ resource in its own resource fork, it creates one that points to your boot disk’s “Apple Menu Items” folder. So, if you drop icons onto Alias Meister without first double-clicking the application (see the next section), Alias Meister assumes you want the newly created Finder aliases to be sent to your “Apple Menu Items” folder.

How Can I Edit the Destination?

Launch Alias Meister by double-clicking its icon and you’ll be presented with a CustomGetFile dialog that shows only volumes and folders. You’ll also see two new buttons: “Select” and “Use “Apple Menu Items”” (see Figure 1).

Figure 1: Alias Meister’s CustomGetFile dialog

The “Use “Apple Menu Items”” button is renamed each time you open another folder, back up a level (by hitting COMMAND-UP ARROW), or jump to the desktop (by clicking the “Desktop” button or pressing COMMAND-D). The button’s new name always includes as much of the target destination’s name as fits comfortably in the button. This name is the same as the name in the popup menu near the top of the dialog and represents the folder whose contents you’re currently browsing. Clicking this button (which we will call the “Use current directory”) designates the named folder/volume as the destination while clicking the “Select” button designates the folder/volume currently hilited in the scrolling list as the destination.

Navigate to your desired target folder and click one of the two new buttons. Alias Meister creates an alias for this new destination and stores that alias in Alias Meister’s resource fork. Naturally, Alias Meister understands that you might want to reconfigure it after you’ve used it for awhile, so before it stores the destination’s ‘alis’ record, Alias Meister removes any existing ‘alis’ resource with the same resource ID that Alias Meister wants to use (128).

The “Use Current Directory” button lets you pick the directory currently shown in the popup at the top of the CustomGetFile dialog (which is the directory whose contents are currently shown in the scrolling list in the dialog). The “Select” button lets you pick the folder that’s hilited in the scrolling list (so you don’t have to open it, then click “Use Current Directory”). To select the desktop as the target, click the “Desktop” button, then the “Use Current Directory” button. If no folder is selected in the list, the “Select” button is dimmed (it has no meaning in this situation, so dimming is the “proper” thing to do).

Remembering the Destination

Hmmm, how do we create an alias out of thin air for the boot disk’s “Apple Menu Items” folder? Simple, call FindFolder, telling it to search the disk you booted from for the “Apple Menu Items” folder. The Folders.h interface file defines constants to specify the boot disk (kOnSystemDisk) and the “Apple Menu Items” folder (kAppleMenuFolderType). Be certain to use the constants since international users may have a different name for their “Apple Menu Items” folder.

FindFolder stuffs the proper volume reference number and parent directory ID into variables passed to it (See Listing 1). Now that we know what volume, and in which folder, the “Apple Menu Items” folder lives, we can create an FSSpec that describes that folder. Once we have an FSSpec, we call NewAlias (see Listing 2), passing it a nil for the first FSSpecPtr, a pointer to the FSSpec we just conjured up for the “Apple Menu Items” folder, and a pointer to an AliasHandle (yes, pass the handle’s address).

Listing 1: Finding the “Apple Menu Items” Folder

myErr = FindFolder(kOnSystemDisk, kAppleMenuFolderType,
 kDontCreateFolder, &myVRefNum, &myDirID);
Listing 2: Creating an Alias
NewAlias(nil, &destFSSpec, &myAliasHndl);

A Brief Tangent

There are three ways the Alias Manager can search for a file: relative, absolute fast, and absolute exhaustive.

A “relative” search starts at a folder that’s a common parent to the two files “linked” by the alias record. This means the Alias Manager doesn’t have to search the entire volume looking for the target file. Only the part of the volume from the common parent folder “downwards” has to be searched.

During all absolute searches, the Alias Manager starts by identifying the target’s volume and, if necessary, hunts through the entire volume looking for the target. A “fast absolute” search is a slight variation on a relative search - the differences are that a relative search isn’t as picky about identifying the volume the target lives on and a “fast absolute” search starts with the target file’s parent folder instead of a “common parent” folder. An “exhaustive absolute” search identifies all possible matches on a given volume by starting at the root of that volume and searching the entire volume.

The important thing to remember is that a “relative” alias links two files together and an absolute alias simply links a file to a given volume. Alias Meister creates absolute alias records, but your application might need to create relative aliases.

Return From Interrupt

NewAlias knows that if the first FSSpecPtr is nil, an absolute alias record should be created (if it’s not nil, the alias that’s created will be relative to this first FSSpecPtr’s target file). The resulting alias is stuffed into the block of memory pointed to by the AliasHandle we passed into NewAlias.

Since we have a handle to the resulting alias, storing our destination’s alias record in Alias Meister’s resource fork is as simple as calling:

AddResource((Handle)myAliasHndl, rAliasType, kMyAlisID, nil);

The interface file “Aliases.h” defines rAliasType to be ‘alis’ and I’ve defined kMyAlisID to be 128 (there’s nothing magic about 128, but as we’ll see later, the ID used when creating a Finder alias MUST be 0).

Combining the calls to create the alias, delete any existing ‘alis’ resources, and saving the new destination alias record gives us the code shown in Listing 3.

Listing 3: Storing an alias as a resource

if (!NewAlias(nil, &destFSSpec, &myAliasHndl)){
 // rip out current 'alis' resource and stuff this one in
 curDestAliasHndl = (AliasHandle) 
 Get1Resource(rAliasType, kMyAlisID);
 if (curDestAliasHndl) { // delete current 'alis' first
 RmveResource((Handle)curDestAliasHndl);
 DisposHandle((Handle)curDestAliasHndl);
 }
 AddResource((Handle)myAliasHndl, rAliasType, kMyAlisID, nil);
 UpdateResFile(gMyResFileRefNum); // flush changes
 
 // hide Finder glitch - renamed files on desktop don't show up
 // with new name until you've rebooted.  Old name is cached and
 // you can't open the app until you've rebooted unless you 
 // change the date/time stamp on the “Desktop Folder”
 TouchDirectory(myFSSpec.vRefNum, myFSSpec.parID);
 
 // rename this application to reflect its new destination.
 DoRenameMe(&destFSSpec);
}

Bad News and Good News

The bad news is that there’s a drawback to storing the ‘alis’ record in the application’s resource fork (and this is part of why “normal” applications use a custom file in the “Preferences” folder): multiple users can’t launch this application at the same time from a shared volume (each user needs to have write permissions on the application’s resource fork). But, if I put the ‘alis’ resource in a “preferences” file, you couldn’t configure multiple copies very easily (they’d all try to use the same “preferences” file).

The good news is that since Alias Meister is tiny (well under 20K), the loss of multi-launching simply isn’t that big of a problem. In fact, it’s small size also means there’s practically no reason to mind keeping multiple copies around (each configured for a different destination).

Then What Happens?

When Alias Meister’s icon has other icons dropped onto it, this ‘alis’ resource is retrieved, converted back into an FSSpec and used to describe the parent folder in which the new Finder aliases are created. As mentioned earlier, if there is no ‘alis’ resource, an FSSpec for the “Apple Menu Items” folder is created (see Listing 1).

The code for retrieving the ‘alis’ record and converting it back into an FSSpec is shown in Listing 4.

Listing 4: Retrieving an Alias Record from a Resource File.

curDestAliasHndl = (AliasHandle) 
 Get1Resource(rAliasType, kMyAlisID);
if (curDestAliasHndl) { // convert alias into FSSpec
 myErr = ResolveAlias(nil, curDestAliasHndl, 
 curDestFSSpecPtr, &wasChanged);
 if (!myErr) { didItWork = true; }
}

How Are Finder Aliases Created?

A Finder alias is simply a file with one ‘alis’ resource (whose ID is always 0). You’ve already seen how to store an alias record as an ‘alis’ resource, all that remains is to open the target file using the FSSpec we created in Listing 4.

One quick note: icons dropped on a System 7-savvy application are sent as Apple events. When the event’s parameters are retrieved, the “incoming” file will be described by an FSSpec, so we have to convert it to an alias record.

Listing 5 shows the source code for opening/creating the Finder alias file, converting the incoming file’s FSSpec into an alias record, saving the alias record into the new file, closing it, and setting the Finder info bit that indicates this file is an alias.

Listing 5: Creating a Finder Alias

// create & open new file in destination folder
FSpCreateResFile(&destFSSpec, ApplCreator, 
 docsFileType, smSystemScript);
fileRef = FSpOpenResFile(&destFSSpec, fsWrPerm); 
// = -1 for error
if (fileRef != -1) {
 // check for existing resource in case file already existed
 newAliasHndl = (AliasHandle) 
 Get1Resource(rAliasType, kFndrAlisID);
 if (newAliasHndl) { // delete current 'alis'
 RmveResource((Handle)newAliasHndl);
 DisposHandle((Handle)newAliasHndl);
 }

 // convert incoming FSSpec into alias
 NewAlias(nil, &myFSS, &newAliasHndl);

 // add new 'alis' to file we opened
 AddResource((Handle)newAliasHndl, rAliasType, kFndrAlisID, nil);

 CloseResFile(fileRef);
 FlushAVolume(&destFSSpec); // force changes to disk

 // get Finder info for doc we just created
 FSpGetFInfo(&destFSSpec, &theFInfo); 

 // set 'isAlias' bit in Finder info
 theFInfo.fdFlags |= kIsAlias; 
 FSpSetFInfo(&destFSSpec, &theFInfo); // write it to disk
 FlushAVolume(&destFSSpec); // force changes to disk
}

File Type Glitch!

The file and creator types for a Finder alias that points to a document is the same as that document uses (which allows the Finder to automatically use the same icon for the alias as it does for the file the alias points to). File types for Finder aliases that point to non-document files (folders, volumes, applications, the trash can, and many more) are much trickier to deal with. Inside Macintosh, Volume VI, page 9-30, presents a list of 18 special-cased file types for use with Finder alias files.

To make matters worse, there’s a problem with that list - it’s incomplete. System 7.1 adds a “Fonts” folder inside the System Folder and there’s a special-cased file type for aliases to the “Fonts” folder, yet that file type doesn’t show up in the list (because “Inside Macintosh, Volume VI” was released long before System 7.1 was released). We could easily find out what file type to use for a “Fonts” folder alias by installing System 7.1, manually creating a Finder alias for our “Fonts” folder (via the Finder’s “Make Alias” menu item), peeking at the file type assigned to this new alias, and updating our code to reflect this new knowledge. But there simply must be a better way! What good is an application that doesn’t automatically keep up with changes in System Software?

Make the Finder Do It!

What we need is a way to make the Finder assign special file types for us. That way, if new special cases are added in the future, Finder aliases created via Alias Meister will automatically be given the correct file type!

Could it be that simple? Could there be a way to make the Finder do the dirty-work of sifting through all of those special cases, handling future additional special cases, and assigning the right file and creator types for us?

It appears that the answer is a resounding, “Yes!” If you examine the code in Listing 5, you’ll see that I use my own constants for the creator type and file type assigned to new Finder aliases as I create them. The creator type constant is defined to be ‘AM10’ and the file type constant is defined to be ‘TimS’ (both are registered with Apple’s Developer Technical Support group for use with this application).

When the Finder alias is first created by Alias Meister, it will appear in the destination folder using the document icon for files of type ‘TimS’ as defined within Alias Meister’s resource fork. The first time you use a Finder alias that was created with Alias Meister, the Finder will detect that the file type and creator type don’t match the target’s types and the Finder will correct these values in the alias file! Alternately, if you click once on the new alias in the Finder and pick Get Info from the File menu, then click on the Find Original button, the Finder will detect the incorrect file/creator types and corrects them.

Regardless of which way you use to have the Finder correct the file/creator types, the alias’ icon will be updated accordingly. This means that any Finder alias that has the icon assigned by Alias Meister has not yet been used (which might indicate that you didn’t really need it after all if it still has the “wrong” icon months from now).

Folder Selection Notes

“Inside Macintosh: Files”, chapter 3, presents Pascal source code for a variety of routines that together implement a CustomGetFile dialog that lets users see and pick only folder and volumes (all documents are filtered out and an extra button has been added to allow a folder to be “picked” instead of “opened”). Also, the Developer CD from Apple has ready-to-use source code for doing the same thing.

I didn’t use either of them. Instead, I chose to write my own set of routines from scratch, using a slightly different approach. If you have access to “Inside Macintosh: Files” and/or a reasonably current Developer CD, you might want to compare their approaches with mine and use the one that you’re most comfortable with.

My approach uses a different implementation for most of the important routines and I use a different user interface. The others adhere to the guidelines presented in Human Interface Note #12, “Specifying Folders with Standard File”. I chose to ignore parts of that note because I was uncomfortable with the operation of the single button it suggests adding (it’s too hard in some situations to pick the folder that you’re currently browsing) and I wanted to truncate file names on the right (like Standard File does it) instead of in the middle (like the Human Interface Note shows in its Figure 2).

Listing 6 shows the code that sets calls CustomGetFile dialog (which in turns calls various filter procs that I provide).

Listing 6: Calling CustomGetFile

// The following routine is responsible for letting the user pick 
// a new destination for aliases created by this application.  If 
// the user cancels this operation, "FALSE" is returned and 
// destFSSpecPtr is undefined.  Otherwise, "TRUE" is returned and 
// destFSSpecPtr contains the new destination's FSSpec record.
Boolean DoGetNewDestination(FSSpecPtr destFSSpecPtr)
{
 Point  centerDlog = {-1, -1};
 StandardFileReply myReply;
 myGetHookRec    myHookData;

 myHookData.replyPtr = &myReply;
 myHookData.curDestFSSpecPtr = destFSSpecPtr;
 CustomGetFile((FileFilterYDProcPtr) myFileFilter, 0, 
 nil, &myReply, kMyCustomDlogID, centerDlog, 
 (DlgHookYDProcPtr) myDlgHook, nil, nil, nil, &myHookData);
 BlockMove(&(myReply.sfFile), &(destFSSpecPtr->vRefNum), 
 sizeof(FSSpec));
 return(myHookData.theFSSpecIsValid);
}

Listing 7 shows the simple filter proc that weeds out all documents (so that only folders and volumes are shown).

Listing 7: Filtering Out Non-Folders

// The following routine is responsible for filtering out files  
// (so only folders and volumes appear in my CustomGetFile 
// dialog).

static pascal Boolean myFileFilter(CInfoPBPtr thePBPtr,
 myGetHookRecPtr myHookDataPtr)
{
 return(!((thePBPtr->dirInfo.ioFlAttrib) & 8));
}

Listing 8 shows the code to rename the “Use ” button to include the name of the folder currently being browsed.

Listing 8: Renaming A Button

// The following routine renames the 'Use " "' button to the name 
// given in btnName (where " " represents the name of the current 
// folder being browsed). theDlogPtr refers to the dialog in 
// which the button to be renamed lives.
void DoRenameButton(DialogPtr theDlogPtr, Str63 btnName)
{
 ControlHandle itemHandle;
 Rect   itemRect;
 short  itemType, howWide;
 Str63  rootName = "\pUse “ ”"; // static part of name
 
 // get a handle on the button we want to rename
 GetDItem(theDlogPtr, kUseBtnID, &itemType, 
 &((Handle)itemHandle), &itemRect);
 
 // figure out how much space their is for the folder's name
 howWide = itemRect.right - itemRect.left - 
 StringWidth(rootName);
 
 // make name fit in available space
 TruncString(howWide, btnName, smTruncEnd);

 // concat the folder's name right behind the first quote mark
 BlockMove(btnName + (char) 1, 
 rootName + rootName[0] - 1, btnName[0]);
 
 // adjust the length byte to include the concat'd characters
 // "-1" takes out the white space which helped leave a little 
 // room around the ends
 rootName[0] = rootName[0] + btnName[0] - 1;
 
 // stuff a quote mark on the end of the new name
 rootName[rootName[0]] = '”';
 
 // rename button and force it to be redrawn
 SetCTitle(itemHandle, rootName);
 ValidRect(&itemRect);
}

Last, but not least, Listing 9 shows the source code for the dialog hook function that really ties it all together.

Listing 9: CustomGetFile Dialog Hook Function

// The following routine is the hook function for our 
// CustomGetFile dialog. It conveys the FSSpec for the current 
// destination into the dialog and the new selection's FSSpec out 
// of the dialog.
static pascal short myDlgHook(short theItem, DialogPtr 
 theDlogPtr, myGetHookRecPtr myHookDataPtr)
{
 short  myVRefNum;
 short  returnCode = theItem;
 short  tempBtnHilite1, tempBtnHilite2, itemType;
 OSErr  myErr;
 long   myDirID;
 ControlHandle   itemHandle;
 Rect   itemRect;
 Str63  desktopName = "\pDesktop Folder";

 // avoid nested dialogs
 if (GetWRefCon((WindowPtr)theDlogPtr) == sfMainDialogRefCon) { 
 switch(theItem) {
 case sfHookFirstCall: // initialize stuff here
 // btn name initially correct 
 myHookDataPtr->fixBtnName = true; 

 // in case user clicks Cancel button
 myHookDataPtr->theFSSpecIsValid = false;
 
 // To force StandardFile to use our default 
 // directory initially, move the default 
 // directory's FSSpec from the custom data 
 // block pointed to by myHookDataPtr into 
 // the reply record used by StandardFile (a 
 // pointer to that record is also part of 
 // the custom data block).  To complete the 
 // forced change to the default directory, 
 // return 'sfHookChangeSelection' as the result.
 BlockMove(&(myHookDataPtr->curDestFSSpecPtr->vRefNum), 
 &(myHookDataPtr->replyPtr->sfFile.vRefNum), 
 sizeof(FSSpec));
 DoRenameButton(theDlogPtr, 
 myHookDataPtr->replyPtr->sfFile.name);
 // name is invalid right now,so zap it
 myHookDataPtr->replyPtr->sfFile.name[0] = ''; 
 returnCode = sfHookChangeSelection;
 break;
 
 case sfHookOpenFolder: 
 // rename bottom button as folder is opened
 DoRenameButton(theDlogPtr, 
 myHookDataPtr->replyPtr->sfFile.name);
 break;
 
 case sfHookGoToDesktop: // rename bottom button
 // the reply record does NOT indicate "Desktop Folder", so 
 // force that name into the bottom button
 DoRenameButton(theDlogPtr, desktopName);
 break;
 
 case kSelectBtnID:
 // Validate FSSpec for hilited folder & fake a hit on the 
 // Cancel button to dismiss dialog
 myHookDataPtr->theFSSpecIsValid = true;
 returnCode = sfItemCancelButton;
 break;
 
 case kUseBtnID:
 // Get FSSpec for current directory - reply record's 
 // FSSpec includes the ID for the parent folder (the one 
 // we really want), so convert that parID into complete 
 // FSSpec.  This doesn't work for the case where the 
 // desktop is selected as the "current" directory.  So, we 
 // have to special case this based on whether or not the 
 // Desktop button is dimmed.  If that button is dimmed, 
 // user wants the desktop.
 GetDItem(theDlogPtr, sfItemDesktopButton, &itemType, 
 &((Handle)itemHandle), &itemRect);
 // if current dir IS desktop
 if ((**itemHandle).contrlHilite) {  
 myErr = FindFolder(kOnSystemDisk, kDesktopFolderType, 
 kDontCreateFolder, &myVRefNum, &myDirID);
 // if desired folder exists, create FSSpec for it
 if (!myErr) { 
 myErr = FSMakeFSSpec(myVRefNum, myDirID, nil, 
 &(myHookDataPtr->replyPtr->sfFile));
 if (!myErr) { 
 // let caller know we succeeded
 myHookDataPtr->theFSSpecIsValid = true; 
 }
 }
 } else { // current directory is NOT the desktop
 myHookDataPtr->theFSSpecIsValid = 
 DoFindParentFSSpec(&(myHookDataPtr->replyPtr->sfFile));
 }
 // fake a hit on Cancel button to dismiss dialog
 returnCode = sfItemCancelButton;  
 break;
 case sfHookGoToNextDrive:
 case sfHookGoToPrevDrive:
 // For these two cases, we need to rename the "Use  " 
 // button, but at this exact moment in time, the 
 // incoming reply record still holds info for the folder 
 // we WERE viewing (i.e.: we can't use the information in 
 // that record). We could fumble around here and find out 
 // where we're supposed to be, but
 // if we just make a note of the fact that the reply 
 // record is junk right now, we can wait for the next 
 // available null event, by which time the reply record 
 // will be up to date and we can easily rename the button.
 myHookDataPtr->fixBtnName = false;
 break;
 
 case sfItemVolumeUser:
 case sfHookGoToParent:
 // For these two cases, we need to rename the "Use  " 
 // button. Unlike the prior two cases, we can build off of 
 // the current reply record. We have to special case the 
 // desktop folder
 if (myHookDataPtr->replyPtr->sfFile.parID == fsRtDirID) {  
 // Current directory IS the desktop, so set button's 
 // name to "Desktop Folder"
 DoRenameButton(theDlogPtr, desktopName);
 myHookDataPtr->fixBtnName = true; // btn name is OK now
 } else { // current directory is NOT the desktop
 DoFindParentFSSpec(&(myHookDataPtr->replyPtr->sfFile));
 myHookDataPtr->fixBtnName = 
 DoFindParentFSSpec(&(myHookDataPtr->replyPtr->sfFile));
 
 // rename the button
 DoRenameButton(theDlogPtr, 
 myHookDataPtr->replyPtr->sfFile.name);
 }
 break;

 case sfHookNullEvent: 
 // use spare time to dim/undim button as needed
 // if 'theFSSpecIsValid' is false, "Use  " button has 
 // wrong name
 if (!(myHookDataPtr->fixBtnName)) {
 // we have to special case the desktop folder
 GetDItem(theDlogPtr, sfItemDesktopButton, &itemType, 
 &((Handle)itemHandle), &itemRect);
 // if current directory IS the desktop 
 if ((**itemHandle).contrlHilite) {  
 // set button's name to "Desktop Folder"
 DoRenameButton(theDlogPtr, desktopName);
 } else { // current directory is NOT the desktop
 DoFindParentFSSpec(
 &(myHookDataPtr->replyPtr->sfFile));
 myHookDataPtr->fixBtnName = 
 DoFindParentFSSpec(
 &(myHookDataPtr->replyPtr->sfFile));
 }

 // rename the button
 DoRenameButton(theDlogPtr, 
 myHookDataPtr->replyPtr->sfFile.name);
 myHookDataPtr->fixBtnName = true; // btn name is OK now
 }
 
 // make Select button's hilite match Open button
 GetDItem(theDlogPtr, sfItemOpenButton, &itemType, 
 &((Handle)itemHandle), &itemRect);
 tempBtnHilite1 = (**itemHandle).contrlHilite;
 GetDItem(theDlogPtr, kSelectBtnID, &itemType, 
 &((Handle)itemHandle), &itemRect);
 tempBtnHilite2 =  (**itemHandle).contrlHilite;
 if (tempBtnHilite1 != tempBtnHilite2) {
 HiliteControl(itemHandle, tempBtnHilite1);
 }
 break;
 } // end of: switch(theItem)
 }
 return (returnCode);
}

The addition of a separate button to choose the currently hilited folder required one extra bit of caution. If nothing was selected, that button should be disabled. Rather than monitor every click to see if it left nothing selected, I simply look at the “Open” button. That buttons dims any time nothing is selected, so the next null event that comes along after the “Open” button dims or undims, my code changes the “Select” button to match it. The code to do this is shown near the end of Listing 9 (it’s the block of code that starts with “case sfHookNullEvent:”).

Exercises For The Reader

As it exists today, Alias Meister provides basic functionality. It’s more than enough to get the job done, but there are a few bells and whistles that could be added:

• Checking to see if the alias should be given a custom icon (if the item the alias points to has one, the alias should have the same one). The complete icon family should be brought over (ICN#, ics#, icl4, ics4, icl8, ics8).

• Add a menu item to the Files menu that brings up the CustomGetFile (where user selects the destination folder) and don’t quit after that dialog is dismissed. This would allow users to keep Alias Meister always open and would slightly speed up its operation (launch time would be eliminated when files are dropped onto Alias Meister’s icon). Bear in mind though that Alias Meister launches pretty quickly now (which is why I didn’t bother to implement this feature).

• Add error reporting, currently, Alias Meister just quietly quits if it hits a major problem. Don’t just handle the obvious errors, be prepared for stuff like the disk being full (preventing an alias from being saved), the selected destination being on a server and the user’s access privileges getting reduced, etc.

• Find a way to squish more of the name for a long folder into the “Use ” button. Passing -1 to SpaceExtra() while renaming that button should do what you want, but be careful not to mess up the spacing within any of the other buttons.

Listing: Alias Meister.h
/*----------------------------------------------------------
#
#Alias Meister.h - used with Rez and C source files
#Version 1.0
#
#Copyright © 1992 Tim Swihart
#All rights reserved.
#
#October 10, 1992 (v.1.0)
#
---------------------------------------------------------*/

#define AppName  "Alias Meister"
#define AppVers  "v.1.0"
#define CopyrightNotice \
 "© October 10, 1992 Tim Swihart\nAll Rights Reserved"
#define AuthorCredit "by Tim Swihart"

// Application signature
// file type for documents created by this app
#define ApplCreator'AM10'
#define docsFileType 'TimS'

// Resource IDs:

#define StopIconID 0 // Stop sign icon
#define rBundle  128 // Application bundle
#define rSignature 0 // Signature resource
#define rRefAPPL 128 // APPL file reference
#define rRefDoc  129 // document file reference
#define rRefStar 130 // lets all files be
 // dropped on our icon
#define rRefDisk 131 // lets disks be dropped
 // on our icon
#define rRefFold 132 // lets folders be
 // dropped on our icon
#define rIconAPPL128 // Application ICN#
#define rIconDoc 129 // document ICN#

#define rAboutBox128 // About box
#define rNotSystem7129    // Alert box

#define rHelpString128    // Finder help string

// ID for CustomGetFile dialog
#define kMyCustomDlogID 2362
// ID for our 'alis' resource
#define kMyAlisID128
// ID for 'alis' resource in Finder alias file
#define kFndrAlisID0
// item number for "Use Current Directory" button
#define kUseBtnID10
// item number for "Select" button
#define kSelectBtnID 11

// Menu and Menu Item IDs:
// (Note: I'm using Menu resource IDs that
// are the same as Menu IDs)

#define rMenuBar 128 // application's menu bar

#define mApple   128 // Apple menu
#define iAbout   1

#define mFile    129 // File menu
#define iQuit    1

#define mEdit    130 // Edit menu
#define iUndo    1
#define iCut3
#define iCopy    4
#define iPaste   5
#define iClear   6
#define iSelectAll 7

// Miscellaneous:

#define kMinSize 35// minimum partition size (in K)
#define kPrefSize40// preferred partition size (in K)

// Application constants
// top coord for disk init dialog
#define kDITop   0x0050
// left coord for disk init dialog
#define kDILeft  0x0070

/* Use these to set the enable/disable flags of a menu: */
/* 31 flags */
#define AllItems 0b1111111111111111111111111111111
#define NoItems  0b0000000000000000000000000000000
#define MenuItem10b0000000000000000000000000000001
#define MenuItem20b0000000000000000000000000000010
#define MenuItem30b0000000000000000000000000000100
#define MenuItem40b0000000000000000000000000001000
#define MenuItem50b0000000000000000000000000010000
#define MenuItem60b0000000000000000000000000100000
#define MenuItem70b0000000000000000000000001000000
#define MenuItem80b0000000000000000000000010000000
#define MenuItem90b0000000000000000000000100000000
#define MenuItem10 0b0000000000000000000001000000000
#define MenuItem11 0b0000000000000000000010000000000
#define MenuItem12 0b0000000000000000000100000000000
#define MenuItem13 0b0000000000000000001000000000000
#define MenuItem14 0b0000000000000000010000000000000
Listing: Alias Meister Structs.h
/*--------------------------------
#
#Alias Meister Structs.h
#Version 1.0
#
#Copyright © 1992 Tim Swihart
#All rights reserved.
#
#October 10, 1992 (v.1.0)
#
#This file contains the declarations for all custom
# data structures needed within Alias Meister.  They're
# in this file instead of in "Alias Meister.h" because 
# they don't compile under Rez and the "Alias Meister.h" 
# file is used by both the C and Rez compilers.
#
----------------------------------*/

// Used by CustomGetFile */
typedef struct { 
 FSSpecPtrcurDestFSSpecPtr;
 StandardFileReply *replyPtr; 
 BooleantheFSSpecIsValid;
 BooleanfixBtnName;
} myGetHookRec;

typedef myGetHookRec *myGetHookRecPtr, **myGetHookRecHndl;
Listing: Mac includes.c
#include <Controls.h>
#include <Desk.h>
#include <Devices.h>
#include <Dialogs.h>
#include <DiskInit.h>
#include <Errors.h>
#include <Events.h>
#include <Files.h>
#include <Fonts.h>
#include <Lists.h>
#include <Memory.h>
#include <Menus.h>
#include <Notification.h>
#include <OSEvents.h>
#include <OSUtils.h>
#include <Quickdraw.h>
#include <Resources.h>
#include <Scrap.h>
#include <SegLoad.h>
#include <StandardFile.h>
#include <TextEdit.h>
#include <Timer.h>
#include <ToolUtils.h>
#include <Types.h>
#include <Windows.h>
Listing: Alias Meister Headers.c
/*--------------------------------
#
#Alias Meister Headers.c
#Version 1.0
#
#Copyright © 1992 Tim Swihart
#All rights reserved.
#
#October 10, 1992 (v.1.0)
#
----------------------------------*/

// This file simplifies the task of moving this
// application's C source back and forth between MPW
// and Think.  It doesn't solved all of the conflicts,
// but it cuts out a lot of them.

// make sure we get the std stuff as well
#include "Mac includes.c"
#include <AppleEvents.h>
#include <Script.h>
#include <Traps.h>
#include <GestaltEqu.h>
#include <Balloons.h>
#include <Folders.h>
#include <Aliases.h>
#include <Finder.h>
#include "Alias Meister.h"
#include "Alias Meister Structs.h"

#pragma dump "AliasMeisterDump"
Listing: Initialize.c
/*--------------------------------
#
#Initialize.c
#Version 1.0
#
#Copyright © 1992 Tim Swihart
#All rights reserved.
#
#October 10, 1992 (v.1.0)
#
#The code in this file initializes the various
#toolbox managers we'll need & installs our
#Apple event handlers.  All of the routines in
#this file are called only once & that's 
#while the application is starting up.  So, 
#I put it in its own segment so it can be 
#unloaded after executing, freeing up its memory
#& reducing the amount of memory needed for this app.
#
----------------------------------*/

#pragma load "AliasMeisterDump"

#pragma segment InitializationStuff

// External gloabls:
extern  Boolean  gQuitting;
extern  Boolean  gInBackground;
extern  Boolean  gLetUserConfigureMe;
extern  RgnHandlegCursorRgn;

// External references:
extern pascal OSErr HandleOAPP(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon);
extern pascal OSErr HandleODOC(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon);
extern pascal OSErr HandlePDOC(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon);
extern pascal OSErr HandleQUIT(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon);

// Function prototypes:
void  DoAEInstallation(void);
short   Initialize(void);
Boolean System7Available(void);

// The following routine initializes the toolbox
// managers, sets up our menu bar, and does a few 
// other housekeeping things.
short Initialize(void)
{
 Handle menuBar;

 FlushEvents (everyEvent, 0);

 InitGraf(&(qd.thePort));
 InitFonts();
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs(0L);
 InitCursor();

 gInBackground = false;
 gQuitting = false;
 // config dialog suppressed by default
 gLetUserConfigureMe = false;
 // force cursor-moved event immediately
 gCursorRgn = NewRgn();

 if (!System7Available()) {
 // Not running System 7?  PUNT!
 Alert(rNotSystem7, 0L);
 gQuitting = true;
 return(CurResFile());
 }

 TEFromScrap();
 ZeroScrap();

 // Create the menu bar
 menuBar = GetNewMBar(rMenuBar);
 if (menuBar) {
 SetMenuBar(menuBar);
 DisposHandle(menuBar);
 // add Apple Menu items
 AddResMenu(GetMHandle(mApple), 'DRVR');
 DrawMenuBar();
 } else { // no menu bar, so punt
 gQuitting = true;
 return(CurResFile());
 }
 DoAEInstallation(); // install AppleEvent handlers
 return(CurResFile());
}

// The following routine installs our AppleEvent handlers.
void DoAEInstallation(void)
{
 AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
 (EventHandlerProcPtr)HandleODOC, 0, false);
 AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
 (EventHandlerProcPtr)HandleQUIT, 0, false);
 AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments,
 (EventHandlerProcPtr)HandlePDOC, 0, false);
 AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
 (EventHandlerProcPtr)HandleOAPP, 0, false);    
}

// The following routine tells us whether or not we're
// running under at least System 7.0.  I'm skipping the
// check for whether or not Gestalt is present because
// MPW provides glue that handles the case where Gestalt
// wasn't present.
Boolean System7Available(void)
{
 long whichVers;

 // ex: 7.0.1 comes back as $0701
 Gestalt(gestaltSystemVersion, &whichVers);
 // mask off minor version number
 whichVers = whichVers & 0xF00;
 if (whichVers < 0x700) {
 return(false);  // didn't get what we wanted
 } else {
 return(true); // we're running under at least 7.0.0
 }
}

Listing: DoIdle.c
/*--------------------------------
#
#DoIdle.c
#Version 1.0
#
#Copyright © 1992 Tim Swihart
#All rights reserved.
#
#October 10, 1992 (v.1.0)
#
----------------------------------*/

#pragma load "AliasMeisterDump"
#pragma segment IdleStuff

// Function prototypes:
Boolean DoFindParentFSSpec(FSSpecPtr theFSSpecPtr);
void DoRenameMe(FSSpecPtr destFSSpecPtr);
void DoRenameButton(DialogPtr theDlogPtr, Str63 btnName);
void DoIdle(void);
Boolean DoGetNewDestination(FSSpecPtr destFSSpecPtr);
Boolean CustomFileOK(void);
static pascal short myDlgHook(short theItem, DialogPtr
 theDlogPtr, myGetHookRecPtr myHookDataPtr);
static pascal Boolean myFileFilter(CInfoPBPtr thePBPtr,
 myGetHookRecPtr myHookDataPtr);

// External function prototypes:
extern Boolean DoGetCurDestination(FSSpecPtr
 curDestFSSPecPtr);
extern void FlushAVolume(FSSpecPtr theFSSpecPtr);
extern OSErr TouchDirectory(short vRefNum, long dirID);

// External globals:
extern shortgMyResFileRefNum;
extern Boolean gQuitting;
extern Boolean gLetUserConfigureMe;

// The following routine is responsible for converting an
// incoming FSSpec into one the describes the parent folder
// holding the file described by the incoming FSSpec
Boolean DoFindParentFSSpec(FSSpecPtr theFSSpecPtr)
{
 
 CInfoPBRec paramBlock;
 BooleandidItWork = false;
 
 paramBlock.dirInfo.ioCompletion = nil;
 paramBlock.dirInfo.ioNamePtr = theFSSpecPtr->name;
 paramBlock.dirInfo.ioVRefNum = theFSSpecPtr->vRefNum;
 paramBlock.dirInfo.ioFDirIndex = -1; // find by ID
 paramBlock.dirInfo.ioDrDirID = theFSSpecPtr->parID;
 PBGetCatInfoSync(&paramBlock);
 if (paramBlock.dirInfo.ioResult == 0) {
 // grab real parent ID
 theFSSpecPtr->parID = paramBlock.dirInfo.ioDrParID;
 didItWork = true;
 }
 return(didItWork);
}

// The following routine is responsible for renaming the
// application to reflect its new destination after it's
// been configured.  The name should be "»" plus the
// destination name (truncate new name so it stays under
// 32 characters).  
void DoRenameMe(FSSpecPtr destFSSpecPtr)
{
 Str63  newName;
 ProcessInfoRec  myProcessInfo;
 ProcessSerialNumber myPSNum;
 FSSpec myFSSpec;
 
 myPSNum.highLongOfPSN = 0L;
 myPSNum.lowLongOfPSN = kCurrentProcess;
 
 // In order for an app to change it's name on the fly,
 // it has to know who it is, where it lives, and which
 // volume it lives on.  We can get all of this information
 // from the Process Manger by asking it who the current
 // process is (we're the current process at the time
 // we're allowed to ask ).  But first, we have to set up
 // some fields 
 myProcessInfo.processInfoLength = sizeof(ProcessInfoRec);
 myProcessInfo.processName = nil; // we get name from FSSpec
 myProcessInfo.processAppSpec = &myFSSpec;
 if (!GetProcessInformation(&myPSNum, &myProcessInfo)) {
 // We also need to munge the incoming name to tack
 // the "»" at the front of the name.  Currently,
 // destFSSpecPtr contains destination's file name,
 // so move it into newName, but move it down one char
 //to make room for "»"
 BlockMove(destFSSpecPtr->name, newName + (char) 1,
 destFSSpecPtr->name[0] + 1);
 newName[1] = '»'; // stuff in the "arrow" character
 // update length byte
 newName[0] = destFSSpecPtr->name[0] + 1;
 
 // make sure new file name isn't more than 31
 // characters long
 if (newName[0] > 31) { newName[0] = 31; }
 
 // rename the file
 FSpRename(myProcessInfo.processAppSpec, newName);
 
 // hide Finder glitch - renamed files on desktop
 // don't show up with new name until you've rebooted.
 // Old name is cached and you can't open the app
 // until you've rebooted.
 TouchDirectory(myFSSpec.vRefNum, myFSSpec.parID);
 
 // flush changes to disk
 FlushAVolume(&myFSSpec);
 }
}

/* INSERT LISTING 8 HERE */

/* INSERT LISTING 7 HERE */

/* INSERT LISTING 9 HERE */

/* INSERT LISTING 6 HERE */

// The following routine handles idle-time activities.  For
// this app, we'll use this "spare time" as a place to put
// the configuration code.
void DoIdle(void)
{
 FSSpec destFSSpec;
 AliasHandlemyAliasHndl, curDestAliasHndl;

 if ((gLetUserConfigureMe) && (CustomFileOK())) {
 // get current destination to use as default
 if (DoGetCurDestination(&destFSSpec)) {
 // if user picks new destination, record it
 if (DoGetNewDestination(&destFSSpec)) {
 // convert the destFSSpec to an alias
 // store it as an 'alis' resource in this app

 /* INSERT LISTING 3 HERE */

 }
 }
 gQuitting = true; // set quit flag
 }
}

// The following routine verifies whether or not we can
// call CustomGetFile().  If we can, "TRUE" is returned.
// Otherwise, "FALSE" is returned.
Boolean CustomFileOK(void)
{
 OSErr  myErr;
 long   gestaltResponse;
 
 myErr = Gestalt(gestaltStandardFileAttr, &gestaltResponse);
 return ((myErr == noErr) && ((gestaltResponse) &
 (1<<gestaltStandardFile58)));
}
Listing: EventHandlers.c
/*--------------------------------
#
#EventHandlers.c
#Version 1.0
#
#Copyright © 1992 Tim Swihart
#All rights reserved.
#
#October 10, 1992 (v.1.0)
#
----------------------------------*/

#pragma load "AliasMeisterDump"

// Function prototypes:
pascal OSErr HandleODOC(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon);
pascal OSErr HandleQUIT(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon);
pascal OSErr HandleOAPP(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon);
pascal OSErr HandlePDOC(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon);
static OSErr RequiredCheck(AppleEvent *theAppleEvent);
void FlushAVolume(FSSpecPtr theFSSpecPtr);
Boolean DoGetCurDestination(FSSpecPtr curDestFSSPecPtr);
Boolean AliasMgrCallsOK(void);
Boolean FindFolderOK(void);

// External globals:
extern Boolean gQuitting;
extern Boolean gLetUserConfigureMe;
 
// ------------ Apple  Event  Handlers ------------------

// The following routine is the "required" Apple event
// handler for "odoc" events.  This is the routine that
// creates the aliases for us, then forces the app to quit.
pascal OSErr HandleODOC(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon)
{
 short  markChar, fileRef, howManyChars;
 OSErr  myErr;
 long   itemsInList, i, tempCount;
 Size   actualSize;
 MenuHandle myMenuHndl;
 AliasHandlenewAliasHndl;
 AEKeywordtheKeyword;
 AEDescList docList;
 FSSpec myFSS, destFSSpec;
 FInfo  theFInfo;
 DescType typeCode;
 
 // After processing the incoming files, auto-quit this
 // app (makes it behave like a "grinder")
 gQuitting = true;
 // supresses configuration dialog
 gLetUserConfigureMe = false;

 myErr = AEGetParamDesc(theAppleEvent, keyDirectObject,
 typeAEList, &docList);
 if (myErr) {
 // release memory used by our copy of the
 // event descriptor
 AEDisposeDesc(&docList);
 return(myErr);
 }
 
 myErr = RequiredCheck(theAppleEvent);
 if (myErr) {
 // release memory used by our copy of the
 // event descriptor
 AEDisposeDesc(&docList);
 return(myErr);
 }
 
 myErr = AECountItems(&docList, &itemsInList);
 if (myErr) {
 // release memory used by our copy of the
 // event descriptor
 AEDisposeDesc(&docList);
 return(myErr);
 }
 
 for (i = 1; i <= itemsInList; i++) {
 myErr = AEGetNthPtr(&docList, i, typeFSS,
 &theKeyword, &typeCode, (Ptr)&myFSS,
 sizeof(FSSpec), &actualSize);
 if (myErr) {
 // release memory used by our copy of the
 // event descriptor
 AEDisposeDesc(&docList); 
 return(myErr);
 }
 // get destFSSpec, convert incoming FSSpec to alias,
 // create new file at dest, attach 'alis' to new file,
 // close file, attach custom icon if needed
 if (DoGetCurDestination(&destFSSpec)) {
 // name is invalid, but parID is correct.
 // Use name from incoming file
 tempCount = sizeof(Str63);
 BlockMove(myFSS.name, destFSSpec.name, sizeof(Str63));
 howManyChars = destFSSpec.name[0];
 if (howManyChars < 31) { // there's room in the buffer
 // pad blank space
 destFSSpec.name[++howManyChars] = ' ';
 // adjust length byte for new character
 destFSSpec.name[0] = howManyChars;
 } else {
 // no room in the buffer, overwrite last character
 // pad blank space
 destFSSpec.name[howManyChars] = ' ';
 }

 /* INSERT LISTING 5 HERE */

 }
 }
 // Finder puts up watch when document is dropped
 // on an application, so set cursor back to an arrow
 InitCursor();
 // release memory used by our copy of the
 // event descriptor
 AEDisposeDesc(&docList);
 return(noErr);
}

// The following routine is the "required" Apple event
// handler for "quit" events.  Don't ExitToShell from here,
// just set a global flag so the main event loop knows we
// need to quit.  Bad things will happen to you if you
// ExitToShell here!!!
pascal OSErr HandleQUIT(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon)
{
 OSErr  myErr;
 
 myErr = RequiredCheck(theAppleEvent);
 if (myErr) return(myErr);
 gQuitting = true;
 return(noErr);
}

// The following routine is the "required" Apple event
// handler for "oapp" events.  This routine just sets a
// flag for the main event loop so it knows where we were
// launched by the user double-clicking this app or by
// something being dropped onto this app.  For "normal"
// launces, allow the user to configure the app.  For
// launching with incoming documents, we'll need to
// auto-quit (which is set by the ODOC handler).
pascal OSErr HandleOAPP(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon)
{
 gLetUserConfigureMe = true;
 return(RequiredCheck(theAppleEvent));
}

// The following routine is the "required" Apple event
// handler for "pdoc" events.  This app doesn't support
// printing, so reject this event unconditionally.
pascal OSErr HandlePDOC(AppleEvent *theAppleEvent,
 AppleEvent *reply, long myRefCon)
{
 return(errAEEventNotHandled); 
}

// The following routine verifies that all required
// parameters have been retrieved properly.
static OSErr RequiredCheck(AppleEvent *theAppleEvent)
{
 OSErr  myErr;
 DescType typeCode;
 Size   actualSize;
 
 myErr = AEGetAttributePtr(theAppleEvent,
 keyMissedKeywordAttr, typeWildCard, &typeCode,
 0L, 0, &actualSize);

 // no parms skipped, this is good.
 if (myErr == errAEDescNotFound) return(noErr);
 // we missed something, this is bad.
 if (myErr == noErr) return(errAEEventNotHandled);
 return(myErr);  // something else went wrong
}

// The following routine is responsible for flushing the
// volume that the incoming file is on.  We can find the
// right volume's ref number from the FSSpecPtr bring
// passed in  
void FlushAVolume(FSSpecPtr theFSSpecPtr)
{
 ParamBlockRec   myPBlockRec;

 // flush boot volume to make sure change is written to disk
 myPBlockRec.ioParam.ioCompletion = 0L;
 myPBlockRec.ioParam.ioNamePtr = 0L;
 myPBlockRec.ioParam.ioVRefNum = theFSSpecPtr->vRefNum;
 PBFlushVolSync(&myPBlockRec);
}

// The following routine is responsible for finding the
// current destination that aliases created by this app will
// be sent to.  The current destination is stored as an
// 'alis' resource in the app's resource fork.  If no 'alis'
// exists (this happens when the app is new), then the user's
// "Apple Menu Items" folder inside their boot volume's
// System folder will be used.  If the destination was found,
// "TRUE" is returned and the FSSpecPtr points to the current
// destination folder.  If no destination can be found,
// 'FALSE' is returned and the FSSpecPtr is undefined.
Boolean DoGetCurDestination(FSSpecPtr curDestFSSpecPtr)
{
 short  myVRefNum;
 OSErr  myErr;
 Boolean  wasChanged;
 BooleandidItWork = false;// assume the worst
 BooleanfoundIt = true;   // assume the best
 long   myDirID;
 AliasHandlecurDestAliasHndl=nil;
 CInfoPBRec paramBlock;
 
 if ((FindFolderOK()) && (AliasMgrCallsOK())) {
 // try to load 'alis' resource from app's rFork
 curDestAliasHndl = (AliasHandle)
 Get1Resource(rAliasType, kMyAlisID);
 if (curDestAliasHndl) { // convert alias into FSSpec
 myErr = ResolveAlias(nil, curDestAliasHndl,
 curDestFSSpecPtr, &wasChanged);
 if (!myErr) { didItWork = true; }
 } 
 if (!didItWork) {
 // if no 'alis' resource, or alias didn't resolve,
 //use Apple Menu Items
 myErr = FindFolder(kOnSystemDisk,
 kAppleMenuFolderType, kDontCreateFolder,
 &myVRefNum,&myDirID);
 if (!myErr) {
 // if desired folder exists, create FSSpec for it
 myErr = FSMakeFSSpec(myVRefNum, myDirID,
 nil, curDestFSSpecPtr);
 // let caller know we succeeded
 if (!myErr) { didItWork = true; }
 }
 }
 if (didItWork) {
 // munge FSSpec a tad.  Move the target's dirID into
 // the "parID" field. That way, when CustomGetFile
 // jumps to the "current destination", it lands 
 // correctly and not up one level.
 paramBlock.dirInfo.ioCompletion = nil;
 paramBlock.dirInfo.ioNamePtr = curDestFSSpecPtr->name;
 paramBlock.dirInfo.ioVRefNum =
 curDestFSSpecPtr->vRefNum;
 paramBlock.dirInfo.ioFDirIndex = 0; // find by name
 paramBlock.dirInfo.ioDrDirID =
 curDestFSSpecPtr->parID;
 PBGetCatInfoSync(&paramBlock);
 if (paramBlock.dirInfo.ioResult == 0) {
 curDestFSSpecPtr->parID =
 paramBlock.dirInfo.ioDrDirID;
 }
 }
 }
 return(didItWork);
}

// The following routine verifies whether or not the Alias
// Manager is present so we can make calls to it.
Boolean AliasMgrCallsOK(void)
{
 OSErr  myErr;
 long   gestaltResponse;
 
 myErr = Gestalt(gestaltAliasMgrAttr, &gestaltResponse);
 return ((myErr == noErr) && ((gestaltResponse) &
 (1<<gestaltAliasMgrPresent)));
}

// The following routine verifies whether or not we can
// call Findfolder().  If we can, "TRUE" is returned.
// Otherwise, "FALSE" us returned.
Boolean FindFolderOK(void)
{
 OSErr  myErr;
 long   gestaltResponse;
 
 myErr = Gestalt(gestaltFindFolderAttr, &gestaltResponse);
 return ((myErr == noErr) && ((gestaltResponse) &
 (1<<gestaltFindFolderPresent)));
}
Listing: MenuStuff.c
/*
 MenuStuff.c
 Version 1.0
  
 Copyright © 1992 Tim Swihart
 All Rights Reserved

 October 17, 1992 (v.1.0)
*/

#pragma load "AliasMeisterDump"

#pragma segment MenuStuff

// External globals:
extern Boolean gQuitting;

// Function prototypes:
void AdjustMenus(void);
void DoMenuCommand(long menuResult);
static pascal Boolean doKeepCircleDrawn(DialogPtr theDialog,
 EventRecord *er, short *itemHit);

// The following routines enables & disables our menu
// items as needed.
void AdjustMenus(void)
{
 MenuHandle fileMenu, editMenu;

 fileMenu = GetMHandle(mFile);
 editMenu = GetMHandle(mEdit);

 EnableItem(fileMenu, iQuit);
 DisableItem(editMenu, iCut);
 DisableItem(editMenu, iCopy);
 DisableItem(editMenu, iPaste);
 DisableItem(editMenu, iClear);
 DisableItem(editMenu, iUndo);
 DisableItem(editMenu, iSelectAll);
}

// The following routine is responsible for determining
// which menu item the user selected and what should be
// done about it.  I moved it to its own segment so it
// wouldn't have to be loaded if we are just creating
// aliases (i.e.: files were dropped on us to launch us,
// so this code doesn't get called at all).
void DoMenuCommand(long menuResult)
{
 short  menuID;
 short  menuItem;
 Str255 daName;

 menuID = menuResult >> 16;
 menuItem = menuResult & 0x0000ffff;
 switch (menuID) {
 case mApple:
 switch (menuItem) {
 case iAbout:    // display the About box
 //Alert(rAboutBox, 0L);
 Alert(rAboutBox, (ModalFilterProcPtr)
 doKeepCircleDrawn);
 break;
 default: // handle DA selection
 GetItem(GetMHandle(mApple), menuItem, daName);
 OpenDeskAcc(daName);
 break;
 }
 break;
 case mFile:
 switch (menuItem) {
 case iQuit:
 gQuitting = true;
 break;
 }
 break;
 case mEdit:
 break;
 }
 HiliteMenu(0);
}

// The following routine is a filter proc for our alerts.
// Its sole purpose is to redraw the bold circle around
// the default button during update events.
pascal Boolean doKeepCircleDrawn(DialogPtr theDialog,
 EventRecord *theEvent, short *itemHit)
{
 short  itemType;
 BooleanhandledEvt = false;
 char   key;
 Handle itemHndl;
 Rect   itemRect;
 long   curTickCount;
 
 switch (theEvent->what) {
 case updateEvt:
 GetDItem(theDialog, ok, &itemType, &itemHndl,
 &itemRect);// get 'OK' btn's rect
 PenSize(3,3); // use a fat pen tip
 InsetRect(&itemRect, -4, -4 );  // widen rect a little
 // draw the stupid outline
 FrameRoundRect(&itemRect,16,16);
 break;
 case keyDown:
 key = theEvent->message & charCodeMask;
 if ((key == 13) || (key ==3)) {
 // map RETURN and ENTER into hits on default btn
 // hilite the default btn so the user knows why the
 // alert suddenly goes away
 GetDItem(theDialog, ok, &itemType, &itemHndl,
 &itemRect);// get 'OK' btn's hndl
 // hilite the OK btn
 HiliteControl((ControlHandle)itemHndl,inButton);
 // make sure user sees the hilited btn
 Delay(5L, &curTickCount);
 // unhilite the OK btn
 HiliteControl((ControlHandle)itemHndl,0);
 handledEvt = true;// makes Alert go away
 }
 break;
 } // end of: switch (theEvent->what)
 return(handledEvt);
}
Listing: TouchDirectory.c
/*--------------------------------
#
#TouchDirectory.c
#Version 1.0
#
----------------------------------*/

#pragma load "AliasMeisterDump"

#pragma segment IdleStuff

// Function prototypes:
OSErr TouchDirectory(short vRefNum, long dirID);

// The following routine sets the date/time stamp of the
// selected folder to the current date/time.  This was
// originally done as part of an attempt to force the Finder
// into re-reading the BNDL information for the banged
// application, but it didn't work (the Finder ignores the
// fact that the folder's date/time stamp changed and
// continues to use stale data 

OSErr TouchDirectory(short vRefNum, long dirID)
{
    CInfoPBRec  info;
    Str63       name;
    OSErr       err;
   
    info.dirInfo.ioDrDirID = dirID;
    info.dirInfo.ioVRefNum = vRefNum;
    info.dirInfo.ioNamePtr = name;
    info.dirInfo.ioFDirIndex = -1;
     
    err = PBGetCatInfoSync(&info);
    if (err == noErr) {
        info.dirInfo.ioCompletion = 0;
        info.dirInfo.ioDrDirID = info.dirInfo.ioDrParID;
        info.dirInfo.ioFDirIndex = 0;
        GetDateTime(&info.dirInfo.ioDrMdDat);
        err = PBSetCatInfoSync(&info);
    }
     return (err);
}
Listing: Alias Meister.c
/*
 Alias Meister.c
 Version 1.0
  
 Copyright © 1992 Tim Swihart
 All Rights Reserved

 October 8, 1992 (v.1.0)
*/

#pragma load "AliasMeisterDump"

// Global variables:
short   gMyResFileRefNum;
Boolean gQuitting;
Boolean gInBackground;
Boolean gLetUserConfigureMe;
RgnHandle gCursorRgn;

// Function prototypes:
void main(void);
void MainEventLoop(void);
void DoEvent(EventRecord *event);

// External references:
extern void _DataInit(void);
extern void DoIdle(void);
extern short Initialize(void);
extern void AdjustMenus(void);
extern void DoMenuCommand(long menuResult);

// The following routine is the top-level view of our app.
// It initializes the toolbox managers, unloads some code
// that's need just once and turns control over to our main
// event loop (where the real action lurks).
void main(void)
{
 // jettison %A5Init segment.  we don't need
 UnloadSeg((Ptr)_DataInit);
 // it anymore and it's fragmenting memory 
 MaxApplZone();

 gMyResFileRefNum = Initialize();
 // jettison initialization code since we no longer need it
 UnloadSeg((Ptr)Initialize);
 // clean up a little from unloaded segments
 PurgeMem(FreeMem());

 MainEventLoop();

 TEToScrap();  // export our scrap for others to enjoy
}

// The following routine is the main event loop.  It gives up
// time to other apps and figures out whether or not there's
// time for doing idle-time tasks.
void MainEventLoop()
{
 EventRecordevent;
 long   sleepTime;
 BooleangotEvent;
 short  stayOutCounter = 5; // it's a fudge factor 
 
 while (!gQuitting) {

 // user adjusts by changing dbl-click time in ctrl panel
 sleepTime = GetDblTime();
 if (gInBackground) sleepTime = -1L;
 gotEvent = WaitNextEvent(everyEvent, &event,
 sleepTime, gCursorRgn);
 if (gotEvent) {
 DoEvent(&event);
 } else {
 // To try to avoid loading IdleStuff segment when
 // we're just creating aliases, we need to ignore
 // the first couple of null events while we wait for
 // the 'ODOC' Apple event to come through 
 if (!--stayOutCounter) { DoIdle(); }
 }
 }
}

// The following routine handles the normal set of events
// that we're likely to receive.  Apple events are handled by
// different routines (see Events.d for those routines).
void DoEvent(EventRecord *event)
{
 short  myError;
 short  windowPart;
 WindowPtrwindow;
 char   key;
 Point  mountPoint;

 switch (event->what) {
 case mouseDown:
 windowPart = FindWindow(event->where, &window);
 switch (windowPart) {
 case inMenuBar:
 AdjustMenus();  // prepare menu items first
 DoMenuCommand(MenuSelect(event->where));
 break;
 case inContent:
 break;
 case inDrag:
 break;
 case inGoAway:
 break;
 }
 break;
 case keyDown:
 case autoKey:
 key = event->message & charCodeMask;
 // is command key down?
 if (event->modifiers & cmdKey) {
 if (event->what == keyDown) {
 AdjustMenus();  // prepare menu items first
 DoMenuCommand(MenuKey(key));
 break;
 }
 }
 break;
 case activateEvt:
 break;
 case updateEvt:
 break;
 case diskEvt:
 if ((event->message >> 16) != noErr) {
 mountPoint.h = kDILeft;
 mountPoint.v = kDITop;
 myError = DIBadMount(mountPoint, event->message);
 }
 break;
 case osEvt:
 switch ((event->message >> 24) & 0x0ff) {
 case suspendResumeMessage:
 // suspending
 if ((event->message & resumeFlag) == 0) {
 gInBackground = true;
 // export our scrap for others to enjoy
 TEToScrap();
 } else { // resuming
 gInBackground = false;
 if (event->message & convertClipboardFlag)
 TEFromScrap();
 // make sure we have pointer for cursor
 InitCursor(); 
 }
 break;
 case mouseMovedMessage:
 DisposeRgn(gCursorRgn);  // get rid of old region
 gCursorRgn = NewRgn();
 SetRectRgn(gCursorRgn, -32768, -32768,
 32766, 32766); // entire space
 break;
 }
 break;
 case kHighLevelEvent:  // It's an Apple event 
 AEProcessAppleEvent(event);
 break;   
 }
}

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom 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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.