Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: Programmer's Guide to MacApp / Part 2 - Working With MacApp
Chapter 16 - Working With Documents


Recipes--Documents

The recipes and sample code in this section start with a general outline on how to work with documents, then demonstrate how to revert to the previous version of a document, how to use a designator to save a document's current selection, and how to save a document's display state.

Working With Documents--A General Outline

This outline describes the steps most MacApp applications take to work with documents. Your application uses document objects to store data; for most documents, that data is stored on disk. (Some applications may work with special documents that store data in an arbitrary location, such as a database.)

You define one or more document classes, based on the classes provided by MacApp, and override methods to read and write your specific data. Your application creates and initializes document objects based on the classes you define, and uses them to save your data to disk and read it back from disk. When a document is closed, you free any storage allocated by the document.

Working with documents usually includes the following steps:

  1. Declare a subclass of TFileBasedDocument or (less commonly) TDocument, or of one of MacApp's subclasses of these two classes: TEditionDocument or TMailableDocument. Name your class TYourDocument.
  2. Override your application's DoMakeDocument method:

    • Your application can set the command number passed to DoMakeDocument by overriding the TApplication::KindOfDocument method, then create different kinds of documents based on the command number.
    • After creating a document object in the DoMakeDocument method, call the object's IYourDocument method. (See "Recipe--Defining a Subclass of TApplication," beginning on page 289.)

  3. Add initialization to your document class:

    • Supply a constructor method to put the fields of the document in a safe or default state, and a destructor method to free any memory allocated by your document class.
    • In the IYourDocument method, call the initialization method of the parent class. (For example, if your class descends from TFileBasedDocument, you call IFileBasedDocument.) Follow that with any additional initialization required for the fields declared for your object.
    • If you wish, override the DoInitialState method to modify the document's initial state.

  4. Override the DoMakeViews method in your document class to create the document's views.
  5. Add code to your document class to save the document's data:

    • If you need to change the filename or other file information just before the document is saved, override the AboutToSave method.
    • If you need to modify the way MacApp handles saving your document when there is not enough space on the disk to create a copy, you can change the value of the fHowToSave field of the document's file handler (see "Saving a Document in Place," beginning on page 180).
    • If you don't want MacApp to save document print information with the file, set the value of the fSavePrintInfo field to FALSE in your initialization method, after calling IFileBasedDocument (page 403). The default is for MacApp to save the document's print information automatically.
    • Override the DoNeedDiskSpace method in your document class to calculate the number of bytes needed to write your document data.
    • Override the DoWrite method in your document class to write the document's data to disk.

  6. To open an existing document, override the following methods:

    • Override the DoRead method in your document class to read the document's data from. If you use streams to read data, remember to read the data in the exact order you wrote it.

  7. To close a document, do one of the following:

    • If you want a document to remain open until its last window is closed, set the value of the fClosesDocument field of the document's window objects to FALSE.
    • If you use methods other than the DoIt, UndoIt, and RedoIt methods of command objects to change a document's data, maintain the fChangeCount value in those methods. This allows MacApp to know whether the document needs to be saved when it is closed.
    • Supply a destructor method, ~TYourDocument, to free any storage allocated by your document object.
    • When a constructor method is called, you can't count on the class hierarchy still being intact. If your document class has to do any special cleanup that may involve calling inherited class methods, override the Free method (which is called before the constructor) and perform the cleanup there.

  8. To perform special operations when closing a document:

    • For the simplest cases, override the Close method in your document class. Your version should normally call Inherited after performing its tasks.
    • For more complicated situations, you may need to define a special close command class that descends from the TCloseDocCommand class or the TCloseFileDocCommand class. You then override the document's MakeCloseCommand method and create and return a close document command object based on your command class.

  9. If your document class handles any additional menu commands:

    • Override the DoSetupMenus method to enable the additional menu commands at the appropriate time.
    • Override the DoMenuCommand method to handle the menu commands.
    • If the commands are scriptable, override the DoScriptCommand method to respond to the Apple events that specify the commands.

  10. To support the Revert command for your document objects, perform these steps:

    • Include the Revert menu item in your File menu resource definition.
    • Override the FreeData method to free any storage that you allocate for the document.
    • Optionally override the DoInitialState, RevertDocument, or ShowReverted methods.

Recipe--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 DoMenuCommand method of the TDocument class, which creates and posts a TRevertDocCommand object. The IRevertDocCommand method asks the user to confirm that they want to revert, since reverting cannot be undone.

The DoIt method of the TRevertDocCommand object calls two document methods:

To allow a user to revert to a previous version of a document, you perform these steps:

  1. Include the Revert menu item in your File menu.
  2. Override the FreeData method in your document class to free any storage allocated by your document.

You may also need to perform some of the following steps:

  1. Override the DoInitialState method in your document class.
  2. Override the RevertDocument method in your document class.
  3. Override the ShowReverted method in your document or view class.

The sample code shown in this recipe is taken from various MacApp sample applications.

Include the Revert Menu Item in Your File Menu

To include the Revert item in your File menu, you can cut the Revert command definition from the File 'CMNU' resource in MacApp's Defaults.r file and paste it into your own 'CMNU' resource. Or you can include MacApp's entire File menu 'CMNU' resource into your application by adding the following line to your resource definition file:

include "Defaults.rsrc" 'CMNU' (mFile);
The TDocument::DoSetupMenus method automatically enables the Revert menu command whenever the document's change count is not zero (indicating the document has changed).

Override FreeData in Your Document Class

Your document class overrides the FreeData method to free any data storage allocated by the document. If a parent class needs to free any data, your FreeData method should call the inherited method as well.

For the DemoText application's TTEDocument class, the FreeData method just sets the size of the document's text handle to zero.

void TTEDocument::FreeData() // Override.
{
   SetPermHandleSize(fDocText, 0);
}

Override DoInitialState in Your Document Class

You may need to override the DoInitialState method for the case in which the user reverts a document that has not yet been saved, which causes the document to revert to its original state (when first created). The DoInitialState method of the TIconDocument class contains code to set the icon bitmap to its default value:

void TIconDocument::DoInitialState() // Override.
{
   Handle seedIcon;
   // Get the seed icon resource and set the icon bitmap to display.
   seedIcon = GetIcon(kSeedIconId);
   if (seedIcon != NULL)
      fIconBitMap->SetIconBitMap(seedIcon);
#if qDebug
   else
      ProgramBreak("Unable to get the seed icon resource.");
#endif
}
If the application is built with debug information, this method displays an error message if there is a problem getting the seed icon.

Override RevertDocument in Your Document Class

The RevertDocument method does nothing in the TDocument class. The version in the TFileBasedDocument class provides a general mechanism for reverting documents. Although this general mechanism should work for many types of documents, you may want to override RevertDocument to

MacApp's TEditionDocument demonstrates the first of these three possibilities. TEditionDocument overrides the RevertDocument method to delete any new publishers belonging to the document:

void TEditionDocument::RevertDocument()
{
   this->Abandon();
   Inherited::RevertDocument();
}
The Abandon method iterates over the document's sections, deleting any that represent new publishers. The call to Inherited lets the RevertDocument method of TFileBasedDocument perform the rest of the revert operation.

Override ShowReverted in Your Document or View Class

You may wish to modify the default behavior of the ShowReverted method in your document or view class. For example, DemoText's TTEDocument class overrides ShowReverted to install its text, then calls Inherited to cause its views to be redrawn in the usual way:

void TTEDocument::ShowReverted()// Override.
{
   CRGBColor aColor;

   fTEView->StuffText(fDocText);// Put in the text.
   TESetSelect(0, 0, fTEView->fHTE);
   if (fStyles && fElements)  // If possible, save style info.
      fTEView->StuffStyles(fStyles, fElements);
   else
      this->SetSpecStyle();   // Otherwise, use default.

   if (qNeedsColorQD || HasColorQD())// Restore background color.
   {
      aColor = fTextSpecs.theBackColor;
      this->ChangeBackColor(aColor);
   }
   fTEView->SetJustification(fTextSpecs.theJustification, kDontRedraw);
   Inherited::ShowReverted();
}
MacApp's TTEView class overrides ShowReverted to recalculate information such as page breaks and line heights before the view is redrawn:

void TTEView::ShowReverted()// Override.
{
   this->RecalcText();  // Calls Toolbox TECalc routine.
   fLastHeight = 0;
   fLastWidth = 0;
   Inherited::ShowReverted();
}

Recipe--Saving the Current Selection With a Designator

MacApp supplies a framework for saving a document's current selection in the TDocument and TFileBasedDocument classes:

Since MacApp has provided this framework, the rest is relatively easy.

To save your document's current user selection with a designator, you perform these steps:

  1. Override SetUserSelection in your document class.
  2. Override RevealSelection in your document class.

The sample code shown in this recipe is from the Calc application.

Override SetUserSelection in Your Document Class

Your document class overrides SetUserSelection to set any selection fields of your document or view class. The Calc application uses a region designator to designate the cells currently selected by the user. The SetUserSelection method of TCalcDocument uses the region from the region designator to set the selection for the document's cells view (its main spreadsheet view).

void TCalcDocument::SetUserSelection(TDesignator* newSelection)
{
   // Inherited sets the fUserSelection field.
   Inherited::SetUserSelection(newSelection);
   
   if (fCellsView && fCellsView->fSelections)
   {
      // If the selection designator is the right type (a region
      // designator), get the region handle from the designator and
      // use it to set selected cells in main spreadsheet view.
      TDesignator * userSelection = this->GetUserSelection();
      if ((userSelection) && userSelection->DescendsFrom(
                     TRegionDesignator::GetClassDescStatic()))
         fCellsView->SetSelection(((TRegionDesignator *)
            (userSelection))->fDesignation, kDontExtend,
            kHighlight, kSelect);
   }
}

Override RevealSelection in Your Document Class

The RevealSelection method in TDocument activates only the first window in the document's window list. That's a start, but you'll also need to make sure the selection is visible in your view. The RevealSelection method for the TCalcDocument class calls Inherited to make sure the window is selected, then sets the selection for the main spreadsheet view based on the selection designator, and finally scrolls that selection into view.

void TCalcDocument::RevealSelection(TDesignator* aSelection)// Override.
{
   CRect qdExtent;
   VRect viewRect;

   // Select the window (make it frontmost).
   Inherited::RevealSelection(aSelection); 

   // If the selection designator is the right type (a region designator),
   // get the region handle from the designator and use it to set
   // the selected cells in our main spreadsheet view, then scroll
   // those cells into view.
   if (aSelection->DescendsFrom(TRegionDesignator::GetClassDescStatic()))
   {
      fCellsView->SetSelection(((TRegionDesignator *)
         (aSelection))->fDesignation, kDontExtend, kHighlight, kSelect);

      fCellsView->ScrollSelectionIntoView(kRedraw);
   }
   // If the designator isn't the correct type, and we're in a debug
   // version of the application, say something about it.
   else if (qDebug)
      ProgramBreak("handed an unknown designator");
}

Recipe--Saving and Restoring a Document's Display State

When opening an existing document, users may like to find the window and view the way they were left when the document was saved--that is, with the window in the same position and at the same size and displaying the view in the same scroll position. To provide this capability, you need to save the display state of the document when you save the document, then restore it when the document is opened.

To save and restore a document's display state, you perform these steps:

  1. Define a display state information record and add a display state field to your document class.
  2. Add a Boolean field to your document class to specify when the document is being reopened.
  3. Write the display state record when writing the document.
  4. Read the display state record when reading the document.
  5. Set the document's display state from the record when reopening.

The sample code shown in this recipe is for a hypothetical application.

Define a Display State Information Record and a Document Field

The display state record should store any information about the document's display state that your application needs to save and restore. That information would normally include the location, size, and scroll position of the window, as well as any display information specific to your application. This recipe stores its information in a simple structure, but your specific data may require your application to define a more complicated display state structure or even a special class.

struct DocState
{
   VPoint   theLocation;
   VPoint   theSize;
   VPoint   theScrollPosition;
   // Add fields for information specific to your document class.
};
You also add a field to your document class to store the display state:

class TYourDocument : public TFileBasedDocument
{  .
   .
   .
   DocState fDocState;

Add a Boolean Field to Specify Reopening

You add a Boolean field to your document class definition to specify whether a document is being opened:

BooleanfReopening;
You set fReopening to FALSE in the constructor method for your document class. You set it to TRUE after reading the display information when opening an existing document.

Write the Display State Record When Writing

You write the display state record when writing your document's data in its WriteTo method:

void TYourDocument::WriteTo(TStream* aStream) // Override.
{
   DocState localDocState;
   TScroller * theScroller = NULL;
   // Declare other variables needed for writing data (not shown).

   // Do any inherited writing, such as print information.
   Inherited::WriteTo(aStream);

   // Get document's main window location, size, and scroll position.
   TWindow* mainWindow = (TWindow*)fWindowList->First();
   if (mainWindow != NULL)
   {
      localDocState.theLocation = mainWindow->fLocation;
      localDocState.theSize = mainWindow->fSize;
      theScroller = mainWindow->GetScroller(FALSE);
      if (theScroller != NULL)
         localDocState.theScrollPosition = theScroller->fTranslation;
      // Add display state info specific to your document (not shown).

      // Write out the display info structure as a number of bytes.
      aStream->WriteBytes(&localDocState, sizeof(DocState));
   }

   // Write other document data (not shown).
}
This method first gathers the current display state information into a local structure, then writes the bytes of that structure to the stream, which is typically a file stream.

Read the Display State Record When Reading

You read the display state record when reading your document's data in its ReadFrom method:

void TYourDocument::ReadFrom(TStream* aStream) // Override.
{
   // Do any inherited reading.
   Inherited::ReadFrom(aStream);

   // Read in the doc state record.
   aStream->ReadBytes(&fDocState, sizeof(DocState));
   // Set variable indicating we are reopening the document.
   fReopening = TRUE;

   // Read other document data (not shown).
}
WARNING
When reading a document from a stream, read information in exactly the same order in which you wrote it. Failure to do so will at best result in garbled data, and will generally result in program crashes.

Set the Document's Display State From the Record When Reopening

After creating views to display the document's data in its DoMakeViews method, you can set the display state with code like the following:

if (fReopening)
   this->RestoreWindow(aWindow);
else
   {
      aWindow->AdaptToScreen();
      CPoint stagger(kStaggerAmount, kStaggerAmount);
      aWindow->SimpleStagger(stagger, gStaggerCount);
   }
When opening a new document (fReopening is FALSE), this code staggers the window so that it won't directly overlap a previous new window. When opening an existing document (fReopening is TRUE), it calls the RestoreWindow method:

void TYourDocument::RestoreWindow(TWindow* aWindow)
{
	// Restore the window and scroller using the settings in the
	// document's fDocState field.
	aWindow->Locate(fDocState.theLocation, FALSE);
	aWindow->Resize(fDocState.theSize, FALSE);
	aWindow->ForceOnScreen();

	TScroller* theScroller(NULL);
	theScroller = aWindow->GetScroller(FALSE);
	if (theScroller)
		theScroller->SetLocalOrigin(fDocState.theScrollPosition, FALSE);

	// Adjust the display state further based on information
	//		specific to your application (not shown).	
}

Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996