Rixstep
 About | ACP | Buy | Industry Watch | Learning Curve | News | Search | Test
Home » Learning Curve

Full Equivalence

Carbon is just another API.


John Gruber is always off on one tangent or another, charging a windmill here, an anthill there. One of his favourites - former employee as he is of 'Bumbling Boners Software' - is telling all the fanboys Carbon and Cocoa are 'equivalent' - that they're two fully equivalent ways of doing the same thing.

This of course gives him 'carte blanche' [<-- that's French] to light into the likes of Andrew Stone, Dan Gillmor, and his all-time #1 love-hate object Avie Tevanian who had the audacity to put forth NeXTSTEP, the OpenStep specification, NeXTSTEP's OPENSTEP implementations for a variety of hardware and system platforms - and even marshall a few stillborn releases of Gruber's all-time non-pareil favourite industrial strength OS, 'MacOS'.

But how 'equivalent' are Carbon and Cocoa anyway? Cocoa developers know; Carbon developers might have a guess; and fanboys like John Gruber himself generally don't have a clue. And if you read John Gruber and believe anything he says, you might be inclined to take his mad rantings at face value. And then you'd be doing yourself a grave disservice.

So one time for all, just to clear the air, here's another example of the 'full equivalence' of Carbon and Cocoa. [For an earlier tutorial on this subject see 'File Management Macintosh Style'. Ed.]

OK, let's go. What we're going to look at here is the task of putting an 'about' box on screen for a Carbon app. And we're going to consult Apple's own text on the subject, 'Learning Carbon', reputedly written by 'JDD', an Apple employee. [And if you know who 'JDD' is, all the better; but if you don't know then don't worry your little head over it - it's irrelevant. Ed.]

The text for this task starts on page 190. There are several steps involved. At the end you'll see the 'fully equivalent' code in Cocoa - so just hold on. First it's Carbon - the 'fully equivalent' 'API' for OS X.

Step One: Creating the About Window

The first thing we have to do is create the about window with Interface Builder. This is a rather involved procedure, so just hang in there. You'll get the essentials here. Just be patient. OK, here we go.

  1. Double-click your NIB in your project manager (PBX, Xcode, whatever).
  2. Create the new window by dragging a window from the palette to the desktop.
  3. Name the window. Let's call it 'AboutWindow'.
  4. Put a title on the window. As the project in the book is called 'Moon Travel Planner' [sic] we'll put 'Moon Travel Planner' in the title bar.
  5. Set the window controls so only the close button is enabled.
  6. Set the window attributes so only 'Standard Handler' is selected.
  7. Set the class of the window to 'Document'.
  8. Set the 'Theme Brush' setting as 'Dialog'.
  9. Resize the window to 264x165 pixels.
  10. Add the application icon to the window.
  11. Show the layout rectangles so you can place everything properly.
  12. Adjust spacing so it follows the HI guidelines.
  13. Centre the icon.
  14. Add a static text field below the icon.
  15. Put in 'Moon Travel Planner' in the text field.
  16. Adjust the spacing of the text field so it follows the HI guidelines. You have to be careful here (if you're not Rich Siegel). It's twelve pixels from the top of the text field to the bottom of the icon. You calculate the spacing from the top of the window to the top of your layout rectangle. It's 8 + 64 + 12 or 84 pixels. Now with the text field selected, choose 'Size' from the inspector. Choose 'Top/Left' from the 'Bounds' pop-up menu and type in that '84' for the 'y' value.
  17. Centre the text and adjust the spacing.
  18. Add a new static text field below the first. This is for version information.
  19. Delete the title of this text. You'll fill it in with version info later. So choose 'Attributes' from the pop-up menu and delete 'Static Text' from the 'Title' text field.
  20. Enter 'MTTP' as the text field's signature and 132 as its ID. Choose 'Control' from the pop-up menu in the 'Static Text Info' window. In the 'Control ID' section type 'MTTP' in the 'Signature' field and 132 in the 'ID' field.
  21. Turn off the layout rectangles.
  22. Position the window where you'd like it to appear when the user opens it.
  23. Save the NIB and exit Interface Builder.

Got all that? You might find it interesting to know that this Interface Builder is itself a Cocoa application and that the technology you've just witnessed would be impossible under Carbon, but that would be jumping over the head of John Gruber, so we'll just leave it go for now.

OK, so now we have a window - sort of. Now we have to make it appear on screen and behave like an 'about' window should.

Step Two: Writing the Code

The first thing we have to do is get a so-called 'NIB reference'. This is easy.

err = CreateNibReference(CFSTR("main"), &nibRef);

The variable nibRef is one of your own; 'CFSTR' creates in essence a 'CoreFoundation string' from the ASCII string 'main'. This is basically just code Avie yanked out from the NeXTSTEP classes so John Gruber could claim full equivalence. It's Cocoa code at work - not Carbon.

We also need a 'window reference' for when we start loading our window.

WindowRef gAboutWindow;

The 'g' stands for 'global' and Carbon apps always have lots of global variables.

So now we're ready to go. Now we create our window.

err = CreateWindowFromNib(nibRef, CFSTR("AboutWindow"), &gAboutWindow);

And of course we have to check the return value (err). It should be zero. If it's not we can't really proceed. But assuming it is zero, we move on.

Step Three: The About Function

It's not enough to 'create' the window - we have to handle it once it gets on screen. [And it's not on screen yet but don't get yourself into a Gruber hissy fit. We'll get to it - just be patient.]

First we have to add some constants that we'll need for this function.

#define kMTPOpenAboutWindowCommand 'aBtb'
#define kMTPVersionInfoID           132

[Note the lovely way commands are defined: it is up to the programmer to ensure there are no conflicts. But whatever: Carbon is 'fully equivalent'. Hehe. Ed.]

Next we have to declare the function prototype.

pascal void MTPAboutWindowCommandHandler(WindowRef window);

That word 'pascal' refers to an archaic programming language from the 1970s created by Niklas Wirth in Switzerland. It was meant only as a teaching tool but sad to say some people - notably the original whiz team building the first Macintosh - never grasped that. Pascal has been duly slammed by the programming community. Take a look at Brian Kernighan's classic 'Why Pascal is Not My Favorite Programming Language' for the definitive slam.

Anyway, now we can write our function.

pascal void MTPAboutWindowCommandHandler(WindowRef window)
{
    CFStringRef text;
    CFBundleRef appBundle;
    ControlID versionInfoID = {kMTPApplicationSignature,
                               kMTPVersionInfoID};
    ControlRef  versionControl;
    ControlFontStyleRec controlStyle;

    appBundle = CFBundleGetMainBundle();
    text = (CStringRef) CFBundleGetValueForInfoDictionaryKey(appBundle,
            CFSTR("CFBundleGetInfoString"));
    if (text == CFSTR(" ") || text == NULL) // Weak, JDD!
        text = CFSTR("Nameless Application");
    GetControlByID(window, &versionInfoID, &versionControl);
    SetControlData(versionControl, kControlLabelPart,
            kControlStaticTextCFStringTag, sizeof(CStringRef), &text);
    controlStyle.flags = kControlUseJustMask;
    controlStyle.just = teCenter;
    SetControlFontStyle(versionControl, &controlStyle);
    ShowWindow(window);
    SelectWindow(window);
}

Wow! That was a lot! Is that it? No! That's only the code that makes the window appear on screen. It is not the code that 'handles' the window - and we still need code to call the above function. So we have quite a bit more work to do!

[BTW, you again see all those 'CF' prefixes. Again, that's 'CoreFoundation': it's Cocoa code Avie ripped out of Cocoa and put elsewhere so Carbon programmers could claim 'full equivalence' and Gruber could have his windmills to charge. Ed.]

Step Four: The Main Event Handler

So to get the above code to be called we have to dig into the innards of our otherwise finished application code and put in the following.

switch (command.commandID) {
/* * */
case kOpenAboutWindowCommand:
    MTPAboutWindowCommandHandler(gAboutWindow);
    result = noErr;
    break;
/* * */
}

But that only loads the window when the user invokes the command - now we need to handle the about window itself.

Step Five: The About Window Event Handler

First we declare an event type specifier. [Don't ask - you don't want to know. Ed.]

EventTypeSpec aboutSpec = {kEventClassWindow, kEventWindowClose};

Next we declare the about window event handler.

pascal OSStatus MTPAboutWindowEventHandler(
    EventHandlerCallRef handlerRef, EventRef ref, void *userData);

Next we have to have code to install the handler.

InstallWindowEventHandler(gAboutWindow,
        NewEventHandlerUPP(MTPAboutWindowEventHandler),
        1, &aboutSpec, (void *) gAboutWindow, NULL);

[You'll notice the 'UPP' suffix above: that's another legacy thing from 'MacOS'. It basically means you can use all the hairy crash prone code you want. Notice also the embedded parentheses: these are so-called 'explicit typecasts' - the same kinds of things that give Windows all its security vulnerabilities. But otherwise this is of course 'fully equivalent' to Cocoa.]

And now - finally - we come to the about window event handler itself. Hooray!

[Note: this code is somewhat optimised as the code supplied by 'JDD' was a bit lame. Just a bit. Hehe. Ed.]

pascal OSStatus MTPAboutWindowEventHandler(
        EventHandlerCallRef handlerRef, EventRef ref, void *userData)
{
    if (GetEventKind(ref) == kEventWindowClose) {
        HideWindow((WindowRef) userData);
        return noErr;
    }
    return eventNotHandledErr;
}

And that's it! With a puny hour or two's work we have an about window and can get it on screen! The above code obviously looks for a signal that the user wants to close the window - and then we hide it instead. But hey that's cool.


So OK - now we know how to create and run an 'about' box with the fully equivalent Carbon 'API' which the great John Gruber says is 'just another API' for OS X and a full equivalent of Cocoa. And he should know, right? After all... Anyway, what does the Cocoa code for the same procedure look like?

Dying to find out, aren't ya?

Creating and Running an 'About' Box Cocoa Style

There is no code. There is no work creating the window with Interface Builder either.

It's all automatic. It comes prefab and preset with every Cocoa application. Just like Safari. Just like them all. Just like the entire Rixstep ACP.

It's automatically hooked up, it's automatically ready to run, and it automatically gets all the data - the version information and so forth - and puts it in the about window.

And it automatically handles its own events. Such as closing the window when the user no longer needs it.

Summary time. With Cocoa, the 'fully equivalent API', there is:

  1. No line of code.
  2. No time building the window.

It just runs. It's insanely great.

Footnote: you'll remember 'JDD' told you in the first part (with Interface Builder) to 'position the window where you'd like it to appear when the user opens it'. That's amateurish. The Cocoa 'equivalent' automatically centres the about window on screen. To do this in Carbon would cost you even more code. And the Cocoa 'equivalent' displays three text fields, not two, and none are set in any NIB, and it displays two separate version numbers as well. And it can include any 'resource' file with the name 'Credits'. And use any HTML or RTF formatting data found there. (Look at Preview and TextEdit.) Automatically.

About | ACP | Buy | Industry Watch | Learning Curve | News | Products | Search | Substack
Copyright © Rixstep. All rights reserved.