TweetFollow Us on Twitter

January 94 - Printing in MacApp 3.0.1

Printing in MacApp 3.0.1

Rich Gillam

Several years ago, when I worked at ElvisWare (obviously not their real name), one of the principals of the firm was a guy with a really strong sales background. He had little technical concept, although he thought he knew what he was talking about, and he had a real "promise them everything, but give them ElvisWare" approach to talking to potential customers. It was amazing sometimes the things he'd tell people our next great product was going to do.

But the thing that always stands out was one pet phrase of his: "It's only a couple lines of code, right?" Usually he'd ask us this just after telling someone that one of our database-related products would have a multi-user capability, or something else like that. Sometimes, he'd come in with pseudocode he'd written to prove that it would be easy, things like

#define multiuser              true

and then wonder why the developers were all laughing.

As you can imagine, that made me pretty wary of the phrase "it's only a couple lines of code." So naturally, when I started reading the MacApp documentation and got to the printing section, which seemed to read suspiciously close to "it's only a couple lines of code," I was skeptical.

"it's only a couple lines of code"

So let's take a look at just what you get for your couple lines of code. Figure 1 shows a fairly typical-looking spreadsheet/database-style report window. We have two grid views, one representing the list and one representing the column headings. Each grid view is contained in a scroller, the list in a TPrimaryScroller and the column headings in a TSecondaryScroller, and the TPrimaryScroller has two TScrollerScrollBars hanging off of it. Finally, there's another view across the top that represents a heading of some type.

The MacApp documentation implies that all you have to do is attach a TStdPrintHandler to the view you want to print and life is happy. In fact, they intimate you probably won't even need to subclass it to do most things. So you go into AdLib and attach a TStdPrintHandler to the list view. After adding the macroDontDeadStrip call to your main routine, recompiling, and relinking, you run the program again. Sure enough, the Print command on the menu is now enabled. You choose it, get the Standard Print Dialog, and click OK. The Print Monitor window opens, your printer whirs, and you're feeling good. Maybe printing in MacApp really is that easy.

Then you go and get the page out of the printer. What you've got looks like Figure 2. Like pretty much everything else in MacApp, there's a lot more to this printing stuff than meets the eye.

You wanted something like the window, where the column headings appeared over the list, and the right column headings were repeated on every page. The heading view across the top of the window should have been printed on every page as well. Instead, all you got was the data in the list itself. Pretty bare bones. Pretty much what you'd expect for a few lines of code.

Maybe you attached the print handler to the wrong view. You go back and attach it to the window and try again. Now you get Figure 3. All of the views are there now, but you've gotten only the parts that are showing on the screen, and you also printed the scroll bars. In fact, what you have now looks suspiciously like a screen shot, except that the window frame didn't print. Now you're sure: Maybe MacApp's printing is just a little too WYSIWYG. If you want output that's truly tailored to the printed page instead of the screen, you're going to have to write more than a couple lines of code. In this article, we'll try to cover this situation and some others and look at some of the more common things you might want to do when you print, and how you might do them in MacApp.

disclaimer

Before I go on, a quick disclaimer. There is without question more than one way to do just about everything I'm going to talk about here. I'm not going to profess that my suggestions are the best or the most object-oriented ways of achieving these results. But they work; I'm using them in my current project. In almost all cases, the example code was written especially for this article, based on actual production code. It has not been tested and may have developed errors in the process of making it generic. Take the example code as basically being for illustrative purposes only. If you have improvements or criticisms, I'd like to hear them so I can improve this stuff.

Headers and footers

Probably the most common thing people will want to add to a document when they print it are headers and footers. Even the simplest of documents probably benefits from the addition of page numbers. Fortunately, headers and footers really are quite easy to do in MacApp.

Say, for example, that you want each page of your document to have a footer like the one shown in Figure 4. One way to do this is to override TStdPrintHandler: :AdornPage(). the TStdPrintHandler defines four rectangles that it uses to keep track of different parts of the page. These are stored collectively in a struct called fPageAreas. theInk represents the entire printable area of the page, and its upper left-hand corner is always at (0, 0). thePaper represents the whole page, including the non-printable area, relative to theInk. It will always be either the same size as theInk (in which case they're equal), or bigger, in which case its upper left-hand corner will have negative coordinates. theInterior represents the area within theInk where the view attached to TStdPrintHandler will actually be drawn. theMargins defines the difference between theInk and theInterior, with the default being an inch (72 pixels) on each side. Note that because theInterior.right and theInterior.bottom are less than theInk.right and theInk.bottom, theMargins.right and theMargins.bottom are always negative (this got me at first). The margins, then, are the part of theInk not occupied by theInterior.

When AdornPage() is called, the print handler has already taken care of setting up all the coordinate systems and clipping for you. You are focused on theInk and can draw anywhere within that rectangle in that rectangle's coordinate system. Your AdornPage() method, then, doesn't have to worry about focusing or anything like that; it just has to draw, as a TView::Draw() override would. The only difference is that instead of calling this->GetExtent() to find your bearings, you would refer to fPageAreas.theInk.

So the code to draw Figure 4 would look something like this:

TMyPrintHander :: AdornPage()
{
    VRect           marginRect, footerRect;
    VPoint          offset(0, 0);
    TextStyle       footerTextStyle;
    long            dateTime;
    CStr255         text;
    FontInfo        fontInfo;
    
    // load up the proper text style and measure it
    MAGetTextStyle(kFooterTextStyle, footerTextStyle);
    SetPortTextStyle(footerTextStyle);
    GetFontInfo(fontInfo);
    
    // figure out where we're going to draw (centered
    // vertically, pulled in 1/2 inch from the edge
    // horizontally)
    marginRect = fPageAreas.theInk;
    marginRect.top = fPageAreas.theInterior.bottom;
    footerRect = marginRect;
    footerRect.bottom = footerRect.top + fontInfo.ascent +
        fontInfo.descent + fontInfo.leading;
    offset.v = (marginRect.GetLength(vSel) - footerRect.
        GetLength(vSel)) / 2;
    footerRect += offset;
    offset = VPoint(5, 0);
    footerRect.Inset(offset);
    
    // draw the footer
    MoveTo(footerRect.left, footerRect.top - 1);
    LineTo(footerRect.right - 1, footerRect.top - 1);
    
    GetDateTime(dateTime);
    IUDateString(dateTime, longDate, text);
    MoveTo(footerRect.left, footerRect.bottom);
    DrawString(text);
    
    NumToString(fFocusedPage, text);
    MAParamText("#", text);
    GetIndString(text, kPrintingStrings, kPageNumberString);
    MAReplaceText(text);
    MoveTo(footerRect.right - StringWidth(text), footerRect.bottom);
    DrawString(text);
}

That's all there is to this. Note, by the way, that you can get the current page number from the fFocusedPage instance variable of TStdPrintHandler.

You can also do this kind of thing with a page adorner. I prefer this method because it allows me easier access to TStdPrintHandler's instance variables, and because it makes life easier when you're doing more complicated types of drawing.

Putting a header on the first page

What if you only want the header to appear on the first page? Well, if it's something simple and you can print it in the margins, you can do something like what we did above: Have your AdornPage() method do the drawing, and just test fFocusedPage to see what page you're on.

But if you want to do something more exotic, it takes more work. If you want the header to appear in the page interior, as you might if the header is of variable height, or if the header is already implemented as a view in a window somewhere (one good example is an email message with a distribution list and the message text in different panels), you have to use a more complicated approach.

It's basically not worth the trouble to try and do this kind of printing with the views in the window. Instead, create a new view hierarchy resource that defines exactly how you want things to print. In the example I gave above, you have two views which size horizontally to meet the page and vertically to hold their contents, and they're stacked on top of each other. So you draw a view hierarchy like that, making each view sizePage horizontally and sizeVariable vertically.

Every view hierarchy needs a single view to form the top of it, so we need a containing view. This will be the view the print handler is actually going to print. MacApp gives us no way to define algorithmic positioning of views (enabling us, for example, to keep controls in the window's lower-left corner without writing code), nor do they give us a sizeRelSubViews size determiner. We have to do these things ourselves.

TExpandingView

I created a class to manage this type of window. The work of this view is done in two places: CalcMinFrame(), which figures out how big the view is based on its subviews, and SubViewChangedFrame(), which moves the views around when one of them changes size. They look like this:
pascal void TExpandingView :: CalcMinFrame( VRect& minFrame)
{
    VHSelect            vhs;
    
    // start out with the view's current size (since it
    // won't change in one direction)
    this->GetFrame(minFrame);

    // then do the following for each direction
    for (vhs = vSel; vhs <= hSel; vhs++) {
        if (fSizeDeterminer[vhs] == sizeVariable) {
            CSubViewIterator    iter(this);
            TView*              curView;
            VRect               frame;
            VCoordinate         accumSize = 0;
            VCoordinate         difference;
            
            // add up the sizes of all the subviews in the
            // current direction
            for (curView = iter.FirstSubView(); iter.More(); 
                    curView = iter.NextSubView()) {
                curView->GetFrame(frame);
                accumSize += frame.GetLength(vhs);
            }

            // remember how big everything was before
            this->GetFrame(frame);
            difference = frame.GetLength(vhs) - accumSize;
            accumSize = 0;

            // recalculate the sizes of all the subviews, and
            // add up their sizes again
            for (curView = iter.FirstSubView(); iter.More(); 
                    curView = iter.NextSubView()) {
                curView->AdjustFrame();
                curView->GetFrame(frame);
                accumSize += frame.GetLength(vhs);
            }

            // then add the new total size to the difference of
            // the old total size and the old frame size
            // and make that the new frame size
            if (vhs == vSel)
                minFrame.bottom = minFrame.top + accumSize +
                                    difference;
            else
                minFrame.right = minFrame.left + accumSize +
                                    difference;
        }
    }
}

pascal void TExpandingView :: SubViewChangedFrame(
                TView*              theSubView,
                const VRect&    oldFrame,
                const VRect&    newFrame,
                Boolean             /*invalidate*/)
{
    VHSelect            vhs;
    VPoint              oldSize, newSize;
    VRect               myOldFrame, myNewFrame;
    VCoordinate         difference;
    
    // remember how big we were before
    oldSize = newSize = this->fSize;

    // then, for each direction, bump our size by the
    // amount the subview changed 
    for (vhs = vSel; vhs <= hSel; vhs++) {
        if (fSizeDeterminer[vhs] == sizeVariable) {
            difference = newFrame.GetLength(vhs) -
                                oldFrame.GetLength(vhs);
            newSize[vhs] += difference;

            // move all the subviews according to the
            // difference
            CSubviewIterator    iter(this);
            for (curView = iter.FirstSubView(); iter.More(); 
                    curView = iter.NextSubView()) {
                if (vhs == vSel) {
                    if (curView.top > theSubView.bottom)
                        curView->fLocation.v += difference;
                }
                else {
                    if (curView.left > theSubView.right)
                        curView->fLocation.h += difference;
                }
            }
        }
    }

    // signal the frame change to the parent view,
    // if necessary
    if (oldSize.h != newSize.h || oldSize.v != newSize.v) {
        this->GetFrame(myOldFrame);
        this->fSize = newSize;
        this->GetFrame(myNewFrame);
        if (fSuperView != NULL)
            fSuperView->SubViewChangedFrame(this, myOldFrame,
                                myNewFrame, false);
    }
}

The parent view is then a TExpandingView. To tell this view which is the expanding direction, set the size determiner for that direction to sizeVariable and the size determiner for the other direction to anything other then sizeVariable. Be warned that the code above is designed to deal with the simplest case only: a series of views stacked in a single direction. It will barf on a view hierarchy where you have views that are next to each other in the direction you want it to expand, or where you want it to expand in both directions.

Creating and using a printing view hierarchy

The other part of this is where you create this new view hierarchy. I did this by overriding TStdPrintHandler's Print() method. Attach the print handler to the content view of the window you're printing. Then, in the Print() method, before calling the inherited Print() routine, save off your fView parameter (that content view), call gViewServer->DoCreateViews() to create the printing view hierarchy, set fView to point to it, and call the new view's AddBehavior() method to attach it to the print handler (you don't have to remove it from the view it was attached to). Now call the new view's AdjustFrames() method twice (because the vertical dimension of a text view depends on its width and MacApp calculates the height before the width, you have to call AdjustFrames() twice, once to get the width right and again to get the height right). Then you call inherited.

After the inherited call, you remove the print handler from the printing view hierarchy with RemoveBehavior(), restore fView to its original value, and throw away the printing view hierarchy. It all looks like this:

pascal void TMyPrintHandler :: Print(
        CommandNumber       itsCommandNumber,
        Boolean&        proceed)
{
    TView*              view = NULL;
    TView*              saveView;

    // remember our original view
    saveView = fView;
    
    // create a special view hierarchy for printing, and
    // make it the view this print handler is attached to
    view = gViewServer->DoCreateViews(fDocument, NULL,
                        kTextWindowPrintVH, VPoint(0, 0));
    fView = view;
    view->AddBehavior(this);
    
    // calculate the right view sizes
    view->AdjustFrame();
    view->AdjustFrame();

    // call inherited to actually do the printing
    inherited :: Print(itsCommandNumber, proceed);

    // and restore the old hooks
    view->RemoveBehavior(this);
    view->Free();
    fOwner = (TEventHandler*)(fView = saveView);
}

page breaking

Page breaking is actually one of the areas where the MacApp documentation is relatively decent, but I'll cover it here in the interest of completeness.

Again, if you just use the generic TStdPrintHandler attached to a view without any enhancement on your part, you'll likely discover that page breaks come in places where you don't really want them. There are two primary ways to fix this, depending on the type of view you're printing.

If the view has the possible locations for page breaks occurring at regular intervals (such as for the rows in the chart in Figure 1), you can get by pretty simply. All you have to do is override TView::DoCalcViewPerPage() in your TView subclass. The way I usually do this is to call the inherited DoCalcViewPerPage() first to figure out where the page break would go if you weren't concerned about breaking lines. DoCalcViewPerPage returns a VPoint that shows how many pixels of the view in each direction can be printed on a page. Take the value returned to you by the inherited method, and decrement it by itself modulo the height of a row. For the chart example, that would look something like this:

pascal void TMyView :: DoCalcViewPerPage(  VPoint&
                viewPerPage)
{
    inherited :: DoCalcViewPerPage(viewPerPage);
    if (viewPerPage.v % this->GetRowHeight() != 0)
        viewPerPage.v -= viewPerPage.v %
                            this->GetRowHeight();
}

You'll have to do that calculation in each direction you have regular page breaks.

If you have irregular page breaks, such as the columns in the chart in Figure 1, the job is a little more complicated. Instead of overriding DoCalcViewPerPage(), you override DoBreakFollowing(). DoBreakFollowing() takes two parameters, a direction and the location of the last page break calculated by DoBreakFollowing(), and returns the location of the next page break in the view's coordinate system and a flag telling whether it was algorithmically generated or placed by the user.

In the chart example in Figure 1, you would go through and accumulate columns until you find the position of previousBreak. Then you accumulate column widths after previousBreak until the total is greater than the print handler's fViewPerPage field in the direction you're going (fViewPerPage is what DoCalcViewPerPage() calculated: the maximum number of pixels worth of view content that can be printed on the page). For our chart example, DoBreakFollowing() would look something like this:

pascal VCoordinate TMyView :: DoBreakFollowing(
                VHSelect            vhs,
                VCoordinate         previousBreak,
                Boolean&            automatic)
{
    register short      i;
    VCoordinate         returnVal;
    VCoordinate         viewPerPage;
    VCoordinate         firstOnPage;
    VCoordinate         colWidth;
    
    // this routine shouldn't have to worry about
    // horizontal page breaks
    if (vhs == hSel)
        return inherited :: DoBreakFollowing(vhs,
                            previousBreak, automatic);
        
    // figure out the maximum number of pixels on the page
    // (so we know what fits)
    viewPerPage = this->GetPrintHandler()->fViewPerPage.h;
    
    // loop through the columns
    returnVal = 0;
    firstOnPage = -1;
    for (i=1; i <= NumberOfColumns(); i++) {
        
        // keep track of where the first break on the page in
        // question is
        if (returnVal >= previousBreak && firstOnPage == -1)
            firstOnPage = returnVal;
        
        // if the column we're on won't fit entirely on the
        // current page (if we're up to that page yet), break
        // out of the loop (which will put the break before
        // it); if the column we're on is of 0 width, skip it
        // and go on to the next one
        colWidth = this->GetColWidth(i);
        if (colWidth == 0)
            continue;
        if (firstOnPage != -1 && (returnVal + colWidth) -
                            firstOnPage > viewPerPage)
            break;
        
        // otherwise add this column's width to the total and
        // advance to the next column
        returnVal += colWidth;
    }
    if (i > NumberOfColumns())
        returnVal = fSize.h;
    
    // return the right coordinate of the last column to
    // fit on the page in question, unless that column is
    // too wide to fit on the page by itself, in which case
    // we go ahead and divide it
    automatic = true;
    if (returnVal > previousBreak && NumberOfColumns()
                        != 0)
        return returnVal;
    else
        return previousBreak + viewPerPage;
}

A couple caveats: First, keep in mind that the vhs parameter refers to the direction the page breaks run, not to the direction you're moving through the view to find the page break positions (i.e., page breaks between columns are vertical page breaks). Second, if this routine somehow gets out of sync between calls to get different page break positions, you'll generate ProgramBreak() messages out the wazoo in MacApp.

Now, of course, you need to tell the print handler whether the page breaks are regular or irregular in each direction. IStdPrintHandler() has parameters for this. When you set up the print handler, pass true for itsHFixedSize or itsVFixedSize if the page breaks in that direction come at regular intervals and false if they don't. (Here, true to form, things are backwards from how they are in DoBreakFollowing(): itsHFixedSize controls whether vertical page breaks are regular, and itsVFixedSize controls whether horizontal page breaks are regular.) Note, by the way, that because IStdPrintHandler() takes a bunch of different parameters from IBehavior(), it's probably not a good idea to add the print handler as a behavior in Ad Lib or IcePick. If you do, you'll still have to go back, find it, and set up these parameters manually in your code.

Of course, if you use the TExpandingView, you now have to have it manage page breaking for all of its subviews. It would be nice to have the subviews be responsible for managing page breaks in their own territory and just let the TExpandingView manage the boundary conditions. You can do that as follows:

pascal VCoordinate TExpandingView :: DoBreakFollowing(
            VHSelect        vhs,
            VCoordinate     previousBreak,
            Boolean&    automatic)
{
    VHSelect        ortho;
    
    ortho = gOrthogonal[vhs];

    // we only worry about page breaking in the direction
    // we're sizeVariable
    if (fSizeDeterminer[ortho] != sizeVariable)
        return inherited :: DoBreakFollowing(vhs,
                            previousBreak, automatic);
    
    // if we only have one subview, pass the call on to it
    // (this assumes it isn't inset or offset much from
    // this view)
    if (fSubViews->GetSize() == 1) {
        TView*              subView;
        VCoordinate         returnVal;
        VRect               frame;
        
        subView = (TView*)fSubViews->At(1);
        if (previousBreak != 0)
            previousBreak -= subView->fLocation[ortho];
        subView->GetFrame(frame);
        returnVal = subView->DoBreakFollowing(vhs,
                            previousBreak, automatic);
        returnVal += subView->fLocation[ortho];
        if (returnVal == frame[botRight][ortho]) {
            this->GetExtent(frame);
            return frame[botRight][ortho];
        }
        else
            return returnVal;
    }
    
    // otherwise, we need to iterate through the subviews
    // to break the page
    else {
        CSubViewIterator        iter(this);
        TView*                  subView;
        VRect                   frame;
        VCoordinate             curBreak;
        VPoint                  saveViewPerPage;
        TPrintHandler*          printHandler;
        
        // first, figure out which subview contains
        // previousBreak
        for (subView = iter.FirstSubView(); iter.More();
                            subView = iter.NextSubView()) {
            subView->GetFrame(frame);
            if (frame[topLeft][ortho] <= previousBreak &&
                                frame[botRight][ortho] >=
                                previousBreak)
                break;
        }
        
        // do DoBreakFollowing() on the subview
        if (previousBreak != 0)
            previousBreak -= subView->fLocation[ortho];
        curBreak = subView->DoBreakFollowing(vhs,
                            previousBreak, automatic);
        curBreak += subView->fLocation[ortho];
        
        // for as long as the current break is not within the
        // current view, go on to the next view, doctor it to
        // take care of the amount of the current page taken
        // up with previous views, and do DoBreakFollowing()
        // on it to get the next current-break position
        printHandler = this->GetPrintHandler();
        while (curBreak >= frame[botRight][ortho] &&
                            iter.More()) {
            subView = iter.NextSubView();
            if (subView == NULL)
                break;
            subView->GetFrame(frame);
            saveViewPerPage = printHandler->fViewPerPage;
            printHandler->fViewPerPage[ortho] -= curBreak -
                                previousBreak;
            curBreak = subView->DoBreakFollowing(vhs, 0,
                                automatic);
            curBreak += subView->fLocation[ortho];
            printHandler->fViewPerPage = saveViewPerPage;
        }
        
        // eventually, we'll either get the break in the
        // middle of a view or reach the end of the last
        // subview.  In either case, we return the value of
        // curBreak
        return curBreak;
    }
}

One other instance variable of TStdPrintHandler you might be interested in: TStdPrintHandler::fPageDirection controls which direction the page numbers go in when the view must be split across multiple pages in both directions. If this is set to vSel, pages print columnwise (the first column of pages prints before any pages from the second column). If it's set to hSel, pages print rowwise (the first row of pages prints before any pages from the second row). The default is vSel (columnwise). If you want the pages to print rowwise (which works better in chart-like views), you have to reset fPageDirection in your Initialize() override.

varying the way the view draws

Earlier, we talked about how to set up a special printing view hierarchy. But in a lot of cases, although the view should look a little bit different when you print it, the changes you're making to it seem to be too minor to create a whole new view subclass and go to the trouble of bringing up a printing view hierarchy. Depending on what you're trying to do, there are two ways around this problem.

One of the most common cases is where you want the size determiners to be different. When you show something on the screen, maybe it's sizeSuperView. When you print it, you want it to be sizePage. The easiest way to do this is to override the print handler's Print() method. Get fView's size determiners and save them off in instance variables, set them to the desired values for printing, call the view's AdjustFrame() method so the new size determiners "take", call inherited to do the actual printing, and restore the old size determiners.

In many other cases, you want to change the actual drawing of the view. For instance, maybe your window has controls you don't want to show up in the printed document. Or you used Geneva on the screen because it looks better and want to print in Helvetica because it looks better. Here, you need to alter the view's drawing code so that it does one thing when you're printing and another when you're drawing on the screen. MacApp provides the gPrinting global variable for just this sort of thing. Just test gPrinting to determine which kind of drawing you want to do.

using a view as a header on every page

But in all of this, we haven't really touched on how to print the example we started the article with. Here, you have a view which should print as a header on every page, sized properly to fit the page and another view which also should print on every page, but which needs to be adjusted to match the positioning of the main content view on each page (so the heading line up with their columns). Doing this kind of thing is more tricky:

First, we want to reserve additional space in the margins for the header view. This way the data and the column headings both print inside the area originally defined by theInterior. To do this, your print handler's I method should locate each header view in the window and add their height to fMargins.top. Use a temporary variable to hold the new margin values (initialize it from fMargins), and pass it to TStdPrintHandler::InstallMargins() rather than setting fMargins directly.

Now we're going to tap into AdornPage() to actually draw the views. We'll start with the column headings. I have a routine which is called by my AdornPage() override that draws the column headings. It does essentially what TStdPrintHandler::DrawPageInterior() does to draw the main view. But because the print handler is set up only for drawing the view that represents the page interior, we have a lot of additional grunt work to do.

First, the print handler's Print() override needs to find the column heading view and call its BeInPort() method so that the view thinks it's supposed to draw into the Printing Manager's grafPort. That call would look like this:

fColHeadView->BeInPort((GrafPtr)fPPrPort);

The fPPrPort field has already been initialized by MacApp to point to the Printing Manager's grafPort.

pascal void TMyPrintHandler :: DrawColumnHeadings()
{
    VRect           workRect;
    CRect           qdRect;
    TView*          saveView;
    VRect           clipRect(fPageAreas.theInk);
    VRect           viewExtent;
    
    // refocus on the page border, since God knows what we
    // may be focused on coming into this routine
    this->FocusOnBorder();
    
    // figure out where to draw it
    workRect = this->fPageAreas.theInterior;
    workRect.bottom = workRect.top - 1;
    workRect.top -= fColHeadView->fSize.v + 1;
    fView->ViewToQDRect(workRect, qdRect);
    
    // now set up the print handler as though the col-head
    // view was the view this is set up to print, and
    // arrange all the coordinate systems accordingly
    saveView = fView;
    fView = fColHeadView;
    fQDOrigin.v = -qdRect.top;
    fQDOrigin.h = (short)(fPageAreas.theInk.left +
                        gPageOffset[hSel]);
    SetOrigin(fQDOrigin.h, fQDOrigin.v);
    fColHeadView->UpdateCoordinates();
    clipRect.left += gPageOffset[hSel];
    clipRect.right = fViewedRect.right;
    fColHeadView->ViewToQDRect(clipRect, qdRect);
    ClipRect(qdRect);
    fQDRectToClipTo = qdRect;
    
    // use HandleDraw() to draw the view's contents
    fColHeadView->GetExtent(viewExtent);
    fColHeadView->ViewToQDRect(viewExtent, qdRect);
    fColHeadView->HandleDraw(qdRect);
    
    // and restore the print handler's view pointer
    fView = saveView;
}

Here's what we're doing: First, we call the print handler's FocusOnBorder() method. MacApp calls this for us before calling AdornPage(), but if we're doing other view drawing in here (such as the main header view), we might not still be focused on the border by the time we get here, so we reset it to make sure. Now figure out where on the page the column headings are supposed to go. The rectangle that contains them will be directly above the page interior rectangle, the width of the page interior, and the height of the view (which is fixed). Translate that into QuickDraw coordinates.

Now save off the print handler's fView field and set fView to point to the column heading view. Now we have to set all of the print handler's other fields as though the column heading view were the view we always print. fQDOrigin is the location, relative to theInk, where the printing port's QuickDraw origin should go. In the vertical direction, that spot is always the same: the upper left-hand corner of the rectangle we got above. In the horizontal direction, we move with the main view. gPageOffset is the distance from the view's origin to where this page's part of the view is. This is what causes the column headings to line up horizontally with the data in the chart. Once we've calculated fQDOrigin, call SetOrigin() to put it into effect in the print port. Then call the view's UpdateCoordinates() method so that its internal values match the ones we've set up for the print handler.

Finally, we set up fQDRectToClipTo, which causes only the part of the view that is actually supposed to print on the current page to do so. Vertically, we can clip to theInk. Horizontally, the left boundary is gPageOffset.h again and the right boundary is determined by fViewedRect, which has been set up by the page-breaking code.

Now we can call the view's HandleDraw(), which will take care of the remaining coordinate setup and clipping for us and draw the view's contents (along with all its adorners and subviews). And all we have to do to clean up is restore fView to its original value.

Printing the main heading view is almost the same, but there are some important differences. This view needs to be sized according to the width of the page, and the whole view is printed on every page, rather than being offset according to the stuff in content area of the page. To accomplish the resizing, we do the trick I described above: in the Print() method, save off the view's original size determiners, set the horizontal size determiner to sizePage, and call AdjustFrame(). Then restore the original size determiners after the call to inherited.

In the printing code, we do exactly what we did above, with a few exceptions: We have to offset workRect by the height of the column-heading view so that the main heading view prints above it. fQDOrigin is fixed at the position on the page where the view is to go, so the lines in the function above that set fQDOrigin can change to

fQDOrigin = gZeroPt - qdRect[topLeft];

Likewise, all we have to do to set up fQDRectToClipTo is translate theInk into QuickDraw coordinates:

fIconInfoView->ViewToQDRect(clipRect, qdRect);
ClipRect(qdRect);
fQDRectToClipTo = qdRect;

Everything else about drawing the main heading view is the same.

More than a few lines of code

As you can see, truly robust printing in MacApp involves more than "a few lines of code". Hopefully, this article gives a good introduction to some of the techniques that are common in printing real-world documents with MacApp. MacApp really does do a good job of providing the basics. All you really have to do is fill in the gaps. I hope that the "more than a few lines of code" in this article can give you a good start.
 
AAPL
$119.00
Apple Inc.
+1.40
MSFT
$47.75
Microsoft Corpora
+0.28
GOOG
$540.37
Google Inc.
-0.71

MacTech Search:
Community Search:

Software Updates via MacUpdate

Skype 7.2.0.412 - Voice-over-internet ph...
Skype allows you to talk to friends, family and co-workers across the Internet without the inconvenience of long distance telephone charges. Using peer-to-peer data transmission technology, Skype... Read more
HoudahSpot 3.9.6 - Advanced file search...
HoudahSpot is a powerful file search tool built upon MacOS X Spotlight. Spotlight unleashed Create detailed queries to locate the exact file you need Narrow down searches. Zero in on files Save... Read more
RapidWeaver 6.0.3 - Create template-base...
RapidWeaver is a next-generation Web design application to help you easily create professional-looking Web sites in minutes. No knowledge of complex code is required, RapidWeaver will take care of... Read more
iPhoto Library Manager 4.1.10 - Manage m...
iPhoto Library Manager lets you organize your photos into multiple iPhoto libraries. Separate your high school and college photos from your latest summer vacation pictures. Or keep some photo... Read more
iExplorer 3.5.1.9 - View and transfer al...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
MacUpdate Desktop 6.0.3 - Discover and i...
MacUpdate Desktop 6 brings seamless 1-click installs and version updates to your Mac. With a free MacUpdate account and MacUpdate Desktop 6, Mac users can now install almost any Mac app on macupdate.... Read more
SteerMouse 4.2.2 - Powerful third-party...
SteerMouse is an advanced driver for USB and Bluetooth mice. It also supports Apple Mighty Mouse very well. SteerMouse can assign various functions to buttons that Apple's software does not allow,... Read more
iMazing 1.1 - Complete iOS device manage...
iMazing (was DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and from... Read more
PopChar X 7.0 - Floating window shows av...
PopChar X helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to access any font's special characters. Expanded... Read more
OneNote 15.4 - Free digital notebook fro...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more

Latest Forum Discussions

See All

Raby (Games)
Raby 1.0.3 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.3 (iTunes) Description: ***WARNING - Raby runs on: iPhone 5, iPhone 5C, iPhone 5S, iPhone 6, iPhone 6 Plus, iPad Mini Retina, iPad Mini 3, iPad 4, iPad Air,... | Read more »
Oddworld: Stranger's Wrath (Games)
Oddworld: Stranger's Wrath 1.0 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0 (iTunes) Description: ** PLEASE NOTE: Oddworld Stranger's Wrath requires at least an iPhone 4S, iPad 2, iPad Mini or iPod Touch 5th gen... | Read more »
Bounce On Back (Games)
Bounce On Back 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: | Read more »
Dwelp (Games)
Dwelp 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: === 50% off for a limited time, to celebrate release === Dwelp is an elegant little puzzler with a brand new game mechanic. To complete a... | Read more »
Make Way for Fat Chicken, from the Maker...
Make Way for Fat Chicken, from the Makers of Scrap Squad Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Relevant Games has announced they will be releasing their reverse tower defense game, | Read more »
Tripnary Review
Tripnary Review By Jennifer Allen on November 26th, 2014 Our Rating: :: TRAVEL BUCKET LISTiPhone App - Designed for the iPhone, compatible with the iPad Want to create a travel bucket list? Tripnary is a fun way to do exactly that... | Read more »
Ossian Studios’ RPG, The Shadow Sun, is...
Ossian Studios’ RPG, The Shadow Sun, is Now Available for $4.99 Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Mmmm, Tasty – Having the Angry Birds for...
The very first Angry Birds debuted on iOS back in 2009. When you sit back and tally up the number of Angry Birds games out there and the impact they’ve had on pop culture as a whole, you just need to ask yourself: “How would the birds taste... | Read more »
Rescue Quest Review
Rescue Quest Review By Jennifer Allen on November 26th, 2014 Our Rating: :: PATH BASED MATCH-3Universal App - Designed for iPhone and iPad Guide a wizard to safety by matching gems. Rescue Quest might not be an entirely original... | Read more »
You Can Play the Final Chapter of Lone W...
You Can Play the Final Chapter of Lone Wolf: Dawn Over V’taag Right Now Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »

Price Scanner via MacPrices.net

Black Friday: 15% off iTunes Gift Cards
Staples is offering 15% off $50 and $100 iTunes Gift Cards on their online store as part of their Black Friday sale. Click here for more information. Shipping is free. Best Buy is offering $100... Read more
BEVL Releases Dock Tailored for iPhone 6 and...
Seattle based BEVL has released their first product: an iPhone dock that is divergent in build quality, rock-solid function and visual simplicity to complement the iPhone. BEVL is now accepting... Read more
Black Friday: $150 off 13-inch Retina MacBook...
 Best Buy has 13-inch 2.6GHz Retina MacBook Pros on sale for $150 off MSRP on their online store as part of their Black Friday sale. Choose free shipping or free local store pickup (if available).... Read more
Black Friday: $300 off 15-inch Retina MacBook...
 B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for $300 off MSRP as part of their Black Friday sale. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina... Read more
Black Friday: Up to $140 off MacBook Airs, fr...
 B&H Photo has 2014 MacBook Airs on sale for up to $140 off MSRP as part of their Black Friday sale. Shipping is free, and B&H charges NY sales tax only: - 11″ 128GB MacBook Air: $799 $100... Read more
Black Friday: 13-inch 2.5GHz MacBook Pro on s...
 Best Buy has the 13″ 2.5GHz MacBook Pro on sale for $899.99 on their online store as part of their Black Friday sale. Choose free shipping or free instant local store pickup (if available). Their... Read more
Black Friday: 21-inch 1.4GHz iMac on sale for...
 Best Buy has the 21″ 1.4GHz iMac on sale for $899.99 on their online store as part of their Black Friday sale. Their price is $200 off MSRP. Choose free shipping or free local store pick up. Price... Read more
Black Friday iPad Air 2 sale prices, $100 off...
 Best Buy has iPad Air 2s on sale for $100 off MSRP on their online store for Black Friday. Choose free shipping or free local store pickup (if available). Sale prices available for online orders... Read more
2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has the new 1.4GHz Mac mini on sale for $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new model. Adorama... Read more
Early Black Friday pricing on 27-inch 5K iMac...
 B&H Photo continues to offer Black Friday sale prices on the 27″ 3.5GHz 5K iMac, in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.