![](/file/12652/www.mactech.com.tar/www.mactech.com/sites/all/themes/custom_front/images/you_are_here_red.gif)
![](/file/12652/www.mactech.com.tar/www.mactech.com/sites/default/files/beta-site.gif)
|
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(¶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; } }
![](/file/12652/www.mactech.com.tar/www.mactech.com/sites/all/themes/custom_front/img/search_text.gif)
- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine