Click here to Skip to main content
15,885,216 members
Articles / Mobile Apps / Windows Mobile

Cool Owner Drawn Menus with Bitmaps - Version 3.03

Rate me:
Please Sign up or sign in to vote.
4.92/5 (76 votes)
28 Apr 2002CPOL15 min read 1.2M   16.9K   268  
This class implements an owner drawn menu class that mimics the menu style used in XP, Office and Visual C++
/////////////////////////////////////////////////////////////////////////////
// Copyright (C) 1997,'98 by Joerg Koenig
// All rights reserved
//
// Distribute freely, except: don't remove my name from the source or
// documentation (don't take credit for my work), mark your changes (don't
// get me blamed for your possible bugs), don't alter or remove this
// notice.
// No warrantee of any kind, express or implied, is included with this
// software; use at your own risk, responsibility for damages (if any) to
// anyone resulting from the use of this software rests entirely with the
// user.
//
// Send bug reports, bug fixes, enhancements, requests, flames, etc., and
// I'll try to keep a version up to date.  I can be reached as follows:
//    J.Koenig@adg.de                 (company site)
//    Joerg.Koenig@rhein-neckar.de    (private site)
/////////////////////////////////////////////////////////////////////////////

// last revised: $Date: 10.05.98 17:45 $ $Revision: 2 $

// ToolBarEx.cpp : implementation file
//
// Description:
//	CToolBarEx provides additional features to the standard toolbar
//	"CToolBar". The main addition is the flat mode (last seen in
//	Developer Studio 5.0).
//	There are no special requirements for having the flat mode in your
//	application (no special comctl32.dll or what ever)!
//	If you need custom draw abilities, then you have to use VC++ >= 4.2
//	However, the flat mode should work with older versions of VC++ too (let
//	me know of your experiences!)
//
// Usage:
//	The only task you have to perform, is to
//		#include "ToolBarEx.h"
//	in either StdAfx.h or MainFrm.h and to change the type of
//	CMainFrame::m_wndToolBar from CToolBar to CToolBarEx.
//	Don't forget to recompile :-)
//	if you want to reduce flickering, then you have to include
//	the header of Keith Rule's CMemDC class in your <stdafx.h>.
//	CToolBarEx will use it automagically then.
//	If you want a "real cool" 3D look, then you have to exchange the call
//	to "EnableDocking()" in your CMainFrame::OnCreate() method to
//	"FrameEnableDocking()". This will ensure, that the toolbar has the
//	same outfit as in Office or DevStudio.
//
// Acknowledgements:
//	o	The main idea of how to draw a separator and the gripper is stolen
//		from Roger Onslow's MSIE flat toolbar.
//		Thanks for saving my time, Roger ;-)
//	o	The embossed drawing of a disabled image came from
//		Victor M. Vogelpoel (victorv@telic.nl)
//	o	Some hints for buttons with text came from
//		David Bates (bates@econ.ubc.ca)
//		(I'm still thinking, text on toolbar-buttons is broken.
//		That has to be tooltip's work. However, texts on buttons
//		now work)
//	o	Thanks to Patrick Liechty (patrickl@code3.code3.com) for
//		the reports of his experiences with VC++ 4.0/4.1
//	o	Thanks to Jeng-Yuan Sheu (m8501511@chu.edu.tw) for the
//		enhanced checked button.
//	o	Thanks to Todd C. Wilson (todd@mediatec.com) for his
//		bug report and code-enhancement for users of VC++ 4.2b
//	o	Matthias Drigalla <matthias.drigalla@bluewin.ch> has pointed
//		me out, that the timer is not released in all circumstances.
//	o	thanks to Jonathan Chin <jonathan.chin@scitec.com.au> for his
//		experiences with UNICODE and for his enhancement to conform to
//		the standard interface (the flat toolbar should not perform any
//		hit test (and hence displaying the raised button state) if the
//		parent window (application) does not have the focus or is disabled.)
//	o	thanks to Wang Ruopeng (ripple@thinker.ep.tsinghua.edu.cn) for
//		the enhanced outfit of checked buttons
//
//	o	Many thanks to Victor Vogelpoel, Jonathan Chin and Patrick Liechty
//		for their help to test and correct some bugs in the enhanced version.
//		Their work made this class much cleaner. Thanks.
//
//	o	Wolfgang Loch (W.Loch@intershop.de) sent in a fix for undockable bars
//	o	Thanks to John Armstrong (jarmstrong@runge.com.au) for his fix to
//		the adjustment of gripperspace in "classic" mode
//	o	Thanks to Anatoly Ivasyuk (aivasyuk@clark.net) for a much cooler method
//		to draw checked buttons that does not have the cursor on it.
//
//
// (known) bugs and limitations:
//	o	the CDRF_NEWFONT notification is still untested ...
//	o	Assigning texts to buttons may cause the buttons to
//		resize horizontally without notified by CToolBar. This
//		leads to a wrong calculation inside CalcDynamicLayout()
//		and CalcFixedLayout(). One could override both these
//		functions in derived classes to avoid that problem,
//		but that would be a greater pain ...
//	o	some features of the toolbars seen in Office97/DevStudio
//		are not implemented (for instance text-only buttons or
//		the way how image+text buttons are displayed.
//
//	if you find others (and have a solution for them ?!), please let me know:
//		Joerg.Koenig@rhein-neckar.de		(private site) or
//		J.Koenig@adg.de						(company site)
//
// Changes:
//	05/10/98
//	o	buttons that are checked *and* disabled are now looking ok.
//	o	don't draw a gripper if the bar is not dockable
//	o	do not adjust space for gripper in "classic" mode
//
//	01/17/98
//	o	If you have Keith Rule's "MemDC.h" included via
//		"stdafx.h", then CToolBarEx uses it to reduce
//		flickering.
//	o	release the timer in all circumstances
//	o	made the class compile even if UNICODE is #define'ed
//	o	do not make any hit test (and display a raised button state)
//		if the application does not have the focus.
//	o	Changed the outfit of a checked button that does not have
//		the cursor on it.
//	Major enhancements:
//	o	added easy way to add controls to a toolbar
//		(both: replacing a button with a control and
//		inserting/appending new controls)
//	o	Added many functions especially for the often
//		requested need for additional controls
//	o	ALT-Drag feature implemented. You just can move buttons
//		(and controls!) from one CToolBarEx-object to another
//		in the style of the Developer Studio. There is no additional
//		requirement for having this feature in your application.
//		Just follow the online-help for how to enable customizable
//		toolbars. CToolBarEx uses the same technique.
//
//	11/25/97
//	o	Some minor modifications to compile with VC++ 4.0/4.1 and 4.2b
//	o	checked buttons now look hilighted (as in Office97/DevStudio)
//
//	11/07/97
//	(2 minor bugs have been occured as a result of the last update :)
//	o	The WRAP state of a separator will be ignored now, if
//		the bar is docked vertically
//	o	Draw an image transparently. This is needed only if one
//		uses 256 color images.
//
//	10/30/97
//	o	texts on buttons now work
//	o	gripper improved for a closer look like Office97
//	o	disabled images now look embossed
//	o	a separator is drawn only if it has no WRAP state set



#include "stdafx.h"
#include "ToolBarEx.h"

#ifndef __AFXPRIV_H__
	#include <afxpriv.h>	// one of the most interesting headers ;-)
							// if you plan to change CToolBarEx, then you
							// should better insert it in your "stdafx.h"
							// to speed up compilation
#endif


#ifdef _MEMDC_H_
	//#undef _MEMDC_H_		// this is for testing purposes
#endif



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


/////////////////////////////////////////////////////////////////////////////
// local helper class CCustomDrawInfo
//
// The helper class CCustomDrawInfo handles the messaging to the docking
// frame of the toolbar in flat mode only. If flat-mode is disabled, then
// MFC's own messanger will be used.
//
// A few words about custom draw on toolbars:
// o custom draw is possible for MFC >= 4.2 only (older versions don't know
//   anything about certain structures ...)
// o MFC does not set the "rc" member of NMCUSTOMDRAW to the rectangle of the
//	 button that will be drawn. However, we do, so watch out, whether the
//	 toolbar is flat or not (or ignore the "rc" member in both cases).
//	 If the current mode is not "flat", then MFC's art of message arrives ...
// o MFC does not send a message for separators, so we too don't do it.
// o It seems that MFC toolbars never send *ERASE notifications; instead they
//   send TBN_QUERYDELETE for instance.
// o The CDRF_NEWFONT notification result is ignored (in flat mode. Never
//   tried with original MFC, because it is broken on toolbars).
/////////////////////////////////////////////////////////////////////////////



class CCustomDrawInfo {

#if _MFC_VER >= 0x0420
	NMCUSTOMDRAW	m_CDRW;				// custom draw information holder
	LRESULT			m_PrePaint;			// result from prepaint notification
	LRESULT			m_ItemPrePaint;		// dito for specific item
	CToolBarEx *	m_pToolBar;			// the real sender of the notification
	CWnd *			m_pReceiver;		// the receiver of the notification

	LRESULT			NotifyParent();
#endif // _MFC_VER

	public:		// construction
		CCustomDrawInfo( CDC & dc, CToolBarEx * pToolBar );

	public:
		// NotifyItemPrePaint() returns TRUE,
		// if the user wants to do the default
		// (CDRF_DODEFAULT) or FALSE, if the
		// user wants to skip (CDRF_SKIPDEFAULT)
		// Note that CDRF_SKIPDEFAULT is not
		// allowed for CDDS_PREPAINT, CDDS_POSTPAINT !
		// and CDDS_ITEMPOSTPAINT
		void	NotifyPrePaint();
		BOOL	NotifyItemPrePaint(int item);
		void	NotifyItemPostPaint(int item);
		void	NotifyPostPaint();
};


#if _MFC_VER >= 0x420

	LRESULT CCustomDrawInfo :: NotifyParent() {
		LRESULT lRes = CDRF_DODEFAULT;

		if( m_pReceiver )
			lRes = m_pReceiver->SendMessage(WM_NOTIFY,
											WPARAM(m_CDRW.hdr.idFrom),
											LPARAM(&m_CDRW));
		return lRes;
	}


	CCustomDrawInfo :: CCustomDrawInfo( CDC & dc, CToolBarEx * pBar )
		: m_PrePaint(0)
		, m_ItemPrePaint(0)
	{
		VERIFY((m_pToolBar = pBar) != 0);
		VERIFY((m_CDRW.hdc = dc.GetSafeHdc()) != 0);

		HWND hwnd = pBar->GetSafeHwnd();
		VERIFY(::IsWindow(hwnd));

		// initialise the NMHDR member of the customdraw structure
		m_CDRW.hdr.hwndFrom = hwnd;
		m_CDRW.hdr.idFrom = UINT(::GetWindowLong(hwnd, GWL_ID));
		m_CDRW.hdr.code = NM_CUSTOMDRAW;

		// Do not use CControlBar::GetDockingFrame() to receive
		// the parent. CWnd::GetParent() is inacceptable too.
		// Both these functions don't work, if the toolbar is
		// floating in the air!
		m_pReceiver = pBar->GetParentFrame();
		if( m_pReceiver )
			VERIFY(::IsWindow(m_pReceiver->GetSafeHwnd()));
	}

	void CCustomDrawInfo :: NotifyPrePaint() {
		// fill the customdraw structure with values for CDDS_PREPAINT
		m_CDRW.dwDrawStage = CDDS_PREPAINT;
		// the rest of the structure stays undefined in this stage
		// of drawing.
		m_PrePaint = NotifyParent();
	}

	BOOL CCustomDrawInfo :: NotifyItemPrePaint( int nItem ) {
		BOOL bRet = TRUE;	// we assume to do the default
		if( m_PrePaint & CDRF_NOTIFYITEMDRAW ) {
			m_CDRW.dwDrawStage = CDDS_ITEMPREPAINT;
			m_pToolBar->GetItemRect(nItem, &m_CDRW.rc);
			m_CDRW.dwItemSpec = DWORD(m_pToolBar->GetItemID(nItem));
			UINT uStyle = m_pToolBar->GetButtonStyle(nItem);
			BOOL bEnable = m_pToolBar->GetToolBarCtrl()
							.IsButtonEnabled(m_CDRW.dwItemSpec);
			m_CDRW.uItemState = (bEnable ? 0 : CDIS_DISABLED) |
								(((uStyle & TBBS_PRESSED) || (uStyle & TBBS_CHECKED)) ?
									CDIS_CHECKED : 0);
			m_CDRW.lItemlParam = 0;
			m_ItemPrePaint = NotifyParent();
			if( m_ItemPrePaint & CDRF_SKIPDEFAULT )
				bRet = FALSE;
		}
		return bRet;
	}

	void CCustomDrawInfo :: NotifyItemPostPaint( int nItem ) {
		if( m_ItemPrePaint & CDRF_NOTIFYPOSTPAINT ) {
			m_CDRW.dwDrawStage = CDDS_ITEMPOSTPAINT;
			// the rest of the data has not been changed since ITEMPREPAINT
			// make sure it is so:
			ASSERT(m_pToolBar->GetItemID(nItem) == m_CDRW.dwItemSpec);
			NotifyParent();
		}
	}

	void CCustomDrawInfo :: NotifyPostPaint() {
		if( m_PrePaint & CDRF_NOTIFYPOSTPAINT ) {
			m_CDRW.dwDrawStage = CDDS_POSTPAINT;
			NotifyParent();
		}
	}
			
#else	// _MFC_VER < 4.2

	CCustomDrawInfo :: CCustomDrawInfo( CDC & dc, CToolBarEx * pParent ) {
	}

	void CCustomDrawInfo :: NotifyPrePaint() {
	}

	void CCustomDrawInfo :: NotifyPostPaint() {
	}
	
	BOOL CCustomDrawInfo :: NotifyItemPrePaint( int ) {
		return TRUE;	// we always make the drawing by ourself
	}

	void CCustomDrawInfo :: NotifyItemPostPaint( int ) {
	}

#endif	// _MFC_VER


/////////////////////////////////////////////////////////////////////////////
// CToolBarEx  --  class static data

HCURSOR			CToolBarEx :: m_hOrigCursor		= 0;
HCURSOR			CToolBarEx :: m_hDragCursor		= 0;
HCURSOR			CToolBarEx :: m_hNoDragCursor	= 0;
BOOL			CToolBarEx :: m_bDragging		= FALSE;
BOOL			CToolBarEx :: m_bDragCursor		= FALSE;
int				CToolBarEx :: m_nDragButton		= -1;
CToolBarEx *	CToolBarEx :: m_pDropBar		= 0;
CPoint			CToolBarEx :: m_ptDrop(0,0);

/////////////////////////////////////////////////////////////////////////////
// CToolBarEx

CToolBarEx::CToolBarEx()
	: m_bFlatLook(TRUE)
	, m_clrBtnFace(::GetSysColor(COLOR_BTNFACE))
	, m_clrBtnHilight(::GetSysColor(COLOR_BTNHILIGHT))
	, m_clrBtnShadow(::GetSysColor(COLOR_BTNSHADOW))
	, m_clrBtnLight(::GetSysColor(COLOR_3DLIGHT))
	, m_nLastBtn(-1)
	, m_uTimerEvent(0)
	, m_pControls(0)
	, m_bReal3DBorder(FALSE)	// set to TRUE, if you're using FrameEnableDocking()
	, m_bDragChild(FALSE)
{
	CalculateOffset();

	// create the default font, used for buttons with text
	CFont Font;
	BOOL bOldSys = FALSE;
	if( ! Font.CreateStockObject( DEFAULT_GUI_FONT ) ) {
		// older versions of Windows* (NT 3.51 for instance)
		// fail with DEFAULT_GUI_FONT
		VERIFY( Font.CreateStockObject( SYSTEM_FONT ) );
		bOldSys = TRUE;
	}
	LOGFONT logfont ;
	Font.GetLogFont( &logfont ) ;
	if( bOldSys ) {
		logfont.lfWeight = 400;
		_tcscpy(logfont.lfFaceName,_T("MS Sans Serif"));

	}
	logfont.lfHeight = 6 ;
	logfont.lfWidth = 0 ;	// let windows compute this.
	VERIFY( m_GuiFont.CreateFontIndirect( &logfont ) ) ;
}

CToolBarEx::~CToolBarEx()
{
	if( m_pControls ) {
		for( POSITION pos = m_pControls->GetHeadPosition() ; pos ; )
			delete m_pControls->GetNext(pos);
		delete m_pControls;
	}
}


IMPLEMENT_DYNAMIC(CToolBarEx, CToolBar)


BEGIN_MESSAGE_MAP(CToolBarEx, CToolBar)
	//{{AFX_MSG_MAP(CToolBarEx)
	ON_WM_PAINT()
	ON_WM_SYSCOLORCHANGE()
	ON_WM_NCCALCSIZE()
	ON_WM_MOUSEMOVE()
	ON_WM_NCPAINT()
	ON_WM_TIMER()
	ON_WM_CREATE()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_WINDOWPOSCHANGING()
	ON_WM_CAPTURECHANGED()
	ON_WM_PARENTNOTIFY()
	ON_WM_KILLFOCUS()
	//}}AFX_MSG_MAP
	ON_MESSAGE(TB_SETBUTTONSIZE, OnSetButtonSize)
	ON_MESSAGE(TB_SETBITMAPSIZE, OnSetBitmapSize)
	ON_MESSAGE(TB_ADDBITMAP, OnAddBitmap)
	ON_MESSAGE(TB_DELETEBUTTON, OnDeleteButton)
#ifdef _MEMDC_H_
	ON_WM_ERASEBKGND()
#endif
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CToolBarEx message handlers

LRESULT CToolBarEx :: OnSetButtonSize(WPARAM wParam, LPARAM lParam) {
	LRESULT lResult = CToolBar::OnSetButtonSize(wParam, lParam);
	if( lResult )
		CalculateOffset();
	return lResult;
}

LRESULT CToolBarEx :: OnSetBitmapSize(WPARAM wParam, LPARAM lParam) {
	LRESULT lResult = CToolBar::OnSetBitmapSize(wParam, lParam);
	if( lResult )
		CalculateOffset();
	return lResult;
}


void CToolBarEx :: SetFlatLook( BOOL bFlat ) {
	if( bFlat != m_bFlatLook ) {
		m_bFlatLook = bFlat;
		if( ::IsWindow(GetSafeHwnd()) ) {
            //flat tool bars have gripper space at the left, cause non-client to resize
            SetWindowPos(0, 0,0,0,0,SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER);
			// force a repaint of all buttons
			Invalidate();
			// erase/draw the gripper
			OnNcPaint();
			// if the "FrameEnableDocking()" function is used, we have to adjust
			// the bars inside the parent.
			VERIFY(::IsWindow(m_hwndParent));
			((CFrameWnd*)(CWnd::FromHandle(m_hwndParent)))->RecalcLayout();
		}
	}
}


void CToolBarEx::OnPaint() 
{
	HIMAGELIST hImg = GetImageList();

#ifdef _DEBUG
	if( hImg == 0 ) {
		TRACE0("CToolBarEx::OnPaint(): could not get image list\n");
	}
#endif

	if( m_bFlatLook && hImg ) {
		CRect rcUpdate;
		if( ! GetUpdateRect(rcUpdate) )
			return;

		if( HasButtonText() )
			CalculateOffset();		// strings may have been added

		// attach image-list for even more MFC feeling :)
		CImageList imglist;
		imglist.Attach(hImg);

		POINT cursor;
		::GetCursorPos(&cursor);
		ScreenToClient(&cursor);

		#ifdef _MEMDC_H_
			// if you have Keith Rule's CMemDC class inserted
			// in "stdafx.h", then we use it here ...
			CPaintDC dcp(this);
			CMemDC dc(&dcp);
			dc.FillSolidRect(rcUpdate, m_clrBtnFace);
		#else	// _MEMDC_H_
			CPaintDC dc(this); // device context for painting
		#endif	// _MEMDC_H_

		CFont * pOldFont = dc.SelectObject(&m_GuiFont);

		// Now it's time for the first custom-draw-notification...
		CCustomDrawInfo cdrw(dc, this);
		cdrw.NotifyPrePaint();

		register const int nBtn = GetToolBarCtrl().GetButtonCount();

		for( register int i = 0; i < nBtn; ++i ) {
			CRect rc;
			GetItemRect(i, rc);

			int nBitmap; UINT uID, uStyleState;
			GetButtonInfo(i, uID, uStyleState, nBitmap);
			WORD wStyle = LOWORD(uStyleState);
			WORD wState = HIWORD(uStyleState);

			if( wState & TBSTATE_HIDDEN )
				continue;

			if( wStyle == TBSTYLE_SEP ) {
				if( !(wState & TBSTATE_WRAP) || ! IsFloating() )
					DrawSeparator(dc, rc);
			} else {
				if( ! CRect().IntersectRect(rcUpdate, rc) )
					continue;	// this button needs no repaint

				BOOL bBtnDown = (wState & TBSTATE_CHECKED) || (wState & TBSTATE_PRESSED);
				BOOL bBtnEnabled = GetToolBarCtrl().IsButtonEnabled(int(uID));
				BOOL bHasCursor = rc.PtInRect(cursor);
				COLORREF clrRect = (bBtnDown && !bHasCursor) ? m_clrBtnLight : m_clrBtnFace;

				// maybe the button has text
				dc.SetTextColor(RGB(0,0,0));
				dc.SetBkColor(clrRect);

				if( HasButtonText() )
					// There is a bug in CToolBar: If there are texts assigned
					// to buttons, then the button-widths may change transparently
					// (without notified by CToolBar), so we recalculate the
					// horizontal offset here:
					m_sizeOffset.cx = (rc.Width() - m_sizeImage.cx) / 2;

				if( ! cdrw.NotifyItemPrePaint(i) )
					continue;	// parent has already drawn the button

				dc.FillSolidRect(rc, clrRect);

				// it seems, that CDC::Draw3dRect() changes the background color
				COLORREF clrBk = dc.GetBkColor();

				if( bBtnDown ) {
					// draw a pressed button
					dc.Draw3dRect(rc, m_clrBtnShadow, m_clrBtnHilight);
					if( ! bHasCursor ) {
						// if the button does not have the cursor on it,
						// then the pressed button is somewhat lighter
						// then the other buttons.
						CRect rcCheck = rc;
						rcCheck.DeflateRect(1,1);

						// draw an invisible frame around the hilighted area
						dc.Draw3dRect(rcCheck, m_clrBtnFace, m_clrBtnFace);

						rcCheck.DeflateRect(1,1);
						CBrush *pBrush = CDC::GetHalftoneBrush();
						dc.SetTextColor(m_clrBtnHilight);
						dc.SetBkColor(m_clrBtnFace);
						dc.FillRect(rcCheck, pBrush);
						dc.SetTextColor(RGB(0,0,0));
						dc.SetBkColor(clrRect);
					}
				} else if( bHasCursor && ! bBtnDown && bBtnEnabled )
					// draw a normal button
					dc.Draw3dRect(rc, m_clrBtnHilight, m_clrBtnShadow);
				else if( ! bBtnDown && bBtnEnabled )
					// Draw an invisible rect around the button.
					// This prevents us from erasing the background
					// if the button was formed before
					// (that would cause the button to flicker ...)
					dc.Draw3dRect(rc, m_clrBtnFace, m_clrBtnFace);
				
				dc.SetBkColor(clrBk);

				// the point where to start with the image
				CPoint pt(rc.left + m_sizeOffset.cx + bBtnDown,
						  rc.top + m_sizeOffset.cy + bBtnDown);
				
				imglist.Draw(&dc, nBitmap, pt, ILD_TRANSPARENT);

				CString strText = GetButtonText(i);
				if( strText.GetLength() ) {
					CRect rectText(
						rc.left+3+bBtnDown,
						rc.top+m_sizeOffset.cy+m_sizeImage.cy+1+bBtnDown,
						rc.right-3+bBtnDown,
						rc.bottom-3+bBtnDown
					);
					int nBkMode = dc.SetBkMode(TRANSPARENT) ;
					dc.DrawText(strText, rectText, DT_CENTER|DT_VCENTER|DT_NOCLIP);
					dc.SetBkMode(nBkMode) ;
				}
				
				if( ! bBtnEnabled ) {
					// gray out that button
					rc.DeflateRect(bBtnDown,bBtnDown);
					DrawDisabledButton(dc, rc);
				}

				cdrw.NotifyItemPostPaint(i);
			}
		}

		dc.SelectObject(pOldFont);

		if( ! m_bDeleteImgList )
			imglist.Detach();

		// last but not least: inform the parent for end of painting
		cdrw.NotifyPostPaint();
	} else
		// classic mode (or couldn't receive imagelist)
		CToolBar::OnPaint();
}


void CToolBarEx :: DrawDisabledButton( CDC & dc, const CRect & rc ) const {
	// create a monochrome memory DC
	CDC ddc;
	ddc.CreateCompatibleDC(0);
	CBitmap bmp;
	bmp.CreateCompatibleBitmap(&ddc, rc.Width(), rc.Height());
	CBitmap * pOldBmp = ddc.SelectObject(&bmp);
	
	// build a mask
	ddc.PatBlt(0, 0, rc.Width(), rc.Height(), WHITENESS);
	dc.SetBkColor(m_clrBtnFace);
	ddc.BitBlt(0, 0, rc.Width(), rc.Height(), &dc, rc.left, rc.top, SRCCOPY);
	dc.SetBkColor(m_clrBtnHilight);
	ddc.BitBlt(0, 0, rc.Width(), rc.Height(), &dc, rc.left, rc.top, SRCPAINT);

	// Copy the image from the toolbar into the memory DC
	// and draw it (grayed) back into the toolbar.
	dc.FillSolidRect(rc.left, rc.top, rc.Width(), rc.Height(), m_clrBtnFace);
	dc.SetBkColor(RGB(0, 0, 0));
	dc.SetTextColor(RGB(255, 255, 255));
	CBrush brShadow, brHilight;
	brHilight.CreateSolidBrush(m_clrBtnHilight);
	brShadow.CreateSolidBrush(m_clrBtnShadow);
	CBrush * pOldBrush = dc.SelectObject(&brHilight);
	dc.BitBlt(rc.left+1, rc.top+1, rc.Width(), rc.Height(), &ddc, 0, 0, 0x00E20746L);
	dc.SelectObject(&brShadow);
	dc.BitBlt(rc.left, rc.top, rc.Width(), rc.Height(), &ddc, 0, 0, 0x00E20746L);
	
	// reset DCs
	dc.SelectObject(pOldBrush);
	ddc.SelectObject(pOldBmp);
	ddc.DeleteDC();

	bmp.DeleteObject();
}


void CToolBarEx :: DrawSeparator( CDC & dc, CRect & rc ) const {
    BOOL bHorz = ((m_dwStyle & CBRS_ORIENT_HORZ) != 0) ? TRUE : FALSE;
	// make sure, this separator is not a placeholder for
	// another control.
	if( rc.Width() <= 8 ) {
		if( bHorz ) {
			// draw the separator bar in the middle
			int x = (rc.left + rc.right) / 2;
			rc.left = x-1; rc.right = x+1;
			dc.Draw3dRect(
				rc,
				m_clrBtnShadow,
				m_clrBtnHilight
			);
		} else {
			// draw the separator bar in the middle
			rc.left = rc.left - m_sizeButton.cx;
			rc.right = rc.left + m_sizeButton.cx;
			rc.top = rc.bottom+1;
			rc.bottom = rc.top+3;
			int y = (rc.top+rc.bottom)/2;
			rc.top = y-1; rc.bottom = y+1;
			dc.Draw3dRect(
				rc,
				m_clrBtnShadow,
				m_clrBtnHilight
			);
		}
	}
}


void CToolBarEx :: DrawGripper( CDC & dc ) const { 
    // Do not draw a gripper if the bar is floating or not
	// dockable.
	if( (m_dwStyle & CBRS_FLOATING) || m_dwDockStyle == 0 )
		return;

	CRect gripper;
	GetWindowRect(gripper);
	ScreenToClient(gripper);
	gripper.OffsetRect(-gripper.left, -gripper.top);

	if( m_dwStyle & CBRS_ORIENT_HORZ ) {
		// gripper at left
		gripper.DeflateRect(4, 4);
		gripper.right = gripper.left+3;
        dc.Draw3dRect(
			gripper,
			m_clrBtnHilight,
            m_clrBtnShadow
		);
		gripper.OffsetRect(3, 0);
        dc.Draw3dRect(
			gripper,
			m_clrBtnHilight,
            m_clrBtnShadow
		);
	} else {
		// gripper at top
		gripper.DeflateRect(4, 4);
		gripper.bottom = gripper.top+3;
		dc.Draw3dRect(
			gripper,
			m_clrBtnHilight,
            m_clrBtnShadow
		);
		gripper.OffsetRect(0, 3);
        dc.Draw3dRect(
			gripper,
			m_clrBtnHilight,
            m_clrBtnShadow
		);
	}
}

void CToolBarEx :: OnUpdateCmdUI( CFrameWnd* pTarget, BOOL bDisableIfNoHndler ) {
	if( m_bFlatLook ) {
		// save current styles
		register const int nBtn = GetToolBarCtrl().GetButtonCount();
		register int nIdx;
		for( nIdx = 0; nIdx < nBtn; ++nIdx )
			m_Styles.SetAtGrow(nIdx, GetButtonStyle(nIdx));

		// do base class processing
		CToolBar::OnUpdateCmdUI(pTarget,bDisableIfNoHndler);

		//check whether styles have been changed
		for( nIdx = 0; nIdx < nBtn; ++nIdx ) {
			if( m_Styles[nIdx] != GetButtonStyle(nIdx) )
				// invalidate that button
				InvalidateButton(nIdx);
		}
	} else
		// simply delegate
		CToolBar::OnUpdateCmdUI(pTarget,bDisableIfNoHndler);
}

void CToolBarEx::OnSysColorChange() 
{
	CToolBar::OnSysColorChange();

	m_clrBtnFace    = ::GetSysColor(COLOR_BTNFACE);
	m_clrBtnHilight = ::GetSysColor(COLOR_BTNHILIGHT);
	m_clrBtnShadow  = ::GetSysColor(COLOR_BTNSHADOW);
	m_clrBtnLight   = ::GetSysColor(COLOR_3DLIGHT);
}


void CToolBarEx::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp) 
{
	CToolBar::OnNcCalcSize(bCalcValidRects, lpncsp);
	if( m_bFlatLook ) {
		// adjust non-client area for gripper at left or top
		if( m_dwStyle & CBRS_ORIENT_HORZ ) {
			lpncsp->rgrc[0].left += 4;
			lpncsp->rgrc[0].right += 4;
		} else {
			lpncsp->rgrc[0].top += 6;
			lpncsp->rgrc[0].bottom += 6;
		}
	}
}


void CToolBarEx::OnMouseMove(UINT nFlags, CPoint point) 
{
	if( m_bDragging ) {
		DragMove();
		return;
	}

	if( m_bFlatLook && IsTopParentActive() && GetTopLevelParent()->IsWindowEnabled()) {
		register const int nBtn = GetToolBarCtrl().GetButtonCount();
		const int nLastBtn = m_nLastBtn;
		m_nLastBtn = -1;

		for( register int i = 0 ; i < nBtn ; ++i ) {
			CRect rc;
			GetItemRect(i, rc);

			const BOOL bBtnEnabled = GetToolBarCtrl().IsButtonEnabled(int(GetItemID(i)));
			const BOOL bSep = GetButtonStyle(i) & TBBS_SEPARATOR;

			if( bSep || ! bBtnEnabled )
				continue;

			const BOOL bHasCursor = rc.PtInRect(point);

			if( bHasCursor && bBtnEnabled ) {
				if( nLastBtn != i ) {
					// force a repaint of the button with the cursor on it
					InvalidateRect(rc, FALSE);
				}
				m_nLastBtn = i;
			} else if( !bHasCursor && i == nLastBtn ) {
				// force a repaint of the last formed button
				InvalidateRect(rc, FALSE);
			}
		}
		// One problem occures with WM_MOUSEMOVE: we cannot detect
		// that the mouse leaves the window. If the mouse moves quick
		// enough, then the last formed button stays visible. To
		// resolve this problem, we set a timer and check, whether
		// the mouse is outside the window ...
		KillTimer(m_uTimerEvent);
		m_uTimerEvent = SetTimer(1, 250, 0);
	}
	CToolBar::OnMouseMove(nFlags, point);
}


void CToolBarEx::OnNcPaint() 
{
	if( m_bFlatLook ) {
		// get window DC that is clipped to the non-client area
		CWindowDC dc(this);
		CRect rectClient;
		GetClientRect(rectClient);
		CRect rectWindow;
		GetWindowRect(rectWindow);
		ScreenToClient(rectWindow);
		rectClient.OffsetRect(-rectWindow.left, -rectWindow.top);
		dc.ExcludeClipRect(rectClient);

		// draw borders in non-client area
		rectWindow.OffsetRect(-rectWindow.left, -rectWindow.top);

		Draw3DBorders(&dc, rectWindow);

		dc.IntersectClipRect(rectWindow);

		#ifdef _MEMDC_H_
			// You're using Keith Rule's CMemDC. In this case
			// we have to make sure that WM_ERASEBKGND
			// will not be sent.
			dc.FillSolidRect(rectWindow, m_clrBtnFace);
		#else
			// erase parts not drawn
			SendMessage(WM_ERASEBKGND, (WPARAM)dc.m_hDC);
		#endif

		DrawGripper(dc);
	} else
		CToolBar::OnNcPaint();
}


void CToolBarEx :: Draw3DBorders(CDC * pDC, CRect & rect) {
	ASSERT_VALID(this);
	ASSERT_VALID(pDC);

	if( m_bReal3DBorder ) {
		DWORD dwStyle = m_dwStyle;
		if (!(dwStyle & CBRS_BORDER_ANY))
			return;

		COLORREF clr = (m_dwStyle & CBRS_BORDER_3D) ? m_clrBtnHilight : m_clrBtnShadow;
		if(m_dwStyle & CBRS_BORDER_LEFT)
			pDC->FillSolidRect(0, 0, 1, rect.Height() - 1, clr);
		if(m_dwStyle & CBRS_BORDER_TOP)
			pDC->FillSolidRect(0, 0, rect.Width()-1 , 1, clr);
		if(m_dwStyle & CBRS_BORDER_RIGHT)
			pDC->FillSolidRect(rect.right, 1, -1, rect.Height() - 1, m_clrBtnShadow);
		if(m_dwStyle & CBRS_BORDER_BOTTOM)
			pDC->FillSolidRect(0, rect.bottom, rect.Width()-1, -1, m_clrBtnShadow);

		// if undockable toolbar at top of frame, apply special formatting to mesh
		// properly with frame menu
		if(!m_pDockContext) {
			pDC->FillSolidRect(0,0,rect.Width(),1,m_clrBtnShadow);
			pDC->FillSolidRect(0,1,rect.Width(),1,m_clrBtnHilight);
		}

		if (dwStyle & CBRS_BORDER_LEFT)
			++rect.left;
		if (dwStyle & CBRS_BORDER_TOP)
			++rect.top;
		if (dwStyle & CBRS_BORDER_RIGHT)
			--rect.right;
		if (dwStyle & CBRS_BORDER_BOTTOM)
			--rect.bottom;
	} else
		DrawBorders(pDC, rect);
}

void CToolBarEx::OnTimer(UINT nIDEvent) 
{
	if( nIDEvent == m_uTimerEvent ) {
		if( m_nLastBtn >= 0 ) {
			POINT pt;
			::GetCursorPos(&pt);
			CRect rc;
			GetWindowRect(rc);
			if( ! rc.PtInRect(pt) ) {
				InvalidateButton(m_nLastBtn);
				m_nLastBtn = -1;
			}
		}
		if( m_nLastBtn < 0 )
			KillTimer(nIDEvent);
	} else
		CToolBar::OnTimer(nIDEvent);
}

int CToolBarEx::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (CToolBar::OnCreate(lpCreateStruct) == -1)
		return -1;
	
	// Save the parent at creation time. It may change, if
	// the toolbar is floating; but we want to know of the
	// "real" parent (for notification messages)!
	m_hwndParent = lpCreateStruct->hwndParent;

	return 0;
}

void CToolBarEx::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
	CToolBar::OnWindowPosChanging(lpwndpos);
	
	// If moved just force a redraw. This stops the partial redraw bug.
	if( !(lpwndpos->flags & SWP_NOMOVE) )
		Invalidate(FALSE);
	
}

#define PADWIDTH(x)	(((x)*8+31)&~31)/8


HIMAGELIST CToolBarEx :: GetImageList() {
	m_bDeleteImgList = FALSE;

	HIMAGELIST hImg = 0;

#ifdef TB_GETIMAGELIST
	// Some older versions of VC++ do not know of
	// the TB_GETIMAGELIST macro (defined in commctrl.h).

	hImg = HIMAGELIST(SendMessage(TB_GETIMAGELIST));

#ifdef _DEBUG
	if( hImg == 0 ) {
		TRACE0("CToolBarEx::GetImageList(): could not get image list\n");
	}
#endif
#endif // TB_GETIMAGELIST

	if( ! hImg ) {
		// comctl32.dll version prior to 4.70 doesn't know
		// anything of the TB_GETIMAGELIST message
		if( m_hbmImageWell != 0 ) {
			// Yep - we have a valid image.
			// But beware: Do not use this bitmap directly.
			// We make the copy by ourself. CopyImage() (for
			// instace) produces inacceptable copies under
			// some circumstances ...
			CImageList imglist;
			CBitmap bmp;
			
			// retrieve the size of the bitmap
			BITMAP bmHdr;
			::GetObject(m_hbmImageWell, sizeof(BITMAP), &bmHdr);
			
			DWORD dwWidth, dwHeight = bmHdr.bmHeight;
			
			if (bmHdr.bmBitsPixel > 8)
				dwWidth = PADWIDTH(bmHdr.bmWidth * 3);
			else
				dwWidth = PADWIDTH(bmHdr.bmWidth);
			
			// copy the bitmap
			CClientDC cdc(this);
			CDC dc1, dc2;
			dc1.CreateCompatibleDC(&cdc);
			dc2.CreateCompatibleDC(&cdc);
			bmp.CreateCompatibleBitmap(&cdc, dwWidth, dwHeight);
			CBitmap * pOBmp = dc1.SelectObject(&bmp);
			HGDIOBJ hOObj = ::SelectObject(dc2.GetSafeHdc(), m_hbmImageWell);
			dc1.BitBlt(0,0,dwWidth,dwHeight,&dc2,0,0,SRCCOPY);
			::SelectObject(dc2.GetSafeHdc(), hOObj);
			dc1.SelectObject(pOBmp);
			dc1.DeleteDC();
			dc2.DeleteDC();

			imglist.Create(m_sizeImage.cx, m_sizeImage.cy,TRUE,dwWidth/m_sizeImage.cx,1);
			imglist.SetBkColor(m_clrBtnFace);
			imglist.Add(&bmp,m_clrBtnFace);
			hImg = imglist.Detach();
			bmp.DeleteObject();
			m_bDeleteImgList = TRUE;
		}
	}

	return hImg;
}

void CToolBarEx::InvalidateButton(int nIndex) {
	if( nIndex < 0 || nIndex >= GetToolBarCtrl().GetButtonCount() )
		return;
	CRect rc;
	GetItemRect(nIndex, rc);
	InvalidateRect(rc, FALSE);
}

BOOL CToolBarEx :: IsSeparator(int nIndex) const {
	if( nIndex >= 0 && nIndex < GetToolBarCtrl().GetButtonCount() )
		if( GetButtonStyle(nIndex) == TBBS_SEPARATOR ) {
			// make sure this is a "real" separator
			CRect rc;
			GetItemRect(nIndex, rc);
			if( rc.Width() <= 8 )
				return TRUE;
		}

	return FALSE;
}

BOOL CToolBarEx :: IsControl(int nIndex) const {
	if( nIndex >= 0 && nIndex < GetToolBarCtrl().GetButtonCount() )
		if( GetButtonStyle(nIndex) == TBBS_SEPARATOR ) {
			// make sure this is a placeholder for a control
			CRect rc;
			GetItemRect(nIndex, rc);
			if( rc.Width() > 8 )
				return TRUE;
		}

	return FALSE;
}


CWnd * CToolBarEx :: GetControl(int idx, BOOL bIdxIsID) const {
	UINT uID = bIdxIsID ? UINT(idx) : GetItemID(idx);
	for( CWnd * pWnd = GetWindow(GW_CHILD); pWnd; pWnd = pWnd->GetNextWindow() )
		if( UINT(::GetWindowLong(pWnd->GetSafeHwnd(), GWL_ID)) == uID )
			return pWnd;
	return 0;
}


CWnd * CToolBarEx :: CtrlReplace(
						CRuntimeClass * pClass,
						CRect & rc,
						UINT ID,
						DWORD dwStyle ) {
	int nIndex = CommandToIndex(ID);
	if(nIndex < 0) {
		TRACE1("CToolBarEx::CtrlReplace(): 0x%x is not a valid ID for this toolbar.\n,", ID);
		return 0;
	}

	int nWidth = rc.Width();
	if( nWidth < 0 )
		nWidth = -nWidth;
	SetButtonInfo(nIndex, ID, TBBS_SEPARATOR, nWidth);


	CWnd * pCtrl = CreateControl(pClass, rc, ID, dwStyle);

	return pCtrl;
}

CWnd * CToolBarEx :: CtrlInsert(
						CRuntimeClass * pClass,
						CRect & rc,
						UINT ID,
						int nBefore,
						DWORD dwStyle) {
	BOOL bAppend = FALSE;
	CToolBarCtrl & wndToolBarCtrl = GetToolBarCtrl();
	if( nBefore < 0 || nBefore > wndToolBarCtrl.GetButtonCount() )
		bAppend = TRUE;

	int nWidth = rc.Width();
	if( nWidth < 0 )
		nWidth = -nWidth;

	TBBUTTON tbButton;
	tbButton.iBitmap = nWidth;
	tbButton.idCommand = ID;
	tbButton.fsState = TBSTATE_ENABLED;
	tbButton.fsStyle = TBSTYLE_SEP;
	tbButton.dwData = 0;
	tbButton.iString = 0;
	if( bAppend )
		wndToolBarCtrl.AddButtons(1, &tbButton);
	else
		wndToolBarCtrl.InsertButton(nBefore, &tbButton);

	CWnd * pCtrl = CreateControl(pClass, rc, ID, dwStyle);

	return pCtrl;
}

CWnd * CToolBarEx :: CreateControl(
						CRuntimeClass * pClass,
						CRect & rc,
						UINT ID,
						DWORD dwStyle) {
	if( ! pClass || ! pClass->IsDerivedFrom(RUNTIME_CLASS(CWnd)) ) {
		TRACE0("CToolBarEx::CreateControl(): given class is NULL or not derived from CWnd.\n");
		return 0;
	}

	CWnd * pCtrl = 0;

	BOOL bSelfDeleting = TRUE;

	CString strClass = pClass->m_lpszClassName;

	if( strClass == TEXT("CComboBox") ) {
		pCtrl = new CComboBox();
		bSelfDeleting = FALSE;
	} else if( strClass == TEXT("CEdit") ) {
		pCtrl = new CEdit();
		bSelfDeleting = FALSE;
	} else {
		pCtrl = (CWnd *)pClass->CreateObject();
		if( pCtrl == 0 ) {
			TRACE1("CToolBarEx::CreateControl(): dynamic create of control %hs failed.\n",
				pClass->m_lpszClassName);
			return 0;
		}
	}

	// create the control itself
	CRect rect = rc;
	BOOL bCreate = FALSE;

	if( pCtrl->IsKindOf(RUNTIME_CLASS(CComboBox)) )
		bCreate = ((CComboBox*)pCtrl)->Create(WS_CHILD|WS_VISIBLE|dwStyle, rect, this, ID);
	else if( pCtrl->IsKindOf(RUNTIME_CLASS(CEdit)) )
		bCreate = ((CEdit*)pCtrl)->Create(WS_CHILD|WS_VISIBLE|dwStyle, rect, this, ID);
	else
		bCreate = pCtrl->Create(0, 0, WS_CHILD|WS_VISIBLE|dwStyle, rect, this, ID);
	
	if( ! bCreate ) {
		TRACE1("CToolBarEx::CreateControl(): could not Create() control.\n,", ID);
		if( ! bSelfDeleting )
			delete pCtrl;
		return 0;
	}

	if( ! bSelfDeleting ) {
		// we have to remember this control, so we can delete it later
		if( ! m_pControls )
			m_pControls = new CObList();
		m_pControls->AddTail(pCtrl);
	}

	RepositionControls();

	return pCtrl;
}

void CToolBarEx :: RepositionControls() {
	// Walk all descendents.
	for( CWnd * pWnd = GetWindow(GW_CHILD); pWnd; pWnd = pWnd->GetNextWindow() ) {
		// If that window is a child of this
		// toolbar, then reposition this control.
		if( pWnd->GetParent() == this ) {
			DWORD dwID = ::GetWindowLong(pWnd->GetSafeHwnd(), GWL_ID);
			int idx = CommandToIndex(dwID);
			ASSERT(idx >= 0);
			CRect rc;
			GetItemRect(idx, rc);
			pWnd->SetWindowPos(0, rc.left, rc.top, 0, 0, SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOCOPYBITS);
			pWnd->ShowWindow(SW_SHOW);
		}
	}
}

void CToolBarEx :: RecalcLayout() {
	// Recalculate the size of the bar.
	BOOL bHorz = (m_dwStyle & CBRS_ORIENT_HORZ) != 0;
	if ((m_dwStyle & CBRS_FLOATING) && (m_dwStyle & CBRS_SIZE_DYNAMIC))
		CalcDynamicLayout(0, LM_HORZ | LM_MRUWIDTH | LM_COMMIT);
	else if (bHorz)
		CalcDynamicLayout(0, LM_HORZ | LM_HORZDOCK | LM_COMMIT);
	else
		CalcDynamicLayout(0, LM_VERTDOCK | LM_COMMIT);

	RepositionControls();

	// recalculate the parent frame
	if( m_dwStyle & CBRS_FLOATING ) {
		ASSERT(m_pDockBar != 0);
		((CMiniDockFrameWnd *)m_pDockBar->GetParent())->RecalcLayout();
	} else {
		((CFrameWnd*)GetParentFrame())->RecalcLayout();
	}
}

HBITMAP CToolBarEx :: GetBitmap(int nBtnID) {
	int nBitmap = SendMessage(TB_GETBITMAP, WPARAM(nBtnID));
	return GetBitmap(nBitmap, m_sizeImage);
}

HBITMAP CToolBarEx :: GetBitmap(int nIndex, const CSize & sizeBitmap) {
	HIMAGELIST hImgList = GetImageList();
	if( ! hImgList )
		return 0;

	CImageList imglist;
	imglist.Attach(hImgList);

	HICON hIcon = imglist.ExtractIcon(nIndex);
	CBitmap bmp;

	if( hIcon ) {
		CClientDC cdc(this) ;
		CDC dc;
		dc.CreateCompatibleDC(&cdc);
		VERIFY(bmp.CreateCompatibleBitmap(&cdc, sizeBitmap.cx, sizeBitmap.cy));
		CBitmap* pOldBmp = dc.SelectObject(&bmp);
		CBrush brush ;
		VERIFY(brush.CreateSolidBrush(m_clrBtnFace));
		::DrawIconEx(
			dc.GetSafeHdc(),
			0,
			0,
			hIcon,
			sizeBitmap.cx,
			sizeBitmap.cy,
			0,
			(HBRUSH)brush,
			DI_NORMAL
		);
		dc.SelectObject( pOldBmp );
		dc.DeleteDC();

		// the icon is not longer needed
		DestroyIcon(hIcon);
	} else
		TRACE1("CToolBarEx::GetBitmap(): unable to extract bitmap with index %d\n", nIndex);

	if( ! m_bDeleteImgList )
		imglist.Detach();

	return hIcon ? HBITMAP(bmp.Detach()) : 0;
}


LRESULT CToolBarEx::OnAddBitmap(WPARAM wParam, LPARAM lParam) {
	// work around a bug in CToolBar:
	// if one calls CToolBar::GetToolBarCtrl().AddBitmap(...),
	// then CToolBar does not realize this. This can lead to
	// confusing button images ...

	int nButtons = int(wParam);
	LPTBADDBITMAP pAddBmp = LPTBADDBITMAP(lParam);

	if( pAddBmp->hInst != HINST_COMMCTRL ) {
		// This workaround does not work, if one
		// specifies "HINST_COMMCTRL" to the "hInst"
		// member of pAddBmp, because we cannot access
		// the internals of commctl32.dll ...

		TRACE0("Adding a bitmap\n");

		HBITMAP hBitmap;
		if( pAddBmp->hInst != 0 )
			// Have to load the bitmap "pAddBmp->nID"
			// contains the resource-ID of the bitmap.
			hBitmap = ::LoadBitmap(pAddBmp->hInst, MAKEINTRESOURCE(pAddBmp->nID));
		else
			// "pAddBmp->nID" is the handle of the bitmap.
			hBitmap = HBITMAP(pAddBmp->nID);

		if(hBitmap == 0)
			return HRESULT(-1);

		// You really should use CToolBarEx as a replacement for
		// CToolBar. So make sure you have set up a toolbar
		// properly before you begin to customize it.
		ASSERT(m_hbmImageWell);

		// retrieve number of images currently stored in CToolBar
		BITMAP bitmap;
		VERIFY(::GetObject(m_hbmImageWell, sizeof(BITMAP), &bitmap));
		int nImageCount = bitmap.bmWidth / m_sizeImage.cx;

		CClientDC cdc(this);
		CDC dcOld, dcNew;
		dcOld.CreateCompatibleDC(&cdc);
		dcNew.CreateCompatibleDC(&cdc);

		HGDIOBJ hOldBmp = ::SelectObject(dcOld.GetSafeHdc(), m_hbmImageWell);

		// create the new bitmap and make it big enough to
		// hold all images (old+new)
		CBitmap bmpNew;
		bmpNew.CreateCompatibleBitmap(
			&cdc,
			bitmap.bmWidth + nButtons * m_sizeImage.cx,
			m_sizeImage.cy);
		CBitmap * pbmpNew = dcNew.SelectObject(&bmpNew);

		dcNew.BitBlt(0,0,bitmap.bmWidth,bitmap.bmHeight,&dcOld,0,0,SRCCOPY);

		::SelectObject(dcOld.GetSafeHdc(), hBitmap);
		dcNew.BitBlt(bitmap.bmWidth,0,m_sizeImage.cx*nButtons,bitmap.bmHeight,&dcOld,0,0,SRCCOPY);

		::SelectObject(dcOld.GetSafeHdc(), hOldBmp);
		dcNew.SelectObject(pbmpNew);
		dcOld.DeleteDC();
		dcNew.DeleteDC();

		VERIFY(bmpNew.GetObject(sizeof(BITMAP), &bitmap));

		HRESULT hRes = DefWindowProc(TB_ADDBITMAP, wParam, lParam);

		// syncronize toolbarcontrol's bitmap with our's
		AddReplaceBitmap(HBITMAP(bmpNew.Detach()));

		return HRESULT(nImageCount);
	}

	return DefWindowProc(TB_ADDBITMAP, wParam, lParam);
}


// intercept TB_DELETEBUTTON, so we can delete controls too.
LRESULT CToolBarEx::OnDeleteButton(WPARAM wParam, LPARAM lParam) {
	CWnd * pControl = GetControl(int(wParam));
	if( pControl ) {
		// this is the control associated with the button to delete
		BOOL bMustDelete = FALSE;
		if( m_pControls ) {
			// It is really a good idea to add a control via the
			// CToolBarEx own members. This will guarantee that
			// all resources are freed.
			POSITION pos = m_pControls->Find(pControl);
			if( pos ) {
				m_pControls->RemoveAt(pos);
				bMustDelete = TRUE;
			}
		}
		pControl->DestroyWindow();
		if( bMustDelete )
			delete pControl;
	}

	return DefWindowProc(TB_DELETEBUTTON, wParam, lParam);
}


#ifdef _MEMDC_H_
BOOL CToolBarEx::OnEraseBkgnd(CDC* pDC) 
{
	return IsFlatLook() ? FALSE : CToolBar::OnEraseBkgnd(pDC);
}
#endif

/////////////////////////////////////////////////////////////////////////////
// ALT-drag

// To keep the users of CToolBarEx from inserting cursor-resources, we
// create the cursors on the fly. This makes usage of CToolBarEx as easy as
// possible:

static const BYTE ANDmaskDrop[] = { 
    0xFF, 0xFF, 0xFF, 0xFF,   // line 1
    0xFF, 0xFF, 0xFF, 0xFF,   // line 2
    0xF3, 0xFF, 0xFF, 0xFF,   // line 3
    0xF1, 0xFF, 0xFF, 0xFF,   // line 4
 
    0xF0, 0xFF, 0xFF, 0xFF,   // line 5
    0xF0, 0x7F, 0xFF, 0xFF,   // line 6
    0xF0, 0x3F, 0xFF, 0xFF,   // line 7
    0xF0, 0x1F, 0xFF, 0xFF,   // line 8
 
    0xF0, 0x0F, 0xFF, 0xFF,   // line 9
    0xF0, 0x07, 0xFF, 0xFF,   // line 10
    0xF0, 0x03, 0xFF, 0xFF,   // line 11
    0xF0, 0x01, 0xFF, 0xFF,   // line 12
 
    0xF0, 0x00, 0xFF, 0xFF,   // line 13
    0xF0, 0x0F, 0xFF, 0xFF,   // line 14
    0xF0, 0x0F, 0xFF, 0xFF,   // line 15
    0xF1, 0x07, 0xFF, 0xFF,   // line 16
 
    0xF3, 0x07, 0xFF, 0xFF,   // line 17
    0xF6, 0x00, 0x00, 0x3F,   // line 18
    0xFE, 0x00, 0x00, 0x3F,   // line 19
    0xFE, 0x00, 0x00, 0x3F,   // line 20
 
    0xFE, 0x00, 0x00, 0x3F,   // line 21
    0xFE, 0x00, 0x00, 0x3F,   // line 22
    0xFE, 0x00, 0x00, 0x3F,   // line 23
    0xFE, 0x00, 0x00, 0x3F,   // line 24
 
    0xFE, 0x00, 0x00, 0x3F,   // line 25
    0xFE, 0x00, 0x00, 0x3F,   // line 26
    0xFF, 0xFF, 0xFF, 0xFF,   // line 27
    0xFF, 0xFF, 0xFF, 0xFF,   // line 28
 
    0xFF, 0xFF, 0xFF, 0xFF,   // line 29
    0xFF, 0xFF, 0xFF, 0xFF,   // line 30
    0xFF, 0xFF, 0xFF, 0xFF,   // line 31
    0xFF, 0xFF, 0xFF, 0xFF    // line 32
};
 
static const BYTE XORmaskDrop[] = { 
    0x00, 0x00, 0x00, 0x00,   // line 1
    0x00, 0x00, 0x00, 0x00,   // line 2
    0x00, 0x00, 0x00, 0x00,   // line 3
    0x04, 0x00, 0x00, 0x00,   // line 4
 
    0x06, 0x00, 0x00, 0x00,   // line 5
    0x07, 0x00, 0x00, 0x00,   // line 6
    0x07, 0x80, 0x00, 0x00,   // line 7
    0x07, 0xC0, 0x00, 0x00,   // line 8
 
    0x07, 0xE0, 0x00, 0x00,   // line 9
    0x07, 0xF0, 0x00, 0x00,   // line 10
    0x07, 0xF8, 0x00, 0x00,   // line 11
    0x07, 0xFC, 0x00, 0x00,   // line 12
 
    0x07, 0xE0, 0x00, 0x00,   // line 13
    0x07, 0x60, 0x00, 0x00,   // line 14
    0x06, 0x60, 0x00, 0x00,   // line 15
    0x04, 0x30, 0x00, 0x00,   // line 16
 
    0x00, 0x30, 0x00, 0x00,   // line 17
    0x00, 0x18, 0x00, 0x00,   // line 18
    0x00, 0xDD, 0xFF, 0x00,   // line 19
    0x00, 0xAC, 0xAA, 0x00,   // line 20
 
    0x00, 0xCD, 0x55, 0x00,   // line 21
    0x00, 0xA0, 0xAA, 0x00,   // line 22
    0x00, 0xD5, 0x55, 0x00,   // line 23
    0x00, 0xAA, 0xAA, 0x00,   // line 24
 
    0x00, 0x00, 0x00, 0x00,   // line 25
    0x00, 0x00, 0x00, 0x00,   // line 26
    0x00, 0x00, 0x00, 0x00,   // line 27
    0x00, 0x00, 0x00, 0x00,   // line 28
 
    0x00, 0x00, 0x00, 0x00,   // line 29
    0x00, 0x00, 0x00, 0x00,   // line 30
    0x00, 0x00, 0x00, 0x00,   // line 31
    0x00, 0x00, 0x00, 0x00    // line 32
};

static const BYTE ANDmaskNoDrop[] = { 
    0xFF, 0xFF, 0xFF, 0xFF,   // line 1
    0xFF, 0xFF, 0xFF, 0xFF,   // line 2
    0xF3, 0xFF, 0xFF, 0xFF,   // line 3
    0xF1, 0xFF, 0xFF, 0xFF,   // line 4
 
    0xF0, 0xFF, 0xFF, 0xFF,   // line 5
    0xF0, 0x7F, 0xFF, 0xFF,   // line 6
    0xF0, 0x3F, 0xFF, 0xFF,   // line 7
    0xF0, 0x1F, 0xFF, 0xFF,   // line 8
 
    0xF0, 0x0F, 0xFF, 0xFF,   // line 9
    0xF0, 0x07, 0xFF, 0xFF,   // line 10
    0xF0, 0x03, 0xFF, 0xFF,   // line 11
    0xF0, 0x01, 0xFF, 0xFF,   // line 12
 
    0xF0, 0x00, 0xFF, 0xFF,   // line 13
    0xF0, 0x0F, 0xFF, 0xFF,   // line 14
    0xF0, 0x0F, 0xFF, 0xFF,   // line 15
    0xF1, 0x07, 0xFF, 0xFF,   // line 16
 
    0xF3, 0x07, 0xFF, 0xFF,   // line 17
    0xF6, 0x00, 0x00, 0x3F,   // line 18
    0xFE, 0x00, 0x00, 0x3F,   // line 19
    0xFE, 0x00, 0x00, 0x3F,   // line 20
 
    0xFE, 0x00, 0x00, 0x3F,   // line 21
    0xFE, 0x00, 0x00, 0x0F,   // line 22
    0xFE, 0x00, 0x00, 0x0F,   // line 23
    0xFE, 0x00, 0x00, 0x0F,   // line 24
 
    0xFE, 0x00, 0x00, 0x0F,   // line 25
    0xFE, 0x00, 0x00, 0x0F,   // line 26
    0xFF, 0xFF, 0x80, 0x0F,   // line 27
    0xFF, 0xFF, 0x80, 0x0F,   // line 28
 
    0xFF, 0xFF, 0x80, 0x0F,   // line 29
    0xFF, 0xFF, 0x80, 0x0F,   // line 30
    0xFF, 0xFF, 0x80, 0x0F,   // line 31
    0xFF, 0xFF, 0x80, 0x0F    // line 32
};
 
static const BYTE XORmaskNoDrop[] = {
    0x00, 0x00, 0x00, 0x00,   // line 1
    0x00, 0x00, 0x00, 0x00,   // line 2
    0x00, 0x00, 0x00, 0x00,   // line 3
    0x04, 0x00, 0x00, 0x00,   // line 4
 
    0x06, 0x00, 0x00, 0x00,   // line 5
    0x07, 0x00, 0x00, 0x00,   // line 6
    0x07, 0x80, 0x00, 0x00,   // line 7
    0x07, 0xC0, 0x00, 0x00,   // line 8
 
    0x07, 0xE0, 0x00, 0x00,   // line 9
    0x07, 0xF0, 0x00, 0x00,   // line 10
    0x07, 0xF8, 0x00, 0x00,   // line 11
    0x07, 0xFC, 0x00, 0x00,   // line 12
 
    0x07, 0xE0, 0x00, 0x00,   // line 13
    0x07, 0x60, 0x00, 0x00,   // line 14
    0x06, 0x60, 0x00, 0x00,   // line 15
    0x04, 0x30, 0x00, 0x00,   // line 16
 
    0x00, 0x30, 0x00, 0x00,   // line 17
    0x00, 0x18, 0x00, 0x00,   // line 18
    0x00, 0xDD, 0xFF, 0x00,   // line 19
    0x00, 0xAC, 0xAA, 0x00,   // line 20
 
    0x00, 0xCD, 0x55, 0x00,   // line 21
    0x00, 0xA0, 0x80, 0x00,   // line 22
    0x00, 0xD5, 0x3F, 0xE0,   // line 23
    0x00, 0xAA, 0xA7, 0x20,   // line 24
 
    0x00, 0x00, 0x22, 0x20,   // line 25
    0x00, 0x00, 0x30, 0x60,   // line 26
    0x00, 0x00, 0x38, 0xE0,   // line 27
    0x00, 0x00, 0x30, 0x60,   // line 28
 
    0x00, 0x00, 0x22, 0x20,   // line 29
    0x00, 0x00, 0x27, 0x20,   // line 30
    0x00, 0x00, 0x3F, 0xE0,   // line 31
    0x00, 0x00, 0x00, 0x00    // line 32
};

void CToolBarEx::OnLButtonDown(UINT nFlags, CPoint point) 
{
	if( ::GetAsyncKeyState(VK_MENU) & (1<<15) )
		// one of the ALT keys is pressed too - begin drag operation
		if( BeginDrag() )
			return;
	
	if( ::GetAsyncKeyState(VK_SHIFT) & (1<<15) )
		// disable the old-style drag
		return;
	
	CToolBar::OnLButtonDown(nFlags, point);
}

void CToolBarEx::OnLButtonUp(UINT nFlags, CPoint point) 
{
	if( m_bDragging )
		EndDrag();
	else
		CToolBar::OnLButtonUp(nFlags, point);
}

BOOL CToolBarEx :: BeginDrag() {
	TRACE0("beginning drag operation\n");

	VERIFY(!m_hDragCursor);
	VERIFY(!m_hNoDragCursor);

	if( !(::GetWindowLong(GetToolBarCtrl().GetSafeHwnd(),GWL_STYLE) & CCS_ADJUSTABLE) )
		return FALSE;	// Bar is not adjustable

	register const int nBtn = GetToolBarCtrl().GetButtonCount();
	const int nLastBtn = m_nLastBtn;
	m_nLastBtn = -1;
	CPoint pt;
	::GetCursorPos(&pt);
	ScreenToClient(&pt);

	m_nDragButton = -1;

	// have a look for whether the button is valid and
	// - if so - draw a dragging border around it.
	for( register int i = 0 ; i < nBtn ; ++i ) {
		if( IsSeparator(i) )
			continue;	// real separators are not draggable

		CRect rc;
		GetItemRect(i, rc);

		const BOOL bHasCursor = rc.PtInRect(pt);

		if( bHasCursor ) {
			// OK we've found the button. Now ask for deletion:
			if( ! DoQueryDelete(i) )
				// the app does not allow the removal ...
				return FALSE;

			m_nDragButton = i;
			CClientDC cdc(this);
			cdc.DrawDragRect(rc, CSize(2,2),0,CSize(0,0));
			break;
		}
	}

	if( m_nDragButton < 0 )
		return FALSE;		// nothing to drag ...

	VERIFY(m_hDragCursor = ::CreateCursor(0, 4, 2, 32, 32, ANDmaskDrop, XORmaskDrop));
	VERIFY(m_hNoDragCursor = ::CreateCursor(0, 4, 2, 32, 32, ANDmaskNoDrop, XORmaskNoDrop));

	// capture the mouse during the drag operation
	SetCapture();

	// make sure we receive keyboard-input
	SetFocus();

	m_hOrigCursor = ::SetCursor(m_hDragCursor);
	m_bDragCursor = TRUE;

	m_bDragging = TRUE;

	::GetCursorPos(&pt);
	SetMarker(m_pDropBar=this, m_ptDrop=pt);

	return m_bDragging;
}



BOOL CToolBarEx :: IsValidDropTarget(const CWnd * pWnd) const {
	return	(pWnd &&
			pWnd->IsKindOf(RUNTIME_CLASS(CToolBarEx)) &&
			(::GetWindowLong(((CToolBarEx*)pWnd)->GetToolBarCtrl()
						.GetSafeHwnd(),GWL_STYLE) & CCS_ADJUSTABLE) &&
			((const CToolBarEx*)pWnd)->GetParentFrame() == GetParentFrame())
			? TRUE : FALSE;
}


void CToolBarEx :: DragMove() {
	//TRACE0("dragmove in progress\n");

	CPoint pt;
	::GetCursorPos(&pt);
	const CWnd * pWnd = WindowFromPoint(pt);

	// is the cursor moving over an adjustable toolbar ?
	BOOL bToolBar =	IsValidDropTarget(pWnd);
	
	// If the window under the cursor is not a toolbar, then
	// check whether this window is a child of a toolbar.
	while( ! bToolBar && (pWnd = pWnd->GetParent()) != 0 )
		bToolBar =	IsValidDropTarget(pWnd);

	// check whether we have to switch the cursor
	if( bToolBar && ! m_bDragCursor ) {
		::SetCursor(m_hDragCursor);
		m_bDragCursor = TRUE;
	} else if( ! bToolBar && m_bDragCursor ) {
		::SetCursor(m_hNoDragCursor);
		m_bDragCursor = FALSE;
	}

	SetMarker(m_pDropBar = (bToolBar ? (CToolBarEx*)pWnd : 0), m_ptDrop = pt);
}

void CToolBarEx :: EndDrag(BOOL bDoMove) {
	TRACE0("ending drag operation\n");

	// remove the marker
	SetMarker(0, CPoint(0,0));

	VERIFY(::SetCursor(m_hOrigCursor));
	::DestroyCursor(m_hDragCursor);
	::DestroyCursor(m_hNoDragCursor);
	m_hDragCursor = 0;
	m_hNoDragCursor = 0;

	if( m_nDragButton >= 0 ) {
		CToolBarCtrl & wndTBCtrl = GetToolBarCtrl();
		register const int nBtn = wndTBCtrl.GetButtonCount();

		if( m_bDragCursor && bDoMove ) {
			// move the button to a different location

			// make sure the last "DragMove()" has done its work correctly:
			ASSERT(m_pDropBar != 0);
			ASSERT_KINDOF(CToolBarEx, m_pDropBar);
			ASSERT(::GetWindowLong(m_pDropBar->GetToolBarCtrl()
									.GetSafeHwnd(),GWL_STYLE) & CCS_ADJUSTABLE);

			// have a look for where to drop the button
			int nDropBtn = m_pDropBar->FindDropButton(m_ptDrop);
			TBBUTTON tbButton;
			memset(&tbButton, 0, sizeof(TBBUTTON)); // not the safest, but the easiest way
													// to zero out all members ;-)

			if( m_pDropBar == this ) {
				// move the button around, but stay on *this* toolbar
				if( nDropBtn == m_nDragButton+1 || (nDropBtn < 0 && m_nDragButton == nBtn-1) ) {
					// simply insert a separator before the dragged button,
					// if there is still none
					if( m_nDragButton > 0 && !IsSeparator(m_nDragButton-1) ) {
						tbButton.iBitmap = 8;
						tbButton.fsState = TBSTATE_ENABLED;
						tbButton.fsStyle = TBSTYLE_SEP;
						if( DoQueryInsert(tbButton, m_nDragButton) )
							wndTBCtrl.InsertButton(m_nDragButton, &tbButton);
					}
				} else if( nDropBtn == m_nDragButton && m_nDragButton > 0 ) {
					// Remove the separator immediately before the dragged button.
					// if there is no such separator, then do nothing
					if( IsSeparator(nDropBtn-1) )
						if( DoQueryDelete(nDropBtn-1) )
							wndTBCtrl.DeleteButton(nDropBtn-1);
				} else {
					wndTBCtrl.GetButton(m_nDragButton, &tbButton);
					if( DoQueryInsert(tbButton, (nDropBtn>=0) ? nDropBtn : nBtn) ) {
						CWnd * pControl = 0;
						if( IsControl(m_nDragButton) ) {
							// Beware: The TB_DELETEBUTTON message causes the toolbar
							// to destroy the associated control.
							// To avoid this we temporary set the parent of the
							// control to NULL.
							pControl = GetControl(m_nDragButton);
							VERIFY(pControl != 0);
							pControl->SetParent(0);
						}

						if( nDropBtn >= 0 )
							// have to insert
							wndTBCtrl.InsertButton(nDropBtn, &tbButton);
						else
							// append the button
							wndTBCtrl.AddButtons(1, &tbButton);

						// delete the button at its original location
						// we do not need to ask the owner, because this
						// was already done in "BeginDrag()"
						wndTBCtrl.DeleteButton(
							(m_nDragButton < nDropBtn || nDropBtn < 0)
								? m_nDragButton
								: m_nDragButton+1
						);

						// Reconnect the control (if any)
						if( pControl )
							pControl->SetParent(this);

						if( m_nDragButton == wndTBCtrl.GetButtonCount()-1 )
							// remove trailing separators too
							RemoveTrailingSeparators();
					}
				}
			} else {
				// move the button to a different toolbar

				wndTBCtrl.GetButton(m_nDragButton, &tbButton);
				CToolBarCtrl & wndDropTBCtrl = m_pDropBar->GetToolBarCtrl();

				if( m_pDropBar->DoQueryInsert(tbButton, (nDropBtn>=0) ? nDropBtn : wndDropTBCtrl.GetButtonCount()) ) {
					// Get the bitmap of the dragged button and resize it to
					// the image-size of the destination-bar.
					int nDestBitmap = 0;
					if( IsControl(m_nDragButton) ) {
						CRect rc;
						GetItemRect(m_nDragButton, rc);
						nDestBitmap = rc.Width();
					} else {
						HBITMAP hBmp = GetBitmap(tbButton.iBitmap, m_pDropBar->m_sizeImage);
						CBitmap bmp;
						if( hBmp ) {
							bmp.Attach(hBmp);
							nDestBitmap = wndDropTBCtrl.AddBitmap(1, &bmp);
						}
					}
					tbButton.iBitmap = nDestBitmap;
					//tbButton.iString = nDestString;
					tbButton.iString = -1;
					BOOL bInsertOK;
					if( nDropBtn >= 0 )
						bInsertOK = wndDropTBCtrl.InsertButton(nDropBtn, &tbButton);
					else
						bInsertOK = wndDropTBCtrl.AddButtons(1, &tbButton);

					if( bInsertOK ) {
						// transfer the string too, if any (check target first)
						if( m_pDropBar->HasButtonText() && HasButtonText() )
						{
							// let the CToolBar class do all the leg work
							m_pDropBar->SetButtonText(
								m_pDropBar->CommandToIndex(tbButton.idCommand),
								GetButtonText(m_nDragButton)
							);
						}
						// check whether the dragged button was a control in real life and
						// - if so - move that control to its new parent.
						CheckMoveControl(m_pDropBar, tbButton);

						wndTBCtrl.DeleteButton(m_nDragButton);

						if( m_nDragButton == wndTBCtrl.GetButtonCount()-1 )
							// remove trailing separators too
							RemoveTrailingSeparators();

						m_pDropBar->RecalcLayout();
					}
				}
			}
		} else {
			// remove the button from the toolbar
			if( bDoMove ) {
				wndTBCtrl.DeleteButton(m_nDragButton);

				if( m_nDragButton == wndTBCtrl.GetButtonCount() )
					// remove trailing separators too
					RemoveTrailingSeparators();
			} else
				// User has aborted the drag-operation.
				// Remove the drag-border from the button
				InvalidateButton(m_nDragButton);
		}

		// Recalculate the size of the bar.and the parent
		RecalcLayout();
	}

	m_bDragging = FALSE;

	// mouse capture is not longer needed
	ReleaseCapture();
}


void CToolBarEx :: CheckMoveControl( CToolBarEx * pToolBar, const TBBUTTON & tbButton ) {
	ASSERT_VALID(pToolBar);
	CWnd * pControl = GetControl(tbButton.idCommand, TRUE);
	if( pControl ) {
		// now change the parent of the control, so that it jumps to the
		// other toolbar
		pControl->SetParent(pToolBar);

		// remove the control from our list (if it's present there) and
		// add it to the target's list
		if( m_pControls ) {
			POSITION pos = m_pControls->Find(pControl);
			if(pos) {
				m_pControls->RemoveAt(pos);
				if( ! pToolBar->m_pControls )
					pToolBar->m_pControls = new CObList();
				pToolBar->m_pControls->AddTail(pControl);
			}
		}
	}
}


void CToolBarEx :: RemoveTrailingSeparators() {
	CToolBarCtrl & wndTBCtrl = GetToolBarCtrl();
	register const int nBtn = wndTBCtrl.GetButtonCount();
	register int i = nBtn;
	while( i && IsSeparator(--i) )
		if( DoQueryDelete(i) )
			wndTBCtrl.DeleteButton(i);
}

int CToolBarEx :: FindDropButton( const CPoint & point ) {
	CPoint pt = point;
	ScreenToClient(&pt);

	CRect rc;
	// find the button which is closest to the cursor
	register const int nBtn = GetToolBarCtrl().GetButtonCount();
	for( register int i = 0 ; i < nBtn ; ++i ) {
		GetItemRect(i, rc);
		if( rc.PtInRect(pt) )
			// insert the button to drop before this button:
			return (pt.x - rc.left < rc.right - pt.x)
					? i
					: ((i==nBtn-1)
						? -1
						: i+1);
	}
	
	// have to append the button
	return -1;
}

void CToolBarEx :: GetMarkerRect( int nButton, CRect & rc ) {
	register const int nBtn = GetToolBarCtrl().GetButtonCount();
	if( nButton < 0 || nButton > nBtn ) {
		// set the marker behind the last button
		GetItemRect(nBtn-1, rc);
		rc.right += 3;
		rc.left = rc.right-6;
	} else {
		// set the marker before the given button
		GetItemRect(nButton, rc);
		rc.left -= 3;
		rc.right = rc.left+6;
	}
	rc.DeflateRect(0,1);
}

void CToolBarEx :: ShowMarker( const CRect & rcMarker, CBitmap & bmpArea ) {
	ASSERT( bmpArea.GetSafeHandle() == 0 );

	CClientDC  WinDC(this);
	CDC MemDC; MemDC.CreateCompatibleDC(&WinDC);

	bmpArea.CreateCompatibleBitmap(&WinDC, rcMarker.Width(), rcMarker.Height());
	CBitmap * pOldBmp = MemDC.SelectObject(&bmpArea);

	CPen pen(PS_SOLID, 1, RGB(0,0,0));
	CPen * pOldPen = WinDC.SelectObject(&pen);

	// save original area:
	MemDC.BitBlt(0,0,rcMarker.Width(),rcMarker.Height(),
				&WinDC, rcMarker.left, rcMarker.top, SRCCOPY);

	WinDC.MoveTo(rcMarker.TopLeft());
	WinDC.LineTo(rcMarker.right, rcMarker.top);
	WinDC.MoveTo(rcMarker.left+1, rcMarker.top+1);
	WinDC.LineTo(rcMarker.right-1, rcMarker.top+1);

	WinDC.MoveTo(rcMarker.left+2, rcMarker.top+2);
	WinDC.LineTo(rcMarker.left+2, rcMarker.bottom-2);
	WinDC.MoveTo(rcMarker.left+3, rcMarker.top+2);
	WinDC.LineTo(rcMarker.left+3, rcMarker.bottom-2);

	WinDC.MoveTo(rcMarker.left, rcMarker.bottom-1);
	WinDC.LineTo(rcMarker.right, rcMarker.bottom-1);
	WinDC.MoveTo(rcMarker.left+1, rcMarker.bottom-2);
	WinDC.LineTo(rcMarker.right-1, rcMarker.bottom-2);

	MemDC.SelectObject(pOldBmp);
	MemDC.DeleteDC();
	WinDC.SelectObject(pOldPen);
}

void CToolBarEx :: RestoreMarker( const CRect & rcArea, CBitmap & bmpArea ) {
	if( bmpArea.GetSafeHandle() == 0 )
		return;

	CClientDC  WinDC(this);
	CDC MemDC; MemDC.CreateCompatibleDC(&WinDC);

	CBitmap * pOldBmp = MemDC.SelectObject(&bmpArea);
	WinDC.BitBlt(rcArea.left, rcArea.top, rcArea.Width(), rcArea.Height(),
				&MemDC, 0, 0, SRCCOPY);

	MemDC.SelectObject(pOldBmp);
	MemDC.DeleteDC();
}

void CToolBarEx :: SetMarker( CToolBarEx * pBar, const CPoint & point ) {
	static CToolBarEx * pLastToolBar = 0;
	static CRect lastRect(0,0,0,0);
	static CBitmap bmpLastSavedArea;

	CRect rcMarker;

	// retrieve proposed rectangle for the marker
	if( pBar != 0 ) {
		int nDropBtn = pBar->FindDropButton(point);
		pBar->GetMarkerRect(nDropBtn, rcMarker);

		if(rcMarker == lastRect)
			return;		// don't need to erase/draw
	}

	// restore the previously marked area:
	if( pLastToolBar ) {
		pLastToolBar->RestoreMarker(lastRect, bmpLastSavedArea);
		bmpLastSavedArea.DeleteObject();
	}

	// draw the marker
	if( pBar != 0 ) {
		pBar->ShowMarker(rcMarker, bmpLastSavedArea);
		lastRect = rcMarker;
	}
	pLastToolBar = pBar;
}

BOOL CToolBarEx :: DoQueryDelete(int nButton) {
	ASSERT(nButton >= 0);

	TBBUTTON tbButton;
	if( ! GetToolBarCtrl().GetButton(nButton, &tbButton) ) {
		TRACE1("CToolBarEx::DoQueryDelete(): could not retrieve button %d\n", nButton);
		return FALSE;
	}

	return QueryDeleteInsert(tbButton, TRUE, nButton);
}

BOOL CToolBarEx :: QueryDeleteInsert(TBBUTTON & tbButton, BOOL bDelete, int nIndex) {
	TBNOTIFY tbn;
	memset(&tbn, 0, sizeof(TBNOTIFY));
	tbn.hdr.hwndFrom = GetSafeHwnd();
	tbn.hdr.idFrom = UINT(::GetWindowLong(tbn.hdr.hwndFrom, GWL_ID));
	tbn.hdr.code = bDelete ? TBN_QUERYDELETE : TBN_QUERYINSERT;
	tbn.iItem = nIndex;
	memcpy((void *)(&tbn.tbButton), (const void *)(&tbButton), sizeof(TBBUTTON));

	CString strText;
	if( bDelete ) {
		strText = GetButtonText(nIndex);
		tbn.cchText = strText.GetLength();
		tbn.pszText = strText.GetBuffer(tbn.cchText);
	}

	ASSERT(GetParentFrame() != 0);
	ASSERT(::IsWindow(GetParentFrame()->GetSafeHwnd()));

	BOOL bRet = GetParentFrame()->SendMessage(
					WM_NOTIFY,
					WPARAM(tbn.hdr.idFrom),
					LPARAM(&tbn)
				);
	
	if( bDelete )
		strText.ReleaseBuffer();

	return bRet;
}

void CToolBarEx::OnCaptureChanged(CWnd *pWnd) 
{
	if( m_bDragging )
		// without the mouse-capture we cannot complete the drag-operation
		EndDrag(FALSE);

	CToolBar::OnCaptureChanged(pWnd);
}


BOOL CToolBarEx :: PreTranslateMessage( MSG * pMsg ) {
	if( m_bDragging &&
		(pMsg->message == WM_KEYDOWN || pMsg->message == WM_KEYUP) && 
		int(pMsg->wParam) == VK_ESCAPE ) {

		// user pressed ESC to abort drag operation
		EndDrag(FALSE);
		return TRUE;
	}
	return CToolBar::PreTranslateMessage(pMsg);
}

void CToolBarEx::OnParentNotify(UINT message, LPARAM lParam) 
{
	if( LOWORD(message) == WM_LBUTTONDOWN && (::GetAsyncKeyState(VK_MENU) & (1<<15)) ) {
		// I see no chance to abort the child's message processing.
		// That's why we set a flag here. If we lose the focus (a click
		// in a child's area will activate that child window), then
		// we start the real drag-operation (that would return the
		// focus to the toolbar).
		// This solution is somewhat obfuscated, so if you know of
		// a better way -- let me know.
		m_bDragChild = TRUE;
		SetFocus();
	}

	CToolBar::OnParentNotify(message, lParam);
}

void CToolBarEx::OnKillFocus(CWnd* pNewWnd) 
{
	CToolBar::OnKillFocus(pNewWnd);
	
	if( m_bDragChild ) {
		// See OnParentNotify() above ...
		m_bDragChild = FALSE;
		if( GetCapture() != this )
			BeginDrag();
	}
}


/////////////////////////////////////////////////////////////////////////////
// helpers for docking 
/////////////////////////////////////////////////////////////////////////////


// We need our own version of a dock bar, because the original
// MFC implementation overlapps toolbars. CToolBarEx don't want
// such a overlapping, because this makes it impossible to draw
// a real 3d border ...
class CToolDockBar : public CDockBar {
	DECLARE_DYNAMIC(CToolDockBar)

	public:
		// this is the one and only method of interest
		virtual CSize	CalcFixedLayout(BOOL bStretch, BOOL bHorz);
};

IMPLEMENT_DYNAMIC(CToolDockBar, CDockBar);

CSize CToolDockBar::CalcFixedLayout(BOOL bStretch, BOOL bHorz)
{
	ASSERT_VALID(this);

	CSize sizeFixed = CControlBar::CalcFixedLayout(bStretch, bHorz);

	// get max size
	CSize sizeMax;
	if (!m_rectLayout.IsRectEmpty())
		sizeMax = m_rectLayout.Size();
	else
	{
		CRect rectFrame;
		CFrameWnd* pFrame = GetParentFrame();
		pFrame->GetClientRect(&rectFrame);
		sizeMax = rectFrame.Size();
	}

	// prepare for layout
	AFX_SIZEPARENTPARAMS layout;
	layout.hDWP = m_bLayoutQuery ?
		NULL : ::BeginDeferWindowPos(m_arrBars.GetSize());
	int cxBorder = 2, cyBorder = 2;
	CPoint pt(-cxBorder, -cyBorder);
	int nWidth = 0;

	BOOL bWrapped = FALSE;

	// layout all the control bars
	for (int nPos = 0; nPos < m_arrBars.GetSize(); nPos++)
	{
		CControlBar* pBar = GetDockedControlBar(nPos);
		void* pVoid = m_arrBars[nPos];

		if (pBar != NULL)
		{
			if(pBar->IsKindOf(RUNTIME_CLASS(CToolBarEx)) && ((CToolBarEx*)pBar)->IsFlatLook())
				((CToolBarEx*)pBar)->m_bReal3DBorder = TRUE,
				cxBorder = cyBorder = 0;
			else if(pBar->IsKindOf(RUNTIME_CLASS(CToolBarEx)) && !((CToolBarEx*)pBar)->IsFlatLook())
				((CToolBarEx*)pBar)->m_bReal3DBorder = FALSE,
				cxBorder = cyBorder = 2;
			else
				cxBorder = cyBorder = 2;

			if (pBar->IsVisible())
			{
				// get ideal rect for bar
				DWORD dwMode = 0;
				if ((pBar->m_dwStyle & CBRS_SIZE_DYNAMIC) &&
					(pBar->m_dwStyle & CBRS_FLOATING))
					dwMode |= LM_HORZ | LM_MRUWIDTH;
				else if (pBar->m_dwStyle & CBRS_ORIENT_HORZ)
					dwMode |= LM_HORZ | LM_HORZDOCK;
				else
					dwMode |=  LM_VERTDOCK;

				CSize sizeBar = pBar->CalcDynamicLayout(-1, dwMode);

				CRect rect(pt, sizeBar);

				// get current rect for bar
				CRect rectBar;
				pBar->GetWindowRect(&rectBar);
				ScreenToClient(&rectBar);

				if (bHorz)
				{
					// Offset Calculated Rect out to Actual
					if (rectBar.left > rect.left && !m_bFloating)
						rect.OffsetRect(rectBar.left - rect.left, 0);

					// If ControlBar goes off the right, then right justify
					if (rect.right > sizeMax.cx && !m_bFloating)
					{
						int x = rect.Width() - cxBorder;
						x = max(sizeMax.cx - x, pt.x);
						rect.OffsetRect(x - rect.left, 0);
					}

					// If ControlBar has been wrapped, then left justify
					if (bWrapped)
					{
						bWrapped = FALSE;
						rect.OffsetRect(-(rect.left + cxBorder), 0);
					}
					// If ControlBar is completely invisible, then wrap it
					else if ((rect.left >= (sizeMax.cx - cxBorder)) &&
						(nPos > 0) && (m_arrBars[nPos - 1] != NULL))
					{
						m_arrBars.InsertAt(nPos, (CObject*)NULL);
						pBar = NULL; pVoid = NULL;
						bWrapped = TRUE;
					}
					if (!bWrapped)
					{
						if (rect != rectBar)
						{
							if (!m_bLayoutQuery &&
								!(pBar->m_dwStyle & CBRS_FLOATING))
							{
								pBar->m_pDockContext->m_rectMRUDockPos = rect;
							}
							AfxRepositionWindow(&layout, pBar->m_hWnd, &rect);
						}
						pt.x = rect.left + sizeBar.cx - cxBorder;
						nWidth = max(nWidth, sizeBar.cy);
					}
				}
				else
				{
					// Offset Calculated Rect out to Actual
					if (rectBar.top > rect.top && !m_bFloating)
						rect.OffsetRect(0, rectBar.top - rect.top);

					// If ControlBar goes off the bottom, then bottom justify
					if (rect.bottom > sizeMax.cy && !m_bFloating)
					{
						int y = rect.Height() - cyBorder;
						y = max(sizeMax.cy - y, pt.y);
						rect.OffsetRect(0, y - rect.top);
					}

					// If ControlBar has been wrapped, then top justify
					if (bWrapped)
					{
						bWrapped = FALSE;
						rect.OffsetRect(0, -(rect.top + cyBorder));
					}
					// If ControlBar is completely invisible, then wrap it
					else if ((rect.top >= (sizeMax.cy - cyBorder)) &&
						(nPos > 0) && (m_arrBars[nPos - 1] != NULL))
					{
						m_arrBars.InsertAt(nPos, (CObject*)NULL);
						pBar = NULL; pVoid = NULL;
						bWrapped = TRUE;
					}
					if (!bWrapped)
					{
						if (rect != rectBar)
						{
							if (!m_bLayoutQuery &&
								!(pBar->m_dwStyle & CBRS_FLOATING))
							{
								pBar->m_pDockContext->m_rectMRUDockPos = rect;
							}
							AfxRepositionWindow(&layout, pBar->m_hWnd, &rect);
						}
						pt.y = rect.top + sizeBar.cy - cyBorder;
						nWidth = max(nWidth, sizeBar.cx);
					}
				}
			}
			if (!bWrapped)
			{
				// handle any delay/show hide for the bar
				pBar->RecalcDelayShow(&layout);
			}
		}
		if (pBar == NULL && pVoid == NULL && nWidth != 0)
		{
			// end of row because pBar == NULL
			if (bHorz)
			{
				pt.y += nWidth - cyBorder;
				sizeFixed.cx = max(sizeFixed.cx, pt.x);
				sizeFixed.cy = max(sizeFixed.cy, pt.y);
				pt.x = -cxBorder;
			}
			else
			{
				pt.x += nWidth - cxBorder;
				sizeFixed.cx = max(sizeFixed.cx, pt.x);
				sizeFixed.cy = max(sizeFixed.cy, pt.y);
				pt.y = -cyBorder;
			}
			nWidth = 0;
		}
	}
	if (!m_bLayoutQuery)
	{
		// move and resize all the windows at once!
		if (layout.hDWP == NULL || !::EndDeferWindowPos(layout.hDWP))
			TRACE0("Warning: DeferWindowPos failed - low system resources.\n");
	}

	// adjust size for borders on the dock bar itself
	CRect rect;
	rect.SetRectEmpty();
	CalcInsideRect(rect, bHorz);

	if ((!bStretch || !bHorz) && sizeFixed.cx != 0)
		sizeFixed.cx += -rect.right + rect.left;
	if ((!bStretch || bHorz) && sizeFixed.cy != 0)
		sizeFixed.cy += -rect.bottom + rect.top;

	return sizeFixed;
}


// dwDockBarMap
const DWORD dwDockBarMap[4][2] =
{
	{ AFX_IDW_DOCKBAR_TOP,      CBRS_TOP    },
	{ AFX_IDW_DOCKBAR_BOTTOM,   CBRS_BOTTOM },
	{ AFX_IDW_DOCKBAR_LEFT,     CBRS_LEFT   },
	{ AFX_IDW_DOCKBAR_RIGHT,    CBRS_RIGHT  },
};


// Unfortunataly a simple rewrite of CFrameWnd's EnableDocking() is not possible,
// because we have not enough permissions to access some data in this class.
// That's why we call CFrameWnd::EnableDocking() first and exchange all occurencies
// of CDockBar objects with our own version of a dock bar.
void FrameEnableDocking(CFrameWnd * pFrame, DWORD dwDockStyle) {
	ASSERT_VALID(pFrame);

	// must be CBRS_ALIGN_XXX or CBRS_FLOAT_MULTI only
	ASSERT((dwDockStyle & ~(CBRS_ALIGN_ANY|CBRS_FLOAT_MULTI)) == 0);

	pFrame->EnableDocking(dwDockStyle);

	for (int i = 0; i < 4; i++) {
		if (dwDockBarMap[i][1] & dwDockStyle & CBRS_ALIGN_ANY) {
			CDockBar* pDock = (CDockBar*)pFrame->GetControlBar(dwDockBarMap[i][0]);
			
			// make sure the dock bar is of correct type
			if( pDock == 0 || ! pDock->IsKindOf(RUNTIME_CLASS(CToolDockBar)) ) {
				BOOL bNeedDelete = ! pDock->m_bAutoDelete;
				pDock->m_pDockSite->RemoveControlBar(pDock);
				pDock->m_pDockSite = 0;	// avoid problems in destroying the dockbar
				pDock->DestroyWindow();
				if( bNeedDelete )
					delete pDock;
				pDock = 0;
			}

			if( pDock == 0 ) {
				pDock = new CToolDockBar;
				if (!pDock->Create(pFrame,
					WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_CHILD|WS_VISIBLE |
						dwDockBarMap[i][1], dwDockBarMap[i][0])) {
					AfxThrowResourceException();
				}
			}
		}
	}
}

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
Web Developer
Canada Canada
I'm the senior software development manager for Rocscience Inc., a company specializing in geomechanics software. I have a PH.D in Civil Engineering, and have been programming since 1978. I've used more computers and languages then 2000 characters lets me list. Besides programming, I enjoy golfing, watching my 3 kids play sports, spending time with my wife, and watching plenty of hockey and football.

Comments and Discussions