/* Generated by Interface Builder */

#import "MoveMatrix.h"
#import "MyCell.h"
#import <appkit/Cell.h>
#import <appkit/TextFieldCell.h>
#import <appkit/SelectionCell.h>
#import <appkit/MenuCell.h>
#import <appkit/defaults.h>
#import <appkit/nextstd.h>
#import <appkit/tiff.h>
#import <appkit/timer.h>
#import <dpsclient/wraps.h>

@implementation MoveMatrix

- initFrame: (const NXRect *) frameRect 
{
	[self setCellClass:[ButtonCell class]];
	self = [super initFrame: frameRect];
	[self setBackgroundGray:0.5];
	return self;
}

- initFrame: (const NXRect *) frameRect mode: (int) mode prototype: anObject
	numRows: (int) nRows numCols: (int) nCols
{
	self = [super initFrame: frameRect mode: mode prototype: anObject
			numRows: nRows numCols: nCols];
	return self;
}

//	MoveMatrix delegate.
//	The delegate should prepare following methods:
//		- matrixDidExchange: (int) index1 : (int) index2
//
- setMoveDelegate: anObject
{
	moveDelegate = anObject;
}

- moveDelegate
{
	return moveDelegate;
}

- mouseDown:(NXEvent *) event
{
	BOOL gotHit;
	int oldMask;
    oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK];
	gotHit = [self move:event];
	
	[window setEventMask:oldMask];

	if (!gotHit) {
		[super mouseDown: event];
	}
	return self;
}

- selectionCache
/*
 * Shares an off-screen window used to draw the selection in so that it
 * can be dragged around.  If the current off-screen window is equal in
 * size or larger than the passed size, then it is simply returned.
 * Otherwise, it is resized to be the specified size.
 */
{
    NXRect rect;
    static id selectioncache = nil;
    NXRect defaultRect = {{0.0, 0.0}, {300.0, 300.0}};

    if (!selectioncache) {
		rect = defaultRect;
		selectioncache = [[Window alloc] initContent:&rect
						  style:NX_PLAINSTYLE
						backing:NX_RETAINED
					 buttonMask:0
						  defer:NO];
		[selectioncache reenableDisplay];
    } else {
		[selectioncache getFrame:&rect];
		if (rect.size.width < bounds.size.width ||
			rect.size.height < bounds.size.height) {
			[selectioncache sizeWindow:MAX(rect.size.width, bounds.size.width)
					  :MAX(rect.size.height, bounds.size.height)];
		}
    }

    return selectioncache;
}


-(int)cacheCell:aCell in:(NXRect *)sbounds inView:aView
{
    int i;
    id selectionCache;

	if (!aView) aView = self;
    selectionCache = [aView selectionCache];
    [[selectionCache contentView] lockFocus];
    PSsetgray(NX_WHITE);
    PSsetalpha(0.0);	/* fully transparent */
    PStranslate(- NX_X(sbounds), - NX_Y(sbounds));
    NX_WIDTH(sbounds) += 1.0;
    NX_HEIGHT(sbounds) += 1.0;
    NXRectFill(sbounds);
    NX_WIDTH(sbounds) -= 1.0;
    NX_HEIGHT(sbounds) -= 1.0;
    PSsetalpha(1.0);	/* fully opaque */
	[aCell drawSelf:sbounds inView:[selectionCache contentView]];
    PStranslate(NX_X(sbounds), NX_Y(sbounds));
    [[selectionCache contentView] unlockFocus];

    return [selectionCache gState];
}




- compositeSelection:(const NXRect *)sbounds from:(int)gstate
/*
 * Composites from the specified gstate whatever part of sbounds is
 * currently visible in the View.
 */
{
	//y position modified to work with a flipped destination view
    PScomposite(0.0, 0.0, NX_WIDTH(sbounds), NX_HEIGHT(sbounds),
                gstate, NX_X(sbounds), NX_Y(sbounds)+NX_HEIGHT(sbounds), NX_SOVER);
    [window flushWindow];
    NXPing();
    return self;
}

#define stopTimer(timer) if (timer) { \
    NXEndTimer(timer); \
    timer = NULL; \
}

#define startTimer(timer) if (!timer) timer = NXBeginTimer(NULL, 0.1, 0.1);

#define MOVE_MASK NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK

- (BOOL) move: (NXEvent *) event
{
    int gstate;
    int row, col;
	static int originalRow, originalCol;
    static id aCell;
    NXCoord dx, dy;
    NXEvent peek;
    NXPoint p, last, center;
    NXRect sbounds, visibleRect, cellFrame;
    NXTrackingTimer *timer = NULL;
    BOOL canScroll, tracking = YES;
    
	static MyCell *substituteCell = nil;
	if (!substituteCell) substituteCell = [MyCell new];
	last = event->location;
	[self convertPoint:&last fromView:nil];
	[self lockFocus];
	
	//unhighlight previously highlit cell (aCell is static and is saved from
	//last entry into method
	if (aCell == nil) {
		// [self highlightCellAt:originalRow:originalCol lit:NO];
	} else {
		[self getRow:&row andCol:&col ofCell:aCell];
		// [self highlightCellAt:row:col lit:NO];
	}
	//highlight cell clicked on
	[self getRow:&originalRow andCol:&originalCol forPoint:&last];
	// [self highlightCellAt:originalRow:originalCol lit:YES];

	event = [NXApp getNextEvent:MOVE_MASK];
	if (event->type == NX_MOUSEUP) {	
		aCell = nil;
		[self unlockFocus];
		return NO;
	}
    

	// cache the image of the selected cell
	[self getCellFrame:&sbounds at:originalRow:originalCol];
	aCell = [self cellAt:originalRow:originalCol];
	gstate = [self cacheCell:aCell in:&sbounds inView: self];   
	[self compositeSelection:&sbounds from:gstate];
	[self putCell:substituteCell at:originalRow:originalCol];
    

    [self getVisibleRect:&visibleRect];
    canScroll = !NXEqualRect(&visibleRect, &bounds);


    while (tracking) {
		p = event->location;
		[self convertPoint:&p fromView:nil];
		//	only moving in horizontal direction
		dx = p.x - last.x;
		if (dx) {
			[self drawSelf:&sbounds :1];
			NXOffsetRect(&sbounds, dx, 0.0);
			//set up so cell image stays within matrix bounds
  			if (!canScroll ||
 			    (visibleRect.origin.x == 0.0) || 
			    ((visibleRect.origin.x + visibleRect.size.width)==bounds.size.width))
			    {
				//assuming a flipped view
				//lefthand of the matrix
				if (sbounds.origin.x < bounds.origin.x) {
					p.x += bounds.origin.x - sbounds.origin.x;
					sbounds.origin.x = bounds.origin.x;
				} else if ((sbounds.origin.x + sbounds.size.width) >
				           (bounds.origin.x + bounds.size.width)) {
					//righthand of the matrix
					p.x -= sbounds.origin.x - ((bounds.origin.x + bounds.size.width)
									- sbounds.size.width);
					sbounds.origin.x = (bounds.origin.x + bounds.size.width)
										- sbounds.size.width + 1;
				}
			}
 			if (!canScroll || NXContainsRect(&visibleRect, &sbounds)) {
				[self compositeSelection:&sbounds from:gstate];
				stopTimer(timer);
			}
			last = p;
		}
		tracking = (event->type != NX_MOUSEUP);
		if (tracking) {
			if (canScroll && !NXContainsRect(&visibleRect, &sbounds)) {
				[window disableFlushWindow];
				[self scrollRectToVisible:&sbounds];
				[self getVisibleRect:&visibleRect];
				[self compositeSelection:&sbounds from:gstate];
				[[window reenableFlushWindow] flushWindow];
				startTimer(timer);
			}
			p = event->location;
			if (![NXApp peekNextEvent:MOVE_MASK into:&peek]) {
 				event = [NXApp getNextEvent:MOVE_MASK|NX_TIMERMASK];
			} else {
				event = [NXApp getNextEvent:MOVE_MASK];
			}
			if (event->type == NX_TIMER) event->location = p;
		}
    }

    if (canScroll) stopTimer(timer);
    
	//set up p so that the y dimension isn't affecting inclusion in matrix
	p.y = 0.0;
	center.x = sbounds.origin.x + sbounds.size.width / 2.0;
	center.y = 0.0;
	// make sure we're dealing with a valid column
	if (![self getRow:&row andCol:&col forPoint:&center]) {
		row = 0;
		if (center.x < 0) col = 0;
		else col = [self cellCount]-1;
	}
	
	[self drawSelf: &sbounds :1];

	//whole bunch of finetuning to get this to look right
	if (col == originalCol) {
		[self putCell: aCell at: row : col];
		[super selectCellAt: row : col];
		[super sendAction];
	} else {
		aCell = [self putCell: aCell at: row : col];
		[self putCell: aCell at: originalRow : originalCol];
		if (moveDelegate) {
			[moveDelegate matrixDidExchange: originalCol : col];
		}
	}
	[self display];
	[window flushWindow];
	[self unlockFocus];

    return YES;
}

@end
