Chapter 24. Printing

Code to handle printing is always relatively hard to write. There are many factors at play: pagination, margins, and page orientation (landscape versus portrait). This chapter is designed to get you started on your journey toward the perfect printout.

Compared to most operating systems, Mac OS X makes writing print routines considerably easier. After all, your views already know how to generate PDF, and Mac OS X knows how to print PDF. If you have a document-based application and a view that knows how to draw itself, you just implement printShowingPrintPanel:. In this method, you create an NSPrintOperation object and run it. The code would look like this:

- (void)printShowingPrintPanel:(BOOL)flag
{
    NSPrintInfo *printInfo = [self printInfo];
    NSPrintOperation *printOp;

     printOp = [NSPrintOperation printOperationWithView:aView
                                              printInfo:printInfo];
    [printOp setShowPanels:flag];
    [self runModalPrintOperation:printOp
                        delegate:nil
                  didRunSelector:NULL
                     contextInfo:NULL];
}

Adding Printing to TypingTutor

If your application is not document based, you will implement a print: method in the target of the menu item. For example, add the following method to the AppController class in your TypingTutor project:

- (IBAction)print:(id)sender
{
    NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
    NSPrintOperation *printOp;
    printOp = [NSPrintOperation printOperationWithView:inLetterView 
                                             printInfo:printInfo];
    [printOp setShowPanels:YES];
    [printOp runOperation];
}

Declare the method in AppController.h. Drag AppController.h into the nib file. Select the menu item called Print…. In the connections inspector, disconnect its target outlet. Make the AppController be the target of the menu item, and set the action to print: (Figure 24.1).

Connect Menu Item

Figure 24.1. Connect Menu Item

Build the project and confirm that printing works. Unless you have a printer set up, you will only be able to preview what would have been printed (Figure 24.2).

Completed Application

Figure 24.2. Completed Application

To print all the views on the window, simply change the print: method to use the window's content view:

- (IBAction)print:(id)sender
{
    NSPrintInfo *printInfo = [NSPrintInfo sharedPrintInfo];
    NSPrintOperation *printOp;
    NSView *v = [[inLetterView window] contentView];
    printOp = [NSPrintOperation printOperationWithView:v
                                             printInfo:printInfo];
    [printOp setShowPanels:YES];
    [printOp runOperation];
}

Dealing with Pagination

What about multiple pages? A view, after all, has only a single page. How will you get a view to print multiple-page documents? Off-screen, you will make a huge view that can display all the pages of the document simultaneously (Figure 24.3). The print system will ask the view how many pages it is displaying. Then it will ask the view where each page can be found in the view.

Each Page Is a Rectangle on the View

Figure 24.3. Each Page Is a Rectangle on the View

Your view, then, must override two methods:

// How many pages? 
- (BOOL)knowsPageRange:(NSRange *)rptr; 

// Where is each page? 
- (NSRect)rectForPage:(int)pageNum; 

As an example, you will add printing to the RaiseMan application. You will print the name and expected raise for as many people as will fit on the paper size that the user selected from the print panel (Figure 24.4).

Completed Application

Figure 24.4. Completed Application

To do so, you will create a view that does the printing. The view will be big enough to display all of the people simultaneously, but for each page there will be a rectangle that contains the information for the people that will be printed on that page. The view will be called PeopleView (Figure 24.5).

PeopleView

Figure 24.5. PeopleView

The code in MyDocument.m is actually pretty simple:

- (void)printShowingPrintPanel:(BOOL)flag
{
    NSPrintInfo *printInfo = [self printInfo];
    NSPrintOperation *printOp;
    PeopleView *view;
    // End editing
    [personController commitEditing];
    view = [[PeopleView alloc] initWithPeople:employees
                                    printInfo:printInfo];
    printOp = [NSPrintOperation printOperationWithView:view
                                             printInfo:printInfo];
    [printOp setShowPanels:flag];
    [self runModalPrintOperation:printOp
                        delegate:nil
                  didRunSelector:NULL
                     contextInfo:NULL];

    [view release];
}

You will have to import PeopleView.h. This is not so different from the TypingTutor example, but there are some minor differences:

  • NSDocument implements printDocument: to call [self printShowingPrint Panel:YES], so your subclass of NSDocument is implementing printShowing PrintPanel: instead of print:.

  • The view that does the printing needs to be created. Usually you will create a view by calling initWithFrame:. In PeopleView, you will create another constructor that takes the array of people and the print info object. The print info object knows the paper size. Using the array of people and the print info object, you can figure out how big the frame should be.

  • NSDocument has a method printInfo that returns the instance of NSPrintInfo for that document.

In the MainMenu.nib file, make sure the Print… menu item is nil-targeted and has set its action to printDocument (Figure 24.6).

Connect Menu Item

Figure 24.6. Connect Menu Item

Create a class called PeopleView that is a subclass of NSView. PeopleView.h would look like this:

#import <Cocoa/Cocoa.h>

@interface PeopleView : NSView {
    NSArray *people;
    NSMutableDictionary *attributes;
    NSSize paperSize;
    float leftMargin;
    float topMargin;
}
- (id)initWithPeople:(NSArray *)array printInfo:(NSPrintInfo *)pi;
- (NSRect)rectForPerson:(int)index;
- (int)peoplePerPage;

@end

In PeopleView.m, you will implement the initWithPeople:printInfo: method. This initializer will call NSView's initWithFrame: method.

#import "PeopleView.h"
#import "Person.h"
#define VSPACE 30.0

@implementation PeopleView

- (id)initWithPeople:(NSArray *)array printInfo:(NSPrintInfo *)pi
{
    NSRange pageRange;
    NSRect frame;
    
    // Get the useful data out of the print info
    paperSize = [pi paperSize];
    leftMargin = [pi leftMargin];
    topMargin = [pi topMargin];
    
    people = [array retain];
    
    // Get the number of pages
    [self knowsPageRange:&pageRange];
    
    // The view must be big enough to hold the first and last pages
    frame = NSUnionRect([self rectForPage:pageRange.location],
                        [self rectForPage:NSMaxRange(pageRange)-1]);
    
    // Call the superclass's designated initializer
    [super initWithFrame:frame];
    
    // The attributes of the text to be printed
    attributes = [[NSMutableDictionary alloc] init];
    [attributes setObject:[NSFont fontWithName:@"Helvetica" size:15.0]
                   forKey:NSFontAttributeName];
    return self;
}

// The origin of the view is at the upper-left corner
- (BOOL)isFlipped
{
    return YES;
}

- (NSRect)rectForPage:(int)page
{
    NSRect result;
    result.size = paperSize;
    
    // Page numbers start at 1
    result.origin.y = (page - 1) * paperSize.height;
    result.origin.x = 0.0;
    return result;
}

- (int)peoplePerPage
{
    float ppp = (paperSize.height - (2.0 * topMargin)) / VSPACE;
    return (int)ppp;
}

- (BOOL)knowsPageRange:(NSRange *)r
{
    int peoplePerPage = [self peoplePerPage];
    
    // Page counts start at 1
    r->location = 1;
    r->length = ([people count] / peoplePerPage);
    if ([people count] % peoplePerPage > 0) {
        r->length = r->length + 1;
    }
    return YES;
}

- (NSRect)rectForPerson:(int)i
{
    NSRect result;
    int peoplePerPage = [self peoplePerPage];
    result.size.height = VSPACE;
    result.size.width = paperSize.width - (2 * leftMargin);
    result.origin.x = leftMargin;
    int page = i / peoplePerPage;
    int indexOnPage = i % peoplePerPage;
    result.origin.y = (page * paperSize.height) + topMargin + 
                                          (indexOnPage * VSPACE);
    return result;
}

- (void)drawRect:(NSRect)r
{
    int count, i;
    count = [people count];
    for (i=0; i<count; i++) {
        NSRect personRect = [self rectForPerson:i];
        if (NSIntersectsRect(r, personRect)) {
            Person *p = [people objectAtIndex:i];
            NSString *dataString;
            dataString = [NSString stringWithFormat:@"%d.	%@		%f", 
                                  i, [p personName], 
                                 [p expectedRaise]];
            [dataString drawInRect:personRect 
                    withAttributes:attributes];
        }
    }
}
- (void)dealloc
{
    [attributes release];
    [people release];
    [super dealloc];
}

@end

Build and run the application. Notice that multiple-pages-per-sheet setup (4-up, for example) works. Notice that you can change the paper size and more or less people subsequently appear on each page.

For the More Curious: Am I Drawing to the Screen?

Often in an application, you will want to draw things differently on screen than on the printer. For example, in a drawing program, the view might show a grid on-screen, but not when printed on paper.

In your drawRect: method, you can ask the current graphics context if it is currently drawing to the screen:

if ([[NSGraphicsContext currentContext] isDrawingToScreen]) {
    ...draw grid...
}

Using ObjectAlloc

Apple supplies developers with a very handy tool for hunting down memory leaks—an application called ObjectAlloc. To see how it works, create a memory leak by commenting out the line that releases PeopleView in MyDocument.m:

    [self runModalPrintOperation:printOp
                        delegate:nil
                  didRunSelector:NULL
                     contextInfo:NULL];
    //[view release];

Recompile your app. In the Debug menu, you will find the Launch Using Performance Tool submenu. Choose ObjectAlloc.

When ObjectAlloc starts, you will be presented with a window that displays the number of instances of each class that is created in your application. Click the run button as shown in Figure 24.7

Run Application in ObjectAlloc

Figure 24.7. Run Application in ObjectAlloc

When the sheet appears, check Also record CF & ObjC reference counting as shown in Figure 24.8.

Record Reference Counting

Figure 24.8. Record Reference Counting

As your application runs, you will see three numbers for each class:

  • Currenthow many objects of this class are in memory right now

  • Peakthe maximum number of objects of this class that have been in memory at the same time

  • Totalthe total number of instances that have been created

Check the box labeled Show since mark.

In RaiseMan, create a new document. In ObjectAlloc, click the mark button. Create some Person objects and note their existence in ObjectAlloc. In RaiseMan, close the document. In ObjectAlloc, notice that the Person objects are deallocated.

Create another new document. Print/preview it three times. Notice that the PeopleView objects are not deallocated correctly.

Change to the Instance Browser tab of ObjectAlloc. Find an instance of PeopleView, and look at its allocation event. (A panel will appear asking whether it is okay to pause the app; it is.) You should see the state of the stack as it was when the PeopleView object was allocated (Figure 24.9).

State of the Stack

Figure 24.9. State of the Stack

Using ObjectAlloc, you can hunt down many types of memory leaks.

Challenge

Add page numbers to the printout.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset