/*===============================
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"


typedef struct
{
	UInt32					command;
	Handle					data;
	SInt16					size;
	BLContentType			content;
	UInt16					flags;
	UInt16					reserved;
} BLWidgetRec, *BLWidgetPtr;


#define bl_NthWidgetPtr(n,handle)	((BLWidgetPtr)((Ptr)(*handle) + ((n - 1) * sizeof(BLWidgetRec))))

static void local_BLSetWidgetSize( Boolean inVertical, UInt16 inWidget,
									SInt16 inSize, BlugsRef inList );
static void local_BLDeselectAllWidgetsButOne( Boolean inVertical, UInt16 inWidget,
											BlugsRef inList );
static BLWidgetPtr local_BLGetWidgetDataPtr( Boolean inVertical, UInt16 inWidget, BlugsRef inList );
static void local_BLSetWidgetFlags( Boolean inVertical, UInt16 inWidget,
									UInt16 inWhichFlags, UInt16 inFlags, BlugsRef inList );


//===============================
BL_PROC OSErr BLAddWidgets( Boolean inVertical, UInt16 inCount,
							UInt16 inWidget, BlugsRef inList )
{
	OSErr	err;
		
	err = _BLValidateList( inList );
	if (!inWidget) err = inputOutOfBounds;
	if (!err && inCount)
	{
		UInt16	nWidgets;
		
		nWidgets = (inVertical) ? (*inList)->nVWidgets : (*inList)->nHWidgets;
		if (inWidget > nWidgets + 1) err = inputOutOfBounds;
		else
		{
			Handle	widgets;
			
			widgets = (inVertical) ? (*inList)->vWidgets : (*inList)->hWidgets;
			if (!widgets)
			{
				widgets = _VANew( 0, sizeof(BLWidgetRec), false );
				if (!widgets) err = memFullErr;
				else
				{
					if (inVertical) (*inList)->vWidgets = widgets;
					else (*inList)->hWidgets = widgets;
				}
			}
			if (!err) err = _VAAdd( inCount, inWidget, nWidgets, sizeof(BLWidgetRec), widgets, true );
			if (!err)
			{
				SInt16	defaultSize;
				UInt16	i;
				
				nWidgets += inCount;
				if (inVertical) (*inList)->nVWidgets = nWidgets;
				else (*inList)->nHWidgets = nWidgets;
				defaultSize = _BLScrollSize( inList );
				for (i = 1; i <= inCount; i++)
					local_BLSetWidgetSize( inVertical, inWidget + i - 1, defaultSize, inList );
			}
		}
	}
	return err;
}

//===============================	
BL_PROC OSErr BLDeleteWidgets( Boolean inVertical, UInt32 inCount,
								UInt32 inWidget, BlugsRef inList )
{
	OSErr		err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateWidget( inVertical, inWidget, inList );
		if (!err)
		{
			Handle		widgets;
			UInt32		entriesBelow, entriesAbove, neededSize;
			UInt16		nWidgets, newNWidgets, widget;
			
			nWidgets = (inVertical) ? (*inList)->nVWidgets : (*inList)->nHWidgets;
			widgets = (inVertical) ? (*inList)->vWidgets : (*inList)->hWidgets;
			if (!inCount)
			{
				inCount = nWidgets;
				inWidget = 1;
			}
			// If trying to delete more than the rest after inWidget, delete rest.
			else if (inWidget + inCount - 1 > nWidgets) inCount = nWidgets - inWidget + 1;
			for (widget = inWidget; widget <= inCount; widget++)
			{
				BLHandlerParamBlock	pb;
				
				(void)_BLCallHandlerForWidget( blCellDeinitMsg, inVertical, widget,
												&pb, inList );
			}
			entriesBelow = inWidget - 1;
			entriesAbove = nWidgets - inCount - entriesBelow;
			// Move first undeleted widget above, down to the first delete widget position.
			if (entriesAbove)
				BlockMoveData(	bl_NthWidgetPtr(inWidget + inCount,widgets),
								bl_NthWidgetPtr(inWidget,widgets),
								entriesAbove * sizeof(BLWidgetRec) );
			newNWidgets = nWidgets - inCount;
			neededSize = newNWidgets * sizeof(BLWidgetRec);
			SetHandleSize( widgets, neededSize );
			if (inVertical) (*inList)->nVWidgets = newNWidgets;
			else (*inList)->nHWidgets = newNWidgets;
			_BLAdjustScrollBarSizesAndLocations( inList );
			_BLUpdate( blUpdateBorder | blUpdateCorners, nil, inList );
		}
	}
	return err;
}

//===============================
BL_PROC UInt16 BLCountWidgets( Boolean inVertical, BlugsRef inList )
{
	UInt16	nWidgets = 0;
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err) nWidgets = (inVertical) ? (*inList)->nVWidgets : (*inList)->nHWidgets;
	return nWidgets;
}

//===============================
BL_PROC OSErr BLGetWidgetInfo( Boolean inVertical, UInt16 inWidget,
								BLWidgetInfo* outInfo, BlugsRef inList )
{
	OSErr		err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateWidget( inVertical, inWidget, inList );
		if (!err)
		{
			BLWidgetPtr		widgetP;
			
			widgetP = local_BLGetWidgetDataPtr( inVertical, inWidget, inList );
			outInfo->command = widgetP->command;
			outInfo->size = widgetP->size;
			outInfo->content = widgetP->content;
			outInfo->widgetFlags = widgetP->flags & blAllPublicWidgetFlags;
		}
	}
	return err;
}

//===============================
BL_PROC OSErr BLSetWidgetInfo( Boolean inVertical, UInt16 inWidget,
								UInt16 inWhichFields, const BLWidgetInfo* inInfo,
								BlugsRef inList )
{
	OSErr		err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateWidget( inVertical, inWidget, inList );
		if (!err)
		{
			BLWidgetPtr	widgetP;
			Handle		wh;
			
			wh = (inVertical) ? (*inList)->vWidgets : (*inList)->hWidgets;
			HLock( wh );
			widgetP = bl_NthWidgetPtr(inWidget,wh);
			if (bl_Test(inWhichFields,blSetWidgetCommand)) widgetP->command = inInfo->command;
			if (bl_Test(inWhichFields,blSetWidgetSize))
				local_BLSetWidgetSize( inVertical, inWidget, inInfo->size, inList );
			if (bl_Test(inWhichFields,blSetWidgetContentType))
			{
				BLContentType	oldContent;
			
				oldContent = widgetP->content;
				if (oldContent != inContent)
				{
					BLHandlerParamBlock	pb;
					
					_BLCallHandlerForWidget( blCellDeinitMsg, inVertical, inWidget, &pb, inList );
					widgetP->content = inInfo->content;
					if (inInfo->content) _BLCallHandlerForWidget( blCellInitMsg, inVertical, inWidget, &pb, inList );
				}
			}
			if (bl_Test(inWhichFields,blSetWidgetFlags))
				widgetP->flags = _BLProcessFlags( inInfo->whichWidgetFlags & blAllPublicWidgetFlags,
												inInfo->widgetFlags & blAllPublicWidgetFlags, widgetP->flags );
			HUnlock( wh );
			_BLUpdate( blUpdateBorder | blUpdateCorners, nil, inList );
		}
	}
	return err;
}


//===============================
static void local_BLSetWidgetSize( Boolean inVertical, UInt16 inWidget,
									SInt16 inSize, BlugsRef inList )
{
	Handle			widgets;
	UInt16			nWidgets;
	BLWidgetPtr		widgetP;
	Rect			maxArea;
	SInt32			maxSize;
	
	nWidgets = (inVertical) ? (*inList)->nVWidgets : (*inList)->nHWidgets;
	widgets = (inVertical) ? (*inList)->vWidgets : (*inList)->hWidgets;
	widgetP = bl_NthWidgetPtr(inWidget,widgets);
	// Make sure that resizing the widget will not make the scroll bar so
	//	small it is nonfunctional.
	// Take the scroll bar/widgets area, subtract out the smallest scroll bar
	//	possible, remainder is max size of all widgets.
	//	Add this widget's original size and subtract size of all widgets
	//	including this one.
	if (inVertical)
	{
		maxArea = _BLVScrollAndWidgetsRect( inList );
		maxSize = bl_Height(maxArea);
	}
	else
	{
		maxArea = _BLHScrollAndWidgetsRect( inList );
		maxSize = bl_Width(maxArea);
		
	}
	maxSize += widgetP->size;
	maxSize -= _BLWidgetsTotalSize( inVertical, inList );
	maxSize -= _BLMinScrollSize( inList );
	if (inSize <= maxSize)
	{
		RgnHandle	rgn;
		
		widgetP->size = inSize;
		_BLAdjustScrollBarSizesAndLocations( inList );
		//BLUpdate( nil, inList );
		rgn = NewRgn();
		if (rgn) RectRgn( rgn, &maxArea );
		_BLUpdate( blUpdateBorder | blUpdateCorners, rgn, inList );
		if (rgn) DisposeRgn( rgn );
	}
}

//===============================
BL_PROC OSErr BLGetWidgetData( Boolean inVertical, UInt16 inWidget, OSType inDataFlavor,
								UInt32* ioDataSize, void* outData, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateWidget( inVertical, inWidget, inList );
		if (!err)
		{
			BLHandlerParamBlock	pb;
			
			pb.ioDataBuffer = outData;
			pb.ioDataSize = *ioDataSize;
			pb.ioDataFlavor = inDataFlavor;
			_BLCallHandlerForWidget( blCellGetDataMsg, inVertical, inWidget, &pb, inList );
			*ioDataSize = pb.ioDataSize;
		}
	}
	if (err) *ioDataSize = 0; // Do we want to do this?
	return err;
}

//===============================
BL_PROC OSErr BLSetWidgetData( Boolean inVertical, UInt16 inWidget, OSType inDataFlavor,
								UInt32 inDataSize, void* inData, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateWidget( inVertical, inWidget, inList );
		if (!err)
		{
			if (inData)
			{
				BLHandlerParamBlock	pb;
				Rect				r;
				RgnHandle			rgn = NewRgn();
				
				pb.ioDataBuffer = inData;
				pb.ioDataSize = inDataSize;
				pb.ioDataFlavor = inDataFlavor;
				pb.inIndent = 0;
				BLGetWidgetRect( inVertical, inWidget, &pb.inRect, inList );
				_BLCallHandlerForWidget( blCellSetDataMsg, inVertical, inWidget, &pb, inList );
				err = BLGetWidgetRect( inVertical, inWidget, &r, inList );
				if (!err)
				{
					RectRgn( rgn, &r );
					_BLUpdate( blUpdateBorder | blUpdateCorners, rgn, inList );
				}
				DisposeRgn( rgn );
			}
			else
			{
				err = paramErr;
				_AssertStr(false,"Error: nil data pointer.");
			}
		}
	}
	return err;
}

//===============================
BL_PROC void BLClearWidget( Boolean inVertical, UInt16 inWidget, BlugsRef inList )
{
	OSErr	err;
	
	_CheckHandlerFlag(inList)
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateWidget( inVertical, inWidget, inList );
		if (!err)
		{
			BLHandlerParamBlock	pb;
			Rect				r;
			
			_BLCallHandlerForWidget( blCellSetDataMsg, inVertical, inWidget, &pb, inList );
			err = BLGetWidgetRect( inVertical, inWidget, &r, inList );
			_BLUpdate( blUpdateBorder | blUpdateCorners, nil, inList );
		}
	}
}

//===============================
BL_PROC OSErr BLGetWidgetRect( Boolean inVertical, UInt16 inWidget,
								Rect* outRect, BlugsRef inList )
{
	OSErr		err;
	
	err = _BLValidateList( inList );
	if (!err) err = _BLGetWidgetRect( inVertical, inWidget, outRect, inList );
	return err;
}

//===============================
OSErr _BLGetWidgetRect( Boolean inVertical, UInt16 inWidget, Rect* outRect, BlugsRef inList )
{
	OSErr	err;
	Rect	r = {0,0,0,0};
	
	err = _BLValidateList( inList );
	if (!err)
	{
		err = _BLValidateWidget( inVertical, inWidget, inList );
		if (!err)
		{
			UInt16			i;
			BLWidgetPtr		widgetP;
			UInt16			nWidgets;
			
			if (inVertical) r = _BLVScrollRect( inList );
			else r = _BLHScrollRect( inList );
			nWidgets = (inVertical) ? (*inList)->nVWidgets : (*inList)->nHWidgets;
			widgetP = local_BLGetWidgetDataPtr( inVertical, nWidgets, inList );
			for (i = nWidgets; i >= 1; i--, widgetP--)
			{
				if (inVertical)
				{
					r.bottom = r.top;
					r.top -= widgetP->size;
				}
				else
				{
					r.right = r.left;
					r.left -= widgetP->size;
				}
				if (i == inWidget) break;
			}
		}
	}
	*outRect = r;
	return err;
}

//===============================
BLContentType _BLGetWidgetContentType( Boolean inVertical, UInt16 inWidget, BlugsRef inList )
{
	BLContentType	content = 0;
	BLWidgetPtr		widgetP;
		
	widgetP = local_BLGetWidgetDataPtr( inVertical, inWidget, inList );
	if (widgetP) content = widgetP->content;
	return content;
}


//===============================
UInt16 _BLGetWidgetFlags( Boolean inVertical, UInt16 inWidget, BlugsRef inList )
{
	UInt16			flags = 0;
	BLWidgetPtr		widgetP;
		
	widgetP = local_BLGetWidgetDataPtr( inVertical, inWidget, inList );
	if (widgetP) flags = widgetP->flags;
	return flags;
}

//===============================
SInt16 _BLWidgetsTotalSize( Boolean inVertical, BlugsRef inList )
{
	SInt16	size = 0;
	UInt16	nWidgets;
	
	nWidgets = (inVertical) ? (*inList)->nVWidgets : (*inList)->nHWidgets;
	if (nWidgets)
	{
		Handle	widgets;
		
		widgets = (inVertical) ? (*inList)->vWidgets : (*inList)->hWidgets;
		if (widgets)
		{
			UInt16			i;
			BLWidgetPtr		widgetP;
			
			widgetP = bl_NthWidgetPtr(1,widgets);
			for (i = 1; i <= nWidgets; i++, widgetP++) size += widgetP->size;
		}
	}
	return size;
}


//===============================
BLPart _BLHitTestWidgets( Boolean inVertical, Point inWhere,
							BLHitTestPtr ioHitTest, BlugsRef inList )
{
	UInt16	nWidgets;
	BLPart	part = blNoPart;
	
	nWidgets = BLCountWidgets( inVertical, inList );
	if (nWidgets)
	{
		Rect	r;
		
		r = (inVertical) ? _BLVScrollAndWidgetsRect( inList ) :
							_BLHScrollAndWidgetsRect( inList );
		if (PtInRect( inWhere, &r))
		{
			UInt16		i;
			
			for (i = 1; i <= nWidgets; i++)
			{
				OSErr	err;
				
				err = BLGetWidgetRect( inVertical, i, &r, inList );
				if (!err)
				{
					if (PtInRect(inWhere,&r))
					{
						part = blWidgetPart;
						ioHitTest->rect = r;
						if (inVertical)
						{
							ioHitTest->cell.col = 0;
							ioHitTest->cell.row = i;
						}
						else
						{
							ioHitTest->cell.col = i;
							ioHitTest->cell.row = 0;
						}
						break;
					}
				}
			}
		}
	}
	return part;
}

//===============================
// Current GWorld is the host window.
void _BLTrackWidgetClick( BLHitTestPtr inTest, Point inWhere, EventModifiers inModifiers,
							BlugsRef inList )
{
	BLContentType			content;
	Point					mouse;
	Boolean					vertical, insideRect = true, callNotification = false;
	MouseTrackingResult		result;
	Boolean					firstTimeThru = true; // Don't play enter/leave sound until state changes.
	ThemeDrawState			state = kThemeStateActive;
	UInt16					widget, flags;
	UInt32					command = 0;
	
	if (inTest->cell.col == 0)
	{
		vertical = true;
		widget = inTest->cell.row;
	}
	else
	{
		vertical = false;
		widget = inTest->cell.col;
	}
	flags = _BLGetWidgetFlags( vertical, widget, inList );
	content = _BLGetWidgetContentType( vertical, widget, inList );
	if (!bl_Test(flags,blWidgetInert))
	{
		Boolean	doTrackingLoop = true;
		
		if (content)
		{
			OSErr			err;
			UInt32			features;
			
			err = _BLGetHandlerFeatures( content, &features );
			if (!err)
			{
				if (bl_Test(features,blWantsClick))
				{
					RgnHandle			saveClip = NewRgn();
					BLHandlerParamBlock	pb;
					UInt32				reply;
					
					pb.inPart = blWidgetPart;
					pb.inRect = inTest->rect;
					pb.inEventWhereLocal = inWhere;
					pb.inEventModifiers = inModifiers;
					pb.inIndent = 0;
					GetClip( saveClip );
					ClipRect( &inTest->rect );
					reply = _BLCallHandlerForWidget( blCellClickMsg, vertical, widget, &pb, inList );
					SetClip( saveClip );
					DisposeRgn( saveClip );
					if (bl_Test(reply,blCellClickUpdate))
						_BLDrawWidget( vertical, widget, &inTest->rect, kThemeStateActive, inList );
					if (bl_Test(reply,blCellClickHandled))
					{
						command = pb.ioCommand;
						callNotification = true;
						doTrackingLoop = false;
					}
				}
			}
		}
		if (doTrackingLoop)
		{
			if (bl_AllegroAppearance) (void)PlayThemeSound( kThemeSoundBevelPress );
			do
			{
				if (insideRect != (state == kThemeStatePressed))
				{
					if (state == kThemeStatePressed) state = kThemeStateActive;
					else state = kThemeStatePressed;
					_BLDrawWidget( vertical, widget, &inTest->rect, state, inList );
					if (!firstTimeThru && bl_AllegroAppearance)
						(void)PlayThemeSound( (insideRect) ? kThemeSoundBevelEnter : kThemeSoundBevelExit );
				}
				_BLTrackMouseLocation( &mouse, &result );
				insideRect = PtInRect(mouse,&inTest->rect);
				firstTimeThru = false;
			} while (result != kMouseTrackingMouseReleased);
			if (insideRect)
			{
				if (bl_AllegroAppearance) (void)PlayThemeSound( kThemeSoundBevelRelease );
				callNotification = true;
				// Now do lots of fiddlybits stuff with flags.
				if (bl_Test(flags,blWidgetOn | blWidgetSticky | blWidgetToggles))
					bl_Clear(flags,blWidgetOn);
				else if (bl_Test(flags,blWidgetSticky)) bl_Set(flags,blWidgetOn);
				if (bl_Test(flags,blWidgetOn | blWidgetRadioGroup))
					local_BLDeselectAllWidgetsButOne( vertical, widget, inList );
			}
		}
		_BLUpdate( blUpdateBorder | blUpdateCorners, nil, inList );
	}
	if (callNotification)
	{
		BLNotificationProcPtr	proc;
		
		proc = (*inList)->callbacks.notificationProc;
		if (proc) (*proc)( blWidgetClickedNotificationMsg, command, blWidgetPart, inTest->cell, inList );
	}
}

//===============================
Handle _BLGetWidgetStorage( Boolean inVertical, UInt16 inWidget, BlugsRef inList )
{
	OSErr	err;
	Handle	storage = nil;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		Handle	widgets;
		UInt16	nWidgets;
		
		nWidgets = (inVertical) ? (*inList)->nVWidgets : (*inList)->nHWidgets;
		widgets = (inVertical) ? (*inList)->vWidgets : (*inList)->hWidgets;
		if (nWidgets && widgets && inWidget && inWidget <= nWidgets)
		{
			BLWidgetPtr		widgetP;
			
			widgetP = bl_NthWidgetPtr(inWidget,widgets);
			storage = widgetP->data;
		}
	}
	return storage;
}

//===============================
OSErr _BLSetWidgetStorage( Boolean inVertical, UInt16 inWidget,
							Handle inStorage, BlugsRef inList )
{
	OSErr	err;
	
	err = _BLValidateList( inList );
	if (!err)
	{
		Handle	widgets;
		UInt16	nWidgets;
		
		nWidgets = (inVertical) ? (*inList)->nVWidgets : (*inList)->nHWidgets;
		widgets = (inVertical) ? (*inList)->vWidgets : (*inList)->hWidgets;
		if (!nWidgets || !widgets || !inWidget || inWidget > nWidgets) err = inputOutOfBounds;
		else
		{
			BLWidgetPtr		widgetP;
			
			widgetP = bl_NthWidgetPtr(inWidget,widgets);
			widgetP->data = inStorage;
		}
	}
	return err;
}

//===============================
static void local_BLDeselectAllWidgetsButOne( Boolean inVertical, UInt16 inWidget,
											BlugsRef inList )
{
	UInt16	nWidgets;
	Handle	widgets;
	
	nWidgets = (inVertical) ? (*inList)->nVWidgets : (*inList)->nHWidgets;
	widgets = (inVertical) ? (*inList)->vWidgets : (*inList)->hWidgets;
	if (nWidgets)
	{
		UInt16		i;
		BLWidgetPtr	widgetP;
		
		widgetP = bl_NthWidgetPtr(1,widgets);
		for (i = 1; i <= nWidgets; i++,widgetP++)
		{
			if (i != inWidget) bl_Clear(widgetP->flags,blWidgetOn);
		}
	}
}

//===============================
static BLWidgetPtr local_BLGetWidgetDataPtr( Boolean inVertical, UInt16 inWidget, BlugsRef inList )
{
	BLWidgetPtr	p = nil;
	Handle		widgets;
	
	widgets = (inVertical) ? (*inList)->vWidgets : (*inList)->hWidgets;
	if (widgets) p = bl_NthWidgetPtr( inWidget, widgets );
	return p;
}

//===============================
static void local_BLSetWidgetFlags( Boolean inVertical, UInt16 inWidget,
									UInt16 inWhichFlags, UInt16 inFlags, BlugsRef inList )
{
	BLWidgetPtr	widgetP;
		
	widgetP = local_BLGetWidgetDataPtr( inVertical, inWidget, inList );
	if (widgetP) widgetP->flags = _BLProcessFlags( inWhichFlags, inFlags, widgetP->flags );
}