Important: The information in this document is obsolete and should not be used for new development.
Recipes--Views
The recipes and sample code in this section demonstrate how to perform a variety of operations with views and adorners.Working With Views--A General Outline
This outline describes the steps most MacApp applications take to work with views.
- Your application can use view classes provided by MacApp or view classes you have defined based on MacApp's view classes.
- If you use a view-editing application to define a
'View'
resource that describes a view or view hierarchy, use aninclude
statement to include the resource into your application's resource file.- Use a
#include
statement to include a text resource file into your application's resource file.- Define a constructor method to initialize the fields of your view class to safe or default values.
- Define an initialization method for your view class to allocate any storage needed by the class.
- Define a destructor method to free any storage allocated in the initialization method and to do any other necessary cleanup when your object is deleted.
- To ensure that your view class can be created from a resource, you register your view classes as described in "Registering Class Information," beginning on page 30.
- Override the
DoMakeViews
method of your document object class to create the views for your document. Your implementation ofDoMakeViews
must handle the cases of opening a document for Finder printing and opening a document for display (see following recipe).- Override the
DoPostCreate
method of your view object class to perform view initialization that can't be specified by your constructor or as part of a view resource definition.- Override the
Draw
method of your view object class to handle drawing in your view.- To recalculate the size of your view, override the
CalcMinFrame
method of your view object class.- To highlight in your view, override the
DoHighlightSelection
method.- To use an adorner with your view, you can either specify the adorner in your resource file or add it procedurally.
Recipe--Creating Views for Displaying and Printing Versus Printing Only
An application normally needs a complete view hierarchy, including a window, to display data. In some cases, however, the application may not need the full hierarchy for printing.
- When a user opens a document, your application creates the full window hierarchy for displaying or printing. You can create the hierarchy by calling the
NewTemplateWindow
routine.- When a user prints your application's documents from the Finder, the application is launched to do the printing. To create a view hierarchy for printing, you don't normally need a window object. You can create a view hierarchy without a window by calling the
DoCreateViews
method of thegViewServer
object.
The following code sample, from the
- IMPORTANT
- It is a common source of error to attempt to create a window or view from a
'View'
resource without registering all view classes in the resource with the MA_REGISTER_CLASS macro.TIconDocument::DoMakeViews
method in the IconEdit sample application, demonstrates how to create a different view for printing only:
if (forPrinting) { // If only for printing, need only the view. iconView = (TIconEditView *)gViewServer->DoCreateViews( this, NULL, kIconEditViewId, gZeroVPt); FailNIL(iconView); } else { // Otherwise, need view and window. aWindow = gViewServer->NewTemplateWindow(kIconWindowId, this); FailNIL(aWindow); // Get reference to the view. iconView = (TIconEditView*)(aWindow->FindSubView('ICON')); } fIconView = iconView;TheNewTemplateWindow
method reads the resource specified by the kIconWindowId resource ID from the resource file, creates a window and view hierarchy, and returns a reference to the window. TheDoCreateViews
method performs a similar operation for the resource specified by the kIconEditViewId resource ID, which doesn't include a window.This sample code calls the window object's
FindSubView
method to get a reference to a specific subview in the hierarchy, the icon view.Recipe--Calculating the Size of a View
Sometimes MacApp needs to know the current size of a view, such as when your application is printing or when the view has changed size and its scroll bars need to be adjusted. To determine the current size of a view, MacApp calls theCalcMinFrame
method of the view object.The default version of
CalcMinFrame
, defined inTView
, computes the minimum frame based on thefLocation
andfSize
fields. This minimum size is sufficient for some view classes, but if your view has more complicated operations, you may need to overrideCalcMinFrame
. The following version of CalcMinFrame is from theTIconEditView
class from the IconEdit application. In calculating the view's minimum size, this method takes into account the current zoom magnification and adds a couple of pixels for a border.
void TIconEditView::CalcMinFrame(VRect& minFrame) { minFrame = VRect ( 0, 0, kIconHBits * fMagnification + kBorder + kBorder, kIconVBits * fMagnification + kBorder + kBorder); }Recipe--Using an Adorner With a View
An adorner is a graphical object that knows how to draw in the view to which it is attached. Adorners are described in detail in "Adorners," beginning on page 213. Adorners can be used for many purposes, and MacApp defines adorner classes for erasing, framing, highlighting, and other drawing operations in views.To use an adorner with a view, you perform these steps:
The sample code shown in this recipe is for a hypothetical application.
- Create and initialize an instance of an adorner.
- Call the
AddAdorner
method of the view.
Create and Initialize an Instance of an Adorner
Your adorner object may be based on one of MacApp's adorner classes or on a subclass you have defined. You can create an adorner by callingnew
:
TMyAdorner * anAdorner = new TMyAdorner; anAdorner->IMyAdorner();In the IMyAdorner initialization method for your adorner class, you callIAdorner
, passing an IDType value to identify your adorner and aBoolean
value indicating whether the adorner should be freed when the view it is attached to is deleted:
IAdorner(kMyAdornerID, kFreeOnDeletion);You passkDontFreeOnDeletion
if you don't want the adorner freed with its view. This is often the case, since it is common to associate one instance of an adorner with many views. In this example,kMyAdornerID
would have a definition something like the following:
const IDType kMyAdornerID = 'MyAd';
- Note
- Instead of creating an adorner to add to a view, you can use one of MacApp's global adorner objects. These adorners are described in "MacApp's Adorner Classes," beginning on page 213.
Call the AddAdorner Method
To add an adorner object to a view, you simply call the view'sAddAdorner
method:
myView->AddAdorner(anAdorner, kAdornLast, kInvalidate);In the AddAdorner call, you pass the adorner to add, a value indicating the drawing priority for the adorner, and a value indicating whether to invalidate the adorner's view, thus forcing it to be redrawn. MacApp's priority constants for adornment specify whether the adorner should be drawn last (as specified in this call), drawn after the view, drawn before the view, or drawn first. For more information, see "Adorner Priorities," beginning on page 215.MacApp supplies the constants
kInvalidate
andkDontInvalidate
to specify whether or not a view should be invalidated (forcing redrawing).Recipe--Using a Document Class With a Text-Editing View
To define a file-based document class that works with a text-editing view, you perform these steps:
The sample code shown in this recipe is from the DemoText application. The
- Create a
'View'
resource with a text-editing view.- Call
InitUTEView
from yourmain
routine.- Provide a document class definition.
- Provide constructor and destructor methods.
- Provide an initialization method.
- Override the
FreeData
method.- Override the
DoMakeViews
method.- Override the
DoNeedDiskSpace
method.- Override the
DoWrite
method.- Override the
DoRead
method.- Override the application's
DoMakeDocument
method.- Make additional modifications.
TTEDocument
class shown in this recipe implements the methods needed to edit text but is not intended as a complete text-editing solution.Create a 'View' Resource With a Text-Editing View
The easiest way to define a'View'
resource containing a window with a view hierarchy is to use a view-editing application. The window should have a scroller view plus vertical and horizontal scroll-bar views. The scroller view serves as the superview for a text-editing view based MacApp'sTTEView
class. You can see a text version of this window and view hierarchy in the fileDemoText.r
. The scroll bars work with the scroller to let the user scroll text.The DemoText application defines a window with resource ID number 1004, containing a
TTEView
class with view resource ID'TEVW'
. To specify these resources in your application, you define constants such as the following:
// 'View' resource ID for DemoText text-editing window. const ResNumber kWindowRsrcID = 1004; // Resource ID for TTEView within window view hierarchy. const IDType kTTEViewSubView = 'TEVW';Call InitUTEView From Your Main Routine
To use MacApp'sTTEView
class, you must call theInitUTEView
initialization routine. You typically do so in yourmain
routine, with a line like the following:
InitUTEView();Provide a Document Class Definition
You define a document class to work with a text-editing view. In the DemoText application, the document's parent class can be eitherTFileBasedDocument
or, if the application is built to use PowerTalk mailers,TMailableDocument
(a subclass ofTFileBasedDocument
). This recipe usesTFileBasedDocument
. For a recipe that demonstrates the use of mailers, see "Recipe--Adding Mailer Support to a Document Class," beginning on page 628.
class TTEDocument : public TFileBasedDocument { MA_DECLARE_CLASS; // Include MacApp's RTTI. public: Handle fDocText; // The text owned by the document. TEStyleHandlefStyles; // Style handle, if any. STHandle fElements; // Handle to element array, if any. TTEView* fTEView; // The view that displays the text. TextSpecs fTextSpecs; // Specifies properties of the text. ResNumber fViewResourceID;// ID of 'View' resource for text view. // Initialization and freeing. TTEDocument(); // Constructor. virtual ~TTEDocument(); // Destructor. virtual void ITEDocument( // Initialization. TFile*itsFile, const OSType itsScrapType, ResNumber itsViewResourceID); virtual void DoInitialState();// Called when creating or reverting. // Disk operations. virtual void DoNeedDiskSpace(TFile* itsFile, long& dataForkBytes, long& rsrcForkBytes); virtual void DoRead(TFile* aFile, Boolean forPrinting); virtual void DoWrite(TFile* aFile, Boolean makingCopy); // Making views and windows. virtual void DoMakeViews(Boolean forPrinting); . . // Some methods not shown. . } // End TTEDocument.Provide Constructor and Destructor Methods
The constructor method puts all fields of the document into a default or safe state to ensure that freeing can take place correctly if there is an error during creation of the document.
TTEDocument::TTEDocument() { fDocText = NULL; fElements = NULL; fStyles = NULL; fTEView = NULL; fTextSpecs = gDefaultSpecs; fViewResourceID = kDefaultViewID; }In this constructor, theTTEDocument
class setsfTextSpecs
(which specifies properties of the text, such as font name, size, and color) to refer to a global variable. The global variablegDefaultSpecs
is initialized in theIDemoTextApplication
method.The constructor sets the fViewResourceID field to kDefaultViewID; this is the default
'View'
resource MacApp installs in a document. The initialization method, shown in the next section, sets the fViewResourceID field to the passed value.The destructor method deletes any storage allocated by the document. For the
TTEDocument
class, it just disposes of the object's text handle.
TTEDocument::~TTEDocument() { fDocText = DisposeIfHandle(fDocText); }Provide an Initialization Method
The initialization method for theTTEDocument
class calls IFileBasedDocument (the initialization method of its parent), creates a handle to store document text, and sets the fViewResourceID field to the passed view resource ID.
void TTEDocument::ITEDocument(TFile* itsFile, const OSType itsScrapType, ResNumber itsViewResourceID) { // Let parent classes perform their initialization. this->IFileBasedDocument(itsFile, itsScrapType); FailInfo fi; Try(fi) { // Allocate a handler for storing the document's text. fDocText = NewPermHandle(0); fi.Success(); } else { this->Free(); fi.ReSignal(); } fViewResourceID = itsViewResourceID; }If a failure occurs, ITEDocument callsthis->Free()
to free the document, then callsReSignal
to propagate the failure. Freeing the document causes its destructor method to be called, freeing the fDocText handle if it isn'tNULL
.This approach to failure handling is simple but not robust, and it may not be appropriate for all applications. A safer implementation would be for ITEDocument to dispose of the handle directly, if necessary, then call
ReSignal
, allowing the calling routine (typically the application'sDoMakeDocument
method) to free the document object with its own failure handling.Override the FreeData Method
TheTFileBasedDocument::RevertDocument
method calls theFreeData
method as part of reverting to a previous version of a document. TheTTEDocument
class overrides theFreeData
method to free the text storage allocated in the initialization method.Override the DoMakeViews Method
A document class typically overrides theDoMakeViews
method to create the kind of views it works with. In TTEDocument, the DoMakeViews method uses the global view server object to create a window and view hierarchy, gets a reference to theTTEView
object in the hierarchy, creates a print handler to handle printing for the document, and calls theShowReverted
method as a means of setting up the window to display. (For more information on reverting, see "Reverting to a Previous Version of a Document," beginning on page 176.)
void TTEDocument::DoMakeViews(Boolean /*forPrinting*/ ) { TWindow* aWindow; TStdPrintHandler* aHandler; aWindow = gViewServer->NewTemplateWindow(fViewResourceID, this); FailNIL(aWindow); // Next call uses a cast because FindSubView returns a TView. fTEView = (TTEView*) aWindow->FindSubView(kTTEViewSubView); aHandler = new TStdPrintHandler; aHandler->IStdPrintHandler( this, // Its document. fTEView, // Its view. !kSquareDots,// Does not have square dots. kFixedSize, // Horizontal page size is fixed. !kFixedSize);// Vertical page size is variable. // Use the show-reverted mechanism to set up the fTEView view object. this->ShowReverted(); }Override the DoNeedDiskSpace Method
TheDoNeedDiskSpace
method determines how much disk space will be required to store the document's text.
void TTEDocument::DoNeedDiskSpace(TFile*itsFile, long& dataForkBytes, long& rsrcForkBytes) // Override. { TEStyleHandle styles; STHandle elements; dataForkBytes += GetHandleSize(fDocText); if (fPrintInfo) rsrcForkBytes += fPrintInfo->GetSize() + kRsrcTypeOverhead + kRsrcOverhead; if ((fTEView->fStyleType == kWithStyle) && (fTEView->fHTE)) { fTEView->ExtractStyles(styles, elements); rsrcForkBytes += GetHandleSize((Handle)styles) + kRsrcTypeOverhead + kRsrcOverhead + GetHandleSize((Handle)elements) + kRsrcTypeOverhead + kRsrcOverhead; } rsrcForkBytes += sizeof(TextSpecs) + kRsrcTypeOverhead + kRsrcOverhead; // Get resource file overhead. Inherited::DoNeedDiskSpace(itsFile, dataForkBytes, rsrcForkBytes); }This method gets the size of the text handle, then adds the size of the document's print info record. If the document contains style information, it adds the amount of space needed to write that information. It also callsInherited
, to determine any disk space needed by other classes in the document class hierarchy.Override the DoWrite Method
TheDoWrite
method writes the document's text and style data to disk.
void TTEDocument::DoWrite(TFile* aFile, Boolean makingCopy) // Override. { long numChars; TextSpecsHdl hTextSpecs; // Write out the text. numChars = GetHandleSize(fDocText); SignedByte savedState = LockHandleHigh(fDocText);// Lock handle. FailOSErr(aFile->WriteData(*fDocText, numChars)); HSetState(fDocText, savedState); // Restore state of handle. if (fTEView->fStyleType == kWithStyle) { TEStyleHandle styles; STHandle elements; fTEView->ExtractStyles(styles, elements); // NOTE: Ought to include failure handling to free new handles on error. PermHandToHand((Handle &) styles); AddResource((Handle)styles, kTextStyleRsrcType, kStylesRsrcID, gEmptyString); FailResError(); PermHandToHand((Handle &) elements); AddResource((Handle)elements, kTextStyleRsrcType, kElementsRsrcID, gEmptyString); FailResError(); } // Write the text specification resource, after converting it to a handle. hTextSpecs = (TextSpecsHdl)NewPermHandle(sizeof(TextSpecs)); MABlockMove(&fTextSpecs, *hTextSpecs, sizeof(TextSpecs)); AddResource((Handle)hTextSpecs, kTextSpecsRsrcType, kTextSpecsRsrcID, gEmptyString); FailResError(); Inherited::DoWrite(aFile, makingCopy); }TheTTEDocument::DoWrite
method shown above performs these steps:
The general case of writing a document is described in Chapter 27, "Working With Streams."
- It writes the data from the document's text handle to the file.
- If the document contains style data,
DoWrite
converts style and element data to resources and adds them to the document's resource map so that they will be saved with the file.- It adds the document's text specification resource to the resource map so that it will be saved with the file.
- It calls
Inherited
to complete the writing process.
Override the DoRead Method
TheDoRead
method reads the document's text and style data from disk.
void TTEDocument::DoRead(TFile* aFile, Boolean forPrinting) // Override. { long numChars; TextSpecsHdl hTextSpecs; Boolean oldTempAllocation; FailInfo fi; // Read in the text. FailOSErr(aFile->GetDataLength(numChars)); // The file may have been created by someone else, so limit it to 32 KB! if (numChars > kUnlimited) { gApplication->ShowError(0, messageAlert + kFileTooBig); numChars = kUnlimited; } // Set size of handle to hold text we will read. Lock handle, and provide // failure handling to unlock it in case of an error. SetPermHandleSize(fDocText, numChars); SignedByte savedState = LockHandleHigh(fDocText); Try(fi) { FailOSErr(aFile->ReadData(*fDocText, numChars)); HSetState(fDocText, savedState); fi.Success(); } else { HSetState(fDocText, savedState);// Restore state of handle. fi.ReSignal(); } // Read in TEStyleRec. fStyles = (TEStyleHandle)Get1Resource(kTextStyleRsrcType, kStylesRsrcID); if (fStyles) DetachResource((Handle)fStyles); // Read in the STElement array, using a permanent allocation. oldTempAllocation = TemporaryAllocation(FALSE); fElements = (STHandle)(Get1Resource(kTextStyleRsrcType, kElementsRsrcID)); TemporaryAllocation(oldTempAllocation); if (fElements) DetachResource((Handle)fElements); // Read the text specs resource. hTextSpecs = (TextSpecsHdl)Get1Resource(kTextSpecsRsrcType, kTextSpecsRsrcID); if (hTextSpecs) { MABlockMove(*hTextSpecs, &fTextSpecs, sizeof(TextSpecs)); ReleaseResource((Handle)hTextSpecs); } else this->DoInitialState(); Inherited::DoRead(aFile, forPrinting); }TheTTEDocument::DoRead
method shown above performs these steps:
The general case of reading a document is described in Chapter 27, "Working With Streams."
- It determines the number of text characters to be read and limits it to a 32 KB maximum.
- It sets the
fDocText
handle to the needed size and reads the text.- It reads the
TEStyleRec
resource.- It attempts to read a
STElement
array resource; if successful, it detaches the resource.- It attempts to set the
fTextSpecs
field by reading a resource; if unsuccessful, it callsDoInitialState
to set the field to a default value.- It calls
Inherited
to complete the reading process.
Override the Application's DoMakeDocument Method
In your application class, you override theDoMakeDocument
method to create and initialize a text-editing document object. The DoMakeDocument method of the TDemoTextApplication class calls ITEDocument, passing the kWindowRsrcID constant defined earlier. Remember that this ID specifies the'View'
resource for a window hierarchy containing a scroller, scroll bars, and aTTEView
object.
TDocument* TDemoTextApplication::DoMakeDocument( CommandNumber /* itsCommandNumber */, TFile* itsFile) { TTEDocument* aTEDocument = new TTEDocument; aTEDocument->ITEDocument(itsFile, kFileType, kWindowRsrcID); return aTEDocument; }Make Additional Modifications
TheTTEDocument
class implements additional methods that are not included in the steps shown above:
The
DoSetupMenus
- Sets up various text-handling menus (supported in the DemoText application), which include menu items for changing the style, size, font, color, and justification of document text.
DoMenuCommand
- Handles the menu choices enabled in
DoSetupMenus
.ShowReverted
- Helps set up the document's text before calling
Inherited
to complete showing of the document.TTEDocument
class also includes methods to handle the text-editing menu choices it supplies. Your text-editing document class is likely to require additional methods as well.