//=============================================================================
//
//		Copyright (C) 1996-1997 by Paul S. McCarthy and Eric Sunshine.
//				Written by Paul S. McCarthy and Eric Sunshine.
//							All Rights Reserved.
//
//		This notice may not be removed from this source code.
//
//		This object is included in the MiscKit by permission from the authors
//		and its use is governed by the MiscKit license, found in the file
//		"License.rtf" in the MiscKit distribution.	Please refer to that file
//		for a list of all applicable permissions and restrictions.
//		
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableViewPrint.M
//
//		Printing support for MiscTableScroll.
//
// FIXME: Move most of this junk up into table scroll.
// FIXME: Separate computing the number of pages from doing all the
//		other work, so that hopefully, we can do all the other work
//		only if the user proceeds with a print operation.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableViewPrint.M,v 1.3 97/06/10 05:25:47 sunshine Exp $
// $Log:		MiscTableViewPrint.M,v $
// Revision 1.3	 97/06/10  05:25:47	 sunshine
// v127: Uses new MiscAssert.
// 
// Revision 1.2	 97/02/11  19:20:05	 sunshine
// v123: Removed unused methods: -tooSmall & -putImage:atPoint:.
// Fixed bug: Was leaking every NXImage created in -getImage... methods.
// Fixed bug: Delegate methods -tableScrollWillPrint: & -tableScroll:didPrint:
// were getting subverted when -printPSCode: was sent directly to this object
// rather than the MiscTableScroll.	 This happens when this object is first-
// responder (such as when Print... is connected to first-responder in IB).
// Was fixed by sending them from here instead of MiscTableScrollPrint.M.
// 
// Revision 1.1	 96/10/24  22:48:12	 zarnuk
// Currently implements most of the work for printing.
// Still needs a lot of work.
//-----------------------------------------------------------------------------
#import "MiscTableView.h"
#import "MiscAssert.h"
#import "MiscTableScrollPrivate.h"
#import "MiscTableBorder.h"
#import "MiscBorderView.h"
#import "MiscGeometry.h"

extern "Objective-C" {
#import <appkit/Application.h>
#import <appkit/NXImage.h>
#import <appkit/PrintInfo.h>
#import <appkit/PrintPanel.h>
}

extern "C" {
#import <math.h>		// floor()
}

@implementation MiscTableView(Print)

//-----------------------------------------------------------------------------
// getHeight:
//-----------------------------------------------------------------------------
- (NXCoord)getHeight:(id)obj
	{
	if (obj != 0 && [obj respondsTo:@selector(getFrame:)])
		{
		NXRect r;
		[obj getFrame:&r];
		return r.size.height;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// numPagesForBorder:pageSize:
//-----------------------------------------------------------------------------
- (int)numPagesForBorder:(MiscBorderType)bt pageSize:(NXCoord)scaled_page_size
	{
	int npages = 0;

	// FIXME: Borders with uniform size slots should be much simpler
	// than this.

	MiscPixels const page_size = (MiscPixels)floor(scaled_page_size);

	MiscAssert( page_size > 0 );

	MiscTableBorder* const border =
				(bt == MISC_COL_BORDER ? col_border : row_border);

	MiscPixels sz_sum = 0;
	MiscCoord_V const numSlots = border->count();
	for (MiscCoord_V vslot = 0; vslot < numSlots; vslot++)
		{
		MiscPixels sz = border->effectiveSize(vslot);
		if (sz <= page_size)
			{
			sz_sum += sz;
			if (sz_sum > page_size)
				{
				sz_sum = sz;
				npages++;
				}
			}
		else
			{
			if (sz_sum > 0)
				npages++;
			npages += sz / page_size;
			sz_sum = sz % page_size;
			}
		}

	if (sz_sum >= 0)
		npages++;

	return npages;
	}


//-----------------------------------------------------------------------------
// append
//-----------------------------------------------------------------------------
static void append( MiscPixels w, MiscTSPageBreak& bk, MiscTSPageBreak bks[],
						int n, int num_breaks )
	{
	MiscAssert( n < num_breaks );
	bk.size = w;
	bks[ n ] = bk;
	bk.offset += w;
	}


//-----------------------------------------------------------------------------
// border:pageSize:numPages:calcBreaks:
//-----------------------------------------------------------------------------
- (void)border:(MiscBorderType)bt
		pageSize:(NXCoord)pageSize
		numPages:(int)nPages
		clip:(BOOL)do_clip
		calcBreaks:(MiscTSPageBreak*)bks
	{
	MiscPixels const page_size = (MiscPixels)floor(pageSize);

	// FIXME: Borders with uniform size slots should be much simpler
	// than this.

	MiscAssert( page_size > 0 );

	MiscTableBorder* const border =
				(bt == MISC_COL_BORDER ? col_border : row_border);

	MiscTSPageBreak bk;
	bk.offset = 0;
	bk.size = 0;
	bk.first = 0;
	bk.last = 0;

	int pg = 0;
	MiscPixels sz_sum = 0;

	MiscCoord_V const numSlots = border->count();
	for (MiscCoord_V vslot = 0; vslot < numSlots; vslot++)
		{
		MiscPixels sz = border->effectiveSize(vslot);
		if (sz <= page_size)
			{
			sz_sum += sz;
			if (sz_sum > page_size)
				{
				bk.last = (vslot - 1);
				append( sz_sum - sz, bk, bks, pg++, nPages );
				if (do_clip) goto clip_exit;
				bk.first = vslot;
				sz_sum = sz;
				}
			}
		else
			{
			if (sz_sum > 0)
				{
				bk.last = (vslot - 1);
				append( sz_sum, bk, bks, pg++, nPages );
				if (do_clip) goto clip_exit;
				}
			bk.first = vslot;
			bk.last = ~vslot;
			do	{
				append( page_size, bk, bks, pg++, nPages );
				if (do_clip) goto clip_exit;
				bk.first = ~vslot;
				sz -= page_size;
				}
			while (sz > page_size);
			sz_sum = sz;
			}
		}

	if (sz_sum >= 0)
		{
		bk.last = numSlots - 1;
		append( sz_sum, bk, bks, pg++, nPages );
		}

clip_exit:

	MiscAssert( pg == nPages );
	}


//-----------------------------------------------------------------------------
// - getImageForView:
//-----------------------------------------------------------------------------
- (NXImage*)getImageForView:(id)view
	{
	NXStream* s = NXOpenMemory( 0, 0, NX_READWRITE );
	NXRect b;	[view getBounds:&b];
	[view copyPSCodeInside:&b to:s];
	NXSeek( s, 0, NX_FROMSTART );
	NXImage* image = [[NXImage alloc] initFromStream:s];
	NXCloseMemory( s, NX_FREEBUFFER );
	return image;
	}


//-----------------------------------------------------------------------------
// getImage:isScaled:scaleFactor:forView:
//-----------------------------------------------------------------------------
- (NXImage*)getImage:(NXRect const*)rect
		isScaled:(BOOL)isScaled
		scaleFactor:(double)scaleFactor
		forView:(MiscBorderView*)view
	{
	// FIXME: Do something about the scaling.
	NXStream* s = NXOpenMemory( 0, 0, NX_READWRITE );
	[view copyPSCodeInside:rect to:s];
	NXSeek( s, 0, NX_FROMSTART );
	NXImage* image = [[NXImage alloc] initFromStream:s];
	NXCloseMemory( s, NX_FREEBUFFER );
	return image;
	}


//-----------------------------------------------------------------------------
// getImages:info:
//-----------------------------------------------------------------------------
- (void)getImages:(MiscTSPageImages*)img
		info:(MiscTablePrintInfo const*)info
	{
	id delg;

	img->page_header = 0;
	img->page_footer = 0;
	img->col_titles = 0;
	img->row_titles = 0;

	if (pages->pageHeader != 0)
		{
		delg = [scroll responsibleDelegate:
						MiscDelegateFlags::DEL_PRINT_PAGE_HEADER];
		if (delg != 0)
			[delg tableScroll:scroll
						willPrintPageHeader:pages->pageHeader
						info:info];
		// FIXME: Need to shrink/grow/center/position.
		img->page_header = [self getImageForView:pages->pageHeader];
		}

	if (pages->pageFooter != 0)
		{
		delg = [scroll responsibleDelegate:
						MiscDelegateFlags::DEL_PRINT_PAGE_FOOTER];
		if (delg != 0)
			[delg tableScroll:scroll
						willPrintPageFooter:pages->pageFooter
						info:info];
		// FIXME: Need to shrink/grow/center/position.
		img->page_footer = [self getImageForView:pages->pageFooter];
		}

	if (pages->colTitles != 0)
		{
		NXRect r = info->print_rect;
		r.origin.y = 0;
		r.size.height = pages->col_titles_height;
		img->col_titles = [self getImage:&r isScaled:info->is_scaled
								scaleFactor:info->scale_factor
								forView:pages->colTitles];
		}

	if (pages->rowTitles != 0)
		{
		NXRect r = info->print_rect;
		r.origin.x = 0;
		r.size.width = pages->row_titles_width;
		img->row_titles = [self getImage:&r isScaled:info->is_scaled
								scaleFactor:info->scale_factor
								forView:pages->rowTitles];
		}
	}


//-----------------------------------------------------------------------------
// calcPages
//-----------------------------------------------------------------------------
- (BOOL)calcPages
	{
	BOOL ok = YES;		// FIXME: Deal with pages that are too small, etc.

	pages = new MiscTablePages;

	pages->pageHeader = [scroll getPageHeader];
	pages->pageFooter = [scroll getPageFooter];
	pages->page_header_height = [self getHeight: pages->pageHeader];
	pages->page_footer_height = [self getHeight: pages->pageFooter];

	pages->colTitles = [scroll colTitles];
	pages->rowTitles = [scroll rowTitles];
	pages->col_titles_height = [scroll colTitlesHeight];
	pages->row_titles_width = [scroll rowTitlesWidth];

	NXCoord total_width = frame.size.width + pages->row_titles_width;
	NXCoord total_height = frame.size.height + pages->col_titles_height;

	PrintInfo* const printInfo = [NXApp printInfo];
	NXRect const* const paperRect = [printInfo paperRect];

	float leftMargin, rightMargin, topMargin, bottomMargin;
	[printInfo getMarginLeft:&leftMargin right:&rightMargin
						top:&topMargin bottom:&bottomMargin];

	NXCoord page_width = paperRect->size.width - leftMargin - rightMargin;
	NXCoord page_height = paperRect->size.height - topMargin - bottomMargin
						- pages->page_header_height
						- pages->page_footer_height;

	int const hPagination = [printInfo horizPagination];
	int const vPagination = [printInfo vertPagination];
//	   int const hCentered = [printInfo isHorizCentered];
//	   int const vCentered = [printInfo isVertCentered];

	BOOL hScaled = NO;	double hScaler = 1.0;
	BOOL vScaled = NO;	double vScaler = 1.0;
	BOOL isScaled = NO; double scaleFactor = 1.0;

	if (hPagination == NX_FITPAGINATION && total_width > page_width)
		{
		hScaled = YES;
		hScaler = double(page_width) / double(total_width);
		}

	if (vPagination == NX_FITPAGINATION && total_height > page_height)
		{
		vScaled = YES;
		vScaler = double(page_height) / double(total_height);
		}

	if (hScaled || vScaled)
		{
		isScaled = YES;
		if (vScaler < hScaler)
			scaleFactor = vScaler;
		else
			scaleFactor = hScaler;
		}

	pages->info.is_scaled = isScaled;
	pages->info.scale_factor = scaleFactor;

	NXCoord scaled_page_width = page_width;
	NXCoord scaled_page_height = page_height;

	if (isScaled)
		{
		scaled_page_width = double(page_width) / scaleFactor;
		scaled_page_height = double(page_height) / scaleFactor;
		}

	scaled_page_width -= pages->row_titles_width;
	scaled_page_height -= pages->col_titles_height;


	//--- Calculate the number of rows/cols/pages -------------------------

	int ncols = 1;
	int nrows = 1;

	if (hPagination == NX_AUTOPAGINATION &&
		frame.size.width > scaled_page_width)
		ncols = [self numPagesForBorder:MISC_COL_BORDER
						pageSize:scaled_page_width];

	if (vPagination == NX_AUTOPAGINATION &&
		frame.size.height > scaled_page_height)
		nrows = [self numPagesForBorder:MISC_ROW_BORDER
						pageSize:scaled_page_height];

	MiscAssert( nrows > 0 );  MiscAssert( ncols > 0 );

	int const npages = nrows * ncols;


	//--- Allocate arrays -------------------------------------------------

	pages->col_breaks = (MiscTSPageBreak*)
						malloc( (ncols + nrows) * sizeof(MiscTSPageBreak) );
	pages->row_breaks = pages->col_breaks + ncols;

	MiscTSPageImages* images = (MiscTSPageImages*)
						malloc( npages * sizeof(*images) );
	pages->images = images;


	//--- Prepare print rectangles and border images ----------------------

	[self border:MISC_COL_BORDER
			pageSize:scaled_page_width
			numPages:ncols
			clip:(hPagination == NX_CLIPPAGINATION)
			calcBreaks:pages->col_breaks];

	[self border:MISC_ROW_BORDER
			pageSize:scaled_page_height
			numPages:nrows
			clip:(vPagination == NX_CLIPPAGINATION)
			calcBreaks:pages->row_breaks];


	MiscTablePrintInfo& info = pages->info;
	info.page_rect = *paperRect;
	info.num_print_pages = npages;
	info.num_print_rows = nrows;
	info.num_print_cols = ncols;
	info.scale_factor = scaleFactor;
	info.is_scaled = isScaled;

	int pg = 0;
	MiscTSPageImages* img = images;
	MiscTSPageBreak const* rbk = pages->row_breaks;
	for (int r = 0; r < nrows; r++,rbk++)
		{
		info.print_rect.origin.y = rbk->offset;
		info.print_rect.size.height = rbk->size;
		info.first_print_row = rbk->first;
		info.last_print_row = rbk->last;

		MiscTSPageBreak const* cbk = pages->col_breaks;
		for (int c = 0; c < ncols; c++,cbk++,img++)
			{
			info.print_rect.origin.x = cbk->offset;
			info.print_rect.size.width = cbk->size;
			info.first_print_col = cbk->first;
			info.last_print_col = cbk->last;
			info.print_page = ++pg;

			[self getImages:img info:&info];
			}
		}

	return ok;
	}


//-----------------------------------------------------------------------------
// freePages
//-----------------------------------------------------------------------------
- (void)freePages
	{
	if (pages->col_breaks != 0)
		{
		free( pages->col_breaks );
		pages->col_breaks = 0;
		}
	if (pages->images != 0)
		{
		for (int i = pages->info.num_print_pages; i-- > 0; )
			{
			MiscTSPageImages const& img = pages->images[i];
			if (img.page_header != 0) [img.page_header free];
			if (img.page_footer != 0) [img.page_footer free];
			if (img.col_titles	!= 0) [img.col_titles  free];
			if (img.row_titles	!= 0) [img.row_titles  free];
			}
		free( pages->images );
		pages->images = 0;
		}
	delete pages;
	pages = 0;
	}


//-----------------------------------------------------------------------------
// printPSCode:
//
// *FIXME*
//		Scale from true screen measurements (92 points per inch)
//		to true printer measurements (72 points per inch).
//-----------------------------------------------------------------------------
- (id)printPSCode:(id)sender
	{
	id rc = 0;

	id del = [scroll responsibleDelegate:MiscDelegateFlags::DEL_WILL_PRINT];
	if (del != 0)
		[del tableScrollWillPrint:scroll];

	if ([self calcPages])
		{
		rc = [super printPSCode:sender];
		[self freePages];
		}

	del = [scroll responsibleDelegate:MiscDelegateFlags::DEL_DID_PRINT];
	if (del != 0)
		[del tableScroll:scroll didPrint:(rc != 0)];

	return rc;
	}


//-----------------------------------------------------------------------------
// knowsPagesFirst:last:
//-----------------------------------------------------------------------------
- (BOOL)knowsPagesFirst:(int*)first last:(int*)last
	{
	if (pages != 0)
		{
		*first = 1;
		*last = pages->info.num_print_pages;
		return YES;
		}
	return NO;
	}


//-----------------------------------------------------------------------------
// getRect:forPage:
//-----------------------------------------------------------------------------
- (BOOL)getRect:(NXRect*)rect forPage:(int)n
	{
	if (pages != 0)
		{
		MiscTablePrintInfo const& info = pages->info;
		if (0 < n && n <= info.num_print_pages)
			{
			n--;
			int const r = (n / info.num_print_cols);
			int const c = (n % info.num_print_cols);
			MiscTSPageBreak const& cbk = pages->col_breaks[c];
			MiscTSPageBreak const& rbk = pages->row_breaks[r];
			rect->origin.x = cbk.offset;
			rect->size.width = cbk.size;
			rect->origin.y = rbk.offset;
			rect->size.height = rbk.size;
			return YES;
			}
		}
	return NO;
	}


//-----------------------------------------------------------------------------
// drawPageBorder::
//-----------------------------------------------------------------------------
- (id)drawPageBorder:(float)width :(float)height
	{
	if (pages != 0)
		{
		MiscTablePrintInfo const& info = pages->info;

		int n = [[NXApp printInfo] currentPage] - 1;

		MiscAssert( 0 <= n ); MiscAssert( n < info.num_print_pages );

		MiscTSPageImages const& img = pages->images[n];

		NXPoint pt;

		float l,r,t,b;
		PrintInfo* pinfo = [NXApp printInfo];
		[pinfo getMarginLeft:&l right:&r top:&t bottom:&b];

		NXCoord bottom = info.page_rect.origin.y + b;
		NXCoord top = info.page_rect.origin.y + info.page_rect.size.height - t;
		NXCoord left = info.page_rect.origin.x + l;

		if (img.page_header != 0)
			{
			top -= pages->page_header_height;
			pt.y = top;
			pt.x = left;
			[img.page_header composite:NX_COPY toPoint:&pt];
			}

		if (img.page_footer != 0)
			{
			pt.x = left;
			pt.y = bottom;
			[img.page_footer composite:NX_COPY toPoint:&pt];
			bottom += pages->page_footer_height;
			}

		if (img.row_titles != 0)
			{
			pt.x = left;		// FIXME: Check for centering
			pt.y = bottom;
			[img.row_titles composite:NX_COPY toPoint:&pt];
			left += pages->row_titles_width;
			}

		if (img.col_titles != 0)
			{
			pt.x = left;		// FIXME: Check for centering
			pt.y = top - pages->col_titles_height;
			[img.col_titles composite:NX_COPY toPoint:&pt];
			}

		return self;
		}

	return 0;
	}


//-----------------------------------------------------------------------------
// placePrintRect:offset:
//-----------------------------------------------------------------------------
- (id)placePrintRect:(NXRect const*)rect offset:(NXPoint*)pt
	{
	// FIXME: Deal with centering.
	float l,r,t,b;
	PrintInfo* const printInfo = [NXApp printInfo];
	NXRect const* const paperRect = [printInfo paperRect];
	[printInfo getMarginLeft:&l right:&r top:&t bottom:&b];

	pt->x = paperRect->origin.x + l				// FIXME: These are not scaled.
				+ pages->row_titles_width;		// FIXME: This is scaled.

	pt->y = paperRect->origin.y					// FIXME: These are not scaled.
				+ paperRect->size.height - t	// FIXME: These are not scaled.
				- pages->page_header_height		// FIXME: This is scaled.
				- pages->col_titles_height		// FIXME: This is scaled.
				- rect->size.height;			// FIXME: This is scaled.

	return self;
	}


//-----------------------------------------------------------------------------
// getPrintInfo
//-----------------------------------------------------------------------------
- (MiscTablePrintInfo const*)getPrintInfo
	{
	return (pages != 0) ? &(pages->info) : 0;
	}

@end
