594 lines
12 KiB
C++
594 lines
12 KiB
C++
// File: Toolbar.cpp
|
|
|
|
#include "precomp.h"
|
|
|
|
#include "GenContainers.h"
|
|
#include "GenControls.h"
|
|
|
|
#include <windowsx.h>
|
|
|
|
// Minimum size for children;
|
|
// BUGBUG georgep; Should probably set this to 0 after debugging
|
|
const static int MinSize = 10;
|
|
|
|
// Default m_gap
|
|
const static int HGapSize = 4;
|
|
// Default m_hMargin
|
|
const static int HMargin = 0;
|
|
// Default m_vMargin
|
|
const static int VMargin = 0;
|
|
|
|
// Init m_uRightIndex and m_uCenterIndex to very large numbers
|
|
CToolbar::CToolbar() :
|
|
m_gap(HGapSize),
|
|
m_hMargin(HMargin),
|
|
m_vMargin(VMargin),
|
|
m_nAlignment(TopLeft),
|
|
m_uRightIndex(static_cast<UINT>(-1)),
|
|
m_bHasCenterChild(FALSE),
|
|
m_bReverseOrder(FALSE),
|
|
m_bMinDesiredSize(FALSE),
|
|
m_bVertical(FALSE)
|
|
{
|
|
}
|
|
|
|
BOOL CToolbar::Create(
|
|
HWND hWndParent, // The parent of the toolbar window
|
|
DWORD dwExStyle // The extended style of the toolbar window
|
|
)
|
|
{
|
|
return(CGenWindow::Create(
|
|
hWndParent, // Window parent
|
|
0, // ID of the child window
|
|
TEXT("NMToolbar"), // Window name
|
|
WS_CLIPCHILDREN, // Window style; WS_CHILD|WS_VISIBLE will be added to this
|
|
dwExStyle|WS_EX_CONTROLPARENT // Extended window style
|
|
));
|
|
}
|
|
|
|
// Get the desired size for a child, and make sure it is big enough
|
|
static void GetWindowDesiredSize(HWND hwnd, SIZE *ppt)
|
|
{
|
|
ppt->cx = ppt->cy = 0;
|
|
|
|
IGenWindow *pWnd = CGenWindow::FromHandle(hwnd);
|
|
if (NULL != pWnd)
|
|
{
|
|
pWnd->GetDesiredSize(ppt);
|
|
}
|
|
|
|
ppt->cx = max(ppt->cx, MinSize);
|
|
ppt->cy = max(ppt->cy, MinSize);
|
|
}
|
|
|
|
BOOL IsChildVisible(HWND hwndChild)
|
|
{
|
|
return((GetWindowLong(hwndChild, GWL_STYLE)&WS_VISIBLE) == WS_VISIBLE);
|
|
}
|
|
|
|
/** Get the total desired size of the child windows: max of heights and sum of
|
|
* widths or vice versa for vertical windows.
|
|
* @param hwndParent The window whose children are to be examined
|
|
* @param size The returned total size
|
|
* @param bVertical Whether to flow vertical or horizontal
|
|
* @returns The number of visible child windows
|
|
*/
|
|
static int GetChildTotals(HWND hwndParent, SIZE *size, BOOL bVertical)
|
|
{
|
|
int nChildren = 0;
|
|
int xMax=0, xTot=0;
|
|
int yMax=0, yTot=0;
|
|
|
|
for (HWND hwndChild=::GetWindow(hwndParent, GW_CHILD); NULL!=hwndChild;
|
|
hwndChild=::GetWindow(hwndChild, GW_HWNDNEXT))
|
|
{
|
|
if (!IsChildVisible(hwndChild))
|
|
{
|
|
continue;
|
|
}
|
|
++nChildren;
|
|
|
|
SIZE pt;
|
|
GetWindowDesiredSize(hwndChild, &pt);
|
|
|
|
xTot += pt.cx;
|
|
yTot += pt.cy;
|
|
if (xMax < pt.cx) xMax = pt.cx;
|
|
if (yMax < pt.cy) yMax = pt.cy;
|
|
}
|
|
|
|
if (bVertical)
|
|
{
|
|
size->cx = xMax;
|
|
size->cy = yTot;
|
|
}
|
|
else
|
|
{
|
|
size->cx = xTot;
|
|
size->cy = yMax;
|
|
}
|
|
|
|
return(nChildren);
|
|
}
|
|
|
|
// Returns the total children desired size, plus the gaps and margins.
|
|
void CToolbar::GetDesiredSize(SIZE *ppt)
|
|
{
|
|
int nChildren = GetChildTotals(GetWindow(), ppt, m_bVertical);
|
|
|
|
if (nChildren > 1 && !m_bMinDesiredSize)
|
|
{
|
|
if (m_bVertical)
|
|
{
|
|
ppt->cy += (nChildren-1) * m_gap;
|
|
}
|
|
else
|
|
{
|
|
ppt->cx += (nChildren-1) * m_gap;
|
|
}
|
|
}
|
|
|
|
ppt->cx += m_hMargin * 2;
|
|
ppt->cy += m_vMargin * 2;
|
|
|
|
SIZE sizeTemp;
|
|
CGenWindow::GetDesiredSize(&sizeTemp);
|
|
ppt->cx += sizeTemp.cx;
|
|
ppt->cy += sizeTemp.cy;
|
|
}
|
|
|
|
void CToolbar::AdjustPos(POINT *pPos, SIZE *pSize, UINT width)
|
|
{
|
|
pPos->x = pPos->y = 0;
|
|
|
|
switch (m_nAlignment)
|
|
{
|
|
default:
|
|
case TopLeft:
|
|
// Nothing to do
|
|
break;
|
|
|
|
case Center:
|
|
if (m_bVertical)
|
|
{
|
|
pPos->x = (width - pSize->cx)/2;
|
|
}
|
|
else
|
|
{
|
|
pPos->y = (width - pSize->cy)/2;
|
|
}
|
|
break;
|
|
|
|
case BottomRight:
|
|
if (m_bVertical)
|
|
{
|
|
pPos->x = (width - pSize->cx);
|
|
}
|
|
else
|
|
{
|
|
pPos->y = (width - pSize->cy);
|
|
}
|
|
break;
|
|
|
|
case Fill:
|
|
if (m_bVertical)
|
|
{
|
|
pSize->cx = width;
|
|
}
|
|
else
|
|
{
|
|
pSize->cy = width;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get the first child to layout
|
|
HWND CToolbar::GetFirstKid()
|
|
{
|
|
HWND ret = ::GetWindow(GetWindow(), GW_CHILD);
|
|
if (m_bReverseOrder && NULL != ret)
|
|
{
|
|
ret = ::GetWindow(ret, GW_HWNDLAST);
|
|
}
|
|
|
|
return(ret);
|
|
}
|
|
|
|
// Get the next child to layout
|
|
HWND CToolbar::GetNextKid(HWND hwndCurrent)
|
|
{
|
|
return(::GetWindow(hwndCurrent, m_bReverseOrder ? GW_HWNDPREV : GW_HWNDNEXT));
|
|
}
|
|
|
|
extern HDWP SetWindowPosI(HDWP hdwp, HWND hwndChild, int left, int top, int width, int height);
|
|
|
|
// Flow child windows according to the fields
|
|
void CToolbar::Layout()
|
|
{
|
|
RECT rc;
|
|
GetClientRect(GetWindow(), &rc);
|
|
|
|
// First see how much extra space we have
|
|
SIZE sizeTotal;
|
|
int nChildren = GetChildTotals(GetWindow(), &sizeTotal, m_bVertical);
|
|
if (0 == nChildren)
|
|
{
|
|
// No children, so nothing to layout
|
|
return;
|
|
}
|
|
|
|
// Add on the margins
|
|
sizeTotal.cx += 2*m_hMargin;
|
|
sizeTotal.cy += 2*m_vMargin;
|
|
|
|
if (nChildren > 1 || !m_bHasCenterChild)
|
|
{
|
|
// Don't layout with children overlapping
|
|
rc.right = max(rc.right , sizeTotal.cx);
|
|
rc.bottom = max(rc.bottom, sizeTotal.cy);
|
|
}
|
|
|
|
// Calculate the total gaps between children
|
|
int tGap = m_bVertical ? rc.bottom - sizeTotal.cy : rc.right - sizeTotal.cx;
|
|
int maxGap = (nChildren-1)*m_gap;
|
|
if (tGap > maxGap) tGap = maxGap;
|
|
tGap = max(tGap, 0); // This can happen if only a center child
|
|
|
|
// If we fill, then children in a vertical toolbar go from the left to the
|
|
// right margin, and similar for a horizontal toolbar
|
|
int fill = m_bVertical ? rc.right-2*m_hMargin : rc.bottom-2*m_vMargin;
|
|
|
|
// Speed up layout by deferring it
|
|
HDWP hdwp = BeginDeferWindowPos(nChildren);
|
|
|
|
HWND hwndChild;
|
|
UINT nChild = 0;
|
|
|
|
// Iterate through the children
|
|
UINT uCenterIndex = m_bHasCenterChild ? m_uRightIndex-1 : static_cast<UINT>(-1);
|
|
// We need to keep track of whether the middle was skipped in case the
|
|
// center control or the first right-aligned control is hidden
|
|
BOOL bMiddleSkipped = FALSE;
|
|
|
|
// Do left/top-aligned children
|
|
// The starting point for laying out children
|
|
int left = m_hMargin;
|
|
int top = m_vMargin;
|
|
|
|
for (hwndChild=GetFirstKid(); NULL!=hwndChild;
|
|
hwndChild=GetNextKid(hwndChild), ++nChild)
|
|
{
|
|
if (!IsChildVisible(hwndChild))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SIZE size;
|
|
GetWindowDesiredSize(hwndChild, &size);
|
|
|
|
if (nChild == uCenterIndex)
|
|
{
|
|
// Take the window size, subtract all the gaps, and subtract the
|
|
// desired size of everybody but this control. That should give
|
|
// the "extra" area in the middle
|
|
if (m_bVertical)
|
|
{
|
|
size.cy = rc.bottom - tGap - (sizeTotal.cy - size.cy);
|
|
}
|
|
else
|
|
{
|
|
size.cx = rc.right - tGap - (sizeTotal.cx - size.cx);
|
|
}
|
|
|
|
bMiddleSkipped = TRUE;
|
|
}
|
|
else if (nChild >= m_uRightIndex && !bMiddleSkipped)
|
|
{
|
|
// Skip the "extra" room in the middle; if there is a centered
|
|
// control, then we have already done this
|
|
if (m_bVertical)
|
|
{
|
|
top += rc.bottom - tGap - sizeTotal.cy;
|
|
}
|
|
else
|
|
{
|
|
left += rc.right - tGap - sizeTotal.cx;
|
|
}
|
|
|
|
bMiddleSkipped = TRUE;
|
|
}
|
|
|
|
POINT pos;
|
|
AdjustPos(&pos, &size, fill);
|
|
|
|
// Move the window
|
|
hdwp = SetWindowPosI(hdwp, hwndChild, pos.x+left, pos.y+top, size.cx, size.cy);
|
|
|
|
// calculate the gap; don't just use a "fixed" gap, since children
|
|
// would move in chunks
|
|
int gap = (nChildren<=1) ? 0 : ((tGap * (nChild+1))/(nChildren-1) - (tGap * nChild)/(nChildren-1));
|
|
|
|
// Update the pos of the next child
|
|
if (m_bVertical)
|
|
{
|
|
top += gap + size.cy;
|
|
}
|
|
else
|
|
{
|
|
left += gap + size.cx;
|
|
}
|
|
}
|
|
|
|
// Actually move all the windows now
|
|
EndDeferWindowPos(hdwp);
|
|
}
|
|
|
|
LRESULT CToolbar::ProcessMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
|
|
}
|
|
|
|
return(CGenWindow::ProcessMessage(hwnd, message, wParam, lParam));
|
|
}
|
|
|
|
void CToolbar::OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
|
|
{
|
|
FORWARD_WM_COMMAND(GetParent(hwnd), id, hwndCtl, codeNotify, SendMessage);
|
|
}
|
|
|
|
static HWND FindControl(HWND hwndParent, int nID)
|
|
{
|
|
if (GetWindowLong(hwndParent, GWL_ID) == nID)
|
|
{
|
|
return(hwndParent);
|
|
}
|
|
|
|
for (hwndParent=GetWindow(hwndParent, GW_CHILD); NULL!=hwndParent;
|
|
hwndParent=GetWindow(hwndParent, GW_HWNDNEXT))
|
|
{
|
|
HWND ret = FindControl(hwndParent, nID);
|
|
if (NULL != ret)
|
|
{
|
|
return(ret);
|
|
}
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
IGenWindow *CToolbar::FindControl(int nID)
|
|
{
|
|
HWND hwndRet = ::FindControl(GetWindow(), nID);
|
|
if (NULL == hwndRet)
|
|
{
|
|
return(NULL);
|
|
}
|
|
|
|
return(FromHandle(hwndRet));
|
|
}
|
|
|
|
CSeparator::CSeparator() :
|
|
m_iStyle(Normal)
|
|
{
|
|
m_desSize.cx = m_desSize.cy = 2;
|
|
}
|
|
|
|
BOOL CSeparator::Create(
|
|
HWND hwndParent, UINT iStyle
|
|
)
|
|
{
|
|
m_iStyle = iStyle;
|
|
return(CGenWindow::Create(
|
|
hwndParent, // Window parent
|
|
0, // ID of the child window
|
|
TEXT("NMSeparator"), // Window name
|
|
WS_CLIPCHILDREN, // Window style; WS_CHILD|WS_VISIBLE will be added to this
|
|
WS_EX_CONTROLPARENT // Extended window style
|
|
));
|
|
}
|
|
|
|
void CSeparator::GetDesiredSize(SIZE *ppt)
|
|
{
|
|
*ppt = m_desSize;
|
|
|
|
// Make sure there's room for the child
|
|
HWND child = GetFirstChild(GetWindow());
|
|
if (NULL == child)
|
|
{
|
|
// Nothing to do
|
|
return;
|
|
}
|
|
IGenWindow *pChild = FromHandle(child);
|
|
if (NULL == pChild)
|
|
{
|
|
// Don't know what to do
|
|
return;
|
|
}
|
|
|
|
SIZE size;
|
|
pChild->GetDesiredSize(&size);
|
|
|
|
ppt->cx = max(ppt->cx, size.cx);
|
|
ppt->cy = max(ppt->cy, size.cy);
|
|
}
|
|
|
|
void CSeparator::SetDesiredSize(SIZE *psize)
|
|
{
|
|
m_desSize = *psize;
|
|
OnDesiredSizeChanged();
|
|
}
|
|
|
|
void CSeparator::Layout()
|
|
{
|
|
HWND hwnd = GetWindow();
|
|
|
|
HWND child = GetFirstChild(hwnd);
|
|
if (NULL == child)
|
|
{
|
|
// Nothing to do
|
|
return;
|
|
}
|
|
IGenWindow *pChild = FromHandle(child);
|
|
if (NULL == pChild)
|
|
{
|
|
// Don't know what to do
|
|
return;
|
|
}
|
|
|
|
// Center the child horizontally and vertically
|
|
SIZE size;
|
|
pChild->GetDesiredSize(&size);
|
|
|
|
RECT rcClient;
|
|
GetClientRect(hwnd, &rcClient);
|
|
|
|
rcClient.left += (rcClient.right-rcClient.left-size.cx)/2;
|
|
rcClient.top += (rcClient.bottom-rcClient.top-size.cy)/2;
|
|
|
|
SetWindowPos(child, NULL, rcClient.left, rcClient.top, size.cx, size.cy,
|
|
SWP_NOZORDER|SWP_NOACTIVATE);
|
|
}
|
|
|
|
void CSeparator::OnPaint(HWND hwnd)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hwnd, &ps);
|
|
|
|
RECT rc;
|
|
GetClientRect(hwnd, &rc);
|
|
|
|
int nFlags = BF_LEFT;
|
|
if (rc.right < rc.bottom)
|
|
{
|
|
// this is a vertical separator
|
|
// center the drawing
|
|
rc.left += (rc.right-rc.left)/2 - 1;
|
|
rc.right = 4;
|
|
}
|
|
else
|
|
{
|
|
// this is a horizontal separator
|
|
nFlags = BF_TOP;
|
|
// center the drawing
|
|
rc.top += (rc.bottom-rc.top)/2 - 1;
|
|
rc.bottom = 4;
|
|
}
|
|
|
|
if (Normal == m_iStyle)
|
|
{
|
|
DrawEdge(hdc, &rc, EDGE_ETCHED, nFlags);
|
|
}
|
|
|
|
EndPaint(hwnd, &ps);
|
|
}
|
|
|
|
LRESULT CSeparator::ProcessMessage(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
HANDLE_MSG(hwnd, WM_PAINT, OnPaint);
|
|
}
|
|
|
|
return(CGenWindow::ProcessMessage(hwnd, message, wParam, lParam));
|
|
}
|
|
|
|
BOOL CLayeredView::Create(
|
|
HWND hwndParent, // The parent of this window
|
|
DWORD dwExStyle // The extended style
|
|
)
|
|
{
|
|
return(CGenWindow::Create(
|
|
hwndParent,
|
|
0,
|
|
TEXT("NMLayeredView"),
|
|
WS_CLIPCHILDREN,
|
|
dwExStyle));
|
|
}
|
|
|
|
void CLayeredView::GetDesiredSize(SIZE *psize)
|
|
{
|
|
CGenWindow::GetDesiredSize(psize);
|
|
|
|
HWND child = GetFirstChild(GetWindow());
|
|
if (NULL == child)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SIZE sizeContent;
|
|
|
|
IGenWindow *pChild;
|
|
pChild = FromHandle(child);
|
|
if (NULL != pChild)
|
|
{
|
|
// Make sure we can always handle the first window
|
|
pChild->GetDesiredSize(&sizeContent);
|
|
}
|
|
|
|
for (child=::GetWindow(child, GW_HWNDNEXT); NULL!=child;
|
|
child=::GetWindow(child, GW_HWNDNEXT))
|
|
{
|
|
if (IsChildVisible(child))
|
|
{
|
|
pChild = FromHandle(child);
|
|
if (NULL != pChild)
|
|
{
|
|
SIZE sizeTemp;
|
|
pChild->GetDesiredSize(&sizeTemp);
|
|
|
|
sizeContent.cx = max(sizeContent.cx, sizeTemp.cx);
|
|
sizeContent.cy = max(sizeContent.cy, sizeTemp.cy);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
psize->cx += sizeContent.cx;
|
|
psize->cy += sizeContent.cy;
|
|
}
|
|
|
|
void CLayeredView::Layout()
|
|
{
|
|
HWND hwndThis = GetWindow();
|
|
|
|
RECT rcClient;
|
|
GetClientRect(hwndThis, &rcClient);
|
|
|
|
// Just move all the children
|
|
for (HWND child=GetFirstChild(hwndThis); NULL!=child;
|
|
child=::GetWindow(child, GW_HWNDNEXT))
|
|
{
|
|
switch (m_lStyle)
|
|
{
|
|
case Center:
|
|
{
|
|
IGenWindow *pChild = FromHandle(child);
|
|
if (NULL != pChild)
|
|
{
|
|
SIZE size;
|
|
pChild->GetDesiredSize(&size);
|
|
SetWindowPos(child, NULL,
|
|
(rcClient.left+rcClient.right-size.cx)/2,
|
|
(rcClient.top+rcClient.bottom-size.cy)/2,
|
|
size.cx, size.cy, SWP_NOZORDER|SWP_NOACTIVATE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Fall through
|
|
case Fill:
|
|
default:
|
|
SetWindowPos(child, NULL,
|
|
rcClient.left, rcClient.top,
|
|
rcClient.right-rcClient.left,
|
|
rcClient.bottom-rcClient.top,
|
|
SWP_NOZORDER|SWP_NOACTIVATE);
|
|
break;
|
|
}
|
|
}
|
|
}
|