Saving to a File

Before we can save the contents of a MathPaper window into a file, we first need to know what kind of file our application will create. Specifically, we need to know the file’s HFS creator code, its HFS type code, and its file extension.

Creator codes are used so that when you double-click on a filename in the Finder, Cocoa knows to open the correct application. Creator codes are also used to locate applications on the computer’s hard disk. You may remember that, in Section 10.4.5, we were assigned by Apple a creator code for the MathPaper application. That creator code was MATP.

Because a single application can create many different kinds of files, and because multiple applications are sometimes able to read the same file, document files stored on HFS filesystems also have a type code . Like creator codes, file type codes are 4-byte strings or 32-bit quantities. In many cases, the file type is the same as the file’s extension in the world of Windows and Unix: GIF and JPEG are used for image files, for example.

In addition to creator codes, Mac OS X uses file extensions to match up applications and their document files. A file extension is the set of letters that come after the last period (dot) in a file’s name. Unlike the Mac OS 9 Finder, the Mac OS X Finder makes it somewhat difficult for the user to accidentally change a file extension. It does this by generally hiding file extensions and by warning the user before an extension is changed. Mac OS relies on file extensions more and more as time goes on, which generally improves interoperability with other platforms that do not have creator and type codes.

Many of the commonly used extensions and their meanings are listed in Table 13-1. These are some of the standard extensions used by Mac OS X.

Table 13-1. Common Mac OS X file extensions

File extension

File type

.a

Unix library file

.app

Directory containing an application

.c

C-language source file

.eps

Encapsulated PostScript file

.gif

Graphics Interchange Format file

.h

C (or Objective-C) header file

.jpeg or .jpg

Joint Photographic Expert’s Group format file

.l or .lex

lex source file

.lproj

Directory containing language-specific nibs

.m

Objective-C language source file

.mbox

Directory containing a Mail.app mailbox

.midi

File containing binary MIDI data

.mp3

MPEG2 Level 3 audio file

.nib

IB file

.o

Unix object code file

.pdf

Portable Document Format file

.pbproj

Project file for use with PB

.rtf

Rich Text Format file

.rtfd

Rich Text Format directory containing .rtf and image files such as .tiff and .eps

.s

File containing assembler source

.tar

Tape Archive format file (see the Unix manpage for tar)

.tiff or .tif

Tagged Image File Format file

.txt

Plain text file

.uu

Uuencoded file

.y

yacc source file

.Z

File that has been compressed

For our application, we’ll use the string " MATP” as the MathPaper file type and the extension .matp for MathPaper document files. Now we’ll set up icons.

Icons for MathPaper

  1. Create a high-resolution icon for MathPaper document files and save it in your MathPaper folder with the name papericon.tiff.

The icon should be saved in the following four resolutions: 16 x 16, 32 x 32, 48 x 48, and 128 x 128 pixels. If you want to make only one icon, make the 128 x 128 icon and let the system scale the others. We came up with the icon shown here. (A quick way to create an icon is to use the Grab application to grab a small screen-shot selection and save it in the TIFF format. Grab is located in the /Applications/Utilities folder.)

Icons for MathPaper

  1. In IconComposer, create a PaperIcon.icns file from your icon files. Refer to Section 6.3.2 for details on using the IconComposer program.

  2. Open MathPaper.pbproj in PB.

  3. Choose PB’s Project Add Files menu command and add the PaperIcon.icns file to the Resources group in the Groups & Files pane (or simply drag the file from the Finder and drop it in the Resources group in PB). Make sure that you add PaperIcon.icns to the MathPaper target.

  4. Select the Targets vertical tab, then click on the MathPaper target.

  5. Click on the newly visible Application Settings tab, scroll down to the Document Types section, and select the MathPaper document type.

  6. Enter PaperIcon.icns in the Icon file field and click the Change (not Add) button. The pertinent part of PB’s main window can be seen in Figure 13-2.

Adding the PaperIcon.icns icon file to MathPaper’s Application settings

Figure 13-2. Adding the PaperIcon.icns icon file to MathPaper’s Application settings

At this point, PB knows that MathPaper can handle only one kind of document — a MathPaper document that has the extension .matp and the HFS type code “MATP”. MathPaper is an Editor for this file type, meaning that it can both read and write these files.

Synchronizing PaperController with MathDocument

Saving the contents of a window means writing all of the states associated with the window into a file, so that we can reconstruct the window’s current state as closely as possible when it’s reloaded. In the case of a MathPaper window, not many states need to be saved. Because the Evaluator back end doesn’t retain state between launches, the only information that we need to save in a MathPaper document file is:

  • The current location of the window (the frame)

  • The window’s history

This is precisely the information that we decided to store in the MathDocument instance variables — how convenient! To implement file saving, all we need to do is to write the code to migrate this information from each window’s PaperController object to its matching MathDocument object, then arrange for the pertinent instance variables to be saved in a file on disk.

In Step 4 in the previous section, we modified the windowDidLoad method in PaperController.h so that the MathPaper window’s initial frame and the contents of the NSTextView would be taken from the MathDocument object. To save a MathPaper document to a file, we need to perform the reverse operation — we need to copy the contents of the NSTextView object and the window’s frame back into the MathDocument instance variables. That’s what the new PaperController synchronizeData method shown below will do.

  1. Insert the new synchronizeData method declaration shown here in bold into the PaperController.h file:

    #import <Cocoa/Cocoa.h>
    
    @interface PaperController : NSWindowController
    {
        NSTask       *evaluator;
        NSPipe       *toPipe;
        NSPipe       *fromPipe;
        NSFileHandle *toEvaluator;
        NSFileHandle *fromEvaluator;
        IBOutlet NSTextView  *theText;
    }- (void)synchronizeData;
    @end
  2. Add the synchronizeData method implementation to the PaperController.m file:

                            - (void)synchronizeData
                            {
                                NSRange allRange = NSMakeRange(0,[ [theText textStorage] length]);
                                MathDocument *doc = [self document];
                                [doc setHistory:[theText RTFFromRange:allRange] ];
                                [doc setFrame:[ [self window] frame] ];
                            }
    
    ...
    @end

This method gets a copy of the RTF data in the NSTextView object in an NSData object. The MathDocument’s history object is then set to be equal to this RTF data. Following that, the MathDocument’s setFrame: method is invoked to set the frame of the PaperController’s window.

Archiving MathPaper Documents

Merely copying the data out of the PaperController’s objects and into the MathDocument is not sufficient; to save this data on the hard disk, we need to create a method that will turn this information into a byte stream.

To create that byte stream, we will use Cocoa’s NSCoder, NSArchiver, and NSUnarchiver classes. These classes are responsible for archiving and restoring data. With these classes, you can archive an object to a byte stream that is stored in an NSData object with a single method invocation, as follows:

NSData *theData = [NSArchiver archiveDataWithRootObject:anObject];

After this message is sent, the NSArchiver class creates an NSCoder instance that is responsible for doing the actual encoding. The NSCoder instance then invokes a version of theencodeWithCoder: method that we create, passing a pointer to itself as the argument.

Thus, to operate with the NSCoder system, our MathDocument class needs to implement the encodeWithCoder: method. This method is quite simple; it usually consists of a message to the coder object argument for each instance variable that needs to be archived. For example, if you had a class with a single instance variable called frame, your encodeWithCoder: method would look like this:

-(void)encodeWithCoder:(NSCoder *)coder
{
     [coder encodeRect:frame];
}

The real power of the NSCoder system comes when you are archiving complicated objects that contain references to many other objects. Each object that is archived automatically archives all of its subobjects, resulting in an entire tree or graph of objects being archived in the NSData object. The system transparently handles objects that are referenced in more than one location, as well as circular references.

If you casually read the NSCoder documentation or the NSCoder.h include file, you may feel that the NSCoder system is powerful but somewhat difficult to use. That’s because the NSCoder class just defines the base methods that are used for writing out raw data, byte arrays, and objects. There are many higher-level NSCoder methods that are defined as category methods in other include files, such as NSGeometry.h. Table 13-2 contains a list of many of the NSCoder methods that you may use in creating your applications.

Table 13-2. Common methods in the NSCoder class

Method

Purpose

- (void)encodeObject:(id)object

Encodes an object

- (void)encodePropertyList:(id)aPropertyList

Encodes a property list

- (void)encodePoint:(NSPoint)aPoint

Encodes an NSPoint structure

- (void)encodeSize:(NSSize)aSize

Encodes an NSSize structure

- (void)encodeRect:(NSRect)aRect

Encodes an NSRect structure

- (void)encodeDataObject:(NSData *)data

Encodes an NSData object

- (void)encodeValuesOfObjCTypes:@encode(int)i

Encodes an integer

- (void)encodeValuesOfObjCTypes:@encode(float)f

Encodes a floating-point value

- (void)encodeArrayOfObjcCTypes:@encode(type) count:(unsigned)aCount at:(void *)addr

Encodes an array of type type

To use the NSCoder class, we will create an encodeWithCoder: method for MathDocument:

  1. Add the encodeWithCoder: method shown here in bold to the MathDocument.m file:

    #import "MathDocument.h"
    #import "PaperController.h"
    #import "RTF.h" 
    
    @implementation MathDocument
    ...- (void)encodeWithCoder:(NSCoder *)coder
                            {
                                [coder encodeRect:frame];
                                [coder encodeObject:history];
                            }
    ...
    @end

Many implementations of the encodeWithCoder: method will invoke their superclass’s encodeWithCoder: method (e.g., [super encodeWithCoder:coder] ) so that the instance variables of the superclass are automatically encoded. This is not the correct approach with subclasses of the NSDocument class, however, because we do not actually want the instance variables of the NSDocument class stored in the byte stream. (Another reason not to send the [super encodeWithCoder:coder] message is that the NSDocument class does not implement the encodeWithCoder: method, so calling the super method would result in an error.)

Writing the Save Methods

We have created one method for copying the state of each MathPaper window into the MathDocument object and another method for copying these instance variables into an NSData object. All that remains for us to do to implement file saving is to display the appropriate Save panel when the user chooses File Save (or types Command-S). The Save panel will prompt for a filename, get the filename, get the NSData for the instance variables, and put all of this information into a file. Fortunately, we need to write only five lines of code to implement this functionality; Cocoa gives almost all of it to us for free.

To understand how all this happens, we’ll first focus on the First Responder icon that we first discussed back in Chapter 3. We’ve already discussed the First Responder in the context of the File menu items. Now we’re going to find out what it means.

The First Responder icon in the Nib File window is a placeholder for the application’s current first responder; that is, the object that will be the first to try to respond to keyboard events and menu commands such as Cut, Copy, and Paste. Any message sent to the First Responder icon is sent in order to each of the following objects, until an object is found that can receive the message and respond with a value other than nil:

  • The key window

  • The key window’s delegate

  • The key window’s NSWindowController (if you are using the document application framework)

  • The key window’s NSDocument

  • The application’s main window

  • The application’s main window’s delegate

  • The main window’s NSWindowController

  • The main window’s NSDocument

  • The application’s NSApplication object

  • The application’s NSApplication object’s delegate

  • The application’s NSDocumentController

If you open MathPaper’s MainMenu.nib file in IB and choose the File Save menu item, you will see that it sends the saveDocument: action message to the First Responder icon, as shown in Figure 13-3. (Note that we saw a similar premade connection from File New in Chapter 10.)

MathPaper’s File → Save connection to the First Responder in IB

Figure 13-3. MathPaper’s File Save connection to the First Responder in IB

Thus, when a user chooses the File Save menu command in a running MathPaper program, the saveDocument: message will be sent to the MathDocument object that controls the key (active) window. Cocoa implements the saveDocument: method in the NSDocument class. The behavior of this method depends on whether or not a filename has been assigned to the document. If a filename has not been assigned, the NSDocument class runs a modal Save panel to get a document and type. (A modal Save panel requires user action and must be dismissed before anything else can be done in the application.) After the user enters an appropriate name, etc., the document is saved and its “edited” status is cleared.

After the NSDocument class gets the filename, it needs to create an NSData object to contain the archived instance variables of your class. This NSData then needs to be written into a file on disk. The NSDocument architecture gives you not one, but three different ways for providing this information in your subclass, as well as for reading back the files from disk after they are created:

  • If you simply want to be able to save a document to disk and load it back in, all you need to do in your NSDocument subclass is to override the following two methods:

    (NSData *)dataRepresentationOfType:(NSString *)type;

    This method returns an NSData object that contains a sequence of bytes that corresponds to the document currently in memory. Some documents can be stored as multiple types, so you should check the NSString argument and provide the requested type.

    (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)type;

    This method is passed an NSData object that contains the sequence of bytes that you should load, and an NSString that contains the document type that you need to read in. Your method should load the NSData object into the NSDocument’s memory and return TRUE if it’s successful or FALSE if it fails. The NSString argument is useful if your application knows how to load more than one kind of file.

  • For some kinds of applications, it is easier to store a document in a file wrapper that consists of a folder and multiple files. If you want to store the contents of your document in a file wrapper, override these two methods:

    (NSFileWrapper *)fileWrapperRepresentationOfType:(NSString *)type;

    This method should return an NSFileWrapper object that corresponds to the document’s contents.

    (BOOL)loadFileWrapperRepresentation:(NSFileWrapper *)wrapper ofType:(NSString *)type;

    This method loads the current document from the NSFileWrapper wrapper.

  • If you need still more control over the saving and loading of your documents, you can override these four methods:

    (BOOL)writeToFile:(NSString *)fileName ofType:(NSString *)type;
    (BOOL)writeToURL:(NSURL *)url ofType:(NSString *)type;
    (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)type;
    (BOOL)readFromURL:(NSURL *)url ofType:(NSString *)type;

The NSDocument class contains additional methods that you can use to control whether or not backup files are preserved, if you need to access the previous version of a file when you are saving a new version (for example, if the entire document cannot fit in memory) or if your “revert” action needs to have some sort of specific behavior. As with all classes discussed in this book, you can consult the Cocoa documentation for a further explanation of the methods mentioned here, as well as additional methods that we have not described.

To help implement file saving in MathPaper, we will override NSDocument’s dataRepresentationOfType: method in our MathDocument subclass. Our implementation will get a list of all the PaperController objects associated with the current MathDocument document (object), send each of those objects a synchronizeData message, and then return the NSData object that results when an NSArchiver is asked to archive the MathDocument object.

  1. Insert the new code shown here in bold into the dataRepresentationOfType: method in the MathDocument.m file:

    - (NSData *)dataRepresentationOfType:(NSString *)aType
    {    if ([aType isEqualToString:@"MathPaper"]) {
                            
            // Ask our windows to synchronize their data
                                    [ [self windowControllers]
                                        makeObjectsPerformSelector:@selector(synchronizeData)];
                            
            // Encode the data
                                    return [NSArchiver archivedDataWithRootObject:self];
                                }
        return nil;   // Cannot encode
    }

The “list of all the PaperControllers” approach may seem like overkill, because we know that our application has only one PaperController per MathDocument window. Indeed, the second statement could have been written like this:

                  ...
                     // Ask our one window to synchronize its data
                     [[ [self windowControllers] objectAtIndex:0]] synchronizeData];
                  ...

But because the NSDocument architecture returns an NSArray * reference when we send it the windowControllers message, it’s actually easier to write the generalized code that sends the synchronizeData message to every object in the NSArray (even though we know that only a single element is there). With this code, if a second NSWindowController instance is ever added to the MathPaper application — for example, if we should want to have two windows for every MathDocument — we’ll be prepared. See Section 13.7 at the end of the chapter for more on this topic.

Testing the Save Features

After you’ve made the changes, it’s time to test them.

  1. Build and run MathPaper. Save all pertinent files before building.

  2. Create four MathPaper windows and drag them to the four corners of your screen.

  3. Type some equations in each of the four windows.

  4. Choose the MathPaper Save (or Save As) menu command for each window. Because you haven’t given this window a filename yet, both functions do the same thing — bring up the Save panel. Note that the application icon automatically appears in the Save panel.

  5. We’ll save each window with a name such as “upper-left”, “lower-left”, “upper-right”, and “lower-right”, as shown in Figure 13-4. If necessary, click Home in the Save panel to open your Home directory. Then type a filename and click OK (or hit Return) to save the file in your Home directory. The document icon will appear above your chosen filename in your File Viewer, as shown in Figure 13-5.

  6. Quit MathPaper.

Note that each time you save a file, the title of the window bar changes. If you look closely, you’ll also see that each window has the MathPaper document icon in its title bar, as shown in Figure 13-4.

Saving several MathPaper windows

Figure 13-4. Saving several MathPaper windows

The document icon that you’ve created should also show up in the Finder, as it does in Figure 13-5. If the document icon does not show up, log out of your computer and log back in — the Mac OS X Finder doesn’t always recognize new icons when they are manufactured by applications under development. If that doesn’t work, try putting a copy of the MathPaper.app application icon in your Dock.

The NSDocument architecture automatically adds the file extension .matp to the documents that we create. You can use the Finder’s Show Info panel (Command-I) to control whether the extension is shown or displayed on a file-by-file basis.

The MathPaper document icons should show up in the Finder

Figure 13-5. The MathPaper document icons should show up in the Finder

Advanced Save Panel Options

There may be circumstances in which the Save panel that is produced by the NSDocument architecture is not sufficient for what you wish to do. For example, you may wish to have a radio button or a checkbox on the Save panel. Cocoa makes it easy to customize both Save panels and Open panels with your own accessory views. We won’t need such customization here, but this is a good place to provide an overview of how it’s done.

When the NSDocument class (or your subclass) attempts to invoke saveDocument: and no filename is specified, or when the user asks to do a Save As or a Save To operation, the following methods in your NSDocument subclass are called, in this order:

    • (void)runModalSavePanelForSaveOperation:

      • (NSSaveOperationType)saveOperation

        • delegate:(id)delegate

      • didSaveSelector:(SEL)didSaveSelector

        • contextInfo:(void *)contextInfo

  1. (BOOL)prepareSavePanel:(NSSavePanel *)savePanel

    • (void)saveToFile:(NSString *)fileName

      • saveOperation:(NSSaveOperationType)saveOperation

        • delegate:(id)delegate

      • didSaveSelector:(SEL)didSaveSelector

        • contextInfo:(void *)contextInfo

  2. (NSString *)fileTypeFromLastRunSavePanel

So, if you want to have a custom control that specifies the file type for a file to be saved, you can follow this sequence of steps:

  1. Subclass prepareSavePanel: so that an “accessory view” containing the control is inserted into the Save panel.

  2. Subclass fileTypeFromLastRunSavePanel to return the file type that the user chose.

  3. Arrange for your dataRepresentationOfType: method to examine the type that is passed in and save the file according to the specified format. Your method could also examine the settings of other controls on the auxiliary view if they can be used to change the format of the saved file. (For example, the accessory view might be used in a program that saves JPEG files to specify a compression setting.)

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

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