//=============================================================================
//
//		Copyright (C) 1996-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.
//
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableScrollEdit.M
//
//		Text cell editing support for MiscTableScroll.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableScrollEdit.M,v 1.3 97/06/10 05:49:00 sunshine Exp $
// $Log:		MiscTableScrollEdit.M,v $
// Revision 1.3	 97/06/10  05:49:00	 sunshine
// v127: Fixed bug: Was not returning 'nil' from -getRow:col:forPoint: when
// point was out of bounds.
// Fixed bug: Wasn't grabbing a copy of cell when initiating editing; only
// referenced it.  In lazy mode, scrolling around would cause the contents
// of the referenced cell to change (even just the inital scroll-to-visible),
// thus causing a bus-load of bugs.	 Now grabs copy of cell instead.
// Fixed bug: Keyboard cursor was lagging behind as TAB was used to change
// from cell to cell during editing.
// First-responder is now returned to TableScroll when cell editing ends via
// Return key, rather than reverting uselessly to the window.
// 
// Revision 1.2	 97/01/24  08:12:16	 sunshine
// v0.119: Fixed bug: Wasn't scrolling cell to visible upon edit initiation.
// -setClickedRow: & -setClickedCol: --> -setClickedRow:col:
// Added explicit (id) as return and argument type.
// 
// Revision 1.1	 96/12/30  16:30:56	 zarnuk
// v113: Migrated cell editing to here from table view.
//-----------------------------------------------------------------------------
#import "MiscTableScrollPrivate.h"
#import "MiscTableBorder.h"
#import "MiscTableView.h"
extern "Objective-C" {
#import <appkit/Cell.h>
#import <appkit/Text.h>
}
extern "C" {
#import <stdlib.h>		// malloc(), free()
}

typedef MiscDelegateFlags DF;

@implementation MiscTableScroll(Edit)

//-----------------------------------------------------------------------------
// getCellFrame:at::
//-----------------------------------------------------------------------------
- (id)getCellFrame:(NXRect*)rect at:(int)row :(int)col
	{
	[tableView getCellFrame:rect at:row:col];
	[self convertRect:rect fromView:tableView];
	return self;
	}


//-----------------------------------------------------------------------------
// getRow:andCol:forPoint:
//-----------------------------------------------------------------------------
- (id)getRow:(int*)row andCol:(int*)col forPoint:(NXPoint const*)point
	{
	NXPoint pt = *point;
	[tableView convertPoint:&pt fromView:self];
	return ([tableView getRow:row andCol:col forPoint:&pt] != 0 ? self : 0);
	}


//-----------------------------------------------------------------------------
// isEditing
//-----------------------------------------------------------------------------
- (BOOL)isEditing
	{
	return editInfo.editing;
	}


//-----------------------------------------------------------------------------
// cleanupEditing
//-----------------------------------------------------------------------------
- (void)cleanupEditing
	{
	[editInfo.cell free];
	editInfo.cell = 0;
	editInfo.editor = 0;
	editInfo.editing = NO;
	}


//-----------------------------------------------------------------------------
// finishEditing
//-----------------------------------------------------------------------------
- (BOOL)finishEditing
	{
	if ([self isEditing] && [window makeFirstResponder:window])
		[window endEditingFor:editInfo.editor];
	return ![self isEditing];
	}


//-----------------------------------------------------------------------------
// abortEditing
//-----------------------------------------------------------------------------
- (void)abortEditing
	{
	if (editInfo.editing)
		{
		editInfo.editing = NO;
		int const r = editInfo.row;
		int const c = editInfo.col;

		[editInfo.cell endEditing:editInfo.editor];
		[self cleanupEditing];

		id d;
		if ((d = [self responsibleDelegate:DF::DEL_ABORT_EDIT_AT]) != 0)
			[d tableScroll:self abortEditAt:r:c];

		if ((d = [self responsibleDelegate:DF::DEL_DID_EDIT_AT]) != 0)
			[d tableScroll:self didEdit:NO at:r:c];

		[self drawCellAt:r:c];
		}
	}


//-----------------------------------------------------------------------------
// getNext:editRow:andCol:
//-----------------------------------------------------------------------------
- (BOOL)getNext:(BOOL)foreward
		editRow:(MiscCoord_P*)p_row
		andCol:(MiscCoord_P*)p_col
	{
	MiscCoord_P pr = *p_row;
	MiscCoord_P pc = *p_col;

	MiscTableBorder const* const br = rowInfo.border;
	MiscTableBorder const* const bc = colInfo.border;

	MiscCoord_V const v_row = br->physicalToVisual( pr );		// Start pos.
	MiscCoord_V const v_col = bc->physicalToVisual( pc );

	MiscCoord_V vr = v_row;
	MiscCoord_V vc = v_col;

	do	{
		if (foreward)
			{
			if (++vc >= num_cols)
				{
				vc = 0;
				if (++vr >= num_rows)
					vr = 0;
				pr = br->visualToPhysical( vr );
				}
			}
		else
			{
			if (--vc < 0)
				{
				vc = num_cols - 1;
				if (--vr < 0)
					vr = num_rows - 1;
				pr = br->visualToPhysical( vr );
				}
			}
		if (vc == v_col && vr == v_row)			// Wrapped to start pos.
			return NO;
		pc = bc->visualToPhysical( vc );
		}
	while (![self canEdit:0 at:pr:pc]);

	*p_row = pr;
	*p_col = pc;
	return YES;
	}


//-----------------------------------------------------------------------------
// getPreviousEditRow:andCol:
//-----------------------------------------------------------------------------
- (BOOL)getPreviousEditRow:(MiscCoord_P*)p_row andCol:(MiscCoord_P*)p_col
	{
	return [self getNext:NO editRow:p_row andCol:p_col];
	}


//-----------------------------------------------------------------------------
// getNextEditRow:andCol:
//-----------------------------------------------------------------------------
- (BOOL)getNextEditRow:(MiscCoord_P*)p_row andCol:(MiscCoord_P*)p_col
	{
	return [self getNext:YES editRow:p_row andCol:p_col];
	}


//-----------------------------------------------------------------------------
// textDidEnd:endChar:
//-----------------------------------------------------------------------------
- (id)textDidEnd:(id)sender endChar:(unsigned short)whyEnd
	{
	id d;

	editInfo.editing = NO;
	int const len = [sender textLength] + 1;
	char* const s = (char*) malloc( len );
	[sender getSubstring:s start:0 length:len];
	[editInfo.cell endEditing:editInfo.editor];
	int const r = editInfo.row;
	int const c = editInfo.col;
	char const* const t = [editInfo.cell stringValue];
	BOOL changed = (strcmp( s, (t != 0 ? t : "") ) != 0);
	if (changed)
		{
		if ((d = [self responsibleDelegate:DF::DEL_SET_STRINGVALUE_AT]) != 0)
			changed = [d tableScroll:self setStringValue:s at:r:c];
		else
			[[self cellAt:r:c] setStringValue:s];
		}
	free(s);
	[self cleanupEditing];

	if ((d = [self responsibleDelegate:DF::DEL_DID_EDIT_AT]) != 0)
		[d tableScroll:self didEdit:changed at:r:c];

	[self drawCellAt:r:c];

	if (changed)
		{
		if ([self autoSortRows])
			[self sortRow:r];
		if ([self autoSortCols])
			[self sortCol:c];
		}

	if ((d = [self responsibleDelegate:DF::DEL_TEXT_DID_END]) != 0)
		[d textDidEnd:sender endChar:whyEnd];
	else
		{
		MiscCoord_P row = (MiscCoord_P) r;
		MiscCoord_P col = (MiscCoord_P) c;

		switch (whyEnd)
			{
		case NX_RETURN:
				[self selectText:self];
				[self sendAction];
				break;

		case NX_TAB:
				if ([self getNext:YES editRow:&row andCol:&col])
					[self editCellAt:row:col];
				else
					[[self nextText] selectText:self];
				break;

		case NX_BACKTAB:
				if ([self getNext:NO editRow:&row andCol:&col])
					[self editCellAt:row:col];
				else
					[[self previousText] selectText:self];
				break;

		// FIXME: CTRL-TAB => nextText, CTRL-SHIFT-TAB => previousText
		default:
				break;
			}
		}

	return self;
	}


//-----------------------------------------------------------------------------
// textDidChange:
//-----------------------------------------------------------------------------
- (id)textDidChange:(id)sender
	{
	id d = [self responsibleDelegate:DF::DEL_TEXT_DID_CHANGE];
	if (d != 0)
		[d textDidChange:sender];
	return self;
	}


//-----------------------------------------------------------------------------
// textDidGetKeys:isEmpty:
//-----------------------------------------------------------------------------
- (id)textDidGetKeys:(id)sender isEmpty:(BOOL)flag
	{
	id d = [self responsibleDelegate:DF::DEL_TEXT_DID_GET_KEYS];
	if (d != 0)
		[d textDidGetKeys:sender isEmpty:flag];
	return self;
	}


//-----------------------------------------------------------------------------
// textWillChange:
//-----------------------------------------------------------------------------
- (BOOL)textWillChange:(id)sender
	{
	id d = [self responsibleDelegate:DF::DEL_TEXT_WILL_CHANGE];
	if (d != 0)
		return [d textWillChange:sender];
	return NO;			// allow change (reverse boolean logic)
	}


//-----------------------------------------------------------------------------
// textWillEnd:
//-----------------------------------------------------------------------------
- (BOOL)textWillEnd:(id)sender
	{
	id d = [self responsibleDelegate:DF::DEL_TEXT_WILL_END];
	if (d != 0)
		return [d textWillEnd:sender];
	return NO;			// allow end-of-editing (reverse boolean logic)
	}


//-----------------------------------------------------------------------------
// edit:at::
//-----------------------------------------------------------------------------
- (void)edit:(NXEvent const*)ev at:(MiscCoord_P)row :(MiscCoord_P)col
	{
	id d;
	if ((d = [self responsibleDelegate:DF::DEL_WILL_EDIT_AT]) != 0)
		[d tableScroll:self willEditAt:row:col];

	id const cell = [[self cellAt:row:col] copyFromZone:[self zone]];
	id const editor = [[self window] getFieldEditor:YES for:self];
	[editor setOpaque:YES];

	editInfo.editing = YES;
	editInfo.row = row;
	editInfo.col = col;
	editInfo.editor = editor;
	editInfo.cell = cell;

	if ((d = [self responsibleDelegate:DF::DEL_EDIT_AT]) != 0)
		[d tableScroll:self edit:ev at:row:col];
	else
		{
		if ([cell respondsTo:@selector(tableScroll:edit:at: :)])
			[cell tableScroll:self edit:ev at:row:col];
		else
			{
			NXRect rect; [tableView getCellInside:&rect at:row:col];
			[tableView scrollRectToVisible:&rect];
			if (ev != 0)
				{
				NXEvent tmp = *ev;
				[cell edit:&rect inView:tableView editor:editor
						delegate:self event:&tmp];
				}
			else
				{
				char const* const s = [cell stringValue];
				int const len = (s != 0 ? strlen(s) : 0);
				[cell select:&rect inView:tableView editor:editor
						delegate:self start:0 length:len];
				}
			}
		}
	}


//-----------------------------------------------------------------------------
// canEdit:at::
//-----------------------------------------------------------------------------
- (BOOL)canEdit:(NXEvent const*)ev at:(MiscCoord_P)row :(MiscCoord_P)col
	{
	id d = [self responsibleDelegate:DF::DEL_CAN_EDIT_AT];
	if (d != 0)
		return [d tableScroll:self canEdit:ev at:row:col];

	id const cell = [self cellAt:row:col];
	if ([cell respondsTo:@selector(tableScroll:canEdit:at: :)])
		return [cell tableScroll:self canEdit:ev at:row:col];

	return (ev == 0 || ev->data.mouse.click == 2) && 
				[cell respondsTo:@selector(isEditable)] &&
				[cell respondsTo:@selector(isEnabled)] &&
				[cell isEditable] && [cell isEnabled];
	}


//-----------------------------------------------------------------------------
// editIfAble:at::
//-----------------------------------------------------------------------------
- (BOOL)editIfAble:(NXEvent const*)ev at:(MiscCoord_P)row :(MiscCoord_P)col
	{
	if ([self canEdit:ev at:row:col])
		{
		[self edit:ev at:row:col];
		return YES;
		}
	return NO;
	}


//-----------------------------------------------------------------------------
// editCellAt::
//-----------------------------------------------------------------------------
- (void)editCellAt:(MiscCoord_P)row :(MiscCoord_P)col
	{
	[self clearSelection];
	if ([self trackingBy] == MISC_ROW_BORDER)
		{
		[self selectRow:row];
		[self setCursorRow:row];
		}
	else
		{
		[self selectCol:col];
		[self setCursorCol:col];
		}
	[self setClickedRow:row col:col];
	[self edit:0 at:row:col];
	}

@end
