// Uncomment following line if working without precompiled headers
#include "stdafx.h"
#include <commctrl.h>
#include "PNControls.h"

// PNCreateButton - Simplified button creation function.
// Parameters:
//     hParent - Window that is to be parent for the new button
//     lpCaption - Text to appear in the button (a string)
//     x - X coordinate, relative to client area of parent
//     y - Y coordinate, relative to client area of parent
//     nWidth - Width of new button
//     nHeight - Height of new button
//     nID - ID for new button (should be unique within parent)
//     nStyle - Additional styles for a button
// Returns: Window handle if succesful, NULL upon failure.
HWND PNCreateButton( HWND hParent, LPCTSTR lpCaption, int x, int y, int nWidth, int nHeight, int nID, int nStyle )
{
	HWND Ret;
	HFONT hFont;
	
	// Create the window
	Ret = CreateWindow( "BUTTON", lpCaption, WS_VISIBLE|WS_CHILD|WS_TABSTOP|BS_PUSHBUTTON | nStyle,
				x, y, nWidth, nHeight, hParent, (HMENU)nID, GetModuleHandle(NULL), NULL );
	if( Ret )
	{
		// If window was created, then we set the font of the button to be the same
		// as its parent window
		hFont = (HFONT)SendMessage( hParent, WM_GETFONT, 0, 0 );
		SendMessage( Ret, WM_SETFONT, (WPARAM)hFont, 1 );
	}
	return( Ret );
}

// PNSetButtonImage - Simplified function to set image for an image-only button
// Parameters:
//     hParent - Window that is parent for the button
//     uButtonID - ID for button to set image for
//     uImageID - ID of bitmap or icon resource to use as image
// Returns: TRUE if all went OK, FALSE upon failure
BOOL PNSetButtonImage( HWND hParent, UINT uButtonID, UINT uImageID )
{
	HANDLE hImage;
	int Type;
	BOOL IsIcon=FALSE, IsBitmap=FALSE;

	// Get window handle for the button
	HWND hChild = GetDlgItem( hParent, uButtonID );
	if( hChild )
	{
		// Determine if window style is bitmap or icon (should be 1 of the 2).
		if( GetWindowLong( hChild, GWL_STYLE ) & BS_BITMAP )
		{
			IsBitmap = TRUE;
			Type = IMAGE_BITMAP;
		}
		if( GetWindowLong( hChild, GWL_STYLE ) & BS_ICON )
		{
			IsIcon = TRUE;
			Type = IMAGE_ICON;
		}
		if( IsBitmap || IsIcon )
		{
			// Load the bitmap or icon specified by ImageID parameter
			hImage = LoadImage( GetModuleHandle(NULL), MAKEINTRESOURCE(uImageID), Type, 
				16, 16, LR_DEFAULTCOLOR );
			if( hImage ) // and tell the button to display it (set the button image)
				SendMessage( hChild, BM_SETIMAGE, Type, (LPARAM)hImage );
		}
	}
	return( hImage ? TRUE : FALSE );
}

// PNClearButtonImage - Simplified function to remove image from an image-only button
// Parameters:
//     hParent - Window that is parent for the button
//     uButtonID - ID for button to clear image for
// Comment: Call before button is destroyed, to release image resources.
void PNClearButtonImage( HWND hParent, UINT uButtonID )
{
	HANDLE hImage;
	int Type;
	BOOL IsIcon=FALSE, IsBitmap=FALSE;

	// Get Handle for the desired button
	HWND hChild = GetDlgItem( hParent, uButtonID );
	if( hChild )
	{
		// Determine if the button has the Bitmap or Icon property set true (should be one of the two)
		if( GetWindowLong( hChild, GWL_STYLE ) & BS_BITMAP )
		{
			IsBitmap = TRUE;
			Type = IMAGE_BITMAP;
		}
		else if( GetWindowLong( hChild, GWL_STYLE ) & BS_ICON )
		{
			IsIcon = TRUE;
			Type = IMAGE_ICON;
		}

		if( IsBitmap || IsIcon ) // If properties are OK
		{
			// Get a handle for the image the button is currently using
			hImage = (HANDLE) SendMessage( hChild, BM_GETIMAGE, Type, 0 );
			
			// Tell the button to forget about the image, telling it to use a NULL image.
			SendMessage( hChild, BM_SETIMAGE, Type, 0 );

			if( IsBitmap ) // If the image was a bitmap, the use DeleteObject to unload it
				DeleteObject( hImage );
			else // If the image was an Icon, the use DestroyIcon to unload it
				DestroyIcon( (HICON)hImage );
		}
	}
}

// PNCreateRadioButton - Simplified radio button creation function.
// Parameters:
//     hParent - Window that is to be parent for the new button
//     lpCaption - Text to appear in the button (a string)
//     x - X coordinate, relative to client area of parent
//     y - Y coordinate, relative to client area of parent
//     nWidth - Width of new button
//     nHeight - Height of new button
//     nID - ID for new button (should be unique within parent)
//     nStyle - Additional styles for a button
// Returns: Window handle if succesful, NULL upon failure.
HWND PNCreateRadioButton( HWND hParent, LPCTSTR lpCaption, int x, int y, int nWidth, int nHeight, int nID, int nStyle )
{
	HWND Ret;
	HFONT hFont;
	
	// Create the window
	Ret = CreateWindow( "BUTTON", lpCaption, WS_VISIBLE|WS_CHILD|WS_TABSTOP|BS_AUTORADIOBUTTON | nStyle,
				x, y, nWidth, nHeight, hParent, (HMENU)nID, GetModuleHandle(NULL), NULL );
	if( Ret )
	{
		// If window was created, then we set the font of the button to be the same
		// as its parent window
		hFont = (HFONT)SendMessage( hParent, WM_GETFONT, 0, 0 );
		SendMessage( Ret, WM_SETFONT, (WPARAM)hFont, 1 );
	}
	return( Ret );
}

// PNCreateCheckBox - Simplified checkbox creation function.
// Parameters:
//     hParent - Window that is to be parent for the new checkbox
//     lpCaption - Text to appear in the checkbox
//     x - X coordinate, relative to client area of parent
//     y - Y coordinate, relative to client area of parent
//     nWidth - Width of new checkbox
//     nHeight - Height of new checkbox
//     nID - ID for new checkbox (should be unique within parent)
//     nState - Checked, unchecked, or grayed state
//     nStyle - Additional styles for a checkbox
// Returns: Window handle if succesful, NULL upon failure.
HWND PNCreateCheckBox( HWND hParent, LPCTSTR lpCaption, int x, int y, int nWidth, int nHeight, int nID, int State, int nStyle )
{
	HWND Ret;
	HFONT hFont;
	
	// Make sure atleast BS_AUTOCHECKBOX is used
	if( nStyle == 0 )
		nStyle = BS_AUTOCHECKBOX;

	// Create the window
	Ret = CreateWindow( "BUTTON", lpCaption, WS_VISIBLE|WS_CHILD|WS_TABSTOP| nStyle,
				x, y, nWidth, nHeight, hParent, (HMENU)nID, GetModuleHandle(NULL), NULL );
	if( Ret )
	{
		// If window was created, then we set the font of the button to be the same
		// as its parent window
		hFont = (HFONT)SendMessage( hParent, WM_GETFONT, 0, 0 );
		SendMessage( Ret, WM_SETFONT, (WPARAM)hFont, 1 );
		// Set the default check status
		SendMessage( Ret, BM_SETCHECK, State, 0 );
	}
	return( Ret );
}

// PNCreateComboBox - Simplified combo box creation function.
// Parameters:
//     hParent - Window that is to be parent for the new combobox
//     x - X coordinate, relative to client area of parent
//     y - Y coordinate, relative to client area of parent
//     nWidth - Width of new combobox
//     nRows - Number of rows to appear in dropdown list
//     nID - ID for new combobox (should be unique within parent)
//     nStyle - Additional styles for a combobox
// Returns: Window handle if succesful, NULL upon failure.
HWND PNCreateComboBox( HWND hParent, int x, int y, int nWidth, int nRows, int nID, int nStyle )
{
	HWND Ret;
	HFONT hFont;
	int LineHeight;

	// Create the window
	Ret = CreateWindow( "COMBOBOX", NULL, 
			WS_VISIBLE|WS_CHILD|WS_TABSTOP|WS_VSCROLL|CBS_AUTOHSCROLL | nStyle,
				x, y, nWidth, 1, hParent, (HMENU)nID, GetModuleHandle(NULL), NULL );
	if( Ret )
	{
		// If window was created, then we set the font of the button to be the same
		// as its parent window
		hFont = (HFONT)SendMessage( hParent, WM_GETFONT, 0, 0 );
		SendMessage( Ret, WM_SETFONT, (WPARAM)hFont, 1 );
		
		nRows++;

		// Determine height of the font
		LOGFONT LogFont;
		GetObject( hFont, sizeof(LogFont), &LogFont );

		// Determine Height of the current window
		RECT Rect;
		GetWindowRect( Ret, &Rect );
		Rect.bottom -= Rect.top;
		Rect.right -= Rect.left;

		// Determine line height based on font
		if( LogFont.lfHeight < 0 )
			LineHeight = (-LogFont.lfHeight)+2;
		else
			LineHeight = LogFont.lfHeight+4;

		// Increase window size to hold as many lines as requested
		Rect.bottom += (nRows * LineHeight);
		// Resize window, making it taller adds to list box.
		SetWindowPos( Ret, NULL, 0, 0, Rect.right, Rect.bottom, SWP_NOMOVE|SWP_NOZORDER );
	}
	return( Ret );
}


// PNCreateEditBox - Creates an edit box at runtime
// Parameters:
//     hParent - Parent window handle (usually the dialog to create on)
//     lpCaption - String to store in edit box after created, may be NULL
//     x - X coordinate for location
//     y - Y coordinate for location
//     nWidth - Width of control
//     nHeight - Height of control
//     nID - Unique ID for the new edit box (must be unique on the parent window)
//     nStyle - Additional edit styles (start with 'ES_')
// Returns: HWND of new window of NULL upon failure
HWND PNCreateEditBox( HWND hParent, LPCTSTR lpCaption, int x, int y, int nWidth, int nHeight, int nID, int nStyle )
{
	HWND Ret;
	HFONT hFont;
	
	// Create the window
	Ret = CreateWindowEx( WS_EX_CLIENTEDGE, "EDIT", lpCaption, WS_VISIBLE|WS_CHILD|WS_TABSTOP| nStyle,
				x, y, nWidth, nHeight, hParent, (HMENU)nID, GetModuleHandle(NULL), NULL );
	if( Ret )
	{
		// If window was created, then we set the font of the button to be the same
		// as its parent window
		hFont = (HFONT)SendMessage( hParent, WM_GETFONT, 0, 0 );
		SendMessage( Ret, WM_SETFONT, (WPARAM)hFont, 1 );
	}
	return( Ret );
}

// PNCreateListBox - Creates a list box at runtime
// Parameters:
//     hParent - Parent window handle (usually the dialog to create on)
//     x - X coordinate for location
//     y - Y coordinate for location
//     nWidth - Width of control
//     nHeight - Height of control
//     nID - Unique ID for the list box (must be unique on the parent window)
//     nStyle - Additional list styles (start with 'LS_')
// Returns: HWND of new window of NULL upon failure
HWND PNCreateListBox( HWND hParent, LPCTSTR lpCaption, int x, int y, int nWidth, int nHeight, int nID, int nStyle )
{
	HWND Ret;
	HFONT hFont;
	
	// Create the window
	Ret = CreateWindowEx( WS_EX_CLIENTEDGE, "LISTBOX", lpCaption, WS_VISIBLE|WS_CHILD|WS_TABSTOP| nStyle,
				x, y, nWidth, nHeight, hParent, (HMENU)nID, GetModuleHandle(NULL), NULL );
	if( Ret )
	{
		// If window was created, then we set the font of the button to be the same
		// as its parent window
		hFont = (HFONT)SendMessage( hParent, WM_GETFONT, 0, 0 );
		SendMessage( Ret, WM_SETFONT, (WPARAM)hFont, 1 );
	}
	return( Ret );
}

// PNMLEditGetLine - Gets a specific line from a multi-line edit box.
// Parameters:
//     hChild - Window handle for multi-line edit control
//     Line - Which line to retrieve
//     Dest - Where to store the gotten line
//     MaxLen - Maximum number of characters to retrieve
// Returns: TRUE if succesful, FALSE upon failure
BOOL PNMLEditGetLine( HWND hChild, int Line, char* Dest, int MaxLen )
{
	char *Tmp;
	int LineSize, LineIndex;

	// Determine the starting character position of the line
	LineIndex = SendMessage( hChild, EM_LINEINDEX, Line, 0 );
	if( LineIndex == -1 ) // Line parameter was bad
		return( FALSE );
	// Determine the length of line that character is on.
	LineSize = SendMessage( hChild, EM_LINELENGTH, LineIndex, 0 );
	// Dynamically allocate space for the string
	if( (Tmp=new char[max(LineSize+1,sizeof(WORD))]) == NULL ) // Note: C programmers use malloc here
		return( FALSE );
	// Store the # of characters to in our input buffer	
	*(WORD*)Tmp=LineSize;
	// Retrieve the string from the control
	SendMessage( hChild, EM_GETLINE, Line, (LPARAM) Tmp );
	// Copy it into the Dest parameter
	strncpy( Dest, Tmp, min( MaxLen, LineSize ) );
	// Properly null-terminate the Dest parameter
	Dest[ min( MaxLen, LineSize ) ] = '\0';
	delete[] Tmp; // Note: C programmers use free here.
	return( TRUE );
}

// PNMLEditSetLine - Sets a specific line in a multi-line edit box.
// Parameters:
//     hChild - Window handle for multi-line edit control
//     Line - Which line to set
//     Src - String to place into edit box
// Returns: TRUE if succesful, FALSE upon failure
BOOL PNMLEditSetLine( HWND hChild, int Line, char* Src )
{
	int LineStart, LineSize;

	// Determine the starting character position of the line
	LineStart = SendMessage( hChild, EM_LINEINDEX, Line, 0 );
	if( LineStart == -1 ) // Line parameter was bad
		return( FALSE );

	// Determine the length of the line to be replaced
	LineSize = SendMessage( hChild, EM_LINELENGTH, LineStart, 0 );
	// Select the text on that line (like a Cut&Paste operation)
	SendMessage( hChild, EM_SETSEL, LineStart, LineStart+LineSize );
	// Then replace selected text with our new string
	SendMessage( hChild, EM_REPLACESEL, FALSE, (LPARAM) Src );
	return( TRUE );
}

// PNDlgInit - Initializes dialog box members, like strings in comboboxes
// Parameters:
//     hParent - Window handle for dialog to initialize
//     ID - Dialog resource ID
// Returns: TRUE if succesful, FALSE upon failure
// Comments: Loads strings from dialog editor into combobox controls.
BOOL PNDlgInit( HWND hParent, int ID )
{
	// Find the initializer resource for dialog controls, with same Resource name as this dialog
	HRSRC hDlgInit = ::FindResource(GetModuleHandle(NULL), MAKEINTRESOURCE(ID), MAKEINTRESOURCE(240) );
	if( !hDlgInit )
		return( FALSE );

	// Load the initializer resource, and get its handle
	HGLOBAL hResource = NULL;
	hResource = LoadResource(GetModuleHandle(NULL), hDlgInit);
	if( !hResource )
		return( FALSE );

	// Lock the resource, which gives us a pointer to the data
	LPVOID lpResource = NULL;
	lpResource = LockResource(hResource);
	if( !lpResource )
	{
		FreeResource(hResource);
		return(FALSE);
	}

	// Now, we can 'walk through' the loaded resource which is an array
	// of initializer items.  The format of each item in the array is:
	// Window ID, Window Message, Length of Data, Data for message
	// We enter a loop to iterate this array, extracting the needed
	// data and sending the Window Message to the desired child control
	
	// Data to help our iteration
	UNALIGNED WORD* lpnRes = (WORD*)lpResource;
	WORD nIDC;
	WORD nMsg;
	DWORD dwLen;
	BOOL bSuccess = TRUE;

	// Iterate the elements in the initializer array (from resource)
	while( bSuccess && *lpnRes != 0 )
	{
		nIDC = *lpnRes++;
		nMsg = *lpnRes++;
		dwLen = *((UNALIGNED DWORD*&)lpnRes)++;

		// The hard-coded numbers deal with the fact that the resource editor
		// stores strings in the resource file with Win16 messages.  So here
		// we convert them to Win32 messages
		if( nMsg == 0x1234 )
			nMsg = CBEM_INSERTITEM;
		else if( nMsg == 0x0401 )
			nMsg = LB_ADDSTRING;
		else if( nMsg == 0x0403 )
			nMsg = CB_ADDSTRING;

		if( nMsg == CBEM_INSERTITEM ) // Insert into Extended Combobox?
		{
			COMBOBOXEXITEM item;
			item.mask = CBEIF_TEXT;
			item.iItem = -1;
#if defined(_UNICODE)
			{
				USES_CONVERSION;
				WideCharToMultiByte(acp, 0, lpw, -1, lpa, nChars, NULL, NULL);
				item.pszText = A2T(LPSTR(lpnRes));
			}
#else
			item.pszText = (LPSTR)lpnRes;
#endif
			if( SendDlgItemMessage( hParent, nIDC, nMsg, 0, (LPARAM) &item) == -1 )
				bSuccess = FALSE;
		}
		// Add string to List box or Combo box?
		else if( nMsg == LB_ADDSTRING || nMsg == CB_ADDSTRING )
		{
			if( SendDlgItemMessageA(hParent, nIDC, nMsg, 0, (LPARAM) lpnRes) == -1 )
				bSuccess = FALSE;
		}
		// skip past data, in preparation for next string
		lpnRes = (WORD*)((LPBYTE)lpnRes + (UINT)dwLen);
	}

	// Unlock and release our resrouces
	UnlockResource(hResource);
	FreeResource(hResource);
	return( TRUE );
}


// PNDrawTransparent - Draws a bitmap with transparency
// Parameters:
//     DC - HDC to draw bitmap on
//     x - X coordinate on DC to draw bitmap at
//     y - Y coordinate on DC to draw bitmap at
//     hbmImage - Handle to bitmap to display
//     LowerLeft - If True, then transparency color is taken from lower left of bitmap
//     crColor - If LowerLeft is false, then this must specify transparent color for bitmap
void PNDrawTransparent(HDC DC, int x, int y, HBITMAP hbmImage, BOOL LowerLeft, COLORREF crColor)
{
	HDC hdcImage;
	HDC hdcTrans;
	HBITMAP hbmTrans;
	BITMAP bmBitmap;
	GetObject( hbmImage, sizeof(bmBitmap), &bmBitmap );
	// Change Background and text color, saving values for end
	COLORREF crOldBack = SetBkColor(DC, RGB(255,255,255));
	COLORREF crOldText = SetTextColor(DC, RGB(0,0,0) );

	// Create Memory DCs to do our work in
	hdcImage = CreateCompatibleDC(DC);
	hdcTrans = CreateCompatibleDC(DC);

	// Select passed Image bitmap into Image memory DC
	SelectObject(hdcImage, hbmImage);
	
	// Create transparent bitmap, and select into transparent DC
	hbmTrans = CreateBitmap( bmBitmap.bmWidth, bmBitmap.bmHeight, 1, 1, NULL);
	SelectObject(hdcTrans, hbmTrans);

	// If LowerLeft is true, then determine transparent color from bitmap passed
	if( LowerLeft )
		crColor = GetPixel( hdcImage, 0, bmBitmap.bmHeight-1 );

	// Select background color (transparent color) for our image memory DC
	SetBkColor(hdcImage, crColor);

	BitBlt(hdcTrans, 0, 0, bmBitmap.bmWidth, bmBitmap.bmHeight, hdcImage, 0, 0, SRCCOPY);

	// Perform BitBlt operations (this is where the Masking occurs)
	BitBlt(DC, x, y, bmBitmap.bmWidth, bmBitmap.bmHeight, hdcImage, 0, 0, SRCINVERT);
	BitBlt(DC, x, y, bmBitmap.bmWidth, bmBitmap.bmHeight, hdcTrans, 0, 0, SRCAND);
	BitBlt(DC, x, y, bmBitmap.bmWidth, bmBitmap.bmHeight, hdcImage, 0, 0, SRCINVERT);

	// Release our memory DCs and Bitmap we created
	DeleteDC( hdcImage );
	DeleteDC( hdcTrans );
	DeleteObject( hbmTrans );
	
	// Retore original background and text colors for the passed DC
	SetBkColor(DC, crOldBack);
	SetTextColor(DC, crOldText);

}


// PNImageButtonEmboss - Basic embossing ability
// Parameters: 
//      bmSource - Source bitmap to be embossed
// Returns: Embossed version of Source bitmap
// Comments: Uses brute force so we can maintain 3 colors on a disabled bitmap:
//    the highlight, dark, and transparent colors
HBITMAP PNImageButtonEmboss( HBITMAP bmSource )
{
	HDC memDC, memDCEmbossed;
	HBITMAP bmEmbossed, hbmOldBM, hbmOldBMEmbossed;
	BITMAP bmInfo;
	COLORREF crTransparent, crLo = GetSysColor(COLOR_3DHILIGHT),crHi=GetSysColor(COLOR_3DSHADOW);
	COLORREF crCur, crNewTransparent = GetSysColor( COLOR_3DFACE );
	int Row, Col, ColorAvg=0, Total=0;

	// Determine information for the bitmap passed
	GetObject( bmSource, sizeof(bmInfo), &bmInfo );
	
	// Create memory DCs, and the return bitmap, for drawing and creation
	memDC = CreateCompatibleDC(NULL);
	memDCEmbossed = CreateCompatibleDC(NULL);
	hbmOldBM = (HBITMAP)SelectObject(memDC, bmSource );
	bmEmbossed = CreateCompatibleBitmap( memDC, bmInfo.bmWidth, bmInfo.bmHeight );
	
	// Select the new bitmap into the memory DC.  Now, when we draw on the memory DC, it
	// will manipulate the bitmap that is selected into it.
	hbmOldBMEmbossed = (HBITMAP)SelectObject(  memDCEmbossed, bmEmbossed );

	// Perform some basic color analisys, to determine what colors to use
	crTransparent = GetPixel( memDC, 0, bmInfo.bmHeight-1 );
	for( Row=0; Row < bmInfo.bmHeight; Row++ )
		for( Col=0; Col < bmInfo.bmWidth; Col++ )
		{
			crCur = GetPixel( memDC, Row, Col );
			if( crCur != crTransparent )
			{
				ColorAvg+=(GetGValue(crCur)+GetBValue(crCur)+GetRValue(crCur));
				Total++;
			}
		}
	ColorAvg /= Total;

	// Draw the original bitmap into the memory DC, which will set the color depth and
	// dimensions of the new bitmap.
	BitBlt( memDCEmbossed, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, memDC, 0, 0, SRCCOPY);
	
	// Now, go through each pixel, and make it one of 3 colors: Dark, light, and transparent
	for( Row=0; Row < bmInfo.bmHeight; Row++ )
		for( Col=0; Col < bmInfo.bmWidth; Col++ )
		{
			crCur = GetPixel( memDC, Col, Row );
			if( crCur != crTransparent )
			{
				if( (GetGValue(crCur)+GetBValue(crCur)+GetRValue(crCur)) > ColorAvg )
					SetPixel( memDCEmbossed, Col, Row, crHi);
				else
					SetPixel( memDCEmbossed, Col, Row, crLo);
			}
			else
				SetPixel( memDCEmbossed, Col, Row, crNewTransparent );
		}

	// Return the memory DCs to their previous state, and delete them from memory
	SelectObject( memDC, hbmOldBM );
	SelectObject( memDCEmbossed, hbmOldBMEmbossed );
	DeleteDC( memDC );
	DeleteDC( memDCEmbossed );

	// Return the new bitmap
	return( bmEmbossed );
}

// PNImageButtonSetData - Sets images for an Image with Text button
// Parameters:
//     hParent - Window handle to parent of button
//     ImageOnLeft - If TRUE then image appears to left of text, otherwise it appears to right
//     ID - ID for the child window
//     IDUp - ID for bitmap to used for button up image (must not be zero)
//     IDDown - ID for bitmap to used for button down image (if zero, then Up image is used)
//     IDFocus - ID for bitmap to used for button focus image (if zero, then Up image is used)
//     IDDisabled - ID for bitmap to used for button disabled image (if zero, then Up image is used)
// Returns: TRUE if succesful, or FALSE upon failure
BOOL PNImageButtonSetData( HWND hParent, BOOL ImageOnLeft, int ID, int IDUp, int IDDown, int IDFocus, int IDDisabled )
{
	PNImageButtonInfo * pInfo=0;
	HWND hChild = GetDlgItem( hParent, ID );

	// If we can't find the child window, we can't continue
	if( !hChild )
		return( FALSE );

	// If the window already has it's 32-bit 'user data' in use, we can't do it.
	if( GetWindowLong( hChild, GWL_USERDATA ) )
		return( FALSE );

	// Standard 'new' now throws an exception, handle it here in case compiler
	// adapts this standard
	try {
		pInfo = new PNImageButtonInfo;
	} catch( ... ) { return( FALSE ); } 

	// And handle here in case it doesn't
	if( !pInfo )
		return( FALSE );
	
	pInfo->hImageUp = pInfo->hImageDown = pInfo->hImageFocus = 0;
	pInfo->bImageOnLeft = ImageOnLeft;

	// Load bitmap information for the 'button up' image
	pInfo->hImageUp = LoadBitmap( GetModuleHandle(NULL), MAKEINTRESOURCE( IDUp ) );
	
	// Determine the height and width of the images (they are all assumed to be the same
	BITMAP BM;
	GetObject( pInfo->hImageUp, sizeof(BM), &BM );
	pInfo->nImageWidth = BM.bmWidth;
	pInfo->nImageHeight = BM.bmHeight;

	// Now, load the rest of the images, if they are available
	if( IDDown )
		pInfo->hImageDown = LoadBitmap( GetModuleHandle(NULL), MAKEINTRESOURCE( IDDown ) );
	if( IDFocus )
		pInfo->hImageFocus = LoadBitmap( GetModuleHandle(NULL), MAKEINTRESOURCE( IDFocus ) );
	// If they specified a disabled image, use that, otherwise we create an embossed version
	// of the 'up' image.
	if( IDDisabled )
		pInfo->hImageDisabled = LoadBitmap( GetModuleHandle(NULL), MAKEINTRESOURCE( IDDisabled ) );
	else
		pInfo->hImageDisabled = PNImageButtonEmboss( (HBITMAP)pInfo->hImageUp );
	
	// Store the pointer to the structure with the window, as the 'user data'
	SetWindowLong( hChild, GWL_USERDATA, (LPARAM) pInfo );
	return( TRUE );
}

BOOL PNImageButtonClearData( HWND hParent, int ID )
{
	PNImageButtonInfo * pInfo;

	// Get child window handle.  If not found, we can't continue
	HWND hChild = GetDlgItem( hParent, ID );
	if( !hChild )
		return(FALSE);

	// Get our structure pointer from the 'user data' for the window.  If not there,
	// then we can't continue (it wasn't setup correctly)
	pInfo = (PNImageButtonInfo*) GetWindowLong( hChild, GWL_USERDATA );
	if( !pInfo )
		return(FALSE);
	
	// We got our data, now start deleting from memory
	if( pInfo->hImageDisabled )
		DeleteObject( pInfo->hImageDisabled );
	if( pInfo->hImageUp )
		DeleteObject( pInfo->hImageUp );
	if( pInfo->hImageDown )
		DeleteObject( pInfo->hImageDown );
	if( pInfo->hImageFocus )
		DeleteObject( pInfo->hImageFocus );
	delete pInfo;
	
	SetWindowLong( hChild, GWL_USERDATA, 0 ); // Set user data back to zero.

	return( TRUE );

}


// PNImageButtonDrawItem - Handles owner-draw requirements for PN Image with Text buttons
// Parameters:
//     hParent - Window handle to parent of button
//     ID - ID for the child window
//	   lpDrawItem - Pointer to a DRAWITEMSTRUCT structure
// Returns: TRUE if succesful, or FALSE upon failure
BOOL PNImageButtonDrawItem( HWND hParent, int ID, LPDRAWITEMSTRUCT lpDrawItem  )
{
	HDC DC = lpDrawItem->hDC;
	RECT Rect = lpDrawItem->rcItem;
	SIZE DrawArea;
	int XOffset, YOffset, YTextOffset=0, YImageOffset=0;
	UINT nOffset = 0;
	UINT nFrameStyle=0;
	int	nStateFlag;
	HANDLE hImage;

	char Text[128];
	PNImageButtonInfo * pInfo;

	// Get child window handle.  If not found, we can't continue
	HWND hChild = GetDlgItem( hParent, ID );
	if( !hChild )
		return(FALSE);

	// Get our structure pointer from the 'user data' for the window.  If not there,
	// then we can't continue (it wasn't setup correctly)
	pInfo = (PNImageButtonInfo*) GetWindowLong( hChild, GWL_USERDATA );
	if( !pInfo )
		return(FALSE);

	// Based on the size of the button text, and the images, determine drawing 
	// positions of each.
	GetWindowText( hChild, Text, sizeof(Text) );
	GetTextExtentPoint32(DC, Text, strlen(Text), &DrawArea );
	if( strchr( Text, '&' ) )
	{
		SIZE Tmp;
		GetTextExtentPoint32( DC, "&", 1, &Tmp );
		DrawArea.cx -= Tmp.cx;
	}

	DrawArea.cx += (pInfo->nImageWidth+4);
	if( DrawArea.cy > pInfo->nImageHeight )
		YTextOffset = ( pInfo->nImageHeight - DrawArea.cy ) /2;
	else
	{
		YImageOffset = (DrawArea.cy - pInfo->nImageHeight) /2;
		DrawArea.cy = pInfo->nImageHeight;
	}
	XOffset = (Rect.right - DrawArea.cx)/2;
	YOffset = (Rect.bottom - DrawArea.cy)/2;
	
	// Determine if button is in the selected state
	if ( lpDrawItem->itemState & ODS_SELECTED)
	{
		nFrameStyle = DFCS_PUSHED;
		hImage = pInfo->hImageDown ? pInfo->hImageDown : pInfo->hImageUp;
		nOffset += 1;
	}

	// Determine if button is disabled
	if( lpDrawItem->itemState & ODS_DISABLED )
	{
		nStateFlag = DSS_DISABLED;
		hImage = pInfo->hImageDisabled?pInfo->hImageDisabled : pInfo->hImageUp;
	}
	else
	{
		nStateFlag = DSS_NORMAL;
		hImage = pInfo->hImageUp;
	}
	
	// Determine if button has the focus state
	if ( lpDrawItem->itemState & ODS_FOCUS )
		if( (hImage = pInfo->hImageFocus) == 0  )
			hImage = pInfo->hImageUp;

	// If button is selected, the use DrawFrameControl to display its frame
	if( ! (lpDrawItem->itemState & ODS_SELECTED) )
	{
		// If the button is focused, then we need to draw the black rectangle,
		// and shrink the button a tiny bit (visual appearance of all buttons)
		if( lpDrawItem->itemState & ODS_FOCUS  )
		{
			Rectangle(DC, Rect.left, Rect.top, Rect.right, Rect.bottom  );
			Rect.left++;
			Rect.top++;
			Rect.bottom--;
			Rect.right--;
		}
		DrawFrameControl(DC, &Rect, DFC_BUTTON, DFCS_BUTTONPUSH | nFrameStyle);
	}
	else
	{
		// If it's not selected, then drawing is more complex
		// Create out pens and brushes for drawing, and draw a rectangle.
		HBRUSH NewBrush = CreateSolidBrush( GetSysColor( COLOR_3DFACE ) );
		HBRUSH hOldBrush = (HBRUSH)SelectObject( DC, NewBrush );
		Rectangle(DC, Rect.left, Rect.top, Rect.right, Rect.bottom );
		HPEN NewPen = CreatePen( PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW) );
		HPEN hpOldPen = (HPEN)SelectObject( DC, NewPen );

		// Then, shrink the rectangle a tiny bit, and draw the inner rectangle
		Rect.left++;
		Rect.top++;
		Rect.bottom--;
		Rect.right--;
		Rectangle(DC, Rect.left, Rect.top, Rect.right, Rect.bottom  );
		SelectObject( DC, hpOldPen );
		SelectObject( DC, hOldBrush );
		DeleteObject( NewPen );
	}

	if( pInfo->bImageOnLeft )
	{
		// Draw the bitmap image, transparently, and then the text
		PNDrawTransparent( DC, XOffset+nOffset, YOffset+nOffset+YImageOffset, (HBITMAP)hImage, TRUE, 0 );
		DrawState( DC, 0, 0, (LPARAM)Text, 0, XOffset+pInfo->nImageWidth+4+nOffset, YOffset+nOffset+YTextOffset, DrawArea.cx, DrawArea.cy, DST_PREFIXTEXT|nStateFlag  );
	}
	else
	{
		// Draw the text, and then the bitmap image transparently
		DrawState( DC, 0, 0, (LPARAM)Text, 0, XOffset+nOffset, YOffset+nOffset+YTextOffset, DrawArea.cx, DrawArea.cy, DST_PREFIXTEXT|nStateFlag  );
		PNDrawTransparent( DC, XOffset+nOffset+DrawArea.cx-pInfo->nImageWidth, YOffset+nOffset+YImageOffset, (HBITMAP)hImage, TRUE, 0 );
	}

	// Draw the focus rectangle for the button
	if( ( lpDrawItem->itemState & ODS_FOCUS ) ) 
	{
		RECT Rect2;
		Rect2 = Rect;
		Rect2.left += 3;
		Rect2.right -= 3;
		Rect2.top += 3;
		Rect2.bottom -= 3;
		DrawFocusRect( DC, &Rect2 );
	}
	return(TRUE);
}


// PNSetFont - Sets a font for a child window
// Parameters:
//     hParent - Window handle to parent of control
//     nID - ID for the child window
//	   lpszFontName - String with name of font to use
//     nHeight - Height of font in device units
//     bBold - If TRUE then use bold font, otherwise use normal font
//     bItalic - TRUE for italic font, FALSE for normal
//     bUnderline - TRUE for underlined font, FALSE for normal
// Returns: TRUE if succesful, or FALSE upon failure
BOOL PNSetFont( HWND hParent, int nID, const char* lpszFontName, int nHeight, BOOL bBold, BOOL bItalic, BOOL bUnderline )
{
	HFONT ParentFont, CurFont;
	HWND hChild = GetDlgItem( hParent, nID );
	
	if( !hChild )
		return(FALSE);
	
	ParentFont = (HFONT)SendMessage( hParent, WM_GETFONT, 0, 0 );
	CurFont = (HFONT)SendMessage( hChild, WM_GETFONT, 0, 0 );

	// If the font is not the parent or dsystem font, we can delete it.
	if( CurFont != ParentFont && CurFont != (HFONT)GetStockObject(SYSTEM_FONT) )
		DeleteObject( CurFont );

	HFONT NewFont = CreateFont( nHeight, 0, 0, 0, bBold?FW_BOLD:FW_NORMAL,
			bItalic, bUnderline, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
			CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE|DEFAULT_PITCH, lpszFontName );
	if( NewFont )
	{
		SendMessage( hChild, WM_SETFONT, (WPARAM) NewFont, TRUE );
		return( TRUE );
	}
	return(FALSE);
}

// PNClearFont - Removes font created by PNSetFont
// Parameters:
//     hParent - Window handle to parent of control
//     nID - ID for the child window
void PNClearFont( HWND hParent, int nID  )
{
	HFONT ParentFont, CurFont;
	HWND hChild = GetDlgItem( hParent, nID );
	
	if( !hChild )
		return;
	
	ParentFont = (HFONT)SendMessage( hParent, WM_GETFONT, 0, 0 );
	CurFont = (HFONT)SendMessage( hChild, WM_GETFONT, 0, 0 );

	// If the font is not the parent or dsystem font, we can delete it.
	if( CurFont != ParentFont && CurFont != (HFONT)GetStockObject(SYSTEM_FONT) )
	{
		DeleteObject( CurFont );
		SendMessage( hChild, WM_SETFONT, (WPARAM)ParentFont, 1 );
	}
}
