Previous Book Contents Book Index Next

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


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.

  1. To initialize MacApp's UPrinting unit, you call the InitUPrinting routine from your main routine, after calling InitUMacApp and before creating your application object. (See "Recipe--Launching a Simple Application," beginning on page 292.)
  2. 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.

  3. MacApp supplies the TPrintHandler and TStdPrintHandler 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 the DoMakeViews 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.
  1. 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's AttachPrintHandler method.
  2. The TDocument::AttachPrintHandler method creates a TPrintMenuBehavior object and adds it to the document. This object handles printing menu commands in its DoMenuCommand method by calling the DoPrintCommand method of the print handler.
  3. The TView::AttachPrintHandler method adds a print adorner to the view, adds the print handler to the view, and calls the view's DoCheckPrinter method, which causes the print handler to update itself based on the current machine configuration.
  4. 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):

    • Define a subclass of TStdPrintHandler or, less commonly, TPrintHandler, and override methods such as BreakFollowing.
    • Define a view subclass and override methods such as DoBreakFollowing.

Recipe--Adding a Standard Print Handler to a View

You normally associate a print-handler object with a view in the DoMakeViews method of your document class. You create a TStdPrintHandler object with the new routine and call the IStdPrintHandler method to initialize the object, as shown in the DoMakeViews 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::DoMakeViews
The parameters to the IStdPrintHandler method are used as follows:

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 constant kSquareDots (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).
The call to 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.

Attaching a Print Handler to Its View

The IStdPrintHandler method contains this call:

fView->AttachPrintHandler(this);
The TView::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.

  1. The application's MenuEvent method gets information about the menu choice and calls the HandleMenuCommand method of the current target object, which is the view.
  2. The HandleMenuCommand method gives any behaviors attached to the view a chance to handle the command.
  3. The TStdPrintHandler behavior's DoMenuCommand method handles the menu command by calling its DoPrintCommand method.

This same mechanism is used to process all Print menu commands (described in "Handling Print Menu Commands," beginning on page 528).

The AttachPrintHandler method also adds gPrintAdorner 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

The IStdPrintHandler method also contains this code:

if (itsDocument)
   itsDocument->AttachPrintHandler(this); // Passes the print handler.
The TDocument::AttachPrintHandler method creates a TPrintMenuBehavior 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:

  1. The application object's DoScriptCommand method creates and posts a TPDocCommand object.
  2. The DoIt method of the TPDocCommand object calls the application's PrintDocuments method.
  3. The PrintDocuments method calls DoMakeDocument to create a document and calls the document's DoMakeViews method to create its views.
  4. The DoMakeViews method creates a print handler and calls IStdPrintHandler, which attaches the print handler to the document.
  5. The DoIt method of the TPDocCommand object calls the document's HandleMenuCommand method.
  6. The HandleMenuCommand method gives any behaviors attached to the document a chance to handle the command.
  7. The TPrintMenuBehavior behavior's DoMenuCommand method handles the menu command by calling its print handler's DoPrintCommand method.

In this case, the document prints the view, even though the view is not currently targeted.

Note
The TPrintMenuBehavior class uses an algorithm in its DoMenuCommand method that allows a document to arbitrate between multiple TPrintMenuBehavior 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 the theInterior 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 your DoMakeViews method after calling IStdPrintHandler:

aPrintHandler->fMinimalMargins = TRUE;

Installing New Margins

You can also call the InstallMargins 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)
The newMargins rectangle defines the new values for the theMargins rectangle in the fPageArea record of the print handler. If the areMinimalMargins parameter is TRUE, the margins are reset to the minimal margins for the printer as described in the previous section and the newMargins parameter is ignored.

After you call the print handler's InstallMargins method, you call the DoPagination 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's TStdPrintHandler class displays these page breaks in a view based on the current value of the print handler's fShowBreaks field. When the value is FALSE, no page breaks are shown. When the value is TRUE, the print handler's DrawPrintFeedback method calls the view's DoDrawPageBreak method, which in turn calls the print handler's DrawPageBreak 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 file IconEdit.r:

/* [12] */"Show Page Breaks",noIcon,noKey,noMark,plain,cShowBreaks
The Show Page Breaks menu command is enabled by the print handler's DoSetupPrintMenus 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
Page Strips

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.

The 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 the DoBreakFollowing 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:

  1. Define a subclass of TStdPrintHandler.
  2. Override the CalcViewPerPage method in your print handler.
  3. Override the DoBreakFollowing method of your view class.

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

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 a TPrintCommand object. When the command is performed, its DoIt method calls the Print method of the print handler. The Print method calls the view's DoCalcPageStrips method, which results in a call to the view's DoBreakFollowing method (shown in the next recipe step), followed by a call to the print handler's CalcViewPerPage method.

The TCalcPrintHandler class overrides CalcViewPerPage 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 overriding CalcViewPerPage, because only the DoBreakFollowing 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's GetViewPerPage method. GetViewPerPage returns the value stored in the fViewPerPage 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:

  1. Build a debug version of your application. Appendix A describes how to build a version of your application that includes MacApp's debugging code.
  2. Run the application.
  3. Choose the Show Debug Flags Window menu option from the Debug menu.
  4. Check the Debug Printing checkbox. This causes the value of the gDebugPrinting flag to be set to TRUE.

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 (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.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996