Moon Travel Planner: Handling Files

You’ve now built an application that allows users to plan trips to the moon. Planning how to get somewhere is helpful, but what do tourists do once they get to the moon? To make your application truly useful as a moon travel planner, you should help users plan their itineraries once they’re moon-side. In this section, you will add a new window to your application: the itinerary window, which will display a traveler’s vacation event schedule for the duration of a stay on the moon.

You will also save the contents of the itinerary window in a file, and retrieve the contents of existing itinerary files. If you were creating a fully featured, robust application, you would make the text of the itinerary file editable by the user; however, that would involve creating a text editor. Since we do not show you how to handle text editing in this book, we will use a read-only itinerary file. Using a read-only file, we can still implement the Open and Save As commands. Once your application can handle these commands, you will have implemented most of the file handling code that you would need in order to implement the other standard File menu commands, Save and New. We will note in the chapter the places where you would normally implement these other commands.

Note

To get a quick start on supporting text editing capabilities in your application, read the Multilingual Text Engine documentation in Carbon Help.

To add an itinerary to your Moon Travel application, you will:

  1. Create a new itinerary window.

  2. Define the constants and variables needed to create an itinerary file.

  3. Write the window event handler for the itinerary window.

  4. Write the functions to open an itinerary file.

  5. Write the functions to save an itinerary file.

  6. Write the function to close the itinerary window.

  7. Add code to the main window event handler to open an itinerary.

  8. Build, run, and test the application.

Create a Window to Display the Itinerary

The first step in creating itineraries that your users can actually use is creating an interface for the itinerary. Before we do anything else, we need a window in which we can display the itinerary. By now, you’re an old pro at creating windows. This time, however, we’ll do things a bit differently. In Chapter 8 we created a new window by adding a window to the existing main.nib file. Now we’ll create a new nib file and then put a window in it.

As you write more sophisticated and complex applications, you may find you need a large number of interface resources (document windows, tool palettes, message boxes, and so forth), but the resources don’t need to be available all the time. For example, in an editing application, a user may sometimes need one document window, but at other times needs multiple document windows along with several tool palettes.

Although you can define all interface resources in the main.nib file, it’s more memory efficient to create one or more auxiliary nib files. A good strategy is to limit what you put in the main.nib file to the menu bar and perhaps one window—the minimum number of items that must be open when your application launches. Then create auxiliary nib files for other interface resources.

A document window, such as the itinerary window, is a perfect candidate for an auxiliary nib file. You’ll create a new nib file for a window that you’ll use to display the contents of the itinerary file your users open:

  1. Double-click the main.nib file in the Moon Travel Planner project to activate Interface Builder. (If you are already in Interface Builder, you don’t need to do this.)

  2. Choose New from the File menu.

  3. Select a Carbon window from the Starting Points dialog, as shown in Figure 11.12. When you click New, an untitled nib file opens along with a new window.

    The Starting Points dialog in Interface Builder

    Figure 11-12. The Starting Points dialog in Interface Builder

  4. Choose Save As from the File menu. Name the nib file itinerary.nib. You should save the nib file to the English.lproj folder.

  5. Name the window object Itinerary, as shown in Figure 11.13. In the Instances pane of the itinerary.nib window, double-click the word “Window,” type Itinerary, and press Return. This is the name by which you’ll refer to the window in your code. You’ll use this name to create the window from the nib file.

    Naming the window object Itinerary

    Figure 11-13. Naming the window object Itinerary

  6. Use the Show Info window to enter Itinerary as the window’s title. The title is the text users see in the title bar at the top of the window. With the window active, choose Show Info from the Tools menu, choose Attributes from the pop-up menu at the top of the Info window, type Itinerary in the Title text field, and press Return.

  7. Choose Document from the Window Class pop-up menu.

  8. Set the window’s controls. In the Controls group, make sure Close Box and Collapse Box are the only checkboxes selected.

  9. Set the window’s attributes. In the Attributes group, make sure Standard Handler and Resizable are selected.

  10. Resize the window to 500 by 600 pixels. Choose Size from the pop-up menu at the top of the Show Info window, choose Width/Height from the right Content Rect pop-up menu, and type 500 for width and 600 for height. Then press Return.

  11. Choose Save from the File menu. You’re done with the window. Now you need to add the itinerary.nib file to your project.

  12. Click the Project Builder icon in the Dock to make it active.

  13. Choose Add Files from the Project menu and select the itinerary.nib file.

  14. When the dialog shown in Figure 11.14 opens, click Open, select “Copy into group’s folder,” and click Add.

    Adding the nib file to your project

    Figure 11-14. Adding the nib file to your project

  15. If necessary, in the project window, drag the itinerary.nib file reference to the Resources group.

Define Constants and Variables for Creating Itinerary Files

Before you begin adding the code to your Moon Travel Planner application to open and save itinerary files, you need to define the constants and global variables that you will need later. Copy the code shown in Example 11.1 to the main.c file, after the kPrintInfoPtrProperty constant that you defined in Chapter 9.

Example 11-1. Constants for Opening and Saving Itinerary Files

#define kMTPOpenItineraryCommand     'oPit'
#define kMTPSaveAsItineraryCommand   'sAit'
#define kMTPDocType                  'iTin'
#define kMTPCFStringProperty         'cfst'

Here’s what the constants represent:

  • kMTPOpenItineraryCommand (the Open command). In Chapter 7 you defined the commands for opening and saving itineraries in Interface Builder. To make it easier to refer to the Open and Save commands, you should define constants for the four-character codes that represent the commands.

  • kMTPSaveAsItineraryCommand (the Save As command). Just as with the Open command, it is easier to refer to the four-character code for the Save As command if we define a constant for it.

  • kMTPDocType. The file type of the documents that the Moon Travel Planner application creates. You will learn more about file types and how to provide them in Chapter 13.

  • kMTPCFStringProperty. A constant that you will use to store and retrieve information about the itinerary text, as a “property” of the itinerary window. You stored printing information in a similar manner in Chapter 9.

Finally, let’s define a global variable. Paste the following line into the main.c file after the declaration for MTPDocumentPrintInfoPtr:

NavEventUPP gEventProc;

You will use this variable to hold a pointer to the navigation event handler that you will define in Section 11.2.4.

Write the Itinerary Window Event Handler

You’ve already created the definition for the itinerary window’s appearance using Interface Builder; now you need to implement the itinerary window’s behavior. For the itinerary window, we will define an event handler for the window events (events of class kEventClassWindow) and command events (events of class kEventClassCommand) that we wish to handle. Back in Chapter 7 when you created the Moon Travel Planner application’s menu bar, you selected the menu commands for opening, saving, and closing an itinerary, and you assigned them four-character code constants to identify them. In this section, you will write the MTPItineraryWindowEventHandler, which responds to those commands.

First, let’s declare the itinerary window event handler. Copy the following lines into the main.c file, after the declaration for the MTPDoPrintLoop function:

OSStatus MTPItineraryWindowEventHandler (EventHandlerCallRef handlerRef,
                            EventRef event, void *userData);

Now implement it. Copy the MTPItineraryWindowEventHandler function in Example 11.2 into the main.c file, after the body of the MTPDoPrintLoop function:

Example 11-2. The Event Handler for the Itinerary Window

OSStatus MTPItineraryWindowEventHandler (EventHandlerCallRef handlerRef,
                                            EventRef event, void *userData)
{
    OSStatus err;
    HICommandcommand;
    UInt32 eventKind;
    UInt32 eventClass;
    
    eventClass = GetEventClass(event);                                     //1
    eventKind = GetEventKind (event);                                      //2
    err = eventNotHandledErr;                                              //3
    if ((eventClass == kEventClassWindow) && (eventKind == kEventWindowClose)) 
        {
            SelectWindow (gMainWindow);                                    //4
            err = MTPDoCloseItinerary((WindowRef)userData);                //5
        }  
     else if (eventClass == kEventClassCommand)
        {
            GetEventParameter (event, kEventParamDirectObject, typeHICommand,
                    NULL, sizeof (HICommand), NULL, &command);             //6
            switch (command.commandID) 
            {
            case kMTPOpenItineraryCommand:
                 err =  MTPDoOpenItinerary((WindowRef)userData);           //7
                 break;
            case kMTPSaveAsItineraryCommand:
                 err = MTPDoSaveItineraryAs((WindowRef)userData);          //8
                 break;
            case kMTPCloseCommand:
                 SelectWindow (gMainWindow);
                 err = MTPDoCloseItinerary((WindowRef)userData);           //9
                 break;
            }
        }
    return err;
}

Here is what the itinerary window command event handler does:

  1. The Carbon Event Manager function GetEventClass returns the class of the event that triggered the call to the itinerary window event handler.

  2. The Carbon Event Manager function GetEventKind returns the particular type of event that triggered the call to the itinerary window event handler.

  3. The event handler initializes the error message to eventNotHandledErr. If the handler does not handle the event that was passed to it, the Carbon Event Manager will handle the event if it can.

  4. If the event was a window close event—that is, if the user clicked the itinerary window’s close button—the Window Manager function SelectWindow makes the Moon Travel Planner main window the active window.

  5. If the event was a window close event, the MTPDoCloseItinerary function, which you will write in Section 11.2.6 closes the itinerary window.

  6. If the event was a command event, the Carbon Event Manager function GetEventParameter retrieves the command that triggered the call to the handler.

  7. If the command was the Open command, the handler calls the function MTPDoOpenItinerary to open an itinerary from an existing file. You will write the MTPDoOpenItinerary function in Section 11.2.4. The MTPDoOpenItinerary function, along with the other Moon Travel Planner functions called by the event handler, takes a window reference (WindowRef) as its argument. When you install the window event handler in Section 11.2.4.4 you will specify a pointer to the itinerary window as an argument to the InstallWindowEventHandler function. The Carbon Event Manager passes this reference back to the event handler in the userData parameter when it calls the handler.

  8. If the command was the Save As command, the handler calls the function MTPDoSaveItineraryAs, which displays a Save dialog that allows the user to specify a new name and location for the file, and then saves the file. You will write this function in Section 11.2.5.

  9. If the command was the Close command, the handler makes the same function calls as it did for the window close event in steps 4 and 5. It first makes the Moon Travel Planner main window the active window by calling SelectWindow, and then calls the MTPDoCloseItinerary function to close the window.

Open an Itinerary File

When a user selects the Open command from the File menu in the Moon Travel Planner application, the application needs to provide an interface for the user to select the file to open and then to open the itinerary file chosen by the user. To open a previously created itinerary file, you need to do six things:

  1. Configure and display an Open dialog that lets the user locate the file to open.

  2. Respond to the user’s action.

  3. Retrieve the file to open.

  4. Create a new itinerary window in which to display the contents of the file.

  5. Read the data in from the itinerary file.

  6. Display the contents of the itinerary file in the itinerary window.

Configure and display an Open dialog

The first step in opening the itinerary file is to create and display a dialog that allows the user to choose the itinerary file to open. In Section 11.2.3 you wrote your itinerary window event handler to respond to the Open Itinerary command by calling the MTPDoOpenItinerary function. Now, you will implement that function.

First, let’s declare the function. Copy the following lines into the main.c file, after the declaration for the MTPItineraryWindowEventHandler function:

OSStatus MTPDoOpenItinerary();

Now implement the function. Copy the MTPDoOpenItinerary function in Example 11.3 into the main.c file.

Example 11-3. A Function that Creates and Displays an Open Dialog

OSStatus MTPDoOpenItinerary()
{
    OSStatus err = noErr;
    NavDialogRef theOpenDialog;
    NavDialogCreationOptions dialogOptions;
    if (( err = NavGetDefaultDialogCreationOptions( 
                          &dialogOptions)) == noErr ) {                    //1
    dialogOptions.modality = kWindowModalityAppModal;                      //2
    gEventProc = NewNavEventUPP( MTPNavEventCallback );                    //3
    if ((err =  NavCreateGetFileDialog( &dialogOptions, NULL, 
             gEventProc, NULL, 
             NULL, NULL, &theOpenDialog )) == noErr) {                     //4
            if ( theOpenDialog != NULL ) {
                if (( err = NavDialogRun( theOpenDialog )) != noErr) {     //5 
                        NavDialogDispose( theOpenDialog );                 //6
                        DisposeNavEventUPP( gEventProc );
                    }
                }
            }
        }
    return err;
}

Here’s what the MTPDoOpenItinerary function does:

  1. The Navigation Services function NavGetDefaultDialogCreationOptions fills out a NavDialogCreationOptions structure with default values for configuring the Open dialog. The dialog options structure tells Navigation Services how to set up such features as the size, location, and modality of the Open dialog.

  2. The function specifies that the dialog should be application modal by setting the modality field of the NavDialogCreationOptions structure to kWindowModalityAppModal. An application modal dialog takes over the application until the user responds to the dialog. For more information on modality, see Inside Mac OS X: Aqua Human Interface Guidelines.

  3. The Navigation Services function NewNavEventUPP creates a pointer to the Moon Travel Planner navigation event handler, MTPNavEventCallback. When the user dismisses the Open dialog, Navigation Services calls the navigation event handler with information about the event that triggered the call. You will write the navigation event handler in Section 11.2.4.2.

  4. The Navigation Services function NavCreateGetFileDialog creates the Open dialog, with the characteristics specified by the NavDialogCreationOptions structure. Passing the pointer to the navigation event handler in the third parameter tells Navigation Services which callback function to call when the user takes an action.

  5. The Navigation Services function NavDialogRun displays the Open dialog to the user. If there is no error in displaying the dialog, the navigation event handler performs the next step in opening the itinerary file.

  6. If there was an error displaying the Open dialog, the Navigation Services function NavDialogDispose disposes of the dialog reference, and the Navigation Services function DisposeNavEventUPP disposes of the pointer to the navigation event callback.

Respond to the user’s action

In the previous section, you implemented the MTPDoOpenItinerary function, which creates the dialog that allows the user to select which file to open. When displaying a dialog using Navigation Services, the NavDialogRun function may return before the user has responded to the dialog. How, then, do you know when the user has taken an action? Furthermore, how do you know what that action is, once it has been taken? The answer: through a navigation event handler.

As we discussed in Section 11.1.2.1 Navigation Services calls your navigation event handler when a navigation event occurs. For the most part, the type of event that you will be interested in is a user action event, although there are other types of events that you can choose to handle. However, the Moon Travel Planner application only has to handle the user action event. When you receive the user action event, you can retrieve the user’s response to the dialog and perform whatever operations are appropriate for the user’s action.

In this section, you’ll write the MTPNavEventCallback function and handle the user action for opening a file. When you created the Open dialog, you specified the navigation event handler, MTPNavEventCallback. When the user responds to the Open dialog, by clicking either the Cancel or Open button, Navigation Services calls MTPNavEventCallback with information about the user’s response. You can then proceed with opening the file, if the user has indeed selected a file to open.

First, let’s declare the navigation event handler. Copy the following lines into the main.c file, after the declaration for the MTPDoOpenItinerary function:

pascal void MTPNavEventCallback(
NavEventCallbackMessage callBackSelector, 
NavCBRecPtr callBackParms, void* callBackUD);

To implement the MTPNavEventCallback function, copy the MTPNavEventCallback function in Example 11.4 into the main.c file.

Example 11-4. The Navigation Event Handler

pascal void MTPNavEventCallback( NavEventCallbackMessage callBackSelector, 
                                          NavCBRecPtr callBackParms, void* callBackUD)
{
    OSStatus err = noErr;    
    switch (callBackSelector) {
        case kNavCBUserAction:  {                                         //1
            NavReplyRecord reply;
            NavUserAction userAction = 0;
            if ((err = NavDialogGetReply (callBackParms->context, 
                                         &reply )) == noErr ) {           //2
                userAction = NavDialogGetUserAction (
                                      callBackParms->context);            //3
                switch (userAction) {
                    case kNavUserActionOpen: {
                        MTPOpenTheFile (&reply);                          //4
                        break;
                    }
                }         
                err = NavDisposeReply (&reply);                           //5
            }
            break;
        }
        case kNavCBTerminate: {                                           //6
            NavDialogDispose (callBackParms->context);                    //7
            DisposeNavEventUPP (gEventProc);                              //8
            break;
        }
    }
}

The callback selector passed as an argument to your navigation event handler tells it what type of event triggered the call. Here’s what the navigation event handler does:

  1. The function tests for a user action event. If the event was a user action, you should handle this event, because information about the user’s response to the dialog is described by this type of event.

  2. The Navigation Services function NavGetDialogReply retrieves information about the user’s response in the form of a NavReplyRecord structure. If the dialog is an Open dialog, the reply record should hold the information necessary to identify the file to open. If it was a Save dialog, the reply record would contain the information necessary to identify the location where the file should be saved and the name that the file should be given.

  3. The Navigation Services function NavDialogGetUserAction determines exactly what type of action the user took. The user action that you should respond to is the action represented by the kNavUserActionOpen constant. If this is the action returned by Navigation Services, then you can go ahead and open the file.

  4. If the user chose a file to open (that is, if the last step returned the kNavUserActionOpen constant), then the callback function calls the MTPOpenTheFile function to finish opening the file.

  5. The Navigation Services function NavDisposeReply disposes of the reply record after the function MTPOpenTheFile has exited.

  6. If the event that triggered the call to the callback function was kNavCBTerminate, then the dialog has been terminated, and the callback should tidy up.

  7. The function NavDisposeDialog disposes of the open dialog.

  8. The function DisposeNavEventUPP releases the memory associated with the pointer to the navigation event callback function.

Retrieve the file to open

In the last section, you wrote the navigation event handler, which retrieves the user’s reply from Navigation Services. If the user has indeed selected a file to open, the handler calls the MTPOpenTheFile function to finish opening the itinerary file. In this section, you will implement the MTPOpenTheFile function.

Declare the MTPOpenTheFile function. Copy the following lines into the main.c file, after the declaration for the MTPNavEventCallback function:

void  MTPOpenTheFile(NavReplyRecord *reply);

Now implement the function. Copy the MTPOpenTheFile function in Example 11.5 into the main.c file.

Example 11-5. A Function that Retrieves the File to Open

void  MTPOpenTheFile(NavReplyRecord *reply)
{     
    AEDesc actualDesc;
    FSRef fileToOpen;
    HFSUniStr255 theFileName;
    CFStringRef fileNameCFString; 
    WindowRef newWindow;
    OSStatus err;

    if ((err = AECoerceDesc(&reply->selection,
                    typeFSRef, &actualDesc)) == noErr)                    //1
     {
        if (&actualDesc != NULL) {
        if ((err = AEGetDescData (&actualDesc,
                    (void *)(&fileToOpen), 
                    sizeof(FSRef)) == noErr))                             //2
             {
                err = FSGetCatalogInfo ( &fileToOpen, kFSCatInfoNone,
                            NULL, &theFileName,
                             NULL, NULL );                                //3
            fileNameCFString = CFStringCreateWithCharacters (NULL, 
                        theFileName.unicode,
                        theFileName.length );                             //4
            newWindow = MTPDoNewItinerary( fileNameCFString);             //5
            ShowWindow(newWindow);                                        //6
            err = MTPReadFile(&fileToOpen, newWindow);                    //7
            }
        }
        AEDisposeDesc(&actualDesc);                                       //8
    }
}

Here’s what the MTPOpenTheFile function does:

  1. The Apple Event Manager function AECoersceDesc coerces the data returned by Navigation Services in the NavReplyRecord structure to validate that it is indeed an FSRef.

  2. If the coercion succeeds, the Apple Event Manager function AEGetDescData retrieves the FSRef identifying the file to open.

  3. The File Manager function FSGetCatalogInfo returns the name of the file. You will use the filename to set the title of the itinerary window. The FSCatalogInfo call can return much more information about the file, such as its creation date, size, permissions, and so forth. However, all you need for the Moon Travel Planner is the name, so you specify kFSCatInfoNone to indicate that you do not wish to retrieve any additional information.

  4. The Core Foundation String Services function CFStringCreateWithCharacters converts the Unicode filename returned by the File Manager into a CFString representation. The File Manager uses the HFSUniStr255 data type to store Unicode filenames; the function that you will use in the next section to set the window title expects a CFStringRef data type.

  5. The MTPDoNewItinerary function creates a window in which to display the itinerary and returns that window. You will write the MTPDoNewItineray function in the next section.

  6. The Window Manager function ShowWindow shows the itinerary window, which is created to be initially hidden.

  7. The MTPReadFile function reads in the itinerary data from the file. You will write the MTPReadFile function in Section 11.2.4.5.

  8. The Apple Event Manager function AEDisposeDesc disposes of the Apple Event descriptor that you used to retrieve the FSRef for the file to open.

Create a new itinerary window

The last function that you wrote, MTPOpenTheFile, gets all of the information that you need in order to identify the file to open. You’re still not ready to read the itinerary data from the file, however. Before you can do that, you need to create the itinerary window in which you will display the information that you read from the itinerary file. It might seem as if you’ve already done just that. However, in Section 11.2.1 you defined only the appearance and characteristics of the itinerary window. In this section, you’ll create an actual instance of an itinerary window to hold the itinerary text. The MTPDoNewItinerary function creates and initializes a new itinerary window.

First, declare the MTPDoNewItinerary function. Copy the following lines into the main.c file, after the declaration for the MTPOpenTheFile function:

WindowRef MTPDoNewItinerary(CFStringRef inName);

Now implement the function. Copy the MTPDoNewItinerary function in Example 11.6 into the main.c file.

Example 11-6. A function that creates a new itinerary window

WindowRef MTPDoNewItinerary(CFStringRef inName)
{ 
    IBNibRef itineraryNib;
    EventTypeSpec itinerarySpec[2] = {
                {kEventClassCommand,kEventCommandProcess}, 
                {kEventClassWindow, kEventWindowClose}};                  //1

    OSStatus err;
    WindowRef theWindow;

    err = CreateNibReference(CFSTR("itinerary"), &itineraryNib);          //2
    require_noerr( err, CantGetNibRef );  
    err = CreateWindowFromNib(itineraryNib,
                 CFSTR("Itinerary"), &theWindow);                         //3
    require_noerr( err, CantCreateWindow );
    DisposeNibReference(itineraryNib);                                    //4
    err = InstallWindowEventHandler (theWindow,
                 NewEventHandlerUPP (MTPItineraryWindowEventHandler),
                 2,
                 itinerarySpec,
                 (void *) theWindow, NULL);                               //5
    err =  SetWindowTitleWithCFString ( theWindow, inName);               //6
    return theWindow;                                                     //7

    CantCreateWindow:
    CantGetNibRef:
        return NULL;
}

Here’s what the MTPDoNewItinerary function does:

  1. MTPDoNewItinerary initializes an event specification, identifying the events that the itinerary window handles. You use this specification to register the itinerary’s window event handler in step 5. The itinerary window handles two types of events: command events and window close events.

  2. The function CreateNibReference finds and opens the itinerary.nib file that you created in Section 11.2.1.

  3. The function CreateWindowFromNib creates the itinerary window, using the definition in the itinerary.nib file.

  4. The function DisposeNibReference disposes of the nib reference, which we no longer need now that the window has been created.

  5. The Carbon Event Manager function InstallWindowEventHandler registers the itinerary event handler that you wrote in Section 11.2.3. You pass a reference to the newly created itinerary window to the InstallWindowEventHandler function. When the Carbon Event Manager calls MTPItineraryWindowEventHandler, the Carbon Event Manager passes the reference to the itinerary window back to your application.

  6. The Window Manager function SetWindowTitleWithCFString sets the title of the itinerary window to the name of the file, which is passed as an argument in the inName parameter.

  7. The MTPDoNewItinerary function returns a reference to the new window that it creates.

Read the itinerary file

Finally, now that you have an itinerary window in which to display the itinerary text, you are ready to read the data from the itinerary file on disk. The MTPReadFile function opens the itinerary file and reads data from the itinerary’s data fork.

First, declare the MTPReadFile function. Copy the following lines into the main.c file, after the declaration for the MTPDoNewItinerary function:

OSStatus MTPReadFile(FSRef *inFSRef, WindowRef theWindow);

Now implement the function. Copy the MTPReadFile function in Example 11.7 into the main.c file.

Example 11-7. A Function that Reads Data from an Itinerary File

OSStatus MTPReadFile(FSRef *inFSRef, WindowRef theWindow)
{ 
    ByteCount count, actualCount;
    SInt16 forkRefNum=0;
    UniChar buffer[256];
    OSStatus err = noErr;
    CFMutableStringRef theText;
    HFSUniStr255 forkName;

    theText = CFStringCreateMutable (NULL, 0);                            //1
    err = FSGetDataForkName (&forkName);                                  //2
    err = FSOpenFork (inFSRef, forkName.length,
                     forkName.unicode, fsRdPerm,  &forkRefNum);           //3
    if (err == noErr) {
        do {
            count = 256 * sizeof(UniChar);                                //4
            err = FSReadFork (forkRefNum, fsAtMark,
                            0, count, &buffer, &actualCount);             //5
            actualCount/= sizeof (UniChar);                               //6
            CFStringAppendCharacters (theText, buffer, actualCount);      //7
        } while (err == noErr);
        if (err == eofErr) {                                              //8
            err = noErr;
            MTPDisplayFile (theText, theWindow);                          //9
            err = SetWindowProperty (theWindow,
                        kMTPApplicationSignature, 
                        kMTPCFStringProperty,
                        sizeof (CFStringRef),
                        (void *)&theText);                                //10 
            }
        err = FSCloseFork(forkRefNum);                                    //11
        }
    return err;
}

Here’s what the MTPReadFile function does:

  1. The Core Foundation String Services function CFStringCreateMutable creates a new, changeable, CFString object. You will use this CFString object to hold the text that you read from the itinerary file.

  2. The File Manager function FSGetDataForkName returns the name of the data fork of the file where the itinerary text is stored.

  3. The File Manager function FSOpenFork opens the data fork of the itinerary file. We request read permission by specifying the fsRdPerm constant. The FSOpenFork function returns a fork reference number that identifies the access path to the itinerary file that the File Manager has created. You will use this fork reference number to read the file.

  4. At the beginning of the do loop, the MTPReadFile function initializes the count variable to the size of 256 Unicode characters, the size of the buffer that it has allocated to receive the data read in by the File Manager.

  5. While data still remains to be read from the file, the File Manager function FSReadFork reads data from the file into the buffer that you have provided. Specifying fsAtMark to the FSReadFork function indicates that the read operation should begin at the current position of the file mark. Since the file mark gets moved with each read we make, on the next call to FSReadFork, we pick up right where we left off. When you opened the file with the FSOpenFork function, the File Manager automatically set the file mark to the start of the file. Therefore, when you read the file, you automatically start reading at the beginning. If you were accessing the file via an access path that had not just been opened or if you wished to start reading from elsewhere in the file, you would call the File Manager function FSSetForkPosition to ensure that you began reading the file from the intended position.

  6. The MTPReadFile function divides the number of total bytes read by the size of a Unicode character to get the number of Unicode characters in the buffer.

  7. The Core Foundation String Services function CFStringAppendCharacters appends the text that the File Manager read from the file to the CFString object.

    Note

    Normally, you would not read in the data from the file and store it in a string. Instead, you would typically associate it with some sort of document record that contains the information that your application needs to create, track, and manipulate a document in your application. In this particular case, the itinerary document would be a text editing document, using Multilingual Text Engine (MLTE), to handle text editing. You would then associate the file and the file data with the “text object” that represents a simple MLTE document. However, text editing is beyond the scope of this tutorial, and we use the CFString object simply to allow us to illustrate the tasks of saving data to a file and retrieving data from a file.

  8. The MTPReadFile function tests for the end-of-file error. When the FSReadFork function returns an end-of-file error (represented by the eofErr constant), all of the data has been read from the file and the loop terminates. If the loop terminates because of another error, the MTPReadFile function exits.

  9. The MTPDisplayFile function displays the text that you’ve read in from the file in the itinerary window. You will write MTPDisplayFile next in Section 11.2.4.6.

  10. The Window Manager function SetWindowProperty associates the CFString holding the itinerary text with the itinerary window.

  11. The File Manager function FSCloseFork closes the data fork of the itinerary file.

Display the itinerary text

Your last task in opening a file is to display the contents of the file that you’ve just read. To do this, you will write the MTPDisplayFile function.

First, declare the MTPDisplayFile function. Copy the following line into the main.c file, after the declaration for the MTPReadFile function:

void MTPDisplayFile(CFStringRef stringToDisplay, WindowRef inWindow);

Now implement the function. Copy the MTPDisplayFile function in Example 11.8 into the main.c file.

Example 11-8. A Function that Displays the Contents of an Itinerary File

void MTPDisplayFile (CFStringRef stringToDisplay,
                         WindowRef inWindow)
{
  Rect bounds;
  GrafPtr oldPort;
  
  GetPort (&oldPort);                                                     //1
  SetPortWindowPort (inWindow);                                           //2
  EraseRect (GetWindowPortBounds (inWindow, &bounds));                    //3
  TXNDrawCFStringTextBox (stringToDisplay, &bounds, NULL, NULL);          //4
  SetPort (oldPort);                                                      //5
}

Here’s what the MTPDisplayFile function does:

  1. The QuickDraw function GetPort gets the current graphics port; you will use this later on to restore the graphics port.

  2. The Window Manager function SetPortWindowPort sets the graphics port to the itinerary window’s port.

  3. The QuickDraw function EraseRect makes sure that the area bounded by the window is empty. The Window Manager function GetWindowPortBounds returns the boundaries of the itinerary window, which the EraseRect function uses as the bounds of the area that it erases.

  4. The Multilingual Text Engine (MLTE) function TXNDrawCFStringTextBox draws the contents of the CFString in which we’ve stored the itinerary text into the window. This is static text. Note that if you were displaying the contents of a real user file, you should allow the user to view and change the document, so you would have to display the text differently. For editable text documents, MLTE does most of the work of displaying the text onscreen for you. For more information, see the Multilingual Text Engine documentation in Carbon Help.

  5. The QuickDraw function SetPort restores the current graphics port to its original state.

Save the Itinerary

A user who creates an itinerary for the moon trip may wish to save it as a text file. When the user invokes the Save or Save As command, either by selecting them in the File menu or by using the Command-S keyboard shortcut, your application needs to save the contents of the itinerary to disk. In this section, we will implement the Save As command, to save a new file. To save a new file you must:

  1. Configure and display a Save dialog that lets the user specify the name and location for the file to save.

  2. Respond to the user’s action.

  3. Create the Save file to hold the itinerary text.

  4. Write the itinerary contents to the file.

Once you’ve implemented the Save As command for new files, implementing the Save command is relatively simple. When a user selects the Save command from the File menu, or types Command-S, your application should check to see if a file exists for the current document. If not, it’s the same as a Save As operation, and you should create a new file. If a file already exists on disk for the document, you can skip right to step 4 in the list above, writing the data to disk. Because we are not implementing an editable itinerary window in this tutorial, there is little point in implementing the Save command (there would be no changes to save!). However, as described here, once you’ve implemented the Save As command, adding in the handler for the Save command is not difficult.

Configure and display a Save dialog

When the user selects the Save As command or when the user tries to save an itinerary for which a file does not yet exist, you must provide an interface to the user that allows her to choose the location at which to store the file and the name that she wishes to give to the new file. Only then can you proceed with the save operation. In this section, you will write the code to display a Save dialog.

First, declare the MTPDoSaveItineraryAs function. Copy the following lines into the main.c file, after the declaration for the MTPDisplayFile function:

OSStatus MTPDoSaveItineraryAs(WindowRef theItineraryWindow);

Now implement the function. Copy the MTPDoSaveItineraryAs function in Example 11.9 into the main.c file.

Example 11-9. A Function that Creates and Displays a Save Dialog

OSStatus MTPDoSaveItineraryAs (WindowRef theItineraryWindow)
{
    OSStatus err = noErr;
    NavDialogRef theSaveDialog;
    NavDialogCreationOptions dialogOptions;
    if (( err = NavGetDefaultDialogCreationOptions
                      (&dialogOptions )) == noErr ) {                     //1
             err = CopyWindowTitleAsCFString( theItineraryWindow,
                         &dialogOptions.saveFileName );                   //2
             dialogOptions.parentWindow = theItineraryWindow;             //3
             dialogOptions.modality = kWindowModalityWindowModal;         //4
             gEventProc = NewNavEventUPP( MTPNavEventCallback );          //5
             if ((err = NavCreatePutFileDialog(&dialogOptions,
                                  kMTPDocType, 
                                  kMTPApplicationSignature,
                                  gEventProc,
                                  (void *)(theItineraryWindow),
                                  &theSaveDialog)) == noErr) {            //6
                  if ( theSaveDialog != NULL ) {
                         if (( err = NavDialogRun
                                 (theSaveDialog)) != noErr) {             //7
                             NavDialogDispose( theSaveDialog );           //8
                             DisposeNavEventUPP( gEventProc );            //9
                             }
                     }
             }
            if ( dialogOptions.saveFileName != NULL )
                    CFRelease( dialogOptions.saveFileName );              //10
     }
    return err;
}

Here’s what the MTPDoSaveItineraryAs function does:

  1. The Navigation Services function NavGetDefaultDialogCreationOptions fills out a NavDialogCreationOptions structure with standard default values. The dialog options structure tells Navigation Services how to set up such features as the size, location, and modality of the Save dialog.

  2. The function CopyWindowTitleAsCFString retrieves the itinerary window’s title and assigns it to be the default filename that appears in the Save dialog.

  3. The MTPDoSaveItineraryAs function sets the itinerary window as the parent window of the Save dialog; this makes the dialog a sheet belonging to the itinerary window. In the Aqua user interface, sheets are dialogs that belong to a particular window and appear to emerge from that window to emphasize the connection between the dialog and the parent window. Save dialogs should be sheets. The Save dialog is only relevant to the window that received the Save command, and the direct correlation between the window to be saved and the Save dialog is made clear by the visual presentation of the dialog.

  4. The MTPDoSaveItineraryAs function sets the modality of the dialog creation options to kWindowModalityWindowModal to make the dialog a sheet. A sheet is a window modal dialog, meaning it prevents all further action upon the window until the user addresses the dialog. The sheet does not, however, interfere with other documents within the same application. Window modal is not the default value in the dialog creation options structure, so you must specify the modality before creating the dialog.

  5. The Navigation Services function NewNavEventUPP allocates a pointer to the navigation event handler that you wrote in Section 11.2.4.2. In the next section, you will add code to this handler to handle saving. This navigation event handler is called by Navigation Services when the user responds to the Save dialog.

  6. The Navigation Services function NavCreatePutFileDialog creates the Save dialog. Passing the pointer to the navigation event handler tells Navigation Services to call this function when the user responds to the dialog. When calling NavCreatePutFileDialog, pass a pointer to the itinerary window in the callBackUD parameter. When the dialog is dismissed, Navigation Services passes this window reference back to your navigation event handler, so that you may use it in further calls.

  7. The Navigation Services function NavDialogRun displays the Save dialog.

  8. If there was an error displaying the dialog, the NavDisposeDialog function disposes of the Save dialog.

  9. Also in case of an error displaying the dialog, the DisposeNavEventUPP function disposes of the pointer to the save event callback function.

  10. When the NavDialogRun function returns, the MTPDoSaveItineraryAs function checks for a valid CFString in the saveFileName field of the dialog creation options structure and, if it is valid, releases the CFString.

Respond to the user’s action

In the previous section, you created and displayed a Save dialog to allow the user to specify the name and location of the new file. Just as in Section 11.2.4 you do not know when the user has responded to the dialog, or what that response is, until Navigation Services calls your navigation event handler. In this section, you will add a few lines of code to the event handler that you wrote in the section “Open an Itinerary File,” so that the navigation event handler responds to save as well as open events.

Modify the second switch statement in the MTPNavEventCallback function so it now looks like the code shown in Example 11.10.

Example 11-10. Navigation Event Handler Switch Statement After Adding “Save As” Code

switch (userAction) {
        case kNavUserActionOpen: {
                MTPOpenTheFile (&reply);
                break;
             }
        case kNavUserActionSaveAs: {
                err = MTPDoFSRefSave ((WindowRef)(callBackUD), &reply );
                break;
            }
}

The navigation event handler works exactly as it did before, except now it handles saving in addition to opening. The new lines of code state that if the user action that triggers the call to the navigation event handler is a Save As action, the navigation event handler calls the Moon Travel Planner function MTPDoFSRefSave to save the file. You will write the MTPDoFSRefSave function in the next section.

Create the save file

You have now created and displayed a dialog for the save operation and retrieved the user response. Your navigation event handler, which you created in Section 11.2.5.2, called the function MTPDoFSRefSave to continue the save operation. The MTPDoFSRefSave function creates the new file from the information that your navigation event handler retrieved from Navigation Services and then prepares the file for saving.

Note

Normally, if you are implementing the Save As command, you would open the newly created file and display it in the user’s current document window. To keep things simple in this book, we’ve chosen to omit that part.

First, declare the MTPDoFSRefSave function. Copy the following line into the main.c file, after the declaration for the MTPDoSaveItineraryAs function:

OSErr MTPDoFSRefSave (WindowRef  theItineraryWindow, NavReplyRecord* reply );

Now implement the function. Copy the MTPDoFSRefSave function in Example 11.11 into the main.c file.

Example 11-11. A Function that Creates a Save File

OSErr MTPDoFSRefSave (WindowRef theItineraryWindow, NavReplyRecord* reply )
{
    OSErr   err = noErr;
    FSRef   fileRefParent;
    AEDesc  actualDesc;
	
    if ((err = AECoerceDesc (&reply->selection,
                    typeFSRef, &actualDesc )) == noErr) {                 //1
        if ((err = AEGetDescData( &actualDesc, (void *)&fileRefParent,
                         sizeof( FSRef ) )) == noErr ) {                  //2
            UniChar* nameBuffer = NULL;
            UniCharCount sourceLength = 0;
            sourceLength = (UniCharCount) CFStringGetLength(
                         reply->saveFileName );                           //3
            nameBuffer = (UniChar*)NewPtr (sourceLength *2);              //4
            CFStringGetCharacters ( reply->saveFileName,
                        CFRangeMake( 0, sourceLength),
                        &nameBuffer[0] );                                 //5
            if (nameBuffer != NULL) {
                if ( reply->replacing ) {
                    FSRef fileToDelete;
                    if ((err = FSMakeFSRefUnicode (&fileRefParent,
                            sourceLength, nameBuffer,
                            kTextEncodingUnicodeDefault,
                            &fileToDelete )) == noErr) {                  //6
                        err = FSDeleteObject( &fileToDelete );            //7
                    }
                }
                    if ( err == noErr ) {
                        FSRef  newFSRef;
                        FileInfo *fileInfo;
                        FSCatalogInfo catalogInfo;
                        fileInfo = (FileInfo *)
                                &catalogInfo.finderInfo[0];               //8
                        BlockZero(fileInfo, sizeof(FileInfo));            //9
                        fileInfo->fileType = kMTPDocType;                 //10
                        fileInfo->fileCreator =
                                       kMTPApplicationSignature;          //11
                        if ((err = FSCreateFileUnicode (&fileRefParent, 
                                    sourceLength, nameBuffer,
                                    kFSCatInfoFinderInfo,
                                    &catalogInfo, &newFSRef,
                                    NULL)) == noErr) {                    //12
                           MTPWriteFile (&newFSRef,
                               theItineraryWindow);                       //13
                        }
                    }
            }       
            DisposePtr ((Ptr)nameBuffer );                                //14
        }
        AEDisposeDesc( &actualDesc );                                     //15
    }
    return err;
}

Here’s what the MTPDoFSRefSave function does:

  1. The Apple Event Manager function AECoerceDesc checks for a valid FSRef by coercing the Apple Event descriptor returned by Navigation Services in the reply record.

  2. If the coercion succeeds, the Apple Event Manager function AEGetDescData extracts the FSRef from the descriptor. This FSRef does not describe the new file itself, because that file does not yet exist. Rather, the FSRef returned in the reply record describes the parent of the new file; that is, it describes the directory in which the new file will reside.

  3. The Core Foundation String Services function CFStringGetLength gets the length of the CFString returned by Navigation Services as the filename of the new file.

  4. The NewPtr function allocates storage space for a buffer into which the filename will be read.

  5. The Core Foundation String Services function CFStringGetCharacters retrieves the name specified by the user for the new file. The name is read from the CFString into a buffer of Unicode characters, which is the representation that the File Manager expects.

  6. If the user has chosen to replace the existing file, the File Manager function FSMakeFSRefUnicode makes an FSRef for the existing file. The user has the option of replacing an existing file. In that case, the application should delete the old file before saving the new one. FSMakeFSRefUnicode identifies the file to delete using the FSRef of the file’s parent directory and the filename that you’ve retrieved.

  7. If the user has chosen to replace an existing file, the File Manager function FSDeleteObject deletes the old file.

  8. The function sets the pointer to the FileInfo structure to the finderInfo field of the catalogInfo structure. The finderInfo field of the catalogInfo structure contains information that the Finder uses to identify the file. When we create the file, we will pass this information to the FSCreateFileUnicode function, so that the file is created with the appropriate Finder information. Casting the finderInfo field to a FileInfo structure makes it easier to fill out the finder information that we wish to set.

  9. The BlockZero function initializes all of the fields of the FileInfo structure to zero.

  10. The function sets the file type of the new file to the Moon Travel Planner application’s file type.

  11. The function sets the file creator of the new file to the Moon Travel Planner application signature.

  12. The File Manager function FSCreateFileUnicode creates a new file for the itinerary. The file is specified by the name and the identity of the parent directory. The File Manager returns an FSRef for the new file. When you create the file, you pass to the FSCreateFileUnicode function, the FSCatalogInfo structure containing the information that identifies your file to the Finder. You also pass the kFSCatInfoFinderInfo constant to notify the File Manager that it should use this information to set the Finder information for the new file.

  13. The MTPWriteFile function opens the file and saves the itinerary data. You will implement this function in Section 11.2.5.4.

  14. The DisposePtr function disposes of the storage allocated to the filename buffer.

  15. The Apple Event Manager function AEDisposeDesc disposes of the event descriptor used to retrieve the FSRef from the NavReplyRecord structure.

Write the save data

The final step in saving a file is writing the data to disk. The MTPWriteFile function opens the file for writing and calls FSWriteFork to write the itinerary data to disk.

First, declare the MTPWriteFile function. Copy the following lines into the main.c file, after the declaration for the MTPDoFSRefSave function:

OOSStatus MTPWriteFile (FSRef *inRef, WindowRef theItineraryWindow);

Now implement the function. Copy the MTPWriteFile function in Example 11.12 into the main.c file.

Example 11-12. A Function that Writes the Itinerary Data to Disk

OSStatus MTPWriteFile (FSRef *inRef, WindowRef theItineraryWindow)
{
    HFSUniStr255 forkName;
    SInt16 forkRefNum;
    CFIndex length;
    CFStringRef theString;
    OSStatus err;
    UniChar * buffer;

    err = GetWindowProperty (theItineraryWindow,
                     kMTPApplicationSignature, 
                     kMTPCFStringProperty,
                     sizeof(CFStringRef), NULL,
                     (void *)&theString);                                 //1
    length = CFStringGetLength(theString);                                //2
    buffer = (UniChar*)NewPtr(length * sizeof(UniChar));                  //3
    CFStringGetCharacters (theString, CFRangeMake(0, length), buffer);    //4
    err = FSGetDataForkName (&forkName);                                  //5
    if (( err = FSOpenFork (inRef, (UniCharCount)forkName.length,
                 forkName.unicode, 
                 fsRdWrPerm, &forkRefNum)) == noErr) {                    //6
            err = FSWriteFork(forkRefNum, fsFromStart,
                     0, length, (void *) buffer, NULL);                   //7
            err = FSCloseFork(forkRefNum);                                //8
    }
    free(buffer);                                                         //9
    return err;
}

Here’s what the MTPWriteFile function does:

  1. The Window Manager function GetWindowProperty retrieves the CFString that you associated with the itinerary file in Section 11.2.4.5.

  2. The Core Foundation String Services function CFStringGetLength returns the length of the itinerary text contained in the CFString.

  3. The NewPtr function allocates a buffer for the characters to write to disk. In this case, the buffer should be the length of the text in the CFString object.

  4. The Core Foundation String Services function CFStringGetCharacters retrieves the text from the CFString object into the buffer that we have provided.

    Note

    As we mentioned earlier, in Section 11.2.4.5, if you were to make the itinerary file user editable, you would normally have some sort of structure that represents the document as a whole (such as an MLTE text object). You would have to retrieve the data associated with that document before writing it to disk.

  5. The File Manager function FSGetDataForkName retrieves the name of the file’s data fork. You should always store your data in the data fork. Currently, the File Manager simply returns a constant for the data fork name, but using the function provided by the File Manager ensures that future changes in implementation won’t affect your code.

  6. The File Manager function FSOpenFork opens the data fork of the file to save. When you open a file fork, you must specify the type of access you wish to have to the fork. In this case, we pass the constant fsRdWrPerm to the File Manager, requesting read and write privileges. A fork reference number specifying the path to the open fork is returned; a fork can have more than one path open at a time, as long as their permissions don’t conflict.

  7. The File Manager function FSWriteFork writes the specified number of bytes from the buffer containing the itinerary text to the disk.

  8. The File Manager function FSCloseFork closes the file fork.

  9. The function frees the memory allocated to the buffer.

Close the Itinerary

At this point, you have one more command and one more event to handle. These are the Close command and the window close event. Luckily, you can use the same function for both of them. You will write that function in this section.

When you close a document, you should perform any clean-up that may be necessary for your program. In our case, this clean-up is quite simple. However, if you were working with a full-fledged editable text document, there would be more to do. For instance, you would want to check for any unsaved changes in the document, and if there are any, you would give the user a chance to save those changes before closing the window, using the Save Changes dialog that you learned about in Section 11.1.2.4. You would close any fork reference that you may have been using; dispose of text objects, pointers to callback functions, or any other objects for which you’ve allocated memory; and perform any other clean-up tasks that need to be done.

First, declare the MTPDoCloseItinerary function. Copy the following lines into the main.c file, after the declaration for the MTPWriteFile function:

OSStatus MTPDoCloseItinerary(WindowRef inWindow);

Now implement the function. Copy the MTPDoCloseItinerary function in Example 11.13 into the main.c file.

Example 11-13. A Function that Closes an Itinerary Window

OSStatus MTPDoCloseItinerary(WindowRef inWindow)
{
    CFStringRef theString;
    OSStatus err = noErr;
    
    err = GetWindowProperty (inWindow, kMTPApplicationSignature, 
                     kMTPCFStringProperty, sizeof(CFStringRef),
                     NULL, (void *)&theString);                           //1
    if (theString != NULL) CFRelease (theString);                         //2
    DisposeWindow(inWindow);                                              //3
    return err;
}

Here’s what the MTPDoCloseItinerary function does:

  1. The Window Manager function GetWindowProperty gets the CFString that you associated with the itinerary window in the MTPReadFile function.

  2. If the string is not empty, the Core Foundation function CFRelease releases the memory allocated to the CFString.

  3. The Window Manager function DisposeWindow disposes of the window.

Handle the Open Command from the Main Window

As a last step, you need to add code to the Moon Travel Planner application to handle the Open Itinerary command. All you need to do to handle the Open Itinerary command from the Moon Travel Planner main window, is add a case to the switch statement in the main window event handler you wrote in Chapter 6. Modify the switch statement in the MTPMainWindowEventHandler function so it now looks like the code shown in Example 11.14.

Example 11-14. The Modified Switch Statement in the Main Window Event Handler

switch (command.commandID)  
      {
        case kMTPComputeCommand:
            ComputeCommandHandler ((WindowRef) userData);
            result = noErr;
            break;
        case kMTPShowMoonFactsCommand: 
            MTPShowMoonFactsWindow(gMoonFactsWindow);
            result = noErr;
            break;
        case kMTPOpenAboutWindowCommand: 
            MTPAboutWindowCommandHandler(gAboutWindow);
            result = noErr;
            break;
        case kMTPOpenItineraryCommand:
            result = MTPDoOpenItinerary();
            break;
      }

Now, when the user chooses Open Itinerary from the File menu, the Moon Travel Planner main window will process the command and open an itinerary window.

Build, Run, and Test the Application

Now do the following:

  1. Click the Build button in the upper-left corner of the Moon Travel Planner project window.

  2. Click the Run button in the upper-left corner of the project window.

  3. Open an itinerary by using the Open menu command. Select the Open Itinerary command from the Moon Travel Planner File menu. The application will bring up an Open dialog, such as the one shown in Figure 11.7. Use the column view browser or the Where pop-up menu to navigate to the Itineraries folder that was provided with the tutorial. In the column view browser, select the itinerary file “Itinerary” and click the Open button in the Open dialog. You should see the window shown in Figure 11.15.

    The Itinerary window

    Figure 11-15. The Itinerary window

  4. Save an itinerary. Select the Save As command from the Moon Travel Planner File menu. The application will bring up a Save Location dialog, similar to the one in Figure 11.10. In the Save as text box, enter the filename for the new file. Choose the location in which to save the file by navigating through the file system with the column view browser (which you can see by clicking the disclosure button to the right of the Where pop-up menu) or by selecting a folder in the Where pop-up menu.

    Click the Save button. A new file should appear in the location that you specified, with the filename that you provided. Right now this file has a generic document icon; in Chapter 13 you will learn how to provide a document icon that will be displayed for the documents that you create with Moon Travel Planner. You can open the saved file to make sure the text was actually saved.

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

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