/////////////////////////////////////////////////////////////////////////////
// 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(TRUE)	// 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();
				}
			}
		}
	}
}
