Click here to Skip to main content
15,891,951 members
Articles / Desktop Programming / MFC

ClassLib, A C++ class library

Rate me:
Please Sign up or sign in to vote.
4.80/5 (32 votes)
25 May 2005CPOL8 min read 401.7K   11.5K   141  
C++ class library.
//
// bitmapmenu.cpp
//
// (C) Copyright 2000 Jan van den Baard.
//     All Rights Reserved.
//

#include "bitmapmenu.h"
#include "../strings/string.h"
#include "../gdi/brush.h"
#include "../gdi/pen.h"
#include "../gdi/font.h"
#include "../gdi/bitmap.h"
#include "../gdi/selector.h"
#include "../gdi/dc.h"
#include "../gdi/getdc.h"
#include "../collectors/linkedlist.h"
#include "../tools/color.h"
#include "../tools/drawstate.h"
#include "../windows/common controls/imagelist.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// List of created temporary objects.
ClsLinkedList<ClsBitmapMenu>	temporary_bm_list;
extern ClsLinkedList<ClsMenu>	global_menu_list;

// Constructor. Clears data fields.
ClsBitmapMenu::ClsBitmapMenu()
{
	// Clear fields.
	m_hImageList  = NULL;
	m_bIsSubMenu  = FALSE;
	m_bIconFrame  = TRUE;
	m_bConverted  = FALSE;
	m_bIsPopup    = FALSE;
	m_bDrawShadow = TRUE;
	m_cxBitmap    = m_cyBitmap = 16;
}

// Destructor. Deletes the image list
// and the item strings.
ClsBitmapMenu::~ClsBitmapMenu()
{
	// Delete it all.
	Destroy();
}

// Destroy the item data of all the menu items.
void ClsBitmapMenu::DestroyItemData( HMENU hMenu /* = NULL */ )
{
	// Use menu which is passed or the wrapped handle.
	HMENU hUseMenu = hMenu ? hMenu : m_hMenu;

	// How many items?
	int nItems = ::GetMenuItemCount( hUseMenu );

	// Iterate items.
	for ( int i = 0; i < nItems; i++ )
	{
		// Does this item have a submenu? If so
		// destroy it.
		HMENU hSubMenu = ::GetSubMenu( hUseMenu, i );
		if ( hSubMenu )	DestroyItemData( hSubMenu );
		
		// Obtain item data pointer.
		ClsMenuItemInfo mi;
		mi.fMask = MIIM_DATA;
		if ( ::GetMenuItemInfo( hUseMenu, i, TRUE, &mi ))
		{
			// Cast pointer and delete it.
			ItemData *pData = ( ItemData * )mi.dwItemData;
			if ( pData ) delete pData;
		}
	}
}

// Delete a menu item including it's sub-items.
BOOL ClsBitmapMenu::DeleteMenu( UINT uPosition, BOOL bByPos /* = FALSE */ )
{
	// Get item data.
	ClsMenuItemInfo	 mi;
	mi.fMask = MIIM_DATA | MIIM_SUBMENU | MIIM_TYPE;
	mi.dwTypeData = NULL;
	if ( GetItemInfo( uPosition, &mi, bByPos ))
	{
		// Only ownerdraw items.
		if ( mi.fType == MFT_OWNERDRAW )
		{
			// Get the pointer to the item
			// data structure.
			ItemData *pData = ( ItemData * )mi.dwItemData;
			if ( pData ) delete pData;

			// Popup?
			if ( mi.hSubMenu )
				// Destroy the data of that aswell.
				DestroyItemData( mi.hSubMenu );
		}
		// And the menu itself.
		return ClsMenu::DeleteMenu( uPosition, bByPos );
	}
	return FALSE;
}

// Destroys the image list and item
// strings.
BOOL ClsBitmapMenu::Destroy()
{
	// Only when the handle is valid.
	if ( m_hMenu )
	{
		// Free item data if this is not a
		// menu created by GetSubMenu().
		if ( ! m_bIsSubMenu )
		{
			// Destroy the data from the menus.
			DestroyItemData();

			// Destroy the handle.
			return ClsMenu::Destroy();
		}
	}
	return TRUE;
}

// Remove all objects from the temporary list. All call's
// to GetSubMenu() will create a temporary object.
void ClsBitmapMenu::DeleteTempObjects()
{
	// Iterate temporary objects deleting
	// them.
	ClsBitmapMenu *pBMenu;
	while (( pBMenu = temporary_bm_list.RemoveHead()) != NULL )
	{
		// Detach the handle before destroying it.
		pBMenu->Detach();
		delete pBMenu;
	}
}

// Setup the imagelist to use.
HIMAGELIST ClsBitmapMenu::SetImageList( HIMAGELIST hImages )
{
	// Get the old images.
	HIMAGELIST hOldImages = m_hImageList;

	// Get the new image-list and retrieve it's image size.
	if (( m_hImageList = hImages ) != NULL )
		::ImageList_GetIconSize( hImages, &m_cxBitmap, &m_cyBitmap );
	
	// Return old list.
	return hOldImages;
}

// Convert the menus.
BOOL ClsBitmapMenu::ConvertMenus()
{
	// Do the conversion.
	m_bConverted = ConvertMenusHelper();
	return m_bConverted;
}

// Convert the menus to ownerdraw menus.
BOOL ClsBitmapMenu::ConvertMenusHelper( HMENU hMenu /* = NULL */ )
{
	_ASSERT_VALID( m_hMenu ); // Must be valid.

	// Get the menu handle to use.
	HMENU hUseMenu = hMenu ? hMenu : m_hMenu;

	// Get the item count.
	int nItems = ::GetMenuItemCount( hUseMenu );

	// Iterate items.
	ClsMenuItemInfo	mi;
	TCHAR szBuffer[ 4096 ]; // Win32 strings are 4Kchars maximum.
	for ( int i = 0; i < nItems; i++ )
	{
		// Any submenus?
		HMENU hSubMenu = ::GetSubMenu( hUseMenu, i );
		if ( hSubMenu )
		{
			// Convert the submenus also.
			if ( ! ConvertMenusHelper( hSubMenu ))
				return FALSE;
		}

		// Get the item type data.
		mi.fMask	= MIIM_TYPE | MIIM_ID;
		mi.dwTypeData	= szBuffer;
		mi.cch		= 4096;

		if ( ::GetMenuItemInfo( hUseMenu, i, TRUE, &mi ))
		{
			// String?
			if ( mi.fType == MFT_STRING )
			{
				// Create data structure.
				ItemData *pData = new ItemData;

				// Setup data structure.
				pData->m_bSeparator  = FALSE;
				pData->m_bToplevel   = ( hMenu == NULL && m_bIsPopup == FALSE ) ? TRUE : FALSE;
				pData->m_iBitmap     = -1;
				pData->m_strItemName = szBuffer;

				// Force it to be an onwerdraw
				// item.
				mi.fMask      = MIIM_TYPE | MIIM_DATA;
				mi.fType      = MFT_OWNERDRAW;
				mi.dwItemData = ( ULONG_PTR )pData;
				
				// Change the item.
				if ( ! ::SetMenuItemInfo( hUseMenu, i, TRUE, &mi ))
					return FALSE;
			}
			else if ( mi.fType == MFT_SEPARATOR )
			{
				// Create data structure.
				ItemData *pData = new ItemData;

				// Setup the structure.
				pData->m_bSeparator = TRUE;
				pData->m_bToplevel  = FALSE;
				pData->m_iBitmap    = -1;

				// Change the type of the
				mi.fMask	= MIIM_TYPE | MIIM_DATA | MIIM_STATE;
				mi.fType	= MFT_OWNERDRAW | MFT_SEPARATOR;
				mi.fState	= MFS_DISABLED;
				mi.dwItemData   = ( ULONG_PTR )pData;
				
				// Change the item.
				if ( ! ::SetMenuItemInfo( hUseMenu, i, TRUE, &mi ))
					return FALSE;
			}
		}
		else
			return FALSE;
	}
	return TRUE;
}

// Set an item it's bitmap.
BOOL ClsBitmapMenu::SetItemBitmap( UINT nItemID, int iBitmap, BOOL bByPos /* = FALSE */ )
{
	// Is the conversion done yet?
	if ( ! m_bConverted ) 
		m_bConverted = ConvertMenusHelper();

	// Get item data.
	ClsMenuItemInfo	 mi;
	mi.fMask	= MIIM_DATA | MIIM_TYPE;
	mi.dwTypeData	= NULL;
	mi.cch		= 0;
	if ( GetItemInfo( nItemID, &mi, bByPos ))
	{
		// Only ownerdraw items.
		if ( mi.fType == MFT_OWNERDRAW )
		{
			// Get the pointer to the item
			// data structure.
			ItemData *pData = ( ItemData * )mi.dwItemData;
			if ( pData )
			{
				// Setup the bitmap index.
				pData->m_iBitmap = iBitmap;
				return TRUE;
			}
		}
		else
			return TRUE;
	}

	return FALSE;
}

// Set item bitmaps.
BOOL ClsBitmapMenu::SetItemBitmap( StrItemBitmap *pItemBitmaps, BOOL bByPos /* = FALSE */ )
{
	_ASSERT_VALID( pItemBitmaps ); // Must be valid.

	// Iterate array.
	while ( pItemBitmaps->m_wID != SBM_END )
	{
		// Set the bitmap.
		if ( SetItemBitmap( pItemBitmaps->m_wID, pItemBitmaps->m_iBitmap, bByPos ) == FALSE )
			return FALSE;

		// Next...
		pItemBitmaps++;
	}
	return TRUE;
}

// Get a menu text.
BOOL ClsBitmapMenu::GetItemText( UINT nItemID, ClsString& str, BOOL bByPos /* = FALSE */ )
{
	_ASSERT_VALID( m_hMenu ); // No items in a NULL handle.

	// Preset string.
	str = _T( "" );

	// Obtain item information.
	ClsMenuItemInfo mi;
	TCHAR sz[ 4096 ];
	mi.dwTypeData = sz;
	mi.cch        = 4096;
	mi.fMask      = MIIM_DATA | MIIM_TYPE;
	if ( GetItemInfo( nItemID, &mi, bByPos ))
	{
		// Is it a converted item?
		if ( mi.fType & MFT_OWNERDRAW && mi.dwItemData )
		{
			// Obtain data and copy string.
			ItemData *pData = ( ItemData * )mi.dwItemData;
			str = pData->m_strItemName;
		}
		else
			// Set string.
			str = sz;
		return TRUE;
	}
	return FALSE;
}

// Set a menu text.
BOOL ClsBitmapMenu::SetItemText( UINT nItemID, LPCTSTR pszName, BOOL bByPos /* = FALSE */ )
{
	_ASSERT_VALID( m_hMenu ); // No items in a NULL handle.
	_ASSERT_VALID( pszName ); // Must be valid.

	// Obtain item information.
	ClsMenuItemInfo mi;
	mi.dwTypeData = NULL;
	mi.fMask      = MIIM_DATA | MIIM_TYPE;
	if ( GetItemInfo( nItemID, &mi, bByPos ))
	{
		// Is it a converted item?
		if ( mi.fType & MFT_OWNERDRAW && mi.dwItemData )
		{
			// Obtain data an replace the old string.
			ItemData *pData = ( ItemData * )mi.dwItemData;
			pData->m_strItemName = pszName;
			return TRUE;
		}
		else
		{
			// Set string.
			mi.cch = _tcslen( pszName );
			mi.dwTypeData = ( LPTSTR )pszName;
                        BOOL rc = SetItemInfo( nItemID, &mi, bByPos );
			return rc;
		}
	}
	return FALSE;
}

// Get a sub-menu.
ClsBitmapMenu *ClsBitmapMenu::GetSubMenu( int nPos )
{
	_ASSERT_VALID( m_hMenu ); // No items in a NULL handle.

	// Get the sub menu.
	HMENU hMenu = ::GetSubMenu( m_hMenu, nPos );

	// OK?
	if ( hMenu )
	{
		// Does it already exist?
		ClsBitmapMenu *pMenu;
		for ( pMenu = temporary_bm_list.GetFirst(); pMenu; pMenu = temporary_bm_list.GetNext( pMenu ))
		{
			// Is this it?
			if ( *pMenu == hMenu )
				return pMenu;
		}
			
		// Create menu.
		pMenu = new ClsBitmapMenu;
		pMenu->Attach( hMenu );

		// Copy resource data.
		//
		// The Free() routine will not 
		// free the allocated data from an 
		// object which is created with the 
		// GetSubMenu() call.
		pMenu->m_cxBitmap   = m_cxBitmap;
		pMenu->m_cyBitmap   = m_cyBitmap;
		pMenu->m_hImageList = m_hImageList;

		// Make sure the object knows it is
		// a submenu. This way it will not
		// free the parent resources.
		pMenu->m_bIsSubMenu = TRUE;
		pMenu->m_bIsPopup   = TRUE;

		// Remove it from the global menu list.
		global_menu_list.Remove( pMenu );

		// Add it to the list.
		temporary_bm_list.AddHead( pMenu );
		return pMenu;
	}
	return NULL;
}

// Measure an item.
LRESULT ClsBitmapMenu::OnReflectedMeasureItem( LPMEASUREITEMSTRUCT pmis, BOOL& bNotifyParent )
{
	// Correct message?
	if ( pmis->CtlType != ODT_MENU )
		return FALSE;

	// Seperator?
	ItemData *pData = ( ItemData * )pmis->itemData;
	if ( pData == NULL ) return FALSE;
	if ( pData->m_bSeparator == TRUE )
	{
		pmis->itemWidth = ::GetSystemMetrics( SM_CXFRAME * 3 );
		pmis->itemHeight = ::GetSystemMetrics( SM_CYFRAME ) * 3;
		return TRUE;
	}

	// Preset result.
	BOOL	 bRC = FALSE;
	
	// No need to bother the parent.
	bNotifyParent = FALSE;

	// Valid data?
	if ( pData != NULL )
	{
		// Yes. We have at least a width and
		// height of the image width and height
		// increased by 2 pixels unless we are
		// top-level.
		if ( ! pData->m_bToplevel )
		{
			pmis->itemWidth  = m_cxBitmap + 6;
			pmis->itemHeight = m_cyBitmap + 4;
		}
		else
			pmis->itemWidth = pmis->itemHeight = 0;

		// Any text?
		if ( pData->m_strItemName.GetStringLength())
		{
			// Create a display device context.
			ClsDC displayDC;
			if ( displayDC.CreateDC( _T( "DISPLAY" ), NULL, NULL, NULL ))
			{
				// Get the menu font from the system
				NONCLIENTMETRICS	ncm = { sizeof( NONCLIENTMETRICS ), 0 };
				if ( ::SystemParametersInfo( SPI_GETNONCLIENTMETRICS, sizeof( ncm ), ( void * )&ncm, 0 ))
				{
					// Create the font.
					ClsFont	hFont( &ncm.lfMenuFont );
					if ( hFont.IsValid())
					{
						// Set the font.
						ClsSelector selFont( &displayDC, hFont );

						// Measure the text.
						ClsRect	cRect( 0, 0, 0, 0 );
						if ( displayDC.DrawText( pData->m_strItemName, pData->m_strItemName.GetStringLength(), cRect, DT_EXPANDTABS | DT_CALCRECT ))
						{
							// Adjust the size to also include the
							// item text.
							if ( ! pData->m_bToplevel )
							{
								pmis->itemWidth += cRect.Width() + 20;
								pmis->itemHeight = pmis->itemHeight < ( UINT )cRect.Height() ? cRect.Height() : pmis->itemHeight;
							}
							else
							{
								// The system seems to add the extra room needed...
								pmis->itemWidth += cRect.Width();
								pmis->itemHeight += cRect.Height();
							}

							// Make sure the system defined size is not
							// larger than what we computed.
							int cSystemSize = ::GetSystemMetrics( SM_CYMENU );
							if ( pmis->itemHeight < ( UINT )cSystemSize )
								pmis->itemHeight = cSystemSize;

							// Success.
							bRC = TRUE;
						}
					}
				}
				// Delete the DC.
				displayDC.DeleteDC();
			}
		}
	}

	return bRC;
}

// Split-up the menu caption string in a left and a right part.
void ClsBitmapMenu::SplitCaption( LPCTSTR pszCaption, ClsString& sLeft, ClsString& sRight )
{
	// Assign left strings.
	sLeft = pszCaption;
	sRight.SetStringLength( 0 );

	// If we have a tab character in the item text
	// we split the string in two seperate parts.
	// The item name and the accelerator key string.
	if ( _tcschr( pszCaption, _T( '\t' )))
	{
		// Create a left and right part.
		int nTabPos = sLeft.Find( _T( '\t' )), nPos = nTabPos;

		// Skip other tab characters.
		while ( sLeft[ nPos ] == _T( '\t' )) nPos++;

		// Copy the right part of the item text.
		sRight = sLeft.Right( sLeft.GetStringLength() - nPos );

		// Truncate the left part.
		sLeft.SetStringLength( nPos );
	}
}

// Render radioitem bullet.
void ClsBitmapMenu::RenderRadioBullet( ClsDC *pDC, ClsRect& rcRect, COLORREF crColor )
{
	// Get GDI objects.
	ClsBrush	hBrush( crColor );
	ClsPen		hPen( PS_SOLID, 0, crColor );

	// Objects OK?
	if ( hBrush.IsValid() && hPen.IsValid())
	{
		// Select them. When these objects go out
		// of scope the original objects will be
		// selected automatically.
		ClsSelector selBrush( pDC, hBrush );
		ClsSelector selPen( pDC, hPen );

		// Render the bullet.
		pDC->Ellipse( rcRect.Left()   + (( rcRect.Width()  / 2 ) - 4 ),
			      rcRect.Top()    + (( rcRect.Height() / 2 ) - 4 ),
			      rcRect.Right()  - (( rcRect.Width()  / 2 ) - 4 ),
			      rcRect.Bottom() - (( rcRect.Height() / 2 ) - 4 ));
	}
}

// Render checkmark.
void ClsBitmapMenu::RenderCheckmark( ClsDC *pDC, ClsRect& rcRect, COLORREF crFgColor, COLORREF crBgColor, DWORD dwItemState, LPMENUITEMINFO pmii )
{
	// Bitmap to render.
	ClsBitmap	hCheckmark;

	// Checked?
	if ( dwItemState & ODS_CHECKED )
	{
		// Do we have a bitmap?
		if ( pmii->hbmpChecked ) hCheckmark.Attach( pmii->hbmpChecked );
	}
	else	
	{
		// Any object?
		if ( pmii->hbmpUnchecked )
			hCheckmark.Attach( pmii->hbmpUnchecked );
	}

	// Any bitmap?
	if ( hCheckmark.IsValid())
	{
		// Get information.
		BITMAP bitMap;
		if ( hCheckmark.GetBitmap( &bitMap ))
		{
			// Setup image colors.
			pDC->SetTextColor( crFgColor );
			pDC->SetBkColor(   crBgColor );

			// Draw the image.
			ClsBrush cmc( crFgColor );
			pDC->DrawState( ClsPoint( rcRect.Left() + (( rcRect.Width()  / 2 ) - ( bitMap.bmWidth  / 2 )),
							rcRect.Top()  + (( rcRect.Height() / 2 ) - ( bitMap.bmHeight / 2 )) - 1 ),
					ClsSize( rcRect.Width(), rcRect.Height()),
					&hCheckmark,
					DSS_MONO | ( dwItemState & ODS_DISABLED ? DSS_DISABLED : 0 ), &cmc );
		}
	}
	else
		// Draw our own checkmark...
		ClsDrawTools::RenderMonoBitmap( *pDC, ClsGetApp()->GetClsImages(), ClsApp::CII_CHECKMARK, rcRect, crFgColor ); 
}

// Render an item.
LRESULT ClsBitmapMenu::OnReflectedDrawItem( LPDRAWITEMSTRUCT pdis, BOOL& bNotifyParent )
{
	// Menu?
	if ( pdis->CtlType != ODT_MENU )
		return FALSE;

	// No need to bother the parent.
	bNotifyParent = FALSE;

	// Seperator?
	ItemData *pData = ( ItemData * )pdis->itemData;
	if ( pData == NULL ) return FALSE;
	if ( pData->m_bSeparator == TRUE )
	{
		// Render the seperator.
		ClsRect rc( pdis->rcItem );
		::FillRect( pdis->hDC, rc, ( HBRUSH )( COLOR_MENU + 1 ));

		// Adjust seperator position and
		// render it.
		rc.Top() += rc.Height() / 2;
		::DrawEdge( pdis->hDC, rc, EDGE_ETCHED, BF_TOP );
		return TRUE;
	}

	// Wrap the DC.
	ClsDC	cDC( pdis->hDC );

	// Get some flags settings.
	BOOL bDisabled = ( BOOL )( pdis->itemState & ( ODS_DISABLED | ODS_GRAYED ));
	BOOL bSelected = ( BOOL )( pdis->itemState & ODS_SELECTED );
	BOOL bChecked  = ( BOOL )( pdis->itemState & ODS_CHECKED );

	// Get background color.
	ClsRect		rcBitmap;

	// Snapshot the DC.
	int nDC = cDC.SaveDC();
	int nCol;

	// Are we top-level?
	if ( ! pData->m_bToplevel )
	{
		// Create icon rectangle.
		rcBitmap	 = pdis->rcItem;
		rcBitmap.Right() = rcBitmap.Left() + m_cxBitmap + 5;

		// Do we render a frame or are we checked?
		if ( m_bIconFrame || bChecked )
			nCol = COLOR_MENU;
		else
		{
			// Disabled?
			if ( bDisabled )
			{
				// Are we selected?
				if ( bSelected ) nCol = COLOR_HIGHLIGHT;
				else 		 nCol = COLOR_MENU;
			}
			else
			{
				// Selected or not?
				if ( bSelected ) nCol = COLOR_HIGHLIGHT;
				else		 nCol = COLOR_MENU;
			}
		}

		// Clear icon area.
		cDC.FillRect( rcBitmap, ( HBRUSH )( nCol + 1 ));
		rcBitmap.Right()--;

		// Render the checkmark rectangle.
		if ( bChecked )
		{
			// Create a rectangle 2 pixels smaller than the
			// bitmap rectangle.
			ClsRect		rc = rcBitmap;
			rc.Deflate( 1, 1 );
			
			// Render the background of the
			// checkmark rectangle.
			if ( bSelected == FALSE && bDisabled == FALSE )
			{
				// Convert the menu background color to a color
				// with it's lightness increased or decreased by 20
				// depending on the current lightness level.
				ClsColor cc( ::GetSysColor( COLOR_MENU ));
				if ( cc.Lightness() >= 220 ) cc.DarkenColor( 0.1, 0.1, 0.1 );
				else cc.LightenColor( 0.5, 0.5, 0.5 );
				ClsBrush bgc( cc );
				cDC.FillRect( rc, bgc );
			}
			
			// Render the rectangle.
			cDC.DrawEdge( rc, BDR_SUNKENOUTER, BF_RECT );
		}

		// Render bitmap?.
		if ( pData->m_iBitmap >= 0 && m_hImageList )
		{
			_ASSERT_VALID( m_hImageList ); // Should be valid.

			// Render.
			ClsDrawTools::RenderBitmap( pdis->hDC, m_hImageList, pData->m_iBitmap, rcBitmap, ( pdis->itemState & ODS_DISABLED ? ClsDrawTools::CDSF_DISABLED : 0 ));

			// Draw icon rectangle when necessary
			if ( bSelected && bDisabled == FALSE && m_bIconFrame )
			{
				// Create icon rectangle.
				ClsRect	rcRect = pdis->rcItem;

				// Adjust...
				rcRect.Left()++;
				rcRect.Right() = rcRect.Left() + m_cxBitmap + 4;

				// Render rectangle.
				cDC.DrawEdge( rcRect, BDR_RAISEDINNER, BF_RECT );
			}
		}
	}

	// Render text...
	if ( pData->m_strItemName.GetStringLength())
		RenderItemText( pdis, pData );

	// Did we render a bitmap?
	if ( pData->m_iBitmap == -1 )
	{
		// Checkmark?
		ClsMenuItemInfo	mi;
		mi.fMask = MIIM_CHECKMARKS | MIIM_TYPE;

		// Get the information.
		if ( GetItemInfo( pdis->itemID, &mi ))
		{
			// Draw a bullet checkmark?
			if (( mi.fType & MFT_RADIOCHECK ) && bChecked )
				// Render the radio bullet.
				RenderRadioBullet( &cDC, rcBitmap, bDisabled ? ::GetSysColor( COLOR_GRAYTEXT ) : ::GetSysColor( COLOR_MENUTEXT ));
			else if ( bChecked )
				// Render the checkmark.
				RenderCheckmark( &cDC, rcBitmap, bDisabled ? ::GetSysColor( COLOR_GRAYTEXT ) : ::GetSysColor( COLOR_MENUTEXT ), ::GetSysColor( COLOR_MENU ), pdis->itemState, &mi );
		}
	}

	// Reset last snapshot.
	cDC.RestoreDC( nDC );
	return TRUE;
}

// Helper to render the item text.
void ClsBitmapMenu::RenderItemText( DRAWITEMSTRUCT *pdis, ItemData *pData )
{
	int			nCol, nBrush;
	ClsBrush		hBrush;
	ClsSize			szSize;
	ClsRect			rcRect = pdis->rcItem;
	ClsDC			cDC( pdis->hDC );

	// Get some flags settings.
	BOOL bDisabled = ( BOOL )( pdis->itemState & ( ODS_DISABLED | ODS_GRAYED ));
	BOOL bSelected = ( BOOL )( pdis->itemState & ODS_SELECTED );
	BOOL bChecked  = ( BOOL )( pdis->itemState & ODS_CHECKED );

	// Toplevel item?
	if ( pData->m_bToplevel )
	{
		// Render background.
		cDC.FillRect( &pdis->rcItem, ( HBRUSH )( COLOR_BTNFACE + 1 ));

		// Render rectangle when necessary.
		if ( bSelected || pdis->itemState & ODS_HOTLIGHT )
			cDC.DrawEdge( &pdis->rcItem, ( bSelected && IsDropped()) ? BDR_SUNKENOUTER : BDR_RAISEDINNER, BF_RECT );

		// Select the color in which the text is rendered.
		nCol = pdis->itemState & ODS_INACTIVE ? COLOR_GRAYTEXT : COLOR_MENUTEXT;

		// Determine text rectangle.
		rcRect.Offset(( bSelected && IsDropped()) ? 7 :6, ( bSelected && IsDropped()) ? 1 : 0 );
	}
	else
	{
		// Disabled?
		if ( bDisabled )
		{
			// Are we selected?
			if ( bSelected )
			{
				// Render using highlight color.
				nBrush = COLOR_HIGHLIGHT;

				// Remove the disabled bit and render
				pdis->itemState &= ~ODS_DISABLED;
			}
			else
				// Menu color.
				nBrush = COLOR_MENU;

			// Gray text.
			nCol   = COLOR_GRAYTEXT;
		}
		else
		{
			// Selected or not?
			if ( bSelected )
			{
				// Highlight color.
				nBrush = COLOR_HIGHLIGHT;

				// Highlight text color.
				nCol   = COLOR_HIGHLIGHTTEXT;
			}
			else
			{
				// Menu
				nBrush = COLOR_MENU;
				nCol   = COLOR_MENUTEXT;
			}
		}

		// Create the brush.
		hBrush.CreateSysColorBrush( nBrush );

		// We fill the whole area if the item has
		// a bitmap to render or if it is checked.
		if ( pData->m_iBitmap >= 0 || bChecked ) rcRect.Left() += m_cxBitmap +  + ( m_bIconFrame ? 6 : 4 );

		// Render the background.
		if ( hBrush.IsValid())
			cDC.FillRect( rcRect, &hBrush );

		// Skip checkmark width.
		rcRect.Right() -= ::GetSystemMetrics( SM_CXMENUCHECK );

		// Move right.
		if ( pData->m_iBitmap >= 0 || bChecked ) rcRect.Left() += 2;
		else					 rcRect.Left() += m_cxBitmap + ( m_bIconFrame ? 6 : 4 );
	}

	// Any text?
	if ( pData->m_strItemName )
	{
		// Set draw mode and color.
		cDC.SetBkMode( TRANSPARENT );
		cDC.SetTextColor( GetSysColor( nCol ));

		// Split caption in a left an right part.
		ClsString Left, Right;
		SplitCaption( pData->m_strItemName, Left, Right );

		// Make some room.
		rcRect.Right() -= 6;

		// Render the parts.
		if ( Left.GetStringLength())  ClsDrawTools::RenderText( pdis->hDC, Left,  rcRect, ClsDrawTools::CDSF_LEFTALIGN  | ( pdis->itemState & ODS_DISABLED ? ClsDrawTools::CDSF_DISABLED : 0 ) | ( pdis->itemState & ODS_NOACCEL ? ClsDrawTools::CDSF_HIDEACCEL : 0 ));
		if ( Right.GetStringLength()) ClsDrawTools::RenderText( pdis->hDC, Right, rcRect, ClsDrawTools::CDSF_RIGHTALIGN | ( pdis->itemState & ODS_DISABLED ? ClsDrawTools::CDSF_DISABLED : 0 ));
	}
}

// Do we render frames?
BOOL ClsBitmapMenu::DoFrameRendering() 
{
	// Yes, we do it ourselves.
	return TRUE; 
}

// Adjust popup menu position size.
void ClsBitmapMenu::OnChangeWindowPos( LPWINDOWPOS pWindowPos )
{
	// Move allowed?
	if ( ! ( pWindowPos->flags & SWP_NOMOVE ))
	{
		// Windows 2000 has the anoying habbit to open the menu right
		// of the parent item the first time it has to open above
		// the parent when we use custom frames. We adjust it here.
		if ( pWindowPos->x == m_LastParent.Right()) pWindowPos->x = m_LastParent.Left();
	}
}

// Measure the frame.
void ClsBitmapMenu::OnMeasureFrame( LPRECT pRect ) 
{ 
	// Get frame sizes.
	int x = ::GetSystemMetrics( SM_CXEDGE ) + 1;
	int y = ::GetSystemMetrics( SM_CYEDGE ) + 1;

	// Setup frame size in pixels.
	pRect->left   = x;
	pRect->top    = y;
	pRect->right  = m_bDrawShadow ? ( ClsGetApp()->IsShadowEnabled() ? x : x + 4 ) : x;
	pRect->bottom = m_bDrawShadow ? ( ClsGetApp()->IsShadowEnabled() ? y : y + 4 ) : y;
}

// Render the menu frame.
void ClsBitmapMenu::OnDrawFrame( HDC hDC, LPCRECT pRect, LPCRECT pRectSrc ) 
{
	// Wrap the input DC.
	ClsDC *pDC = ClsDC::FromHandle( hDC );
	if ( pDC == NULL ) return;

	// Copy the input rectangle.
	ClsRect rc = *pRect;

	// Are shadows enabled? If so we only render the frame.
	// If not we have room to render the shadow ourselves.
	if ( ! ClsGetApp()->IsShadowEnabled() && m_bDrawShadow )
	{
		// Adjust for the shadow pixels.
		rc.Right()  -= 4;
		rc.Bottom() -= 4;
	}

	// Render the menu frame.
	pDC->DrawEdge( rc, EDGE_RAISED, BF_RECT );
	rc.Deflate( ::GetSystemMetrics( SM_CXEDGE ), ::GetSystemMetrics( SM_CYEDGE ));
	ClsBrush brush( ::GetSysColor( COLOR_MENU ));
	pDC->FrameRect( rc, &brush );

	// Do we render the shadow ourselves?
	if ( ! ClsGetApp()->IsShadowEnabled() && m_bDrawShadow )
		ClsDrawTools::DrawShadow( hDC, pRect );
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
I have been programming for a hobby since 1985. I have started programming on the C= 64. After that I migrated to the C= Amiga which I traded in for a PC back in 1997 I believe. Back in 2000 I decided to lose a hobby and start developing software for a living.

Currently I am working mainly in developing software for building security and access control systems.

Comments and Discussions