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,