Chapter 13. Saving, Loading, and Printing

In the previous chapter we saw how the contents of a text object can be translated into a Rich Text stream of characters. In this chapter, we’ll see how to take that stream and save it into a file; we’ll also see how to load one of those files and place its contents in a new window. Finally, we’ll learn about printing. All of these tasks will be made dramatically easier by using the Cocoa Application Kit framework.

Data Management with NSDocument

At this point, our MathPaper application does a great job with math, fonts, and handling multiple windows. However, it’s missing a lot of basic functionality, such as:

  • Saving the contents of a window into a file

  • Loading a saved file, so that you can continue calculating where you left off

  • Marking an edited window with the “unsaved” close button with a dot inside so that you know it has been edited (the “saved” close button has an X inside:

    Data Management with NSDocument

    )

  • Alerting a user who tries to close an edited window without first saving the edited file

  • Printing the contents of a window

  • Graying out menu items that are not appropriate in a given context (e.g., the Save menu item when there are no open documents)

Writing the code for document saving and loading can be quite an ordeal in some computing platforms. However, it’s easy in Cocoa because most of the required code is already part of the multiple-document framework.

Memory to Disk and Back

When you save a document to a file on your computer’s hard drive, you are making a representation of a part of the computer’s memory and archiving that representation in a way that can be restored later. In some cases, the easiest representation is a byte-for-byte copy of the computer’s memory. Applications that use this technique are simply storing a memory snapshot on disk.

Other applications take a more intelligent approach to archiving information. For example, a vector-graphics drawing program stores the endpoints of a line rather than the positions of all of the individual pixels that make up the line. Also, an object-oriented drawing program might archive a document to disk by telling its document object to write a stream of bytes to the disk containing the values in all of the instance variables. This command would then be recursively applied to all of the objects that the document contains. In this manner, all of the text, images, lines, and other information that can be referenced from the document would be written out to disk.

There are other ways that applications can save documents to disk as well. Microsoft Word, for instance, has a feature called fast saves . If you open a large document with Word, make a few changes, and then try to save it, Word can simply append the change journal to the end of the first document, rather than making a whole new copy. Fast saves can be a real timesaver, but they have the disadvantage that the unmodified text remains in the document — which can produce embarrassing situations at times! Early versions of Word were also somewhat buggy, and occasionally fast saves caused document files to become corrupted.

Rethinking MathDocument and PaperController

Let’s review where we are with our MathPaper program. Our MathDocument class is a subclass of NSDocument, and our PaperController class is a subclass of NSWindowController. There is also a single NSDocumentController object in our program that’s mostly invisible to the programmer, even though it has overall control of the multiple-document architecture and does a lot for both the interface and the program. For example, this NSDocumentController object automatically creates a new MathDocument object when MathPaper launches and initializes another new MathDocument object every time a user chooses File New from MathPaper’s menu. NSDocumentController also maintains a list of NSDocument (i.e., MathDocument) objects and all of their NSWindowControllers. In addition, this object manages the on-screen document windows and much of the main menu.

MathDocument’s makeWindowControllers method is automatically invoked at MathPaper launch time and when File New is chosen. Our override of makeWindowControllers in the previous chapter creates a new PaperController object, unarchives PaperWindow.nib with the new PaperController object as its owner, and adds the new object to the list of window controllers.

Note that NSDocumentController implements thesaveAllDocuments: , newDocument: , and openDocument: action methods associated with the File menu commands. These methods are all connected to the First Responder icon, as you can see in Interface Builder.

Our MathDocument class doesn’t do much yet. At this point, it responds to makeWindowControllers and windowNibName messages but otherwise sits dormant. All of the controls, the state information, and the Evaluator hookup for each window are kept in the associated copy of PaperController. What is the state information? For each MathPaper window, the following information is relevant:

  • The current location and size of that MathPaper window (called the window frame)

  • The window’s history — that is, the calculations that have been displayed

To this end, we need to redesign the way that data is kept in the application. For each MathPaper window, the MathDocument object will be responsible for keeping the “official” copy of the data, and the PaperController object will be responsible for keeping the “working copy” — that is, the copy the user is modifying.

Our changes to the MathPaper class will consist of adding two instance variables and five accessor methods. The two instance variables, frame and history, will keep track of the MathDocument’s window frame and its math-expression history. The accessor methods allow us to set and inspect these variables. Later, we’ll also modify the PaperController class so that it takes its data from the MathDocument class.

We’ll also add a new initializer to the MathDocument class that will initialize the history variable to a default value. In this case, we’ll choose a default that improves the usability of the MathPaper application by adding a line of instruction on how to use the application.

  1. Back in Project Builder, insert the lines shown here in bold into the MathDocument.h class interface file:

    #import <Cocoa/Cocoa.h>
    
    @interface MathDocument : NSDocument
    {    NSRect              frame;
                                NSMutableData       *history;
    }-(NSData *)history;
                            -(void)setHistory:(NSData *)theHistory;
                            -(NSRect)frame;
                            -(void)setFrame:(NSRect)aFrame;
                            -(BOOL)hasFrame;
    @end

With the exception of hasFrame, the names of these five methods indicate what they do. The hasFrame method tells the invoking object whether the frame instance variable has been set. If it hasn’t, it still reads the “factory default” value — that is, all zeros.

  1. Insert the #import directive and add the methods shown here in bold to the MathDocument.m class implementation file:

    #import "MathDocument.h"
    #import "PaperController.h"
    
    @implementation MathDocument
    
    ...-(NSData *)history
                            {
                                return history;
                            }
                            
    -(void)setHistory:(NSData *)theHistory
                            {
                                [history setData:theHistory];
                            }
                            
    -(NSRect)frame
                            {
                                return frame;
                            }
                            
    -(void)setFrame:(NSRect)aFrame
                            {
                                frame = aFrame;
                            }
                            
    -(BOOL)hasFrame
                            {
                                return (frame.size.height!=0 && frame.size.width!=0);
                            }
                            ...

As you can see, these methods are all pretty straightforward. The history and frame methods return their respective instance variables, while the setHistory: and setFrame: methods set them. The hasFrame method returns TRUE if the frame has been set (that is, if the size of the frame is nonzero) and FALSE if it has not been set.

We also need to create a designated initializer and deallocator for the MathDocument class. The initializer will create the initial NSMutableData object for the MathPaper history instance variable and fill history with its initial content. The deallocator will release the storage that has been allocated when it’s no longer needed.

  1. Add the init and dealloc methods shown here in bold to the MathDocument.m class implementation file:

                            // Designated initializer; create the empty document
                            - (id)init
                            {
                                RTF *rtf = [ [ [RTF alloc] init] autorelease];
                                [super init];
                                [rtf setBold:TRUE];
                                [rtf setSize:10.0];
                                [rtf appendString:@"Enter a math expression and hit return:"];
                                [rtf setSize:12.0];
                                [rtf appendString:@"
    "];
                                history = [ [NSMutableData alloc] initWithData:[rtf data] ];
                                return self;
                            }
                            
    -(void)dealloc
                            {
                                [history release];
                                [super dealloc];
                            }

Finally, we want to modify the PaperController’s windowDidLoad method so that it reads the value for the window’s frame and history after the PaperWindow.nib file is loaded. With the code that we’ve written, history will always be equal to the default value and frame will never be set. That’s okay for now; we’ll set the frame value in the next part of this chapter.

  1. Insert the lines shown here in bold into the windowDidLoad method in the PaperController.m file:

    #import "PaperController.h";
    #import "RTF.h";#import "MathDocument.h";
                            
    ...
    - (void)windowDidLoad
    {
        NSString *path;    MathDocument *doc;
    
        [super windowDidLoad];
        [ [self window] makeFirstResponder:theText];
        // Initialize with document
                                doc = [self document];
                                [theText replaceCharactersInRange:NSMakeRange(0,0)
                                                          withRTF:[doc history] ];
                                if ([doc hasFrame]) {
                                    [ [self window] setFrame:[doc frame] display:YES];
                                }
    
        path  = [ [NSBundle mainBundle]
                   pathForResource:@"Evaluator"
                   ofType:@""];
    
        toPipe   = [NSPipe pipe];    // NSTask below will retain
        fromPipe = [NSPipe pipe];    // NSTask below will retain
    
        toEvaluator   = [toPipe fileHandleForWriting];
        fromEvaluator = [fromPipe fileHandleForReading];
    
        evaluator = [[NSTask alloc] init];
        [evaluator setLaunchPath:path];
    
        [evaluator setStandardOutput:fromPipe];
        [evaluator setStandardInput:toPipe];
        [evaluator launch];
    
        [[NSNotificationCenter defaultCenter]
            addObserver:self
            selector:@selector(gotData:)
            name:NSFileHandleReadCompletionNotification
            object:fromEvaluator];
     
       [fromEvaluator readInBackgroundAndNotify];
    }
  2. Build and run the MathPaper application. Type Command-N a few times to create a few windows. Each window should contain the brief instructions that we’ll now create in MathPaper’s designated initializer, init. (See Figure 13-1.)

MathPaper with prompt output from the designated initializer method

Figure 13-1. MathPaper with prompt output from the designated initializer method

Many simple document-based applications don’t bother with separate subclasses of both the NSDocument and NSWindowController classes. Instead, they simply put all of the document-management and window-control functionality inside the NSDocument subclass. This approach works fine until you want to display two different kinds of document windows — for example, plain text and RTF text windows in TextEdit, or two windows on a single document.

Because most Cocoa programmers will eventually need to subclass both the NSWindowController and NSDocument classes, we decided to simply start that way in our demonstration program. We hope it hasn’t been too confusing!

In the next section, we’ll build upon this framework by having Cocoa save the contents and the position of the window into a file.

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

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