Adding Defaults to GraphPaper

In this section, we’ll modify the GraphPaper application to work with the defaults database. We will do this by making changes to both the Controller and the ColorGraphView classes.

Cocoa applications use a single instance of the Foundation class NSUserDefaults for managing the defaults database. Apple’s documentation notes that the NSUserDefaults class is not thread-safe, so you should use it only from your application’s main thread.

To use the NSUserDefaults class, you must first decide what default values your application will need to store. Each of these values should be given a name that will be its key in the defaults database. For each value, you must also decide upon a representation — that is, how the representation will be stored. Table 21-3 shows the defaults that we will use for GraphPaper.

Table 21-3. Defaults for the GraphPaper application

Default name

#define

Our default

AxesColor

GP_AxesColor

[NSColor red]

LabelColor

GP_LabelColor

[NSColor blue]

GraphColor

GP_GraphColor

[NSColor black]

xstep

GP_xstep

0.1

xmin

GP_xmin

-10.0

xmax

GP_xmax

10.0

ymin

GP_ymin

-5.0

ymax

GP_ymax

5.0

formula

GP_Formula

cos(x)

GraphPaper window frame

Not needed

NSMakeRect(0,0,500,500)

Once you have decided on the default values that your application will be using, you need to write the code that will install these into the default registration table. This table will provide the default default values for your application — that is, the values that the application will use before any are set by the user.

Apple recommends that you register the defaults that each of your classes will use in a method you create called +initialize . The +initialize method is a special class method that is invoked when your class is first used (recall that the plus (+) means class method, whereas a minus (-) means instance method). The Objective-C runtime system ensures that the initialize message[40] is sent once and only once to each class in your program. The initialize message is always sent to a class’s superclass before it is sent to the class itself.

The GraphPaper application will use the defaults system in three locations:

  1. The GraphView class will use the defaults system to determine the initial values of the xmin, xmax, xstep, ymin, ymax, and formula values, overriding the information stored in the NSForm instance in MainMenu.nib.

  2. The ColorGraphView class will use the defaults system to determine the initial values of the AxesColor, LabelColor, and GraphColor colors, overriding the values that were hardcoded into the ColorGraphView class.

  3. The NSWindow class will use the defaults system to determine the initial location of the GraphPaper window.

For consistency and to prevent typographical errors, we will create a separate file called defaults.h that will contain #define values for each of the default keys.

  1. Create the defaults.h file (in PB or elsewhere) and save it in your ~/GraphPaper folder.

                         // defaults.h
                         // Define the default values used in GraphPaper
                         
    #define GP_AxesColor    @"AxesColor"
                         #define GP_LabelColor   @"LabelColor"
                         #define GP_GraphColor   @"GraphColor"
                         #define GP_xstep        @"xstep"
                         #define GP_xmin         @"xmin"
                         #define GP_xmax         @"xmax"
                         #define GP_ymin         @"ymin"
                         #define GP_ymax         @"ymax"
                         #define GP_Formula      @"Formula"
  2. Add defaults.h to your GraphPaper project in the group Other Sources.

Registering the Default Values

We will need to add two methods to each of the GraphView and ColorGraphView classes: an initialize method that will register the appropriate defaults, and an awakeFromNib method that will set the appropriate controls based on the values in the defaults system.

  1. Insert the following declarations into GraphView.h:

                            + (void)initialize;
                            - (void)awakeFromNib;
  2. Insert the #import directive and the #define macro shown here in bold into GraphView.m:

    #import "GraphView.h"
    #import "Segment.h"
    #import "Label.h"
    #import "Controller.h"#import "defaults.h"
                            
    #define FLOAT(x) [NSNumber numberWithFloat:x]
    
    @implementation GraphView
  3. Insert the implementation for the initialize class method into GraphView.m:

                            +(void)initialize
                            {
                                NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                                NSMutableDictionary *appDefs = [NSMutableDictionary dictionary];
                            
        [appDefs setObject:@"0.1"    forKey:GP_xstep];
                                [appDefs setObject:@"-10.0"  forKey:GP_xmin];
                                [appDefs setObject:@"10.0"   forKey:GP_xmax];
                                [appDefs setObject:@"-5.0"   forKey:GP_ymin];
                                [appDefs setObject:@"5.0"    forKey:GP_ymax];
                                [appDefs setObject:@"cos(x)" forKey:GP_Formula];
                            
        [defaults registerDefaults:appDefs];
                            }

For ease of typing and reading, this initialize method uses string values, rather than creating an NSNumber with the appropriate float value.

Next we’ll make the necessary changes to the ColorGraphView class.

  1. Insert the method and function declarations shown here in bold into ColorGraphView.h:

                            + (void) initialize;
                            - (void) awakeFromNib;
    @end
    NSData *DataForColor(NSColor *aColor);
                            NSColor *ColorForData(NSData *data);
  2. Insert the #include directive shown here in bold into ColorGraphView.m:

    #import "ColorGraphView.h"
    #import "Segment.h"
    #import "Label.h"#import "defaults.h"
  3. Insert the following two transformation functions and the class method declaration for initialize into ColorGraphView.m:

                            NSData *DataForColor(NSColor *aColor)
                            {
                                return [NSArchiver archivedDataWithRootObject:aColor];
                            }
                            
    NSColor *ColorForData(NSData *data)
                            {
                                return [NSUnarchiver unarchiveObjectWithData:data];
                            }
    
    @implementation ColorGraphView
    +(void)initialize
                            {
                                NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
                                NSMutableDictionary *appDefs = [NSMutableDictionary dictionary];
                            
        [appDefs setObject:DataForColor([NSColor redColor])
                                            forKey:GP_AxesColor];
                            
        [appDefs setObject:DataForColor([NSColor blueColor])
                                            forKey:GP_LabelColor];
                            
        [appDefs setObject:DataForColor([NSColor blackColor])
                                            forKey:GP_GraphColor];
                            
        [defaults registerDefaults:appDefs];
                            }

In addition to providing the initialize method, we must equip the ColorGraphView implementation with two functions — one for converting an NSColor object into an NSData object, and one for converting back. We need to do this because the defaults system cannot store NSColor objects by themselves. The defaults system can store NSData objects, however, so we can store colors in the defaults system by first converting them to NSData objects. (Indeed, because any object can be archived in an NSData structure, it is possible to store any object in the defaults system.)

Notice that the initialize method does not need to call the initialize method in the superclass; the Objective-C runtime system handles this for us automatically.

Reading Values from the Defaults Database

When GraphPaper starts up, it will read the defaults database to discover the user’s preferences for graph, axes, and label colors. (We’ll add the initial graph parameters to this list in a later section.) To read the database, we use the standardUserDefaults method.

  1. Insert the following awakeFromNib instance method into GraphView.m:

                            - (void)awakeFromNib
                            {
                                NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
                            
        [xminCell  setObjectValue:[defs objectForKey:GP_xmin]];
                                [xmaxCell  setObjectValue:[defs objectForKey:GP_xmax]];
                                [xstepCell setObjectValue:[defs objectForKey:GP_xstep]];
                                [yminCell  setObjectValue:[defs objectForKey:GP_ymin]];
                                [ymaxCell  setObjectValue:[defs objectForKey:GP_ymax]];
                            
        [formulaField setObjectValue:[defs objectForKey:GP_Formula]];
                            }

This method queries the defaults system for the value that corresponds to each key. The first time that this version of GraphPaper is run, these values will correspond to the values that are registered in the initialize class method. However, if any of the values are changed and saved in the defaults system, those values will override the values that are registered.

  1. Insert the following awakeFromNib method into the ColorGraphView.m implementation file:

                            - (void)awakeFromNib
                            {
                            
        NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
                            
        [super awakeFromNib];
                            
        [self setObjectsToColor:ColorForData([defs dataForKey:GP_AxesColor])
                                                 forTag:AXES_TAG];
                            
        [self setObjectsToColor:ColorForData([defs dataForKey:GP_GraphColor])
                                                 forTag:GRAPH_TAG];
                            
        [self setObjectsToColor:ColorForData([defs dataForKey:GP_LabelColor])
                                                 forTag:LABEL_TAG];
                            }

This method queries the defaults system for the NSColor object for each color, then uses the ColorGraphView setObjectsToColor:forTag: method to set the color. We use the setObjectsToColor:forTag: method because it performs the proper sequence of release and retain steps to ensure that we do not leak memory.

Finally, we’ll take advantage of some machinery that is built into the NSWindow class that automatically remembers where the GraphPaper window is dragged by the user and restores the window to that location when the program runs again.

  1. Insert the following class method into Controller.h:

                            - (void)awakeFromNib;
  2. Insert the followingawakeFromNib method into the Controller.m implementation file:

                            - (void)awakeFromNib
                            {
                                [[graphView window]  setFrameUsingName:@"Main Window"];
                                [[graphView window]  setFrameAutosaveName:@"Main Window"];
                            }

Now let’s test the work we’ve done so far:

  1. Build and run GraphPaper, saving all files first. Click the Graph button. You should see the window shown in Figure 21-3.

    GraphPaper window with defaults

    Figure 21-3. GraphPaper window with defaults

    Notice that the values for xmin, xmax, step, ymin, ymax, and formula are the values that were registered in the initialize method, rather than the values that are stored in the nib.

  2. Move the GraphPaper window to a different location and then quit GraphPaper.

  3. Restart GraphPaper. Notice that the GraphPaper window now appears where you previously left it, rather than in the original location (where the window was positioned in IB).

  4. Now move the GraphPaper window so that it’s partially off-screen.

  5. Quit GraphPaper and restart it again. Note that the window is completely visible!

  6. Quit GraphPaper.

Restoring a window to the location where it was positioned the last time that the application ran is a very friendly feature, but it needs to be implemented correctly. For example, suppose your screen’s resolution is set to 1600 x 1280, and you leave an application’s window in a corner of the screen and then quit the application. If you then lower your screen’s resolution to 1024 x 768 and run the application again, you might not be able to find the application’s window because it is off-screen. Fortunately, the Cocoa implementation of setFrameUsingName: (which we used in Controller’s awakeFromNib method) and related methods will never restore a window in a position where it cannot be seen. These methods interrogate the screen to find out its current resolution and, if the stored frame of the window will not completely appear on the screen, the frame is modified so that it will fit before the window is restored.

Finally, let’s see what the property list for the GraphPaper application looks like at this point:

  1. Restart GraphPaper again, choose GraphPaper Preferences, and then open the Colors panel and make changes to the colors. Quit GraphPaper.

  2. Now double-click the ~/Library/Preferences/GraphPaper.plist file in the Finder to view the GraphPaper defaults in PropertyListEditor, as shown in Figure 21-4.

    The defaults in your GraphPaper.plist file will probably differ from those in Figure 21-4. You may even have different properties listed — it depends on what you did while GraphPaper was running.

GraphPaper.plist file in PropertyListEditor

Figure 21-4. GraphPaper.plist file in PropertyListEditor

As you can see, other parts of the application kit have been using the defaults system without our knowledge! In particular, the NSColorPanel uses the defaults system to remember its position on the screen as well as its current mode. Note that the main GraphPaper window’s position is stored as well.

Notice also that there is no entry in the defaults system for xmin, xmax, xstep, ymin, ymax, or formula. That’s because the default values for these items are never changed in our current code. To do that, we’ll need to implement the okay: method associated with the GraphPaper Preferences panel.



[40] Remember, the message sent does not have a plus sign. Because the receiver of the message is a class, the Objective-C runtime looks for a class method called initialize rather than an instance method.

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

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