/*===============================
Copyright © 1998-2002 Brian Hall

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*===============================*/
#include "BlugsPrivate.h"

static void			local_BLBeginInlineEdit( BLCell inCell, BlugsRef inList );



/*The process of inline editing:
	Blugs gets a click on a cell.
	If the cell wants inline, call BLTrackBeginInlineEdit().
		Handler determines if the click was in the text rect.
		If true, begin inline edit.
		Set appropriate flags and start sending key events to the content handler
	Call handler to adjust cursor over edit rect.
	Return key might end edit -- but not for milti-line fields (unlike Finder).
	A click anywhere in bl_Rect() ends edit, but we should make this more elegant;
		the title corners are usually inert, for example.
*/

#define blTicksBeforeInline		20	// We click-wait this number of ticks. (Provisional)

//===============================
// If the host application wishes to begin an edit on the fly, it should call this.
// For example, a double click on a cell might begin inline.
// We could also roll this function into BLBeginInlineEdit.
BL_PROC Boolean BLIsCellEditable( BLCell inCell, BlugsRef inList )
{
	OSErr	err;
	Boolean	editable = false;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateCell( inCell, inList );
		if (!err) editable = _BLIsCellEditable( inCell, inList );
	}
	return editable;
}

//===============================
BL_PROC OSErr BLSetCellEditable( BLCell inCell, Boolean inEditable, BlugsRef inList )
{
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateCell( inCell, inList );
		if (!err)
		{
			BLIndexPtr	indexP;
			
			indexP = bl_CellIndexAddr(inCell,inList);
			if (inEditable) bl_Clear(indexP->flags,blCantEdit);
			else bl_Set(indexP->flags,blCantEdit);
		}
	}
	return err;
}

//===============================
// Returns true if there is an inline edit session going on.
BL_PROC Boolean BLIsInlineEdit( BlugsRef inList )
{
	OSErr	err;
	Boolean	isEdit = false;
	
	err = _BLValidateList( inList );
	if (!err) isEdit = (bl_InlineNow(inList));
	return isEdit;
}


//===============================
// Returns the inline edit cell. If no edit, cell is unchanged and returns error code.
BL_PROC OSErr BLGetInlineEditCell( BLCell* outCell, BlugsRef inList )
{
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		if (!bl_InlineNow(inList)) err = invalidEditState; /* -2023 */
		else err = BLGetCellFromUID( &bl_InlineCell(inList), outCell, inList );
	}
	return err;
}


//===============================
BL_PROC OSErr BLBeginInlineEdit( BLCell inCell, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	// We use BLIsCellEditable and not _BLIsCellEditable because we should use a routine
	//	that will validate these parameters for us.
	if (BLIsCellEditable( inCell, inList ))
	{
		local_BLBeginInlineEdit( inCell, inList );
		err = noErr;
	}
	else err = editingNotAllowed;
	return err;
}


//===============================
BL_PROC void BLEndInlineEdit( BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err) _BLEndInlineEdit( true, inList );
}

//===============================
// Returns:
//	blInlineEditCommandStatusIgnore		Command is not relevant.
//	blInlineEditCommandStatusEnable		Command should be enabled.
//	blInlineEditCommandStatusDisable	Command should be disabled.
BL_PROC UInt32 BLGetCommandStatus( UInt32 inCommand, UInt32 inModifiers, BlugsRef inList )
{
	OSErr		err;
	UInt32		reply = blInlineEditCommandStatusIgnore;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		if (!bl_InlineNow(inList)) err = invalidEditState; /* -2023 */
		else
		{
			BLHandlerParamBlock	pb;
			
			err = BLGetCellFromUID( &bl_InlineCell(inList), &pb.inCell, inList );
			if (!err)
			{
				(void)_BLCellRect( pb.inCell, &pb.inRect, inList );
				pb.ioCommand = inCommand;
				pb.inIndent = _BLIndent( pb.inCell, inList );
				pb.inEventModifiers = inModifiers;
				reply = _BLCallHandlerForCell( blInlineEditCommandStatusMsg, pb.inCell, &pb, inList );
			}
		}
	}
	return reply;
}

//===============================
BL_PROC OSErr BLProcessCommand( UInt32 inCommand, UInt32 inModifiers, BlugsRef inList )
{
	OSErr		err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		if (!bl_InlineNow(inList)) err = invalidEditState; /* -2023 */
		else
		{
			BLHandlerParamBlock	pb;
			
			err = BLGetCellFromUID( &bl_InlineCell(inList), &pb.inCell, inList );
			if (!err)
			{
				UInt32	reply;
				
				(void)_BLCellRect( pb.inCell, &pb.inRect, inList );
				pb.ioCommand = inCommand;
				pb.inIndent = _BLIndent( pb.inCell, inList );
				pb.inEventModifiers = inModifiers;
				reply = _BLCallHandlerForCell( blInlineEditCommandProcessMsg, pb.inCell, &pb, inList );
				if (!reply) err = eventNotHandledErr;
				//if (bl_Test(reply,blCellCommandProcessUpdate))
				//	_BLUpdateCell( blUpdateSuppressTriangle, pb.inCell, inList );
			}
		}
	}
	if (err) err = eventNotHandledErr;
	return err;
}
//===============================
// Returns true if the click was in the edit region and the handler said OK to edit.
Boolean _BLTrackBeginInlineEdit( UInt32 inWhen, BLCell inCell, Point inMouse,
								const Rect* inCellRect, BlugsRef inList )
{
	Boolean				begin = false;
	BLIndexPtr			indexP;
			
	indexP = bl_CellIndexAddr(inCell,inList);
	if (!bl_CantEdit(indexP))
	{
		RgnHandle		inlineRgn = NewRgn();
		
		if (inlineRgn)
		{
			RgnHandle	rectRgn = NewRgn();
			
			if (rectRgn)
			{
				BLHandlerParamBlock	pb;
				
				pb.inRegion = inlineRgn;
				pb.inRect = *inCellRect;
				pb.inIndent = _BLIndent( inCell, inList );
				_BLCallHandlerForCell( blInlineEditRegionMsg, inCell, &pb, inList );
				RectRgn( rectRgn, inCellRect );
				SectRgn( rectRgn, inlineRgn, inlineRgn );
				if (PtInRgn(inMouse,inlineRgn))
				{
					if (bl_Test((*inList)->listFlags,blImmediateInline))
						local_BLBeginInlineEdit( inCell, inList );
					else
					{
						bl_Set((*inList)->privateFlags,blPendingInline);
						(void)BLGetCellUID( inCell, &bl_InlineCell(inList), inList );
						(*inList)->pendingInlineTime = inWhen;
					}
					begin = true;
				}
				DisposeRgn( rectRgn );
			}
			DisposeRgn( inlineRgn );
		}
	}
	return begin;
}

//===============================
// Called from BLIdle
void _BLWaitingForInlineEdit( BlugsRef inList )
{
	if (bl_PendingInline(inList))
	{
		if (TickCount() > (*inList)->pendingInlineTime + blTicksBeforeInline)
		{
			BLCell	inlineCell;
			OSErr	err;
			
			err = BLGetCellFromUID( &bl_InlineCell(inList), &inlineCell, inList );
			if (!err) local_BLBeginInlineEdit( inlineCell, inList );
			bl_Clear((*inList)->privateFlags,blPendingInline);
		}
	}
}

//===============================
/* This routine should be called by BLClick() whenever there is a click anywhere in the list
and an edit session is going on. Basically, anything outside cell ends the session.
Otherwise, call handler to determine if the click is within the current edit rect.
If not, end the edit session and return false to indicate that other cells may be selected.
*/
Boolean _BLInlineEditClick( BLCell inCell, Point inMouse, EventModifiers inModifiers, BlugsRef inList )
{
	Rect				clickedRect;
	Boolean				handled = false;
	BLHandlerParamBlock	pb;
	UInt32				reply;
	
	pb.inEventWhereLocal = inMouse;
	pb.inEventModifiers = inModifiers;
	// A cell may not have been clicked; could be {0,0}
	if (bl_ValidCell(inCell))
	{
		BLCellUID			inCellID;
		
		(void)BLGetCellUID( inCell, &inCellID, inList );
		if (_BLEqualCellUID( &bl_InlineCell(inList), &inCellID ))
		{
			RgnHandle	saveClip;
			OSErr		err;
			
			err = _BLCellRect( inCell, &clickedRect, inList );
			if (!err)
			{
				pb.inRect = clickedRect;
				pb.inIndent = _BLIndent( inCell, inList );
				saveClip = _BLClipToViewRectIntersection( &clickedRect, inList );
				reply = _BLCallHandlerForCell( blInlineEditClickMsg, inCell, &pb, inList );
				if (reply == blInlineEditClickHandled) handled = true;
				if (saveClip)
				{
					SetClip( saveClip );
					DisposeRgn( saveClip );
				}
			}
		}
	}
	if (!handled)
	{
		_BLEndInlineEdit( true, inList );
		(void)BLSetFocusedPart( kControlListBoxPart, inList );
	}
	return handled; // Only returns true if the click was inside the edit cell and processed
}

//===============================
BLKeyResult _BLInlineEditKey( UInt32 inMessage, EventModifiers inModifiers, BlugsRef inList )
{
	char				theChar;
	BLHandlerParamBlock	pb;
	UInt32				reply;
	Rect				cellRect;
	RgnHandle			saveClip;
	BLCell				inlineCell;
	OSErr				err;
	
	err = BLGetCellFromUID( &bl_InlineCell(inList), &inlineCell, inList );
	theChar = inMessage & charCodeMask;
	if (theChar == kReturnCharCode || theChar == kEnterCharCode)
	{
		UInt32	features;
		
		err = _BLGetCellHandlerFeatures( inlineCell, &features, inList );
		if (!err)
		{
			if (!bl_Test(features,blWantsInlineReturn))
			{
				_BLSelectCell( 0, inlineCell, inList );
				_BLEndInlineEdit( true, inList );
				(void)BLSetFocusedPart( kControlListBoxPart, inList );
				return blInlineEditEndedKeyResult;
			}
		}
	}
	err = _BLCellRect( inlineCell, &cellRect, inList );
	if (!err)
	{
		pb.inRect = cellRect;
		pb.inEventMessage = inMessage;
		pb.inEventModifiers = inModifiers;
		pb.inIndent = _BLIndent( inlineCell, inList );
		saveClip = _BLClipToViewRectIntersection( &cellRect, inList );
		reply = _BLCallHandlerForCell( blInlineEditKeyMsg, inlineCell, &pb, inList );
		if (saveClip)
		{
			SetClip( saveClip );
			DisposeRgn( saveClip );
		}
		if (bl_Test(reply,blInlineEditKeyUpdate))
			_BLUpdateCellOnscreen( inlineCell, &cellRect, inList );
	}
	return blSentToInlineEditKeyResult;
}

//===============================
Boolean _BLNextEditable( BLCell* ioCell, BlugsRef inList )
{
	BLCell		cell = *ioCell;
	Boolean		hasEditable = false;
	
	if (_BLNextCell( &cell, inList ))
	{
		UInt16		nRows, nCols;
		
		nRows = bl_NRows(inList);
		nCols = bl_NCols(inList);
		for (; cell.row <= nRows; cell.row++)
		{
			if (_BLRowIsDisclosed( cell.row, inList ))
			{
				if (cell.row != ioCell->row) cell.col = 1;
				for (; cell.col <= nCols; cell.col++)
				{
					if (_BLIsCellEditable( cell, inList ))
					{
						ioCell->row = cell.row;
						ioCell->col = cell.col;
						hasEditable = true;
						// Break out of column loop.
						break;
					}
				}
				// Break out of row loop.
				if (hasEditable) break;
			}
		}
	}
	return hasEditable;
}


//===============================
Boolean _BLPreviousEditable( BLCell* ioCell, BlugsRef inList )
{
	BLCell		cell = *ioCell;
	Boolean		hasEditable = false;
	
	// Only look if ioCell is greater than (1,1)
	if (cell.row > 1 || cell.col > 1)
	{
		UInt16		nCols;
		
		nCols = bl_NCols(inList);
		// Start at the previous cell.
		// The column will be adjusted right before the column for loop.
		if (cell.col == 1) cell.row--;
		else cell.col--;
		// Check all disclosed rows in reverse order.
		for (; cell.row > 0; cell.row--)
		{
			if (_BLRowIsDisclosed( cell.row, inList ))
			{
				// Start the column loop at the last column, unless we're in the start row.
				if (cell.row != ioCell->row) cell.col = nCols;
				for (; cell.col > 0; cell.col--)
				{
					if (_BLIsCellEditable( cell, inList ))
					{
						ioCell->row = cell.row;
						ioCell->col = cell.col;
						hasEditable = true;
						break;
					}
				}
				if (hasEditable) break;
			}
		}
	}
	return hasEditable;
}


//===============================
Boolean _BLIsCellEditable( BLCell inCell, BlugsRef inList )
{
	OSErr	err;
	UInt32	features;
	Boolean	canEdit = false;
	
	err = _BLGetCellHandlerFeatures( inCell, &features, inList );
	if (!err)
	{
		if (bl_Test(features,blWantsInlineEdit))
		{
			BLIndexPtr	indexP;
			
			indexP = bl_CellIndexAddr(inCell,inList);
			if (!bl_CantEdit(indexP)) canEdit = true;
		}
	}
	return canEdit;
}


//===============================
// Returns true if it *needed to* sort the list. (Whether inSortIfNeeded was true or false)
Boolean _BLEndInlineEdit( Boolean inSortIfNeeded, BlugsRef inList )
{
	Boolean	neededSort = false;
	
	if (bl_InlineNow(inList))
	{
		BLCell	inlineCell;
		OSErr	err;
		
		err = BLGetCellFromUID( &bl_InlineCell(inList), &inlineCell, inList );
		if (!err && bl_ValidCell(inlineCell))
		{
			BLCellUID				nullCell = {{0,0},{0,0}};
			BLHandlerParamBlock		pb;
			UInt32					reply;
			BLNotificationProcPtr	proc;
			GWorldPtr				savePort;
			Boolean					needRestore;
			
			needRestore = _BLGotoWindow( &savePort, inList );
			(void)_BLCellRect( inlineCell, &pb.inRect, inList );
			pb.inIndent = _BLIndent( inlineCell, inList );
			reply = _BLCallHandlerForCell( blInlineEditEndMsg, inlineCell, &pb, inList );
			bl_Clear((*inList)->privateFlags,blInlineEditNow);
			// Set focus to list. Or should we just remove it?
			// If the user wants to click to focus another control, we want to
			//	just end the edit.
			//(void)BLSetFocusedPart( kControlListBoxPart, inList );
			_BLUpdateCell( blUpdateBlit | blUpdateTriangle, inlineCell, inList );
			bl_InlineCell(inList) = nullCell;
			if (reply == blInlineEditEndChanged)
			{
				// We don't want to automatically sort in response to the end of an edit.
				// Let the user or app sort it first, then we can respond by keeping it sorted.
				if (bl_Sorted(inList))
				{
					neededSort = true;
					if (inSortIfNeeded) _BLSort( blSortStateNoChange, inList );
				}
			}
			if (needRestore) _BLSwapPort( savePort, nil );
			proc = (*inList)->callbacks.notificationProc;
			if (proc) (*proc)( blInlineEditEndedNotificationMsg, 0, blCellPart, inlineCell, inList );
		}
	}
	return neededSort;
}


//===============================
// Advances or retrogresses (?huh? retreats? 'makes it go backwards?')
//	inline edit to next or previous editable cell. Returns true if one was found.
// Always ends the current session; this means that tabbing past the end of the list results
//	in no inline edit session.
void _BLMoveInlineEdit( Boolean inAdvance, BlugsRef inList )
{
	BLCell	inlineCell;
	OSErr	err;
	
	err = BLGetCellFromUID( &bl_InlineCell(inList), &inlineCell, inList );
	if (!err && bl_ValidCell(inlineCell))
	{
		BLCell	newCell;
		Boolean	needSort, doMove;
		
		newCell = inlineCell;
		if (inAdvance) doMove = _BLNextEditable( &newCell,inList );
		else doMove = _BLPreviousEditable( &newCell,inList );
		needSort = _BLEndInlineEdit( !doMove, inList );
		if (doMove) local_BLBeginInlineEdit( newCell, inList );
		// OK, now we can sort.
		if (needSort) _BLSort( blSortStateNoChange, inList );
		// Sorting may move our new inline cell far, far away!
		if (doMove) _BLMakeVisible( newCell, inList );
	}
}


//===============================
// Called when we are sure we can do the inline edit.
static void local_BLBeginInlineEdit( BLCell inCell, BlugsRef inList )
{
	if (BLGetCellUID( inCell, &bl_InlineCell(inList), inList ) == noErr)
	//if (bl_ValidCell(inCell) && bl_CellInRange(inCell,inList))
	{
		Rect				viewRect;
		BLHandlerParamBlock	pb;
		ControlPartCode		part;
		RgnHandle			saveClip;
		GWorldPtr			saveWorld;
		OSErr				err;
		
		_BLMakeVisible( inCell, inList );
		viewRect = _BLViewRect( inList );
		_BLDeselectAll( true, inList );
		err = _BLCellRect( inCell, &pb.inRect, inList );
		if (!err)
		{
			BLNotificationProcPtr	proc;
			Boolean					restore;
			
			pb.inIndent = _BLIndent( inCell, inList );
			bl_Set((*inList)->privateFlags,blInlineEditNow);
			//(void)BLGetCellUID( inCell, &bl_InlineCell(inList), inList );
			// This just removes focus from the list box.
			part = BLSetFocusedPart( blInlineEditPart, inList );
			// Moses 11/12/2000. Make absolutely sure the current port is the list window.
			restore = _BLGotoWindow( &saveWorld, inList );
			saveClip = _BLClipToViewRectIntersection( &pb.inRect, inList );
			_BLCallHandlerForCell( blInlineEditBeginMsg, inCell, &pb, inList );
			if (saveClip)
			{
				SetClip( saveClip );
				DisposeRgn( saveClip );
			}
			proc = (*inList)->callbacks.notificationProc;
			if (proc) (*proc)( blInlineEditBeganNotificationMsg, 0, blCellPart, inCell, inList );
			if (restore) SetGWorld( saveWorld, nil );
			// No need to draw the border, it was drawn if necessary by BLSetFocusedPart
			if (BLGetCellFromUID( &bl_InlineCell(inList), &inCell, inList ) == noErr)
				_BLUpdateCell( blUpdateBlit | blUpdateTriangle, inCell, inList );
		}
	}
}
