/* BigView.m
 * Purpose: Demonstrates custom pagination.
 * a BigView is a "matrix" of 3x4 rectangles, each of which is 8" x 10"
 * A bigView therefore has very logical page breaks, although the Appkit by
 * default won't break up the bigView on those boundaries.  This class demonstrates
 * how to do this.  This class also demonstrates somewhat optimized scrolling by only
 * drawing affected pages (because a page is the smallest divisible unit in this class.)
 *
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 *
 * Written by: Samuel Streeper
 * Created: (04/April/91)
 */

#import "BigView.h"
#import "drawRect.h"
#import "MyPrintInfo.h"
#import <dpsclient/wraps.h>
#import <math.h>

#define INCHES * 72.0

#define PAGEWIDTH (8 INCHES)
#define PAGEHEIGHT (10 INCHES)
#define GRIDWIDTH (1. INCHES)
#define GRIDHEIGHT (1. INCHES)

#define MAXCOLUMN 3
#define MAXROW 4
#define PAGES (MAXCOLUMN * MAXROW)

#define min(X, Y)  ((X) < (Y) ? (X) : (Y))

@implementation BigView

static char *string[PAGES] = {
	"one","two","three","four",
	"five","six","seven","eight",
	"nine","ten","and","more!"
	};

// I could just printf these.  Nah...
static char *numbers[PAGES] = {
	"1","2","3","4","5","6",
	"7","8","9","A","B","C"
	};
	
- initFrame:(const NXRect *)frameRect
{
	// normally you would always initialize to the frame you are passed, like this:
	//	[super initFrame:frameRect];
	// however, I want to force all BigView intances to be the 'correct' size.
	// (you usually shouldn't do this...)

	NXRect r = {0,0,(MAXCOLUMN*PAGEWIDTH),(MAXROW*PAGEHEIGHT)};
	[super initFrame:&r];

	[self allocateGState];		// for faster focusing and scrolling
	loadPSProcedures();			// initialize the drawing context
	return self;
}

// DrawSelf is used to construct the View for printing or when a portion
// gets scrolled onto the screen.  It should only draw the areas that
// intersect with rects.
- drawSelf:(const NXRect *)rects :(int)rectCount
{
	int darkFlag;
	int minColumn, maxColumn, minRow, maxRow;
	int row, col, offset;
	
	if (!rectCount) return self;
	minColumn = rects->origin.x / PAGEWIDTH;
	maxColumn = (rects->origin.x + rects->size.width) / PAGEWIDTH;

	minRow = rects->origin.y / PAGEHEIGHT;
	maxRow = (rects->origin.y + rects->size.height) / PAGEHEIGHT;
	
	if (maxColumn > MAXCOLUMN-1) maxColumn = MAXCOLUMN-1;
	if (maxRow > MAXROW-1) maxRow = MAXROW-1;

	// Drawing is optimized to draw only the affected pages.  In this app,
	// it's possible for one page to somewhat overlap another page.
	// Without clipping, drawing optimization could allow a page in back to
	// draw, while not telling the page in front to draw.  Clipping to rects
	// insures that either the background will not draw over the foreground,
	// or if it does, the foreground page will also draw
	
	NXRectClip(rects);
	
	for (col = minColumn; col <= maxColumn; col++)
	{
		for (row = minRow; row <= maxRow; row++)
		{
			offset = ((MAXROW-1)-row)*MAXCOLUMN + col;
			darkFlag = ((row + col) & 1);
			drawRect(col*PAGEWIDTH,row*PAGEHEIGHT,string[offset], numbers[offset], darkFlag);
		}
	}
	return self;
}

// If the extended printInfo has been set to application controlled
// pagination, tell the kit we know where our pages lie.
- (BOOL)knowsPagesFirst:(int *)firstPageNum last:(int *)lastPageNum
{
	if ([pi paginationMode] != APPCONTROLLED) return NO;
	
	*lastPageNum = PAGES;
	return YES;
}

// If we know where our pages lie, the kit will ask us for the rect
// for each page.
- (BOOL)getRect:(NXRect *)theRect forPage:(int)page
{
	int row, col;

	if (page < 1 || page > PAGES) return NO;
	page--;
	row = (MAXROW-1) - (page / MAXCOLUMN);
	col = page % MAXCOLUMN;

	theRect->origin.x = col * PAGEWIDTH;
	theRect->origin.y = row * PAGEHEIGHT;
	theRect->size.width = PAGEWIDTH;
	theRect->size.height = PAGEHEIGHT;
	return YES;
}


// There are at least 3 good reasons to override this method:
// 1) The printing rect does not appear where you desire on the paper
//		so you set your own value for location
// 2) The rect in aRect is not the one that you want placed, due to
//		scaling by your own pagination algorithm (which is applied _after_
//		this method gets invoked), or you will change
//		the size with adjustPageHeight and andjustPageWidth.  I override
//		this method for reason #2
// 3) The kit version of this method will not horizontally center
// 		a rect if the View has more than 1 column, and it will not
// 		vertically center a rect if the View has more than 1 row.
//		If you need this, it is trivial to rewrite this method to
//		center the rect within the paperRect.

- placePrintRect:(const NXRect *)aRect offset:(NXPoint *)location
{
	NXRect myRect;
	float vertScale, horScale;
	NXCoord l,r,t,b;
	const NXRect *paperRect;
	
	if ([pi paginationMode] != APPCONTROLLED)
	{
		[super placePrintRect:aRect offset:location];
	}
	else
	{	
		myRect = *aRect;
		paperRect = [pi paperRect];
		[pi getMarginLeft:&l right:&r top:&t bottom:&b];

		// calculate scaling factor so that rect will fit within margins
		horScale = (paperRect->size.width - r - l)/PAGEWIDTH;
		if (horScale <= 0) horScale = 1;
		vertScale = (paperRect->size.height - t - b)/PAGEHEIGHT;
		if (vertScale <= 0) vertScale = 1;

		realScale = min(horScale, vertScale);
		myRect.size.width *= realScale;
		myRect.size.height *= realScale;
		
		// now place the scaled rect (scaling actually happens later,
		// in addToPageSetup...)

		[super placePrintRect:&myRect offset:location];
	}

	return self;	
}

// Note: add to page setup is called after placePrintRect.  Thus placePrintRect attempts
// to place an unscaled rect.  Thus any scaling you will add here should be figured
// into the placement of the print rectangle.
- addToPageSetup
{
	[super addToPageSetup];

	if ([pi paginationMode] == APPCONTROLLED)
	{
		PSscale(realScale, realScale);
	}

	return self;
}


// A helpful hint:  You won't be able to adjust the height if vertical pagination
// is set to clip...

- adjustPageHeightNew:(float *)newBottom
	top:(float)oldTop
	bottom:(float)oldBottom
	limit:(float)bottomLimit
{
	if ([pi paginationMode] == AUTOWITHHELP)
	{
		// caution! with normal coordinates, you need to round the height up...
		* newBottom = (ceil(oldBottom/GRIDHEIGHT)) * GRIDHEIGHT;
		if (*newBottom < oldBottom || *newBottom > bottomLimit)
			*newBottom = oldBottom;
	}
	else
	{
		[super adjustPageHeightNew:newBottom top:oldTop bottom:oldBottom
			limit:bottomLimit];
	}
	return self;
}

// A helpful hint:  You won't be able to adjust the width if horizontal pagination
// is set to clip...

- adjustPageWidthNew:(float *)newRight
	left:(float)oldLeft
	right:(float)oldRight
	limit:(float)rightLimit
{
	if ([pi paginationMode] == AUTOWITHHELP)
	{
		* newRight = (floor(oldRight/GRIDWIDTH)) * GRIDWIDTH;
		if (*newRight > oldRight || *newRight < rightLimit)
			*newRight = oldRight;
	}
	else
	{
		[super adjustPageWidthNew:newRight left:oldLeft right:oldRight
			limit:rightLimit];
	}
	return self;
}

// Remember that when you print, you are outputting to a different context
// than when you were drawing.  Therefore, you need to define the
// procedures you will use for the printing context.

- endPrologue
{
	loadPSProcedures();
    return [super endPrologue];
}

// I do this just so I don't have to keep calling [NXApp printInfo]
- setPrintInfo:newPi
{
	pi = newPi;
	return self;
}


@end
