Previous Book Contents Book Index Next

Inside Macintosh: Programmer's Guide to MacApp / Part 1 - MacApp Theory and Architecture
Chapter 7 - Document Handling

Document Operations

MacApp provides built-in machinery for document operations such as creating a new document, opening an existing document, saving a document, reverting to a previous version of a document, and closing a document. You add code to perform operations such as reading and writing your document's specific data and creating views to display that data.

MacApp's default behavior is to create a new document when an application is launched. A user can also create a new document by choosing New from the File menu or typing Command-N. The application can also receive an Apple event specifying creation of a new document. In each of these situations, MacApp calls the application object's OpenNew method to create the new document.

The TApplication::OpenNew method first calls the application object's DoMakeDocument method to create a document, then calls several methods of the returned document object, including the DoMakeViews method, which creates views for the document. This process is described in "Creating a New Document," beginning on page 170.

Your application class overrides DoMakeDocument to make the kinds of documents it needs. This mechanism works for many different types of documents--you override OpenNew only if you want to implement a nonstandard way of creating a new document.

How MacApp Document Classes Work With Files

MacApp supports disk-based file operations--reading from disk, writing to disk, checking for disk space to save the document's contents, and so on--through the interaction of three classes: TFile, TFileHandler, and TFileBasedDocument.

MacApp supplies the TFile class to manage low-level File Manager calls. The TFile class contains fields and methods for opening, closing, moving, renaming, and deleting a file, and for performing low-level operations such as getting a volume name or reference number, or setting the creation date or file permissions.

You can specify a disk file to the File Manager with three pieces of information: a volume reference number, a directory ID, and a filename. The TFile class uses these three pieces of information to identify the file it manages, storing the information in the fFileSpec field.

When a new document based on the TFileBasedDocument class is initialized, the IFileBasedDocument method creates both a TFile object and a TFileHandler object for the document; for an existing document, a TFile object is passed in to IFileBasedDocument. The document's file-handler field manages disk file access, calling methods of its TFile object to make the low-level file system input and output calls. Methods of the file handler also call methods of the document, such as DoRead and DoWrite, passing a reference to the TFile object to do the low-level reading or writing.

A TFileHandler object can be associated with one or more instances of TFile, allowing the document to store and retrieve data in multiple formats from multiple disk files. MacApp's default behavior, however, assumes one file per document--more complex file handling is left as an exercise for the reader.

Kinds of Documents

MacApp defines command constants to allow your application to use a range of open and new commands:

const CommandNumber cNew = 10;
const CommandNumber cNewLast = 19;
const CommandNumber cOpen = 20;
const CommandNumber cOpenLast = 29;
MacApp uses the cNew constant for the New menu command in the File menu, but your application can use any value between cNew and cNewLast to specify particular versions of the New command. Similarly, MacApp uses cOpen for the Open menu command, but you can use any value between cOpen and cOpenLast. These menu command numbers are handled in the TApplication::DoMenuCommand method.

For a command number in the New range, DoMenuCommand calls the application object's DoNew method. The DoNew method creates and posts a TNewDocumentCommand object, which stores the command number in its fIdentifier field. The DoIt method of the command object calls the application's OpenNew method, passing the command number. The OpenNew method makes the following call to determine the kind of document that should be created:

this->KindOfDocument(itsCommandNumber, NULL)
In the TApplication class, KindOfDocument just returns the command number, which is passed on by OpenNew in a call to DoMakeDocument. Your application class always overrides DoMakeDocument to create the kinds of documents it knows about. Your application may create only one kind of document, or it may create different kinds depending on the command number passed by OpenNew. Your application class can also override KindOfDocument to return a special value based on the command number. The process of opening a new document is described in the next section.

MacApp uses a similar mechanism to open existing documents. For a menu command number in the Open range, the application object's DoMenuCommand method calls DoOpen, which creates a TODocCommand object. The command's DoIt method calls the application's OpenOld method. Like the OpenNew method, OpenOld calls KindOfDocument before calling DoMakeDocument. Opening an existing document is described beginning on page 173.

Creating a New Document

When a user creates a new document, the application object calls its OpenNew method, which in turn calls DoMakeDocument. In your application class, you override DoMakeDocument to create the kinds of documents your application uses. (Kinds of documents are described in the previous section.)

When the OpenNew method calls DoMakeDocument, the following actions take place, as shown in Figure 7-2:

  1. The TYourApplication::DoMakeDocument method creates an instance of TYourDocument and calls IYourDocument. This allows your application to create the type of document it needs, and to perform any special initialization the document requires.
  2. The IYourDocument method calls IFileBasedDocument.
  3. The IFileBasedDocument method calls this->DoMakeFile and this->DoMakeFileHandler to create an instance of TFile and an instance of TFileHandler for the document.

Figure 7-2 Creating a new document, file, and file handler

  1. The TFileBasedDocument::DoMakeFile method calls gApplication->DoMakeFile, giving your application the flexibility to control file creation at the document or application level.
  2. The TApplication::DoMakeFile method makes the following call to the global routine NewFile to instantiate a TFile object:

    NewFile(fMainFileType, fCreator, kUsesDataFork,
    kUsesRsrcFork, !kDataOpen, !kRsrcOpen);

    File forks are described in the next section.

  3. After calling DoMakeDocument to create a new document, the OpenNew method calls the document's DoInitialState method to perform any special initialization.
  4. OpenNew calls the document's DoMakeViews method to create a window and other views for the document. It then calls the document's UntitledName and SetTitle methods to give the document a title (such as "Untitled 1").
  5. Finally, OpenNew calls the document's DoPostMakeViews method, giving the document a chance to perform any final initialization at a time when all the views have been created.

The next section describes how your application can modify the values passed to the NewFile routine (in step 5 above) to change MacApp's default file-handling behavior.

How a MacApp Document Uses File Forks

A Macintosh file consists of two parts or forks: the data fork and the resource fork. The data fork generally stores data, and the resource fork stores resources, such as an application's menu resources. Documents generally use the data fork and applications use the resource fork.

When you create a document, you specify which file forks your application will use and whether it will keep the forks open. Keeping a file fork open is appropriate if your application must frequently access data in the fork. The default is to use both the data and resource forks, but not to keep them open. The call to NewFile in step 5 above shows how the default values are set.

MacApp defines a Boolean constant for each of the values passed to NewFile, as shown in Table 7-1.

Table 7-1 Table 7-1 Constants passed as parameters to NewFile
Parameter nameConstant name

If MacApp's default file configuration is not appropriate for a particular document type in your application, you can override the DoMakeFile method of the document class. If you want to change the file configuration for all document types used by your application, you can override the DoMakeFile method of your application class and pass different values to NewFile.

Opening an Existing Document

A user may launch your application by selecting one or more documents in the Finder and double-clicking. When that happens, the operating system sends the application an Open Documents event with a file list containing an entry for each selected document. MacApp opens the documents through a series of calls to the application object's OpenOld method. If the same type of Apple event is sent by a script, it is handled in a similar way.

A user can also open an existing document by choosing Open from the File menu or typing Command-O. MacApp presents a standard file dialog to obtain the name of the file to open. If the user chooses a document that is already open, MacApp displays the "Document is already open" message and brings the document's window or windows to the front. If the specified document is not open, MacApp again calls the application object's OpenOld method to open the document.

The application object calls its ChooseDocument method to put up the standard file dialog. ChooseDocument calls GetStandardFileParameters, which calls GetFileTypeList (both methods of the application) to get a list of file types to display in the file dialog. GetFileTypeList uses only the application's fMainFileType, but you can override this method if your application uses more than one document type.
The TApplication::OpenOld method is shown in Figure 7-3, which illustrates how MacApp opens an existing document. It is similar to the OpenNew method, in that it first calls the application object's DoMakeDocument method to create a document, then calls several methods of the returned document object. The main difference is that since it is opening an existing document, OpenOld calls the document's DoRead method to read the document's data before calling DoMakeViews to make the document's views. And since the OpenOld method already has a TFile object describing the document to be opened, the document doesn't need to create a new TFile object.

The document, file, and file-handler objects work together with a file stream object (not shown) to read the document's data from disk. File streams are described in "Streams," beginning on page 50.

Creating Views for a Document

When a user creates a new document, opens an existing document, or prints a document from the Finder, MacApp calls the document object's DoMakeViews method to create a view hierarchy to display the document's data. You override the default implementation of DoMakeViews, found in TDocument, to create the specialized views used by your documents.

In your DoMakeViews method, you can create view objects by calling the new routine and initializing the views procedurally. However, this approach has a significant disadvantage--each time you change the appearance of a view, you have to recompile and rebuild your application.

Figure 7-3 Opening an existing document

A better way to define most views is by using a 'View' resource definition in your application's resource file. Then you can use a graphical resource editor to change a view's appearance and the value of its instance variables without having to modify your code. For more information, see "Specifying Views With View Resource Templates," beginning on page 217.

Reverting to a Previous Version of a Document

A user can choose the Revert command from the File menu to return the current document to its most recently saved version. The Revert command is handled by the document object's DoMenuCommand method:

  1. The TDocument::DoMenuCommand method creates and posts a TRevertDocCommand object.
  2. The DoIt method of the TRevertDocCommand object calls the document's RevertDocument method, then calls its ShowReverted method.
  3. The TFileBasedDocument::RevertDocument method

    • gives the user a chance to confirm before completing the revert operation, since a revert operation is not undoable; if the user confirms, it performs the next two steps:
    • calls the document's FreeData method to free the current document's storage
    • re-reads the disk version of the document to get the most recently saved version; if there is no disk version, RevertDocument instead calls the DoInitialState method of the document

  4. The TDocument::ShowReverted method iterates over all the document's windows, calling ShowReverted for each window, which in turn calls ShowReverted for each of the window's views. Some views, such as MacApp's TTEView, perform calculations or other operations when reverting.

The default behavior of the TRevertDocCommand command is to send an Apple event specifying the revert document operation, so the reverting is recordable and can be handled by an attached script.

Determining Whether a Document Has Changed

When a user closes a document, MacApp examines the fChangeCount field of the document object to determine whether the document has changed since it was last saved. If the value of fChangeCount is greater than 0, the document is considered to be changed.

If you specify the document as the command's context when you use a command object to make changes to your document's data, the document's change count is updated automatically when the command's DoIt, UndoIt, or RedoIt method is called.

However, if you change the document's data through other mechanisms, you need to update the change count directly. You can do so in one of two ways:

When a user closes a document whose content has changed, MacApp displays an alert box with the "Save Changes Before Closing?" message and takes appropriate action based upon the user's choice:

Closes the document and saves the changes
Don't Save
Closes the document but does not save the changes
Cancels the close operation
You shouldn't normally use a document as a notifier to send messages to other objects through the dependency system. Calling Changed to broadcast a message also marks the document as changed (and in need of saving). Instead, have the document create a separate object that you can use as a notifier hub.

Saving a Document

When the user chooses Save from the File menu, the command is handled by the document object. Saving can be performed as a function of either the TDocument class or the TFileBasedDocument class. In this scenario, we consider the more common case of saving a file-based document. Saving the document is a cooperative task performed by the document, its file-handler object, and its file object, in the following series of steps:

  1. The TDocument::DoMenuCommand method calls DoSave.
  2. The TFileBasedDocument::DoSave method creates, initializes, and posts a TSaveFileDocCommand object.
  3. The initialization method of the TSaveFileDocCommand object performs any required interaction with the user, such as getting a name for the saved file.
  4. When the command is performed, it creates and sends an Apple event describing the save operation. In this way the operation is recordable and can be handled by an attached script.
  5. The DoIt method of the TSaveFileDocCommand object calls the document object's SaveDocument method. Figure 7-4 shows the steps that take place starting with the call to SaveDocument.
  6. The TFileBasedDocument::SaveDocument method calls aFileHandler->SaveFile.

    The TMailableDocument class overrides SaveDocument to write the document's attached letter, if any. MacApp's electronic mail support is described in "PowerTalk Mailers," beginning on page 192.

  7. The TFileHandler::SaveFile method

    • if necessary, asks the user for a filename
    • calls AboutToSaveFile, which gives the document a chance to do any special handling before the document is saved
    • calls DoNeedDiskSpace to see how much space the document will take on disk
    • depending on the file handler's fHowToSave field (described in the next section) and on whether there is sufficient room to create a copy of the file, calls SaveViaTemp (the default) or SaveInPlace

  8. Both SaveViaTemp and SaveInPlace call the file handler's DoWrite method. SaveViaTemp attempts to save the file in a new location, so the old file is not deleted until the save operation completes successfully. SaveInPlace attempts to save the file on top of the current version.
  9. The file-handler object's DoWrite method calls the document's DoWrite method to write the document's data. Your document class overrides DoWrite to write its own data, and calls Inherited to let its parent document classes write their data.

Figure 7-4 Saving a document

A MacApp document's DoWrite method typically creates a file-based stream (not shown) and calls methods of the stream to write the data. The stream object calls the file's WriteData method to make the low-level calls that write the data to disk.

Saving a Document in Place

When a user saves a document, the document's file handler can use its SaveViaTemp method to create a copy of the existing document file before writing the new version or it can use its SaveInPlace method to save the data on top of the old version. Saving with a temporary file is safer since there is always a saved version of the file to fall back on. Saving in place can save a user's changes when there is not enough room to save with a temporary file.

MacApp's default behavior is to try to save with SaveViaTemp and, if that is not possible, to ask the user before saving with SaveInPlace. This behavior is controlled by the fSaveInPlace field of the document's file handler. The default is set in the TFileHandler constructor:

fHowToSave = svtAskUser;
To change the default behavior, you can set the value of fHowToSave in your IYourDocument method after calling IFileBasedDocument. Here are the possible values for fSaveInPlace:

Always save with SaveViaTemp. Never overwrite the existing file.
Try to save with SaveViaTemp. If not possible, then ask user before saving with SaveInPlace.
Always save with SaveInPlace. Never create a temporary file.
Always save with SaveInPlace, but only if user confirms.
For example, if you always want to save your document in place, your document's initialization method can change the value of fHowToSave with code like the following:

fFileHandler->fHowToSave = sipAlways;

Leaving a Document Open

By default, MacApp closes a document when a document window is closed if the window's fClosesDocument field has the value TRUE. MacApp closes the document when the last open window is closed, even if that window's fClosesDocument field has the value FALSE. To modify this behavior, you can override the TDocument::CloseWindow method in your document class.

Closing a Document

Most documents have one or more associated windows. A user typically closes a document by closing its window (or its last open window). A user can close a window by clicking the close box, choosing the Close menu command, typing Command-W, or quitting the application. In each of these cases, MacApp handles basic closing operations automatically. Your document class may have to do nothing more than provide a destructor routine to free any storage allocated by the class.

The application may also receive a Close Window or Close Document event from another application or from a script. Or it may receive a Quit Application event from the operating system, from another application, or from a script. Any of these Apple events can lead to document being closed, and again, most of the work of closing windows and documents is handled automatically by MacApp.

The next sections describe MacApp's mechanisms for closing a document.

Closing a Document Directly

When a user closes a window by clicking the close box, choosing the Close menu command, or typing Command-W, MacApp calls the window object's CloseByUser method. If a window's fClosesDocument field has the value TRUE and that document is file based, calling CloseByUser results in the following sequence of steps:

  1. The TWindow::CloseByUser method calls the CloseWindow method of the window's document, if it has one.
  2. If the passed window belongs to the document and is the last open window, the TDocument::CloseWindow method calls its own DoClose method; otherwise, it calls the window's DoClose method.
  3. The TFileBasedDocument::DoClose method creates and posts a TCloseFileDocCommand object.

    For a TDocument object, the DoClose method creates and posts a TCloseDocCommand object. Both versions set the command object's fUseAppleEvent field to TRUE. But the TCloseFileDocCommand object performs additional steps to save the document on disk, including creating a file-handler object.

    The following steps examine only the TCloseFileDocCommand class.

  4. If necessary, the TCloseFileDocCommand::ICloseFileDocCommand method calls the file handler's RequestFileName routine to obtain a name and location for saving the document.
  5. When TCloseFileDocCommand is performed, it sends an Apple event describing the close operation (because fUseAppleEvent was set to TRUE in a previous step). This makes the close operation recordable and gives any attached script a chance to handle the operation.
  6. The TCloseFileDocCommand::DoIt method

    • may call fDocument->PoseSaveDialog to ask the user to confirm saving the file
    • may call aFileDocument->SaveDocument to save the file (see "Saving a Document," beginning on page 177)
    • calls fDocument->CloseAndFree to close and free the document object (CloseAndFree first calls the document's Close method, then its Free method)

  7. The TFileBasedDocument::Close method may ask the user whether to save the document. It may also call SaveDocument to save the document, if it wasn't already saved in the previous step. Finally, it calls Inherited.
  8. The TDocument::Close method calls CloseAndFree on each of the document's windows.
  9. Calling Free on a document or window object causes the object to be deleted, a process that in turn causes the object's destructor method to be called. The destructor method frees any memory allocated by the object.

Closing a Document With a Close Document or Close Window Event

When an application receives a Close Document Apple event, it dispatches the event to the specified document's DoAEClose method. The DoAEClose method of the TDocument class creates a TCloseDocCommand object; in TFileBasedDocument, DoAEClose creates a TCloseFileDocCommand object. In either case, the command is performed immediately. The command closes and frees the document, as described in the previous section. One difference in closing the document with an Apple event is that function overloading is used to specify a different version of ICloseFileDocCommand (or ICloseDocCommand)--one whose parameter list includes message and reply Apple events. Another difference is that the command does not send an Apple event when it is performed, since it is already being performed in response to a received Apple event.

When an application receives a Close Window Apple event, it dispatches the event to the specified window's DoAEClose method. If the window closes its document (the fClosesDocument field has the value TRUE), the window's DoAEClose method calls the document's DoAEClose method, with the same result described in the previous paragraph.

Closing a Document When Quitting the Application

When a user quits the application, the application object's DoMenuCommand method creates a TQuitCommand object and posts it to the command queue. When the application receives a Quit Application event from the operating system or from a script, the application object's DoAEClose method creates a TQuitCommand object and performs it immediately. In both cases, the DoIt method of the TQuitCommand command closes and frees all documents and windows before closing the application.

A TQuitCommand object makes and processes other command objects so that Apple events to close all the open windows and documents will be recorded before the event to close the application. This is done so that a recorded script can be played back and the same actions will happen in the same order, including Save/Don't Save choices and save file locations specified by the user, without having to prompt the user again.

The TQuitCommand performs the following steps:

  1. The TQuitCommand::DoIt method calls three of its own methods:

    • DoCloseInWindowOrder
    • DoCloseWindowlessDocuments
    • DoCloseApplication

  2. The DoCloseInWindowOrder method iterates through all open windows. If a window has a document, it calls the TQuitCommand::CloseADocument method. CloseADocument calls the document's MakeCloseCommand method, then immediately processes the returned command to close the document.

    After executing the DoCloseInWindowOrder method, all windows are closed and all documents that had open windows are closed.

  3. The DoCloseWindowlessDocuments method iterates over each remaining document in the application, calling the document's MakeCloseCommand method, then immediately processing the returned command to close the document. DoCloseWindowlessDocuments doesn't really care if the document is windowless or not, but all documents with windows should have been closed by now.
  4. Now that all windows and documents have been closed, the DoCloseApplication method creates and processes a TQuitAppCommand to finish quitting the application.

A Note on Ghost Documents

MacApp's scripting support allows an application to get and set properties of objects, including document objects. In the course of determining the object specified by an Apple event, it may be necessary to count the number of documents. To do so, MacApp works with the application object's document list. However, in some cases the document list may contain a reference to a document that should not be considered when dispatching Apple events. For example, the application may have created a document solely to help in displaying Clipboard data.

A ghost document is a document that should not be visible to the AppleScript interface. The TDocument class defines a Boolean field, fIsGhostDocument, to identify ghost documents. If the fIsGhostDocument field has the value TRUE, the document is not considered as a possible Apple event object. MacApp supplies the CNoGhostDocsIterator class to iterate over a list of documents, returning only those documents whose fIsGhostDocument field has the value FALSE (the default value).

Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996