Important: The information in this document is obsolete and should not be used for new development.
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:
- Declare a subclass of
TFileBasedDocument
or (less commonly)TDocument
, or of one of MacApp's subclasses of these two classes:TEditionDocument
orTMailableDocument
. Name your classTYourDocument
.- Override your application's
DoMakeDocument
method:
- Your application can set the command number passed to
DoMakeDocument
by overriding theTApplication::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'sIYourDocument
method. (See "Recipe--Defining a Subclass of TApplication," beginning on page 289.)
- 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 fromTFileBasedDocument
, you callIFileBasedDocument
.) 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.
- Override the
DoMakeViews
method in your document class to create the document's views.- 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 toFALSE
in your initialization method, after callingIFileBasedDocument
(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.
- 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.
- 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 toFALSE
.- If you use methods other than the
DoIt
,UndoIt
, andRedoIt
methods of command objects to change a document's data, maintain thefChangeCount
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.
- To perform special operations when closing a document:
- For the simplest cases, override the
Close
method in your document class. Your version should normally callInherited
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 theTCloseFileDocCommand
class. You then override the document'sMakeCloseCommand
method and create and return a close document command object based on your command class.
- 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.
- To support the Revert command for your document objects, perform these steps:
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 theDoMenuCommand
method of theTDocument
class, which creates and posts aTRevertDocCommand
object. TheIRevertDocCommand
method asks the user to confirm that they want to revert, since reverting cannot be undone.The
DoIt
method of theTRevertDocCommand
object calls two document methods:
To allow a user to revert to a previous version of a document, you perform these steps:
- RevertDocument. In the
TDocument
class, theRevertDocument
method
- calls the document's
FreeData
method to free the current document 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 theDoInitialState
method of the document
- ShowReverted. In the
TDocument
class, theShowReverted
method just calls theShowReverted
method of each of the document's views, causing the views to redraw themselves.
You may also need to perform some of the following steps:
- Include the Revert menu item in your File menu.
- Override the
FreeData
method in your document class to free any storage allocated by your document.
The sample code shown in this recipe is taken from various MacApp sample applications.
- Override the
DoInitialState
method in your document class.- Override the
RevertDocument
method in your document class.- Override the
ShowReverted
method in your document or view class.
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'sDefaults.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);TheTDocument::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 theFreeData
method to free any data storage allocated by the document. If a parent class needs to free any data, yourFreeData
method should call the inherited method as well.For the DemoText application's
TTEDocument
class, theFreeData
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 theDoInitialState
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). TheDoInitialState
method of theTIconDocument
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
TheRevertDocument
method does nothing in theTDocument
class. The version in theTFileBasedDocument
class provides a general mechanism for reverting documents. Although this general mechanism should work for many types of documents, you may want to overrideRevertDocument
to
MacApp's
- add to the behavior of the
RevertDocument
method provided byTFileBasedDocument
- change the basic behavior of the
RevertDocument
method provided byTFileBasedDocument
- support reverting in a document class that doesn't descend from
TFileBasedDocument
TEditionDocument
demonstrates the first of these three possibilities.TEditionDocument
overrides theRevertDocument
method to delete any new publishers belonging to the document:
void TEditionDocument::RevertDocument() { this->Abandon(); Inherited::RevertDocument(); }TheAbandon
method iterates over the document's sections, deleting any that represent new publishers. The call toInherited
lets theRevertDocument
method ofTFileBasedDocument
perform the rest of the revert operation.Override ShowReverted in Your Document or View Class
You may wish to modify the default behavior of theShowReverted
method in your document or view class. For example, DemoText's TTEDocument class overridesShowReverted
to install its text, then callsInherited
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'sTTEView
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 theTDocument
andTFileBasedDocument
classes:
Since MacApp has provided this framework, the rest is relatively easy.
- The
TDocument
class supplies two fields used in saving and restoring the user selection:
Boolean fSaveUserSelection
: Determines whether the document should save the current user selection; defaults toTRUE
.TDesignator* fUserSelection
: Designates the current user selection; defaults toNULL
.
TDocument
also provides accessor methods for thefUserSelection
field, as well as two other methods used in saving and restoring the user selection:DoPostMakeViews
: If there is a user selection, this method calls theRevealSelection
method.RevealSelection
: InTDocument
, this method selects the first window in the document's window list (if it wasn't already active).
- The
TFileBasedDocument
class supplies methods to read and write the current user selection:
DoReadSelection
: IffSaveUserSelection
isTRUE
and the file has a resource fork,DoReadSelection
attempts to read a resource of typekDesignatorResType
with IDkSelectionRsrcID
. If successful, it uses the resource to set the document'sfUserSelection
field.DoWriteSelection
: IffSaveUserSelection
isTRUE
,DoWriteSelection
attempts to add thefUserSelection
field to the file's resource fork (if it has one) as a resource of typekDesignatorResType
with IDkSelectionRsrcID
.
To save your document's current user selection with a designator, you perform these steps:
The sample code shown in this recipe is from the Calc application.
- Override
SetUserSelection
in your document class.- Override
RevealSelection
in your document class.
Override SetUserSelection in Your Document Class
Your document class overridesSetUserSelection
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. TheSetUserSelection
method ofTCalcDocument
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 inTDocument
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 theTCalcDocument
class callsInherited
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:
The sample code shown in this recipe is for a hypothetical application.
- Define a display state information record and add a display state field to your document class.
- Add a Boolean field to your document class to specify when the document is being reopened.
- Write the display state record when writing the document.
- Read the display state record when reading the document.
- Set the document's display state from the record when reopening.
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 setfReopening
toFALSE
in the constructor method for your document class. You set it toTRUE
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 itsWriteTo
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 itsReadFrom
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 itsDoMakeViews
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
isFALSE
), this code staggers the window so that it won't directly overlap a previous new window. When opening an existing document (fReopening
isTRUE
), it calls theRestoreWindow
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). }