www.pudn.com > gui_Outlook.rar > flatbar.cpp
////////////////////////////////////////////////////////////////
// CFlatToolBar 1997 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
#include "StdAfx.h"
#include "ModulVer.h"
#include "FlatBar.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
////////////////////////////////////////////////////////////////
// CFlatToolBar--does flat tool bar in MFC.
//
IMPLEMENT_DYNAMIC(CFlatToolBar, CToolBar)
BEGIN_MESSAGE_MAP(CFlatToolBar, CToolBar)
//{{AFX_MSG_MAP(CFlatToolBar)
ON_WM_WINDOWPOSCHANGING()
ON_WM_WINDOWPOSCHANGED()
ON_WM_NCCREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
////////////////////////////////////////////////////////////////
// FixTB code from August 98 article:
////////////////////////////////////////////////////////////////
//////////////////
// ********PD** My stuff below
static int GetVerComCtl32()
{
DLLVERSIONINFO dvi;
return CModuleVersion::DllGetVersion(_T("comctl32.dll"), dvi) ?
(dvi.dwMajorVersion*100 + dvi.dwMinorVersion) : 0;
}
// class global
int CFlatToolBar::iVerComCtl32 = GetVerComCtl32();
CFlatToolBar::CFlatToolBar()
{
// **PD**
// default: show dropdown arrows for vertical toolbar
m_bShowDropdownArrowWhenVertical = TRUE;
}
CFlatToolBar::~CFlatToolBar()
{
}
//////////////////
// **PD**
// This is the all-important function that gets the true size of a button,
// instead of using m_sizeButton. And it's virtual, so you can override if
// my algorithm doesn't work, as will surely be the case in some circumstances.
//
CSize CFlatToolBar::GetButtonSize(TBBUTTON* pData, int iButton)
{
// Get the actual size of the button, not what's in m_sizeButton.
// Make sure to do SendMessage instead of calling MFC's GetItemRect,
// which has all sorts of bad side-effects! (Go ahead, take a look at it.)
//
CRect rc;
SendMessage(TB_GETITEMRECT, iButton, (LPARAM)&rc);
CSize sz = rc.Size();
////////////////
// Now must do special case for various versions of comctl32.dll,
//
DWORD dwStyle = pData[iButton].fsStyle;
if ((pData[iButton].fsState & TBSTATE_WRAP)) {
if (dwStyle & TBSTYLE_SEP) {
// this is the last separator in the row (eg vertically docked)
// fudge the height, and ignore the width. TB_GETITEMRECT will return
// size = (8 x 22) even for a separator in vertical toolbar
//
if (iVerComCtl32 <= 470)
sz.cy -= 3; // empircally good fudge factor
else if (iVerComCtl32 != 471)
sz.cy = sz.cx;
sz.cx = 0; // separator takes no width if it's the last one
} else if (dwStyle & TBSTYLE_DROPDOWN &&
!m_bShowDropdownArrowWhenVertical) {
// ignore width of dropdown
sz.cx = 0;
}
}
return sz;
}
////////////////////////////////////////////////////////////////
// ******* Stuff below is copied from MFC; my mods marked **PD**
#ifdef _MAC
#define CX_OVERLAP 1
#else
#define CX_OVERLAP 0
#endif
CSize CFlatToolBar::CalcSize(TBBUTTON* pData, int nCount)
{
ASSERT(pData != NULL && nCount > 0);
CPoint cur(0,0);
CSize sizeResult(0,0);
int cyTallestOnRow = 0;
for (int i = 0; i < nCount; i++)
{
if (pData[i].fsState & TBSTATE_HIDDEN)
continue;
// **PD** Load actual size of button into local var
// that obscures CToolBar::m_sizeButton.
CSize m_sizeButton = GetButtonSize(pData, i);
// **PD** I also changed the logic below to be more correct.
cyTallestOnRow = max(cyTallestOnRow, m_sizeButton.cy);
sizeResult.cx = max(cur.x + m_sizeButton.cx, sizeResult.cx);
sizeResult.cy = max(cur.y + m_sizeButton.cy, sizeResult.cy);
cur.x += m_sizeButton.cx - CX_OVERLAP;
if (pData[i].fsState & TBSTATE_WRAP)
{
cur.x = 0;
cur.y += cyTallestOnRow;
cyTallestOnRow = 0;
if (pData[i].fsStyle & TBSTYLE_SEP)
cur.y += m_sizeButton.cy;
}
}
return sizeResult;
}
int CFlatToolBar::WrapToolBar(TBBUTTON* pData, int nCount, int nWidth)
{
ASSERT(pData != NULL && nCount > 0);
int nResult = 0;
int x = 0;
for (int i = 0; i < nCount; i++)
{
pData[i].fsState &= ~TBSTATE_WRAP;
if (pData[i].fsState & TBSTATE_HIDDEN)
continue;
int dx, dxNext;
// **PD** Load actual size of button into local var
// that obscures CToolBar::m_sizeButton.
CSize m_sizeButton = GetButtonSize(pData, i);
dx = m_sizeButton.cx;
dxNext = dx - CX_OVERLAP;
if (x + dx > nWidth)
{
BOOL bFound = FALSE;
for (int j = i; j >= 0 && !(pData[j].fsState & TBSTATE_WRAP); j--)
{
// Find last separator that isn't hidden
// a separator that has a command ID is not
// a separator, but a custom control.
if ((pData[j].fsStyle & TBSTYLE_SEP) &&
(pData[j].idCommand == 0) &&
!(pData[j].fsState & TBSTATE_HIDDEN))
{
bFound = TRUE; i = j; x = 0;
pData[j].fsState |= TBSTATE_WRAP;
nResult++;
break;
}
}
if (!bFound)
{
for (int j = i - 1; j >= 0 && !(pData[j].fsState & TBSTATE_WRAP); j--)
{
// Never wrap anything that is hidden,
// or any custom controls
if ((pData[j].fsState & TBSTATE_HIDDEN) ||
((pData[j].fsStyle & TBSTYLE_SEP) &&
(pData[j].idCommand != 0)))
continue;
bFound = TRUE; i = j; x = 0;
pData[j].fsState |= TBSTATE_WRAP;
nResult++;
break;
}
if (!bFound)
x += dxNext;
}
}
else
x += dxNext;
}
return nResult + 1;
}
//////////////////////////////////////////////////////////////////////////
// **PD**
// Functions below are NOT modified. They're only here because they
// call the modified functions above, which are NOT virtual.
//////////////////////////////////////////////////////////////////////////
void CFlatToolBar::SizeToolBar(TBBUTTON* pData, int nCount, int nLength, BOOL bVert)
{
ASSERT(pData != NULL && nCount > 0);
if (!bVert)
{
int nMin, nMax, nTarget, nCurrent, nMid;
// Wrap ToolBar as specified
nMax = nLength;
nTarget = WrapToolBar(pData, nCount, nMax);
// Wrap ToolBar vertically
nMin = 0;
nCurrent = WrapToolBar(pData, nCount, nMin);
if (nCurrent != nTarget)
{
while (nMin < nMax)
{
nMid = (nMin + nMax) / 2;
nCurrent = WrapToolBar(pData, nCount, nMid);
if (nCurrent == nTarget)
nMax = nMid;
else
{
if (nMin == nMid)
{
WrapToolBar(pData, nCount, nMax);
break;
}
nMin = nMid;
}
}
}
CSize size = CalcSize(pData, nCount);
WrapToolBar(pData, nCount, size.cx);
}
else
{
CSize sizeMax, sizeMin, sizeMid;
// Wrap ToolBar vertically
WrapToolBar(pData, nCount, 0);
sizeMin = CalcSize(pData, nCount);
// Wrap ToolBar horizontally
WrapToolBar(pData, nCount, 32767);
sizeMax = CalcSize(pData, nCount);
while (sizeMin.cx < sizeMax.cx)
{
sizeMid.cx = (sizeMin.cx + sizeMax.cx) / 2;
WrapToolBar(pData, nCount, sizeMid.cx);
sizeMid = CalcSize(pData, nCount);
if (nLength < sizeMid.cy)
{
if (sizeMin == sizeMid)
{
WrapToolBar(pData, nCount, sizeMax.cx);
return;
}
sizeMin = sizeMid;
}
else if (nLength > sizeMid.cy)
sizeMax = sizeMid;
else
return;
}
}
}
struct _AFX_CONTROLPOS
{
int nIndex, nID;
CRect rectOldPos;
};
CSize CFlatToolBar::CalcLayout(DWORD dwMode, int nLength)
{
ASSERT_VALID(this);
ASSERT(::IsWindow(m_hWnd));
if (dwMode & LM_HORZDOCK)
ASSERT(dwMode & LM_HORZ);
int nCount;
TBBUTTON* pData;
CSize sizeResult(0,0);
// Load Buttons
{
nCount = SendMessage(TB_BUTTONCOUNT, 0, 0);
if (nCount != 0)
{
int i;
pData = new TBBUTTON[nCount];
for (i = 0; i < nCount; i++)
GetButton(i, &pData[i]); // **PD** renamed from _GetButton
}
}
if (nCount > 0)
{
if (!(m_dwStyle & CBRS_SIZE_FIXED))
{
BOOL bDynamic = m_dwStyle & CBRS_SIZE_DYNAMIC;
if (bDynamic && (dwMode & LM_MRUWIDTH))
SizeToolBar(pData, nCount, m_nMRUWidth);
else if (bDynamic && (dwMode & LM_HORZDOCK))
SizeToolBar(pData, nCount, 32767);
else if (bDynamic && (dwMode & LM_VERTDOCK))
SizeToolBar(pData, nCount, 0);
else if (bDynamic && (nLength != -1))
{
CRect rect; rect.SetRectEmpty();
CalcInsideRect(rect, (dwMode & LM_HORZ));
BOOL bVert = (dwMode & LM_LENGTHY);
int nLen = nLength + (bVert ? rect.Height() : rect.Width());
SizeToolBar(pData, nCount, nLen, bVert);
}
else if (bDynamic && (m_dwStyle & CBRS_FLOATING))
SizeToolBar(pData, nCount, m_nMRUWidth);
else
SizeToolBar(pData, nCount, (dwMode & LM_HORZ) ? 32767 : 0);
}
sizeResult = CalcSize(pData, nCount);
if (dwMode & LM_COMMIT)
{
_AFX_CONTROLPOS* pControl = NULL;
int nControlCount = 0;
BOOL bIsDelayed = m_bDelayedButtonLayout;
m_bDelayedButtonLayout = FALSE;
for(int i = 0; i < nCount; i++)
if ((pData[i].fsStyle & TBSTYLE_SEP) && (pData[i].idCommand != 0))
nControlCount++;
if (nControlCount > 0)
{
pControl = new _AFX_CONTROLPOS[nControlCount];
nControlCount = 0;
for(int i = 0; i < nCount; i++)
{
if ((pData[i].fsStyle & TBSTYLE_SEP) && (pData[i].idCommand != 0))
{
pControl[nControlCount].nIndex = i;
pControl[nControlCount].nID = pData[i].idCommand;
CRect rect;
GetItemRect(i, &rect);
ClientToScreen(&rect);
pControl[nControlCount].rectOldPos = rect;
nControlCount++;
}
}
}
if ((m_dwStyle & CBRS_FLOATING) && (m_dwStyle & CBRS_SIZE_DYNAMIC))
m_nMRUWidth = sizeResult.cx;
for (i = 0; i < nCount; i++)
SetButton(i, &pData[i]); // **PD** renamed from _SetButton
if (nControlCount > 0)
{
for (int i = 0; i < nControlCount; i++)
{
CWnd* pWnd = GetDlgItem(pControl[i].nID);
if (pWnd != NULL)
{
CRect rect;
pWnd->GetWindowRect(&rect);
CPoint pt = rect.TopLeft() - pControl[i].rectOldPos.TopLeft();
GetItemRect(pControl[i].nIndex, &rect);
pt = rect.TopLeft() + pt;
pWnd->SetWindowPos(NULL, pt.x, pt.y, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
}
}
delete[] pControl;
}
m_bDelayedButtonLayout = bIsDelayed;
}
delete[] pData;
}
//BLOCK: Adjust Margins
{
CRect rect; rect.SetRectEmpty();
CalcInsideRect(rect, (dwMode & LM_HORZ));
sizeResult.cy -= rect.Height();
sizeResult.cx -= rect.Width();
CSize size = CControlBar::CalcFixedLayout((dwMode & LM_STRETCH), (dwMode & LM_HORZ));
sizeResult.cx = max(sizeResult.cx, size.cx);
sizeResult.cy = max(sizeResult.cy, size.cy);
}
return sizeResult;
}
CSize CFlatToolBar::CalcFixedLayout(BOOL bStretch, BOOL bHorz)
{
DWORD dwMode = bStretch ? LM_STRETCH : 0;
dwMode |= bHorz ? LM_HORZ : 0;
return CalcLayout(dwMode);
}
CSize CFlatToolBar::CalcDynamicLayout(int nLength, DWORD dwMode)
{
if ((nLength == -1) && !(dwMode & LM_MRUWIDTH) && !(dwMode & LM_COMMIT) &&
((dwMode & LM_HORZDOCK) || (dwMode & LM_VERTDOCK)))
{
return CalcFixedLayout(dwMode & LM_STRETCH, dwMode & LM_HORZDOCK);
}
return CalcLayout(dwMode, nLength);
}
/////////////////////////////////////////////////////////////////////////////
// CToolBar attribute access
// **PD** I renamed this from _GetButton.
//
void CFlatToolBar::GetButton(int nIndex, TBBUTTON* pButton) const
{
CToolBar* pBar = (CToolBar*)this;
VERIFY(pBar->SendMessage(TB_GETBUTTON, nIndex, (LPARAM)pButton));
// TBSTATE_ENABLED == TBBS_DISABLED so invert it
pButton->fsState ^= TBSTATE_ENABLED;
}
// **PD** I renamed this from _SetButton.
//
void CFlatToolBar::SetButton(int nIndex, TBBUTTON* pButton)
{
// get original button state
TBBUTTON button;
VERIFY(SendMessage(TB_GETBUTTON, nIndex, (LPARAM)&button));
// prepare for old/new button comparsion
button.bReserved[0] = 0;
button.bReserved[1] = 0;
// TBSTATE_ENABLED == TBBS_DISABLED so invert it
pButton->fsState ^= TBSTATE_ENABLED;
pButton->bReserved[0] = 0;
pButton->bReserved[1] = 0;
// nothing to do if they are the same
if (memcmp(pButton, &button, sizeof(TBBUTTON)) != 0)
{
// don't redraw everything while setting the button
DWORD dwStyle = GetStyle();
ModifyStyle(WS_VISIBLE, 0);
VERIFY(SendMessage(TB_DELETEBUTTON, nIndex, 0));
VERIFY(SendMessage(TB_INSERTBUTTON, nIndex, (LPARAM)pButton));
ModifyStyle(0, dwStyle & WS_VISIBLE);
// invalidate appropriate parts
if (((pButton->fsStyle ^ button.fsStyle) & TBSTYLE_SEP) ||
((pButton->fsStyle & TBSTYLE_SEP) && pButton->iBitmap != button.iBitmap))
{
// changing a separator
Invalidate(FALSE);
}
else
{
// invalidate just the button
CRect rect;
if (SendMessage(TB_GETITEMRECT, nIndex, (LPARAM)&rect))
InvalidateRect(rect, FALSE); // don't erase background
}
}
}
////////////////////////////////////////////////////////////////
// End FixTb code.
////////////////////////////////////////////////////////////////
////////////////
// Load override modifies the style after loading toolbar.
//
BOOL CFlatToolBar::LoadToolBar(LPCTSTR lpszResourceName)
{
if (!CToolBar::LoadToolBar(lpszResourceName))
return FALSE;
ModifyStyle(0, TBSTYLE_FLAT); // make it flat
return TRUE;
}
//#define ILLUSTRATE_DISPLAY_BUG // remove comment to see the bug
//////////////////
// MFC doesn't handle moving a TBSTYLE_FLAT toolbar correctly.
// The simplest way to fix it is to repaint the old rectangle and
// toolbar itself whenever the toolbar moves.
//
void CFlatToolBar::OnWindowPosChanging(LPWINDOWPOS lpwp)
{
CToolBar::OnWindowPosChanging(lpwp);
#ifndef ILLUSTRATE_DISPLAY_BUG
if (!(lpwp->flags & SWP_NOMOVE)) { // if moved:
CRect rc; // Fill rectangle with..
GetWindowRect(&rc); // ..my (toolbar) rectangle.
CWnd* pParent = GetParent(); // get parent (dock bar/frame) win..
pParent->ScreenToClient(&rc); // .. and convert to parent coords
// Ask parent window to paint the area beneath my old location.
// Typically, this is just solid grey. The area won't get painted until
// I send WM_NCPAINT after the move, in OnWindowPosChanged below.
//
pParent->InvalidateRect(&rc); // paint old rectangle
}
#endif
}
//////////////////
// Now toolbar has moved: repaint old area
//
void CFlatToolBar::OnWindowPosChanged(LPWINDOWPOS lpwp)
{
CToolBar::OnWindowPosChanged(lpwp);
#ifndef ILLUSTRATE_DISPLAY_BUG
if (!(lpwp->flags & SWP_NOMOVE)) { // if moved:
// Now paint my non-client area at the new location.
// This is the extra bit of border space surrounding the buttons.
// Without this, you will still have a partial display bug (try it!)
//
SendMessage(WM_NCPAINT);
}
#endif
}
////////////////////////////////////////////////////////////////
// The following stuff is to make the command update UI mechanism
// work properly for flat tool bars. The main idea is to convert
// a "checked" button state into a "pressed" button state. Changed
// lines marked with "PD"
void CFlatOrCoolBarCmdUI::Enable(BOOL bOn)
{
m_bEnableChanged = TRUE;
CToolBar* pToolBar = (CToolBar*)m_pOther;
ASSERT(pToolBar != NULL);
ASSERT_KINDOF(CToolBar, pToolBar);
ASSERT(m_nIndex < m_nIndexMax);
UINT nNewStyle = pToolBar->GetButtonStyle(m_nIndex) & ~TBBS_DISABLED;
if (!bOn)
{
nNewStyle |= TBBS_DISABLED;
// WINBUG: If a button is currently pressed and then is disabled
// COMCTL32.DLL does not unpress the button, even after the mouse
// button goes up! We work around this bug by forcing TBBS_PRESSED
// off when a button is disabled.
nNewStyle &= ~TBBS_PRESSED;
}
ASSERT(!(nNewStyle & TBBS_SEPARATOR));
pToolBar->SetButtonStyle(m_nIndex, nNewStyle);
}
// Take your pick:
#define MYTBBS_CHECKED TBBS_CHECKED // use "checked" state
//#define MYTBBS_CHECKED TBBS_PRESSED // use pressed state
//////////////////
// This is the only function that has changed: instead of TBBS_CHECKED,
// I use TBBS_PRESSED--PD
//
void CFlatOrCoolBarCmdUI::SetCheck(int nCheck)
{
ASSERT(nCheck >= 0 && nCheck <= 2); // 0=>off, 1=>on, 2=>indeterminate
CToolBar* pToolBar = (CToolBar*)m_pOther;
ASSERT(pToolBar != NULL);
ASSERT_KINDOF(CToolBar, pToolBar);
ASSERT(m_nIndex < m_nIndexMax);
UINT nOldStyle = pToolBar->GetButtonStyle(m_nIndex); // PD
UINT nNewStyle = nOldStyle &
~(MYTBBS_CHECKED | TBBS_INDETERMINATE); // PD
if (nCheck == 1)
nNewStyle |= MYTBBS_CHECKED; // PD
else if (nCheck == 2)
nNewStyle |= TBBS_INDETERMINATE;
// Following is to fix display bug for TBBS_CHECKED:
// If new state is unchecked, repaint--but only if style actually changing.
// (Otherwise will end up with flicker)
//
if (nNewStyle != nOldStyle) {
ASSERT(!(nNewStyle & TBBS_SEPARATOR));
pToolBar->SetButtonStyle(m_nIndex, nNewStyle);
pToolBar->Invalidate();
}
}
void CFlatOrCoolBarCmdUI::SetText(LPCTSTR)
{
// ignore for now, but you should really set the text
}
//////////////////
// This function is mostly copied from CToolBar/BARTOOL.CPP. The only thing
// that's different is I instantiated a CFlatOrCoolBarCmdUI instead of
// CToolCmdUI.
//
void CFlatToolBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler)
{
CFlatOrCoolBarCmdUI state; // <<<< This is the only line that's different--PD
state.m_pOther = this;
state.m_nIndexMax = (UINT)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex++)
{
// get button state
TBBUTTON button;
VERIFY(DefWindowProc(TB_GETBUTTON, state.m_nIndex, (LPARAM)&button));
// TBSTATE_ENABLED == TBBS_DISABLED so invert it
button.fsState ^= TBSTATE_ENABLED;
state.m_nID = button.idCommand;
// ignore separators
if (!(button.fsStyle & TBSTYLE_SEP))
{
// allow the toolbar itself to have update handlers
if (CWnd::OnCmdMsg(state.m_nID, CN_UPDATE_COMMAND_UI, &state, NULL))
continue;
// allow the owner to process the update
state.DoUpdate(pTarget, bDisableIfNoHndler);
}
}
// update the dialog controls added to the toolbar
UpdateDialogControls(pTarget, bDisableIfNoHndler);
}
//////////////////
// Make the parent frame my owner. This is important for status bar
// prompts to work. Note that when you create the CCoolToolBar in
// CYourCoolBar::OnCreateBands, you must also set CBRS_FLYBY in the
// the CCoolToolBar style!
//
BOOL CFlatToolBar::OnNcCreate(LPCREATESTRUCT lpCreateStruct)
{
CFrameWnd* pFrame = GetParentFrame();
ASSERT_VALID(pFrame);
SetOwner(pFrame);
return CToolBar::OnNcCreate(lpCreateStruct);
}