Recipes--Printing
The recipes and sample code in this section summarize how to work with MacApp's printing support and demonstrate how to associate a standard print handler with a view, change default margin settings, show page breaks, define a custom print handler class to modify page strips, and turn on MacApp's debugging code for printing.Working With Printing--A General Outline
This outline describes the steps most MacApp applications take to work with printing.
- To initialize MacApp's UPrinting unit, you call the
InitUPrinting
routine from yourmain
routine, after callingInitUMacApp
and before creating your application object. (See "Recipe--Launching a Simple Application," beginning on page 292.)- You include MacApp's printing-related resources into your application by adding the following line to your resource definition file:
include "Printing.rsrc" not 'ckid';The words
not 'ckid'
ensure that you will not copy the resource that is used for source code control.- MacApp supplies the
TPrintHandler
andTStdPrintHandler
behavior classes to handle printing. To make a view printable, you create and initialize a print-handler object, passing it a reference to the view and optionally a reference to the view's document. Most applications do this in theDoMakeViews
method of a document class. (See "Recipe--Adding a Standard Print Handler to a View," following this outline.)
- IMPORTANT
- Be sure to attach the print handler to the correct view. If you attach it to a window, you'll get a printout of the window as it looks on the screen, including scroll bars. You probably want to attach the print handler to the top-level view inside the primary scroller in the window.
- In its initialization method, a standard print handler attaches itself to its document, if it has one, by calling the document's
AttachPrintHandler
method. The print handler attaches itself to its view by calling the view'sAttachPrintHandler
method.- The
TDocument::AttachPrintHandler
method creates aTPrintMenuBehavior
object and adds it to the document. This object handles printing menu commands in itsDoMenuCommand
method by calling theDoPrintCommand
method of the print handler.- The
TView::AttachPrintHandler
method adds a print adorner to the view, adds the print handler to the view, and calls the view'sDoCheckPrinter
method, which causes the print handler to update itself based on the current machine configuration.- You modify printing behavior by one or both of the following mechanisms (see "Recipe--Defining a Custom Print Handler to Modify Page Strips," beginning on page 537):
Recipe--Adding a Standard Print Handler to a View
You normally associate a print-handler object with a view in theDoMakeViews
method of your document class. You create aTStdPrintHandler
object with thenew
routine and call theIStdPrintHandler
method to initialize the object, as shown in theDoMakeViews
method from the IconEdit sample application. The code that creates the print handler is shown in boldface:
void TIconDocument::DoMakeViews(Boolean forPrinting) // Override. { TWindow* aWindow; TIconEditView* iconView; if (forPrinting) { // If for printing, you need only the view. iconView = (TIconEditView *)gViewServer->DoCreateViews (this, NULL, kIconEditViewId, gZeroVPt); FailNIL(iconView); } else { // Otherwise, you need the view and a window. aWindow = gViewServer->NewTemplateWindow(kIconWindowId, this); FailNIL(aWindow); // Get reference to the view. iconView = (TIconEditView*)(aWindow->FindSubView('ICON')); } fIconView = iconView; // Create and initialize a print handler. Pass references to the // document and view to be printed, the dot type, and the // horizontal and vertical page size. TStdPrintHandler*aPrintHandler; aPrintHandler = new TStdPrintHandler; aPrintHandler->IStdPrintHandler(this, iconView, !kSquareDots, kFixedSize, kFixedSize); } // TIconDocument::DoMakeViewsThe parameters to the IStdPrintHandler method are used as follows:
The call to
itsDocument
- The document associated with the print handler (if any).
itsView
- The view to which the print handler is attached.
itsSquareDots
- If
TRUE
, a default print record for ImageWriter printers uses square dots ("tall adjusted") rather than rectangular ones. This sacrifices some resolution, but ensures that graphics will not appear distorted. You can use the MacApp constantkSquareDots
(or!kSquareDots
).itsHFixedSize
- A flag that specifies whether the horizontal size of the printed page is fixed. You can use the MacApp constant
kFixedSize
(or!kFixedSize
).itsVFixedSize
- A flag that specifies whether the vertical size of the printed page is fixed. Again, you can use the MacApp constant
kFixedSize
(or!kFixedSize
).IStdPrintHandler
shown above attaches the print handler to the view and document objects. The attached print handler has these characteristics:
That's all you need to do to create and attach a print-handler object. The two following sections provide additional information about how the print handler is attached to its document and view objects.
- The default page margins are set to 1 inch on all sides.
- The horizontal and vertical page sizes are fixed.
- The page direction is set to vertical--page 2 is below page 1, page 3 below page 2, and so on (rather than horizontal, with some pages appearing side by side).
- The Print, Print One, Page Setup, and Show Page Breaks menu commands will normally be enabled (if the corresponding menu items are defined in the application's resource definition file).
Attaching a Print Handler to Its View
TheIStdPrintHandler
method contains this call:
fView->AttachPrintHandler(this);TheTView::AttachPrintHandler
method attaches the print handler to the view. As a result, the view can be printed whenever it is targeted. Assume, for example, that a user chooses the Print command while the document is open and that the view with an attached print handler is the current target.
This same mechanism is used to process all Print menu commands (described in "Handling Print Menu Commands," beginning on page 528).
- The application's
MenuEvent
method gets information about the menu choice and calls theHandleMenuCommand
method of the current target object, which is the view.- The
HandleMenuCommand
method gives any behaviors attached to the view a chance to handle the command.- The
TStdPrintHandler
behavior'sDoMenuCommand
method handles the menu command by calling itsDoPrintCommand
method.
The
AttachPrintHandler
method also addsgPrintAdorner
to the view for possible use in drawing page-break feedback. Page breaks are described beginning on page 235 and screen feedback is described beginning on page 238.Attaching a Print Handler to Its Document
TheIStdPrintHandler
method also contains this code:
if (itsDocument) itsDocument->AttachPrintHandler(this); // Passes the print handler.TheTDocument::
AttachPrintHandler method creates aTPrintMenuBehavior
object, initializes it with the passed print handler, and adds the behavior to the document. As a result, the Print menu behavior object is able to handle any print commands directed to the document. For example, when a user prints a document from the Finder, the following steps take place:
In this case, the document prints the view, even though the view is not currently targeted.
- The application object's
DoScriptCommand
method creates and posts aTPDocCommand
object.- The
DoIt
method of theTPDocCommand
object calls the application'sPrintDocuments
method.- The
PrintDocuments
method callsDoMakeDocument
to create a document and calls the document'sDoMakeViews
method to create its views.- The
DoMakeViews
method creates a print handler and callsIStdPrintHandler
, which attaches the print handler to the document.- The
DoIt
method of theTPDocCommand
object calls the document'sHandleMenuCommand
method.- The
HandleMenuCommand
method gives any behaviors attached to the document a chance to handle the command.- The
TPrintMenuBehavior
behavior'sDoMenuCommand
method handles the menu command by calling its print handler'sDoPrintCommand
method.
- Note
- The
TPrintMenuBehavior
class uses an algorithm in itsDoMenuCommand
method that allows a document to arbitrate between multipleTPrintMenuBehavior
objects, representing multiple printable views owned by the document.Recipe--Changing Default Margin Settings
The default page margins for a standard print-handler object are set to 1 inch on all sides. There are two ways to change the default margin settings.Using Minimal Margins
You can instruct the print handler to use the minimal margins on the printer device. This means that the margins are maintained at the size necessary to have thetheInterior
area of the page (described in "Page Definition Areas," beginning on page 235) coincide exactly with the printable area of the page on the printer. To specify minimal margins, add the following line to yourDoMakeViews
method after calling IStdPrintHandler:
aPrintHandler->fMinimalMargins = TRUE;Installing New Margins
You can also call theInstallMargins
method of your print handler to modify the margin settings. To set the margins to 2 inches on each side, you can use code like the following, called from within a view method:
// There are 72 points in an inch. VCoordinate dpi = 72; // Use 2 * dpi to set all four margins to 2 inches. VRect modifiedMargins(2 * dpi, 2 * dpi, -2 * dpi, -2 * dpi); TStdPrintHandler * itsPrintHandler = NULL; // Get the view's print handler, set the new margins, and call // DoPagination to force recalculation of page breaks. itsPrintHandler = (TStdPrintHandler *) this->GetPrintHandler(); if (itsPrintHandler) { itsPrintHandler->InstallMargins(modifiedMargins, FALSE); this->DoPagination(); }The InstallMargins method is defined as follows:
void TStdPrintHandler::InstallMargins(const VRect& newMargins, Boolean areMinimalMargins)ThenewMargins
rectangle defines the new values for thetheMargins
rectangle in thefPageArea
record of the print handler. If theareMinimalMargins
parameter isTRUE
, the margins are reset to the minimal margins for the printer as described in the previous section and thenewMargins
parameter is ignored.After you call the print handler's
InstallMargins
method, you call theDoPagination
method of its associated view object to force the printable area of the page (fInteriorRect
) to be recalculated; this in turn causes the page breaks in the view to be recalculated.Recipe--Showing Page Breaks in a View
MacApp uses a model that divides a view into vertical and horizontal page strips. You can think of the view as being divided by horizontal and vertical lines, called page breaks, which produce a "checkerboard" of pages. MacApp'sTStdPrintHandler
class displays these page breaks in a view based on the current value of the print handler'sfShowBreaks
field. When the value isFALSE
, no page breaks are shown. When the value isTRUE
, the print handler'sDrawPrintFeedback
method calls the view'sDoDrawPageBreak
method, which in turn calls the print handler'sDrawPageBreak
method to show page breaks.To add the Show Page Breaks command to your application, you add a line like the following to a
'CMNU
' definition in your resource definition file. This line is from the Edit'CMNU'
resource in the fileIconEdit.r
:
/* [12] */"Show Page Breaks",noIcon,noKey,noMark,plain,cShowBreaksThe Show Page Breaks menu command is enabled by the print handler'sDoSetupPrintMenus
method. When a user chooses the Show Page Breaks menu command, the current state of showing page breaks is toggled. If breaks were previously shown, the view is redrawn without page breaks and the checkmark is removed from the Show Page Breaks menu command. If breaks were not shown, the view is redrawn to show page breaks and the Show Page Breaks menu command is checked.Recipe--Defining a Custom Print Handler to Modify
As mentioned in the previous recipe, MacApp uses a model that divides a view into vertical and horizontal page strips, divided by vertical and horizontal page breaks. A vertical column of pages, one page wide, is called a vertical page strip. A horizontal row of pages, one page high, is called a horizontal page strip. For more information, see "Page Strips and Page Breaks," beginning on page 237.
Page StripsThe Calc sample application provides an example of a custom print handler that modifies MacApp's default page strips. Depending on the application, a custom print handler may need to override either the
CalcViewPerPage
method or theDoBreakFollowing
method to modify page strips. Calc is an interesting example because it happens to override both. Its custom print handler works with its spreadsheet view to calculate page breaks, using the cell height to determine how many rows will fit on a page.To define a custom print handler to modify page strips, you perform these steps:
The sample code shown in this recipe is from the Calc application.
- Define a subclass of
TStdPrintHandler
.- Override the
CalcViewPerPage
method in your print handler.- Override the
DoBreakFollowing
method of your view class.
Define a Subclass of TStdPrintHandler
The Calc application defines a subclass of TStdPrintHandler to modify MacApp's default page strips. The following is a partial listing of the TCalcPrintHandler class definition:
class TCalcPrintHandler: public TStdPrintHandler { MA_DECLARE_CLASS; public: CommandNumberfCmdNumber; virtual ~TCalcPrintHandler(); // Destructor. virtual void CalcViewPerPage(VPoint& amtPerPage); // Override. // Some code not shown. };The CalcViewPerPage method is shown in the next section.Override the CalcViewPerPage Method in Your Print Handler
When a user chooses the Print command to print a document, the flow of control is somewhat complex. The view's print-handler object handles the menu command by creating and posting aTPrintCommand
object. When the command is performed, itsDoIt
method calls theDoCalcPageStrips
method, which results in a call to the view'sDoBreakFollowing
method (shown in the next recipe step), followed by a call to the print handler'sCalcViewPerPage
method.The
TCalcPrintHandler
class overridesCalcViewPerPage
and uses the cell height to compute the number of rows that fit in a page:
void TCalcPrintHandler::CalcViewPerPage(VPoint& amtPerPage) // Override. { long noOfRows; Inherited::CalcViewPerPage(amtPerPage); noOfRows = amtPerPage.v / kCellHeight; amtPerPage.v = noOfRows * kCellHeight; }This approach works for Calc because Calc allows variable column widths but keeps row heights fixed. If Calc allowed variable row heights, there would be no point in overridingCalcViewPerPage
, because only theDoBreakFollowing
method can handle different amounts of information per page.The value returned by CalcViewPerPage is stored in the print handler's
fViewPerPage
field, which is used by the method shown in the next step.Override the DoBreakFollowing Method of Your View Class
A view's DoBreakFollowing method determines where page breaks should occur for printing when the print area may vary from one page to another. In the Calc application, the spreadsheet view,TCellsView
, overrides the DoBreakFollowing method and calculates page breaks based on the cell height (for vertical page strips) or page width (for horizontal page strips). The DoBreakFollowing method is shown on the following pages.
VCoordinate TCellsView::DoBreakFollowing(VHSelectvhs, VCoordinatepreviousBreak, Boolean& /*Automatic*/) // Override. { VCoordinate thisBreak = 0; short rowsPerPage; short totalWidth; short width; short pageWidth; VRect extentRect; ColumnNumber firstCol; ColumnNumber c; GetExtent(extentRect); switch (vhs) { case hSel: { rowsPerPage = (short)(GetPrintHandler()->GetViewPerPage().v / kCellHeight); thisBreak = previousBreak + (rowsPerPage * kCellHeight); break; } case vSel: { // Note: GetViewPerPage returns the value computed by CalcViewPerPage. pageWidth = (short)GetPrintHandler()->GetViewPerPage().h; totalWidth = 0; if (previousBreak == 0) firstCol = 1; else { c = 0; width = 0; do { ++c; width = width + GetColWidth(c); } while (width < previousBreak); firstCol = c + 1; } for (c = firstCol; c <= fCalcDocument->fNoOfColumns; c++) { width = this->GetColWidth(c); if (totalWidth + width <= pageWidth) totalWidth += width; else { thisBreak = previousBreak + totalWidth; break; } } // Did we set it? if (thisBreak == 0) thisBreak = previousBreak + totalWidth; // Prevent infinite loop from resizing a far-right column! if (thisBreak == previousBreak) thisBreak = extentRect.right; break; } } thisBreak = Min(thisBreak, extentRect[botRight][gOrthogonal[vhs]]); return thisBreak; }The DoBreakFollowing method determines where the next vertical or horizontal break should be located, based on the passed value of the previous break. For horizontal page strips, DoBreakFollowing calls the print handler'sGetViewPerPage
method.GetViewPerPage
returns the value stored in thefViewPerPage
field, which was computed by CalcViewPerPage in the previous recipe step.
- Note
- To use your custom print handler, you must install it, as described in "Recipe--Adding a Standard Print Handler to a View," beginning on page 532.
Recipe--Turning on Debugging Support for Printing
You can use MacApp's debugging code to provide additional support for debugging printing. To do so, you perform the following steps:
MacApp will automatically display page numbers within each printable view at the intersection of each adjoining page. When you print a document, the output will include frames showing the maximum printable page (
- Build a debug version of your application. Appendix A describes how to build a version of your application that includes MacApp's debugging code.
- Run the application.
- Choose the Show Debug Flags Window menu option from the Debug menu.
- Check the Debug Printing checkbox. This causes the value of the
gDebugPrinting
flag to be set toTRUE
.
fInkRect
) and the view's actual drawing area (fInteriorRect
).You should also be prepared to land in your low-level debugger with additional printing-related debug statements. Although this may occasionally catch you by surprise, the information can be quite helpful in debugging printing problems.