//=============================================================================
//
//		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.
//
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableView.M
//
//		General-purpose 2-D display object that works with the
//		MiscTableScroll to provide row/column sizing and dragging.
//
//		This object is responsible for drawing, mouse and keyboard
//		events in the content portion of the display.
//
// FIXME: The PostScript pattern used to draw the dotted "keyboard focus" 
//		rectangle gets "out-of-phase" as this view scrolls.	 Consequently it 
//		does not tile correctly.  The Adobe Purple Book section 10.4.4 
//		implies that the pattern has to be recreated each time the view 
//		changes.  (I forsee using -initGState for this.) 
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableView.M,v 1.28 97/06/10 05:29:41 sunshine Exp $
// $Log:		MiscTableView.M,v $
//	Revision 1.28  97/06/10	 05:29:41  sunshine
//	v127: Fixed bug: Was unconditionally drawing cursor in -setCursorRow: &
//	-setCursorCol: even when it should not have been (such as when cursor
//	disabled or not first responder).
//	Fixed bug: Was not returning 'nil' from -getRow:col:forPoint: when point
//	was out of bounds.	Uses new MiscAssert.
//	
//	Revision 1.27  97/05/04	 18:29:16  zarnuk
//	v126: changed drag operation from copy -> generic.
//	
//	Revision 1.26  97/02/12	 13:03:04  sunshine
//	v124: Added clipping back to -drawSelf::.
//-----------------------------------------------------------------------------
#import "MiscTableView.h"
#import "MiscAssert.h"
#import "MiscColorList.h"
#import "MiscDrawList.h"
#import "MiscGeometry.h"
#import "MiscHighlightTracker.h"
#import "MiscListTracker.h"
#import "MiscRadioTracker.h"
#import "MiscRectColorList.h"
#import "MiscRectList.h"
#import "MiscSparseSet.h"
#import "MiscTableBorder.h"
#import "MiscTableScrollPrivate.h"
#import <misckit/MiscTableCell.h>
#import <misckit/MiscTableScroll.h>
#import <new.h>

extern "Objective-C" {
#import <appkit/Application.h>
#import <appkit/Cell.h>
#import <appkit/FontManager.h>
#import <appkit/Text.h>
#import <appkit/timer.h>
#import <dpsclient/wraps.h>
}

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

int const NUM_EDGES = 4;		// 4 edges to draw per focus rectangle
int const NUM_COORDS = 4;		// 4 coords per rectangle (x,y,w,h)
int const MAX_COORDS = NUM_EDGES * NUM_COORDS;	// 16 coords per 4 rects

typedef MiscDelegateFlags DF;

//----------------------------------------------------------------------------
// startTimer
//----------------------------------------------------------------------------
static inline void startTimer( NXTrackingTimer*& timer )
	{
	if (timer == 0)
		timer = NXBeginTimer( 0, 0.1, 0.1 );
	}


//----------------------------------------------------------------------------
// stopTimer
//----------------------------------------------------------------------------
static inline void stopTimer( NXTrackingTimer*& timer )
	{
	if (timer)
		{
		NXEndTimer( timer );
		timer = 0;
		}
	}


//-----------------------------------------------------------------------------
// init_pswrap
//-----------------------------------------------------------------------------
static inline void init_pswrap()
	{
	static initialized = NO;
	if (!initialized)
		{
		initialized = YES;
		MISC_TV_initps();
		}
	}


//-----------------------------------------------------------------------------
// dump_map
//-----------------------------------------------------------------------------
// static void dump_map( char const* label, int const* map, int nc, int nr )
//	   {
//	   fprintf( stderr, "%s nc=%d, nr=%d\n", label, nc, nr );
//	   int const* p = map;
//	   for (int r = 0; r < nr; r++)
//		   {
//		   for (int c = 0; c < nc; c++, p++)
//			   fprintf( stderr, " %d", *p );
//		   fprintf( stderr, "\n" );
//		   }
//	   }


//-----------------------------------------------------------------------------
// extract_rect
//		Extract a contiguous rectangular group of cells to be drawn from
//		this boolean map of cells that need drawing.
//
//		1) Find the first cell that needs to be drawn (c0,r0).
//		2) Include all contiguous following cells on the same row
//				that also need to be drawn (cN,r0).
//		3) Include all contiguous following rows in which (c0..cN)
//				all need to be drawn.
//		4) Clear the flags from the map to indicate that these cells
//				do not need to be drawn again.
//-----------------------------------------------------------------------------
static int extract_rect( int* map, int nc, int nr,
						MiscCoord_V& c0, MiscCoord_V& r0,
						MiscCoord_V& cN, MiscCoord_V& rN )
	{
	int* p = map;
	int* plim = p + (nc * nr);

	while (p < plim && *p == 0)
		p++;

	if (p >= plim)
		return 0;						// *** RETURN ***

	int rc = *p;

	int r, c;

	int const n = p - map;
	r0 = n / nc;
	c0 = n % nc;

	c = c0;
	while (c < nc && *p == rc)
		{
		c++;
		p++;
		}
	cN = c - 1;

	int const num_cols = (c - c0);

	r = r0;
	do	{
		p -= num_cols;
		memset( p, 0, num_cols * sizeof(*p) );
		if (++r >= nr)
			break;
		p += nc;
		plim = p + num_cols;
		while (p < plim && *p == rc)
			p++;
		}
	while (p >= plim);

	rN = r - 1;

	return rc;
	}


//=============================================================================
// IMPLEMENTATION
//=============================================================================
@implementation MiscTableView

//-----------------------------------------------------------------------------
// - initFrame:
//
//		NOTE *1*: Default behavior is to take keyboard-cursor information from
//				the row-border.
//-----------------------------------------------------------------------------
- (id)initFrame:(NXRect const*)frameRect
		scroll:(MiscTableScroll*)i_scroll
		colInfo:(MiscTableBorder*)i_col_border
		rowInfo:(MiscTableBorder*)i_row_border
	{
	NXZone* const z = [self zone];
	NXRect rect;
	if (frameRect != 0)
		rect.origin = frameRect->origin;
	else
		{
		rect.origin.x = 0;
		rect.origin.y = 0;
		}

	rect.size.width = i_col_border->totalSize();
	rect.size.height = i_row_border->totalSize();

	[super initFrame:&rect];
	[self setFlipped:YES];
	[self setClipping:NO];
	[self setOpaque:YES];

	scroll = i_scroll;
	col_border = i_col_border;
	row_border = i_row_border;
	tracker_border = MISC_ROW_BORDER;					// NOTE *1*

	oldColSel = new( NXZoneMalloc(z,sizeof(*oldColSel)) ) MiscSparseSet;
	oldRowSel = new( NXZoneMalloc(z,sizeof(*oldRowSel)) ) MiscSparseSet;
	[self setSelectionMode:[scroll selectionMode]];

	inhibitCursor = 0;
	cursorSlot = -1;
	return self;
	}


//-----------------------------------------------------------------------------
// - free
//-----------------------------------------------------------------------------
- (id)free
	{
	[scroll abortEditing];
	[tracker free];
	NXZone* const z = [self zone];
	if (oldColSel != 0)
		{
		oldColSel->MiscSparseSet::~MiscSparseSet();
		NXZoneFree( z, oldColSel );
		}
	if (oldRowSel != 0)
		{
		oldRowSel->MiscSparseSet::~MiscSparseSet();
		NXZoneFree( z, oldRowSel );
		}
	return [super free];
	}


//-----------------------------------------------------------------------------
// - isOpaque
//-----------------------------------------------------------------------------
- (BOOL)isOpaque
	{
	return YES;
	}


//-----------------------------------------------------------------------------
// - acceptsFirstMouse
//-----------------------------------------------------------------------------
- (BOOL)acceptsFirstMouse
	{
	return YES;
	}


//-----------------------------------------------------------------------------
// - acceptsFirstResponder
//-----------------------------------------------------------------------------
- (BOOL)acceptsFirstResponder
	{
	return YES;
	}


//-----------------------------------------------------------------------------
// - adjustSize
//-----------------------------------------------------------------------------
- (void)adjustSize
	{
	[self sizeTo:col_border->totalSize() :row_border->totalSize()];
	}


//=============================================================================
// TYPE VARIATIONS
//=============================================================================

- (MiscBorderType)otherBorderType:(MiscBorderType)b
		{ return (b == MISC_ROW_BORDER ? MISC_COL_BORDER : MISC_ROW_BORDER); }
- (MiscTableBorder*)borderFor:(MiscBorderType)b
		{ return (b == MISC_ROW_BORDER ? row_border : col_border); }
- (MiscTableBorder*)otherBorder:(MiscBorderType)b
		{ return [self borderFor:[self otherBorderType:b]]; }


//=============================================================================
// FRAMES
//=============================================================================
//-----------------------------------------------------------------------------
// - getCellFrame:at:: -- Physical coords
//-----------------------------------------------------------------------------
- (id)getCellFrame:(NXRect*)r at:(MiscCoord_P)row :(MiscCoord_P)col
	{
	if (row < 0 || row >= row_border->count() ||
		col < 0 || col >= col_border->count())
		NXSetRect( r, 0, 0, 0, 0 );
	else
		{
		MiscCoord_V v_row = row_border->physicalToVisual( row );
		MiscCoord_V v_col = col_border->physicalToVisual( col );
		NXSetRect( r,
				col_border->getOffset(v_col),
				row_border->getOffset(v_row),
				col_border->effectiveSize(v_col),
				row_border->effectiveSize(v_row) );
		}
	return self;
	}


//-----------------------------------------------------------------------------
// - getCellInside:at:: -- Actual cell frame inside border lines
//-----------------------------------------------------------------------------
- (void)getCellInside:(NXRect*)r at:(MiscCoord_P)row :(MiscCoord_P)col
	{
	[self getCellFrame:r at:row:col];
	if (!NXEmptyRect(r))
		{
		r->size.width--;
		r->size.height--;
		}
	}


//-----------------------------------------------------------------------------
// - getSlotFrame:at:from: -- Physical coords
//-----------------------------------------------------------------------------
- (void)getSlotFrame:(NXRect*)r at:(MiscCoord_P)pslot from:(MiscBorderType)bdr
	{
	MiscTableBorder* const b = [self borderFor:bdr];
	if (pslot < 0 || pslot >= b->count())
		NXSetRect( r, 0, 0, 0, 0 );
	else
		{
		MiscCoord_V vslot = b->physicalToVisual( pslot );
		MiscRect_O ro( bdr, bounds );
		ro.setX_O( b->getOffset(vslot) );
		ro.setWidth_O( b->effectiveSize(vslot) );
		*r = ro;
		}
	}


//-----------------------------------------------------------------------------
// - getSlotInside:at:from: -- Physical coords
//-----------------------------------------------------------------------------
- (void)getSlotInside:(NXRect*)r at:(MiscCoord_P)slot from:(MiscBorderType)bdr
	{
	[self getSlotFrame:r at:slot from:bdr];
	if (!NXEmptyRect(r))
		{
		r->size.width--;
		r->size.height--;
		}
	}


//-----------------------------------------------------------------------------
// - getRow:andCol:forPoint:
//-----------------------------------------------------------------------------
- (id)getRow:(int*)row andCol:(int*)col forPoint:(NXPoint const*)pt
	{
	MiscCoord_V const v_row = row_border->visualForOffset( (MiscPixels)pt->y );
	MiscCoord_V const v_col = col_border->visualForOffset( (MiscPixels)pt->x );
	*row = row_border->visualToPhysical( v_row );
	*col = col_border->visualToPhysical( v_col );
	return (NXPointInRect( pt, &bounds ) ? self : 0);
	}


//=============================================================================
// VISIBLE / SCROLLING STUFF
//=============================================================================
//-----------------------------------------------------------------------------
// - firstVisibleSlot: -- Physical coord
//-----------------------------------------------------------------------------
- (int)firstVisibleSlot:(MiscBorderType)bdr
	{
	MiscCoord_P ret = -1;
	MiscTableBorder* const b = [self borderFor:bdr];
	int const lim = b->count();
	if (lim > 0)
		{
		NXRect nxr; [self getVisibleRect:&nxr];
		MiscRect_O r( bdr, nxr );
		MiscPixels const vorg = r.getX_O();
		MiscPixels const vlim = r.getMaxX_O();
		MiscCoord_V v = b->visualForOffset( vorg );
		if (v < lim - 1 && b->getOffset(v) < vorg && b->getOffset(v+1) < vlim)
			v++;				// First slot whose leading edge is visible
		ret = b->visualToPhysical( v );
		}
	return ret;
	}


//-----------------------------------------------------------------------------
// - lastVisibleSlot: -- Physical coord
//-----------------------------------------------------------------------------
- (int)lastVisibleSlot:(MiscBorderType)bdr
	{
	MiscCoord_P ret = -1;
	MiscTableBorder* const b = [self borderFor:bdr];
	if (b->count() > 0)
		{
		NXRect nxr; [self getVisibleRect:&nxr];
		MiscRect_O r( bdr, nxr );
		MiscPixels const vorg = r.getX_O();
		MiscPixels const vlim = r.getMaxX_O();
		MiscCoord_V v = b->visualForOffset( vlim );
		if (v > 0 && b->getOffset(v) + b->effectiveSize(v) > vlim &&
					 b->getOffset(v-1) + b->effectiveSize(v-1) > vorg)
			v--;				// Last slot whose trailing edge is visible
		ret = b->visualToPhysical( v );
		}
	return ret;
	}


//-----------------------------------------------------------------------------
// - numVisibleSlots:
//-----------------------------------------------------------------------------
- (int)numVisibleSlots:(MiscBorderType)bdr
	{
	MiscTableBorder* const b = [self borderFor:bdr];
	if (b->count() > 0)
		{
		NXRect nxr; [self getVisibleRect:&nxr];
		MiscRect_O r( bdr, nxr );
		MiscPixels const vorg = r.getX_O();
		MiscPixels const vlim = r.getMaxX_O();
		MiscCoord_V const first = b->visualForOffset( vorg );
		MiscCoord_V const last	= b->visualForOffset( vlim );
		return last - first + 1;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - border:slotIsVisible: -- Physical coord
//-----------------------------------------------------------------------------
- (BOOL)border:(MiscBorderType)bdr slotIsVisible:(int)n
	{
	MiscTableBorder* const b = [self borderFor:bdr];
	if (b->count() > 0)
		{
		MiscCoord_V const x = b->physicalToVisual(n);
		MiscPixels const xorg = b->getOffset(x);
		MiscPixels const xlim = xorg + b->effectiveSize(x);
		NXRect nxr; [self getVisibleRect:&nxr];
		MiscRect_O r( bdr, nxr );
		MiscPixels const vorg = r.getX_O();
		MiscPixels const vlim = r.getMaxX_O();
		return xorg <= vlim && xlim >= vorg;
		}
	return NO;
	}


//-----------------------------------------------------------------------------
// - border:setFirstVisibleSlot: -- Physical coord
//-----------------------------------------------------------------------------
- (void)border:(MiscBorderType)bdr setFirstVisibleSlot:(int)n
	{
	MiscTableBorder* const b = [self borderFor:bdr];
	int const num_slots = b->count();
	if (0 <= n && n < num_slots)
		{
		MiscCoord_V const x = b->physicalToVisual(n);
		MiscPixels const xorg = b->getOffset(x);
		NXRect nxr; [[self superview] getBounds:&nxr];
		MiscRect_O r( bdr, nxr );
		r.setX_O( xorg );
		NXPoint pt = r;
		[self scrollPoint:&pt];
		}
	}


//-----------------------------------------------------------------------------
// - border:setLastVisibleSlot: -- Physical coord
//-----------------------------------------------------------------------------
- (void)border:(MiscBorderType)bdr setLastVisibleSlot:(int)n
	{
	MiscTableBorder* const b = [self borderFor:bdr];
	int const num_slots = b->count();
	if (0 <= n && n < num_slots)
		{
		NXRect nxr; [self getVisibleRect:&nxr];
		MiscPixels const vwidth = MiscRect_O( bdr, nxr ).getWidth_O();
		MiscCoord_V const x = b->physicalToVisual(n);
		MiscPixels const vorg = b->getOffset(x) + b->effectiveSize(x) - vwidth;
		[[self superview] getBounds:&nxr];
		MiscRect_O r( bdr, nxr );
		r.setX_O( vorg );
		NXPoint pt = r;
		[self scrollPoint:&pt];
		}
	}


//=============================================================================
// CURSOR DRAWING
//=============================================================================
- (BOOL)isCursorEnabled { return (inhibitCursor == 0); }
- (void)disableCursor	{ inhibitCursor++; }
- (void)reenableCursor	{ inhibitCursor--; }


//-----------------------------------------------------------------------------
// - shouldDrawCursor
//-----------------------------------------------------------------------------
- (BOOL)shouldDrawCursor
	{
	Window* w = [self window];
	return ([self isCursorEnabled] && [self canDraw] && [w isKeyWindow] &&
				[w firstResponder] == self);
	}


//-----------------------------------------------------------------------------
// - getCursorSlot
//-----------------------------------------------------------------------------
- (MiscCoord_V)getCursorSlot
	{
	MiscTableBorder* const b = [self borderFor:tracker_border];
	MiscCoord_V vslot = b->getCursor();
	int const lim = b->count();
	if (lim > 0 && (vslot < 0 || vslot >= lim))
		{
		vslot = b->selectedSlot();
		if (vslot < 0)
			vslot = b->physicalToVisual([self firstVisibleSlot:tracker_border]);
		MiscAssert( 0 <= vslot );
		MiscAssert( vslot < lim );
		b->setCursor( vslot );
		}
	return vslot;
	}


//-----------------------------------------------------------------------------
// - getCursorFrame:
//-----------------------------------------------------------------------------
- (NXRect)getCursorFrame:(NXRect const*)clip
	{
	NXRect ret = {{ 0,0 }, { 0,0 }};
	if (row_border->count() > 0 && col_border->count() > 0)
		{
		MiscTableBorder* const b = [self borderFor:tracker_border];
		MiscCoord_P const pslot = b->visualToPhysical( [self getCursorSlot] );
		if (pslot >= 0)
			{
			NXRect r; [self getSlotInside:&r at:pslot from:tracker_border];
			if (NXIntersectsRect( &r, clip ))
				ret = r;
			}
		}
	return ret;
	}


//-----------------------------------------------------------------------------
// - getCursorEdges:clipTo:
//
//		Compute rectangles for drawing which make up the edges of the "focus
//		frame".	 Each edge is composed of values from the focus frame,
//		possibly including its: X, Y, MAXX, MAXY.
//-----------------------------------------------------------------------------
- (int)getCursorEdges:(NXRect*)edges clipTo:(NXRect const*)clip
	{
	struct TV_Lines { char xs, maxxs, xc, ys, maxys, yc, ws, wc, hs, hc; };
	static TV_Lines const LINES[ NUM_EDGES ] =
		{
			{ 1, 0,	 0, 1, 0,  0, 1,  0, 0,	 1 }, // top
			{ 1, 0,	 0, 0, 1, -1, 1,  0, 0,	 1 }, // bottom
			{ 1, 0,	 0, 1, 0,  1, 0,  1, 1, -2 }, // left
			{ 0, 1, -1, 1, 0,  1, 0,  1, 1, -2 }, // right
		};

	NXRect* edge = edges;
	NXRect r = [self getCursorFrame:clip];
	if (!NXEmptyRect( &r ))
		{
		for (int i = 0; i < NUM_EDGES; i++)
			{
			TV_Lines const& l = LINES[i];
			NXSetRect( edge, 
					l.xs * NX_X(&r) + l.maxxs * NX_MAXX(&r) + l.xc,
					l.ys * NX_Y(&r) + l.maxys * NX_MAXY(&r) + l.yc,
					l.ws * NX_WIDTH(&r) + l.wc,
					l.hs * NX_HEIGHT(&r) + l.hc );
			if (NXIntersectionRect( clip, edge ) != 0)
				edge++;
			}
		}
	return (edge - edges);
	}


//-----------------------------------------------------------------------------
// - getCursorCoords:clipTo:
//-----------------------------------------------------------------------------
- (int)getCursorCoords:(NXCoord*)coords clipTo:(NXRect const*)in_clip
	{
	int num_coords = 0;

	NXRect clip;
	if (in_clip == 0)
		[self getVisibleRect:&clip];
	else
		clip = *in_clip;

	NXRect edges[ NUM_EDGES ];
	int const num_edges = [self getCursorEdges:edges clipTo:&clip];
	if (num_edges > 0)
		{
		NXCoord* p = coords;
		for (int i = 0; i < num_edges; i++)
			{
			NXRect* edge = &edges[i];
			*p++ = NX_X( edge );
			*p++ = NX_Y( edge );
			*p++ = NX_WIDTH( edge );
			*p++ = NX_HEIGHT( edge );
			}
		num_coords = num_edges * NUM_COORDS;
		}
	return num_coords;
	}


//-----------------------------------------------------------------------------
// - drawCursorCoords:count:
//-----------------------------------------------------------------------------
- (void)drawCursorCoords:(NXCoord const*)coords count:(int)n
	{
	init_pswrap();
	BOOL needsFocus = ![self isFocusView];
	if (needsFocus) [self lockFocus];
	MISC_TV_dashedrects( coords, n );
	if (needsFocus) [self unlockFocus];
	}


//-----------------------------------------------------------------------------
// - drawCursorClipTo:
//-----------------------------------------------------------------------------
- (void)drawCursorClipTo:(NXRect const*)clip
	{
	if ([self isCursorEnabled])
		{
		NXCoord coords[ MAX_COORDS ];
		int const num_coords = [self getCursorCoords:coords clipTo:clip];
		if (num_coords > 0)
			{
			[self drawCursorCoords:coords count:num_coords];
			cursorSlot = [self borderFor:tracker_border]->getCursor();
			}
		}
	}


//-----------------------------------------------------------------------------
// - drawCursor
//-----------------------------------------------------------------------------
- (void)drawCursor
	{
	[self drawCursorClipTo:0];
	}


//-----------------------------------------------------------------------------
// - eraseCursor
//-----------------------------------------------------------------------------
- (void)eraseCursor
	{
	if ([self isCursorEnabled])
		{
		MiscTableBorder* const b = [self borderFor:tracker_border];
		if (cursorSlot >= 0 && cursorSlot < b->count())
			{
			[self disableCursor];

			MiscCoord_P s = b->visualToPhysical( cursorSlot );
			if (tracker_border == MISC_COL_BORDER)
				[self drawCol:s];
			else
				[self drawRow:s];

			[self reenableCursor];
			cursorSlot = -1;
			}
		}
	}


//-----------------------------------------------------------------------------
// - becomeFirstResponder
//-----------------------------------------------------------------------------
- (id)becomeFirstResponder
	{
	Window* win = [self window];
	if (win && [win isKeyWindow])
		{
		if ([self shouldDrawCursor])
			{
			[self drawCursor];
			[win flushWindow];
			}
		[[FontManager new] setSelFont:[scroll font] isMultiple:NO];
		}
	return self;
	}


//-----------------------------------------------------------------------------
// - resignFirstResponder
//-----------------------------------------------------------------------------
- (id)resignFirstResponder
	{
	[self eraseCursor];
	[[self window] flushWindow];
	return self;
	}


//-----------------------------------------------------------------------------
// - becomeKeyWindow
//-----------------------------------------------------------------------------
- (id)becomeKeyWindow
	{
	if ([self shouldDrawCursor])
		{
		[self drawCursor];
		[[self window] flushWindow];
		}
	[[FontManager new] setSelFont:[scroll font] isMultiple:NO];
	return self;
	}


//-----------------------------------------------------------------------------
// - resignKeyWindow
//-----------------------------------------------------------------------------
- (id)resignKeyWindow
	{
	[self eraseCursor];
	[[self window] flushWindow];
	return self;
	}


//=============================================================================
// SCROLLING
//=============================================================================
//-----------------------------------------------------------------------------
// - scrollCellToVisible:: -- Physical coords
//-----------------------------------------------------------------------------
- (void)scrollCellToVisible:(int)row :(int)col
	{
	NXRect r;
	[self getCellFrame:&r at:row:col];
	[self scrollRectToVisible:&r];
	}


//-----------------------------------------------------------------------------
// - border:scrollToVisible: -- Physical coord
//-----------------------------------------------------------------------------
- (void)border:(MiscBorderType)bdr scrollToVisible:(MiscCoord_P)pslot
	{
	MiscTableBorder* const b = [self borderFor:bdr];
	MiscCoord_V const vslot = b->physicalToVisual( pslot );
	NXRect v; [self getVisibleRect:&v];
	MiscRect_O r( bdr, v );
	r.setX_O( b->getOffset(vslot) );
	r.setWidth_O( b->effectiveSize(vslot) );
	NXRect nxr = r;
	[self scrollRectToVisible:&nxr];
	}


//-----------------------------------------------------------------------------
// - scrollRowToVisible: -- Physical coord
//-----------------------------------------------------------------------------
- (void)scrollRowToVisible:(int)row
	{
	[self border:MISC_ROW_BORDER scrollToVisible:row];
	}


//-----------------------------------------------------------------------------
// - scrollColToVisible: -- Physical coord
//-----------------------------------------------------------------------------
- (void)scrollColToVisible:(int)col
	{
	[self border:MISC_COL_BORDER scrollToVisible:col];
	}


//=============================================================================
// DRAWING
//=============================================================================
//-----------------------------------------------------------------------------
// - cMin:cMax:rMin:rMax:forRect:
//-----------------------------------------------------------------------------
- (void)cMin:(MiscCoord_V*)cmin cMax:(MiscCoord_V*)cmax
		rMin:(MiscCoord_V*)rmin rMax:(MiscCoord_V*)rmax
		forRect:(NXRect const*)nxrect
	{
	*cmin = col_border->visualForOffset( (MiscPixels) nxrect->origin.x );
	*cmax = col_border->visualForOffset(
				(MiscPixels) (nxrect->origin.x + nxrect->size.width) - 1 );
	*rmin = row_border->visualForOffset( (MiscPixels) nxrect->origin.y );
	*rmax = row_border->visualForOffset(
				(MiscPixels) (nxrect->origin.y + nxrect->size.height) - 1 );
	}


//-----------------------------------------------------------------------------
// - drawRect:
//-----------------------------------------------------------------------------
- (void)drawRect:(NXRect const*)nxrect
	{
	MiscCoord_V c, cmin, cmax, c0, cN;
	MiscCoord_V r, rmin, rmax, r0, rN;
	[self cMin:&cmin cMax:&cmax rMin:&rmin rMax:&rmax forRect:nxrect];

	if (cmin >= 0 && cmax >= 0 && rmin >= 0 && rmax >= 0)
		{
		MiscColorList cl;

		int const NORM_COLOR = cl.store([scroll backgroundColor]) + 1;
		int const HIGH_COLOR = cl.store([scroll highlightBackgroundColor]) + 1;

		int const nc = (cmax - cmin) + 1;
		int const nr = (rmax - rmin) + 1;
		int* map = (int*) calloc( nc * nr, sizeof(*map) );
		int* p = map;

		for (r = rmin; r <= rmax; r++)	// Transparent and ownerDraw
			{							// cell backgrounds.
			MiscCoord_P const pr = row_border->visualToPhysical(r);
			BOOL const row_lit = row_border->isSelected(r);
			for (c = cmin; c <= cmax; c++,p++)
				{
				MiscCoord_P const pc = col_border->visualToPhysical(c);
				BOOL const lit = row_lit || col_border->isSelected(c);
				id cell = [scroll cellAt:pr:pc];
				if (cell == 0 || ![cell isOpaque])
					*p = lit ? HIGH_COLOR : NORM_COLOR;
				else if ([cell respondsTo:@selector(ownerDraw)] &&
						[cell ownerDraw])
					{
					if (lit && [cell respondsTo:
								@selector(highlightBackgroundColor)])
						*p = cl.store( [cell highlightBackgroundColor] ) + 1;
					else if ([cell respondsTo:@selector(backgroundColor)])
						*p = cl.store( [cell backgroundColor] ) + 1;
					else
						*p = lit ? HIGH_COLOR : NORM_COLOR;
					}
				}
			}

		MiscRectColorList rcl;
		NXRect rect;
		int color;
		while ((color = extract_rect( map, nc, nr, c0, r0, cN, rN )) != 0)
			{
			c0 += cmin; cN += cmin;
			r0 += rmin; rN += rmin;
			MiscPixels const x0 = col_border->getOffset( c0 );
			MiscPixels const y0 = row_border->getOffset( r0 );
			MiscPixels const xN = col_border->getOffset( cN ) +
								col_border->effectiveSize( cN );
			MiscPixels const yN = row_border->getOffset( rN ) +
								row_border->effectiveSize( rN );
			rect.origin.x = x0;
			rect.origin.y = y0;
			rect.size.width = (xN - x0);
			rect.size.height = (yN - y0);

			rcl.append( rect, cl[ color - 1 ] );
			}

		free( map );

		MiscPixels x,y;
		MiscPixels const ix0 = col_border->getOffset( cmin );
		MiscPixels const iy0 = row_border->getOffset( rmin );
		MiscPixels const ixN = col_border->getOffset( cmax ) +
								col_border->effectiveSize( cmax );
		MiscPixels const iyN = row_border->getOffset( rmax ) +
								row_border->effectiveSize( rmax );

		MiscRectList grid_rl;			// Grid drawing list.
		y = iy0;						// Horizontal grid lines.
		rect.origin.x	 = floor( (float) ix0 );
		rect.size.width	 = floor( (float) (ixN - ix0) );
		rect.size.height = 1;
		for (r = rmin; r <= rmax; r++)
			{
			MiscPixels const h = row_border->effectiveSize(r);
			y += h - 1;
			rect.origin.y = floor( (float) y );
			grid_rl.append( rect );
			y++;
			}

		x = ix0;						// Vertical grid lines.
		rect.origin.y	 = floor( (float) iy0 );
		rect.size.width	 = 1;
		rect.size.height = floor( (float) (iyN - iy0) );
		for (c = cmin; c <= cmax; c++)
			{
			MiscPixels const w = col_border->effectiveSize(c);
			x += w - 1;
			rect.origin.x = floor( (float) x );
			grid_rl.append( rect );
			x++;
			}

		rcl.draw();
		grid_rl.draw( NX_COLORGRAY );

		NXColor const fgColor = [scroll textColor];
		NXColor const hfgColor = [scroll highlightTextColor];
		Font* const fnt = [scroll font];
		MiscDrawList dl;

		y = iy0;						// Cell contents.
		for (r = rmin; r <= rmax; r++)
			{
			MiscCoord_P const pr = row_border->visualToPhysical(r);
			BOOL const row_lit = row_border->isSelected(r);
			MiscPixels const h = row_border->effectiveSize(r);
			rect.origin.y	 = floor( (float) y );
			rect.size.height = floor( (float) (h - 1) );
			x = ix0;
			for (c = cmin; c <= cmax; c++)
				{
				MiscCoord_P const pc = col_border->visualToPhysical(c);
				BOOL const lit = row_lit || col_border->isSelected(c);
				MiscPixels const w = col_border->effectiveSize(c);
				rect.origin.x	= x;
				rect.size.width = (w - 1);
				id cell = [scroll cellAt:pr:pc];
				if (cell == 0 ||
					[cell respondsTo:@selector(ownerDraw)] && [cell ownerDraw])
					{
					dl.append( rect, cell, lit,
								(lit ? hfgColor : fgColor), fnt );
					}
				else
					[cell drawSelf:&rect inView:self];
				x += w;
				}
			y += h;
			}

		dl.draw();
		if ([self shouldDrawCursor])
			{
			NXRect clip = {{ ix0, iy0 },{ ixN - ix0, iyN - iy0 }};
			[self drawCursorClipTo:&clip];
			}
		}
	}


//-----------------------------------------------------------------------------
// - drawSelf::
//-----------------------------------------------------------------------------
- (id)drawSelf:(NXRect const*)rects :(int)nrects
	{
	BOOL const need_clip = ([subviews count] != 0);
	
	if (need_clip)
		{
		PSgsave();
		NXRectClip( rects );
		}

	if (nrects == 1)
		[self drawRect:rects];
	else if (nrects == 3)
		{
		[self drawRect:++rects];
		[self drawRect:++rects];
		}

	if (need_clip)
		PSgrestore();

	return self;
	}


//-----------------------------------------------------------------------------
// - drawCellAt:: -- Physical coords
//-----------------------------------------------------------------------------
- (id)drawCellAt:(int)row :(int)col
	{
	NXRect r;
	[self getCellFrame:&r at:row:col];
	[self drawSelf:&r:1];
	return self;
	}


//-----------------------------------------------------------------------------
// - drawRow: -- Physical coord
//-----------------------------------------------------------------------------
- (id)drawRow:(int)row
	{
	if ([self canDraw] && row >= 0 && row < row_border->count())
		{
		MiscCoord_V const vRow	 = row_border->physicalToVisual(row);
		MiscPixels	const offset = row_border->getOffset(vRow);
		MiscPixels	const size	 = row_border->effectiveSize(vRow);

		NXRect r;
		[self getVisibleRect:&r];
		if (offset + size >= NX_Y(&r) && offset < NX_MAXY(&r))
			{
			r.origin.y = row_border->getOffset(vRow);
			r.size.height = row_border->effectiveSize(vRow);
	
			BOOL const didLock = ![self isFocusView];
			if (didLock) [self lockFocus];
			[self drawRect:&r];
			if (didLock) [self unlockFocus];
			}
		}
	return self;
	}


//-----------------------------------------------------------------------------
// - drawCol: -- Physical coord
//-----------------------------------------------------------------------------
- (id)drawCol:(int)col
	{
	if ([self canDraw] && col >= 0 && col < col_border->count())
		{
		MiscCoord_V const vCol	 = col_border->physicalToVisual(col);
		MiscPixels	const offset = col_border->getOffset(vCol);
		MiscPixels	const size	 = col_border->effectiveSize(vCol);

		NXRect r;
		[self getVisibleRect:&r];
		if (offset + size >= NX_X(&r) && offset < NX_MAXX(&r))
			{
			r.origin.x = col_border->getOffset(vCol);
			r.size.width = col_border->effectiveSize(vCol);
	
			BOOL const didLock = ![self isFocusView];
			if (didLock) [self lockFocus];
			[self drawRect:&r];
			if (didLock) [self unlockFocus];
			}
		}
	return self;
	}


//=============================================================================
// SELECTION
//=============================================================================
//-----------------------------------------------------------------------------
// - setSelectionMode:
//-----------------------------------------------------------------------------
- (void)setSelectionMode:(MiscSelectionMode)mode
	{
	NXZone* const z = [self zone];
	if (tracker != 0)
		[tracker free];
	switch (mode)
		{
		case MISC_LIST_MODE:
			tracker = [MiscListTracker allocFromZone:z];
			break;
		case MISC_RADIO_MODE:
			tracker = [MiscRadioTracker allocFromZone:z];
			break;
		case MISC_HIGHLIGHT_MODE:
			tracker = [MiscHighlightTracker allocFromZone:z];
			break;
		}
	[tracker initBorder:[self borderFor:tracker_border]];
	}


//-----------------------------------------------------------------------------
// - selectionChanged
//-----------------------------------------------------------------------------
- (void)selectionChanged
	{
	MiscSparseSet const& newColSel = col_border->selectionSet();
	MiscSparseSet const& newRowSel = row_border->selectionSet();

	if ([self isAutodisplay] && [self canDraw])
		{
		MiscCoord_V c, cmin, cmax;
		MiscCoord_V r, rmin, rmax;
		NXRect vis;		[self getVisibleRect:&vis];
		[self cMin:&cmin cMax:&cmax rMin:&rmin rMax:&rmax forRect:&vis];

		int const nc = (cmax - cmin) + 1;
		int const nr = (rmax - rmin) + 1;

		int* const map = (int*) calloc( nc * nr, sizeof(*map) );
		int* pmap = map;

		for (r = rmin; r <= rmax; r++)
			{
			BOOL rowWasOn = oldRowSel->contains(r);
			BOOL rowIsOn = newRowSel.contains(r);
			for (c = cmin; c <= cmax; c++, pmap++)
				{
				BOOL wasOn = rowWasOn || oldColSel->contains(c);
				BOOL isOn = rowIsOn || newColSel.contains(c);
				if (isOn != wasOn)
					*pmap = 1;
				}
			}

		*oldColSel = newColSel;
		*oldRowSel = newRowSel;

		MiscCoord_V c0, cN;
		MiscCoord_V r0, rN;
		BOOL locked = NO;
		while (extract_rect( map, nc, nr, c0, r0, cN, rN ) != 0)
			{
			if (!locked)
				{
				locked = YES;
				[window disableFlushWindow];
				[self lockFocus];
				}
			c0 += cmin; cN += cmin;
			r0 += rmin; rN += rmin;
			MiscPixels const x0 = col_border->getOffset(c0);
			MiscPixels const y0 = row_border->getOffset(r0);
			MiscPixels const xN = col_border->getOffset(cN) +
									col_border->effectiveSize(cN);
			MiscPixels const yN = row_border->getOffset(rN) +
									row_border->effectiveSize(rN);
			NXRect v;
			v.origin.x = floor( (float) x0 );
			v.origin.y = floor( (float) y0 );
			v.size.width = floor( (float)( xN - x0 ) );
			v.size.height = floor( (float)( yN - y0 ) );
			[self drawSelf:&v:1];
			}

		if (locked)
			{
			[self unlockFocus];
			[window reenableFlushWindow];
			[window flushWindow];
			}

		free( map );
		}
	else
		{
		*oldColSel = newColSel;
		*oldRowSel = newRowSel;
		[self setNeedsDisplay:YES];
		}
	}


//-----------------------------------------------------------------------------
// - resetSelection
//-----------------------------------------------------------------------------
- (void)resetSelection
	{
	*oldColSel = col_border->selectionSet();
	*oldRowSel = row_border->selectionSet();
	}


//-----------------------------------------------------------------------------
// - trackBy:
//-----------------------------------------------------------------------------
- (void)trackBy:(MiscBorderType)b
	{
	tracker_border = b;
	}


//-----------------------------------------------------------------------------
// - trackingBy
//-----------------------------------------------------------------------------
- (MiscBorderType)trackingBy
	{
	return tracker_border;
	}


//-----------------------------------------------------------------------------
// - reflectCursor
//-----------------------------------------------------------------------------
- (void)reflectCursor
	{
	if ([self shouldDrawCursor])
		{
		Window* w = [self window];
		[w disableFlushWindow];
		[self eraseCursor];
		[self drawCursor];
		[w reenableFlushWindow];
		[w flushWindow];
		}
	}


//=============================================================================
// IMAGE DRAGGING
//=============================================================================
static inline float absval( float x ) { return (x < 0 ? -x : x ); }
static inline BOOL isSlop( NXEvent* e1, NXEvent* e2, float const slop )
	{
	return (absval(e1->location.x - e2->location.x) <= slop &&
			absval(e1->location.y - e2->location.y) <= slop);
	}


//-----------------------------------------------------------------------------
// draggingSourceOperationMaskForLocal:
//-----------------------------------------------------------------------------
- (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
	{
	id d = [scroll responsibleDelegate:DF::DEL_DRAG_OP_MASK];
	if (d != 0)
		return [d tableScroll:scroll draggingSourceOperationMaskForLocal:flag];
	return NX_DragOperationGeneric;
	}


//-----------------------------------------------------------------------------
// ignoreModifierKeysWhileDragging
//-----------------------------------------------------------------------------
- (BOOL)ignoreModifierKeysWhileDragging
	{
	id d = [scroll responsibleDelegate:DF::DEL_DRAG_IGNORE_MODIFIERS];
	if (d != 0)
		return [d tableScrollIgnoreModifierKeysWhileDragging:scroll];
	return YES;
	}


//-----------------------------------------------------------------------------
// getDragImageAt::
//-----------------------------------------------------------------------------
- (NXImage*)getDragImageAt:(MiscCoord_P)row :(MiscCoord_P)col
	{
	NXImage* i = 0;
	id d = [scroll responsibleDelegate:DF::DEL_IMAGE_FOR_DRAG];
	if (d != 0)
		i = [d tableScroll:scroll imageForDragOperationAt:row:col];
	if (i == 0)
		{
		id cell = [scroll cellAt:row:col];
		if ([cell type] == NX_ICONCELL)
			i = [cell image];
		}
	return i;
	}


//-----------------------------------------------------------------------------
// calcOrigin:andOffset:forImage:inRect:at::downEvent:dragEvent:
//
//		Compute the origin of the image and the offset of the mouse for
//		dragging via View's -dragImage:at:offset:... method.  There are two
//		cases:
//
//		CASE *1*: The image being dragged fits exactly in the Cell's image
//				area.  In this case drag from the point where the mouse
//				went down inside the image's boundry.  This give the visual
//				impression of lifting the image exactly from where it is
//				sitting -- without it jumping to some other location.
//		CASE *2*: The image is a different size than the Cell's image area.
//				In this case just center the image under the mouse.
//
//-----------------------------------------------------------------------------
- (void)calcOrigin:(NXPoint*)origin
	andOffset:(NXPoint*)offset
	forImage:(NXImage*)image
	inRect:(NXRect const*)rect
	at:(MiscCoord_P)row :(MiscCoord_P)col
	downEvent:(NXEvent*)downEvent
	dragEvent:(NXEvent*)dragEvent
	{
	offset->x = 0;
	offset->y = 0;

	NXSize imageSize;
	[image getSize:&imageSize];

	id cell = [scroll cellAt:row:col];
	NXRect iconRect = *rect;
	[cell getIconRect:&iconRect];

	if (imageSize.width	 == iconRect.size.width &&				// CASE *1*
		imageSize.height == iconRect.size.height)
		{
		*origin = iconRect.origin;
		if ([self isFlipped])
			origin->y += iconRect.size.height;

		if (dragEvent != 0 && dragEvent->type == NX_MOUSEDRAGGED)
			{
			offset->x = dragEvent->location.x - downEvent->location.x;
			offset->y = dragEvent->location.y - downEvent->location.y;
			}
		}
	else														// CASE *2*
		{
		*origin = downEvent->location;
		[self convertPoint:origin fromView:nil];
		origin->x -= floor( imageSize.width / 2 );
		origin->y -= floor( imageSize.height / 2 ) *
								([self isFlipped] ? -1 : 1);
		}
	}


//-----------------------------------------------------------------------------
// prepareDragPasteboard:at::
//-----------------------------------------------------------------------------
- (void)prepareDragPasteboard:(Pasteboard*)pb
	at:(MiscCoord_P)row :(MiscCoord_P)col
	{
	id d = [scroll responsibleDelegate:DF::DEL_PREPARE_PB_FOR_DRAG];
	if (d != 0)
		[d tableScroll:scroll preparePasteboard:pb forDragOperationAt:row:col];
	}


//-----------------------------------------------------------------------------
// performDrag:at::inRect:dragEvent:
//-----------------------------------------------------------------------------
- (BOOL)performDrag:(NXEvent*)mouseDown
	at:(MiscCoord_P)row :(MiscCoord_P)col
	inRect:(NXRect const*)r
	dragEvent:(NXEvent*)dragEvent
	{
	BOOL ret = NO;

	NXImage* i = [self getDragImageAt:row:col];
	if (i != 0)
		{
		NXPoint origin, offset;
		[self calcOrigin:&origin andOffset:&offset forImage:i inRect:r
				at:row:col downEvent:mouseDown dragEvent:dragEvent];
	
		Pasteboard* pb = [Pasteboard newName:NXDragPboard];
		[self prepareDragPasteboard:pb at:row:col];
	
		[self dragImage:i at:&origin offset:&offset
				event:mouseDown pasteboard:pb source:self slideBack:YES];
		ret = YES;
		}
	return ret;
	}


//-----------------------------------------------------------------------------
// - awaitDragEvent:at::
//-----------------------------------------------------------------------------
- (BOOL)awaitDragEvent:(NXEvent*)p
	at:(MiscCoord_P)row :(MiscCoord_P)col
	inRect:(NXRect const*)rect
	{
	float const DELAY = 0.25;
	float const SLOP = 4.0;
	int const WANTED = (NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK);

	BOOL ret = NO;
	NXEvent mouseDown = *p;
	NXEvent* event;
	NXEvent peeker;
	int old_mask = [[self window] addToEventMask:WANTED];

	do	{
		event = [NXApp peekNextEvent:WANTED into:&peeker waitFor:DELAY
				threshold:NX_MODALRESPTHRESHOLD];
		if (event != 0 && event->type == NX_MOUSEDRAGGED)
			event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK];
		} while (event != 0 && event->type == NX_MOUSEDRAGGED && 
				isSlop( event, &mouseDown, SLOP ));

	[[self window] setEventMask:old_mask];

	if (event == 0 || event->type == NX_MOUSEDRAGGED)
		ret = [self performDrag:&mouseDown at:row:col inRect:rect
						dragEvent:event];
	return ret;
	}


//-----------------------------------------------------------------------------
// - canPerformDrag:at::
//-----------------------------------------------------------------------------
- (BOOL)canPerformDrag:(NXEvent*)p at:(MiscCoord_P)r :(MiscCoord_P)c
	{
	id d = [scroll responsibleDelegate:DF::DEL_ALLOW_DRAG];
	return (d != 0 && [d tableScroll:scroll allowDragOperationAt:r:c] &&
			[scroll responsibleDelegate:DF::DEL_PREPARE_PB_FOR_DRAG] != 0 &&
		   ([[scroll cellAt:r:c] type] == NX_ICONCELL ||
			[scroll responsibleDelegate:DF::DEL_IMAGE_FOR_DRAG] != 0));
	}


//=============================================================================
// MOUSE TRACKING
//=============================================================================
//-----------------------------------------------------------------------------
// - constrainSlot:inBorder:
//-----------------------------------------------------------------------------
- (MiscCoord_V)constrainSlot:(MiscCoord_V)s inBorder:(MiscTableBorder*)b
	{
	if (s < 0)
		s = 0;
	else if (s >= b->count())
		s = b->count() - 1;
	return s;
	}


//-----------------------------------------------------------------------------
// - sendAction:to:
//
//		NOTE *1*: Conforms to Control's -sendAction:to: which does *not* send
//				any message if the "action" is null.
//-----------------------------------------------------------------------------
- (id)sendAction:(SEL)cmd to:(id)obj
	{
	if (cmd != 0)				// NOTE *1*
		return ([NXApp sendAction:cmd to:obj from:scroll] ? self : 0);
	return 0;
	}


//-----------------------------------------------------------------------------
// - setClickedCellFromEvent:
//-----------------------------------------------------------------------------
- (void)setClickedCellFromEvent:(NXEvent const*)e
	{
	NXPoint p = e->location;
	[self convertPoint:&p fromView:0];
	MiscCoord_V const v_row = (p.y < NX_MAXY(&bounds) ?
				row_border->visualForOffset( (MiscPixels)p.y ) : -1);
	MiscCoord_V const v_col = (p.x < NX_MAXX(&bounds) ?
				col_border->visualForOffset( (MiscPixels)p.x ) : -1);
	MiscCoord_P const p_row = row_border->visualToPhysical( v_row );
	MiscCoord_P const p_col = col_border->visualToPhysical( v_col );
	[scroll setClickedRow:p_row col:p_col];
	}


//-----------------------------------------------------------------------------
// - cellTrackMouse:at::
//-----------------------------------------------------------------------------
- (BOOL)cellTrackMouse:(NXEvent*)p at:(MiscCoord_V)v_row :(MiscCoord_V)v_col
	{
	BOOL upInCell = NO;

	MiscCoord_P const p_col = col_border->visualToPhysical( v_col );
	MiscCoord_P const p_row = row_border->visualToPhysical( v_row );

	NXRect rect;
	[self getCellInside:&rect at:p_row:p_col];

	id cell = [scroll cellAt:p_row:p_col];
	[scroll setClickedRow:p_row col:p_col];

	if ([scroll editIfAble:p at:p_row:p_col])
		upInCell = YES;
	else if ([self canPerformDrag:p at:p_row:p_col])
		upInCell = [self awaitDragEvent:p at:p_row:p_col inRect:&rect];
	else if ([cell isEnabled])
		{
		[self lockFocus];
		[scroll setTracking:YES];
		[cell setParameter:NX_CELLHIGHLIGHTED to:1];
		[scroll drawCellAt:p_row:p_col];
		[window flushWindow];
		upInCell = [cell trackMouse:p inRect:&rect ofView:self];
		[scroll setTracking:NO];
		[cell setParameter:NX_CELLHIGHLIGHTED to:0];
		[scroll drawCellAt:p_row:p_col];
		[self unlockFocus];
		[window flushWindow];
		}

	return upInCell;
	}


//-----------------------------------------------------------------------------
// - mouseDown:
//-----------------------------------------------------------------------------
- (id)mouseDown:(NXEvent*)p
	{
	int const WANTED = (NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK | NX_TIMERMASK);

	BOOL doubleClicked = (p->data.mouse.click > 1);
	NXPoint evpt = p->location;
	[self convertPoint:&evpt fromView:0];

	MiscCoord_V const v_col = col_border->visualForOffset((MiscPixels)evpt.x);
	MiscCoord_V const v_row = row_border->visualForOffset((MiscPixels)evpt.y);
	MiscCoord_V slot = (tracker_border == MISC_COL_BORDER ? v_col : v_row);
	MiscTableBorder* const b = [self borderFor:tracker_border];

	[self eraseCursor];
	[self disableCursor];

	BOOL const was_auto = [self isAutodisplay]; // For selection reflection.
	[self setAutodisplay:YES];

	[scroll selectText:self];
	[tracker mouseDown:p atPos:slot];
	[self otherBorder:tracker_border]->selectNone();
	[scroll selectionChanged];

	BOOL mouseUpInCell = NO;
	if ([scroll isEnabled])
		mouseUpInCell = [self cellTrackMouse:p at:v_row:v_col];

	if (!mouseUpInCell)
		{
		int old_mask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
		NXTrackingTimer* timer = 0;
		startTimer( timer );
		NXEvent lastEvent = *p;

		for (;;)
			{
			p = [NXApp getNextEvent:WANTED];
			if (p == 0 || p->type == NX_MOUSEUP)
				break;
			else if (p->type == NX_TIMER)
				[self autoscroll:&lastEvent];
			else
				lastEvent = *p;

			NXPoint new_loc = lastEvent.location;
			[self convertPoint:&new_loc fromView:0];
			MiscPixels const offset =
					MiscPoint_O( tracker_border, new_loc ).getX_O();
			MiscCoord_V const new_slot =
				(offset < MiscRect_O( tracker_border, bounds ).getMaxX_O() ?
				b->visualForOffset(offset) : b->count());
			if (new_slot != slot)
				{
				slot = new_slot;
				[tracker mouseDragged:p atPos:slot];
				[scroll selectionChanged];
				}
			}

		stopTimer( timer );
		[window setEventMask:old_mask];
		[self setClickedCellFromEvent:p];
		}

	[tracker mouseUp:p atPos:slot];
	[scroll selectionChanged];
	[scroll border:tracker_border setCursor:
				b->visualToPhysical([self constrainSlot:slot inBorder:b])];

	[self reenableCursor];
	if ([self shouldDrawCursor])
		[self drawCursor];
	[self setAutodisplay:was_auto];
	[window flushWindow];

	if ([scroll isEnabled])
		{
		[scroll sendAction];
		if (doubleClicked && ![scroll isEditing])		// FIXME: not right
			[scroll sendDoubleAction];
		}

	return self;
	}


//-----------------------------------------------------------------------------
// - moveCursorBy:
//-----------------------------------------------------------------------------
- (void)moveCursorBy:(int)delta
	{
	MiscTableBorder* const b = [self borderFor:tracker_border];
	int const lim = b->count();
	if (lim > 0)
		{
		MiscCoord_V slot = b->getCursor() + delta;
		if (slot < 0)
			slot = lim - 1;
		else if (slot >= lim)
			slot = 0;
		b->setCursor( slot );

		Window* const w = [self window];
		[w disableFlushWindow];
		[self reflectCursor];
		[self border:tracker_border scrollToVisible:b->visualToPhysical(slot)];
		[w reenableFlushWindow];
		[w flushWindow];
		}
	}


//-----------------------------------------------------------------------------
// - keyboardSelect:
//-----------------------------------------------------------------------------
- (void)keyboardSelect:(NXEvent const*)p
	{
	[tracker mouseDown:p atPos:[self borderFor:tracker_border]->getCursor()];
	}

@end
