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