//=============================================================================
//
//		Copyright (C) 1995-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.
//
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableScroll.M
//
//		ScrollView class that displays a 2-D table of cells.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableScroll.M,v 1.33 97/06/10 05:51:08 sunshine Exp $
// $Log:		MiscTableScroll.M,v $
// Revision 1.33  97/06/10	05:51:08  sunshine
// v127: Removed unused header.
// 
// Revision 1.32  97/02/11	19:30:39  sunshine
// v123: Added new ...:byExtension: variations to all selection methods.
// Added deselection methods.  Published -selectedCell.
// reflectSelection --> selectionChanged
// 
// Revision 1.31  97/02/05	07:57:24  sunshine
// v122: Added -tracking and -setTracking: to fix lazy-mode mouse-tracking /
// display problem.
//-----------------------------------------------------------------------------
#import <misckit/MiscTableScroll.h>
#import <misckit/MiscTableCell.h>
#import <misckit/MiscIntList.h>
#import "MiscColView.h"
#import "MiscNullView.h"
#import "MiscRowView.h"
#import "MiscTableBorder.h"
#import "MiscTableScrollPrivate.h"
#import "MiscTableView.h"
#import <new.h>

extern "Objective-C" {
#import <appkit/Application.h>
#import <appkit/Cell.h>
#import <appkit/ClipView.h>
#import <appkit/Font.h>
#import <appkit/FontManager.h>
}

extern "C" {
#import <math.h>
#import <strings.h>
}

// There are no structural differences between versions 0 and 1.  However,
// the range of the MiscSortType values increased from 0..8 to 0..12
#define MISC_TS_VERSION_0		0
#define MISC_TS_VERSION_1		1
#define MISC_TS_VERSION			MISC_TS_VERSION_1

typedef MiscDelegateFlags DF;

@implementation MiscTableScroll

//-----------------------------------------------------------------------------
// + initialize
//-----------------------------------------------------------------------------
+ (id)initialize
	{
	if (self == [MiscTableScroll class])
		{
		[self setVersion: MISC_TS_VERSION];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// Tag
//-----------------------------------------------------------------------------
- (int)tag				{ return tag; }
- (id)setTag:(int)x		{ tag = x;	return self; }


//-----------------------------------------------------------------------------
// Next / Previous Text
//-----------------------------------------------------------------------------
- (id)nextText			{ return nextText; }
- (id)previousText		{ return previousText; }
- (id)setNextText:(id)obj
	{
	nextText = obj;
	if (obj && [obj respondsTo: @selector(setPreviousText:)])
		[obj setPreviousText: self];
	return self;
	}
- (id)setPreviousText:(id)obj			{ previousText = obj; return self; }
- (BOOL)isSelectable					{ return YES; }
- (BOOL)isEnabled						{ return enabled; }
- (id)setEnabled:(BOOL)flag				{ enabled = flag; return self; }

- (id)selectText:(id)sender
	{
	if ([window firstResponder] != (id)tableView)
		[window makeFirstResponder:tableView];
	return self;
	}


//-----------------------------------------------------------------------------
// Multicast Messages
//-----------------------------------------------------------------------------
- (id)sendAction:(SEL)aSel to:(id)obj forAllCells:(BOOL)flag
		{
		int const rlim = num_rows;
		int const clim = num_cols;
		for (int r = 0;	 r < rlim;	r++)
			for (int c = 0;	 c < clim;	c++)
				if (flag || [self cellIsSelected:r:c])
					if (![obj perform:aSel with:[self cellAt:r:c]])
						return self;
		return self;
		}

- (int)makeCellsPerform:(SEL)s with:(id)p1 with:(id)p2 selectedOnly:(BOOL)f
		{
		int count = 0;
		int const rlim = num_rows;
		int const clim = num_cols;
		for (int r = 0;	 r < rlim;	r++)
			for (int c = 0;	 c < clim;	c++)
				if (!f || [self cellIsSelected:r:c])
					{
					id cell = [self cellAt:r:c];
					if ([cell respondsTo:s])
						if ([cell perform:s with:p1 with:p2])
							count++;
						else
							return count;
					}
		return count;
		}

- (int)makeCellsPerform:(SEL)aSel with:(id)p1 selectedOnly:(BOOL)flag
	{ return [self makeCellsPerform:aSel with:p1 with:0 selectedOnly:flag]; }
- (int)makeCellsPerform:(SEL)aSel selectedOnly:(BOOL)flag
	{ return [self makeCellsPerform:aSel with:0 with:0 selectedOnly:flag]; }

- (int)makeCellsPerform:(SEL)aSel
	{ return [self makeCellsPerform:aSel selectedOnly:NO]; }
- (int)makeCellsPerform:(SEL)aSel with:(id)p1
	{ return [self makeCellsPerform:aSel with:p1 selectedOnly:NO]; }
- (int)makeCellsPerform:(SEL)aSel with:(id)p1 with:(id)p2
	{ return [self makeCellsPerform:aSel with:p1 with:p2 selectedOnly:NO];}


//-----------------------------------------------------------------------------
// FINDING CELLS
//-----------------------------------------------------------------------------
- (int)border:(MiscBorderType)b findSlotWithTag:(int)x
		{
		int const lim = (int) [self numSlots:b];
		for (int i = 0;	 i < lim;  i++)
			if ([self border:b slotTag:i] == x)
				return i;
		return -1;
		}
- (int)findColWithTag:(int)x
		{ return [self border:MISC_COL_BORDER findSlotWithTag:x]; }
- (int)findRowWithTag:(int)x
		{ return [self border:MISC_ROW_BORDER findSlotWithTag:x]; }

- (id)findCell:(id)cell row:(int*)row col:(int*)col
	{
	int const NRows = [self numRows];
	int const NCols = [self numCols];
	for (int r = 0;	 r < NRows;	 r++)
		for (int c = 0;	 c < NCols;	 c++)
			if ([self cellAt:r:c] == cell)
				{
				*row = r;
				*col = c;
				return self;
				}
	*row = -1;
	*col = -1;
	return 0;
	}

- (id)findCellWithTag:(int)x row:(int*)row col:(int*)col
	{
	int const NRows = [self numRows];
	int const NCols = [self numCols];
	for (int r = 0;	 r < NRows;	 r++)
		for (int c = 0;	 c < NCols;	 c++)
			{
			id const cell = [self cellAt:r:c];
			if (cell && [cell respondsTo:@selector(tag)] && [cell tag] == x)
				{
				*row = r;
				*col = c;
				return cell;
				}
			}
	*row = -1;
	*col = -1;
	return 0;
	}

- (id)findCellWithTag:(int)x
	{
	int r, c;
	return [self findCellWithTag:x row:&r col:&c];
	}


//-----------------------------------------------------------------------------
// Selection
//-----------------------------------------------------------------------------
- (MiscSelectionMode)selectionMode { return mode; }
- (void)setSelectionMode:(MiscSelectionMode)x
	{
	if (x != mode)
		{
		[self clearSelection];
		mode = x;
		[colInfo.view setSelectionMode: mode];
		[rowInfo.view setSelectionMode: mode];
		[tableView setSelectionMode: mode];
		}
	}

- (id)selectedCell
	{
	MiscCoord_P const col = [self selectedCol];
	MiscCoord_P const row = [self selectedRow];
	return (col >= 0 && row >= 0) ? [self cellAt:row:col] : 0;
	}

- (BOOL)borderHasSelection:(MiscBorderType)b
		{ return info[b]->border->hasSelection(); }
- (BOOL)hasRowSelection { return [self borderHasSelection:MISC_ROW_BORDER]; }
- (BOOL)hasColSelection { return [self borderHasSelection:MISC_COL_BORDER]; }

- (BOOL)borderHasMultipleSelection:(MiscBorderType)b
		{ return info[b]->border->hasMultipleSelection(); }
- (BOOL)hasMultipleRowSelection
		{ return [self borderHasMultipleSelection: MISC_ROW_BORDER]; }
- (BOOL)hasMultipleColSelection
		{ return [self borderHasMultipleSelection: MISC_COL_BORDER]; }

- (unsigned int)borderNumSelectedSlots:(MiscBorderType)b
		{ return info[b]->border->numSelected(); }
- (unsigned int)numSelectedRows
		{ return [self borderNumSelectedSlots: MISC_ROW_BORDER]; }
- (unsigned int)numSelectedCols
		{ return [self borderNumSelectedSlots: MISC_COL_BORDER]; }

- (BOOL)border:(MiscBorderType)b slotIsSelected:(MiscCoord_P)slot
		{
		MiscTableBorder const* bp = info[b]->border;
		return bp->goodPos( slot ) && bp->isSelected_P( slot );
		}
- (BOOL)rowIsSelected:(MiscCoord_P)row
		{ return [self border:MISC_ROW_BORDER slotIsSelected:row]; }
- (BOOL)colIsSelected:(MiscCoord_P)col
		{ return [self border:MISC_COL_BORDER slotIsSelected:col]; }
- (BOOL)cellIsSelected:(MiscCoord_P)r :(MiscCoord_P)c
		{ return ([self rowIsSelected:r] || [self colIsSelected:c]); }

- (MiscCoord_P)borderSelectedSlot:(MiscBorderType)b
		{ return info[b]->border->selectedSlot_P(); }
- (MiscCoord_P)selectedRow
		{ return [self borderSelectedSlot:MISC_ROW_BORDER]; }
- (MiscCoord_P)selectedCol
		{ return [self borderSelectedSlot:MISC_COL_BORDER]; }

- (void)border:(MiscBorderType)b selectedTags:(MiscIntList*)tags
		{ info[b]->border->selectedTags( tags ); }
- (void)selectedRowTags:(MiscIntList*)tags
		{ [self border:MISC_ROW_BORDER selectedTags:tags]; }
- (void)selectedColTags:(MiscIntList*)tags
		{ [self border:MISC_COL_BORDER selectedTags:tags]; }

- (void)border:(MiscBorderType)b selectedSlots:(MiscIntList*)slots
		{ info[b]->border->selectedSlots( slots ); }
- (void)selectedRows:(MiscIntList*)rows
		{ [self border:MISC_ROW_BORDER selectedSlots:rows]; }
- (void)selectedCols:(MiscIntList*)cols
		{ [self border:MISC_COL_BORDER selectedSlots:cols]; }

- (void)border:(MiscBorderType)b selectSlot:(MiscCoord_P)p_slot
		byExtension:(BOOL)extend
		{
		BOOL changed = NO;
		MiscTableBorder* const ob = info[ MISC_OTHER_BORDER(b) ]->border;
		if (ob->hasSelection())
			{
			ob->selectNone();
			changed = YES;
			}
		MiscTableBorder* const bp = info[b]->border;
		if (!extend && bp->numSelected() > 0)
			{
			bp->selectNone();
			changed = YES;
			}
		if (bp->goodPos( p_slot ))
			{
			MiscCoord_V const v_slot = bp->physicalToVisual( p_slot );
			if (!bp->isSelected( v_slot ))
				{
				if (mode == MISC_RADIO_MODE)
					bp->selectOne( v_slot );
				else
					bp->select( v_slot );
				changed = YES;
				}
			}
		if (changed)
			[self selectionChanged];
		}
- (void)border:(MiscBorderType)b selectSlot:(MiscCoord_P)p_slot
		{ [self border:b selectSlot:p_slot]; }
- (void)selectRow:(MiscCoord_P)row byExtension:(BOOL)extend
		{ [self border:MISC_ROW_BORDER selectSlot:row byExtension:extend]; }
- (void)selectRow:(MiscCoord_P)row
		{ [self selectRow:row byExtension:NO]; }
- (void)selectCol:(MiscCoord_P)col byExtension:(BOOL)extend
		{ [self border:MISC_COL_BORDER selectSlot:col byExtension:extend]; }
- (void)selectCol:(MiscCoord_P)col
		{ [self selectCol:col byExtension:NO]; }

- (void)border:(MiscBorderType)b selectTags:(MiscIntList*)tags
		byExtension:(BOOL)extend
		{
		info[b]->border->selectTags( tags, extend );
		[self selectionChanged];
		}
- (void)border:(MiscBorderType)b selectTags:(MiscIntList*)tags
		{ [self border:b selectTags:tags byExtension:NO]; }
- (void)selectRowTags:(MiscIntList*)tags byExtension:(BOOL)extend
		{ [self border:MISC_ROW_BORDER selectTags:tags byExtension:extend]; }
- (void)selectRowTags:(MiscIntList*)tags
		{ [self selectRowTags:tags byExtension:NO]; }
- (void)selectColTags:(MiscIntList*)tags byExtension:(BOOL)extend
		{ [self border:MISC_COL_BORDER selectTags:tags byExtension:extend]; }
- (void)selectColTags:(MiscIntList*)tags
		{ [self selectColTags:tags byExtension:NO]; }

- (void)border:(MiscBorderType)b selectSlots:(MiscIntList*)slots
		byExtension:(BOOL)extend
		{
		info[b]->border->selectSlots( slots, extend );
		[self selectionChanged];
		}
- (void)border:(MiscBorderType)b selectSlots:(MiscIntList*)slots
		{ [self border:b selectSlots:slots byExtension:NO]; }
- (void)selectRows:(MiscIntList*)rows byExtension:(BOOL)extend
		{ [self border:MISC_ROW_BORDER selectSlots:rows byExtension:extend]; }
- (void)selectRows:(MiscIntList*)rows
		{ [self selectRows:rows byExtension:NO]; }
- (void)selectCols:(MiscIntList*)cols byExtension:(BOOL)extend
		{ [self border:MISC_COL_BORDER selectSlots:cols byExtension:extend]; }
- (void)selectCols:(MiscIntList*)cols
		{ [self selectCols:cols byExtension:NO]; }

- (void)borderSelectAll:(MiscBorderType)b
		{
		if (mode == MISC_LIST_MODE || mode == MISC_HIGHLIGHT_MODE)
			{
			info[b]->border->selectAll();
			[self selectionChanged];
			}
		}
- (void)selectAllRows { [self borderSelectAll: MISC_ROW_BORDER]; }
- (void)selectAllCols { [self borderSelectAll: MISC_COL_BORDER]; }
- (id)selectAll:(id)sender
		{
		[self selectAllRows];
		[self sendActionIfEnabled];
		return self;
		}

- (void)border:(MiscBorderType)b deselectSlot:(MiscCoord_P)p_slot
		{
		MiscTableBorder* const bp = info[b]->border;
		if (bp->goodPos( p_slot ))
			{
			MiscCoord_V const v_slot = bp->physicalToVisual( p_slot );
			if (bp->isSelected( v_slot ))
				{
				bp->unselect( v_slot );
				[self selectionChanged];
				}
			}
		}
- (void)deselectRow:(MiscCoord_P)row
		{ [self border:MISC_ROW_BORDER deselectSlot:row]; }
- (void)deselectCol:(MiscCoord_P)col
		{ [self border:MISC_COL_BORDER deselectSlot:col]; }

- (void)border:(MiscBorderType)b deselectTags:(MiscIntList*)tags
		{
		info[b]->border->unselectTags( tags );
		[self selectionChanged];
		}
- (void)deselectRowTags:(MiscIntList*)tags
		{ [self border:MISC_ROW_BORDER deselectTags:tags]; }
- (void)deselectColTags:(MiscIntList*)tags
		{ [self border:MISC_COL_BORDER deselectTags:tags]; }

- (void)border:(MiscBorderType)b deselectSlots:(MiscIntList*)slots
		{
		info[b]->border->unselectSlots( slots );
		[self selectionChanged];
		}
- (void)deselectRows:(MiscIntList*)rows
		{ [self border:MISC_ROW_BORDER deselectSlots:rows]; }
- (void)deselectCols:(MiscIntList*)cols
		{ [self border:MISC_COL_BORDER deselectSlots:cols]; }

- (void)borderClearSelection:(MiscBorderType)b
		{
		info[b]->border->selectNone();
		[self selectionChanged];
		}
- (void)clearRowSelection { [self borderClearSelection: MISC_ROW_BORDER]; }
- (void)clearColSelection { [self borderClearSelection: MISC_COL_BORDER]; }
- (void)clearSelection { [self clearRowSelection]; [self clearColSelection]; }
- (id)deselectAll:(id)sender
		{
		[self clearSelection];
		[self sendActionIfEnabled];
		return self;
		}


//-----------------------------------------------------------------------------
// Clicked slot stuff
//-----------------------------------------------------------------------------
- (BOOL)tracking { return tracking; }
- (void)setTracking:(BOOL)flag { tracking = flag; }

- (MiscCoord_P)borderClickedSlot:(MiscBorderType)b
		{ return info[b]->border->clickedSlot_P(); }
- (MiscCoord_P)clickedRow { return [self borderClickedSlot:MISC_ROW_BORDER]; }
- (MiscCoord_P)clickedCol { return [self borderClickedSlot:MISC_COL_BORDER]; }
- (id)clickedCell
		{
		MiscCoord_P const r = [self clickedRow];
		MiscCoord_P const c = [self clickedCol];
		return (r >= 0 && c >= 0 ? [self cellAt:r:c] : 0);
		}

- (void)border:(MiscBorderType)b setClickedSlot:(MiscCoord_P)n
		{ info[b]->border->setClickedSlot_P(n); }
- (void)setClickedRow:(MiscCoord_P)r col:(MiscCoord_P)c
		{
		[self border:MISC_ROW_BORDER setClickedSlot:r];
		[self border:MISC_COL_BORDER setClickedSlot:c];
		}

- (void)borderClearClickedSlot:(MiscBorderType)b
		{ info[b]->border->clearClickedSlot(); }
- (void)clearClicked
		{
		[self borderClearClickedSlot:MISC_ROW_BORDER];
		[self borderClearClickedSlot:MISC_COL_BORDER];
		}


//-----------------------------------------------------------------------------
// Keyboard cursor stuff
//-----------------------------------------------------------------------------
- (void)reflectCursor { [tableView reflectCursor]; }
- (MiscCoord_P)borderCursor: (MiscBorderType)b
		{ return info[b]->border->getCursor_P(); }
- (MiscCoord_P)cursorRow { return [self borderCursor: MISC_ROW_BORDER]; }
- (MiscCoord_P)cursorCol { return [self borderCursor: MISC_COL_BORDER]; }

- (void) border:(MiscBorderType)b setCursor:(MiscCoord_P)slot
		{ info[b]->border->setCursor_P( slot ); [self reflectCursor]; }
- (void)setCursorRow:(MiscCoord_P)row
		{ [self border: MISC_ROW_BORDER setCursor: row]; }
- (void)setCursorCol:(MiscCoord_P)col
		{ [self border: MISC_COL_BORDER setCursor: col]; }

- (void)borderClearCursor:(MiscBorderType)b
		{ info[b]->border->clearCursor(); [self reflectCursor]; }
- (void)clearCursorRow { [self borderClearCursor: MISC_ROW_BORDER]; }
- (void)clearCursorCol { [self borderClearCursor: MISC_COL_BORDER]; }
- (void)clearCursor { [self clearCursorRow]; [self clearCursorCol]; }

- (BOOL)borderHasValidCursor:(MiscBorderType)b
		{ return info[b]->border->hasValidCursor(); }
- (BOOL)hasValidCursorRow
		{ return [self borderHasValidCursor: MISC_ROW_BORDER]; }
- (BOOL)hasValidCursorCol
		{ return [self borderHasValidCursor: MISC_COL_BORDER]; }

- (BOOL)isCursorEnabled { return [tableView isCursorEnabled]; }
- (void)disableCursor
	{
	if ([self isCursorEnabled] && [tableView shouldDrawCursor])
		[tableView eraseCursor];
	[tableView disableCursor];
	}

- (void)reenableCursor
	{
	[tableView reenableCursor];
	if ([self isCursorEnabled] && [tableView shouldDrawCursor])
		[tableView drawCursor];
	}


//-----------------------------------------------------------------------------
// Overrides...	 Scrollers are always required.
//-----------------------------------------------------------------------------
- (id)setHorizScrollerRequired:(BOOL)flag { return self; }
- (id)setVertScrollerRequired:(BOOL)flag  { return self; }


//-----------------------------------------------------------------------------
// - getDocClipFrame:
//-----------------------------------------------------------------------------
- (void)getDocClipFrame:(NXRect*)rect
	{
	[contentView getFrame:rect];
	}


//-----------------------------------------------------------------------------
// - freeBorder:
//-----------------------------------------------------------------------------
- (void)freeBorder:(MiscBorderInfo*)p
	{
	[p->view removeFromSuperview];
	[p->view free];
	[p->clip removeFromSuperview];
	[p->clip free];
	delete p->border;
	}


//-----------------------------------------------------------------------------
// - free
//-----------------------------------------------------------------------------
- (id)free
	{
	[self setAutodisplay:NO];			// Don't do any drawing!
	[self emptyAndFreeCells];
	[self freeBorder:&colInfo];
	[self freeBorder:&rowInfo];
	[nullView removeFromSuperview];
	[nullView free];
	[tableView removeFromSuperview];
	[tableView free];
	delete delegateFlags;
	delete dataDelegateFlags;
	return [super free];
	}


//-----------------------------------------------------------------------------
// get/set min/max total size limits.
//-----------------------------------------------------------------------------
- (NXCoord)totalSize:(MiscBorderType)b
		{ return (NXCoord) info[b]->border->totalSize(); }
- (NXCoord)totalWidth
		{ return [self totalSize:MISC_COL_BORDER]; }
- (NXCoord)totalHeight
		{ return [self totalSize:MISC_ROW_BORDER]; }

- (NXCoord)minTotalSize:(MiscBorderType)b
		{ return (NXCoord) info[b]->border->getMinTotalSize(); }
- (NXCoord)maxTotalSize:(MiscBorderType)b
		{ return (NXCoord) info[b]->border->getMaxTotalSize(); }
- (NXCoord)minTotalWidth
		{ return [self minTotalSize:MISC_COL_BORDER]; }
- (NXCoord)maxTotalWidth
		{ return [self maxTotalSize:MISC_COL_BORDER]; }
- (NXCoord)minTotalHeight
		{ return [self minTotalSize:MISC_ROW_BORDER]; }
- (NXCoord)maxTotalHeight
		{ return [self maxTotalSize:MISC_ROW_BORDER]; }

// FIXME: Need to decide whether or not to update the views here.
// FIXME: Should check current value before setting.
- (void)border:(MiscBorderType)b setMinTotalSize:(NXCoord)size
		{ info[b]->border->setMinTotalSize( (MiscPixels)floor(size) ); }
- (void)border:(MiscBorderType)b setMaxTotalSize:(NXCoord)size
		{ info[b]->border->setMaxTotalSize( (MiscPixels)floor(size) ); }
- (void)setMinTotalWidth:(NXCoord)size
		{ [self border:MISC_COL_BORDER setMinTotalSize:size]; }
- (void)setMaxTotalWidth:(NXCoord)size
		{ [self border:MISC_COL_BORDER setMaxTotalSize:size]; }
- (void)setMinTotalHeight:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setMinTotalSize:size]; }
- (void)setMaxTotalHeight:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setMaxTotalSize:size]; }


//-----------------------------------------------------------------------------
// - constrainSize
//-----------------------------------------------------------------------------
- (void)constrainSize
	{
	NXRect r;
	[self getDocClipFrame:&r];
	if ([self minTotalSizeIsConstrained:MISC_COL_BORDER])
		[self border:MISC_COL_BORDER setMinTotalSize: r.size.width];
	if ([self maxTotalSizeIsConstrained:MISC_COL_BORDER])
		[self border:MISC_COL_BORDER setMaxTotalSize: r.size.width];
	if ([self minTotalSizeIsConstrained:MISC_ROW_BORDER])
		[self border:MISC_ROW_BORDER setMinTotalSize: r.size.height];
	if ([self maxTotalSizeIsConstrained:MISC_ROW_BORDER])
		[self border:MISC_ROW_BORDER setMaxTotalSize: r.size.height];
	[rowInfo.view adjustSize];
	[colInfo.view adjustSize];
	[tableView adjustSize];
	}


//-----------------------------------------------------------------------------
// - sizeToCells
//-----------------------------------------------------------------------------
- (id)sizeToCells
	{
	[self constrainSize];
	return self;
	}


//-----------------------------------------------------------------------------
// get/set min/max constrained
//-----------------------------------------------------------------------------
- (BOOL)minTotalSizeIsConstrained:(MiscBorderType)b
		{ return info[b]->constrain_min; }
- (BOOL)maxTotalSizeIsConstrained:(MiscBorderType)b
		{ return info[b]->constrain_max; }
- (BOOL)minTotalWidthIsConstrained
		{ return [self minTotalSizeIsConstrained:MISC_COL_BORDER]; }
- (BOOL)maxTotalWidthIsConstrained
		{ return [self maxTotalSizeIsConstrained:MISC_COL_BORDER]; }
- (BOOL)minTotalHeightIsConstrained
		{ return [self minTotalSizeIsConstrained:MISC_ROW_BORDER]; }
- (BOOL)maxTotalHeightIsConstrained
		{ return [self maxTotalSizeIsConstrained:MISC_ROW_BORDER]; }

- (void)border:(MiscBorderType)b constrainMinTotalSize:(BOOL)flag;
		{
		MiscBorderInfo* ip = info[b];
		if (ip->constrain_min != flag)
			{
			ip->constrain_min = flag;
			if (!flag)
				[self border:MISC_COL_BORDER setMinTotalSize:0];
			[self constrainSize];
			}
		}

- (void)border:(MiscBorderType)b constrainMaxTotalSize:(BOOL)flag
		{
		MiscBorderInfo* ip = info[b];
		if (ip->constrain_max != flag)
			{
			ip->constrain_max = flag;
			if (!flag)
				[self border:MISC_COL_BORDER
						setMaxTotalSize: (NXCoord) MISC_MAX_PIXELS_SIZE ];
			[self constrainSize];
			}
		}

- (void)constrainMinTotalWidth:(BOOL)flag
		{ [self border:MISC_COL_BORDER constrainMinTotalSize:flag]; }
- (void)constrainMaxTotalWidth:(BOOL)flag
		{ [self border:MISC_COL_BORDER constrainMaxTotalSize:flag]; }
- (void)constrainMinTotalHeight:(BOOL)flag
		{ [self border:MISC_ROW_BORDER constrainMinTotalSize:flag]; }
- (void)constrainMaxTotalHeight:(BOOL)flag
		{ [self border:MISC_ROW_BORDER constrainMaxTotalSize:flag]; }


//-----------------------------------------------------------------------------
// - sizeTo::
//-----------------------------------------------------------------------------
- (id)sizeTo:(NXCoord)width :(NXCoord)height
	{
	[super sizeTo:width:height];
	[self constrainSize];
	return self;
	}


//-----------------------------------------------------------------------------
// - forwardBGColor
//-----------------------------------------------------------------------------
- (void)forwardBGColor
	{
	[super setBackgroundColor:backgroundColor];
	[colInfo.clip setBackgroundColor:backgroundColor];
	[rowInfo.clip setBackgroundColor:backgroundColor];
	}


//-----------------------------------------------------------------------------
// - initBorder:type:
//-----------------------------------------------------------------------------
- (void)initBorder:(MiscBorderInfo*)p type:(MiscBorderType)type
	{
	NXZone* const z = [self zone];
	if (p->border == 0)
		p->border = new( NXZoneMalloc(z,sizeof(*(p->border))) )
						MiscTableBorder( type );
	p->border->setOwner( self );

	if (type == MISC_COL_BORDER)
		p->view = [[MiscColView allocFromZone:z] 
						initFrame:0 scroll:self info:p->border];
	else
		p->view = [[MiscRowView allocFromZone:z]
						initFrame:0 scroll:self info:p->border];

	p->clip = [[ClipView allocFromZone:z] initFrame:0];
	[p->clip setDocView:p->view];
	if (p->isOn)
		{
		[self addSubview: p->clip];
		[window invalidateCursorRectsForView: p->view];
		}
	}


//-----------------------------------------------------------------------------
// - doInit:
//
//		NOTE *1*: The tableView was initialized with an argument of 0 so it's
//				frame is invalid.  Calling constrainSize with an invalid frame
//				can result in crashers when it tries to access slots outside
//				of the appropriate area.  Calling -tile first ensures that the
//				frame is valid.
//-----------------------------------------------------------------------------
- (void)doInit:(int)ver
	{
	editInfo.editing = NO;
	tracking = NO;

	[window disableDisplay];
	[self setBorderType: NX_BEZEL];
	[super setHorizScrollerRequired: YES];
	[super setVertScrollerRequired: YES];

	info[ MISC_COL_BORDER ] = &colInfo;
	info[ MISC_ROW_BORDER ] = &rowInfo;

	[self initBorder:&colInfo type:MISC_COL_BORDER];
	[self initBorder:&rowInfo type:MISC_ROW_BORDER];

	NXZone* const z = [self zone];

	delegateFlags =
		new( NXZoneMalloc(z,sizeof(*delegateFlags)) ) MiscDelegateFlags;
	dataDelegateFlags =
		new( NXZoneMalloc(z,sizeof(*dataDelegateFlags)) ) MiscDelegateFlags;

	tableView = [[MiscTableView allocFromZone:z] initFrame:0
		scroll:self
		colInfo:colInfo.border
		rowInfo:rowInfo.border];

	nullView = [[MiscNullView allocFromZone:z] initFrame:0];

	if (colInfo.isOn && rowInfo.isOn)
		[self addSubview:nullView];

	id oldView = [self setDocView: tableView];
	[oldView free];
	[self tile];										// NOTE *1*
	[self constrainSize];
	[self forwardBGColor];

	[self registerServicesTypes];
	[window reenableDisplay];
	}


//-----------------------------------------------------------------------------
// - initFrame:
//-----------------------------------------------------------------------------
- (id)initFrame:(NXRect const*)frameRect
	{
	[super initFrame: frameRect];

	tag = 0;
	enabled = YES;

	colInfo.border = 0;
	colInfo.isOn = YES;
	colInfo.constrain_min = YES;
	colInfo.constrain_max = NO;

	rowInfo.border = 0;
	rowInfo.isOn = NO;
	rowInfo.constrain_min = NO;
	rowInfo.constrain_max = NO;

	id classObj = [self class];
	font = [classObj defaultFont];
	textColor = [classObj defaultTextColor];
	backgroundColor = [classObj defaultBackgroundColor];
	highlightTextColor = [classObj defaultHighlightTextColor];
	highlightBackgroundColor = [classObj defaultHighlightBackgroundColor];

	[self doInit:MISC_TS_VERSION];
	return self;
	}


//-----------------------------------------------------------------------------
// - setFrame:
//-----------------------------------------------------------------------------
- (id)setFrame:(NXRect const*)frameRect
	{
	[super setFrame:frameRect];
	[self constrainSize];
	return self;
	}


//-----------------------------------------------------------------------------
// - adjustSizes
//-----------------------------------------------------------------------------
- (void)adjustSizes
	{
	if (colInfo.isOn && colInfo.view != 0)
		[colInfo.view adjustSize];
	if (rowInfo.isOn && rowInfo.view != 0)
		[rowInfo.view adjustSize];
	[tableView adjustSize];
	}


//-----------------------------------------------------------------------------
// - tile
//-----------------------------------------------------------------------------
- (id)tile
	{
	[window disableDisplay];
	[super tile];
	[self adjustSizes];

	if (colInfo.isOn && !rowInfo.isOn)
		{
		NXCoord colHeight = [colInfo.view frameHeight];
		NXRect colRect;
		NXRect docRect;

		[contentView getFrame: &docRect];

		NXDivideRect( &docRect, &colRect, colHeight, NX_YMIN );

		[colInfo.clip setFrame: &colRect];
		[contentView setFrame: &docRect];
		[window invalidateCursorRectsForView: colInfo.view];
		}
	else if (!colInfo.isOn && rowInfo.isOn)
		{
		NXCoord rowWidth  = [rowInfo.view frameHeight];
		NXRect rowRect;
		NXRect docRect;

		[contentView getFrame: &docRect];

		NXDivideRect( &docRect, &rowRect, rowWidth, NX_XMIN );

		[rowInfo.clip setFrame: &rowRect];
		[contentView setFrame: &docRect];
		[window invalidateCursorRectsForView: rowInfo.view];
		}
	else if (colInfo.isOn && rowInfo.isOn)
		{
		NXCoord colHeight = [colInfo.view frameHeight];
		NXCoord rowWidth  = [rowInfo.view frameHeight];
		NXRect nulRect;
		NXRect colRect;
		NXRect rowRect;
		NXRect docRect;

		[contentView getFrame: &docRect];

		NXDivideRect( &docRect, &colRect, colHeight, NX_YMIN );
		NXDivideRect( &colRect, &nulRect, rowWidth, NX_XMIN );
		NXDivideRect( &docRect, &rowRect, rowWidth, NX_XMIN );

		[nullView setFrame: &nulRect];
		[colInfo.clip setFrame: &colRect];
		[rowInfo.clip setFrame: &rowRect];
		[contentView setFrame: &docRect];
		[window invalidateCursorRectsForView: colInfo.view];
		[window invalidateCursorRectsForView: rowInfo.view];
		}

	[window reenableDisplay];
	return self;
	}


//-----------------------------------------------------------------------------
// - reflectScroll:
//-----------------------------------------------------------------------------
- (id)reflectScroll:(id)aView
	{
	if (aView == contentView) // only reflect position of contentView
		[super reflectScroll: aView];
	return self;
	}


//-----------------------------------------------------------------------------
// - scrollClip:to:
//-----------------------------------------------------------------------------
- (id)scrollClip:(id)aClipView to:(NXPoint const*)aPoint
	{
	if (aClipView == contentView)				// contentView only.
		{
		NXRect rect;
		[window disableFlushWindow];

		[aClipView rawScroll: aPoint];			// Scroll content

		if (colInfo.isOn)
			{
			[colInfo.clip getBounds: &rect];	// Maybe scroll col headings.
			if (rect.origin.x != aPoint->x)
				{
				rect.origin.x = aPoint->x;
				[colInfo.clip rawScroll: &rect.origin];
				[window invalidateCursorRectsForView: colInfo.view];
				}
			}

		if (rowInfo.isOn)
			{
			[rowInfo.clip getBounds: &rect];	// Maybe scroll row labels.
			if (rect.origin.y != aPoint->y)
				{
				rect.origin.y = aPoint->y;
				[rowInfo.clip rawScroll: &rect.origin];
				[window invalidateCursorRectsForView: rowInfo.view];
				}
			}

		[[window reenableFlushWindow] flushWindow];
		}
	else if (aClipView == (id)colInfo.clip)
		{
		NXRect rect;
		[contentView getBounds: &rect];
		rect.origin.x = aPoint->x;
		[self scrollClip:contentView to:&(rect.origin)];
		[self reflectScroll:contentView];
		}
	else if (aClipView == (id)rowInfo.clip)
		{
		NXRect rect;
		[contentView getBounds: &rect];
		rect.origin.y = aPoint->y;
		[self scrollClip:contentView to:&(rect.origin)];
		[self reflectScroll:contentView];
		}
	return self;
	}



//-----------------------------------------------------------------------------
// Delegate Stuff
//-----------------------------------------------------------------------------
- (id)delegate			{ return delegate; }
- (id)dataDelegate		{ return dataDelegate; }
- (id)setDelegate:(id)obj
	{ delegate = obj; delegateFlags->setDelegate( obj ); return self; }
- (id)setDataDelegate:(id)obj
	{ dataDelegate = obj; dataDelegateFlags->setDelegate( obj ); return self; }

- (id)responsibleDelegate:(MiscDelegateFlags::Selector)cmd
	{
	id del = 0;
	if (delegate != 0 && delegateFlags->respondsTo(cmd))
		del = delegate;
	else if (dataDelegate != 0 && dataDelegateFlags->respondsTo(cmd))
		del = dataDelegate;
	return del;
	}

- (char const*)border:(MiscBorderType)b getDelegateSlotTitle:(int)slot
	{
	id del = [self responsibleDelegate:DF::DEL_SLOT_TITLE];
	if (del != 0)
		return [del tableScroll:self border:b slotTitle:slot];
	return 0;
	}

- (id)border:(MiscBorderType)b getDelegateSlotPrototype:(int)s
	{
	id del = [self responsibleDelegate:DF::DEL_SLOT_PROTOTYPE];
	if (del != 0)
		return [del tableScroll:self border:b slotPrototype:s];
	return 0;
	}

- (void)border:(MiscBorderType)b slotDraggedFrom:(int)fromPos to:(int)toPos
	{
	MiscBorderType const ob = otherBorder(b);
	if ([self autoSortSlots:ob])
		{
		int slot = [self border:b slotAtPosition:toPos];
		if ([self border:b slotSortType:slot] != MISC_SORT_SKIP ||
			[self border:b slotSortFunc:slot] != 0)
			[self sortSlots:ob]; // Don't resort if it doesn't affect ordering.
		}
	id del = [self responsibleDelegate:DF::DEL_SLOT_DRAGGED];
	if (del != 0)
		[del tableScroll:self border:b slotDraggedFrom:fromPos to:toPos];
	}

- (void)border:(MiscBorderType)b slotSortReversed:(int)n
	{
	MiscBorderType const ob = otherBorder(b);
	if ([self autoSortSlots:ob])
		[self sortSlots:ob];			// Assume it affects sorting.
	id del = [self responsibleDelegate:DF::DEL_SLOT_REVERSED];
	if (del != 0)
		{
		int const phys = [self border:b slotAtPosition:n];
		[del tableScroll:self border:b slotSortReversed:phys];
		}
	}

- (void)border:(MiscBorderType)b slotResized:(int)n
	{
	id del = [self responsibleDelegate:DF::DEL_SLOT_RESIZED];
	if (del != 0)
		{
		int const phys = [self border:b slotAtPosition:n];
		[del tableScroll:self border:b slotResized:phys];
		}
	}


//-----------------------------------------------------------------------------
// Target / Action
//-----------------------------------------------------------------------------
- (id)target						{ return target; }
- (id)setTarget:(id)obj				{ target = obj; return self; }
- (id)doubleTarget					{ return doubleTarget; }
- (id)setDoubleTarget:(id)obj		{ doubleTarget = obj; return self; }
- (SEL)action						{ return action; }
- (id)setAction:(SEL)new_sel		{ action = new_sel; return self; }
- (id)setDoubleAction:(SEL)new_sel	{ doubleAction = new_sel; return self; }
- (SEL)doubleAction					{ return doubleAction; }

- (id)sendAction:(SEL)aSel to:(id)obj
		{
		if (aSel == 0)
			aSel = action;
		if (obj == 0)
			obj = target;
		return ([NXApp sendAction:aSel to:obj from:self] ? self : 0);
		}

- (id)sendAction
		{
		return [self sendAction:action to:target];
		}

- (id)sendDoubleAction
		{
		return [self sendAction:doubleAction to:doubleTarget];
		}

- (id)sendActionIfEnabled
		{
		if ([self isEnabled])
			return [self sendAction];
		return 0;
		}

- (id)sendDoubleActionIfEnabled
		{
		if ([self isEnabled])
			return [self sendDoubleAction];
		return 0;
		}


//-----------------------------------------------------------------------------
// FONT
//-----------------------------------------------------------------------------
+ (Font*)defaultFont
	{
	return [Font userFontOfSize:12.0 matrix:NX_FLIPPEDMATRIX];
	}

- (id)font
	{
	return font;
	}


static double get_height( Font* font )
	{
	double const LINE_SPACING = 1.20;
	double const size = [font pointSize];
	return size * LINE_SPACING;
	}


- (id)setFont:(id)newFont
	{
	if (newFont != 0 && newFont != (id)font && [newFont isKindOf:[Font class]])
		{
		[window disableDisplay];
		Font* oldFont = font;
		font = newFont;
		NXCoord old_size = [self uniformSizeRows];
		if (old_size != 0)
			{
			// FIXME: Handle this better.  Different cell-types will have
			// different amounts of "fixed-size" border stuff.
			NXCoord const BORDER_THICKNESS = 1;
			old_size -= BORDER_THICKNESS;
			NXCoord const new_size =
				floor(	0.5 + (double(old_size) *
						(get_height( newFont ) / get_height( oldFont ))));
			if (new_size != old_size)
				[self setUniformSizeRows:(new_size + BORDER_THICKNESS)];
			}
		// FIXME: Set font in all existing prototype cells.
		// WARNING: Currently, just asking the border for a prototype
		// in a given slot allocates and initializes an array of prototypes.
		if (![self isLazy])				// Eager beaver sets all cells now.
			{
			int const NRows = num_rows;
			int const NCols = num_cols;
			for (int r = 0;	 r < NRows;	 r++)
				{
				for (int c = 0;	 c < NCols;	 c++)
					{
					id cell = [self cellAt:r:c];
					if (cell != 0)
						{
						if ([cell respondsTo:@selector(setOwnerFont:)])
							[cell setOwnerFont:newFont];
						else if ([cell respondsTo:@selector(setFont:)])
							[cell setFont:newFont];
						}
					}
				}
			}
		id del = [self responsibleDelegate:DF::DEL_FONT_CHANGED];
		if (del != 0)
			[del tableScroll:self fontChangedFrom:oldFont to:newFont];

		[window reenableDisplay];
		[self update];
		}
	return self;
	}


- (id)changeFont:(id)sender
	{
	FontManager* fontMgr = [FontManager new];
	Font* newFont = [fontMgr convertFont:[fontMgr selFont]];
	if (newFont != 0 && newFont != font)
		{
		[window disableDisplay];
		[self abortEditing];
		Font* oldFont = font;
		[self setFont:newFont];
		id del = [self responsibleDelegate:DF::DEL_CHANGE_FONT];
		if (del != 0)
			[del tableScroll:self changeFont:oldFont to:newFont];
		[window reenableDisplay];
		[self update];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// COLOR
//
// The following macros expand into the implementations for these functions.
// Their names are listed so they can be found when searched for.
// setBackgroundColor:, setTextColor:,
// setHighlightBackgroundColor:, setHighlightTextColor:
// setBackgroundGray:, setTextGray:,
// setHighlightBackgroundGray:, setHighlightTextGray:
// backgroundGray:, textGray:, highlightBackgroundGray:, highlightTextGray:
//-----------------------------------------------------------------------------
+ (NXColor)defaultBackgroundColor				{ return NX_COLORLTGRAY; }
+ (NXColor)defaultTextColor						{ return NX_COLORBLACK; }
+ (NXColor)defaultHighlightBackgroundColor		{ return NX_COLORWHITE; }
+ (NXColor)defaultHighlightTextColor			{ return NX_COLORBLACK; }

- (NXColor)backgroundColor				{ return backgroundColor; }
- (NXColor)textColor					{ return textColor; }
- (NXColor)highlightBackgroundColor		{ return highlightBackgroundColor; }
- (NXColor)highlightTextColor			{ return highlightTextColor; }

- (id)setColor:(NXColor)value	{ return [self setBackgroundColor:value]; }
- (NXColor)color				{ return [self backgroundColor]; }

- (id)setColor:(NXColor)x		// New color value.
		var:(NXColor*)v			// Instance variable for the color.
		sel1:(SEL)sel1			// "setOwner...Color:" message for cell.
		sel2:(SEL)sel2			// "set...Color:" message for cell.
		notifySel:(MiscDelegateFlags::Selector)notifySel // "..ColorChangedTo:"
	{
	if (!NXEqualColor( x, *v ))
		{
		BOOL const was_auto = [self isAutodisplay];
		[self setAutodisplay:NO];

		*v = x;
		if (v == &backgroundColor) [self forwardBGColor];

		if (![self isLazy])
			{
			int const NRows = num_rows;
			int const NCols = num_cols;
			for (int r = 0; r < NRows;	r++)
				for (int c = 0;	 c < NCols;	 c++)
					{
					id cell = [self cellAt:r:c];
					if (cell != 0)
						{
						if ([cell respondsTo:sel1])
							(*[cell methodFor:sel1])( cell, sel1, x );
						else if ([cell respondsTo:sel2])
							(*[cell methodFor:sel2])( cell, sel2, x );
						}
					}
			}

		id del = [self responsibleDelegate:notifySel];
		if (del != 0)
			{
			SEL objcSel = MiscDelegateFlags::selToObjc( notifySel );
			(*[del methodFor:objcSel])( del, objcSel, self, x );
			}

		[self setNeedsDisplay:YES];
		[self setAutodisplay:was_auto];			// Will display if needed.
		}

	return self;
	}


#define MISC_SET_COLOR_FUNC(LNAME,CNAME,UNAME)\
- (id)set##CNAME##Color:(NXColor) value\
	{ return [self setColor:value\
				var:& LNAME##Color\
				sel1:@selector(setOwner##CNAME##Color:)\
				sel2:@selector(set##CNAME##Color:)\
				notifySel:MiscDelegateFlags::DEL_##UNAME##_COLOR_CHANGED]; }

MISC_SET_COLOR_FUNC( background, Background, BACK )
MISC_SET_COLOR_FUNC( text, Text, TEXT )
MISC_SET_COLOR_FUNC( highlightBackground, HighlightBackground, BACKH )
MISC_SET_COLOR_FUNC( highlightText, HighlightText, TEXTH )
#undef MISC_SET_COLOR_FUNC


static inline float color_to_gray( NXColor x )
	{
	float gray;
	NXConvertColorToGray( x, &gray );
	return gray;
	}


#define MISC_GET_GRAY_FUNC( LNAME )\
- (float)LNAME##Gray { return color_to_gray( [self LNAME##Color] ); }

MISC_GET_GRAY_FUNC( background )
MISC_GET_GRAY_FUNC( text )
MISC_GET_GRAY_FUNC( highlightBackground )
MISC_GET_GRAY_FUNC( highlightText )
#undef MISC_GET_GRAY_FUNC

#define MISC_SET_GRAY_FUNC( CNAME )\
- (id)set##CNAME##Gray:(float)x\
	{ return [self set##CNAME##Color: NXConvertGrayToColor(x)]; }

MISC_SET_GRAY_FUNC( Background )
MISC_SET_GRAY_FUNC( Text )
MISC_SET_GRAY_FUNC( HighlightBackground )
MISC_SET_GRAY_FUNC( HighlightText )
#undef MISC_SET_GRAY_FUNC


//=============================================================================
// SAVE / RESTORE
//=============================================================================

//-----------------------------------------------------------------------------
// SLOT ORDER
//-----------------------------------------------------------------------------
- (id)border:(MiscBorderType)b slotOrder:(MiscIntList*)list
	{
	if (list != 0)
		{
		MiscTableBorder const* const bp = info[b]->border;
		MiscCoord_P const* const vmap = bp->getP2VMap();
		int const lim = bp->count();
		[list empty];
		for (int i = 0;	 i < lim;  i++)
			{
			int v = (vmap ? vmap[i] : i);
			if (bp->getSortDirection(v) == MISC_SORT_DESCENDING)
				v = ~v;
			[list addInt:v];
			}
		return self;
		}
	return 0;
	}
- (id)colOrder:(MiscIntList*)list
	{ return [self border:MISC_COL_BORDER slotOrder:list]; }
- (id)rowOrder:(MiscIntList*)list
	{ return [self border:MISC_ROW_BORDER slotOrder:list]; }

- (id)border:(MiscBorderType)b setSlotOrder:(MiscIntList*)list
	{
	id retval = 0;
	if (list != 0)
		{
		MiscTableBorder* const bp = info[b]->border;
		int const lim = bp->count();
		if ([list count] == lim)
			{
			bool* desc = (bool*) malloc( lim * sizeof(*desc) );
			int*  map  = (int*)	 malloc( lim * sizeof(*map) );
			for (int i = 0; i < lim; i++)
				{
				int const j = [list intAt:i];
				map[i] = (desc[i] = (j < 0)) ? ~j : j;
				}

			if (bp->setP2VMap( map ))
				{
				for (int i = 0; i < lim; i++)
					{
					MiscSortDirection const dir =
						(desc[i] ? MISC_SORT_DESCENDING : MISC_SORT_ASCENDING);
					if (dir != bp->getSortDirection( map[i] ))
						bp->setSortDirection( map[i], dir );
					}

				MiscBorderType const ob = otherBorder(b);
				if ([self autoSortSlots:ob])
					[self sortSlots:ob];
				retval = self;
				}

			free( map );
			free( desc );
			}
		}
	return retval;
	}

- (id)setColOrder:(MiscIntList*)list
	{ return [self border:MISC_COL_BORDER setSlotOrder:list]; }
- (id)setRowOrder:(MiscIntList*)list
	{ return [self border:MISC_ROW_BORDER setSlotOrder:list]; }

- (char*)border:(MiscBorderType)b slotOrderAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{
	char* retval;
	MiscIntList* list = [[MiscIntList alloc] init];
	[self border:b slotOrder:list];
	retval = [list writeToString:buff size:buff_size canExpand:canExpand];
	[list free];
	return retval;
	}
- (char*)colOrderAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{ return [self border:MISC_COL_BORDER slotOrderAsString:buff
						size:buff_size canExpand:canExpand]; }
- (char*)rowOrderAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{ return [self border:MISC_ROW_BORDER slotOrderAsString:buff
						size:buff_size canExpand:canExpand]; }

- (id)border:(MiscBorderType)b setSlotOrderFromString:(char const*)s
	{
	id retval;
	MiscIntList* list = [[MiscIntList alloc] init];
	[list readFromString:s];
	retval = [self border:b setSlotOrder:list];
	[list free];
	return retval;
	}
- (id)setColOrderFromString:(char const*)s
	{ return [self border:MISC_COL_BORDER setSlotOrderFromString:s]; }
- (id)setRowOrderFromString:(char const*)s
	{ return [self border:MISC_ROW_BORDER setSlotOrderFromString:s]; }


//-----------------------------------------------------------------------------
// SLOT SIZES
//-----------------------------------------------------------------------------
- (id)border:(MiscBorderType)b slotSizes:(MiscIntList*)list
	{
	if (list != 0)
		{
		MiscTableBorder const* const bp = info[b]->border;
		int const lim = bp->count();
		[list empty];
		for (int i = 0;	 i < lim;  i++)
			[list addInt: (int) bp->getSize_P( i ) ];
		return self;
		}
	return 0;
	}
- (id)colSizes:(MiscIntList*)list
	{ return [self border:MISC_COL_BORDER slotSizes:list]; }
- (id)rowSizes:(MiscIntList*)list
	{ return [self border:MISC_ROW_BORDER slotSizes:list]; }

- (id)border:(MiscBorderType)b setSlotSizes:(MiscIntList*)list
	{
	if (list != 0)
		{
		MiscTableBorder* const bp = info[b]->border;
		int const lim = bp->count();
		if ([list count] == lim)
			{
			for (int i = 0;	 i < lim;  i++)
				{
				NXCoord const min_size = [self border:b slotMinSize:i];
				NXCoord const max_size = [self border:b slotMaxSize:i];
				int s = [list intAt:i];
				if (s < min_size)
					s = (int)min_size;
				else if (s > max_size)
					s = (int)max_size;
				bp->setSize_P( i, (MiscPixels)s );
				}
			[self constrainSize];
			return self;
			}
		}
	return 0;
	}
- (id)setColSizes:(MiscIntList*)list
	{ return [self border:MISC_COL_BORDER setSlotSizes:list]; }
- (id)setRowSizes:(MiscIntList*)list
	{ return [self border:MISC_ROW_BORDER setSlotSizes:list]; }

- (char*)border:(MiscBorderType)b slotSizesAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{
	char* retval;
	MiscIntList* list = [[MiscIntList alloc] init];
	[self border:b slotSizes:list];
	retval = [list writeToString:buff size:buff_size canExpand:canExpand];
	[list free];
	return retval;
	}
- (char*)colSizesAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{ return [self border:MISC_COL_BORDER slotSizesAsString:buff
						size:buff_size canExpand:canExpand]; }
- (char*)rowSizesAsString:(char*)buff
		size:(int)buff_size canExpand:(BOOL)canExpand
	{ return [self border:MISC_ROW_BORDER slotSizesAsString:buff
						size:buff_size canExpand:canExpand]; }

- (id)border:(MiscBorderType)b setSlotSizesFromString:(char const*)s
	{
	id retval;
	MiscIntList* list = [[MiscIntList alloc] init];
	[list readFromString:s];
	retval = [self border:b setSlotSizes:list];
	[list free];
	return retval;
	}
- (id)setColSizesFromString:(char const*)s
	{ return [self border:MISC_COL_BORDER setSlotSizesFromString:s]; }
- (id)setRowSizesFromString:(char const*)s
	{ return [self border:MISC_ROW_BORDER setSlotSizesFromString:s]; }



//-----------------------------------------------------------------------------
// Border Views
//-----------------------------------------------------------------------------
- (BOOL)setBorder:(MiscBorderType)type on:(BOOL)on
	{
	MiscBorderInfo& b = *(info[type]);
	if (b.isOn != on)
		{
		BOOL const other_border_is_on = info[otherBorder(type)]->isOn;
		b.isOn = on;
		if (on)
			{
			[self addSubview: b.clip];
			[window invalidateCursorRectsForView: b.view];
			if (other_border_is_on)
				[self addSubview: nullView];
			}
		else
			{
			[b.clip removeFromSuperview];
			if (other_border_is_on)
				[nullView removeFromSuperview];
			}
		[self tile];
		[self constrainSize];
		[self update];
		return YES;
		}
	return NO;
	}


//-----------------------------------------------------------------------------
// SLOT methods
//-----------------------------------------------------------------------------
- (MiscTableBorder*)border:(MiscBorderType)b
		{ return info[b]->border; }
- (BOOL)border:(MiscBorderType)b setTitlesOn:(BOOL)on_off
		{ return [self setBorder:b on:on_off]; }
- (BOOL)borderTitlesOn:(MiscBorderType)b
		{ return info[b]->isOn; }
- (MiscTableTitleMode)borderTitleMode:(MiscBorderType)b
		{ return info[b]->border->getTitleMode(); }
- (void)border:(MiscBorderType)b setTitleMode:(MiscTableTitleMode)x
		{
		MiscBorderInfo* const ip = info[b];
		if (ip->border->setTitleMode(x) && ip->isOn && ip->border->count() > 0)
			[ip->view update];
		}
- (NXCoord)borderTitlesSize:(MiscBorderType)b
		{ return info[b]->isOn ? [info[b]->view frameHeight] : 0; }
- (void)border:(MiscBorderType)b setTitlesSize:(NXCoord)x
		{
		[info[b]->view setFrameHeight:(MiscPixels)floor(x)];
		[self tile];
		}

- (id)border:(MiscBorderType)b moveSlotFrom:(int)fromPos to:(int)toPos
		{
		info[b]->border->moveFromTo(fromPos,toPos);
		[self selectionChanged];
		return self;
		}
- (int)border:(MiscBorderType)b slotPosition:(int)n
		{ return info[b]->border->physicalToVisual(n); }
- (int)border:(MiscBorderType)b slotAtPosition:(int)n
		{ return info[b]->border->visualToPhysical(n); }

- (void)border:(MiscBorderType)b physicalToVisual:(MiscIntList*)list
		{
		for (unsigned int i = [list count];	 i-- > 0; )
			{
			MiscCoord_P const p = [list intAt:i];
			[list replaceIntAt:i with:[self border:b slotPosition:p]];
			}
		}

- (void)border:(MiscBorderType)b visualToPhysical:(MiscIntList*)list
		{
		for (unsigned int i = [list count];	 i-- > 0; )
			{
			MiscCoord_V const v = [list intAt:i];
			[list replaceIntAt:i with:[self border:b slotAtPosition:v]];
			}
		}


- (BOOL)sizeableSlots:(MiscBorderType)b
		{ return info[b]->border->isSizeable(); }
- (BOOL)draggableSlots:(MiscBorderType)b
		{ return info[b]->border->isDraggable(); }
- (BOOL)modifierDragSlots:(MiscBorderType)b
		{ return info[b]->border->isModifierDrag(); }
- (NXCoord)uniformSizeSlots:(MiscBorderType)b
		{ return (NXCoord) info[b]->border->getUniformSize(); }

- (NXCoord)border:(MiscBorderType)b slotAdjustedSize:(int)n
		{ return (NXCoord) info[b]->border->effectiveSize_P(n); }
- (NXCoord)border:(MiscBorderType)b slotSize:(int)n
		{ return (NXCoord) info[b]->border->getSize_P(n); }
- (NXCoord)border:(MiscBorderType)b slotMinSize:(int)n
		{ return (NXCoord) info[b]->border->getMinSize_P(n); }
- (NXCoord)border:(MiscBorderType)b slotMaxSize:(int)n
		{ return (NXCoord) info[b]->border->getMaxSize_P(n); }
- (NXCoord)border:(MiscBorderType)b slotDataSize:(int)n
		{ return (NXCoord) info[b]->border->getDataSize_P(n); }
- (BOOL)border:(MiscBorderType)b slotIsSizeable:(int)n
		{ return info[b]->border->isSizeable_P(n); }
- (BOOL)border:(MiscBorderType)b slotExpandsToData:(int)n
		{ return info[b]->border->isData_P(n); }
- (BOOL)border:(MiscBorderType)b slotIsAutosize:(int)n
		{ return info[b]->border->isSpringy_P(n); }
- (char const*)border:(MiscBorderType)b slotTitle:(int)n
		{ return info[b]->border->getTitle_P(n); }
- (int)border:(MiscBorderType)b slotTag:(int)n
		{ return info[b]->border->getTag_P(n); }
- (MiscTableCellStyle)border:(MiscBorderType)b slotCellType:(int)n
		{ return info[b]->border->getStyle_P(n); }
- (id)border:(MiscBorderType)b slotCellPrototype:(int)n
		{ return info[b]->border->getPrototype_P(n); }

- (void)border:(MiscBorderType)b setSizeableSlots:(BOOL)flag
		{ info[b]->border->setSizeable(flag); }
- (void)border:(MiscBorderType)b setDraggableSlots:(BOOL)flag
		{ info[b]->border->setDraggable(flag); }
- (void)border:(MiscBorderType)b setModifierDragSlots:(BOOL)flag
		{ info[b]->border->setModifierDrag(flag); }
- (void)border:(MiscBorderType)b setUniformSizeSlots:(NXCoord)uniform_size
		{
		MiscBorderInfo* const ip = info[b];
		if (ip->border->setUniformSize( (MiscPixels)floor(uniform_size) ))
			{
			[self constrainSize];
			if (b == MISC_ROW_BORDER)
				{
				NXCoord const scr_size = uniform_size != 0 ?
						uniform_size : ip->border->getDefaultSize();
				[self setLineScroll:scr_size];
				[self setPageScroll:scr_size];
				}
			[self update];
			}
		}

- (void)border:(MiscBorderType)b setSlot:(int)n size:(NXCoord)size
		{
		info[b]->border->setSize_P( n, (MiscPixels)floor(size) );
		[self constrainSize];
		// FIXME: [self update];
		}
- (void)border:(MiscBorderType)b setSlot:(int)n minSize:(NXCoord)size
		{
		info[b]->border->setMinSize_P( n, (MiscPixels)floor(size) );
		[self constrainSize];
		}
- (void)border:(MiscBorderType)b setSlot:(int)n maxSize:(NXCoord)size
		{
		info[b]->border->setMaxSize_P( n, (MiscPixels)floor(size) );
		[self constrainSize];
		}
- (void)border:(MiscBorderType)b setSlot:(int)n dataSize:(NXCoord)size
		{ info[b]->border->setDataSize_P( n, (MiscPixels)floor(size) ); }
- (void)border:(MiscBorderType)b setSlot:(int)n sizeable:(BOOL)flag
		{ info[b]->border->setSizeable_P( n, flag ); }
- (void)border:(MiscBorderType)b setSlot:(int)n expandsToData:(BOOL)flag
		{ info[b]->border->setData_P( n, flag ); }
- (void)border:(MiscBorderType)b setSlot:(int)n autosize:(BOOL)flag
		{
		info[b]->border->setSpringy_P( n, flag );
		[self constrainSize];
		}

- (void)border:(MiscBorderType)b setSlot:(int)n title:(char const*)title
		{
		MiscBorderInfo* const ip = info[b];
		if (ip->border->setTitle_P( n, title ) && ip->isOn)
			[self border:b drawSlotTitle:n];
		}

- (void)border:(MiscBorderType)b setSlot:(int)n tag:(int)x
		{ info[b]->border->setTag_P( n, x ); }
- (void)border:(MiscBorderType)b setSlot:(int)n
				cellType:(MiscTableCellStyle)type
		{ info[b]->border->setStyle_P(n,type); }
- (void)border:(MiscBorderType)b setSlot:(int)n cellPrototype:(id)p
		{ info[b]->border->setPrototype_P(n,p); }



//-----------------------------------------------------------------------------
// COL methods
//-----------------------------------------------------------------------------
- (MiscTableBorder*)colBorder
		{ return colInfo.border; }
- (BOOL)colTitlesOn
		{ return [self borderTitlesOn:MISC_COL_BORDER]; }
- (BOOL)setColTitlesOn:(BOOL)x
		{ return [self setBorder:MISC_COL_BORDER on:x]; }
- (MiscTableTitleMode)colTitleMode
		{ return [self borderTitleMode:MISC_COL_BORDER]; }
- (void)setColTitleMode:(MiscTableTitleMode)x
		{ [self border:MISC_COL_BORDER setTitleMode:x]; }
- (NXCoord)colTitlesHeight
		{ return [self borderTitlesSize:MISC_COL_BORDER]; }
- (void)setColTitlesHeight:(NXCoord)x
		{ [self border:MISC_COL_BORDER setTitlesSize:x]; }
- (MiscBorderView*)colTitles
		{ return ([self colTitlesOn] ? colInfo.view : 0); }

- (id)moveColFrom:(int)fromPos to:(int)toPos
		{ return [self border:MISC_COL_BORDER moveSlotFrom:fromPos to:toPos]; }
- (int)colPosition:(int)n
		{ return [self border:MISC_COL_BORDER slotPosition:n]; }
- (int)colAtPosition:(int)n
		{ return [self border:MISC_COL_BORDER slotAtPosition:n]; }

- (BOOL)sizeableCols
		{ return [self sizeableSlots:MISC_COL_BORDER]; }
- (BOOL)draggableCols
		{ return [self draggableSlots:MISC_COL_BORDER]; }
- (BOOL)modifierDragCols
		{ return [self modifierDragSlots:MISC_COL_BORDER]; }
- (NXCoord)uniformSizeCols
		{ return [self uniformSizeSlots:MISC_COL_BORDER]; }

- (NXCoord)colAdjustedSize:(int)n
		{ return [self border:MISC_COL_BORDER slotAdjustedSize:n]; }
- (NXCoord)colSize:(int)n
		{ return [self border:MISC_COL_BORDER slotSize:n]; }
- (NXCoord)colMinSize:(int)n
		{ return [self border:MISC_COL_BORDER slotMinSize:n]; }
- (NXCoord)colMaxSize:(int)n
		{ return [self border:MISC_COL_BORDER slotMaxSize:n]; }
- (NXCoord)colDataSize:(int)n
		{ return [self border:MISC_COL_BORDER slotDataSize:n]; }
- (BOOL)colIsSizeable:(int)n
		{ return [self border:MISC_COL_BORDER slotIsSizeable:n]; }
- (BOOL)colExpandsToData:(int)n
		{ return [self border:MISC_COL_BORDER slotExpandsToData:n]; }
- (BOOL)colIsAutosize:(int)n
		{ return [self border:MISC_COL_BORDER slotIsAutosize:n]; }
- (char const*)colTitle:(int)n
		{ return [self border:MISC_COL_BORDER slotTitle:n]; }
- (int)colTag:(int)n
		{ return [self border:MISC_COL_BORDER slotTag:n]; }
- (MiscTableCellStyle)colCellType:(int)n
		{ return [self border:MISC_COL_BORDER slotCellType:n]; }
- (id)colCellPrototype:(int)n
		{ return [self border:MISC_COL_BORDER slotCellPrototype:n]; }

- (void)setSizeableCols:(BOOL)flag
		{ [self border:MISC_COL_BORDER setSizeableSlots:flag]; }
- (void)setDraggableCols:(BOOL)flag
		{ [self border:MISC_COL_BORDER setDraggableSlots:flag]; }
- (void)setModifierDragCols:(BOOL)flag
		{ [self border:MISC_COL_BORDER setModifierDragSlots:flag]; }
- (void)setUniformSizeCols:(NXCoord)size
		{ [self border:MISC_COL_BORDER setUniformSizeSlots:size]; }

- (void)setCol:(int)n size:(NXCoord)size
		{ [self border:MISC_COL_BORDER setSlot:n size:size]; }
- (void)setCol:(int)n minSize:(NXCoord)size
		{ [self border:MISC_COL_BORDER setSlot:n minSize:size]; }
- (void)setCol:(int)n maxSize:(NXCoord)size
		{ [self border:MISC_COL_BORDER setSlot:n maxSize:size]; }
- (void)setCol:(int)n dataSize:(NXCoord)size
		{ [self border:MISC_COL_BORDER setSlot:n dataSize:size]; }
- (void)setCol:(int)n sizeable:(BOOL)flag
		{ [self border:MISC_COL_BORDER setSlot:n sizeable:flag]; }
- (void)setCol:(int)n expandsToData:(BOOL)flag
		{ [self border:MISC_COL_BORDER setSlot:n expandsToData:flag]; }
- (void)setCol:(int)n autosize:(BOOL)flag
		{ [self border:MISC_COL_BORDER setSlot:n autosize:flag]; }
- (void)setCol:(int)n title:(char const*)title
		{ [self border:MISC_COL_BORDER setSlot:n title:title]; }
- (void)setCol:(int)n tag:(int)x
		{ [self border:MISC_COL_BORDER setSlot:n tag:x]; }
- (void)setCol:(int)n cellType:(MiscTableCellStyle)x
		{ [self border:MISC_COL_BORDER setSlot:n cellType:x]; }
- (void)setCol:(int)n cellPrototype:p
		{ [self border:MISC_COL_BORDER setSlot:n cellPrototype:p]; }


//-----------------------------------------------------------------------------
// ROW methods
//-----------------------------------------------------------------------------
- (MiscTableBorder*)rowBorder
		{ return rowInfo.border; }
- (BOOL)rowTitlesOn
		{ return [self borderTitlesOn:MISC_ROW_BORDER]; }
- (BOOL)setRowTitlesOn:(BOOL)x
		{ return [self setBorder:MISC_ROW_BORDER on:x]; }
- (MiscTableTitleMode)rowTitleMode
		{ return [self borderTitleMode:MISC_ROW_BORDER]; }
- (void)setRowTitleMode:(MiscTableTitleMode)x
		{ [self border:MISC_ROW_BORDER setTitleMode:x]; }
- (NXCoord)rowTitlesWidth
		{ return [self borderTitlesSize:MISC_ROW_BORDER]; }
- (void)setRowTitlesWidth:(NXCoord)x
		{ [self border:MISC_ROW_BORDER setTitlesSize:x]; }
- (MiscBorderView*)rowTitles
		{ return ([self rowTitlesOn] ? rowInfo.view : 0); }

- (id)moveRowFrom:(int)fromPos to:(int)toPos
		{ return [self border:MISC_ROW_BORDER moveSlotFrom:fromPos to:toPos]; }
- (int)rowPosition:(int)n
		{ return [self border:MISC_ROW_BORDER slotPosition:n]; }
- (int)rowAtPosition:(int)n
		{ return [self border:MISC_ROW_BORDER slotAtPosition:n]; }

- (BOOL)sizeableRows
		{ return [self sizeableSlots:MISC_ROW_BORDER]; }
- (BOOL)draggableRows
		{ return [self draggableSlots:MISC_ROW_BORDER]; }
- (BOOL)modifierDragRows
		{ return [self modifierDragSlots:MISC_ROW_BORDER]; }
- (NXCoord)uniformSizeRows
		{ return [self uniformSizeSlots:MISC_ROW_BORDER]; }

- (NXCoord)rowAdjustedSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotAdjustedSize:n]; }
- (NXCoord)rowSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotSize:n]; }
- (NXCoord)rowMinSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotMinSize:n]; }
- (NXCoord)rowMaxSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotMaxSize:n]; }
- (NXCoord)rowDataSize:(int)n
		{ return [self border:MISC_ROW_BORDER slotDataSize:n]; }
- (BOOL)rowIsSizeable:(int)n
		{ return [self border:MISC_ROW_BORDER slotIsSizeable:n]; }
- (BOOL)rowExpandsToData:(int)n
		{ return [self border:MISC_ROW_BORDER slotExpandsToData:n]; }
- (BOOL)rowIsAutosize:(int)n
		{ return [self border:MISC_ROW_BORDER slotIsAutosize:n]; }
- (char const*)rowTitle:(int)n
		{ return [self border:MISC_ROW_BORDER slotTitle:n]; }
- (int)rowTag:(int)n
		{ return [self border:MISC_ROW_BORDER slotTag:n]; }
- (MiscTableCellStyle)rowCellType:(int)n
		{ return [self border:MISC_ROW_BORDER slotCellType:n]; }
- (id)rowCellPrototype:(int)n
		{ return [self border:MISC_ROW_BORDER slotCellPrototype:n]; }

- (void)setSizeableRows:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setSizeableSlots:flag]; }
- (void)setDraggableRows:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setDraggableSlots:flag]; }
- (void)setModifierDragRows:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setModifierDragSlots:flag]; }
- (void)setUniformSizeRows:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setUniformSizeSlots:size]; }

- (void)setRow:(int)n size:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setSlot:n size:size]; }
- (void)setRow:(int)n minSize:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setSlot:n minSize:size]; }
- (void)setRow:(int)n maxSize:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setSlot:n maxSize:size]; }
- (void)setRow:(int)n dataSize:(NXCoord)size
		{ [self border:MISC_ROW_BORDER setSlot:n dataSize:size]; }
- (void)setRow:(int)n sizeable:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setSlot:n sizeable:flag]; }
- (void)setRow:(int)n expandsToData:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setSlot:n expandsToData:flag]; }
- (void)setRow:(int)n autosize:(BOOL)flag
		{ [self border:MISC_ROW_BORDER setSlot:n autosize:flag]; }
- (void)setRow:(int)n title:(char const*)title
		{ [self border:MISC_ROW_BORDER setSlot:n title:title]; }
- (void)setRow:(int)n tag:(int)x
		{ [self border:MISC_ROW_BORDER setSlot:n tag:x]; }
- (void)setRow:(int)n cellType:(MiscTableCellStyle)x
		{ [self border:MISC_ROW_BORDER setSlot:n cellType:x]; }
- (void)setRow:(int)n cellPrototype:p
		{ [self border:MISC_ROW_BORDER setSlot:n cellPrototype:p]; }


//-----------------------------------------------------------------------------
// ARCHIVING
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// - awakeFromNib
//-----------------------------------------------------------------------------
- (id)awakeFromNib
	{
	delegateFlags->setDelegate( [self delegate] );
	dataDelegateFlags->setDelegate( [self dataDelegate] );
	return self;
	}


//-----------------------------------------------------------------------------
// - read:selector:
//-----------------------------------------------------------------------------
- (SEL)read:(int)ver selector:(NXTypedStream*)stream
	{
	SEL cmd = 0;
	char* str;
	NXReadType( stream, @encode(char*), &str );
	if (str != 0)
		{
		if (*str != '\0')
			cmd = sel_getUid( str );
		free( str );
		}
	return cmd;
	}


//-----------------------------------------------------------------------------
// - read:globalInfo:
//
//		NOTE: Cannot archive sort_entry_func and sort_slot_func because they 
//				are function addresses.	 
//-----------------------------------------------------------------------------
- (void)read:(int)ver globalInfo:(NXTypedStream*)stream
	{
	NXReadType( stream, @encode(int), &tag );
	NXReadType( stream, @encode(BOOL), &enabled );
	NXReadType( stream, @encode(BOOL), &lazy );
	NXReadType( stream, @encode(MiscSelectionMode), &mode );
	font = NXReadObject( stream );
	textColor = NXReadColor( stream );
	backgroundColor = NXReadColor( stream );
	highlightTextColor = NXReadColor( stream );
	highlightBackgroundColor = NXReadColor( stream );
	nextText	= NXReadObject( stream );
	previousText= NXReadObject( stream );
	delegate	= NXReadObject( stream );
	dataDelegate= NXReadObject( stream );
	target		= NXReadObject( stream );
	doubleTarget= NXReadObject( stream );
	action = [self read:ver selector:stream];
	doubleAction = [self read:ver selector:stream];
	}


//-----------------------------------------------------------------------------
// - read:border:stream:
//-----------------------------------------------------------------------------
- (void)read:(int)ver border:(MiscBorderInfo*)p stream:(NXTypedStream*)stream
	{
	NXZone* const z = [self zone];
	int n;
	if (p->sort_vector != 0)
		NXZoneFree( z, p->sort_vector );
	NXReadType( stream, @encode(int), &(n) );
	if (n > 0)
		{
		p->sort_vector_len = n;
		p->sort_vector = (int*) NXZoneMalloc( z, n * sizeof(int) );
		for (int i = 0;	 i < n;	 i++)
			NXReadType( stream, @encode(int), &(p->sort_vector[i]) );
		}
	else
		{
		p->sort_vector_len = 0;
		p->sort_vector = 0;
		}
	NXReadType( stream, @encode(BOOL), &(p->isOn) );
	NXReadType( stream, @encode(BOOL), &(p->autoSort) );
	NXReadType( stream, @encode(BOOL), &(p->constrain_min) );
	NXReadType( stream, @encode(BOOL), &(p->constrain_max) );
	if (p->border == 0)
		p->border = new( NXZoneMalloc( z, sizeof(*(p->border)) ) )
						MiscTableBorder( MISC_ROW_BORDER );
	p->border->read( stream );	// FIXME: Should send ver.
	}


//-----------------------------------------------------------------------------
// - read:cells:
//
// FIXME: What happens when a non-empty non-lazy TableScroll reads the data
// from a lazy, empty one.	Does the memory get freed?
//-----------------------------------------------------------------------------
- (void)read:(int)ver cells:(NXTypedStream*)stream
	{
	NXReadType( stream, @encode(int), &num_cols );
	NXReadType( stream, @encode(int), &num_rows );
	if (lazy)
		{
		max_rows = 0;			// No rows have been allocated.
		max_cells = 0;
		cells = 0;
		}
	else // (!lazy)
		{
		max_rows = num_rows;
		max_cells = max_rows * num_cols;
		if (max_cells == 0)
			cells = 0;
		else
			{
			int const nbytes = max_cells * sizeof(*cells);
			cells = (id*) NXZoneMalloc( [self zone], nbytes );
			id* p = cells;
			for (int r = 0; r < num_rows; r++)
				for (int c = 0; c < num_cols; c++)
					*p++ = NXReadObject( stream );
			}
		}
	}


//-----------------------------------------------------------------------------
// - read:
//-----------------------------------------------------------------------------
- (id)read:(NXTypedStream*)stream
	{
	[super read:stream];
	char const* class_name = [[MiscTableScroll class] name];
	int ver = NXTypedStreamClassVersion( stream, class_name );
	if (((unsigned int)ver) > ((unsigned int)MISC_TS_VERSION))
		[self error:"%s: old library (version %d), can't -read: "
						"new object (version %d).\n",
						class_name, MISC_TS_VERSION, ver ];
	[self read:ver globalInfo:stream];
	[self read:ver border:&colInfo stream:stream];
	[self read:ver border:&rowInfo stream:stream];
	[self read:ver cells:stream];
	[self doInit:ver];
	return self;
	}


//-----------------------------------------------------------------------------
// - writeSelector:stream:
//-----------------------------------------------------------------------------
- (void)writeSelector:(SEL)cmd stream:(NXTypedStream*)stream
	{
	char const* str = 0;
	if (cmd != 0)
		str = sel_getName( cmd );
	if (str == 0)						// not an 'else if'
		str = "";
	NXWriteType( stream, @encode(char*), &str );
	}


//-----------------------------------------------------------------------------
// - writeGlobalInfo:
//
//		NOTE: Cannot archive sort_entry_func and sort_slot_func because they 
//				are function addresses.	 
//-----------------------------------------------------------------------------
- (void)writeGlobalInfo:(NXTypedStream*)stream
	{
	NXWriteType( stream, @encode(int), &tag );
	NXWriteType( stream, @encode(BOOL), &enabled );
	NXWriteType( stream, @encode(BOOL), &lazy );
	NXWriteType( stream, @encode(MiscSelectionMode), &mode );
	NXWriteObject( stream, font );
	NXWriteColor( stream, textColor );
	NXWriteColor( stream, backgroundColor );
	NXWriteColor( stream, highlightTextColor );
	NXWriteColor( stream, highlightBackgroundColor );
	NXWriteObjectReference( stream, nextText );
	NXWriteObjectReference( stream, previousText );
	NXWriteObjectReference( stream, delegate );
	NXWriteObjectReference( stream, dataDelegate );
	NXWriteObjectReference( stream, target );
	NXWriteObjectReference( stream, doubleTarget );
	[self writeSelector: action stream: stream];
	[self writeSelector: doubleAction stream: stream];
	}


//-----------------------------------------------------------------------------
// - writeBorder:stream:
//-----------------------------------------------------------------------------
- (void)writeBorder:(MiscBorderInfo*)p stream:(NXTypedStream*)stream
	{
	int const n = p->sort_vector_len;
	NXWriteType( stream, @encode(int), &(n) );
	if (n > 0)
		for (int i = 0;	 i < n;	 i++)
			NXWriteType( stream, @encode(int), &(p->sort_vector[i]) );
	NXWriteType( stream, @encode(BOOL), &(p->isOn) );
	NXWriteType( stream, @encode(BOOL), &(p->autoSort) );
	NXWriteType( stream, @encode(BOOL), &(p->constrain_min) );
	NXWriteType( stream, @encode(BOOL), &(p->constrain_max) );
	p->border->write( stream );
	}


//-----------------------------------------------------------------------------
// - writeCells:
//-----------------------------------------------------------------------------
- (void)writeCells:(NXTypedStream*)stream
	{
	NXWriteType( stream, @encode(int), &num_cols );
	NXWriteType( stream, @encode(int), &num_rows );
	if (!lazy)
		for (int r = 0;	 r < num_rows;	r++)
			for (int c = 0;	 c < num_cols;	c++)
				NXWriteObject( stream, [self cellAt:r:c] );
	}


//-----------------------------------------------------------------------------
// - write:
//-----------------------------------------------------------------------------
- (id)write:(NXTypedStream*)stream
	{
	// Ensure that subviews are NOT archived.
	if (colInfo.isOn)	[colInfo.clip removeFromSuperview];
	if (rowInfo.isOn)	[rowInfo.clip removeFromSuperview];
	if (rowInfo.isOn && colInfo.isOn) [nullView removeFromSuperview];
	[tableView removeFromSuperview];

	[super write:stream];
	[self writeGlobalInfo:stream];
	[self writeBorder:&colInfo stream:stream];
	[self writeBorder:&rowInfo stream:stream];
	[self writeCells:stream];

	// Restore state.
	[self setDocView: tableView];
	if (colInfo.isOn)	[self addSubview:colInfo.clip];
	if (rowInfo.isOn)	[self addSubview:rowInfo.clip];
	if (rowInfo.isOn && colInfo.isOn) [self addSubview:nullView];
	return self;
	}


//-----------------------------------------------------------------------------
// DRAWING
//		FIXME: This should all be routed through the -display::: mechanism
//		so that the lockFocus can be done automatically, and so that
//		subviews will have an opportunity to draw themselves.
//-----------------------------------------------------------------------------

- (id)drawCellAt:(int)row :(int)col
	{
	[tableView lockFocus];
	[tableView drawCellAt:row:col];
	[tableView unlockFocus];
	[window flushWindow];
	return self;
	}
- (id)drawRow:(int)row
	{
	[tableView lockFocus];
	[tableView drawRow:row];
	[tableView unlockFocus];
	[window flushWindow];
	return self;
	}
- (id)drawCol:(int)col
	{
	[tableView lockFocus];
	[tableView drawCol:col];
	[tableView unlockFocus];
	[window flushWindow];
	return self;
	}
- (id)border:(MiscBorderType)b drawSlot:(int)n
	{ return (b == MISC_COL_BORDER) ? [self drawCol:n] : [self drawRow:n]; }

- (id)border:(MiscBorderType)b drawSlotTitle:(int)n		// physical position.
	{
	MiscBorderInfo* const ip = info[b];
	if (ip->isOn)										// visual position.
		{
		[ip->view lockFocus];
		[ip->view drawSlot: [self border:b slotPosition:n]];
		[ip->view unlockFocus];
		[window flushWindow];
		}
	return self;
	}
- (id)drawRowTitle:(int)n
		{ return [self border:MISC_ROW_BORDER drawSlotTitle:n]; }
- (id)drawColTitle:(int)n
		{ return [self border:MISC_COL_BORDER drawSlotTitle:n]; }

- (void)selectionChanged
	{
	[window disableFlushWindow];
	[rowInfo.view selectionChanged];
	[colInfo.view selectionChanged];
	[tableView selectionChanged];
	[[window reenableFlushWindow] flushWindow];
	}

- (void)resetSelection
	{
	[tableView resetSelection];
	[rowInfo.view resetSelection];
	[colInfo.view resetSelection];
	}

- (id)setAutodisplay:(BOOL)x
	{
	[rowInfo.view setAutodisplay:x];
	[colInfo.view setAutodisplay:x];
	[tableView setAutodisplay:x];
	return [super setAutodisplay:x];
	}


//-----------------------------------------------------------------------------
// VISIBLE / SCROLLING
//-----------------------------------------------------------------------------

- (void)scrollCellToVisible:(int)row :(int)col
		{ [tableView scrollCellToVisible: row:col]; }
- (void)scrollRowToVisible:(int)row { [tableView scrollRowToVisible:row]; }
- (void)scrollColToVisible:(int)col { [tableView scrollColToVisible:col]; }
- (id)scrollSelToVisible
	{
	if ([self hasRowSelection])
		[self scrollRowToVisible: [self selectedRow]];
	else if ([self hasColSelection])
		[self scrollColToVisible: [self selectedCol]];
	return self;
	}


- (int)numVisibleSlots:(MiscBorderType)b
		{ return [tableView numVisibleSlots:b]; }
- (int)firstVisibleSlot:(MiscBorderType)b
		{ return [tableView firstVisibleSlot:b]; }
- (int)lastVisibleSlot:(MiscBorderType)b
		{ return [tableView lastVisibleSlot:b]; }
- (BOOL)border:(MiscBorderType)b slotIsVisible:(int)n
		{ return [tableView border:b slotIsVisible:n]; }
- (void)border:(MiscBorderType)b setFirstVisibleSlot:(int)n
		{ [tableView border:b setFirstVisibleSlot:n]; }
- (void)border:(MiscBorderType)b setLastVisibleSlot:(int)n
		{ [tableView border:b setLastVisibleSlot:n]; }

- (int)numVisibleCols  { return [self numVisibleSlots: MISC_COL_BORDER]; }
- (int)firstVisibleCol { return [self firstVisibleSlot:MISC_COL_BORDER]; }
- (int)lastVisibleCol  { return [self lastVisibleSlot: MISC_COL_BORDER]; }
- (BOOL)colIsVisible:(int)n
		{ return [self border:MISC_COL_BORDER slotIsVisible:n]; }
- (void)setFirstVisibleCol:(int)n
		{ [self border:MISC_COL_BORDER setFirstVisibleSlot:n]; }
- (void)setLastVisibleCol:(int)n
		{ [self border:MISC_COL_BORDER setLastVisibleSlot:n]; }

- (int)numVisibleRows  { return [self numVisibleSlots: MISC_ROW_BORDER]; }
- (int)firstVisibleRow { return [self firstVisibleSlot:MISC_ROW_BORDER]; }
- (int) lastVisibleRow { return [self lastVisibleSlot: MISC_ROW_BORDER]; }
- (BOOL)rowIsVisible:(int)n
		{ return [self border:MISC_ROW_BORDER slotIsVisible:n]; }
- (void)setFirstVisibleRow:(int)n
		{ [self border:MISC_ROW_BORDER setFirstVisibleSlot:n]; }
- (void)setLastVisibleRow:(int)n
		{ [self border:MISC_ROW_BORDER setLastVisibleSlot:n]; }


//-----------------------------------------------------------------------------
// MOUSE & KEYBOARD TRACKING
//-----------------------------------------------------------------------------
- (void)trackBy:(MiscBorderType)b { [tableView trackBy:b]; }
- (MiscBorderType)trackingBy { return [tableView trackingBy]; }

@end
