// File: Toolbar.cpp #include "precomp.h" #include "GenContainers.h" #include "GenControls.h" #include // 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(-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(-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; } } }