8. System Preference Panes

“Use other door. It's broken.”

—sign on donut shop door

System Preference Panes, hereinafter referred to as preference panes, are essentially plug-ins that are dynamically loaded by the System Preferences application. Preference panes and the System Preferences application allow users to alter systemwide settings using a standard host application. They live in the /Library/PreferencePanes folder in any of the standard search paths, but when you install new ones yourself, you will most likely place them in your ~/Library/PreferencePanes folder. Figure 8.1 shows the System Preferences application's main window with a lot of available preference panes.

Figure 8.1. The System Preferences application.

image

Preference panes are primarily used to control settings for applications that do not have a user interface. This could include a background-only application or some other system-level service that is run at startup. Software Update is a good example: The preference pane allows the user to schedule when updates may occur, and a separate application downloads and installs the latest software. The System Preferences application provides a logical “base of operations” for many types of system functionality, but you should avoid doing real work in a System Preference Pane—that is usually better suited for an application.

Here is another example. Figure 8.2 shows the Dock, and Figure 8.3 shows the Dock preferences as displayed in the System Preferences application. The Dock is the application that runs in conjunction with the Finder, and it displays icons of frequently used and currently running applications. The Dock can function in a variety of ways and allows the user to edit its settings via its preference pane. The Dock also has a power-user shortcut to get to its preferences as well, by control clicking on an empty area in the Dock.

Figure 8.2. Dock.

image

Figure 8.3. Dock preferences in the System Preferences application.

image

Note that the Dock preferences all take effect immediately. As soon as the user changes a value in the Dock preferences pane, the change is immediately communicated to the Dock. See CFNotificationCenter.h and the Notification Center documentation from Apple for more details about using this technique. Because the Dock is considered a system-level feature that is always available, System Preferences is the most logical place to store and edit its settings.

The Result

In this chapter, I will discuss a preference pane called MyPreferencePane. As I alluded to in the previous chapter, our preference pane will control one of the settings (the only one actually) of the MyTextService service. By default, MyTextService will paste the altered text directly into the document that has the current selection when it is executed. Our preference allows the altered text to be placed on the global pasteboard instead so that it can be pasted elsewhere, leaving the selected text untouched. Figure 8.4 shows the first tab of MyPreferencePane's preference pane.

Figure 8.4. The MyPreferencePane tab in the System Preferences application.

image

Note that although there are a few different types of controls on this tab, only the check box is used to control the MyTextService service preference. The NSTextField is merely there to show that other types of data can be managed as a preference as well. As this project is examined, you will see that the preferences are stored in a file located at ~/Library/Preferences/com.triplesoft.myPreferencePane.plist, which is updated immediately when the check box state changes. That is, there is no need to “save changes” or close the preference pane before the changes take effect. This property list file is shown in Listing 8.1. You can see the Boolean Value Key for the check box and the empty String Value Key for the NSTextField. When MyTextService is called, it looks up the current value of the Boolean Value Key to decide which pasteboard to use (refer to Listing 7.3 in Chapter 7, “System Services”).

Listing 8.1. com.triplesoft.myPreferencePane.plist



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
         "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
    <key>Boolean Value Key</key>
    <true/>
    <key>String Value Key</key>
    <string></string>
    </dict>
</plist>


Note

The Mac OS X terminal contains a command named defaults that advanced users and developers should take note of. This command allows you to quickly and easily access the Mac OS X user defaults system to read and write user defaults. Open the Terminal and type man defaults for more information.

Figure 8.5 shows the second tab of the MyPreferencePane preference pane. This tab serves no purpose other than to show how simple it is to create a tabbed user interface in Interface Builder and link the items in each tab to actions and outlets in a class. The Click To Beep button, although on a completely different tab in the user interface, is treated no differently when connected in Interface Builder. The Cocoa framework transparently handles all the details of switching tabs for you. You can have as many tabs as you like with as many items in each tab—this is a huge improvement over what developers used to have to do for a multi-tabbed interface.

Figure 8.5. The MyPreferencePane View tab in the System Preferences application.

image

Note in Figure 8.1 that MyPreferencePane appears at the bottom of the System Preference application window in the Other category. This is where any third-party preference panes appear. You simply click it to make it active.

Let's examine the project file for MyPreferencePane!

The Project

Figure 8.6 shows the MyPreferencePane project in Project Builder. You can create this project using the Preference Pane template in the New Project Assistant. At a glance, you will notice the MyPreferencePanePref.h and MyPreferencePanePref.m files that contain the NSPreferencePane subclass that is our preference pane. The project also includes a .tiff file that contains the icon used to represent our preference pane in the System Preferences application (see Figure 8.1). The InfoPlist.strings and MyPreferencePanePref.nib files are also present.

Figure 8.6. The MyPreferencePane project.

image

The Project Settings

There is probably nothing for you to change in the Target settings, depending on whether things have changed in the project template as of this writing. One thing to note, however, is that the WRAPPER_EXTENSION for a preference pane is prefPane. Once again, this is how the operating system will tell that this bundle is a preference pane when searching through the PreferencePanes folder (see Figure 8.7).

Figure 8.7. The MyPreferencePane Target settings.

image

Let's look at the project settings.

The InfoPlist settings are relatively straightforward as well in Figure 8.8. You should note the settings of the CFBundleExecutable being our filename, NSMainNibFile being the name of our nib file, NSPrefPaneIconFile being the name of the icon file, and NSPrincipalClass being our classname.

Figure 8.8. The MyPreferencePane InfoPlist entries.

image

Note

When dealing with TIFF files, you should make sure that their names end in .tiff and not .tif. Although many programs treat both extensions as a TIFF file, Project Builder and the operating system do not. Mac OS X likes the extra f.

Note also that we will place our built preference pane in the ~/Library/PreferencePanes folder. You might have to create this directory if it does not exist. Also, notice the custom icon that our preference pane has in the Finder. This icon does not come free. The easiest way to apply this icon is to use the MyPreferencePane.icns file, included with the source code, to copy and paste the icon into place. By selecting Get Info on the .icns file and copying the icon, you can then easily select Get Info on the .prefPane file and paste it. The key to remember here is that the .tiff file in our project is for use inside the System Preferences application, not in the Finder (see Figure 8.9).

Figure 8.9. The PreferencePanes directory in our ~/Library directory.

image

The Nib File

Before we look at the source code, let's peek into the MyPreferencePanePref.nib file (see Figure 8.10). This standard nib file contains an NSWindow named prefPane that contains the entire user interface of our preference pane. The File's Owner is the MyPreferencePanePref class, which contains all the action and outlet instance variables. By assigning the prefPane window as the _window outlet of the File's Owner, we set up the relationship needed by the System Preferences application to find the views to display when our preference pane is selected. Open the file in Interface Builder to peruse the relationships of the objects for yourself.

Figure 8.10. The MyPreferencePane MyPreferencePanePref.nib file.

image

Note

The preference pane window in System Preferences has specific size requirements. The width is fixed to 595 pixels; the height should not cause the window to extend beyond the bottom of an 800×600 display. To be safe, you should not create a preference pane that is more than approximately 500 pixels in height. Make sure that you check your work on a small screen before you ship!

The Source Code

The MyPreferencePanePref class is a subclass of NSPreferencePane. NSPreferencePane is defined in PreferencePanes.h and implements the default behavior of a preference pane. By overriding methods in NSPreferencePane, you customize the behavior of your own preference pane (see Listing 8.1).

Listing 8.1a. MyPreferencePanePref Interface in MyPreferencePanePref.h



#import <PreferencePanes/PreferencePanes.h>

@interface MyPreferencePanePref : NSPreferencePane
{
    CFStringRef m_appID;    // application ID (this application)

    // Tab 1
    IBOutlet    NSButton       *m_checkBox;
    IBOutlet    NSTextField    *m_textField;

    // Tab 2
    IBOutlet    NSButton       *m_pushButton;
}

- (IBAction)checkboxClicked:(id)sender;
- (IBAction)buttonClicked:(id)sender;

@end


You will note that our subclass simply keeps track of our application ID (com.triplesoft.mypreferencepane from our InfoPlist entries) and our outlets as far as instance variables go. We also implement a few actions to handle our check box and button. Note that even though we have a multi-tabbed user interface, our outlets and actions are all defined as usual—no special treatment is required.

Note

If you choose to not cache the bundle identifier, you can use [[NSBundle bundleForClass:[self class]] bundleIdentifier] to look it up on demand. If you end up accessing this information regularly throughout your preference pane, you might consider the cache method shown here.

Listing 8.2 is an override of the -initWithBundle: method of NSPreferencePane. This method is called during the initialization phase of the preference pane. Its primary goal is to keep track of our application ID in the m_appID instance variable. You can also perform any other initialization here that does not depend on the user interface.

Listing 8.2. MyPreferencePanePref –initWithBundle: Implementation in MyPreferencePanePref.m



// This function is called as we are being initialized
- (id)initWithBundle:(NSBundle *)bundle
{
    // Initialize the location of our preferences
    if ((self = [super initWithBundle:bundle]) != nil) {
        m_appID = CFSTR("com.triplesoft.mypreferencepane");
    }

    return self;
}


Listing 8.3 contains the override of the -mainViewDidLoad method of NSPreferencePane. This method is called once the Nib is loaded and the windows have been created. You would perform tasks in this method similar to those that you would in -awakeFromNib in an application. We have two preferences to load and set at this point. Using the CFPreferencesCopyAppValue function, we load both Boolean Value Key and String Value Key and set the NSButton check box and NSTextField appropriately. Note that we also verify the data coming back from this function using the CFGetTypeID function in conjunction with the CFBooleanGetTypeID and CFStringGetTypeID functions. This provides a reliable way for us to ensure that our data is not corrupt in any way. Last, the values of the outlets are set accordingly to display the current state of the preferences. Remember to CFRelease the values once they are loaded!

Listing 8.3. MyPreferencePanePref -mainViewDidLoad Implementation in MyPreferencePanePref.m



// This function is called once the Nib is loaded and our windows
// are ready to be initialized.
- (void)mainViewDidLoad
{
    CFPropertyListRef value;

    // Load the value of the checkbox as a BOOLEAN
    value = CFPreferencesCopyAppValue(CFSTR("Boolean Value Key"), m_appID);
    if (value && CFGetTypeID(value) == CFBooleanGetTypeID()) {
        [m_checkBox setState:CFBooleanGetValue(value)];
    } else {
        [m_checkBox setState:NO];            // Default value of the checkbox
    }
    if (value) CFRelease(value);

    // Load the value of the text field as a STRING
    value = CFPreferencesCopyAppValue(CFSTR("String Value Key"), m_appID);
    if (value && CFGetTypeID(value) == CFStringGetTypeID()) {
        [m_textField setStringValue:(NSString *)value];
    } else {
        [m_textField setStringValue:@""];    // Default value of the text field
    }
    if (value) CFRelease(value);
}


Note

Remember, .plist files are nothing more than plain text in XML format. These files are very easy for a user to open up and “mess around with.” It doesn't hurt to err on the side of caution and verify that the values retrieved are the types of values we expect.

Listing 8.4 implements the override of the -didUnselect method of NSPreferencePane. This function is called when our preference pane is being deselected. That is, either another preference pane is being selected or the System Preferences application is shutting down. In this case, we save the contents of our NSTextField outlet, which is not saved at any other time. You could implement a notification to tell each time the user typed a character in the NSTextField and save then, but that would be overkill in this situation.

Listing 8.4. MyPreferencePanePref -didUnselect Implementation in MyPreferencePanePref.m



// This function is called when our preference
// pane is being deselected. That is, either
// another preference pane is being selected
// or the System Preferences application is
// shutting down. In this case we will save our
// text field, which is not saved at any other
// time. The checkbox is saved as it is clicked
// but just as easily can be saved here.
- (void)didUnselect
{
    CFNotificationCenterRef center;

    // Save text field
    CFPreferencesSetAppValue(CFSTR("String Value Key"),
        [m_textField stringValue], m_appID);

    // Write out all the changes that have been made for this application
    CFPreferencesAppSynchronize(m_appID);

    // Post a notification that the preferences
    // for this application have changed,
    // any observers will then become the first
    // to know that this has occurred.
    center = CFNotificationCenterGetDistributedCenter();
    CFNotificationCenterPostNotification(center,
        CFSTR("Preferences Changed"), m_appID, NULL, TRUE);
}


Note that the string value of the NSTextField is saved using the CFPreferencesSetAppValue function. Once saved, the CFPreferencesAppSynchronize function is called to flush the contents of the file and be sure that they are written to disk. You will note from Listing 7.2 in Chapter 7 that MyTextService also calls CFPreferenceAppSynchronize before accessing the preferences. This magic keeps everyone happy and “in sync.”

The last step is optional and is shown here as an example. We post a notification using CFNotificationCenterPostNotification and CFNotificationCenterGetDistributedCenter to let any observers know that the preferences have changed. Even though MyTextService does not implement this mechanism, it is not a bad idea to get into the habit of using it should some other application in your suite be “watching” your preferences. An application normally won't do this, but because we are working on a globally available systemwide feature, we do. Earlier in this chapter, I discussed where to find more information on using notifications.

Listing 8.5 contains the action that is called when the user clicks on the NSButton check box. Each time the user clicks the check box; the value is changed and is written to the preference file. Note that the preferences are once again synchronized after being set.

Listing 8.5. MyPreferencePanePref –checkboxClicked: Implementation in MyPreferencePanePref.m



// This action is called when our checkbox is clicked,
// we save the value immediately
- (IBAction)checkboxClicked:(id)sender
{
    CFPreferencesSetAppValue( CFSTR("Boolean Value Key"),
        [sender state] ? kCFBooleanTrue : kCFBooleanFalse, m_appID);

    // Force a synchronization so our preference is "live"
    CFPreferencesAppSynchronize(m_appID);
}


Note

What? You've never seen that ? : thing before? I love this feature of the C programming language and most C programming language derivatives. This mechanism is called a conditional expression. The format is as follows:

A ? B : C;

The first expression, A, is evaluated. If A is TRUE, B is executed; otherwise C is executed. This is equivalent to the following:

if (A) {    B;} else {    C;}

In our case, we are using the conditional operator to return one of two different values: if the -state of the sender (the check box) is YES (also known as 1), return kCFBooleanTrue; otherwise return kCFBooleanFalse.

Finally, in Listing 8.6 we have an action that is called when the button on the second tab is clicked (refer to Figure 8.5). This action is here to show you that the same class can be used to handle actions and outlets on multiple tabs in a multi-tabbed user interface. Clicking the Click To Beep button makes the computer beep.

Listing 8.6. MyPreferencePanePref –buttonClicked: Implementation in MyPreferencePanePref.m



// This action is called when the button on the second tab is clicked
- (IBAction)buttonClicked:(id)sender
{
    NSBeep();
}


More NSPreferencePane

Many other methods are defined in NSPreferencePane that can be overridden by your subclass. We only implement a few of them in this project because that is all that was necessary. Some of the others that you may consider overriding include

-assignMainViewThis method can be overridden if your NSPreferencePane subclass needs to dynamically choose a main view. You might need to do this to show a unique set of preferences based on the current hardware configuration.

-willSelect/-didSelectThese methods can be overridden if you need to be called just before or just after your NSPreferencePane subclass becomes the currently selected preference pane. You might need to do this to load a data file or begin some task.

-willUnselect/-didUnselectThese methods can be overridden if you need to be called just before or just after your NSPreferencePane subclass get swapped out as the currently selected preference pane. You might need to do this to save any preferences that are not saved as they are changed.

-shouldUnselect/-replyToShouldUnselect:These methods allow your NSPreferencePane subclass to postpone or cancel an unselect action. For example, if your preference pane begins a network action and then the user attempts to choose another pane, you might want to delay the closing of your pane until the network action completes or can be cancelled.

-initialKeyView/-firstKeyView/-lastKeyViewThis method can be overridden if your NSPreferencePane subclass needs to dynamically determine which view should be the initial, first, or last key view, respectively.

Be sure to look at NSPreferences.h for more details on other methods and some inside information on how they operate.

Try This

Here are some ideas to expand the preference pane project in this chapter; try these on your own.

Add a third tab to the preference pane and some new controls such as radio buttons and a pop-up button. Save the settings of these controls to the preference file as appropriate. Make sure to test them to see that they are really saving!

Add an NSTextField control below the tabs so that it is always displayed whenever the preference pane is selected. Make use of the NSTimer class you learned about in Chapter 3, “Cocoa Applications,” and have this control display how long, in seconds, that the preference pane has been visible. Be sure to properly dispose of any timer that has yet to “fire” when the user closes the preference pane.

Conclusion

Now you see that we can add preferences to a service by creating a preference pane project to go along with our system service project. Once you know the secrets, these are just as straightforward as implementing any Cocoa application. All similar pieces come together with just a few differences.

In this case, I asked you to drag the .prefPane file into the PreferencePanes folder. Normally, you would create an installer to install these items without intimate knowledge by the user. You don't want users dragging .service and .prefPane files around; you never know when they will drop them in the wrong location and then complain that your software isn't working.

Keep up the good work—you've learned a lot so far!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.136.97.64