AddThis Social Bookmark Button

Listen Print Discuss

Inside Contextual Menu Items, Part 2

by Steven Disbrow
06/04/2004

In my last article, I gave you a very high-level look at Contextual Menu Items (CMIs). This time out, I'm going to show you how to actually create your own CMI using Apple's Xcode IDE. (Note that this process assumes a basic familiarity with the C programming language.)

Start Here

When I was first dabbling in Macintosh programming, back in the early 1990s, my preferred method for learning how to create software was to crack open one of the many Inside Macintosh volumes and start reading. Soon the floor around my desk would be littered with books, each one overflowing with Post-It® notes that marked points of interest for my current project. Of course, those Inside Macintosh volumes are still available, but now instead of paying $50 a pop for wads of dead tree flesh, you can get them for free, in PDF form, from Apple's web site. And instead of bits of sticky paper, I've got scads of Safari bookmarks, each pointing to a different page on Apple's developer web site.

Of particular interest (at least as far as this article is concerned) is a page with a link to a sample source code archive that shows how to create a simple CMI. Since this is Apple's official example of how to create CMIs, we'll use this sample source code as our starting point. (Apple does provide information on the more generic subject of "plugins," which is also worth reading.)

So download the archive, unpack it, open the SampleCMPlugin folder, and then load the SampleCMPlugin.pbproj file into Xcode. (Note that this is a Project Builder project file, but Xcode will happily convert it for you. Just select the default conversion options as Xcode opens the file, and everything should be fine. If you are still using Project Builder, no problem! Most everything I'll discuss here has an obvious Project Builder equivalent.) Once you have the project open in Xcode, you should see a project window like the one shown below.

In this opening screen, we can see the frameworks that CMIs are based on, along with the implementation file: SampleCMPlugIn.c. This brings up a couple of important points: CMIs are based on Carbon, not Cocoa, and they are almost always written in good old C. (This isn't a problem per se, but if you were looking at CMIs as a way to learn Cocoa or Objective-C, you should look for another type of Mac program to write.)

What Does it Do?

Before we look at the code, let's see what the SampleCMPlugIn.plugin actually does. To do that, you'll need to install it. Assuming that the SampleCMPlugin folder ended up on your desktop, look inside there for a folder called build. Inside of that folder, you'll find the SampleCMPlugIn.plugin file. Copy this file into your ~/Library/Contextual Menu Items folder and then log out and back in. (For complete information on how to install CMIs, see my last article.) Once you've logged out and back in, go to the Finder, and right-click (or control-click, if you are still using a one-button mouse) on an icon on the desktop. When you do, you should see a menu something like the one shown below.

In the above picture, I've highlighted the five menu items that the SampleCMPlugIn adds to this contextual menu. The first is a divider line. You'll notice that this line comes right after the divider line that separates it from the Finder's Copy command. This is because in Mac OS X v10.3, Apple seems to have changed the way the Finder places its divider lines, but the SampleCMPlugIn code has not been changed to reflect this. (If you've got one or more third-party CMIs that exhibit this problem, this is your first clue that almost every third-party CMI in existence started life at the intersection of Apple's SampleCMPlugin and some text editor's global "Find and Replace" command.) We'll see how to correct this when we look at the source code in more detail.

The next two lines are informational. The first tells you that you are looking at output from inside of the SampleCMPlugin code, and the second tells you the basic type of information that was passed to the CMI from the operating system. In this case, it was a list. This is the type that you get whenever you invoke a contextual menu on one or more Finder icons. (The other basic type is text, but, strangely, almost no applications pass this type of information. Instead, you'll usually get a type of null when you right-click on a chunk of selected text.)

The last two lines are examples of submenus in a CMI. The first one reports the number of selected items in the menu item name, while the submenu itself is a list of the selected items. The last one is just a generic submenu, filled with three sample items.

The last thing to notice is that each individual menu item (except the divider) also has its internal menu item number shown as part of its name. When an item is selected by the user, this is the number that the CMI will receive, along with the message that the user made a selection.

A Look at the Source Code

If you aren't there already, load the SampleCMPlugin.pbproj file into Xcode again and double-click on the SampleCMPlugIn.c file. Once the file is open, scroll down to the definition for the value kSampleCMPlugInFactoryID. (You'll find it on line 72.) This factory id is a UUID. UUID stands for "Universal Unique IDentifier." Basically, a UUID is a unique 128-bit number that's created using a computer's MAC address, the time of day, and a random seed value. (For a more complete definition, check out this link.) This number is used to differentiate the CMI from any others that might be installed on your computer, so it's important that each CMI have its own UUID.

Apple provides a bit of sample code for generating a UUID, and there's even a uuidgen command-line tool built into Mac OS X, but my preferred solution is to use John C. Daub's UUID Generator. The reason for this is that, as you can see in the source code, the UUID must be in a very specific format. Neither Apple's sample code nor the uuidgen tool produce a value in this format. The UUID Generator tool however, can produce a UUID in several formats, including the one required for a CMI. It can even place the UUID directly on the clipboard for you, so that all you have to do it switch to Xcode and paste it in. (Oh, and it's free, too!)

Examining The Context

The "C" in CMI stands for "Contextual." When it's invoked, your CMI needs some way to evaluate the context in which it was called. In the SampleCMPlugIn, this is handled by a function named SampleCMPlugInExamineContext. When the user right-clicks her mouse, the system calls the equivalent of this function in every loaded CMI. The function then looks at the parameters that the system has passed it and determines whether or not its services make sense within the current context. If so, the function builds a list of menu items and passes it back to the system so that those items can be included in the final contextual menu. That said, let's look at those parameters:

Now that we know what the parameters are, let's see how the function actually works. Assuming that we're all looking at the same version of SampleCMPlugIn.c (and we should be; this sample code hasn't been updated in quite a while), refer to the following line numbers to see the highlights of the SampleCMPlugInExamineContext function:

Now, you are probably looking at the code in this function and thinking, "It's just a bunch of if statements!" Right you are! The only "magical" thing happening here is that the logic behind those if statements is driven by the contents of the inContext parameter.

Pages: 1, 2

Next Pagearrow