Changing the Document Window Title

Ordinarily document windows display as their title the last component of the path under which the document is saved -- that is, the document name. Thus, a document saved at /Users/mike/Pictures/pigs.jpg displays the window title "pigs.jpg."

It's possible, however, to customize your document window's title to display information in addition to (or in place of) the document name. We see this sort of thing in Photoshop where the current zoom factor and the color space of the image are appended to the document name in the window title.

By overriding a method of NSWindowController, it's not difficult to put whatever we want in the window title. The method to override is -windowTitleForDocumentDisplayName:, which returns a string to display as the window title, and whose sole argument is the document name.

As an example of what we can do, let's implement this method to append the current scale to the document name in the following way:

If you don't understand the formatting used in the string above, you might want to read up on the printf function (using your favorite C book, or by typing man printf in the Terminal). Essentially, we're saying the number should be displayed as a float with two digits to the right of the decimal place, followed by a percent sign. (Since "%" is used to escape formatting we have to precede a "%" we want to appear as a character in the string with that same formatting escape character).

The window title produced by this method looks like the following:

The window title produced by the above method.

Now there's an issue with this method. Whenever we change the scale of the image, we'd like the window title to reflect those changes. We need to tell the window controller the state of the window contents has changed, and that change in state needs to be reflected in the information displayed as part of the window title. NSWindowController provides a method to force an update to the window title, -synchronizeWindowTitleWithDocumentName. Since the zoom factor is displayed in the title, we want to update it whenever the zoom changes. The -scaleImageTo: is the lowest common denominator for all zoom operations, so we put as the last line in that method:

[self synchronizeWindowTitleWithDocumentName]

With that we've added another bit of polish to our application. To finish things up today we're going to add a custom About panel to our application.

A Custom About Panel

When making our About panel, we'll use a nib dedicated to this sole purpose.
The main reason you might want to put the About window in a separate nib is to improve the launch time of your application, as well as to decrease the amount of memory it requires. When the application starts, it has to load the entire contents of MainMenu.nib into memory, so by putting the About window in a separate nib, the application has one less object to load with MainMenu.nib at launch time. The nib with the About panel isn't actually loaded until the user needs to see it.

Granted, for our relatively small application and small About panel, a separate nib won't yield significant improvement. However, the technique is worth learning and practicing for when your applications get larger and more complex.

In Interface Builder create a new nib from File-->New...; in the Starting Point dialog choose Empty from the list of templates. As the name suggests, this will create an empty nib file, to which you will now add a window. Go ahead and add any text or images to the About panel to reflect the information you would like displayed. There shouldn't be any controls like buttons or sliders for our implementation.

Save the nib as AboutPanel.nib, and put it in the English.lproj folder of your ImageApp project folder. You will then be prompted to choose what target to add the nib to. Choose ImageApp. I kept mine simple and uninformative, as shown in the image below:

Probably not the best About panel an application could have.

To open the window in response to the user clicking on the "About ImageApp" menu item, we need a controller class for the About panel nib that has an outlet through which we can communicate with the About window. That is, so we can send the window a message telling it to display itself onscreen.

As you can see, AboutPanel.nib presently has no object capable of serving this purpose. To fulfill this role we employ the services of our class AppController. With a bit of modification it can be set up to serve the dual purpose of application delegate, and controller for the About panel. To prepare AppController for its new job, add to the interface file the outlet declaration:

IBOutlet NSWindow *aboutWindow;

Save the changes to this file, and drag it into AboutPanel.nib to add AppController to the nib's repertoire of classes.

Now we're not going to create an instance of AppController in the About panel nib like we have in MainMenu.nib -- our application only needs one instance of this class. Rather, we will make the class of AboutPanel.nib's File's Owner AppController. This brings me to an aside about File's Owner.

An important thing to remember about File's Owner is that it is not an instance of the class we assigned to File's Owner. The nature of File's Owner is quite different from that of the window object, or the instance of AppController in MainMenu.nib. The latter are actual instances of classes saved to disk, and then reloaded to memory when the nib is loaded.

Such is the wonder of Interface Builder, Cocoa, and nib files. You'll see this commonly referred to as an example of Cocoa's ability to "freeze-dry" objects (which is accomplished using the classes NSArchiver and NSUnarchiver. We'll cover these down the road.).

File's Owner, on the other hand, is a proxy object: a stand-in object that represents a link to an instance of its designated class that exists elsewhere at runtime. The reason we assign a class to File's Owner is so we can make meaningful connections that will become true connections between objects when the nib is loaded at runtime. These connections don't exist yet in the nib, as connections between real instances do. We will see in a moment how we tell the nib at runtime what object is really the File's Owner.

Before we get to that, make a connection the window and the About panel outlet of File's Owner. Save your work in AboutPanel.nib, and let's return to Project Builder to make a method to load the nib and open the window.

The next step is to make a point of access to it. That is, we need to write a method that will load the nib containing the window, and open the window in response to the user selecting the "About ImageApp" menu item. This method will go in AppController, which is convenient since the instance of AppController is part of MainMenu.nib where we can easily connect this action to the menu item in question.

To set this up add to AppController.h this method declaration:

- (IBAction)showAboutPanel:(id)sender;

Open MainMenu.nib in Interface Builder and read in AppController.h to update Interface Builder's information about the available methods of AppController. Next, select "About NewApplication" from the NewApplication menu, and change its name to "About ImageApp" (change any other NewApplication to ImageApp as well). Next open the Connections Info Panel and disconnect its current connection to File's Owner. The default connection is to an action of NSApplication that opens the default Cocoa About Panel.

Now drag a new connection from the menu item to AppController, and make the connection to the showAboutPanel: action. Save the changes to MainMenu.nib, and head on back to Project Builder.

Now we will see the code manifestation of our lengthy discussion of File's Owner. Remember, I said that File's Owner isn't a real object until we load the nib and assign an object as the owner. To do this we use a method of NSBundle called -loadNibNamed:owner:. This should set off alarms in your head; the second argument is owner:.

The object we pass as this argument is the object to which all of our fake connections to File's Owner will become real connections. The only restriction to the object passed here is that it must be of the same class that we assigned to File's Owner in the nib. For our purposes, we will pass self, which is the instance of AppController from MainMenu.nib. The first argument is the name of the nib that we wish to load, in our case AboutPanel.

You see here that the nib-loading method is encased in an if statement. All this says is that we only want to load the nib if aboutWindow doesn't point to an object. Before we load the nib, aboutWindow is nil. When we load the nib with self as the owner, the connection between the window and the aboutWindow outlet is actualized, and aboutWindow points to a real object.

Subsequent calls to -showAboutPanel: will pass over the nib-loading code since it is already loaded in memory. Finally, we display the window by sending a makeKeyandOrderFront: message to aboutWindow. And that's all there is to it!

When we started this section I said not to put any controls in the About panel. That was for simplicity's sake. Adding controls is easy. All you have to do is declare the actions you want the controls to invoke in AppController, and make the connections through File's Owner.

If you have a lot of controls, then you might be better off creating a dedicated AboutPanelController class. If you choose to do so, follow the example of using AppController as the About panel controller. That is, create and instantiate the controller class in MainMenu.nib so you can connect the About ... menu item to it, and then make it the File's Owner of the AboutPanel.nib.

The End

So there you have several things you can do to add polish to your application before releasing it into the wild. I hope you got plenty of ideas to take off on your own with whatever application you're building. In the next column we'll get into the class NSBitmapImageRep, and we'll learn how to work with images on a pixel-by-pixel basis by building a class that will convert color images into grayscale images.

Michael Beam
is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.