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-Wesleys 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. Theyre an easy way to keep track of ancillary files that your application needs, but that maybe dont 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 well create an application (Alias Meister) that simplifies creating and sorting Finder aliases. Its 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 well 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 theyre 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 Finders 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 files 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 cant 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 its 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 Managers search algorithms, see A Brief Tangent later in this article or read Chapter 4 of "Inside Macintosh: Files" from Addison-Wesley. Youll 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 records 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 targets 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 wasnt 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 isnt 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 its on a floppy or server thats not currently online (the Alias Manager will prompt the user to insert the target floppy or will mount the desired server) or if its been moved, renamed, or restored from a backup (which tends to alter the files ID number).
Still not convinced? Are you sitting there grumbling that File Manager routines dont actually accept aliases, no matter how wonderful aliases are, so whats 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, Ive 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 items icon; picking Make Alias from the Finders 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 aliass 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 Id still have to create the new Finder aliases and drop them on the destination aliases - thats 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 Meisters 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 havent 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 Meisters resource fork. When Alias Meister is first created, it has no alis resource (theres 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 cant find an alis resource in its own resource fork, it creates one that points to your boot disks 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 youll be presented with a CustomGetFile dialog that shows only volumes and folders. Youll also see two new buttons: Select and Use Apple Menu Items (see Figure 1).
Figure 1: Alias Meisters 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 buttons new name always includes as much of the target destinations 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 youre 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 Meisters resource fork. Naturally, Alias Meister understands that you might want to reconfigure it after youve used it for awhile, so before it stores the destinations 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 thats hilited in the scrolling list (so you dont 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 disks 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 handles 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 thats a common parent to the two files linked by the alias record. This means the Alias Manager doesnt 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 targets 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 isnt as picky about identifying the volume the target lives on and a fast absolute search starts with the target files 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 its not nil, the alias thats created will be relative to this first FSSpecPtrs 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 destinations alias record in Alias Meisters resource fork is as simple as calling:
AddResource((Handle)myAliasHndl, rAliasType, kMyAlisID, nil);
The interface file Aliases.h defines rAliasType to be alis and Ive defined kMyAlisID to be 128 (theres nothing magic about 128, but as well 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 theres a drawback to storing the alis record in the applications resource fork (and this is part of why normal applications use a custom file in the Preferences folder): multiple users cant launch this application at the same time from a shared volume (each user needs to have write permissions on the applications resource fork). But, if I put the alis resource in a preferences file, you couldnt configure multiple copies very easily (theyd 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 isnt that big of a problem. In fact, its small size also means theres practically no reason to mind keeping multiple copies around (each configured for a different destination).
Then What Happens?
When Alias Meisters 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). Youve 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 events 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 files 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, theres a problem with that list - its incomplete. System 7.1 adds a Fonts folder inside the System Folder and theres a special-cased file type for aliases to the Fonts folder, yet that file type doesnt 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 Finders 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 doesnt 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, youll 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 Apples 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 Meisters 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 dont match the targets 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 didnt 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 didnt 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 youre 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 (its too hard in some situations to pick the folder that youre 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 (its the block of code that starts with case sfHookNullEvent:).
Exercises For The Reader
As it exists today, Alias Meister provides basic functionality. Its 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 dont 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 Meisters icon). Bear in mind though that Alias Meister launches pretty quickly now (which is why I didnt bother to implement this feature).
Add error reporting, currently, Alias Meister just quietly quits if it hits a major problem. Dont 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 users 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(¶mBlock);
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(¶mBlock);
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;
}
}