Click here to Skip to main content
15,886,110 members
Articles / Desktop Programming / MFC

ClassLib, A C++ class library

Rate me:
Please Sign up or sign in to vote.
4.80/5 (32 votes)
25 May 2005CPOL8 min read 399.6K   11.5K   141  
C++ class library.
//
// layout.cpp
//
// (C) Copyright 2000 Jan van den Baard.
//     All Rights Reserved.
//

#include "layout.h"
#include "../gdi/getdc.h"
#include "../gdi/selector.h"
#include "../exceptions/memoryexception.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// Scale a size.
#define ScaleWeight( s, f, w ) (( s * w ) / f )

// Special messages.
#define WM_ISLAYOUTENGINE	WM_APP+1 // Is it a layout engine?
#define WM_RELAYOUT		WM_APP+2 // Re-layout members.
#define WM_ORGANIZE		WM_APP+3 // Organize members.
#define WM_GETCONTROLS		WM_APP+4 // Append members.
#define WM_SHOWMEMBERS		WM_APP+5 // Show or hide members.
#define WM_ERRORS		WM_APP+6 // Any errors?
#define WM_REFRESH		WM_APP+7 // Refresh the group.

// Constructor. Set the defaults.
ClsLayoutEngine::ClsLayoutEngine()
{
	// Setup defaults.
	m_nSpacing		= 4;
	m_rcOffsets.Left()	= m_rcOffsets.Top() = m_rcOffsets.Right() = m_rcOffsets.Bottom() = 4;
	m_bIsHorizontal		= TRUE;
	m_bFrameGroup		= FALSE;
	m_dwFlags		= 0;
	m_nFontHeight		= 0;
}

// Destructor.
ClsLayoutEngine::~ClsLayoutEngine()
{
	// Destroy all members.
	ClsMember *pMember;
	while (( pMember = m_Members.RemoveHead()) != NULL )
		delete pMember;
}

// Scale the group.
void ClsLayoutEngine::ScaleGroup( UINT nEffSize )
{
	// Are we horizontal?
	DWORD dwBit = m_bIsHorizontal ? ClsMember::MF_FIXEDWIDTH : ClsMember::MF_FIXEDHEIGHT;

	// First loop to get all scaled sizes.
	ClsMember *pMember;
	UINT nTotalSize = 0, nSize;

	for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
	{
		// Get minimum size.
		nSize = m_bIsHorizontal ? pMember->m_nMinWidth : pMember->m_nMinHeight;

		// Skip fixed sized objects.
		if (( pMember->m_dwFlags & dwBit ) != dwBit ) 
		{
			// Find out scaled size. This may
			// not be smaller than the object
			// it's minimum size.
			if (( pMember->m_nScaledSize = ScaleWeight( nEffSize, m_nFullWeight, pMember->m_nWeight )) < nSize )
				pMember->m_nScaledSize = nSize;

			// Keep track of the total size.
			nTotalSize += pMember->m_nScaledSize;
		}
	}

	// Constantly adjust the size of
	// objects which where scaled larger
	// than their minimum size when the
	// total scaled size was larger than
	// the area the objects must fit in.
	BOOL bAdjust = TRUE;
	while (( nTotalSize > nEffSize ) && bAdjust ) 
	{
		// Set adjust flag to false.
		bAdjust = FALSE;

		// Iterate members.
		for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
		{
			// Get minimum size.
			nSize = m_bIsHorizontal ? pMember->m_nMinWidth : pMember->m_nMinHeight;

			// Skip fixed size objects.
			if (( pMember->m_dwFlags & dwBit ) != dwBit ) 
			{
				// Was the size bigger than the
				// minimum size?
                                if ( pMember->m_nScaledSize > nSize ) 
				{
					// Yes. adjust.
					pMember->m_nScaledSize--;
					nTotalSize--;
					bAdjust = TRUE;

					// Are we done yet?
					if ( nTotalSize <= nEffSize )
						return;
				}
			}
		}
	}
}

// Resize and reposition the group frame.
void ClsLayoutEngine::ResizeGroupFrame()
{
	// Get window bounds.
	ClsRect	rc = GetWindowRect();

	// Adjust for the offsets.
	rc.Left()	+= m_rcOffsets.Left();
	rc.Top()	+= m_rcOffsets.Top();
	rc.Right()	-= m_rcOffsets.Right();
	rc.Bottom()	-= m_rcOffsets.Bottom();

	// Map the points to the client.
	GetParent()->ScreenToClient( rc );

	// Resize and Reposition the frame.
	m_Frame.MoveWindow( rc, FALSE );
}

// Scale a horizontal group.
void ClsLayoutEngine::ScaleHGroup()
{
	// Find out the group domain.
	ClsRect	rc = GetWindowRect();

        // Calculate offsets.
	UINT nLeftOff = rc.Left() + m_rcOffsets.Left();
        UINT nTopOff  = rc.Top()  + m_rcOffsets.Top();

	// Calculate effective width and height
	// in which the objects are scaled.
	UINT nEffWidth  = ( rc.Width()  - ((( m_Members.GetSize() - 1 ) * m_nSpacing ) + m_rcOffsets.Left() + m_rcOffsets.Right()));
	UINT nEffHeight = ( rc.Height() - ( m_rcOffsets.Top() + m_rcOffsets.Bottom()));

	// Decrease the effective width
	// with the total fixed width.
	nEffWidth -= m_nFixedSize;

	// Any framing?
	if ( m_bFrameGroup && m_Frame.GetSafeHWND())
	{
		// Reposition and resize the
		// frame object.
		ResizeGroupFrame();

		// Adjust offsets.
		nLeftOff += ::GetSystemMetrics( SM_CXEDGE ) + m_nSpacing;
		nTopOff  += m_nFontHeight + m_nSpacing;

		// And sizes.
		nEffWidth  -= ( ::GetSystemMetrics( SM_CXEDGE ) * 2 ) + ( m_nSpacing * 2 );
		nEffHeight -= ::GetSystemMetrics( SM_CYEDGE ) + m_nFontHeight + ( m_nSpacing * 2 );
	}

	// Scale objects.
	ScaleGroup( nEffWidth );

	// Iterate members.
	ClsMember *pMember;
	UINT nMemberWidth, nMemberHeight, nTotal = 0;
	for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
	{
		// Calculate object size.
		if ( pMember->m_dwFlags & ClsMember::MF_FIXEDWIDTH ) nMemberWidth = pMember->m_nFixedWidth;
		else 
		{
			nMemberWidth = pMember->m_nScaledSize;
			nTotal += nMemberWidth;
		}
		nMemberHeight = ( pMember->m_dwFlags & ClsMember::MF_FIXEDHEIGHT ) ? pMember->m_nFixedHeight : nEffHeight;

		// Adjust when necessary. This may get a somewhat ugly
		// result in some cases.
		if ( pMember == m_Members.GetLast()) 
		{
			// Was the member scaled to large to fit into the area?
			if ( nTotal > nEffWidth ) nMemberWidth -= ( nTotal - nEffWidth );
			else
			{
				// If the member width is not fixed we stretch it to
				// fill the area.
				if ( ! ( pMember->m_dwFlags & ClsMember::MF_FIXEDWIDTH )) 
				{
					if ( nTotal < nEffWidth )
						nMemberWidth += ( nEffWidth - nTotal );
				}
			}
		}

		// Spacing member?
		if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING ) 
		{
			// Setup object position/dimensions.
			ClsRect points( nLeftOff, nTopOff, nLeftOff + nMemberWidth, nTopOff + nMemberHeight );

			// Extra lines?
			if ( pMember->m_nExtraLines ) 
			{
				if ( pMember->m_nLineSize == 0 )
				{
					// Compute the line size.
					ClsGetDC dc( pMember->m_pMember );
					ClsSelector sel( &dc, ( HFONT )pMember->m_pMember->SendMessage( WM_GETFONT )); 
					ClsSize sz = dc.GetTextExtent( _T( "W" ), 1 );
					pMember->m_nLineSize = sz.CY();
				}

				// OK?
				if ( pMember->m_nLineSize )
					// Add extra height.
					points.Bottom() += ( pMember->m_nExtraLines * pMember->m_nLineSize ) + 4;
			}

			// Convert the screen coordinates
			// to the target client.
			GetParent()->ScreenToClient( points );

			// Layout the member.
			pMember->m_pMember->MoveWindow( points, FALSE );

			// If the member is a layout engine
			// tell it to relayout it's members.
			if ( pMember->m_pMember->SendMessage( WM_ISLAYOUTENGINE ))
				pMember->m_pMember->SendMessage( WM_RELAYOUT );
		}

		// Adjust left offset.
		nLeftOff += ( nMemberWidth + m_nSpacing );
	}
}

// Scale a vertical group.
void ClsLayoutEngine::ScaleVGroup()
{
	// Find out the group domain.
	ClsRect rc = GetWindowRect();

	// Calculate offsets.
	UINT nLeftOff = rc.Left() + m_rcOffsets.Left();
	UINT nTopOff  = rc.Top()  + m_rcOffsets.Top();

	// Calculate the effective height
	// in which the objects are scaled.
	UINT nEffHeight = ( rc.Height() - ((( m_Members.GetSize() - 1 ) * m_nSpacing ) + m_rcOffsets.Top() + m_rcOffsets.Bottom()));
	UINT nEffWidth  = ( rc.Width()  - ( m_rcOffsets.Left() + m_rcOffsets.Right()));

	// Decrease the effective height with
	// the total fixed height.
	nEffHeight -= m_nFixedSize;

	// Any framing?
	if ( m_bFrameGroup && m_Frame.GetSafeHWND()) 
	{
		// Reposition and resize the
		// frame object.
		ResizeGroupFrame();

		// Adjust offsets.
		nLeftOff += ::GetSystemMetrics( SM_CXEDGE ) + m_nSpacing;
		nTopOff  += m_nFontHeight + m_nSpacing;

		// And sizes.
		nEffWidth  -= ( ::GetSystemMetrics( SM_CXEDGE ) * 2 ) + ( m_nSpacing * 2 );
		nEffHeight -= ::GetSystemMetrics( SM_CYEDGE ) + m_nFontHeight + ( m_nSpacing * 2 );
	}

        // Scale the objects.
        ScaleGroup( nEffHeight );

	// Iterate members.
	ClsMember *pMember;
	UINT nMemberWidth, nMemberHeight, nTotal = 0;
	for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
	{
		// Calculate object dimensions.
                nMemberWidth = ( pMember->m_dwFlags & ClsMember::MF_FIXEDWIDTH  ) ? pMember->m_nFixedWidth  : nEffWidth;
		if ( pMember->m_dwFlags & ClsMember::MF_FIXEDHEIGHT ) nMemberHeight = pMember->m_nFixedHeight;
		else 
		{
                        nMemberHeight = pMember->m_nScaledSize;
                        nTotal += nMemberHeight;
                }

		// Adjust when necessary. This may get a somewhat ugly
		// result in some cases.
		if ( pMember == m_Members.GetLast()) 
		{
			// Was the member scaled to large?
			if ( nTotal > nEffHeight ) nMemberHeight -= ( nTotal - nEffHeight );
			else
			{
				// If the member does not have a fixed height we stretch
				// it to fill the area.
				if ( ! ( pMember->m_dwFlags & ClsMember::MF_FIXEDHEIGHT )) 
				{
					if ( nTotal < nEffHeight )
						nMemberHeight += ( nEffHeight - nTotal );
				}
			}
		}

		// Align (if necessary).
		UINT nLeftAdjust;
		if ( pMember->m_dwFlags & ClsMember::MF_ALIGN ) nLeftAdjust = max( nEffWidth - nMemberWidth, 0 );
		else						nLeftAdjust = 0;
		
		// Spacing member?
		if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING ) 
		{
			// Setup object.
			ClsRect points( nLeftOff + nLeftAdjust, nTopOff, nLeftOff + nLeftAdjust + nMemberWidth, nTopOff + nMemberHeight );

			// Extra lines?
			if ( pMember->m_nExtraLines ) 
			{
				if ( pMember->m_nLineSize == 0 )
				{
					// Compute the line size.
					ClsGetDC dc( pMember->m_pMember );
					ClsSelector sel( &dc, ( HFONT )pMember->m_pMember->SendMessage( WM_GETFONT )); 
					ClsSize sz = dc.GetTextExtent( _T( "W" ), 1 );
					pMember->m_nLineSize = sz.CY();
				}

				// OK?
				if ( pMember->m_nLineSize ) 
					// Add extra height.
					points.Bottom() += ( pMember->m_nExtraLines * pMember->m_nLineSize ) + 4;
			}

			// Convert the screen coordinates 
			// to the target client.
			GetParent()->ScreenToClient( points );

			// Layout the member.
			pMember->m_pMember->MoveWindow( points, FALSE );

			// If this member is a layout engine force
			// it to re-layout it's members.
			if ( pMember->m_pMember->SendMessage( WM_ISLAYOUTENGINE ))
				pMember->m_pMember->SendMessage( WM_RELAYOUT );
		}
	        
		// Increase top offset.
		nTopOff += ( nMemberHeight + m_nSpacing );
	}
}

// Pickup the largest minimum width and height.
BOOL ClsLayoutEngine::MaxMinSize()
{
	// Clear the values.
	m_nMaxMinWidth  = 0;
	m_nMaxMinHeight = 0;

	// Iterate the members.
	ClsMember *pMember;
	for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
	{
		// Ask minimum dimensions.
		ClsSize szMinSize( 0, 0 );

		// Only on non-spacing members.
		if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING ) 
		{
			// Use the current control size?
			if ( pMember->m_dwFlags & ClsMember::MF_CONTROLSIZE )
			{
				// Do we already know the design size?
				if ( pMember->m_dwFlags & ClsMember::MF_GOTSIZE )
				{
					// Yes. Simple copy these values.
					szMinSize.CX() = pMember->m_nDesignWidth;
					szMinSize.CY() = pMember->m_nDesignHeight;
				}
				else
				{
					// Get the control size.
					ClsRect rc = pMember->m_pMember->GetWindowRect();
					szMinSize = rc.Size();

					// Save these values for later use.
					pMember->m_nDesignWidth  = szMinSize.CX();
					pMember->m_nDesignHeight = szMinSize.CY();

					// We got the values...
					pMember->m_dwFlags |= ClsMember::MF_GOTSIZE;
				}
			}
			// Call the OnGetMinSize() overidable.
			else if ( pMember->m_pMember->OnGetMinSize( szMinSize ) == FALSE )
				return FALSE;
		}

		// Setup values.
		pMember->m_nMinWidth  = szMinSize.CX();
		pMember->m_nMinHeight = szMinSize.CY();

		// Clear fixed sizes for non fixed size
		// members.
		if ( pMember->m_dwFlags & ClsMember::MF_ASKMIN )
			pMember->m_nFixedWidth = pMember->m_nFixedHeight = 0;

		// Skip spacing objects in this count.
		if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING ) 
		{
			// Make sure the fixed sizes are not
			// smaller than allowed.
			if ( pMember->m_dwFlags & ClsMember::MF_FIXEDWIDTH  ) 
			{
				if ( pMember->m_nMinWidth > pMember->m_nFixedWidth ) pMember->m_nFixedWidth = pMember->m_nMinWidth;
				else						     pMember->m_nMinWidth   = pMember->m_nFixedWidth;
			}
			if ( pMember->m_dwFlags & ClsMember::MF_FIXEDHEIGHT ) 
			{
				if ( pMember->m_nMinHeight > pMember->m_nFixedHeight ) pMember->m_nFixedHeight = pMember->m_nMinHeight;
				else						       pMember->m_nMinHeight   = pMember->m_nFixedHeight;
			}

			// Keep track of the largest minimum member
			// width and height.
			if ( pMember->m_nMinWidth  > m_nMaxMinWidth  ) m_nMaxMinWidth  = pMember->m_nMinWidth;
			if ( pMember->m_nMinHeight > m_nMaxMinHeight ) m_nMaxMinHeight = pMember->m_nMinHeight;
		}
	}
	// Success.
	return TRUE;
}

// Calculate the minumum size of a
// horizontal group.
BOOL ClsLayoutEngine::GroupHMinSize( ClsSize& szMinSize )
{
	// Reset values.
	m_nFullWeight = m_nFixedSize = 0;

	// Query minimum sizes.
	if ( MaxMinSize() == FALSE )
		return FALSE;

	// Scan through the members.
	ClsMember *pMember;
	UINT nWidth = 0, nHeight = 0;
	for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
	{
		// Is this a spacing member?
		if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING ) 
		{
			// No. If this group has equal widths we set the
			// witdth of the member to the group width.
			if ( m_dwFlags & LF_EQUALWIDTH )
				pMember->m_nMinWidth = m_nMaxMinWidth;
		}

		// Does the member have fixed minimum dimensions?
		if (( pMember->m_dwFlags & ClsMember::MF_ASKMIN ) != ClsMember::MF_ASKMIN ) 
		{
			// No. Is it a fixed width object?
			if ( pMember->m_dwFlags & ClsMember::MF_FIXEDWIDTH )
				// Increase the group it's total fixed width.
				m_nFixedSize += pMember->m_nFixedWidth;
			else 
			{
				// Increase the groups width and the total
				// weight.
				nWidth	      += pMember->m_nMinWidth;
				m_nFullWeight += pMember->m_nWeight;
			}
		} 
		else 
		{
			// Is it a fixed width member?
			if ( pMember->m_dwFlags & ClsMember::MF_FIXEDWIDTH ) 
			{
				// Increase the total fixed width by the member
				// it's minimum width.
				m_nFixedSize	      += pMember->m_nMinWidth;
				pMember->m_nFixedWidth = pMember->m_nMinWidth;
			}
			else
			{
				// Increase the group width and the total weight.
				nWidth	      += pMember->m_nMinWidth;
				m_nFullWeight += pMember->m_nWeight;
			}
		}

		// Does the member have fixed minimum dimensions?
		if (( pMember->m_dwFlags & ClsMember::MF_ASKMIN ) != ClsMember::MF_ASKMIN ) 
		{
			// Is it a fixed height object?
			if ( pMember->m_dwFlags & ClsMember::MF_FIXEDHEIGHT ) 
			{
				// Store the member height when it is larger than
				// the height sofar.
				if ( pMember->m_nFixedHeight > nHeight )
					nHeight = pMember->m_nFixedHeight;
			} 
			else 
			{
				// Store the member minimum height when it is larger
				// than the height sofar.
				if ( pMember->m_nMinHeight > nHeight )
					nHeight = pMember->m_nMinHeight;
			}
		} 
		else 
		{
			// Is it a fixed height object?
			if ( pMember->m_dwFlags & ClsMember::MF_FIXEDHEIGHT )
				// Store the minimum height as the fixed height.
				pMember->m_nFixedHeight = pMember->m_nMinHeight;

			// Store the height when it is larger than
			// the height sofar.
			if ( pMember->m_nMinHeight > nHeight )
				nHeight = pMember->m_nMinHeight;
		}
	}

	// Add the offsets etc. to the sizes. The result will be the total
	// group minimum dimensions.
        nWidth  += m_nFixedSize + ( m_rcOffsets.Left() + m_rcOffsets.Right() + (( m_Members.GetSize() - 1 ) * m_nSpacing ));
        nHeight += ( m_rcOffsets.Top() + m_rcOffsets.Bottom());

	//	Adjust for the frame.
	if ( m_bFrameGroup && m_Frame.GetSafeHWND())
	{
		nWidth  += ( ::GetSystemMetrics( SM_CXEDGE ) * 2 ) + ( m_nSpacing * 2 );
		nHeight += ::GetSystemMetrics( SM_CYEDGE ) + ( m_nFontHeight + ( m_nSpacing * 2 ));
	}

	// Store the values.
	szMinSize.CX() = max( 1, nWidth );
        szMinSize.CY() = max( 1, nHeight );

	// Success...
	return TRUE;
}

// Calculate the minimum size of a
// vertical group.
BOOL ClsLayoutEngine::GroupVMinSize( ClsSize& szMinSize )
{
	// Reset values.
	m_nFixedSize = m_nFullWeight = 0;

	// Query minimum sizes.
	if ( MaxMinSize() == FALSE )
		return FALSE;

	// Iterate members.
	ClsMember *pMember;
	for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
	{
		// Is it a spacing member?
		if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING ) 
		{
			// Is it a equal height group?
			if ( m_dwFlags & LF_EQUALHEIGHT )
				// Make the group it's minimum height the
				// member it's minimum height.
				pMember->m_nMinHeight = m_nMaxMinHeight;
		}
	}

	// Iterate members.
	UINT nWidth = 0, nHeight = 0;
	for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
	{
		// Does the member have fixed minimum dimensions?
		if (( pMember->m_dwFlags & ClsMember::MF_ASKMIN ) != ClsMember::MF_ASKMIN) 
		{
			// Is it a fixed height member?
			if ( pMember->m_dwFlags & ClsMember::MF_FIXEDHEIGHT )
				// Increase the group it's fixed height.
				m_nFixedSize += pMember->m_nFixedHeight;
			else 
			{
				// Increase the group height and total weight.
				nHeight	      += pMember->m_nMinHeight;
				m_nFullWeight += pMember->m_nWeight;
			}
		} 
		else 
		{
			// Is it a fixed height member?
                        if ( pMember->m_dwFlags & ClsMember::MF_FIXEDHEIGHT ) 
			{
				// Make the fixed height the minimum height.
				m_nFixedSize	       += pMember->m_nMinHeight;
				pMember->m_nFixedHeight = pMember->m_nMinHeight;
			} 
			else 
			{
				// Increase the group height and total weight.
				nHeight	      += pMember->m_nMinHeight;
				m_nFullWeight += pMember->m_nWeight;
			}
		}

		// Does the member have fixed minimum dimensions?
                if (( pMember->m_dwFlags & ClsMember::MF_ASKMIN ) != ClsMember::MF_ASKMIN ) 
		{
			// Does it have a fixed width?
			if ( pMember->m_dwFlags & ClsMember::MF_FIXEDWIDTH ) 
			{
				// Store the width when it is larger than
				// the width sofar.
				if ( pMember->m_nFixedWidth > nWidth )
					nWidth = pMember->m_nFixedWidth;
			}
			else
			{
				// Store the width when it is larger than
				// the width sofar.
				if ( pMember->m_nMinWidth > nWidth )
					nWidth = pMember->m_nMinWidth;
			}
		}
		else
		{
			// Does it have a fixed width?
			if ( pMember->m_dwFlags & ClsMember::MF_FIXEDWIDTH )
				// Make the fixed width the minimum width.
				pMember->m_nFixedWidth = pMember->m_nMinWidth;

			// Store the width when it is larger than the width
			// sofar.
			if ( pMember->m_nMinWidth > nWidth )
				nWidth = pMember->m_nMinWidth;
		}
	}

	// Add the offsets etc. to the sizes. The result will be the total
	// group minimum dimensions.
	nHeight += m_nFixedSize + ( m_rcOffsets.Top() + m_rcOffsets.Bottom() + (( m_Members.GetSize() - 1 ) * m_nSpacing ));
	nWidth  += ( m_rcOffsets.Left() + m_rcOffsets.Right());

	// Adjust for the frame.
	if ( m_bFrameGroup && m_Frame.GetSafeHWND())
	{
		nWidth  += ( ::GetSystemMetrics( SM_CXEDGE )) * 2 + ( m_nSpacing * 2 );
		nHeight += ::GetSystemMetrics( SM_CYEDGE ) + ( m_nFontHeight + ( m_nSpacing * 2 ));
	}

	// Store the values.
	szMinSize.CX() = max( 1, nWidth  );
	szMinSize.CY() = max( 1, nHeight );

	// Success...
	return TRUE;
}

// Compute the group it's minimum dimensions.
BOOL ClsLayoutEngine::OnGetMinSize( ClsSize& szMinSize )
{
	BOOL	bRC;

	// Get the minimum width and height
	// of this group.
	if ( m_bIsHorizontal ) bRC = GroupHMinSize( szMinSize );
	else		       bRC = GroupVMinSize( szMinSize );

	// We have been dimensioned.
	if ( bRC )
		m_dwFlags |= LF_DIMENSIONED;

	// Return error flag.
	return bRC;
}

// Show or hide members.
void ClsLayoutEngine::ShowMembers( BOOL bShow /* = TRUE */ )
{
	_ASSERT_VALID( GetSafeHWND());

	// Call the show window message.
	SendMessage( WM_SHOWMEMBERS, bShow );
}

// Create the control.
BOOL ClsLayoutEngine::Create( ClsWindow& parent, DWORD dwTag, ... )
{
	// Tag/Data pairs...
	struct TagItem { DWORD dwTag; DWORD dwData; } *pArgs = ( TagItem * )&dwTag;
	LPCTSTR	pszName = NULL;
	BOOL bFrame = FALSE;

	// Evaluate arguments.
	while ( pArgs->dwTag != TAG_END )
	{
		switch ( pArgs->dwTag )
		{
			case	LAYOUT_Title:
				pszName = ( LPCTSTR )pArgs->dwData;
				break;

			case	LAYOUT_Horizontal:
				m_bIsHorizontal = ( BOOL )pArgs->dwData;
				break;

			case	LAYOUT_FrameGroup:
				bFrame = ( BOOL )pArgs->dwData;
				break;

			case	LAYOUT_Spacing:
				m_nSpacing = pArgs->dwData;
				break;

			case	LAYOUT_LeftOffset:
				m_rcOffsets.Left() = pArgs->dwData;
				break;

			case	LAYOUT_TopOffset:
				m_rcOffsets.Top() = pArgs->dwData;
				break;

			case	LAYOUT_RightOffset:
				m_rcOffsets.Right() = pArgs->dwData;
				break;

			case	LAYOUT_BottomOffset:
				m_rcOffsets.Bottom() = pArgs->dwData;
				break;

			case	LAYOUT_EqualMinWidth:
				if ( pArgs->dwData ) m_dwFlags |= LF_EQUALWIDTH;
				else		     m_dwFlags &= ~LF_EQUALWIDTH;
				break;

			case	LAYOUT_EqualMinHeight:
				if ( pArgs->dwData ) m_dwFlags |= LF_EQUALHEIGHT;
				else		     m_dwFlags &= ~LF_EQUALHEIGHT;
				break;

			case	LAYOUT_Master:
				if ( pArgs->dwData ) m_dwFlags |= LF_MASTER;
				else		     m_dwFlags &= ~LF_MASTER;
				break;
		}
		// Next...
		pArgs++;
	}
			
	// Create the control.
	if ( ClsWindow::Create( 0, NULL, pszName, WS_CHILD, ClsRect( 0, 0, 0, 0 ), parent, ( HMENU )0 ))
	{
		// Create frame?
		if ( bFrame )
			FrameGroup( bFrame );
		return TRUE;
	}
	return FALSE;
}

// Set or clear the group frame.
void ClsLayoutEngine::FrameGroup( BOOL bFrame /* = TRUE */ )
{
	// Did it change?
	if ( bFrame != m_bFrameGroup )
	{
		// Set the flag.
		m_bFrameGroup = bFrame;

		// Turn it on?
		if ( bFrame )
		{
			// Get the frame title.
			ClsString str( *this );

			// Create the frame control.
			if ( m_Frame.Create( GetParent(), ClsRect( 0, 0, 0, 0 ), 0, str, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | BS_GROUPBOX ))
			{
				// Change extended style.
				m_Frame.ModifyExStyle( 0, WS_EX_TRANSPARENT );

				// Setup it's font.
				if ( m_Font.IsValid())
					m_Frame.SetFont( &m_Font, FALSE );
			}
		}
		else
			m_Frame.Destroy();
	}
}

// Re-layout the group.
void ClsLayoutEngine::Relayout()
{
	// Are we the master?
	if ( m_dwFlags & LF_MASTER )
		SendMessage( WM_RELAYOUT );
}

// Refresh the group.
void ClsLayoutEngine::Refresh()
{
	// Are we the master?
	if ( m_dwFlags & LF_MASTER )
		SendMessage( WM_REFRESH );
}

// Add a member to the group.
BOOL ClsLayoutEngine::AddMember( ClsWindow *pLMember, ClsWindow *pPred, DWORD dwTag, ... )
{
	_ASSERT( pLMember != this ); // We can't be added to ourselves.
	_ASSERT( pPred != this ); // We can't be the predecessor.

	// Tag/Data pairs...
	struct TagItem { DWORD dwTag; DWORD dwData; } *pArgs = ( TagItem * )&dwTag;

	// Valid input?
	if ( pLMember->GetSafeHWND() == NULL )
	{
		// Set error flag.
		m_dwFlags |= LF_ADDERROR;
		return FALSE;
	}

	try
	{
		// Allocate a member structure.
		ClsMember *pMember = new ClsMember;

		// Setup fields.
		pMember->m_pMember = pLMember;
		pMember->m_nWeight = DEFAULT_WEIGHT;

		// Did we add a layout enigine with en error?
		if ( pLMember->SendMessage( WM_ISLAYOUTENGINE ))
		{
			// Set error bit if necessary...
			if ( pLMember->SendMessage( WM_ERRORS ))
				m_dwFlags |= LF_ADDERROR;
		}

		// Evaluate attributes.
		while ( pArgs->dwTag != TAG_END )
		{
			switch ( pArgs->dwTag )
			{
				case	ATTR_UseControlSize:
					if ( pArgs->dwData )
						pMember->m_dwFlags |= ClsMember::MF_CONTROLSIZE;
					break;

				case	ATTR_FixMinSize:
					if ( pArgs->dwData )
						pMember->m_dwFlags |= ClsMember::MF_ASKMIN | ClsMember::MF_FIXEDWIDTH | ClsMember::MF_FIXEDHEIGHT;
					break;

				case	ATTR_FixWidth:
					if ( pArgs->dwData )
					{
						pMember->m_dwFlags    &= ~ClsMember::MF_ASKMIN;
						pMember->m_dwFlags    |= ClsMember::MF_FIXEDWIDTH;
						pMember->m_nFixedWidth = pArgs->dwData;
					}
					break;

				case	ATTR_FixHeight:
					if ( pArgs->dwData ) 
					{
						pMember->m_dwFlags     &= ~ClsMember::MF_ASKMIN;
						pMember->m_dwFlags     |= ClsMember::MF_FIXEDHEIGHT;
						pMember->m_nFixedHeight = pArgs->dwData;
					}
					break;

				case	ATTR_Weight:
					if (( pMember->m_nWeight = pArgs->dwData ) == 0 )
						pMember->m_nWeight = DEFAULT_WEIGHT;
					break;

				case	ATTR_FixMinWidth:
					if ( pArgs->dwData )
						pMember->m_dwFlags |= ClsMember::MF_ASKMIN | ClsMember::MF_FIXEDWIDTH;
					break;

				case	ATTR_FixMinHeight:
					if ( pArgs->dwData )
						pMember->m_dwFlags |= ClsMember::MF_ASKMIN | ClsMember::MF_FIXEDHEIGHT;
					break;

				case	ATTR_RightAlign:
					if ( pArgs->dwData )
						pMember->m_dwFlags |= ClsMember::MF_ALIGN;
					break;

				case	ATTR_ExtraLines:
					pMember->m_nExtraLines = pArgs->dwData;
					break;
			}
			// Next...
			pArgs++;
		}

		// Predecessor?
		if ( pPred == WND_HEAD )
			// Add it to the member list.
			m_Members.AddHead( pMember );
		else if ( pPred )
		{
			// Look up the predecessor entry.
			ClsMember *p;
			for ( p = m_Members.GetFirst(); p; p = m_Members.GetNext( p ))
			{
				// Is this the one?
				if ( p->m_pMember == pPred )
				{
					m_Members.Insert( pMember, p );
					break;
				}
			}

			// Was it added?
			if ( p == NULL )
				// AddTail() it...
				m_Members.AddTail( pMember );
		}
		else
			// Add it to the member list.
			m_Members.AddTail( pMember );

		// No longer dimensioned/organized.
		m_dwFlags &= ~( LF_ORGANIZED | LF_DIMENSIONED );

		return TRUE;
	}
	catch( ClsMemoryException& me )
	{
		// Setup error flag.
		m_dwFlags |= LF_ADDERROR;
		me;
	}
	return FALSE;
}

// Add a spacing member.
BOOL ClsLayoutEngine::AddSpacingMember( UINT nWeight /* = DEFAULT_WEIGHT */ )
{
	try
	{
		// Allocate member node.
		ClsMember *pMember = new ClsMember;

		// Setup member weight.
		pMember->m_nWeight = nWeight ? nWeight : DEFAULT_WEIGHT;

		// Make us known as a spacing member.
		pMember->m_dwFlags |= ClsMember::MF_SPACING;

		// Add the member to the list.
		m_Members.AddTail( pMember );

		// No longer dimensioned/organized.
		m_dwFlags &= ~( LF_DIMENSIONED | LF_ORGANIZED );
		return TRUE;
	}
	catch( ClsMemoryException& me )
	{
		// Set the error flag.
		m_dwFlags |= LF_ADDERROR;
		me;
	}
	// Return failure.
	return FALSE;
}

// Remove a member from the list.
BOOL ClsLayoutEngine::RemMember( ClsWindow *pRemove )
{
	_ASSERT( pRemove != this ); // Can't be us.

	// Look for the member.
	ClsMember *pMember;
	for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
	{
		// Is this the one?
		if ( pMember->m_pMember == pRemove ) 
		{
			// Yes. Remove the node.
			m_Members.Remove( pMember );

			// Free it.
			delete pMember;

			// Hide the window.
			pRemove->ShowWindow( SW_HIDE );

			// No longer dimensioned/organized.
			m_dwFlags &= ~( LF_DIMENSIONED | LF_ORGANIZED );

			// Return success.
			return TRUE;
		}
	}
	return FALSE;
}

// Get group controls.
void ClsLayoutEngine::GetControls( ClsLinkedList<ClsCNode> *pList )
{
	// Get member controls.
	ClsMember *pMember;
	for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
	{
		// Spacing member?
		if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING ) 
		{
			// Layout control?
			if ( pMember->m_pMember->SendMessage( WM_ISLAYOUTENGINE ))
				// Yes. Tell it to add it's members.
				pMember->m_pMember->SendMessage( WM_GETCONTROLS, 0, ( LPARAM )pList );
			else
			{
				// No. Simply add control.
				ClsCNode *pNode = new ClsCNode;
				pNode->m_hWnd = pMember->m_pMember->GetSafeHWND();
				pList->AddTail( pNode );
			}
		}
	}
}

// Organize the members. This will re-organize the
// controls Z-order so that the tab-order goes from
// top-left to right bottom.
void ClsLayoutEngine::OrganizeMembers()
{
	// Storage for the members.
	ClsLinkedList<ClsCNode> list;

	// Are we already organized?
	if (( m_dwFlags & LF_ORGANIZED ) != LF_ORGANIZED )
	{
		// No. Get all member controls.
		GetControls( &list );

		// Organize the controls.
		ClsCNode *pNode;
		HWND hPrev = NULL;
		for ( pNode = list.GetFirst(); pNode; pNode = list.GetNext( pNode )) 
		{
			// Setup Z order.
			::SetWindowPos( pNode->m_hWnd, hPrev ? hPrev : HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE );

			// Save predecessor.
			hPrev = pNode->m_hWnd;
		}

		// Free nodes.
		while (( pNode = list.RemoveHead()) != NULL )
			delete pNode;
			
		// We are organized.
		m_dwFlags |= LF_ORGANIZED;
	}
}

// Window procedure override. Handles layout engine private messages.
LRESULT ClsLayoutEngine::WindowProc( UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	// Interpred message.
	switch ( uMsg )
	{
		case	WM_CREATE:
		{
			// Get the default font height used
			// by the layout control. This information
			// is needed if the control font is not
			// set.
			ClsGetDC dc( this );
			ClsSize sz = dc.GetTextExtent( _T( "W" ), 1 );
			m_nFontHeight = sz.CY();
			break;
		}

		case	WM_GETFONT:
			// Valid font?
			return ( LRESULT )( m_Font.IsValid() ? ( HFONT )m_Font : NULL );

		case	WM_SETFONT:
		{
			// Detach old font.
			m_Font.Detach();

			// Valid?
			if ( wParam )
			{
				// Attach the new font.
				m_Font.Attach(( HFONT )wParam, FALSE );

				// Set the font to the frame
				// if it exists.
				if ( m_Frame.GetSafeHWND())
					m_Frame.SetFont( &m_Font, FALSE );

				// Pass this message to all our members
				// when they do not have a font set.
				ClsMember *pMember;
				for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember ))
				{
					// Skip spacing members.
					if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING )
					{
						// Does it have a font?
						if ( pMember->m_pMember->SendMessage( WM_GETFONT ) == NULL )
							// No. Set it.
							pMember->m_pMember->SendMessage( WM_SETFONT, wParam, MAKELPARAM( FALSE, 0 ));
					}
				}

				// Pickup the font height.
				ClsGetDC dc(this);
				ClsSelector sel( &dc, ( HFONT )wParam );
				ClsSize sz = dc.GetTextExtent( _T( "W" ), 1 );

				// Store the height.
				m_nFontHeight = sz.CY();

				m_dwFlags &= ~LF_DIMENSIONED;
			}
			break;
		}

		case	WM_ISLAYOUTENGINE:
			// Yes, we really are a layout engine...
			return TRUE;

		case	WM_ORGANIZE:
			// Organize the members.
			m_dwFlags &= ~LF_ORGANIZED;
			OrganizeMembers();
			return TRUE;

		case	WM_GETCONTROLS:
			// Add members to the passed list.
			GetControls(( ClsLinkedList<ClsCNode> * )lParam );
			return TRUE;

		case	WM_ERRORS:
			// Return error bool.
			return ( LRESULT )(( BOOL )( m_dwFlags & LF_ADDERROR ));

		//case	WM_SIZE:
			// Resize the frame.
		//	if ( m_bFrameGroup && m_Frame.GetSafeHWND())
		//	{
		//		ClsRect rc;
		//		GetClientRect( rc );
		//		m_Frame.MoveWindow( rc, FALSE );
		//	}
		//	return 0;

		case	WM_RELAYOUT:
			// Group dimensioned? When ever a group contents
			// has changed we need to call the OnGetMinSize()
			// method before we re-layout. This is to make sure
			// the layout engine always works with the latest
			// information.
			if (( m_dwFlags & LF_DIMENSIONED ) != LF_DIMENSIONED ) 
			{
				ClsSize szMinSize( 0, 0 );

				// OK?
				if ( OnGetMinSize( szMinSize ) == FALSE )
					return FALSE;
			}

			// Is this the master group?
			if ( m_dwFlags & LF_MASTER ) 
			{
				// Organized?
				if (( m_dwFlags & LF_ORGANIZED ) != LF_ORGANIZED )
					OrganizeMembers();

				// Get the parent it's client rectangle.
				ClsRect rc = GetParent()->GetClientRect();

				// Re-size the group.
				MoveWindow( rc, FALSE );
			}

			// Scale the group members.
			if ( m_bIsHorizontal ) ScaleHGroup();
			else		       ScaleVGroup();

			// Master group?
			if ((( m_dwFlags & LF_MASTER ) == LF_MASTER ) && GetParent()->IsWindowVisible())
				// Force the parent to redraw.
				GetParent()->RedrawWindow( NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN );
			return TRUE;

		case	WM_SHOWMEMBERS:
		{
			// Show/Hide members.
			ClsMember *pMember;
			for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
			{
				// A spacing member?
				if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING )
				{
					if ( pMember->m_pMember->SendMessage( WM_ISLAYOUTENGINE )) pMember->m_pMember->SendMessage( WM_SHOWMEMBERS, wParam );
					else
					{
						pMember->m_pMember->ShowWindow( wParam ? SW_SHOW : SW_HIDE );
						if ( wParam ) pMember->m_pMember->UpdateWindow();
					}
				}
			}

			// Show/Hide the frame.
			if ( m_bFrameGroup && m_Frame.GetSafeHWND())
			{
				m_Frame.ShowWindow( wParam ? SW_SHOW : SW_HIDE );
				if ( wParam ) m_Frame.UpdateWindow();
			}
			return TRUE;
		}

		case	WM_REFRESH:
		{
			// Do longer dimensioned or organized...
			m_dwFlags &= ~( LF_ORGANIZED | LF_DIMENSIONED );

			// Refresh layout engine members.
			ClsMember *pMember;
			for ( pMember = m_Members.GetFirst(); pMember; pMember = m_Members.GetNext( pMember )) 
			{
				// A spacing member?
				if (( pMember->m_dwFlags & ClsMember::MF_SPACING ) != ClsMember::MF_SPACING )
				{
					// Is it a layout engine?
					if ( pMember->m_pMember->SendMessage( WM_ISLAYOUTENGINE )) 
						pMember->m_pMember->SendMessage( WM_REFRESH );
				}
			}

			// Are we the master?
			if ( m_dwFlags & LF_MASTER )
				// Layout the group.
				SendMessage( WM_RELAYOUT );
			return TRUE;
		}
	}

	// Call the base class.
	return ClsWindow::WindowProc( uMsg, wParam, lParam );
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
I have been programming for a hobby since 1985. I have started programming on the C= 64. After that I migrated to the C= Amiga which I traded in for a PC back in 1997 I believe. Back in 2000 I decided to lose a hobby and start developing software for a living.

Currently I am working mainly in developing software for building security and access control systems.

Comments and Discussions