/*===============================
|	CheckboxHandler.c
|
|	Blugs 1.1 handler for a color palette pop-up menu
|	www.blugs.com
|
|	Copyright © 1998-2001 Brian Hall
|	Portions (Color MDEF code) slightly modified from material 
|		© 1995 Jordan Zimmerman
|	You may use these routines in your software without restriction.
|
|===============================*/

#ifdef __APPLE_CC__
#include <Carbon/Carbon.h>
#else
#ifndef __MENUS__
#include <Menus.h>
#endif

#ifndef __GESTALT__
#include <Gestalt.h>
#endif

#ifndef __COLORPICKER__
#include <ColorPicker.h>
#endif

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

#include "ColorPopupHandler.h"
#include "BlugsAppearanceExtras.h"
//#include "StubMDEF.h"
#include "ColorMenu.h"

#if !__BL_USE_CARBON_API__
	#pragma options align=mac68k
	typedef struct
	{
		SInt16			jump; /* 4EF9 */
		MenuDefUPP		upp; /* 00000000 */
	} **StubMDEFResource;
	#pragma options align=reset
#endif



#define _NumToHex(n)			(((n<10) ? (n + '0') : (n - 10 + 'A')))
#define _HexToNum(hexChar)		((hexChar < 'A') ? (hexChar - '0') : (hexChar - 'A' + 10))
#define kSwatchWidth			24


static ColorPopupHdl	local_ColorPopupAllocateStorage( void );
static void				local_ColorPopupSet( void* dataP, OSType flavor, ColorPopupHdl cph );
static void				local_ColorPopupGet( ColorPopupHdl cph, BLHandlerParamPtr param );
static void				local_ColorPopupDraw( Rect r, ColorPopupHdl cph, Boolean selected,
									Boolean hiliteSwatch, BlugsRef list );
static Rect				local_ColorPopupButtonRect( Rect cellRect );
static Rect				local_ColorPopupSwatchRect( Rect buttonRect );
static UInt32			local_ColorPopupClick( Rect r, ColorPopupHdl cph, Point mouse, BlugsRef list );
static UInt32			local_ColorPopupCmdClick( Rect r, ColorPopupHdl cph, Point mouse, BlugsRef list );
static Rect				local_ColorPopupButtonRect( Rect cellRect );
static void				local_ColorPopupPstrToColor( Str31 str, RGBColor* color );
static void				local_ColorPopupColorToPstr( RGBColor color, Str31 str );
static void				local_ColorPopupSetMenuItemColor( MenuHandle menu, SInt16 itemNumber, RGBColor color );
static void				local_ColorPopupGetMenuItemColor( MenuHandle menu, SInt16 itemNumber, RGBColor* color);
static void				local_ColorPopupColorPickerVersion( SInt16* outVersion );
static Boolean			local_ColorPopupPickColor( RGBColor oldColor, RGBColor* newColor );

static SInt16			gColorPickerVersion = 0;


//===============================
pascal UInt32 ColorPopupHandler(	BLMessage			message,
									BLHandlerParamPtr	param		)
{
	UInt32			response = 0;
	Rect			buttonRect;
	ColorPopupHdl	cph = nil;
	
	if (param) cph = (ColorPopupHdl)param->ioStorage;
	switch (message)
	{
		case blCellDrawMsg:
		local_ColorPopupDraw( param->inRect, cph, false, false, param->inList );
		break;
		
		case blHandlerInitMsg:
	#if !__BL_USE_CARBON_API__
		{
			StubMDEFResource	res;
			
			res = (StubMDEFResource)GetResource( 'MDEF', 5000 );
			(*res)->upp = NewMenuDefUPP( &ColorMenu );
		}
	#endif
		local_ColorPopupColorPickerVersion( &gColorPickerVersion );
		response = blWantsClick | blWantsMin | blWantsHilite;
		break;
		
		case blCellInitMsg:
		param->ioStorage = (Handle)local_ColorPopupAllocateStorage();
		break;
		
		case blCellRegionMsg:
		buttonRect = local_ColorPopupButtonRect( param->inRect );
		RectRgn( param->inRegion, &buttonRect );
		break;
		
		case blCellClickMsg:
		{
			Rect		rect = param->inRect;
			BlugsRef	list = param->inList;
			Point		where = param->inEventWhereLocal;
			
			// If we want to activate the color picker on cmd click...
			if ((param->inVariationCode & kPickColorOnCmdClick) &&
				(param->inEventModifiers & cmdKey) &&
				(gColorPickerVersion > 0))
				response = local_ColorPopupCmdClick( rect, cph, where, list );
			else response = local_ColorPopupClick( rect, cph, where, list );
		}
		break;
		
		case blCellSetDataMsg:
		if (cph) local_ColorPopupSet( param->ioDataBuffer, param->ioDataFlavor, cph );
		break;
		
		case blCellGetDataMsg:
		if (cph) local_ColorPopupGet( cph, param );
		break;
		
		case blCellClearDataMsg:
		if (cph)
		{
			(*cph)->color.red = (*cph)->color.green = (*cph)->color.blue = 0;
			(*cph)->selectedItem = 0;
		}
		break;
		
		case blCellDeinitMsg:
		if (cph)
		{
			if ((*cph)->menu) DisposeMenu( (*cph)->menu );
			DisposeHandle( (Handle)cph );
		}
		break;
		
		case blCellMinSizeMsg:
		param->outMinWidth = 60;
		param->outMinHeight = 24;
		break;
	}
	return response;
}

//===============================
static ColorPopupHdl local_ColorPopupAllocateStorage( void )
{
	ColorPopupHdl	cph;
	MenuHandle		menu = nil;
	
	cph = (ColorPopupHdl)NewHandle( sizeof(ColorPopupRec) );
	if (cph)
	{
		SInt16		colorQty, i;
		RGBColor	thisColor;
		
	#if __BL_USE_CARBON_API__
		OSStatus	status;
		MenuDefSpec	defSpec;
		
		defSpec.defType = kMenuDefProcPtr;
		defSpec.u.defProc = NewMenuDefUPP( ColorMenu );
		status = RegisterMenuDefinition( kColorMenuResID, &defSpec );
	#endif
		menu = GetMenu( kColorMenuResID );
		if (menu)
		{
			CTabHandle	cTH;
			
		//#if !__BL_USE_CARBON_API__
		//	StubMDEF_SetUPP(menu,NewMenuDefUPP( ColorMenu ));
		//#endif
			
			cTH = GetCTable(8 + 64);
			if (cTH)
			{
				HNoPurge( (Handle)cTH );
				colorQty = (*cTH)->ctSize + 1;
				for (i = 1; i <= colorQty; ++i)
				{
					AppendMenu( menu, "\p-" );
					thisColor = (*cTH)->ctTable[i-1].rgb;
					local_ColorPopupSetMenuItemColor( menu, i, thisColor );
				}
				InsertMenu( menu, -1 );
				DisposeCTable( cTH );
			}
		}
	}
	(*cph)->menu = menu;
	return cph;
}

//===============================
static void local_ColorPopupSet( void* dataP, OSType flavor, ColorPopupHdl cph )
{
	switch (flavor)
	{
		case kColorPopupRGBColorFlavor:
		BlockMoveData( dataP, &((*cph)->color), sizeof(RGBColor) );
		break;
	}
}

//===============================
static void local_ColorPopupGet( ColorPopupHdl cph, BLHandlerParamPtr param )
{
	UInt32	bytes = 0;
	
	switch (param->ioDataFlavor)
	{
		case kColorPopupRGBColorFlavor:
		bytes = sizeof(RGBColor);
		if (param->ioDataBuffer)
		{
			if (param->ioDataSize < bytes) bytes = 0;
			else BlockMoveData( &((*cph)->color), param->ioDataBuffer, bytes );
		}
		break;
	}
	param->ioDataSize = bytes;
}


//===============================
static void local_ColorPopupDraw( Rect r, ColorPopupHdl cph, Boolean selected, Boolean hiliteSwatch,
						BlugsRef list )
{
	Rect			swatchRect, buttonRect, arrowRect;
	RGBColor		blk = {0,0,0}, swatchCol;
	ThemeDrawState	state = kThemeStateActive;
	Boolean			active;
	
	if (!cph) return;
	buttonRect = local_ColorPopupButtonRect( r );
	active = BLIsActive( list );
	if (selected) state = kThemeStatePressed;
	else if (!active) state = kThemeStateInactive;
	BLDrawBevelButton( &buttonRect, state, kThemeButtonOff );
	swatchRect = local_ColorPopupSwatchRect( buttonRect );
	swatchCol = (*cph)->color;
	if (active)
	{
		RGBColor		ltCol, dkCol, redCol = {0xFFFF,0x1010,0x1010};
		
		RGBForeColor( &swatchCol );
		PaintRect( &swatchRect );
		dkCol.red = (swatchCol.red >= 0x2000) ? swatchCol.red - 0x2000 : 0x0000;
		dkCol.green = (swatchCol.green >= 0x2000) ? swatchCol.green - 0x2000 : 0x0000;
		dkCol.blue = (swatchCol.blue >= 0x2000) ? swatchCol.blue - 0x2000 : 0x0000;
		ltCol.red = (swatchCol.red <= 0xDFFF) ? swatchCol.red + 0x2000 : 0xFFFF;
		ltCol.green = (swatchCol.green <= 0xDFFF) ? swatchCol.green + 0x2000 : 0xFFFF;
		ltCol.blue = (swatchCol.blue <= 0xDFFF) ? swatchCol.blue + 0x2000 : 0xFFFF;
		MoveTo( swatchRect.left, swatchRect.bottom - 1 );
		RGBForeColor( &dkCol );
		LineTo( swatchRect.left, swatchRect.top );
		LineTo( swatchRect.right - 1, swatchRect.top );
		RGBForeColor( &ltCol );
		LineTo( swatchRect.right - 1, swatchRect.bottom - 1 );
		LineTo( swatchRect.left + 1, swatchRect.bottom - 1 );
		PenSize( 1, 1 );
		RGBForeColor( &blk );
		InsetRect( &swatchRect, -1, -1 );
		FrameRect( &swatchRect );
		if (hiliteSwatch)
		{
			InsetRect( &swatchRect, -2, -2 );
			RGBForeColor( &redCol );
			PenSize( 2, 2 );
			FrameRect( &swatchRect );
			PenSize( 1, 1 );
		}
	}
	else
	{
		// For an interesting effect, let's try to gray out the swatch color.
		// Average the three color values, then shift color values in that direction by
		//	half the difference.
		UInt32	total;
		
		total = swatchCol.red + swatchCol.green + swatchCol.blue;
		total /= 3;
		if (total > swatchCol.red) swatchCol.red += (total - swatchCol.red) >> 1;
		else swatchCol.red -= (swatchCol.red - total) >> 1;
		if (total > swatchCol.green) swatchCol.green += (total - swatchCol.green) >> 1;
		else swatchCol.green -= (swatchCol.green - total) >> 1;
		if (total > swatchCol.blue) swatchCol.blue += (total - swatchCol.blue) >> 1;
		else swatchCol.blue -= (swatchCol.blue - total) >> 1;
		RGBForeColor( &swatchCol );
		InsetRect( &swatchRect, -1, -1 );
		PaintRect( &swatchRect );
	}
	arrowRect = buttonRect;
	arrowRect.left = buttonRect.right - 14;
	arrowRect.top = buttonRect.bottom - 10;
	BLDrawPopupArrow( arrowRect, kThemeArrowDown, kThemeArrow9pt, state );
}

//===============================
// Let's make the button 20 pixels high and 56 pixels wide, if possible.
#define kButtonHeight	20
#define kButtonWidth	56

static Rect local_ColorPopupButtonRect( Rect cellRect )
{
	Rect	buttonRect = cellRect;
	UInt16	cellHt, cellWd;
	
	cellWd = cellRect.right - cellRect.left;
	cellHt = cellRect.bottom - cellRect.top;
	if (cellWd > kButtonWidth)
	{
		buttonRect.left = cellRect.left + 4;
		buttonRect.right = buttonRect.left + kButtonWidth;
	}
	if (cellHt > kButtonHeight)
	{
		buttonRect.top = cellRect.top + ((cellRect.bottom - cellRect.top - kButtonHeight) / 2);
		buttonRect.bottom = buttonRect.top + kButtonHeight;
	}
	return buttonRect;
}

//===============================
static Rect local_ColorPopupSwatchRect( Rect buttonRect )
{
	Rect	swatchRect;
	
	swatchRect = buttonRect;
	InsetRect( &swatchRect, 6, 6 );
	swatchRect.right = swatchRect.left + kSwatchWidth;
	return swatchRect;
}

//===============================
static UInt32 local_ColorPopupClick( Rect r, ColorPopupHdl cph, Point mouse, BlugsRef list )
{
	UInt32		response = 0;
	
	if (cph)
	{
		Rect		buttonRect;
		
		buttonRect = local_ColorPopupButtonRect( r );
		if (PtInRect( mouse, &buttonRect ))
		{
			SInt16		itemN;
			SInt32		result;
			RGBColor	currentColor;
			Point		globalPt;
			MenuHandle	menu;
			
			menu = (*cph)->menu;
			local_ColorPopupDraw( r, cph, true, false, list );
			InsetRect( &r, 4, 4 );
			CalcMenuSize( menu );
			globalPt.h = buttonRect.left + 2;
			globalPt.v = buttonRect.bottom;
			LocalToGlobal( &globalPt );
			SetItemMark( menu, (*cph)->selectedItem, 1 );	
			result = PopUpMenuSelect( menu, globalPt.v, globalPt.h, (*cph)->selectedItem );
			if (result)
			{
				itemN = LoWord(result);
				// Remove mark from old selection
				SetItemMark( menu, (*cph)->selectedItem, 0 );	
				// change the current color
				local_ColorPopupGetMenuItemColor( menu, itemN, &currentColor );
				(*cph)->color = currentColor;
				(*cph)->selectedItem = itemN;
			}
			response = blCellClickHandled | blCellClickUpdate;
		}
	}
	return response;
}

//===============================
// Returns true if update is needed
static UInt32 local_ColorPopupCmdClick( Rect r, ColorPopupHdl cph, Point mouse, BlugsRef list )
{
	RGBColor	newColor;
	Rect		buttonRect, swatchRect;
	Boolean		needUpdate = true, wasIn = false;
	
	buttonRect = local_ColorPopupButtonRect( r );
	swatchRect = local_ColorPopupSwatchRect( buttonRect );
	if (PtInRect(mouse, &swatchRect))
	{
		while (StillDown())
		{
			GetMouse( &mouse );
			if (PtInRect(mouse, &swatchRect))
			{
				if (!wasIn)
				{
					local_ColorPopupDraw( r, cph, true, true, list );
					wasIn = true;
				}
			}
			else
			{
				if (wasIn)
				{
					local_ColorPopupDraw( r, cph, true, false, list );
					wasIn = false;
				}
			}
		}
		GetMouse( &mouse );
		if (PtInRect(mouse, &swatchRect))
		{
			if (local_ColorPopupPickColor( (*cph)->color, &newColor )) (*cph)->color = newColor;
			else needUpdate = false;
		}
	}
	else needUpdate = local_ColorPopupClick( r, cph, mouse, list );
	return needUpdate;
}

//===============================
// set the given menu item to the given color
static void local_ColorPopupSetMenuItemColor( MenuHandle menu, SInt16 itemNumber, RGBColor color )
{
	Str31	colorStr;
	
	local_ColorPopupColorToPstr( color, colorStr );
	SetMenuItemText( menu, itemNumber, colorStr );
}

//===============================
// return the color for the given menu item
static void local_ColorPopupGetMenuItemColor( MenuHandle menu, SInt16 itemNumber, RGBColor* color )
{
	Str255	colorStr;
	
	GetMenuItemText( menu, itemNumber, colorStr );
	local_ColorPopupPstrToColor( colorStr, color );
}

// convert the given formatted 12 byte pstring into an RGBColor
// the pstring should be in the form RRRRGGGGBBBB where
// RRRR, GGGG, and BBBB are each ASCII four byte hex numbers
static void local_ColorPopupPstrToColor( Str31 str, RGBColor* color )
{
	color->red = _HexToNum( str[4] );
	color->red += _HexToNum( str[3] ) * 16;
	color->red += _HexToNum( str[2] ) * 256;
	color->red += _HexToNum( str[1] ) * 4096;
	
	color->green = _HexToNum( str[8] );
	color->green += _HexToNum( str[7] ) * 16;
	color->green += _HexToNum( str[6] ) * 256;
	color->green += _HexToNum( str[5] ) * 4096;
	
	color->blue = _HexToNum( str[12] );
	color->blue += _HexToNum( str[11] ) * 16;
	color->blue += _HexToNum( str[10] ) * 256;
	color->blue += _HexToNum( str[9] ) * 4096;
}

//===============================
// convert the given RGBColor into a 12 byte formatted pstring
static void local_ColorPopupColorToPstr( RGBColor color, Str31 str )
{
	UInt8	n;
	
	str[0] = 12;
	n = (color.red >> 12) & 0x0F;
	str[1] = _NumToHex(n);
	n = (color.red >> 8) & 0x0F;
	str[2] = _NumToHex(n);
	n = (color.red >> 4) & 0x0F;
	str[3] = _NumToHex(n);
	n = color.red & 0x0F;
	str[4] = _NumToHex(n);
	n = (color.green >> 12) & 0x0F;
	str[5] = _NumToHex(n);
	n = (color.green >> 8) & 0x0F;
	str[6] = _NumToHex(n);
	n = (color.green >> 4) & 0x0F;
	str[7] = _NumToHex(n);
	n = color.green & 0x0F;
	str[8] = _NumToHex(n);
	n = (color.blue >> 12) & 0x0F;
	str[9] = _NumToHex(n);
	n = (color.blue >> 8) & 0x0F;
	str[10] = _NumToHex(n);
	n = (color.blue >> 4) & 0x0F;
	str[11] = _NumToHex(n);
	n = color.blue & 0x0F;
	str[12] = _NumToHex(n);
}


//===============================
static void local_ColorPopupColorPickerVersion( SInt16* outVersion )
{
	OSErr	err;
	SInt32	response;
	SInt16	version = 0;
	
	err = Gestalt( gestaltColorPickerVersion, &response );
	if (!err) version = LoWord(response);
	*outVersion = version;
}


//===============================
static Boolean local_ColorPopupPickColor( RGBColor oldColor, RGBColor* newColor )
{
	Str31	prompt = "\pPlease pick a color.";
	Point	where = {0,0};
	GrafPtr	savePort;
	Boolean	changed;
	
	GetPort( &savePort );
	changed = GetColor( where, prompt, &oldColor, newColor );
	SetPort( savePort );
	return changed;
}
