//=============================================================================
//
//	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.40 97/06/18 10:11:50 sunshine Exp $
// $Log:	MiscTableScroll.M,v $
// Revision 1.40  97/06/18  10:11:50  sunshine
// v125.9: Worked around Objective-C++ compiler crash in OPENSTEP 4.2 for NT
// when sending message to 'super' from within a category.
// Cleaned up more method names:
// numSelected... --> numberOfSelected...
// numVisible... --> numberOfVisible...
// cellIsSelected:: --> cellIsSelectedAtRow:column:
// borderClickedSlot: --> clickedSlot:
// borderTitlesSize: --> slotTitlesSize:
// scrollSelToVisible --> scrollSelectionToVisible
// Color-related methods: "highlight" --> "selected"
// 
// Revision 1.39  97/04/15  09:07:28  sunshine
// v0.125.8: Added "MiscTableScroll/" prefix to #import to facilitate new
// framework organization.
// 
// Revision 1.38  97/04/04  05:02:28  sunshine
// 0.125.6: NSArchiver* --> NSCoder* (to be consistent with rest of OPENSTEP).
// Removed unused <assert.h> header.
//-----------------------------------------------------------------------------
#import <MiscTableScroll/MiscTableScroll.h>
#import <MiscTableScroll/MiscTableCell.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/NSApplication.h>
#import <AppKit/NSCell.h>
#import <AppKit/NSClipView.h>
#import <AppKit/NSControl.h>	// Control-text notifications
#import <AppKit/NSFont.h>
#import <AppKit/NSFontManager.h>
}

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

typedef MiscDelegateFlags DF;

//-----------------------------------------------------------------------------
// Delegate Notifications
//-----------------------------------------------------------------------------
static NSArray* DEL_NOTIFICATIONS = 0;	// Array of MiscNotification objects.

@interface MiscNotification : NSObject
    {
    DF::Selector selector;
    NSString* notification;
    }
+ (MiscNotification*)notificationWithSelector:(DF::Selector)s
	notification:(NSString*)n;
- (id)initWithSelector:(DF::Selector)s notification:(NSString*)n;
- (void)dealloc;
- (DF::Selector)selector;
- (NSString*)notification;
@end

@implementation MiscNotification
+ (MiscNotification*)notificationWithSelector:(DF::Selector)s
	notification:(NSString*)n
    { return [[[self alloc] initWithSelector:s notification:n] autorelease]; }
- (id)initWithSelector:(DF::Selector)s notification:(NSString*)n
    { [super init]; selector = s; notification = [n retain]; return self; }
- (void)dealloc { [notification release]; [super dealloc]; }
- (DF::Selector)selector { return selector; }
- (NSString*)notification { return notification; }
@end


//=============================================================================
// IMPLEMENTATION
//=============================================================================
@implementation MiscTableScroll

//-----------------------------------------------------------------------------
// + initialize
//-----------------------------------------------------------------------------
+ (void)initialize
    {
    if (self == [MiscTableScroll class])
	{
	[self setVersion:MISC_TS_VERSION];
	#define NOTIFY(X,Y) \
	    [MiscNotification notificationWithSelector:(DF::DEL_TEXT_ ## X) \
	    notification:Y]
	DEL_NOTIFICATIONS = [[NSArray arrayWithObjects:
		NOTIFY( DID_END, NSControlTextDidEndEditingNotification ),
		NOTIFY( DID_CHANGE, NSControlTextDidBeginEditingNotification ),
		NOTIFY( DID_GET_KEYS, NSControlTextDidChangeNotification ), 0]
		retain];
	#undef NOTIFY
	}
    }


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


//-----------------------------------------------------------------------------
// Multicast Messages
//-----------------------------------------------------------------------------
- (void)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 cellIsSelectedAtRow:r column:c])
		if (![obj performSelector:aSel
		    withObject:[self cellAtRow:r column:c]])
		    break;
    }

- (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 cellIsSelectedAtRow:r column:c])
		{
		id cell = [self cellAtRow:r column:c];
		if ([cell respondsToSelector:s])
		    if ([cell performSelector:s
			withObject:p1 withObject: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 slotWithTag:(int)x
	{
	int const lim = (int) [self numberOfSlots:b];
	for (int i = 0;  i < lim;  i++)
	    if ([self border:b slotTag:i] == x)
		return i;
	return -1;
	}
- (int)columnWithTag:(int)x
	{ return [self border:MISC_COL_BORDER slotWithTag:x]; }
- (int)rowWithTag:(int)x
	{ return [self border:MISC_ROW_BORDER slotWithTag:x]; }

- (BOOL)getRow:(int*)row column:(int*)col ofCell:(NSCell*)cell
    {
    int const NRows = [self numberOfRows];
    int const NCols = [self numberOfColumns];
    for (int r = 0;  r < NRows;  r++)
	for (int c = 0;  c < NCols;  c++)
	    if ([self cellAtRow:r column:c] == cell)
		{
		*row = r;
		*col = c;
		return YES;
		}
    *row = -1;
    *col = -1;
    return NO;
    }

- (BOOL)getRow:(int*)row column:(int*)col ofCellWithTag:(int)x
    {
    int const NRows = [self numberOfRows];
    int const NCols = [self numberOfColumns];
    for (int r = 0;  r < NRows;  r++)
	for (int c = 0;  c < NCols;  c++)
	    {
	    id const cell = [self cellAtRow:r column:c];
	    if (cell && [cell respondsToSelector:@selector(tag)] &&
		[cell tag] == x)
		{
		*row = r;
		*col = c;
		return YES;
		}
	    }
    *row = -1;
    *col = -1;
    return NO;
    }

- (id)cellWithTag:(int)x
    {
    int r, c;
    return ([self getRow:&r column:&c ofCellWithTag:x] ?
	    [self cellAtRow:r column:c] : 0);
    }


//-----------------------------------------------------------------------------
// 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 selectedColumn];
    MiscCoord_P const row = [self selectedRow];
    return (col >= 0 && row >= 0) ? [self cellAtRow:row column:col] : 0;
    }

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

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

- (unsigned int)numberOfSelectedSlots:(MiscBorderType)b
	{ return info[b]->border->numSelected(); }
- (unsigned int)numberOfSelectedRows
	{ return [self numberOfSelectedSlots:MISC_ROW_BORDER]; }
- (unsigned int)numberOfSelectedColumns
	{ return [self numberOfSelectedSlots: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)columnIsSelected:(MiscCoord_P)col
	{ return [self border:MISC_COL_BORDER slotIsSelected:col]; }
- (BOOL)cellIsSelectedAtRow:(MiscCoord_P)r column:(MiscCoord_P)c
	{ return ([self rowIsSelected:r] || [self columnIsSelected:c]); }

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

- (NSArray*)selectedSlotTags:(MiscBorderType)b
	{ return info[b]->border->selectedTags(); }
- (NSArray*)selectedRowTags
	{ return [self selectedSlotTags:MISC_ROW_BORDER]; }
- (NSArray*)selectedColumnTags
	{ return [self selectedSlotTags:MISC_COL_BORDER]; }

- (NSArray*)selectedSlots:(MiscBorderType)b
	{ return info[b]->border->selectedSlots(); }
- (NSArray*)selectedRows { return [self selectedSlots:MISC_ROW_BORDER]; }
- (NSArray*)selectedColumns { return [self selectedSlots:MISC_COL_BORDER]; }

- (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)selectColumn:(MiscCoord_P)col byExtension:(BOOL)extend
	{ [self border:MISC_COL_BORDER selectSlot:col byExtension:extend]; }
- (void)selectColumn:(MiscCoord_P)col
	{ [self selectColumn:col byExtension:NO]; }

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

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

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

- (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)deselectColumn:(MiscCoord_P)col
	{ [self border:MISC_COL_BORDER deselectSlot:col]; }

- (void)border:(MiscBorderType)b deselectSlotTags:(NSArray*)tags
	{
	info[b]->border->unselectTags( tags );
	[self selectionChanged];
	}
- (void)deselectRowTags:(NSArray*)tags
	{ [self border:MISC_ROW_BORDER deselectSlotTags:tags]; }
- (void)deselectColumnTags:(NSArray*)tags
	{ [self border:MISC_COL_BORDER deselectSlotTags:tags]; }

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

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


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

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

- (void)border:(MiscBorderType)b setClickedSlot:(MiscCoord_P)n
	{ info[b]->border->setClickedSlot_P(n); }
- (void)setClickedRow:(MiscCoord_P)r column:(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)cursorSlot:(MiscBorderType)b
	{ return info[b]->border->getCursor_P(); }
- (MiscCoord_P)cursorRow { return [self cursorSlot:MISC_ROW_BORDER]; }
- (MiscCoord_P)cursorColumn { return [self cursorSlot:MISC_COL_BORDER]; }

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

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

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

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

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


//-----------------------------------------------------------------------------
// Overrides...	 Scrollers are always required.
//-----------------------------------------------------------------------------
- (void)setHasHorizontalScroller:(BOOL)flag {}
- (void)setHasVerticalScroller:(BOOL)flag {}


//-----------------------------------------------------------------------------
// - documentClipRect
//-----------------------------------------------------------------------------
- (NSRect)documentClipRect
    {
    return [[self contentView] frame];
    }


//-----------------------------------------------------------------------------
// Delegate Stuff
//-----------------------------------------------------------------------------
- (void)registerDelegateNotifications:(id)del
    flags:(MiscDelegateFlags const*)flags
    {
    NSNotificationCenter* const nc = [NSNotificationCenter defaultCenter];
    for (unsigned int i = [DEL_NOTIFICATIONS count]; i-- > 0; )
	{
	MiscNotification* const n = [DEL_NOTIFICATIONS objectAtIndex:i];
	DF::Selector const s = [n selector];
	if (flags->respondsTo(s))
	    [nc addObserver:del selector:flags->selToObjc(s)
			name:[n notification] object:tableView];
	}
    }

- (void)cancelDelegateNotifications:(id)del
    flags:(MiscDelegateFlags const*)flags
    {
    NSNotificationCenter* const nc = [NSNotificationCenter defaultCenter];
    for (unsigned int i = [DEL_NOTIFICATIONS count]; i-- > 0; )
	{
	MiscNotification* const n = [DEL_NOTIFICATIONS objectAtIndex:i];
	if (flags->respondsTo( [n selector] ))
	    [nc removeObserver:del name:[n notification] object:tableView];
	}
    }

- (void)replaceDelegate:(id*)old_del with:(id)new_del
    flags:(MiscDelegateFlags*)flags
    {
    if (*old_del != 0)
	[self cancelDelegateNotifications:*old_del flags:flags];
    *old_del = new_del;
    flags->setDelegate( new_del );
    if (new_del != 0)
	[self registerDelegateNotifications:new_del flags:flags];
    }

- (id)delegate			{ return delegate; }
- (id)dataDelegate		{ return dataDelegate; }
- (void)setDelegate:(id)obj
    { [self replaceDelegate:&delegate with:obj flags:delegateFlags]; }
- (void)setDataDelegate:(id)obj
    { [self replaceDelegate:&dataDelegate with:obj flags:dataDelegateFlags]; }

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


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


//-----------------------------------------------------------------------------
// - dealloc
//-----------------------------------------------------------------------------
- (void)dealloc
    {
    NSWindow* win = [self window];
    if (win != 0 && [win firstResponder] == tableView)
	[win makeFirstResponder:win];
    [self emptyAndReleaseCells];
    [nullView removeFromSuperview];
    [nullView release];
    [tableView removeFromSuperview];
    [tableView release];
    [self freeBorder:&colInfo];
    [self freeBorder:&rowInfo];
    if (delegate != 0)
	[self cancelDelegateNotifications:delegate flags:delegateFlags];
    if (dataDelegate != 0)
	[self cancelDelegateNotifications:dataDelegate flags:dataDelegateFlags];
    delete delegateFlags;
    delete dataDelegateFlags;
    [super dealloc];
    }


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

- (float)minTotalSize:(MiscBorderType)b
	{ return (float) info[b]->border->getMinTotalSize(); }
- (float)maxTotalSize:(MiscBorderType)b
	{ return (float) info[b]->border->getMaxTotalSize(); }
- (float)minTotalWidth
	{ return [self minTotalSize:MISC_COL_BORDER]; }
- (float)maxTotalWidth
	{ return [self maxTotalSize:MISC_COL_BORDER]; }
- (float)minTotalHeight
	{ return [self minTotalSize:MISC_ROW_BORDER]; }
- (float)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:(float)size
	{ info[b]->border->setMinTotalSize( (MiscPixels)floor(size) ); }
- (void)border:(MiscBorderType)b setMaxTotalSize:(float)size
	{ info[b]->border->setMaxTotalSize( (MiscPixels)floor(size) ); }
- (void)setMinTotalWidth:(float)size
	{ [self border:MISC_COL_BORDER setMinTotalSize:size]; }
- (void)setMaxTotalWidth:(float)size
	{ [self border:MISC_COL_BORDER setMaxTotalSize:size]; }
- (void)setMinTotalHeight:(float)size
	{ [self border:MISC_ROW_BORDER setMinTotalSize:size]; }
- (void)setMaxTotalHeight:(float)size
	{ [self border:MISC_ROW_BORDER setMaxTotalSize:size]; }


//-----------------------------------------------------------------------------
// - constrainSize
//-----------------------------------------------------------------------------
- (void)constrainSize
    {
    NSRect r = [self documentClipRect];
    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
//-----------------------------------------------------------------------------
- (void)sizeToCells
    {
    [self constrainSize];
    }


//-----------------------------------------------------------------------------
// 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:(float)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]; }


//-----------------------------------------------------------------------------
// - setFrameSize:
//-----------------------------------------------------------------------------
- (void)setFrameSize:(NSSize)s
    {
    [super setFrameSize:s];
    [self constrainSize];
    }


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


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

    if (type == MISC_COL_BORDER)
	p->view = [[MiscColView allocWithZone:z] 
			initWithFrame:NSZeroRect scroll:self info:p->border];
    else
	p->view = [[MiscRowView allocWithZone:z]
			initWithFrame:NSZeroRect scroll:self info:p->border];

    p->clip = [[NSClipView allocWithZone:z] initWithFrame:NSZeroRect];
    [p->clip setDocumentView:p->view];
    if (p->isOn)
	{
	[self addSubview:p->clip];
	[[self 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;

    [self setBorderType:NSBezelBorder];
    [super setHasHorizontalScroller:YES];
    [super setHasVerticalScroller: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];

    NSZone* const z = [self zone];

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

    tableView = [[MiscTableView allocWithZone:z] initWithFrame:NSZeroRect
		scroll:self colInfo:colInfo.border rowInfo:rowInfo.border];
    nullView = [[MiscNullView allocWithZone:z] initWithFrame:NSZeroRect];

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

    [self setDocumentView:tableView];
    [self tile];					// NOTE *1*
    [self constrainSize];
    [self forwardBGColor];

    [self registerServicesTypes];
    }


//-----------------------------------------------------------------------------
// - initWithFrame:
//-----------------------------------------------------------------------------
- (id)initWithFrame:(NSRect)frameRect
    {
    [super initWithFrame: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] retain];
    textColor = [[classObj defaultTextColor] retain];
    backgroundColor = [[classObj defaultBackgroundColor] retain];
    selectedTextColor = [[classObj defaultSelectedTextColor] retain];
    selectedBackgroundColor =
		[[classObj defaultSelectedBackgroundColor] retain];

    [self doInit:MISC_TS_VERSION];
    return self;
    }


//-----------------------------------------------------------------------------
// - setFrame:
//-----------------------------------------------------------------------------
- (void)setFrame:(NSRect)frameRect
    {
    [super setFrame:frameRect];
    [self constrainSize];
    }


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


//-----------------------------------------------------------------------------
// - tile
//-----------------------------------------------------------------------------
- (void)tile
    {
    [super tile];
    [self adjustSizes];
    NSClipView* cv = [self contentView];

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

	docRect = [cv frame];

	NSDivideRect(docRect, &colRect, &docRect, colHeight, NSMinYEdge);

	[colInfo.clip setFrame:colRect];
	[cv setFrame:docRect];
	[[self window] invalidateCursorRectsForView:colInfo.view];
	}
    else if (!colInfo.isOn && rowInfo.isOn)
	{
	float rowWidth  = [rowInfo.view frameHeight];
	NSRect rowRect;
	NSRect docRect;

	docRect = [cv frame];

	NSDivideRect(docRect, &rowRect, &docRect, rowWidth, NSMinXEdge);

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

	docRect = [cv frame];

	NSDivideRect( docRect, &colRect, &docRect, colHeight, NSMinYEdge );
	NSDivideRect( colRect, &nulRect, &colRect, rowWidth , NSMinXEdge );
	NSDivideRect( docRect, &rowRect, &docRect, rowWidth , NSMinXEdge );

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


//-----------------------------------------------------------------------------
// - reflectScroll:
//-----------------------------------------------------------------------------
- (void)reflectScrolledClipView:(NSClipView*)aView
    {
    if (aView == [self contentView]) // only reflect position of contentView
	[super reflectScrolledClipView:aView];
    }


//-----------------------------------------------------------------------------
// - scrollClip:to:
//-----------------------------------------------------------------------------
- (void)scrollClipView:(NSClipView*)aClipView toPoint:(NSPoint)aPoint
    {
    if (aClipView == [self contentView])	// contentView only.
	{
	NSRect rect;
	NSWindow* win = [self window];
	[win disableFlushWindow];

	[aClipView scrollToPoint:aPoint];		// Scroll content

	if (colInfo.isOn)
	    {
	    rect = [colInfo.clip bounds];	// Maybe scroll col headings.
	    if (rect.origin.x != aPoint.x)
		{
		rect.origin.x = aPoint.x;
		[colInfo.clip scrollToPoint:rect.origin];
		[[self window] invalidateCursorRectsForView:colInfo.view];
		}
	    }

	if (rowInfo.isOn)
	    {
	    rect = [rowInfo.clip bounds];	// Maybe scroll row labels.
	    if (rect.origin.y != aPoint.y)
		{
		rect.origin.y = aPoint.y;
		[rowInfo.clip scrollToPoint:rect.origin];
		[[self window] invalidateCursorRectsForView:rowInfo.view];
		}
	    }

	[win enableFlushWindow];
	[win flushWindow];
	}
    else if (aClipView == (id)colInfo.clip)
	{
	NSClipView* cv = [self contentView];
	NSRect rect = [cv bounds];
	rect.origin.x = aPoint.x;
	[self scrollClipView:cv toPoint:rect.origin];
	[self reflectScrolledClipView:cv];
	}
    else if (aClipView == (id)rowInfo.clip)
	{
	NSClipView* cv = [self contentView];
	NSRect rect = [cv bounds];
	rect.origin.y = aPoint.y;
	[self scrollClipView:cv toPoint:rect.origin];
	[self reflectScrolledClipView:cv];
	}
    }


//-----------------------------------------------------------------------------
// Border stuff
//-----------------------------------------------------------------------------
- (NSString*)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];

    NSString* s = @"";
    return s;
    }

- (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 const slot = [self border:b slotAtPosition:toPos];
	if ([self border:b slotSortType:slot] != MISC_SORT_SKIP ||
	    [self border:b slotSortFunction: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
//-----------------------------------------------------------------------------
- (BOOL)isEnabled			{ return enabled; }
- (void)setEnabled:(BOOL)flag		{ enabled = flag; }

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

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

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

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

- (BOOL)sendActionIfEnabled
	{
	if ([self isEnabled]) { [self sendAction]; return YES; }
	return NO;
	}

- (BOOL)sendDoubleActionIfEnabled
	{
	if ([self isEnabled]) { [self sendDoubleAction]; return YES; }
	return NO;
	}


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

- (NSFont*)font
    {
    return font;
    }


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


- (void)setFont:(NSFont*)newFont
    {
    if (![newFont isEqual:font])
	{
	NSFont* oldFont = [font autorelease];
	font = [newFont retain];
	float old_size = [self uniformSizeRows];
	if (old_size != 0)
	    {
	    // FIXME: Handle this better.  Different cell-types will have
	    // different amounts of "fixed-size" border stuff.
	    float const BORDER_THICKNESS = 1;
	    old_size -= BORDER_THICKNESS;
	    float 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 cellAtRow:r column:c];
		    if (cell != 0)
			{
			if ([cell respondsToSelector:@selector(setOwnerFont:)])
			    [cell setOwnerFont:newFont];
			else if ([cell respondsToSelector:@selector(setFont:)])
			    [cell setFont:newFont];
			}
		    }
		}
	    }
	id del = [self responsibleDelegate:DF::DEL_FONT_CHANGED];
	if (del != 0)
	    [del tableScroll:self fontChangedFrom:oldFont to:newFont];

	[self setNeedsDisplay:YES];
	}
    }


- (void)changeFont:(id)sender
    {
    NSFontManager* fontMgr = [NSFontManager sharedFontManager];
    NSFont* newFont = [fontMgr convertFont:[fontMgr selectedFont]];
    if (newFont != 0 && ![newFont isEqual:font])
	{
	[self abortEditing];
	NSFont* oldFont = [[font retain] autorelease];
	[self setFont:newFont];
	id del = [self responsibleDelegate:DF::DEL_CHANGE_FONT];
	if (del != 0)
	    [del tableScroll:self changeFont:oldFont to:newFont];
	}
    }


//-----------------------------------------------------------------------------
// 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:, setSelectedBackgroundColor:,
// setSelectedTextColor:
//-----------------------------------------------------------------------------
+ (NSColor*)defaultBackgroundColor	    { return [NSColor lightGrayColor];}
+ (NSColor*)defaultTextColor		    { return [NSColor blackColor]; }
+ (NSColor*)defaultSelectedBackgroundColor  { return [NSColor whiteColor]; }
+ (NSColor*)defaultSelectedTextColor	    { return [NSColor blackColor]; }

- (NSColor*)backgroundColor		{ return backgroundColor; }
- (NSColor*)textColor			{ return textColor; }
- (NSColor*)selectedBackgroundColor	{ return selectedBackgroundColor; }
- (NSColor*)selectedTextColor		{ return selectedTextColor; }

- (void)setColor:(NSColor*)value	{ [self setBackgroundColor:value]; }
- (NSColor*)color			{ return [self backgroundColor]; }

- (void)setColor:(NSColor*)x		// New color value
	var:(NSColor**)v		// Instance variable for the color
	sel1:(SEL)sel1			// "setOwner...Color:" message for cell
	sel2:(SEL)sel2			// "set...Color:" message for cell
	notifySel:(DF::Selector)notifySel // "..ColorChangedTo:"
    {
    if (![x isEqual:*v])
	{
	[*v autorelease];
	*v = [x retain];
	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 cellAtRow:r column:c];
		    if (cell != 0)
			{
			if ([cell respondsToSelector:sel1])
			    (*[cell methodForSelector:sel1])( cell, sel1, x );
			else if ([cell respondsToSelector:sel2])
			    (*[cell methodForSelector:sel2])( cell, sel2, x );
			}
		    }
	    }

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

	[self setNeedsDisplay:YES];
	}
    }


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

MISC_SET_COLOR_FUNC( background, Background, BACK )
MISC_SET_COLOR_FUNC( text, Text, TEXT )
MISC_SET_COLOR_FUNC( selectedBackground, SelectedBackground, BACK_SEL )
MISC_SET_COLOR_FUNC( selectedText, SelectedText, TEXT_SEL )
#undef MISC_SET_COLOR_FUNC


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

- (NSString*)stringFromIntArray:(NSArray*)array
    {
    NSMutableString* s = [[[NSMutableString alloc] init] autorelease];
    for (int i = 0, lim = [array count]; i < lim; i++)
	{
	if (i > 0) [s appendString:@" "];
	[s appendString:[[array objectAtIndex:i] stringValue]];
	}
    return s;
    }

- (NSArray*)intArrayFromString:(NSString*)s
    {
    NSMutableArray* array = [NSMutableArray array];
    NSScanner* scanner = [NSScanner scannerWithString:s];
    int i;
    while (![scanner isAtEnd])
	if ([scanner scanInt:&i])
	    [array addObject:[NSNumber numberWithInt:i]];
    return array;
    }


//-----------------------------------------------------------------------------
// SLOT ORDER
//-----------------------------------------------------------------------------
- (NSArray*)slotOrder:(MiscBorderType)b
    {
    NSMutableArray* array = [NSMutableArray array];
    MiscTableBorder const* const bp = info[b]->border;
	MiscCoord_P const* const vmap = bp->getP2VMap();
    int const lim = bp->count();
    for (int i = 0;  i < lim;  i++)
	{
	int v = (vmap ? vmap[i] : i);
	if (bp->getSortDirection(v) == MISC_SORT_DESCENDING)
	    v = ~v;
	[array addObject:[NSNumber numberWithInt:v]];
	}
    return array;
    }
- (NSArray*)columnOrder { return [self slotOrder:MISC_COL_BORDER]; }
- (NSArray*)rowOrder { return [self slotOrder:MISC_ROW_BORDER]; }

- (BOOL)border:(MiscBorderType)b setSlotOrder:(NSArray*)array
    {
    BOOL ret = NO;
    if (array != 0)
	{
	MiscTableBorder* const bp = info[b]->border;
	unsigned int const lim = bp->count();
	if ([array count] == lim)
	    {
	    bool* desc = (bool*) malloc( lim * sizeof(*desc) );
	    int*  map  = (int*)  malloc( lim * sizeof(*map) );
	    for (unsigned int i = 0; i < lim; i++)
		{
		int const j = [[array objectAtIndex:i] intValue];
		map[i] = (desc[i] = (j < 0)) ? ~j : j;
		}

	    if (bp->setP2VMap( map ))
		{
		for (unsigned 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];
		ret = YES;
		}
	    free( map );
	    free( desc );
	    }
	}
    return ret;
    }

- (BOOL)setColumnOrder:(NSArray*)list
    { return [self border:MISC_COL_BORDER setSlotOrder:list]; }
- (BOOL)setRowOrder:(NSArray*)list
    { return [self border:MISC_ROW_BORDER setSlotOrder:list]; }

- (NSString*)slotOrderAsString:(MiscBorderType)b
    { return [self stringFromIntArray:[self slotOrder:b]]; }
- (NSString*)columnOrderAsString;
    { return [self slotOrderAsString:MISC_COL_BORDER]; }
- (NSString*)rowOrderAsString
    { return [self slotOrderAsString:MISC_ROW_BORDER]; }

- (BOOL)border:(MiscBorderType)b setSlotOrderFromString:(NSString*)s
    { return [self border:b setSlotOrder:[self intArrayFromString:s]]; }
- (BOOL)setColumnOrderFromString:(NSString*)s
    { return [self border:MISC_COL_BORDER setSlotOrderFromString:s]; }
- (BOOL)setRowOrderFromString:(NSString*)s
    { return [self border:MISC_ROW_BORDER setSlotOrderFromString:s]; }


//-----------------------------------------------------------------------------
// SLOT SIZES
//-----------------------------------------------------------------------------
- (NSArray*)slotSizes:(MiscBorderType)b
    {
    NSMutableArray* array = [NSMutableArray array];
    MiscTableBorder const* const bp = info[b]->border;
    int const lim = bp->count();
    for (int i = 0;  i < lim;  i++)
	[array addObject:[NSNumber numberWithInt:(int)bp->getSize_P(i)]];
    return array;
    }
- (NSArray*)columnSizes { return [self slotSizes:MISC_COL_BORDER]; }
- (NSArray*)rowSizes { return [self slotSizes:MISC_ROW_BORDER]; }

- (BOOL)border:(MiscBorderType)b setSlotSizes:(NSArray*)array
    {
    BOOL ret = NO;
    if (array != 0)
	{
	MiscTableBorder* const bp = info[b]->border;
	unsigned int const lim = bp->count();
	if ([array count] == lim)
	    {
	    for (unsigned int i = 0; i < lim; i++)
		{
		MiscPixels const min_size =
				MiscPixels( [self border:b slotMinSize:i] );
		MiscPixels const max_size =
				MiscPixels( [self border:b slotMaxSize:i] );
		MiscPixels s = MiscPixels([[array objectAtIndex:i] intValue]);
		if (s < min_size)
		    s = min_size;
		else if (s > max_size)
		    s = max_size;
		bp->setSize_P( i, s );
		}
	    [self constrainSize];
	    ret = YES;
	    }
	}
    return ret;
    }
- (BOOL)setColumnSizes:(NSArray*)list
    { return [self border:MISC_COL_BORDER setSlotSizes:list]; }
- (BOOL)setRowSizes:(NSArray*)list
    { return [self border:MISC_ROW_BORDER setSlotSizes:list]; }

- (NSString*)slotSizesAsString:(MiscBorderType)b
    { return [self stringFromIntArray:[self slotSizes:b]]; }
- (NSString*)columnSizesAsString
    { return [self slotSizesAsString:MISC_COL_BORDER]; }
- (NSString*)rowSizesAsString
    { return [self slotSizesAsString:MISC_ROW_BORDER]; }

- (BOOL)border:(MiscBorderType)b setSlotSizesFromString:(NSString*)s
    { return [self border:b setSlotSizes:[self intArrayFromString:s]]; }
- (BOOL)setColumnSizesFromString:(NSString*)s
    { return [self border:MISC_COL_BORDER setSlotSizesFromString:s]; }
- (BOOL)setRowSizesFromString:(NSString*)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];
	    [[self 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 setNeedsDisplay:YES];
	return YES;
	}
    return NO;
    }


//-----------------------------------------------------------------------------
// SLOT methods
//-----------------------------------------------------------------------------
- (MiscTableBorder*)border:(MiscBorderType)b
	{ return info[b]->border; }
- (BOOL)border:(MiscBorderType)b setSlotTitlesOn:(BOOL)on_off
	{ return [self setBorder:b on:on_off]; }
- (BOOL)slotTitlesOn:(MiscBorderType)b
	{ return info[b]->isOn; }
- (MiscTableTitleMode)slotTitleMode:(MiscBorderType)b
	{ return info[b]->border->getTitleMode(); }
- (void)border:(MiscBorderType)b setSlotTitleMode:(MiscTableTitleMode)x
	{
	MiscBorderInfo* const ip = info[b];
	if (ip->border->setTitleMode(x) && ip->isOn && ip->border->count() > 0)
	    [ip->view setNeedsDisplay:YES];
	}

- (float)slotTitlesSize:(MiscBorderType)b
	{ return info[b]->isOn ? [info[b]->view frameHeight] : 0; }
- (void)border:(MiscBorderType)b setSlotTitlesSize:(float)x
	{
	[info[b]->view setFrameHeight:(MiscPixels)floor(x)];
	[self tile];
	}

- (void)border:(MiscBorderType)b moveSlotFrom:(int)fromPos to:(int)toPos
	{
	info[b]->border->moveFromTo( fromPos, toPos );
	[self selectionChanged];
	}
- (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); }

- (NSArray*)border:(MiscBorderType)b physicalToVisual:(NSArray*)p_array
	{
	NSMutableArray* v_array = [NSMutableArray array];
	for (unsigned int i = 0, lim = [p_array count]; i < lim; i++)
	    {
	    MiscCoord_P const p = [[p_array objectAtIndex:i] intValue];
	    MiscCoord_V const v = [self border:b slotPosition:p];
	    [v_array addObject:[NSNumber numberWithInt:v]];
	    }
	return v_array;
	}

- (NSArray*)border:(MiscBorderType)b visualToPhysical:(NSArray*)v_array
	{
	NSMutableArray* p_array = [NSMutableArray array];
	for (unsigned int i = 0, lim = [v_array count]; i < lim; i++)
	    {
	    MiscCoord_V const v = [[v_array objectAtIndex:i] intValue];
	    MiscCoord_P const p = [self border:b slotAtPosition:v];
	    [p_array addObject:[NSNumber numberWithInt:p]];
	    }
	return p_array;
	}


- (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(); }
- (float)uniformSizeSlots:(MiscBorderType)b
	{ return (float) info[b]->border->getUniformSize(); }

- (float)border:(MiscBorderType)b slotAdjustedSize:(int)n
	{ return (float) info[b]->border->effectiveSize_P(n); }
- (float)border:(MiscBorderType)b slotSize:(int)n
	{ return (float) info[b]->border->getSize_P(n); }
- (float)border:(MiscBorderType)b slotMinSize:(int)n
	{ return (float) info[b]->border->getMinSize_P(n); }
- (float)border:(MiscBorderType)b slotMaxSize:(int)n
	{ return (float) info[b]->border->getMaxSize_P(n); }
- (float)border:(MiscBorderType)b slotDataSize:(int)n
	{ return (float) 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); }
- (NSString*)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:(float)uniform_size
	{
	MiscBorderInfo* const ip = info[b];
	if (ip->border->setUniformSize((MiscPixels)floor(uniform_size)))
	    {
	    [self constrainSize];
	    if (b == MISC_ROW_BORDER)
		{
		float const scr_size = uniform_size != 0 ?
			uniform_size : ip->border->getDefaultSize();
		[self setLineScroll:scr_size];
		[self setPageScroll:scr_size];
		}
	    [self setNeedsDisplay:YES];
	    }
	}

- (void)border:(MiscBorderType)b setSlot:(int)n size:(float)size
	{
	info[b]->border->setSize_P( n, (MiscPixels)floor(size) );
	[self constrainSize];
	}
- (void)border:(MiscBorderType)b setSlot:(int)n minSize:(float)size
	{
	info[b]->border->setMinSize_P( n, (MiscPixels)floor(size) );
	[self constrainSize];
	}
- (void)border:(MiscBorderType)b setSlot:(int)n maxSize:(float)size
	{
	info[b]->border->setMaxSize_P( n, (MiscPixels)floor(size) );
	[self constrainSize];
	}
- (void)border:(MiscBorderType)b setSlot:(int)n dataSize:(float)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:(NSString*)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); }



//-----------------------------------------------------------------------------
// COLUMN methods
//-----------------------------------------------------------------------------
- (MiscTableBorder*)columnBorder
	{ return colInfo.border; }
- (BOOL)columnTitlesOn
	{ return [self slotTitlesOn:MISC_COL_BORDER]; }
- (BOOL)setColumnTitlesOn:(BOOL)x
	{ return [self setBorder:MISC_COL_BORDER on:x]; }
- (MiscTableTitleMode)columnTitleMode
	{ return [self slotTitleMode:MISC_COL_BORDER]; }
- (void)setColumnTitleMode:(MiscTableTitleMode)x
	{ [self border:MISC_COL_BORDER setSlotTitleMode:x]; }
- (float)columnTitlesHeight
	{ return [self slotTitlesSize:MISC_COL_BORDER]; }
- (void)setColumnTitlesHeight:(float)x
	{ [self border:MISC_COL_BORDER setSlotTitlesSize:x]; }
- (MiscBorderView*)colTitles
	{ return ([self columnTitlesOn] ? colInfo.view : 0); }

- (void)moveColumnFrom:(int)fromPos to:(int)toPos
	{ [self border:MISC_COL_BORDER moveSlotFrom:fromPos to:toPos]; }
- (int)columnPosition:(int)n
	{ return [self border:MISC_COL_BORDER slotPosition:n]; }
- (int)columnAtPosition:(int)n
	{ return [self border:MISC_COL_BORDER slotAtPosition:n]; }

- (BOOL)sizeableColumns
	{ return [self sizeableSlots:MISC_COL_BORDER]; }
- (BOOL)draggableColumns
	{ return [self draggableSlots:MISC_COL_BORDER]; }
- (BOOL)modifierDragColumns
	{ return [self modifierDragSlots:MISC_COL_BORDER]; }
- (float)uniformSizeColumns
	{ return [self uniformSizeSlots:MISC_COL_BORDER]; }

- (float)columnAdjustedSize:(int)n
	{ return [self border:MISC_COL_BORDER slotAdjustedSize:n]; }
- (float)columnSize:(int)n
	{ return [self border:MISC_COL_BORDER slotSize:n]; }
- (float)columnMinSize:(int)n
	{ return [self border:MISC_COL_BORDER slotMinSize:n]; }
- (float)columnMaxSize:(int)n
	{ return [self border:MISC_COL_BORDER slotMaxSize:n]; }
- (float)columnDataSize:(int)n
	{ return [self border:MISC_COL_BORDER slotDataSize:n]; }
- (BOOL)columnIsSizeable:(int)n
	{ return [self border:MISC_COL_BORDER slotIsSizeable:n]; }
- (BOOL)columnExpandsToData:(int)n
	{ return [self border:MISC_COL_BORDER slotExpandsToData:n]; }
- (BOOL)columnIsAutosize:(int)n
	{ return [self border:MISC_COL_BORDER slotIsAutosize:n]; }
- (NSString*)columnTitle:(int)n
	{ return [self border:MISC_COL_BORDER slotTitle:n]; }
- (int)columnTag:(int)n
	{ return [self border:MISC_COL_BORDER slotTag:n]; }
- (MiscTableCellStyle)columnCellType:(int)n
	{ return [self border:MISC_COL_BORDER slotCellType:n]; }
- (id)columnCellPrototype:(int)n
	{ return [self border:MISC_COL_BORDER slotCellPrototype:n]; }

- (void)setSizeableColumns:(BOOL)flag
	{ [self border:MISC_COL_BORDER setSizeableSlots:flag]; }
- (void)setDraggableColumns:(BOOL)flag
	{ [self border:MISC_COL_BORDER setDraggableSlots:flag]; }
- (void)setModifierDragColumns:(BOOL)flag
	{ [self border:MISC_COL_BORDER setModifierDragSlots:flag]; }
- (void)setUniformSizeColumns:(float)size
	{ [self border:MISC_COL_BORDER setUniformSizeSlots:size]; }

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


//-----------------------------------------------------------------------------
// ROW methods
//-----------------------------------------------------------------------------
- (MiscTableBorder*)rowBorder
	{ return rowInfo.border; }
- (BOOL)rowTitlesOn
	{ return [self slotTitlesOn:MISC_ROW_BORDER]; }
- (BOOL)setRowTitlesOn:(BOOL)x
	{ return [self setBorder:MISC_ROW_BORDER on:x]; }
- (MiscTableTitleMode)rowTitleMode
	{ return [self slotTitleMode:MISC_ROW_BORDER]; }
- (void)setRowTitleMode:(MiscTableTitleMode)x
	{ [self border:MISC_ROW_BORDER setSlotTitleMode:x]; }
- (float)rowTitlesWidth
	{ return [self slotTitlesSize:MISC_ROW_BORDER]; }
- (void)setRowTitlesWidth:(float)x
	{ [self border:MISC_ROW_BORDER setSlotTitlesSize:x]; }
- (MiscBorderView*)rowTitles
	{ return ([self rowTitlesOn] ? rowInfo.view : 0); }

- (void)moveRowFrom:(int)fromPos to:(int)toPos
	{ [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]; }
- (float)uniformSizeRows
	{ return [self uniformSizeSlots:MISC_ROW_BORDER]; }

- (float)rowAdjustedSize:(int)n
	{ return [self border:MISC_ROW_BORDER slotAdjustedSize:n]; }
- (float)rowSize:(int)n
	{ return [self border:MISC_ROW_BORDER slotSize:n]; }
- (float)rowMinSize:(int)n
	{ return [self border:MISC_ROW_BORDER slotMinSize:n]; }
- (float)rowMaxSize:(int)n
	{ return [self border:MISC_ROW_BORDER slotMaxSize:n]; }
- (float)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]; }
- (NSString*)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:(float)size
	{ [self border:MISC_ROW_BORDER setUniformSizeSlots:size]; }

- (void)setRow:(int)n size:(float)size
	{ [self border:MISC_ROW_BORDER setSlot:n size:size]; }
- (void)setRow:(int)n minSize:(float)size
	{ [self border:MISC_ROW_BORDER setSlot:n minSize:size]; }
- (void)setRow:(int)n maxSize:(float)size
	{ [self border:MISC_ROW_BORDER setSlot:n maxSize:size]; }
- (void)setRow:(int)n dataSize:(float)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:(NSString*)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:(id)p
	{ [self border:MISC_ROW_BORDER setSlot:n cellPrototype:p]; }


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


//-----------------------------------------------------------------------------
// - readString:isCString:
//-----------------------------------------------------------------------------
- (NSString*)readString:(NSCoder*)decoder isCString:(BOOL)isCString
    {
    NSString* str = @"";
    if (!isCString)
	str = [decoder decodeObject];
    else
	{
	char* cstr = 0;
	[decoder decodeValueOfObjCType:@encode(char*) at:&cstr];
	if (cstr != 0)
	    {
	    str = [NSString stringWithCString:cstr];
	    NSZoneFree( [decoder objectZone], cstr );
	    }
	}
    return str;
    }


//-----------------------------------------------------------------------------
// - read:selector:
//-----------------------------------------------------------------------------
- (SEL)read:(int)ver selector:(NSCoder*)decoder
    {
    SEL cmd = 0;
    BOOL const isCString = (ver < MISC_TS_VERSION_1000);
    NSString* str = [self readString:decoder isCString:isCString];
    if ([str length] > 0)
	cmd = NSSelectorFromString( 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:(NSCoder*)decoder
    {
    [decoder decodeValueOfObjCType:@encode(int) at:&tag];
    [decoder decodeValueOfObjCType:@encode(BOOL) at:&enabled];
    [decoder decodeValueOfObjCType:@encode(BOOL) at:&lazy];
    [decoder decodeValueOfObjCType:@encode(MiscSelectionMode) at:&mode];
    font = [[decoder decodeObject] retain];

    if (ver < MISC_TS_VERSION_1000)
	{
	textColor		= [[decoder decodeNXColor] retain];
	backgroundColor		= [[decoder decodeNXColor] retain];
	selectedTextColor	= [[decoder decodeNXColor] retain];
	selectedBackgroundColor	= [[decoder decodeNXColor] retain];
	id nextText		= [decoder decodeObject];
	id previousText		= [decoder decodeObject];
	[self setNextKeyView:nextText];
	[previousText setNextKeyView:self];
	}
    else
	{
	textColor		= [[decoder decodeObject] retain];
	backgroundColor		= [[decoder decodeObject] retain];
	selectedTextColor	= [[decoder decodeObject] retain];
	selectedBackgroundColor	= [[decoder decodeObject] retain];
	}

    delegate	= [decoder decodeObject];
    dataDelegate= [decoder decodeObject];
    target	= [decoder decodeObject];
    doubleTarget= [decoder decodeObject];
    action	= [self read:ver selector:decoder];
    doubleAction= [self read:ver selector:decoder];
    }


//-----------------------------------------------------------------------------
// - read:borderSortVector:decoder:
//-----------------------------------------------------------------------------
- (void)read:(int)ver borderSortVector:(MiscBorderInfo*)p
    decoder:(NSCoder*)decoder
    {
    if (p->sort_vector != 0)
	{
	[p->sort_vector release];
	p->sort_vector = 0;
	}

    if (ver >= MISC_TS_VERSION_1001)
	{
	p->sort_vector = [[decoder decodeObject] retain];
	}
    else
	{
	int n;
	[decoder decodeValueOfObjCType:@encode(int) at:&n];
	if (n > 0)
	    {
	    NSMutableArray* a =
			[[NSMutableArray allocWithZone:[self zone]] init];
	    for (int i = 0;  i < n;  i++)
		{
		int j;
		[decoder decodeValueOfObjCType:@encode(int) at:&j];
		[a addObject:[NSNumber numberWithInt:j]];
		}
	    p->sort_vector = a;
	    }
	}
    }


//-----------------------------------------------------------------------------
// - read:border:decoder:
//-----------------------------------------------------------------------------
- (void)read:(int)ver border:(MiscBorderInfo*)p decoder:(NSCoder*)decoder
    {
    [self read:ver borderSortVector:p decoder:decoder];
    [decoder decodeValueOfObjCType:@encode(BOOL) at:&(p->isOn)];
    [decoder decodeValueOfObjCType:@encode(BOOL) at:&(p->autoSort)];
    [decoder decodeValueOfObjCType:@encode(BOOL) at:&(p->constrain_min)];
    [decoder decodeValueOfObjCType:@encode(BOOL) at:&(p->constrain_max)];
    if (p->border == 0)
	p->border = new( NSZoneMalloc( [self zone], sizeof(*(p->border)) ) )
			MiscTableBorder( MISC_ROW_BORDER );
    p->border->initWithCoder( decoder, 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:(NSCoder*)decoder
    {
    [decoder decodeValueOfObjCType:@encode(int) at:&num_cols];
    [decoder decodeValueOfObjCType:@encode(int) at:&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*) NSZoneMalloc( [self zone], nbytes );
	    id* p = cells;
	    for (int r = 0; r < num_rows; r++)
		for (int c = 0; c < num_cols; c++)
		    *p++ = [[decoder decodeObject] retain];
	    }
	}
    }


//-----------------------------------------------------------------------------
// - initWithCoder:
//-----------------------------------------------------------------------------
- (id)initWithCoder:(NSCoder*)decoder
    {
    [super initWithCoder:decoder];
    NSString* class_name = [[MiscTableScroll class] description];
    int const ver = [decoder versionForClassName:class_name];
    if (((unsigned int)ver) > ((unsigned int)MISC_TS_VERSION))
	[NSException raise:NSGenericException format:
	@"%@: old library (version %d), can't -read: new object (version %d)",
	class_name, MISC_TS_VERSION, ver ];
    [self read:ver globalInfo:decoder];
    [self read:ver border:&colInfo decoder:decoder];
    [self read:ver border:&rowInfo decoder:decoder];
    [self read:ver cells:decoder];
    [self doInit:ver];
    return self;
    }


//-----------------------------------------------------------------------------
// - writeSelector:encoder:
//-----------------------------------------------------------------------------
- (void)writeSelector:(SEL)cmd encoder:(NSCoder*)encoder
    {
    NSString* str = @"";
    if (cmd != 0)
	str = NSStringFromSelector( cmd );
    [encoder encodeObject:str];
    }


//-----------------------------------------------------------------------------
// - writeGlobalInfo:
//
// NOTE: Cannot archive sort_entry_func and sort_slot_func because they 
//	are function addresses.  
//-----------------------------------------------------------------------------
- (void)writeGlobalInfo:(NSCoder*)encoder
    {
    [encoder encodeValueOfObjCType:@encode(int) at:&tag];
    [encoder encodeValueOfObjCType:@encode(BOOL) at:&enabled];
    [encoder encodeValueOfObjCType:@encode(BOOL) at:&lazy];
    [encoder encodeValueOfObjCType:@encode(MiscSelectionMode) at:&mode];
    [encoder encodeObject:font];
    [encoder encodeObject:textColor];
    [encoder encodeObject:backgroundColor];
    [encoder encodeObject:selectedTextColor];
    [encoder encodeObject:selectedBackgroundColor];
    [encoder encodeConditionalObject:delegate];
    [encoder encodeConditionalObject:dataDelegate];
    [encoder encodeConditionalObject:target];
    [encoder encodeConditionalObject:doubleTarget];
    [self writeSelector:action encoder:encoder];
    [self writeSelector:doubleAction encoder:encoder];
    }


//-----------------------------------------------------------------------------
// - writeBorder:encoder:
//-----------------------------------------------------------------------------
- (void)writeBorder:(MiscBorderInfo*)p encoder:(NSCoder*)encoder
    {
    [encoder encodeObject:p->sort_vector];
    [encoder encodeValueOfObjCType:@encode(BOOL) at:&(p->isOn)];
    [encoder encodeValueOfObjCType:@encode(BOOL) at:&(p->autoSort)];
    [encoder encodeValueOfObjCType:@encode(BOOL) at:&(p->constrain_min)];
    [encoder encodeValueOfObjCType:@encode(BOOL) at:&(p->constrain_max)];
    p->border->encodeWithCoder( encoder );
    }


//-----------------------------------------------------------------------------
// - writeCells:
//-----------------------------------------------------------------------------
- (void)writeCells:(NSCoder*)encoder
    {
    [encoder encodeValueOfObjCType:@encode(int) at:&num_cols];
    [encoder encodeValueOfObjCType:@encode(int) at:&num_rows];
    if (!lazy)
	for (int r = 0;	 r < num_rows;	r++)
	    for (int c = 0;  c < num_cols;  c++)
		[encoder encodeObject:[self cellAtRow:r column:c]];
    }


//-----------------------------------------------------------------------------
// - encodeWithCoder:
//-----------------------------------------------------------------------------
- (void)encodeWithCoder:(NSCoder*)encoder
    {
    // 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 encodeWithCoder:encoder];
    [self writeGlobalInfo:encoder];
    [self writeBorder:&colInfo encoder:encoder];
    [self writeBorder:&rowInfo encoder:encoder];
    [self writeCells:encoder];

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


//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------

- (void)drawCellAtRow:(int)row column:(int)col
    {
    [tableView lockFocus];
    [tableView drawCellAtRow:row column:col];
    [tableView unlockFocus];
    [[self window] flushWindow];
    }
- (void)drawRow:(int)row
    {
    [tableView lockFocus];
    [tableView drawRow:row];
    [tableView unlockFocus];
    [[self window] flushWindow]; 
    }
- (void)drawColumn:(int)col
    {
    [tableView lockFocus];
    [tableView drawColumn:col];
    [tableView unlockFocus];
    [[self window] flushWindow]; 
    }
- (void)border:(MiscBorderType)b drawSlot:(int)n
    { if (b == MISC_COL_BORDER) [self drawColumn:n]; else [self drawRow:n]; }

- (void)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];
	[[self window] flushWindow];
	} 
    }
- (void)drawRowTitle:(int)n
    { [self border:MISC_ROW_BORDER drawSlotTitle:n]; }
- (void)drawColumnTitle:(int)n
    { [self border:MISC_COL_BORDER drawSlotTitle:n]; }

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

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


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

- (void)scrollCellToVisibleAtRow:(int)row column:(int)col
	{ [tableView scrollCellToVisibleAtRow:row column:col]; }
- (void)scrollRowToVisible:(int)row { [tableView scrollRowToVisible:row]; }
- (void)scrollColumnToVisible:(int)col
	{ [tableView scrollColumnToVisible:col]; }
- (void)scrollSelectionToVisible
    {
    if ([self hasRowSelection])
	[self scrollRowToVisible:[self selectedRow]];
    else if ([self hasColumnSelection])
	[self scrollColumnToVisible:[self selectedColumn]];
    }


- (int)numberOfVisibleSlots:(MiscBorderType)b
	{ return [tableView numberOfVisibleSlots: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)numberOfVisibleColumns
	{ return [self numberOfVisibleSlots:MISC_COL_BORDER]; }
- (int)firstVisibleColumn
	{ return [self firstVisibleSlot:MISC_COL_BORDER]; }
- (int)lastVisibleColumn
	{ return [self lastVisibleSlot:MISC_COL_BORDER]; }
- (BOOL)columnIsVisible:(int)n
	{ return [self border:MISC_COL_BORDER slotIsVisible:n]; }
- (void)setFirstVisibleColumn:(int)n
	{ [self border:MISC_COL_BORDER setFirstVisibleSlot:n]; }
- (void)setLastVisibleColumn:(int)n
	{ [self border:MISC_COL_BORDER setLastVisibleSlot:n]; }

- (int)numberOfVisibleRows
	{ return [self numberOfVisibleSlots: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]; }


//-----------------------------------------------------------------------------
// *FIXME*
//	OPENSTEP 4.2 Objective-C++ compiler for NT (final release) crashes 
//	whenever a message is sent to 'super' from within a category.  This 
//	bug also afflicts the 4.2 (prerelease) compiler for Mach and NT.  
//	Work around it by providing stub methods in the main (non-category) 
//	implementation which merely forward the appropriate message to 'super' 
//	on behalf of the categories.  Though ugly, it works, is very 
//	localized, and simple to remove when the bug is finally fixed.  
//-----------------------------------------------------------------------------
- (void)superKeyDown:(NSEvent*)p
	{ [super keyDown:p]; }
- (id)superValidRequestorForSendType:(NSString*)s returnType:(NSString*)r
	{ return [super validRequestorForSendType:s returnType:r]; }

@end
