///////////////////////////////////////////////////////////////////////////////
//
//
//
///////////////////////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include "CeDebug.h"
#include "CeWnd.h"
#include "CeMisc.h"
#include "CeArray.h"
#include "CeMgr.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
CeChildMgr::CeChildInfo::CeChildInfo()
{
m_hWndChild = NULL;
m_nFlags = 0;
::SetRectEmpty(&m_rcOriginal);
m_sizeMin.cy = m_sizeMin.cy = 0;
::SetRectEmpty(&m_rcMove);
m_sizeAssocLabelDelta = CeSize(0,0);
m_hWndLabel = NULL;
::SetRectEmpty(&m_rcLabel);
m_hWndSpinner = NULL;
::SetRectEmpty(&m_rcSpinner);
};
CeChildMgr::CeChildMgr()
{
m_hWnd = NULL;
::SetRectEmpty(&m_rcOriginal);
::SetRectEmpty(&m_rcLastParent);
}
CeChildMgr::~CeChildMgr()
{
}
BOOL CeChildMgr::Manage(HWND hChild, UINT nFlags)
{
ASSERT( hChild );
ASSERT( ::GetParent(hChild) );
CeChildInfo cInfo;
if (m_hWnd == NULL)
{
// Gather information about controlling window
m_hWnd = ::GetParent(hChild);
::GetClientRect(m_hWnd, &m_rcOriginal);
}
CeWnd wnd;
wnd.Attach(m_hWnd);
ASSERT( ::GetParent(hChild) == m_hWnd);
// We store an hWnd rather than a CeWnd so that
// you can call Register with a CeWnd object that may
// only be temporary (and yet be attached to a persistant
// hWnd).
cInfo.m_hWndChild = hChild;
// Store resize mode
cInfo.m_nFlags = nFlags;
// Store original size of rectangle for some resize operations.
::GetWindowRect(cInfo.m_hWndChild, &cInfo.m_rcOriginal);
wnd.ScreenToClient(cInfo.m_rcOriginal);
// Hack, set to value from user or from non-client size
CeRect rectClient;
::GetClientRect(cInfo.m_hWndChild, &rectClient);
cInfo.m_sizeMin.cx = cInfo.m_rcOriginal.Width() - rectClient.Width();
cInfo.m_sizeMin.cy = cInfo.m_rcOriginal.Height() - rectClient.Height();
// If AL_FIX_ASSOC_LABEL flag is on, then get previous sibling item
// and store in cInfo the distance from right edge of associated label
// to left edge of pWnd.
if(nFlags & (AL_FIX_LABEL|AL_FIX_LABEL_ABOVE))
{
HWND hWndLabel = ::GetWindow(cInfo.m_hWndChild, GW_HWNDPREV);
// If this assertion fails, the window does not have a previous sibling.
// Perhaps AL_FIX_LABEL shouldn't be used.
ASSERT(hWndLabel);
CeRect rectPrev;
::GetWindowRect(hWndLabel, &rectPrev);
wnd.ScreenToClient(rectPrev);
cInfo.m_sizeAssocLabelDelta.cx = cInfo.m_rcOriginal.left - rectPrev.right;
cInfo.m_sizeAssocLabelDelta.cy = rectPrev.top - cInfo.m_rcOriginal.top;
cInfo.m_hWndLabel = hWndLabel;
}
if (nFlags & AL_FIX_SPINNER)
{
HWND hWndSpin = ::GetWindow(cInfo.m_hWndChild, GW_HWNDNEXT);
ASSERT(hWndSpin);
CeRect rectNext;
::GetWindowRect(hWndSpin, &rectNext);
wnd.ScreenToClient(rectNext);
cInfo.m_sizeAssocSpinDelta.cx = rectNext.left - cInfo.m_rcOriginal.right;
cInfo.m_sizeAssocSpinDelta.cy = rectNext.top - cInfo.m_rcOriginal.top;
cInfo.m_hWndSpinner = hWndSpin;
}
// Add the CLayoutInfo object to our collection.
m_arrayInfo.Add(&cInfo);
wnd.Detach();
// Return "handle" (really just index into array) as return
// value. Might use as parameter for future "modifier" functions.
return m_arrayInfo.GetUpperBound();
}
//
// TODO:
// 1. don't move if it didn't move
// 2. try hiding all, moving all, showing all
//
void CeChildMgr::OnSize(UINT nType, int /*cx*/, int /*cy*/)
{
int nMax = m_arrayInfo.GetSize();
if (SIZE_MINIMIZED == nType || nMax <= 0)
return;
ASSERT(m_hWnd);
CeWnd wnd;
wnd.Attach(m_hWnd);
CeRect rectParent;
::GetClientRect(m_hWnd, &rectParent);
// Walk all controls in collection.
for (int ii = 0; ii < nMax; ii++)
{
CeChildInfo *pInfo = &(m_arrayInfo[ii]);
// validate hWnd
if (! ::IsWindow(pInfo->m_hWndChild))
{
ASSERT(0);
// mark for removal instead of error condition?
continue;
}
// get current location
CeRect rectWnd;
::GetWindowRect(pInfo->m_hWndChild, rectWnd);
wnd.ScreenToClient(rectWnd);
// get the new size
CeSize sizeNew = CalcSize(pInfo, rectParent);
//
// Handle X positioning
//
if (pInfo->m_nFlags & (AL_ANCHOR_RIGHT | AL_PROP_ADJUST_HORZPOS))
{
// Original distance from right edge of parent to control.
int nDistToRight = m_rcOriginal.right - pInfo->m_rcOriginal.right;
if (pInfo->m_nFlags & AL_ANCHOR_RIGHT)
{
// Preserve distance to right edge of parent.
rectWnd.right = rectParent.right - nDistToRight;
}
else // if(pInfo->dwLayoutMode & AL_PROP_ADJUST_HORZPOS)
{
// To find the proportionally scaled position, we calculate where the original
// top left point would lie in scaled coordinate frame.
// (We use top left instead of center so that controls of differing sizes
// still move together...)
CePoint ptOrigTopLeft = pInfo->m_rcOriginal.TopLeft();
CePoint ptNewTopLeft(
(ptOrigTopLeft.x * rectParent.Width()) / m_rcOriginal.Width(),
(ptOrigTopLeft.y * rectParent.Height()) / m_rcOriginal.Height()
);
// ::MulDiv(ptOrigTopLeft.x, rectParent.Width(), m_rcOriginal.Width()),
// ::MulDiv(ptOrigTopLeft.y, RectHeight(rectParent), RectHeight(m_rcOriginal))
// New right edge is the new left edge plus new width.
rectWnd.right = ptNewTopLeft.x + sizeNew.cx;
}
// Establish left edge.
rectWnd.left = rectWnd.right - sizeNew.cx;
}
else // if(pInfo->dwLayoutMode & AL_ANCHOR_LEFT)
{
// Simple - new right is old left plus new width.
rectWnd.right = rectWnd.left + sizeNew.cx;
}
//
// Handle Y positioning
//
if (pInfo->m_nFlags & (AL_ANCHOR_BOTTOM | AL_PROP_ADJUST_VERTPOS))
{
// Original distance from bottom of parent to bottom of control.
int nDistToBottom = m_rcOriginal.bottom - pInfo->m_rcOriginal.bottom;
if(pInfo->m_nFlags & AL_ANCHOR_BOTTOM)
{
// Preserve distance to bottom edge of parent.
rectWnd.bottom = rectParent.bottom - nDistToBottom;
}
else // if(pInfo->m_nFlags & AL_PROP_ADJUST_VERTPOS)
{
// To find the proportionally scaled position, we calculate where the original
// top left point would lie in scaled coordinate frame.
// (We use top left instead of center so that controls of differing sizes
// still move together...)
CePoint ptOrigTopLeft = pInfo->m_rcOriginal.TopLeft();
CePoint ptNewTopLeft(
(ptOrigTopLeft.x * rectParent.Width()) / m_rcOriginal.Width(),
(ptOrigTopLeft.y * rectParent.Height()) / m_rcOriginal.Height()
);
// New bottom edge is the new top edge plus new width.
rectWnd.bottom = ptNewTopLeft.y + sizeNew.cy;
}
// Establish top edge.
rectWnd.top = rectWnd.bottom - sizeNew.cy;
if (rectWnd.top > rectWnd.bottom)
rectWnd.top = rectWnd.bottom;
}
else // if(pInfo->m_nFlags & AL_ANCHOR_TOP)
{
// Simple = new bottom is old top plus new height.
rectWnd.bottom = rectWnd.top + sizeNew.cy;
if (rectWnd.bottom < rectWnd.top)
rectWnd.bottom = rectWnd.top;
}
// If AL_FIX_LABEL flag set, then adjust previous sibling (e.g. a text label)
// to have same distance from this window as it did in the beginning...
// (i.e. label's right edge to this window's left edge).
if (pInfo->m_nFlags & (AL_FIX_LABEL|AL_FIX_LABEL_ABOVE))
{
CeRect rectAssoc;
::GetWindowRect(pInfo->m_hWndLabel, rectAssoc);
int nAssocWidth = rectAssoc.Width();
rectAssoc.right = rectWnd.left - pInfo->m_sizeAssocLabelDelta.cx;
rectAssoc.left = rectAssoc.right - nAssocWidth;
int nAssocHeight = RectHeight(rectAssoc);
rectAssoc.top = rectWnd.top + pInfo->m_sizeAssocLabelDelta.cy;
rectAssoc.bottom = rectAssoc.top + nAssocHeight;
pInfo->m_rcLabel = rectAssoc;
}
// If AL_FIX_SPINNER flag set, then adjust spinner sibling
// to have same distance from this window as it did in the beginning...
// (i.e. spinner's left edge to this window's right edge).
if (pInfo->m_nFlags & AL_FIX_SPINNER)
{
CeRect rectAssoc;
::GetWindowRect(pInfo->m_hWndSpinner, rectAssoc);
int nAssocWidth = rectAssoc.Width();
rectAssoc.left = rectWnd.right + pInfo->m_sizeAssocSpinDelta.cx;
rectAssoc.right = rectAssoc.left + nAssocWidth;
int nAssocHeight = RectHeight(rectAssoc);
rectAssoc.top = rectWnd.top + pInfo->m_sizeAssocSpinDelta.cy;
rectAssoc.bottom = rectAssoc.top + nAssocHeight;
pInfo->m_rcSpinner = rectAssoc;
}
if (RectHeight(rectWnd) < pInfo->m_sizeMin.cy &&
rectWnd.Width() < pInfo->m_sizeMin.cx)
{
// move nothing, we just can't right now
return;
}
pInfo->m_rcMove = rectWnd;
}
// Preserve for next time through.
m_rcLastParent = rectParent;
if (! DetectCollision())
{
#if !defined(_WIN32_WCE)
// Preallocate the number of windows
// it will increase if the number includes labels
HDWP hDWP = ::BeginDeferWindowPos(nMax);
for (ii = 0; ii < nMax; ii++)
{
CeChildInfo* pInfo= &(m_arrayInfo[ii]);
hDWP = ::DeferWindowPos(hDWP,
pInfo->m_hWndChild, NULL,
pInfo->m_rcMove.left, pInfo->m_rcMove.top,
pInfo->m_rcMove.Width(), RectHeight(pInfo->m_rcMove),
SWP_NOZORDER);
if (pInfo->m_nFlags & (AL_FIX_LABEL|AL_FIX_LABEL_ABOVE))
hDWP = ::DeferWindowPos(hDWP,
pInfo->m_hWndLabel, NULL,
pInfo->m_rcLabel.left, pInfo->m_rcLabel.top,
pInfo->m_rcLabel.Width(), RectHeight(pInfo->m_rcLabel),
SWP_NOZORDER);
if (pInfo->m_nFlags & AL_FIX_SPINNER)
hDWP = ::DeferWindowPos(hDWP,
pInfo->m_hWndSpinner, NULL,
pInfo->m_rcSpinner.left, pInfo->m_rcSpinner.top,
pInfo->m_rcSpinner.Width(), RectHeight(pInfo->m_rcSpinner),
SWP_NOZORDER);
}
::EndDeferWindowPos(hDWP);
#else // _WIN32_WCE
// No violated contraints on positioning
// move all the windows to their new places
for (ii = 0; ii < nMax; ii++)
{
CeChildInfo* pInfo= &(m_arrayInfo[ii]);
::MoveWindow(pInfo->m_hWndChild,
pInfo->m_rcMove.left, pInfo->m_rcMove.top,
pInfo->m_rcMove.Width(), RectHeight(pInfo->m_rcMove),
TRUE);
if (pInfo->m_nFlags & (AL_FIX_LABEL|AL_FIX_LABEL_ABOVE))
::MoveWindow(pInfo->m_hWndLabel,
pInfo->m_rcLabel.left, pInfo->m_rcLabel.top,
pInfo->m_rcLabel.Width(), RectHeight(pInfo->m_rcLabel),
TRUE);
if (pInfo->m_nFlags & AL_FIX_SPINNER)
::MoveWindow(pInfo->m_hWndSpinner,
pInfo->m_rcSpinner.left, pInfo->m_rcSpinner.top,
pInfo->m_rcSpinner.Width(), RectHeight(pInfo->m_rcSpinner),
TRUE);
}
// Another pass for those controls that don't handle
// being on top of one another for any amount of time
for (ii = 0; ii < nMax; ii++)
{
CeChildInfo* pInfo= &(m_arrayInfo[ii]);
::InvalidateRect(pInfo->m_hWndChild, NULL, TRUE);
if (pInfo->m_nFlags & AL_FIX_LABEL)
::InvalidateRect(pInfo->m_hWndLabel, NULL, TRUE);
if (pInfo->m_nFlags & AL_FIX_SPINNER)
::InvalidateRect(pInfo->m_hWndSpinner, NULL, TRUE);
}
#endif
}
wnd.Detach();
}
SIZE CeChildMgr::CalcSize(CeChildInfo *pInfo, CeRect &rcParent)
{
// Default to no change
SIZE sizeRet;
sizeRet.cx = pInfo->m_rcOriginal.Width();
sizeRet.cy = pInfo->m_rcOriginal.Height();
if (pInfo->m_nFlags & AL_ADJUST_WIDTH)
{
// Adjust width by same as parent has changed
sizeRet.cx += rcParent.Width() - m_rcOriginal.Width();
}
else if (pInfo->m_nFlags & AL_PROP_ADJUST_WIDTH)
{
// Adjust width proportionally to parent change.
// (orig wnd width/orig parent width)*(new parent width) = new wnd width
// new left = new right - new wnd width.
sizeRet.cx = (pInfo->m_rcOriginal.Width() * rcParent.Width()) / m_rcOriginal.Width();
}
if (pInfo->m_nFlags & AL_ADJUST_HEIGHT)
{
// Adjust height by same as parent change.
sizeRet.cy += rcParent.Height() - m_rcOriginal.Height();
}
else if (pInfo->m_nFlags & AL_PROP_ADJUST_HEIGHT)
{
// Adjust height proportionally to parent change.
// (orig wnd height/orig parent height)*(new parent height) = new wnd height
// new top = new bottom - new wnd height.
sizeRet.cy = (pInfo->m_rcOriginal.Height() * rcParent.Height()) / m_rcOriginal.Height();
}
return sizeRet;
}
/*
void CeChildMgr::MsgBoxCurrentSize(CeWnd *pWnd)
{
CeRect rect;
pWnd->GetWindowRect(rect);
long baseunits = ::GetDialogBaseUnits();
int baseunitX = LOWORD(baseunits);
int baseunitY = HIWORD(baseunits);
int dialogunitX = (rect.Width() * 4) / baseunitX;
int dialogunitY = (rect.Height() * 8) / baseunitY;
CString str;
str.Format("Pixel Width: %d\n"
"Pixel Height: %d\n"
"Dialog Unit Width: %d\n"
"Dialog Unit Height: %d",
rect.Width(), rect.Height(), dialogunitX, dialogunitY);
pWnd->MessageBox(str, NULL, MB_ICONINFORMATION);
}
*/
// Call this when handling WM_GETMINMAXINFO, for example:
// lpMMI->ptMinTrackSize = CAutoLayout::SizeDialogToScreen(250,210);
POINT CeChildMgr::SizeDialogToScreen(int nDialogX, int nDialogY)
{
POINT ptRet;
long baseunits = ::GetDialogBaseUnits();
int baseunitX = LOWORD(baseunits);
int baseunitY = HIWORD(baseunits);
ptRet.x = (nDialogX * baseunitX) / 4;
ptRet.y = (nDialogY * baseunitY) / 8;
return ptRet;
}
// This works...but there is not much that you can do with it.
BOOL CeChildMgr::DetectCollision()
{
int nMax = m_arrayInfo.GetSize();
BOOL bCollide = FALSE;
for (int ii = 0; ii < nMax && ! bCollide; ii++)
{
// check to see if we would go too small
CeSize size = m_arrayInfo[ii].m_rcMove.Size();
if (size.cx < m_arrayInfo[ii].m_sizeMin.cx ||
size.cy < m_arrayInfo[ii].m_sizeMin.cy)
return TRUE;
for (int jj = 0; jj < nMax; jj++)
{
if (jj == ii)
continue;
CeRect rectIntersect;
// check for collision of controls, but only
// if one is not contained within another
if (rectIntersect.IntersectRect(m_arrayInfo[ii].m_rcMove, m_arrayInfo[jj].m_rcMove) &&
rectIntersect != m_arrayInfo[ii].m_rcMove &&
rectIntersect != m_arrayInfo[jj].m_rcMove)
return TRUE;
}
}
return FALSE;
}
// Offset ALL child controls by size, this function
// works for all children, not just the managed ones
void CeChildMgr::OffsetChildren(HWND hWnd, CeSize size)
{
CeRect rcChild;
for (HWND hChild = ::GetFirstChild(hWnd); hChild; hChild = ::GetNextSibling(hChild))
{
::GetWindowRect(hChild, &rcChild); // screen coordinates
//::ScreenToClient(hWnd, &rcChild); // to parent coordinates
::ScreenToClient(hWnd, (LPPOINT)&rcChild);
::ScreenToClient(hWnd, ((LPPOINT)&rcChild)+1);
size = ::OffsetRect(rcChild, size.cx, size.cy);
::MoveWindow(hChild, rcChild.left, rcChild.top, rcChild.Width(), rcChild.Height(), TRUE);
}
}
void CeChildMgr::ValidateChildBkgnd(HDC hDC, HWND hWnd)
{
HRGN rgn;
CeRect rcClip;
TCHAR szClass[64];
// Because of bugs in the Windows GDI in ExcludeClipRect() specifically
// that cause the clipping box to be truncated to the size of
// the physical device coordinates, we need to build the update region
// by hand, we do this by creating an update region from the clipping rectangle
// set in the DC as passed to me (which we assume us accurate at this point, which it should be
// if somebody didn't call ExcludeClipRect() on it.
// Then I difference out the child controls from the update region, this appears to work properly
// in all circumstances.
GetClipBox(hDC, rcClip);
rgn = CreateRectRgnIndirect(rcClip);
//SelectClipRgn(hDC, &rgn, RGN_COPY);
SelectClipRgn(hDC, rgn);
// Iterate through all the children and exclude them from the
// DCs clipping rectangle
for (HWND hChild = ::GetFirstChild(hWnd); hChild; hChild = ::GetNextSibling(hChild))
{
// skip windows that aren't visible
if (! IsWindowVisible(hChild))
continue;
// Doesn't mean anything, could be that the child is disabled from input
// but contains meaningful data
//ASSERT(pChild->IsWindowEnabled());
::GetClassName(hChild, szClass, 64);
// skip group boxes, they rely on being hollow
// and on the parent painting it's background
// also
if (_tcsicmp(szClass, _T("BUTTON")) || (GetWindowStyle(hChild) & 0xF) != BS_GROUPBOX)
{
CeRect rcChild;
::GetWindowRect(hChild, &rcChild); // screen coordinates
//::ScreenToClient(hWnd, &rcChild); // to parent coordinates
::ScreenToClient(hWnd, (LPPOINT)&rcChild);
::ScreenToClient(hWnd, ((LPPOINT)&rcChild)+1);
// the old way, before I was required to use regions
//pDC->ExcludeClipRect(rectChild);
// exclude it from painting
SetRectRgn(rgn, rcChild.left, rcChild.top, rcChild.right, rcChild.bottom);
//SelectClipRgn(&hDC, rgn, RGN_DIFF);
SelectClipRgn(hDC, rgn);
}
}
}
// Adjusts the parent rectangle, useful for things like
// adding dialog bars to the parents client area
// and then adjust the controls with fixed distances
// to the parents borders
void CeChildMgr::AdjustArea(const SIZE& sizeDelta, BOOL bTopLeft)
{
int nMax = m_arrayInfo.GetSize();
if (nMax <= 0)
// nothing managed
return;
// expand or shrink client area managed
m_rcOriginal.right += sizeDelta.cx;
m_rcOriginal.bottom += sizeDelta.cy;
if (!bTopLeft)
return;
// Walk all controls in collection.
for (int ii = 0; ii < nMax; ii++)
{
CeChildInfo *pInfo = &(m_arrayInfo[ii]);
if (pInfo->m_hWndChild)
pInfo->m_rcOriginal.OffsetRect(sizeDelta);
if (pInfo->m_hWndLabel)
pInfo->m_rcLabel.OffsetRect(sizeDelta);
if (pInfo->m_hWndSpinner)
pInfo->m_rcSpinner.OffsetRect(sizeDelta);
}
}