/*
 * RZBrowserCell
 *
 * A subclass of BrowserCell supporting fixed and variable position tabs,
 * multi-font text and inline graphics.
 *
 * Here is an example of setting up a Browser to use a RZBrowserCell:
 *
 * 	// create a prototype cell for the browser
 * 	id cellPrototype = [[RZBrowserCell alloc] init];
 * 	
 * 	// initialize the tab stops 
 * 	// fixed tab at 10 pixels 
 * 	[cellPrototype addFixedTab:10.0];
 * 	
 * 	// proportional tab at 20% with min at 30 and max at 600 pixels 
 * 	[cellPrototype addProportionalTab:0.2 min:30 max:600];
 * 	
 * 	// fixed tab 60 pixels from right side 
 * 	[cellPrototype addFixedTab:-60.0];
 * 	
 * 	// make sure to set the prototype before we've loaded any columns
 * 	[browser setCellPrototype:cellPrototype];
 * 	[browser loadColumnZero];
 * 	[[browser window] makeKeyAndOrderFront:self];
 *
 *
 * You may freely copy, distribute and reuse the code in this example.
 * This code is provided AS IS without warranty of any kind, expressed 
 * or implied, as to its fitness for any particular use.
 *
 * Copyright 1995 Ralph Zazula (rzazula@next.com).  All Rights Reserved.
 *
 */


#import "RZBrowserCell.h"
#import "RZSortedList.h"
#import "RZTextToken.h"
#import "RZTabStop.h"
#import "RZSimpleString.h"
#import "RZRefCountedList.h"
#import <appkit/appkit.h>

#define BORDER 1

@interface RZBrowserCell(Private)
- _addTab:(RZTabStop *)aTab;
- _addText:(RZTextToken *)aText;
- _drawToken:(RZTextToken *)token inside:(const NXRect *)cellFrame inView:controlView;
@end

@implementation RZBrowserCell

- copyFromZone:(NXZone *)zone
{
	if(self = [super copyFromZone:zone]) {
		if(tabStops) {
			[tabStops addReference];
		}
		if(textTokens) {
			[textTokens addReference];
		}
	}
	return self;
}

- free
{
	if(tabStops) {
		tabStops = [[tabStops freeObjects] free];
	}
	if(textTokens) {
		textTokens = [[textTokens freeObjects] free];
	}
	return [super free];
}

- setFont:fontObj
{
  [super setFont:fontObj];
  /*
   * save this info so we don't have to look it up every time we draw
   * Note:  support for a TextCell is a font object
   */
  NXTextFontInfo(support, &ascender, &descender, &lineHeight);
  return self;
}    

- calcCellSize:(NXSize *)theSize inRect:(const NXRect *)aRect
{	
	NXRect b;
	
	[super calcCellSize:theSize inRect:aRect];
	[[self controlView] getBounds:&b];
	theSize->width = b.size.width;
	return self;
}

- drawInside:(const NXRect *)cellFrame inView:controlView
{
	int i, count;
	
	if([[self font] screenFont]) {
		[[[self font] screenFont] set];
	} else {
		[[self font] set];
	}

	/* erase the cell */
	PSsetgray((cFlags1.state || cFlags1.highlighted) ? NX_WHITE : NX_LTGRAY);
	NXRectFill(cellFrame);

	/* draw the individual tokens */
	count = [textTokens count];
	for(i=0; i<count; i++) {
		[self _drawToken:(RZTextToken *)[textTokens objectAt:i] 
			inside:cellFrame inView:controlView];
	}

#if BORDER
	/* all drawing from now on will be in dark gray */
	PSsetgray(NX_DKGRAY);
	
	/* draw the two dark gray lines above and below the cell */
	if (cFlags1.state || cFlags1.highlighted) {
		NXRect  rectArray[2];
		/*
			* draw 1-pixel tall rectangles instead of lines (this is faster than
			* PSmoveto(); PSlineto()). 
			*/
		NXSetRect(&(rectArray[0]), NX_X(cellFrame), NX_Y(cellFrame),
				NX_WIDTH(cellFrame), 1.0);
		NXSetRect(&(rectArray[1]), NX_X(cellFrame), NX_MAXY(cellFrame) - 1.0,
				NX_WIDTH(cellFrame), 1.0);
	
		/* using NXRectFillList is faster than separate calls to NXRectFill */
		NXRectFillList(rectArray, 2);
	}
#endif

	return self;
}

- highlight:(const NXRect *)cellFrame inView:controlView lit:(BOOL)flag
{
	if (cFlags1.highlighted != flag) {
		cFlags1.highlighted = flag;
		[self drawInside:cellFrame inView:controlView];
	}
	return self;
}

/*** manipulating tabs ***/

- addProportionalTab:(float)position
{
	return [self addProportionalTab:position min:0.0 max:0.0];
}

- addProportionalTab:(float)position min:(float)minPos max:(float)maxPos
{
	id aTab = [[RZTabStop alloc] initProportional:position min:minPos max:maxPos];
	[self _addTab:aTab];
	return self;
}

- addFixedTab:(float)position
{
	id aTab = [[RZTabStop alloc] initFixed:position];
	[self _addTab:aTab];
	return self;
}
	
- clearTabs
{
	if(tabStops) {
		[tabStops freeObjects];
	}
	return self;
}

/*** manipulating text tokens ***/

- setText:(const char *)format at:(unsigned)index font:(char)font color:(char)color, ...
{
	va_list args;
	static char buf[1024];
	
	va_start(args, format);
	vsprintf(buf, format, args);
	
	[self _addText:[[RZTextToken alloc] initText:buf at:index font:font color:color]];
	
	return self;
}

- setText:(const char *)format at:(unsigned)index, ...
{
	va_list args;
	static char buf[1024];
	
	va_start(args, format);
	vsprintf(buf, format, args);
	
	[self _addText:[[RZTextToken alloc] initText:buf at:index]];
	
	return self;
}

- setText:(const char *)format at:(unsigned)index font:(char)font, ...
{
	va_list args;
	static char buf[1024];
	
	va_start(args, format);
	vsprintf(buf, format, args);
	
	[self _addText:[[RZTextToken alloc] initText:buf at:index font:font color:0]];
	
	return self;
}

- setText:(const char *)format at:(unsigned)index color:(char)color, ...
{
	va_list args;
	static char buf[1024];
	
	va_start(args, format);
	vsprintf(buf, format, args);
	
	[self _addText:[[RZTextToken alloc] initText:buf at:index font:0 color:color]];
	
	return self;
}

- clearText
{
	if(textTokens) {
		[textTokens freeObjects];
	}
	return self;
}

/*** manipulating icons ***/

- setImageNamed:(const char *)name at:(unsigned)index
{
	[self _addText:[[RZTextToken alloc] initImage:name at:index]];
	return self;
}
		
/*** archiving ***/

- write:(NXTypedStream *)ts
{
	[super write:ts];
	NXWriteTypes(ts, "fff@@", &ascender, &descender, &lineHeight, &tabStops, &textTokens);
	return self;
}

- read:(NXTypedStream *)ts
{
	[super read:ts];
	NXReadTypes(ts, "fff@@", &ascender, &descender, &lineHeight, &tabStops, &textTokens);
	return self;
}

@end

@implementation RZBrowserCell(Private)

- _addTab:(RZTabStop *)aTab
{
	if(!tabStops) {
		tabStops = [[RZRefCountedList alloc] init];
	}
	[tabStops addObject:aTab];
	return self;
}

- _addText:(RZTextToken *)aText
{
	int i;
		
	if(!textTokens) {
		textTokens = [[RZSortedList alloc] init];
	}
	/* remove any tokens at the same index */
	for(i=0; i<[textTokens count]; i++) {
		if([(RZTextToken *)[textTokens objectAt:i] position] == [aText position]) {
			[[textTokens removeObjectAt:i] free];
		}
	}
	[textTokens addObject:aText];
	return self;
}

- _drawToken:(RZTextToken *)token inside:(const NXRect *)cellFrame inView:controlView
{
	static id textCell = nil;

	switch([token isText]) {
		case YES : {						// text
			NXRect rect = *cellFrame;
			RZTabStop *thisStop, *nextStop;
			float thisStopPos, nextStopPos;
			
			if(!textCell) {
				textCell = [[TextFieldCell alloc] init];
				[textCell setWrap:NO];
			}
			[textCell setFont:[self font]];
			
			thisStop = [tabStops objectAt:[token position]];
			nextStop = [tabStops objectAt:([token position] + 1)];
			
			if([thisStop fixed]) {
				thisStopPos = [thisStop position];
				if(thisStopPos < 0.0) {
					thisStopPos += NX_WIDTH(cellFrame);
				}
			} else {
				thisStopPos = [thisStop position] * NX_WIDTH(cellFrame);
					
				if(([thisStop max] > 0.0) && (thisStopPos > [thisStop max])) {
					thisStopPos = [thisStop max];
				}
				if(thisStopPos < [thisStop min]) {
					thisStopPos = [thisStop min];
				}
			}
			
			if(nextStop) {
				if([nextStop fixed] ) {
					nextStopPos = [nextStop position];
					if(nextStopPos < 0.0) {
						nextStopPos += NX_WIDTH(cellFrame);
					}
				} else {
					nextStopPos = [nextStop position] * NX_WIDTH(cellFrame);
						
					if(([nextStop max] > 0.0) && (nextStopPos > [nextStop max])) {
						nextStopPos = [nextStop max];
					}
					if(nextStopPos < [nextStop min]) {
						nextStopPos = [nextStop min];
					}
				}
			} else {
				nextStopPos = 0.0;
			}
			
			/* move to the correct tab position */
			if(nextStopPos > 0.0) {
				NX_WIDTH(&rect) = nextStopPos - thisStopPos;
			} else {
				NX_WIDTH(&rect) -= thisStopPos;
			}
			
			NX_X(&rect) = thisStopPos;
//			NX_Y(&rect) += descender - 1;
						
			/* set the gray level */
			switch([token color]) {
				default :
				case 0 :
					[textCell setTextGray:NX_BLACK];
					break;
				case 1 :
					[textCell setTextGray:NX_DKGRAY];
					break;
				case 2 :
				case 3 :
					if(cFlags1.state || cFlags1.highlighted) {
						[textCell setTextGray:NX_LTGRAY];
					} else {
						[textCell setTextGray:NX_WHITE];
					}
					break;
			}

			/* set the font */
			{
			id newFont;
			char buf[128];
				switch([token font]) {	
					default :
					case 0 :
						sprintf(buf, "%s", [[self font] familyName]);
						newFont = [[Font newFont:buf size:[[self font] pointSize]] screenFont];
						break;
					case 1 :
						sprintf(buf, "%s-Bold", [[self font] familyName]);
						newFont = [[Font newFont:buf size:[[self font] pointSize]] screenFont];
						break;
					case 2 :
						sprintf(buf, "%s-Oblique", [[self font] familyName]);
						newFont = [[Font newFont:buf size:[[self font] pointSize]] screenFont];
						break;
					case 3 :
						sprintf(buf, "%s-BoldOblique", [[self font] familyName]);
						newFont = [[Font newFont:buf size:[[self font] pointSize]] screenFont];
						break;
				}
				[textCell setFont:newFont];
			}
			
			[textCell setStringValue:(char *)[[token data] string]];
			[textCell drawInside:&rect inView:controlView];
			break;
		}
		
		case NO : {						// icon
			float thisStopPos;
			RZTabStop *thisStop = [tabStops objectAt:[token position]];
			NXPoint p = cellFrame->origin;
			NXSize imageSize;
			
			if([thisStop fixed]) {
				thisStopPos = [thisStop position];
				if(thisStopPos < 0.0) {
					thisStopPos += NX_WIDTH(cellFrame);
				}
			} else {
				thisStopPos = [thisStop position] * NX_WIDTH(cellFrame);
					
				if(([thisStop max] > 0.0) && (thisStopPos > [thisStop max])) {
					thisStopPos = [thisStop max];
				}
				if(thisStopPos < [thisStop min]) {
					thisStopPos = [thisStop min];
				}
			}

			[[token data] getSize:&imageSize];
			p.x += thisStopPos;
			p.y += NX_HEIGHT(cellFrame) - (NX_HEIGHT(cellFrame) - imageSize.height) / 2.0;
			[[token data] composite:NX_SOVER toPoint:&p];

			break;
		}
		
	}
	return self;
}

@end
