/*//////////////////////////////////////////////////////////////////////////////
// ExtraMessageBox
// 
// Copyright © 2006  Sebastian Pipping <webmaster@hartwork.org>
// 
// -->  http://www.hartwork.org
// 
// This source code is released under the GNU General Public License (GPL).
// See GPL.txt for details. Any non-GPL usage is strictly forbidden.
//////////////////////////////////////////////////////////////////////////////*/


/*
TODO
* realign/recenter after height change
* tab stop order when adding buttons
* offer extra callback?
* auto click timer (one button after XXX seconds)
* allow several checkboxes? radio buttons?

MB_YESNO
MB_YESNOCANCEL 
--> MB_YESNOALL
--> MB_YESNOCANCELALL
--> MB_DEFBUTTON5
--> IDNOALL
--> IDYESALL
*/


#include "Emabox.h"



#define FUNCTION_NORMAL    0
#define FUNCTION_EXTENDED  1
#define FUNCTION_INDIRECT  2



const int SPACE_UNDER_CHECKBOX  = 10;
const int SPACE_EXTRA_BOTTOM    = 4;

TCHAR * const szNeverAgain      = TEXT( "Do not show again" );
TCHAR * const szRememberChoice  = TEXT( "Remember my choice" );

DWORD dwTlsSlot = TLS_OUT_OF_INDEXES;

#ifdef EMA_AUTOINIT
int bEmaInitDone = 0;
#endif



struct StructEmaBoxData
{
	int * bCheckState;
	HHOOK hCBT;                   /* CBT hook handle */
	WNDPROC WndprocMsgBoxBackup;  /* Old wndproc */
	UINT uType;                   /* Message box type */
	HWND hCheck;                  /* Checkbox handle */
};

typedef struct StructEmaBoxData EmaBoxData;



void RectScreenToClient( const HWND h, RECT * const r )
{
	POINT p;
	RECT after;

	p.x = r->left;
	p.y = r->top;
	ScreenToClient( h, &p );
	
	after.left    = p.x;
	after.right   = p.x + r->right - r->left;
	after.top     = p.y;
	after.bottom  = p.y + r->bottom - r->top;
	
	memcpy( r, &after, sizeof( RECT ) );
}



LRESULT CALLBACK WndprocMsgBox( HWND hwnd, UINT message, WPARAM wp, LPARAM lp )
{
	/* Find data */
	EmaBoxData * const data = ( EmaBoxData * )TlsGetValue( dwTlsSlot );
	
	switch( message )
	{
	case WM_COMMAND:
		if( HIWORD( wp ) == BN_CLICKED )
		{
			if( !data->hCheck || ( ( HWND )lp != data->hCheck ) ) break;
			
			{
				const LRESULT res = SendMessage( ( HWND )lp, BM_GETSTATE, 0, 0 );
				const int bCheckedAfter = ( ( res & BST_CHECKED ) == 0 );
				
				/* Update external variable */
				*( data->bCheckState ) = bCheckedAfter ? 1 : 0;
				
				SendMessage( ( HWND )lp, BM_SETCHECK, ( bCheckedAfter ) ? BST_CHECKED : 0, 0 );
			}
		}
		break;
		
	case WM_INITDIALOG:
		{
			/* Add checkbox */
			if( ( data->uType & MB_CHECKMASC ) != 0 )
			{
				int SPACE_OVER_CHECKBOX;
				HDC hdc;
				RECT rw;                 /* Window rect     */
				RECT rc;                 /* Client rect     */
				HWND hText;              /* Message handle  */
				RECT rt;                 /* Message rect    */
				int iLabelHeight;
				TCHAR * szCheckboxLabel;  /* Checkbox label */

				int iWindowWidthBefore;
				int iWindowHeightBefore;
				int iClientWidthBefore;
				int iClientHeightBefore;
				int iNeverAgainWidth;
				int iNeverAgainHeight;



				/* Get original window dimensions */
				GetWindowRect( hwnd, &rw );
				iWindowWidthBefore   = rw.right - rw.left;
				iWindowHeightBefore  = rw.bottom - rw.top;

				GetClientRect( hwnd, &rc );
				iClientWidthBefore   = rc.right - rc.left;
				iClientHeightBefore  = rc.bottom - rc.top;


				
				{
					/* Find handle of the text label */
					HWND hFirstStatic;
					HWND hSecondStatic;

					hFirstStatic = FindWindowEx( hwnd, NULL, TEXT( "STATIC" ), NULL );
					if( !hFirstStatic ) break;
					
					hSecondStatic = FindWindowEx( hwnd, hFirstStatic, TEXT( "STATIC" ), NULL );
					if( !hSecondStatic )
					{
						/* Only one static means no icon. */
						/* So hFirstStatic must be the text window. */
						hText = hFirstStatic;
					}
					else
					{
						TCHAR szBuf[ 2 ] = TEXT( "" );
						if( !GetWindowText( hSecondStatic, szBuf, 2 ) ) break;
						
						if( *szBuf != TEXT( '\0' ) )
						{
							/* Has text so it must be the label */
							hText = hSecondStatic;
						}
						else
						{
							hText = hFirstStatic;
						}
					}
				}
				
				GetWindowRect( hText, &rt );
				RectScreenToClient( hwnd, &rt );
				
				iLabelHeight = rt.bottom - rt.top;

				{
					/* Get distance between label and the buttons */
					HWND hAnyButton;
					RECT rab;

					hAnyButton = FindWindowEx( hwnd, NULL, TEXT( "BUTTON" ), NULL );
					if( !hAnyButton ) break;
					
					GetWindowRect( hAnyButton, &rab );
					RectScreenToClient( hwnd, &rab );
					
					SPACE_OVER_CHECKBOX = rab.top - rt.bottom;
				}

				szCheckboxLabel =	( data->uType & MB_CHECKNEVERAGAIN )
									? EMA_TEXT_NEVER_AGAIN
									: EMA_TEXT_REMEMBER_CHOICE;

				/* Add checkbox */
				data->hCheck = CreateWindow(
					TEXT( "BUTTON" ),
					szCheckboxLabel,
					WS_CHILD |
						WS_VISIBLE |
						WS_TABSTOP |
						BS_VCENTER |
						BS_CHECKBOX,
					CW_USEDEFAULT,
					CW_USEDEFAULT,
					CW_USEDEFAULT,
					CW_USEDEFAULT,
					hwnd,
					NULL,
					GetModuleHandle( NULL ),
					NULL
				);
				
				
				/* Set initial check state */
				SendMessage( data->hCheck, BM_SETCHECK, *( data->bCheckState ) ? BST_CHECKED : 0, 0 );
				
				{
					/* Apply default font */
					const int cyMenuSize  = GetSystemMetrics( SM_CYMENUSIZE );
					const int cxMenuSize  = GetSystemMetrics( SM_CXMENUSIZE );
					const HFONT hNewFont  = ( HFONT )GetStockObject( DEFAULT_GUI_FONT );
					HFONT hOldFont;
					SIZE size;

					SendMessage( data->hCheck, WM_SETFONT, ( WPARAM )hNewFont, ( LPARAM )TRUE );
					
					hdc = GetDC( data->hCheck );
					hOldFont = ( HFONT )SelectObject( hdc, GetStockObject( DEFAULT_GUI_FONT ) );
					GetTextExtentPoint32( hdc, szCheckboxLabel, _tcslen( szCheckboxLabel ), &size );
					SelectObject( hdc, hOldFont );
					ReleaseDC( data->hCheck, hdc );

					iNeverAgainWidth   = cxMenuSize + size.cx + 1;
					iNeverAgainHeight  = ( cyMenuSize > size.cy ) ? cyMenuSize : size.cy;
				}

				MoveWindow(
					data->hCheck,
					( iClientWidthBefore - ( iNeverAgainWidth ) ) / 2,
					rt.top + iLabelHeight + SPACE_OVER_CHECKBOX,
					iNeverAgainWidth,
					iNeverAgainHeight,
					FALSE
				);

				{
					/* Move all buttons down (except the checkbox) */
					const int iDistance = iNeverAgainHeight + SPACE_UNDER_CHECKBOX;
					HWND hLastButton = NULL;
					RECT rb;
					for( ; ; )
					{
						hLastButton = FindWindowEx( hwnd, hLastButton, TEXT( "BUTTON" ), NULL );
						if( !hLastButton ) break;
						if( hLastButton == data->hCheck ) continue;
						
						GetWindowRect( hLastButton, &rb );
						RectScreenToClient( hwnd, &rb );

						MoveWindow( hLastButton, rb.left, rb.top + iDistance, rb.right - rb.left, rb.bottom - rb.top, FALSE );
					}


					/* Enlarge dialog */
					MoveWindow( hwnd, rw.left, rw.top, iWindowWidthBefore, iWindowHeightBefore + iDistance + SPACE_EXTRA_BOTTOM, FALSE );
				}
			}
			else
			{
				data->hCheck = NULL;
			}
			
			/* Modify close button */
			switch( data->uType & MB_CLOSEMASK )
			{
			case MB_DISABLECLOSE:
				{
					const HMENU hSysMenu = GetSystemMenu( hwnd, FALSE );
					EnableMenuItem( hSysMenu, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED );
				}
				break;
				
			case MB_NOCLOSE:
				{
					const LONG style = GetWindowLong( hwnd, GWL_STYLE );
					if( ( style & WS_SYSMENU ) == 0 ) break;
					SetWindowLong( hwnd, GWL_STYLE, ( LONG )( style - WS_SYSMENU ) );
				}
				break;
				
			}
		}
		break;

	}
	return CallWindowProc( data->WndprocMsgBoxBackup, hwnd, message, wp, lp );
}	



/* int bFound = 0; */

LRESULT CALLBACK HookprocMsgBox( int code, WPARAM wp, LPARAM lp )
{
	/* Get hook handle */
	EmaBoxData * const data = ( EmaBoxData * )TlsGetValue( dwTlsSlot );
	
	if( code == HCBT_CREATEWND )
	{
		/* MSDN says WE CANNOT TRUST "CBT_CREATEWND"                 */
		/* so we use only the window handle                          */
		/* and get the class name using "GetClassName". (-> Q106079) */
		HWND hwnd = ( HWND )wp;

		/* Check windowclass */
		TCHAR szClass[ 7 ] = TEXT( "" );
		GetClassName( hwnd, szClass, 7 );
		if( !_tcscmp( szClass, TEXT( "#32770" ) ) )
		{
/*			
			if( bFound )
			{
				return CallNextHookEx( hCBT, code, wp, lp );
			}
			
			bFound = 1;
*/			
			/* Exchange window procedure */
			data->WndprocMsgBoxBackup = ( WNDPROC )GetWindowLong( hwnd, GWL_WNDPROC );
			if( data->WndprocMsgBoxBackup != NULL )
			{
				SetWindowLong( hwnd, GWL_WNDPROC, ( LONG )WndprocMsgBox );
			}
		}
	}
	return CallNextHookEx( data->hCBT, code, wp, lp );
}



int ExtraAllTheSame( const HWND hWnd, const LPCTSTR lpText, const LPCTSTR lpCaption, const UINT uType, const WORD wLanguageId, const LPMSGBOXPARAMS lpMsgBoxParams, int * const pbCheckRes, const int iFunction )
{
	EmaBoxData * data;
	HHOOK hCBT;
	int res;

#ifdef EMA_AUTOINIT
	if( !bEmaInitDone )
	{
		EmaBoxLive();
		bEmaInitDone = 1;
	}
#endif

	/* Create thread data */
	data = ( EmaBoxData * )LocalAlloc( NONZEROLPTR, sizeof( EmaBoxData ) );
	TlsSetValue( dwTlsSlot, data );
	data->bCheckState  = pbCheckRes;
	data->uType        = ( iFunction != FUNCTION_INDIRECT ) ? uType : lpMsgBoxParams->dwStyle;
	
	/* Setup this-thread-only hook */
	hCBT = SetWindowsHookEx( WH_CBT, &HookprocMsgBox, GetModuleHandle( NULL ), GetCurrentThreadId() );

	switch( iFunction )
	{
	case FUNCTION_NORMAL:
		res = MessageBox( hWnd, lpText, lpCaption, uType );
		break;

	case FUNCTION_EXTENDED:
		res = MessageBoxEx( hWnd, lpText, lpCaption, uType, wLanguageId );
		break;

	case FUNCTION_INDIRECT:
		res = MessageBoxIndirect( lpMsgBoxParams );
		break;

	}

	/* Remove hook */
	if( hCBT != NULL ) UnhookWindowsHookEx( hCBT );

	/* Destroy thread data */
	LocalFree( ( HLOCAL )data );

	return res;
}



int EmaBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType, int * pbCheckRes )
{
	/* Check extra flags */
	if( ( uType & MB_EXTRAMASC ) == 0 )
	{
		/* No extra */
		return MessageBox( hWnd, lpText, lpCaption, uType );
	}
	
	return ExtraAllTheSame( hWnd, lpText, lpCaption, uType, 0, NULL, pbCheckRes, FUNCTION_NORMAL );
}



int EmaBoxEx( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType, WORD wLanguageId, int * pbCheckRes )
{
	/* Check extra flags */
	if( ( uType & MB_EXTRAMASC ) == 0 )
	{
		/* No extra */
		return MessageBoxEx( hWnd, lpText, lpCaption, uType, wLanguageId );
	}
	
	return ExtraAllTheSame( hWnd, lpText, lpCaption, uType, wLanguageId, NULL, pbCheckRes, FUNCTION_EXTENDED );
}



int EmaBoxIndirect( const LPMSGBOXPARAMS lpMsgBoxParams, int * pbCheckRes )
{
	/* Check extra flags */
	if( ( lpMsgBoxParams->dwStyle & MB_EXTRAMASC ) == 0 )
	{
		/* No extra */
		return MessageBoxIndirect( lpMsgBoxParams );
	}
	
	return ExtraAllTheSame( NULL, NULL, NULL, 0, 0, lpMsgBoxParams, pbCheckRes, FUNCTION_INDIRECT );
}



int EmaBoxLive()
{
	dwTlsSlot = TlsAlloc();
	if( dwTlsSlot == TLS_OUT_OF_INDEXES ) return 0;
	
	return 1;
}



int EmaBoxDie()
{
	if( dwTlsSlot == TLS_OUT_OF_INDEXES ) return 0;

	TlsFree( dwTlsSlot );
	return 1;
}
