TweetFollow Us on Twitter

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

Related Info: Alias Manager Event Manager

Aliases ’R us

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

By Tim Swihart, Cupertino, California

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

About the author

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

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

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

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

A Word About the Environment

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

A Word About Aliases

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

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

Why Use Aliases?

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

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

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

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

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

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

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

Time For Some Examples

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

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

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

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

How Do I Use It?

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

What Predetermines Where They Go?

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

How Can I Edit the Destination?

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

Figure 1: Alias Meister’s CustomGetFile dialog

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

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

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

Remembering the Destination

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

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

Listing 1: Finding the “Apple Menu Items” Folder

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

A Brief Tangent

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

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

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

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

Return From Interrupt

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

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

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

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

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

Listing 3: Storing an alias as a resource

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

Bad News and Good News

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

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

Then What Happens?

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

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

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

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

How Are Finder Aliases Created?

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

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

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

Listing 5: Creating a Finder Alias

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

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

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

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

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

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

File Type Glitch!

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

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

Make the Finder Do It!

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

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

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

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

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

Folder Selection Notes

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

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

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

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

Listing 6: Calling CustomGetFile

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

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

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

Listing 7: Filtering Out Non-Folders

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

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

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

Listing 8: Renaming A Button

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

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

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

Listing 9: CustomGetFile Dialog Hook Function

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

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

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

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

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

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

Exercises For The Reader

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

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

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

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

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

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

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

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

// Resource IDs:

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

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

#define rHelpString128    // Finder help string

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

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

#define rMenuBar 128 // application's menu bar

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

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

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

// Miscellaneous:

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

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

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

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

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

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

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

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

#pragma load "AliasMeisterDump"

#pragma segment InitializationStuff

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

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

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

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

 FlushEvents (everyEvent, 0);

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

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

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

 TEFromScrap();
 ZeroScrap();

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

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

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

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

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

#pragma load "AliasMeisterDump"
#pragma segment IdleStuff

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

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

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

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

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

/* INSERT LISTING 8 HERE */

/* INSERT LISTING 7 HERE */

/* INSERT LISTING 9 HERE */

/* INSERT LISTING 6 HERE */

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

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

 /* INSERT LISTING 3 HERE */

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

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

#pragma load "AliasMeisterDump"

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

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

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

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

 /* INSERT LISTING 5 HERE */

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

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

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

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

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

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

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

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

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

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

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

 October 17, 1992 (v.1.0)
*/

#pragma load "AliasMeisterDump"

#pragma segment MenuStuff

// External globals:
extern Boolean gQuitting;

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

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

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

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

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

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

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

#pragma load "AliasMeisterDump"

#pragma segment IdleStuff

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

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

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

 October 8, 1992 (v.1.0)
*/

#pragma load "AliasMeisterDump"

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

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

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

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

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

 MainEventLoop();

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

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

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

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

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

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Tor Browser 11.5.8 - Anonymize Web brows...
Using Tor Browser you can protect yourself against tracking, surveillance, and censorship. Tor was originally designed, implemented, and deployed as a third-generation onion-routing project of the U.... Read more
Alarm Clock Pro 15.0 - $19.95 (91% off)
Alarm Clock Pro isn't just an ordinary alarm clock. Use it to wake you up in the morning, send and compose e-mails, remind you of appointments, randomize the iTunes selection, control an internet... Read more
Google Chrome 107.0.5304.121 - Modern an...
Google Chrome is a Web browser by Google, created to be a modern platform for Web pages and applications. It utilizes very fast loading of Web pages and has a V8 engine, which is a custom built... Read more
calibre 6.9.0 - Complete e-book library...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Safari Technology Preview 16.4 - The new...
Safari Technology Preview contains the most recent additions and improvements to WebKit and the latest advances in Safari web technologies. And once installed, you will receive notifications of... Read more
FileZilla 3.62.2 - Fast and reliable FTP...
FileZilla (ported from Windows) is a fast and reliable FTP client and server with lots of useful features and an intuitive interface. The FileZilla Client not only supports FTP, but also FTP over TLS... Read more
djay Pro 4.0.13 - Transform your Mac int...
djay Pro provides a complete toolkit for performing DJs. Its unique modern interface is built around a sophisticated integration with iTunes and Spotify, giving you instant access to millions of... Read more
Opera 93.0.4585.21 - High-performance We...
Opera is a fast and secure browser trusted by millions of users. With the intuitive interface, Speed Dial and visual bookmarks for organizing favorite sites, news feature with fresh, relevant content... Read more
AppCleaner 3.6.6 - Uninstall your apps e...
AppCleaner allows you to uninstall your apps easily. It searches the files created by the applications and you can delete them quickly. Supports macOS Ventura. Fixed an issue causing failed updates... Read more
QuickBooks 21.0.7.1248 - Financial manag...
QuickBooks helps you manage your business easily and efficiently. Organize your finances all in one place, track money going in and out of your business, and spot areas where you can save. Built for... Read more

Latest Forum Discussions

See All

‘Top Hunter Roddy & Cathy’ Review –...
The NEOGEO is generally characterized by, with only a few notable exceptions, fighting games and Metal Slug. Within a couple of years of its launch, the vast majority of the output on the console seemed to be mining (quite successfully) a few... | Read more »
SwitchArcade Round-Up: Reviews Featuring...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for November 28th, 2022. In today’s article, we’ve got a pair of reviews to check out. Full reviews of Pokemon Scarlet and Violet and The Oregon Trail are waiting for you to read. There’... | Read more »
‘OPUS: Echo of Starsong’ Interview: Port...
With OPUS: Echo of Starsong ($8.99) having finally launched on iOS after hitting PC and consoles, I had a chance to talk to Scott Chen who is the co-founder and executive producer of Sigono. In our chat, I touched on topics like game subscription... | Read more »
Best iPhone Game Updates: ‘Rush Rally 3’...
Hello everyone, and welcome to the week! It’s time once again for our look back at the noteworthy updates of the last seven days. As November breaths its last, the holiday season is right around the corner. That means we should start seeing more... | Read more »
‘Total Football’ is an Arcade-Style Socc...
GALA SPORTS recently launched its brand new soccer title, Total Football, and, true to its name, it is a pure arcade-style soccer game in the same vein as FIFA Mobile and PES Mobile. It also features official licensing from FIFPro and Manchester... | Read more »
Genshin Impact will recieve two new char...
HoYoverse has announced that Genshin Impacts version 3.3 will be arriving on December 7th. Titled All Senses Clear, All Existence Void, the update will bring two powerful new characters and a brand new card-based minigame. [Read more] | Read more »
‘Wreckfest’ Mobile Compared With Console...
HandyGames’ mobile version of Bugbear’s demolition derby-style racer Wreckfest ($9.99) released on iOS and Android recently, and we featured it as our Game of the Week. | Read more »
Black Friday Deals Here – The TouchArcad...
After taking a couple of weeks off we return on this glorious Black Friday with another episode of The TouchArcade Show. We get into a big discussion about virtual assistants like Alexa, Siri, and Google, and their place in the greater smarthome... | Read more »
TouchArcade Game of the Week: ‘Station 1...
I’m a big fan of Glitch Games and their unique brand of point-and-click adventure/escape room/puzzle games, and while they’re a tiny outfit and there’d typically be a couple years gap in-between their new releases, they were always worth the wait.... | Read more »
SwitchArcade Round-Up: ‘Super Lone Survi...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for November 25th, 2022. Today we look at the remaining releases for the week, and I’ll be honest with you: it’s not a great assortment. Still, there are at least a couple of things... | Read more »

Price Scanner via MacPrices.net

Cyber Monday: 24″ Apple M1 iMacs for $150 off...
Amazon has Apple’s 24″ M1 iMacs on Black Friday sale for $150 off MSRP. Their prices are currently the lowest available for new iMacs among the Apple retailers we track: – 24″ M1 iMacs (8-Core CPU/7-... Read more
Cyber Monday Sale: 25% off Apple MagSafe acce...
Apple retailers are offering MagSafe accessories for up to 25% off MSRP for Cyber Monday. Here are the best deals available, currently from Verizon and Amazon: (1) Verizon has Apple MagSafe Chargers... Read more
Cyber Monday Sale: Apple AirPods for up to $1...
Looking for Apple AirPods, AirPods Pro, or AirPods Max this Cyber Monday? Look no further than our Apple AirPods Price Tracker. We track prices from 20+ Apple retailers and update the tracker... Read more
Final day for Apple’s Black Friday/Cyber Mond...
CYBER MONDAY Apple’s four day Black Friday/Cyber Monday 2022 event is now live and will run from November 25, 2022 to November 28, 2022 (ends today!). Receive a free $100-$250 Apple Gift Card with... Read more
Cyber Monday: Apple 13″ M2 MacBook Airs for $...
Apple retailers have posted their Cyber Monday prices on 13″ MacBook Airs. Take up to $200 off MSRP on M2-powered Airs with these sales with prices starting at only $1049. Free shipping is available... Read more
The best Cyber Monday iPhone sale? This $500...
If you switch to Xfinity Mobile and open a new line of service, they will take $500 off the price of a new iPhone, no trade-in required. This is the best no trade-in Cyber Monday Apple iPhone 14 deal... Read more
Cyber Monday Sale: Apple 16″ MacBook Pros for...
Amazon is offering $500 off MSRP discounts on Apple 16″ MacBook Pros with M1 Pro CPUs as part of their Cyber Monday sale. Their prices are the lowest available for these models from any Apple... Read more
Cyber Monday Sale: Apple 14″ MacBook Pros for...
Amazon is offering $300-$500 off MSRP discounts on Apple 14-inch MacBook Pros with M1 Pro CPUs as part of their Cyber Monday sale. Their prices are the lowest available for these models from any... Read more
Cyber Monday Sale: Apple Watch Ultra for $60...
Amazon has Apple Watch Ultra models (Alpine Loop, Trail Loop, and Opean Bans) on sale for $60 off MSRP as part of their Cyber Monday sale, each including free shipping, reducing the price for an... Read more
Cyber Monday MacBook Sale: 13″ M1 Apple MacBo...
Amazon has Apple 13″ M1 MacBook Airs back on sale for $200 off MSRP, starting at only $799, for Cyber Monday 2022. Their prices are the lowest available for new MacBooks this Cyber Monday. Stock may... Read more

Jobs Board

*Apple* Electronic Repair Technician - PlanI...
…a highly motivated individual to join our Production Department as an Apple Electronic Repair Technician. The computer repair technician will diagnose, assemble, Read more
Product Manager II - *Apple* - DISH (United...
…you will be doing We seek an ambitious, data-driven thinker to assist the Apple Product Development team as our new Retail Wireless division continues to grow and Read more
Staff Engineer 5G Protocol, *Apple* - DISH...
…metrics. Essential Functions and Responsibilities for a Staff Engineer 5G protocol( Apple ) Knowledge of 5G and 4G/LTE protocols and system architectures Experience Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.