/*===============================
|	EditStringHandler.c
|
|	Blugs 1.1 handler for editable text
|	www.blugs.com
|
|	Copyright © 1998-2001 Brian Hall
|	You may use these routines in your software without restriction.
|
|===============================*/
#include "EditStringHandler.h"
#include "BlugsAppearanceExtras.h"

#ifndef __TOOLUTILS__
#include <ToolUtils.h>
#endif

#ifndef __SOUND__
#include <Sound.h>
#endif

#ifndef __TEXTUTILS__
#include <TextUtils.h>
#endif


#define kDestRectExtraRight		1000
#define kMaximumLength			31
#if !__BL_USE_CARBON_API__
	#define GetQDGlobalsWhite(x)				(&qd.white)
	#define SetPortBackPixPat(port,pat)			(port->bkPixPat = pat)
	#define GetPortBackPixPat(port,pat)			(port->bkPixPat)
#endif

//static const RGBColor blackCol = {0,0,0}, whiteCol = {0xFFFF,0xFFFF,0xFFFF};

static void				local_ESGetRegion( EditStringHdl dataH, const Rect* cellRect, UInt16 indent, RgnHandle rgn );
static void				local_ESDraw( EditStringHdl dataH, Rect cellRect, UInt16 indent );
static void				local_ESInlineEditBegin( BLHandlerParamPtr param, EditStringHdl dataH );
static Boolean			local_ESInlineEditKey( BLHandlerParamPtr param, EditStringHdl dataH );
static Boolean			local_ESInlineEditClick( BLHandlerParamPtr param, EditStringHdl dataH );
static Boolean			local_ESInlineEditEnd( EditStringHdl dataH );
static void				local_ESDrawTextBox( Rect textRect );
static Boolean			local_ESIdle( BLHandlerParamPtr param, EditStringHdl dataH );
static EditStringHdl	local_ESAllocateStorage( void );
static void				local_ESGet( BLHandlerParamPtr param, EditStringHdl dataH );
static void				local_ESSet( BLHandlerParamPtr param, EditStringHdl dataH );
static SInt32			local_ESSort( EditStringHdl dataH, BLHandlerParamPtr param );
static SInt32			local_ESSearch( BLHandlerParamPtr param );
static Rect				local_ESAdjustTERect( EditStringHdl dataH, Rect cellRect, UInt16 indent );
static Rect				local_ESInlineTextRect( EditStringHdl dataH, Rect cellRect, UInt16 indent );
static Rect				local_ESNormalTextRect( EditStringHdl dataH, const Rect* cellRect, UInt16 indent );
static Boolean			local_ESIsDisplayChar( UInt8 theChar );
static Boolean			local_ESIsWithinMaximum( UInt8 inChar, TEHandle inTE );



pascal UInt32 EditString(	BLMessage			message,
							BLHandlerParamPtr	param		)
{
	EditStringHdl		dataH = nil;
	UInt32				answer = 0;
	Rect				textRect;
	Boolean				doingInline = false;
	ThemeDrawingState	saveState;
	OSErr				err;
	
	if (param) dataH = (EditStringHdl)param->ioStorage;
	if (dataH) doingInline = (*dataH)->doingInline;
	if (doingInline || message == blInlineEditBeginMsg)
	{
		err = BLGetThemeDrawingState( &saveState );
		err = BLNormalizeThemeDrawingState();
	}
	else saveState = nil;
	switch (message)
	{
		case blHandlerInitMsg:
		answer = blWantsInlineEdit | blWantsSort;
		break;
		
		case blCellInitMsg:
		param->ioStorage = (Handle)local_ESAllocateStorage();
		break;
		
		case blCellDeinitMsg:
		if (dataH) DisposeHandle( (Handle)dataH );
		break;
		
		case blCellDrawMsg:
		if (dataH) local_ESDraw( dataH, param->inRect, param->inIndent );
		break;
		
		case blCellSetDataMsg:
		if (dataH) local_ESSet( param, dataH );
		break;
		
		case blCellGetDataMsg:
		if (dataH) local_ESGet( param, dataH );
		break;
		
		case blInlineEditRegionMsg:
		if (dataH)
		{
			if (doingInline) textRect = local_ESInlineTextRect( dataH, param->inRect, param->inIndent );
			else
			{
				textRect = local_ESNormalTextRect( dataH, &param->inRect, param->inIndent );
				textRect.left--;
			}
			RectRgn( param->inRegion, &textRect );
		}
		break;
		
		case blInlineEditBeginMsg:
		if (dataH) local_ESInlineEditBegin( param, dataH );
		break;
		
		case blInlineEditKeyMsg:
		if (dataH) answer = local_ESInlineEditKey( param, dataH );
		break;
		
		case blInlineEditClickMsg:
		if (dataH) answer = local_ESInlineEditClick( param, dataH );
		break;
		
		case blInlineEditEndMsg:
		if (dataH) answer = local_ESInlineEditEnd( dataH );
		break;
		
		case blInlineEditCommandStatusMsg:
		answer = blInlineEditCommandStatusDisable;
		break;
		
		case blInlineEditCommandProcessMsg:
		if (param->ioCommand == kHICommandSelectAll) TESetSelect( 0, 32767, (*dataH)->inlineTE );
		break;
		
		case blCellRegionMsg:
		if (dataH) local_ESGetRegion( dataH, &param->inRect, param->inIndent, param->inRegion );
		break;
		
		case blCellIdleMsg:
		if (dataH && doingInline) answer = local_ESIdle( param, dataH );
		break;
		
		case blCellSortMsg:
		if (dataH) answer = local_ESSort( dataH, param );
		break;
		
		case blCellSearchMsg:
		answer = local_ESSearch( param );
		break;
	}
	if (saveState) err = BLSetThemeDrawingState( saveState, true );
	return answer;
}

//===============================
static void local_ESGetRegion( EditStringHdl dataH, const Rect* cellRect, UInt16 indent, RgnHandle rgn )
{
	Rect	textRect;
	
	textRect = local_ESNormalTextRect( dataH, cellRect, indent );
	RectRgn( rgn, &textRect );
}

//===============================
static void local_ESInlineEditBegin( BLHandlerParamPtr param, EditStringHdl dataH )
{
	Rect		viewRect, destRect, newRect;
	TEHandle	inlineTE;
	
	viewRect = local_ESNormalTextRect( dataH, &param->inRect, param->inIndent );
	viewRect.left--;
	destRect = viewRect;
	// Make sure TE doesn't try to wrap the text to a new line.
	destRect.right += kDestRectExtraRight;
	inlineTE = TENew( &destRect, &viewRect );
	if (inlineTE)
	{
		(*dataH)->doingInline = true;
		(*dataH)->inlineTE = inlineTE;
		TESetText( &((*dataH)->string[1]), (*dataH)->string[0], inlineTE );
		newRect = local_ESAdjustTERect( dataH, param->inRect, param->inIndent );
		TEActivate( inlineTE );
		TESetSelect( 0, 32767, inlineTE );
	}
}

//===============================
static Boolean local_ESInlineEditKey( BLHandlerParamPtr param, EditStringHdl dataH )
{
	TEHandle	inlineTE;
	
	inlineTE = (*dataH)->inlineTE;
	if (inlineTE)
	{
		UInt8	filterResult, key;
		Boolean	displayChar;
		
		key = param->inEventMessage & charCodeMask;
		displayChar = local_ESIsDisplayChar( key );
		if ((*dataH)->keyFilter && displayChar)
		{
			BLKeyFilterProcPtr	proc = (*dataH)->keyFilter;
			Ptr					text = *(*inlineTE)->hText;
			
			if (proc)
			{
				UInt16				selStart = (*inlineTE)->selStart,
									selEnd = (*inlineTE)->selEnd,
									teLength = (*inlineTE)->teLength;
				
				filterResult = (*proc)( text, selStart, text + selEnd, teLength - selEnd, &key );
				if (filterResult == keyFilterBad)
				{
					SysBeep( 30 );
					return blInlineEditKeyNoUpdate;
				}
			}
		}
		// We will not allow more than 31 characters
		if (local_ESIsWithinMaximum( key,inlineTE ))
		{
			Rect	oldRect, newRect;
			SInt16	selStart, selEnd;
			
			selStart = (*inlineTE)->selStart;
			selEnd = (*inlineTE)->selEnd;
			oldRect = local_ESInlineTextRect( dataH, param->inRect, param->inIndent );
			// I stole the following from Graham Cox, author of MacZoop.
			// Thanks for posting this sample to Carbon-Dev.
			if (key == kDeleteCharCode)
			{
				if (selStart == selEnd) TESetSelect( selStart, selStart + 1, inlineTE );
				TEDelete( inlineTE );
			}
			else
			{
				if ((param->inEventModifiers & shiftKey ) == shiftKey)
				{
					switch (key)
					{
						case kUpArrowCharCode:
						case kLeftArrowCharCode:
						TEKey( key, inlineTE );
						TESetSelect( (*inlineTE)->selStart, selEnd, inlineTE );
						break;

						case kDownArrowCharCode:
						case kRightArrowCharCode:
						TESetSelect( selEnd, selEnd, inlineTE );
						TEKey( key, inlineTE );
						TESetSelect( selStart, (*inlineTE)->selEnd, inlineTE );
						break;

						default:
						TEKey( key, inlineTE );
						break;
					}
				}
				else TEKey( key, inlineTE );
			}
			// Resize the viewRect to hold the new character
			newRect = local_ESAdjustTERect( dataH, param->inRect, param->inIndent );
			if (!EqualRect(&oldRect,&newRect)) return blInlineEditKeyUpdate;
		}
		else SysBeep( 30 );
	}
	return blInlineEditKeyNoUpdate;
}

//===============================
static Boolean local_ESInlineEditClick( BLHandlerParamPtr param, EditStringHdl dataH )
{
	Point		where;
	Rect		textRect;
	TEHandle	inlineTE;
	
	inlineTE = (*dataH)->inlineTE;
	if (inlineTE)
	{
		textRect = local_ESInlineTextRect( dataH, param->inRect, param->inIndent );
		where = param->inEventWhereLocal;
		if (PtInRect(where,&textRect))
		{
			TEClick( where, false, inlineTE );
			return blInlineEditClickHandled;
		}
	}
	return blInlineEditClickNotHandled;
}

//===============================
static Boolean local_ESInlineEditEnd( EditStringHdl dataH )
{
	TEHandle	inlineTE;
	Boolean		answer = false;
	
	inlineTE = (*dataH)->inlineTE;
	(*dataH)->doingInline = false;
	if (inlineTE)
	{
		UInt8	stringLength;
		
		stringLength = ((*inlineTE)->teLength <= 31) ? (*inlineTE)->teLength : 31;
		// Check to see if the text changed
		if (IdenticalText(*(*inlineTE)->hText, &((*dataH)->string[1]),
							stringLength, (*dataH)->string[0], nil))
								answer = true;
		BlockMoveData( *((*inlineTE)->hText), &((*dataH)->string[1]), stringLength );
		(*dataH)->string[0] = stringLength;
		TEDispose( inlineTE );
		(*dataH)->inlineTE = nil;
	}
	return answer;
}


//===============================
static void local_ESDraw( EditStringHdl dataH, Rect cellRect, UInt16 indent )
{
	Rect	textRect;
	
	if ((*dataH)->doingInline)
	{
		TEHandle	te = (*dataH)->inlineTE;
		
		textRect = local_ESInlineTextRect( dataH, cellRect, indent );
		if (te)
		{
			Rect	teViewRect;
			
			teViewRect = (*te)->viewRect;
			// In rare circumstances, the text rectangle can move after the TE record is created.
			// Most of the time Blugs tries to prevent this from happening, but a public call
			//	like BLMakeVisible can scroll without ending the inline edit session!
			if (!EqualRect( &textRect, &teViewRect ))
			{
				Rect	destRect;
				
				destRect = textRect;
				destRect.right += kDestRectExtraRight;
				(*te)->viewRect = textRect;
				(*te)->destRect = destRect;
			}
			TEUpdate( &textRect, te );
		}
		local_ESDrawTextBox( textRect );
	}
	else BLDrawStringVerticallyCentered( (*dataH)->string, &cellRect, indent );
}

//===============================
static void local_ESDrawTextBox( Rect textRect )
{
	InsetRect( &textRect, -2, -2 );
	FrameRect( &textRect );
}

//===============================
static Boolean local_ESIdle( BLHandlerParamPtr param, EditStringHdl dataH )
{
	TEHandle	inlineTE;
	Boolean		answer = false;
	
	inlineTE = (*dataH)->inlineTE;
	if (inlineTE)
	{
		Rect	textRect;
		
		textRect = local_ESInlineTextRect( dataH, param->inRect, param->inIndent );
		if (PtInRect(param->inEventWhereLocal,&textRect))
		{
			SetCursor( *GetCursor( iBeamCursor ) );
			answer = true;
		}
		TEIdle( inlineTE );
	}
	return answer;
}

//===============================
static EditStringHdl local_ESAllocateStorage( void )
{
	return (EditStringHdl)NewHandleClear( sizeof(EditStringData) );
}

//===============================
static void local_ESGet( BLHandlerParamPtr param, EditStringHdl dataH )
{
	UInt32	bytes = 0;
	
	switch (param->ioDataFlavor)
	{
		case 'TEXT':
		if ((*dataH)->doingInline)
		{
			TEHandle	te;
			
			te = (*dataH)->inlineTE;
			if (te)
			{
				bytes = (param->ioDataSize < (*te)->teLength) ?
						param->ioDataSize : (*te)->teLength;
				if (param->ioDataBuffer) BlockMoveData( *((*te)->hText), param->ioDataBuffer, bytes );
			}
		}
		else
		{
			bytes = (param->ioDataSize < (*dataH)->string[0]) ?
						param->ioDataSize : (*dataH)->string[0];
			if (param->ioDataBuffer) BlockMoveData( &((*dataH)->string[1]), param->ioDataBuffer, bytes );
		}
		param->ioDataSize = bytes;
		break;
	}
}

//===============================
static void local_ESSet( BLHandlerParamPtr param, EditStringHdl dataH )
{
	UInt32	bytes;
	
	switch (param->ioDataFlavor)
	{
		case 'STR#':
		{
			SInt16		resID, strIndex;
			
			resID = HiWord(*(long*)param->ioDataBuffer);
			strIndex = LoWord(*(long*)param->ioDataBuffer);
			// Make sure index is not zero.
			if (strIndex) GetIndString( (*dataH)->string, resID, strIndex );
		}
		break;
		
		case 'TEXT':
		bytes = param->ioDataSize;
		if (bytes > 31) bytes = 31;
		if ((*dataH)->doingInline)
		{
			TEHandle	te;
			
			te = (*dataH)->inlineTE;
			if (te) TESetText( param->ioDataBuffer, bytes, te );
		}
		else
		{
			BlockMoveData( param->ioDataBuffer, &((*dataH)->string[1]), bytes );
			(*dataH)->string[0] = bytes;
		}
		break;
		
		case kKeyFilterFlavor:
		(*dataH)->keyFilter = *(BLKeyFilterProcPtr)(param->ioDataBuffer);
		break;
	}
}

//===============================
static SInt32 local_ESSort( EditStringHdl data1, BLHandlerParamPtr param )
{
	SInt16			result;
	SInt32			longResult = 0;
	EditStringHdl	data2;
	
	data2 = (EditStringHdl)param->inStorage2;
	if (data1 && data2)
	{
		result = CompareString( (*data1)->string, (*data2)->string, nil );
		longResult = result;
	}
	return longResult;
}

//===============================
static SInt32 local_ESSearch( BLHandlerParamPtr param )
{
	EditStringHdl	data;
	SInt32			longResult = 0;
	//TEHandle		te = nil;
	
	data = (EditStringHdl)param->ioStorage;
	if (data && param->ioDataBuffer)
	{
		SInt16		result;
			
		result = CompareText( &((*data)->string[1]), param->ioDataBuffer, (*data)->string[0],
							param->ioDataSize, nil );
		longResult = result;
	}
	return longResult;
}

//===============================
static Rect local_ESAdjustTERect( EditStringHdl dataH, Rect cellRect, UInt16 indent )
{
	Rect		newRect;
	TEHandle	inlineTE;
	
	inlineTE = (*dataH)->inlineTE;
	newRect = local_ESInlineTextRect( dataH, cellRect, indent );
	(*inlineTE)->viewRect = newRect;
	(*inlineTE)->destRect.right = newRect.right + kDestRectExtraRight;
	return newRect;
}

//===============================
static Rect local_ESInlineTextRect( EditStringHdl dataH, Rect cellRect, UInt16 indent )
{
	Rect		r;
	OSErr		err;
	Point		bounds = {0,0};
	SInt16		baseline;
	TEHandle	te;
	
	r = cellRect;
	r.left += indent;
	r.left--;
	te = (*dataH)->inlineTE;
	if (te)
	{
		Handle		textHdl;
		
		textHdl = (*te)->hText;
		HLock( textHdl );
		err = BLGetTextDimensions( *textHdl, (*te)->teLength, kThemeCurrentPortFont,
									kThemeStateActive, false, &bounds, &baseline );
		HUnlock( textHdl );
	}
	r.bottom = r.top + bounds.v;
	r.right = r.left + bounds.h + 1;
	BLAlignRectInRect( blAlignVCenter, false, &r, &cellRect );
	// Make sure the rectangle has some width, so we can hit-test with a zero-length string
	if (r.right <= r.left + 4) r.right = r.left + 4;
	if (r.right > cellRect.right - 3) r.right = cellRect.right - 3;
	return r;
}

//===============================
static Rect local_ESNormalTextRect( EditStringHdl dataH, const Rect* cellRect, UInt16 indent )
{
	Rect		r;
	OSErr		err;
	Point		bounds;
	SInt16		baseline;
	Str31		string;
	
	r = *cellRect;
	r.left += indent;
	// So we don't have to HLock the data handle.
	BlockMoveData( (*dataH)->string, string, (*dataH)->string[0] + 1 );
	err = BLGetTextDimensions( &string[1], string[0], kThemeCurrentPortFont,
								kThemeStateActive, false, &bounds, &baseline );
	r.bottom = r.top + bounds.v;
	r.right = r.left + bounds.h;
	if (r.right > cellRect->right) r.right = cellRect->right;
	BLAlignRectInRect( blAlignVCenter, false, &r, cellRect );
	// Make sure the rectangle has some width, so we can hit-test with a zero-length string
	if (r.right <= r.left + 4) r.right = r.left + 4;
	return r;
}

//===============================
static Boolean local_ESIsDisplayChar( UInt8 inChar )
{
	if ((inChar >= kLeftArrowCharCode && inChar <= kDownArrowCharCode) ||
		inChar == kDeleteCharCode || inChar == kBackspaceCharCode) return false;
	return true;
}

//===============================
static Boolean local_ESIsWithinMaximum( UInt8 inChar, TEHandle inTE )
{
	if (local_ESIsDisplayChar( inChar ))
	{
		if ((*inTE)->teLength - ((*inTE)->selEnd - (*inTE)->selStart) + 1 > kMaximumLength)
			return false;
	}
	return true;
}
