/* Ranker.m
 * Written By:  Thomas Burkholder
 *
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 */

#import "Ranker.h"

#define BOUND(__a,__low,__high) MIN(MAX((__a),(__low)),(__high))

@implementation Ranker

static BOOL dragging;
static int rowDragged;
static NXPoint currentMouse;

- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];
	dragging = NO;
	rankMode = YES;
	[self setMode:NX_TRACKMODE];
	return self;
}

- setRankMode:(BOOL)yn
{
	rankMode = yn;
	return self;
}

- (BOOL)rankMode
{
	return rankMode;
}

- exchangeRow:(int)source with:(int)dest
{
	int i;
	id a,b;

	for(i=0;(i<numCols);i++) {
		a = [self cellAt:source :i];
		b = [self cellAt:dest :i];
		[self putCell:a at:dest :i];
		[self putCell:b at:source :i];
	}
	return self;
}

- mouseDown:(NXEvent *)theEvent
{
	NXRect r, bds;
	int col, rowDropped;
	float diff, offset;
	NXEvent *ev;

	if (!rankMode || (!(theEvent->flags & NX_CONTROLMASK)))
		return [super mouseDown:theEvent];

	[window getMouseLocation:&currentMouse];
	[self convertPoint:&currentMouse fromView:nil];

	[self getRow:&rowDragged andCol:&col forPoint:&currentMouse];
	if (rowDragged == -1) return [super mouseDown:theEvent];

	[window addToEventMask:NX_MOUSEDRAGGEDMASK];
	[self getCellFrame:&r at:rowDragged :col];
	offset = currentMouse.y - r.origin.y;
	currentMouse.y -= offset;

	[window makeFirstResponder:window];
	dragging = YES;
	for(ev = theEvent;(ev->type!=NX_LMOUSEUP);) {
		if (([self getVisibleRect:&r])&&
					([[superview superview] isKindOf:[ScrollView class]])) {
			diff = (currentMouse.y+cellSize.height) -
												(r.size.height+r.origin.y);
			if  (diff > 0) {
				[superview getBounds:&bds];
				bds.origin.y += diff;
				[superview rawScroll:&(bds.origin)];
				[[superview superview] reflectScroll:superview];
			} else {
				diff = (r.origin.y-currentMouse.y);
				if  (diff > 0) {
					[superview getBounds:&bds];
					bds.origin.y -= diff;
					[superview rawScroll:&(bds.origin)];
					[[superview superview] reflectScroll:superview];
				}
			}
		}

		[self displayFromOpaqueAncestor:&r :1 :YES];

		ev = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
		[window getMouseLocation:&currentMouse];
		[self convertPoint:&currentMouse fromView:nil];
		currentMouse.y -= offset;
		currentMouse.y = BOUND(currentMouse.y, bounds.origin.y,
						(bounds.origin.y+bounds.size.height-cellSize.height));
	}
	dragging = NO;
	[window removeFromEventMask:NX_MOUSEDRAGGEDMASK];

	diff = currentMouse.y;
	currentMouse.y += (cellSize.height/2);
	if (![self getRow:&rowDropped andCol:&col forPoint:&currentMouse]) {
		currentMouse.y += (cellSize.height/4);
		if (![self getRow:&rowDropped andCol:&col forPoint:&currentMouse]) {
			NXLogError("Ranker:  Couldn't drop row.\n");
			return [self display];
		}
	}

	[window disableFlushWindow];
	if ((rowDropped==(numRows-1))&&(diff+cellSize.height>= bounds.size.height))
		rowDropped++;
	[self insertRowAt:rowDropped];
	if (rowDropped < rowDragged)
		rowDragged++;
	if (selectedRow >= rowDropped)
		selectedRow++;
	if (selectedRow == rowDragged)
		selectedRow = rowDropped;
	[self exchangeRow:rowDragged with:rowDropped];
	[self removeRowAt:rowDragged andFree:YES];
	[self display];
	[window reenableFlushWindow];
	[window flushWindow];
	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	NXRect r;
	int i;

	if (!dragging)
		return [super drawSelf:rects :rectCount];

	// draw ordinary matrix
	[super drawSelf:rects :rectCount];

	PSgsave();
	// draw well
	PSsetgray([window backgroundGray]);
	for(i=0;(i<numCols);i++) {
		[self getCellFrame:&r at:rowDragged :i];
		NXRectFill(&r);
	}
	PSgrestore();

	// draw dragging row
	for(i=0;(i<numCols);i++) {
		[self getCellFrame:&r at:rowDragged :i];
		r.origin.y = currentMouse.y;
		[[self cellAt:rowDragged :i] drawSelf:&r inView:self];
	}
	return self;
}

- read:(NXTypedStream *)stream
{
	[super read:stream];
	NXReadTypes(stream,"i",&rankMode);
	return self;
}

- write:(NXTypedStream *)stream
{
	[super write:stream];
	NXWriteTypes(stream,"i",&rankMode);
	return self;
}

@end
