/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/02/08
// RCS-ID:		$Id: sizerglyphs.cpp,v 1.7 2001/04/04 12:42:37 aleksandras Exp $
// Copyright:   (c) Aleksandras Gluchovas (alex@soften.ktu.lt)
// Licence:   	GPL (version 2.0 or later)
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "minimal.cpp"
    #pragma interface "minimal.cpp"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "sizerglyphs.h"


/***** Implementation for class BoxSizerNode *****/

IMPLEMENT_DYNAMIC_CLASS( BoxSizerNode, SizerNodeBase )


wxDMNode* BoxSizerNode::Clone( bool uniqueCopy, bool cloneChildren )
{
	BoxSizerNode* pCopy = new BoxSizerNode();

	AssignData( pCopy, uniqueCopy, cloneChildren );

	return pCopy;
}

struct LayoutItemInfo
{
	int   option;
	int   flag;
	int   border;
	XDim  prefSize;
	XRect bounds;
};

static void add_boder_envolpe( XDim& size, LayoutItemInfo& info )
{
	if ( info.flag & wxLEFT )
	
		size.width += info.border;

	if ( info.flag & wxRIGHT )
	
		size.width += info.border;

	if ( info.flag & wxTOP )
	
		size.height += info.border;

	if ( info.flag & wxBOTTOM )
	
		size.height += info.border;
}

static void set_bounds_to_item( WidgetNode& node, LayoutItemInfo& info, wxDC& dc )
{
	if ( info.flag & wxLEFT )
	{
		info.bounds.x += info.border;
		info.bounds.width -= info.border;
	}

	if ( info.flag & wxRIGHT )
	
		info.bounds.width -= info.border;

	if ( info.flag & wxTOP )
	{
		info.bounds.y += info.border;
		info.bounds.height -= info.border;
	}

	if ( info.flag & wxBOTTOM )
	
		info.bounds.height -= info.border;

	node.SetBounds( info.bounds, dc );
}

void BoxSizerNode::RecalcPreferredSize( wxDC& dc )
{
	// FIXME:: efficiency

	if ( mChildren.Count() == 0 )
	{
		mPrefSize.width = 30;
		mPrefSize.height = 30;
		return;
	}

	size_t i = 0;
	for( ; i != mChildren.Count(); ++i )
	{
		WidgetNode& node = *(WidgetNode*)mChildren[i];

		node.RecalcPreferredSize( dc );
	}

	bool vertical = IsVertical();

	mPrefSize.width  = 0;
	mPrefSize.height = 0;
	LayoutItemInfo info;

	for( i = 0; i != mChildren.Count(); ++i )
	{
		WidgetNode& node = *(WidgetNode*)mChildren[i];

		if ( !node.HasExtraProperties() )

			AddExtraPropertiesToChild( &node );

		GetLayoutProps( node, info.option, info.flag, info.border );

		XDim prefSize = node.GetPreferredSize();

		add_boder_envolpe( prefSize, info );

		if ( vertical )
		{
			mPrefSize.height += prefSize.height;
			mPrefSize.width  = wxMax( mPrefSize.width, prefSize.width );
		}
		else
		{
			mPrefSize.width  += prefSize.width;
			mPrefSize.height  = wxMax( mPrefSize.height, prefSize.height );
		}
	}
}

void BoxSizerNode::DoLayoutChildren( wxDC& dc )
{
	RecalcPreferredSize( dc );

	LayoutItemInfo* infos = new LayoutItemInfo[ mChildren.Count() ];

	int numStreched = 0;
	int fixedSpace  = 0;

	bool vertical = IsVertical();

	size_t i = 0;
	for( ; i != mChildren.Count(); ++i )
	{
		WidgetNode& node = *(WidgetNode*)mChildren[i];

		LayoutItemInfo& info = infos[i];

		if ( !node.HasExtraProperties() )

			AddExtraPropertiesToChild( &node );

		GetLayoutProps( node, info.option, info.flag, info.border );

		info.prefSize = node.GetPreferredSize();

		add_boder_envolpe( info.prefSize, info );

		if ( vertical )

			// limit sizes in advance (simplfies the alg.)

			info.prefSize.width = wxMin( mBounds.width, info.prefSize.width );
		else
			info.prefSize.height = wxMin( mBounds.height, info.prefSize.height );

		if ( info.option == 1 ) 
		
			++numStreched;
		else
		{
			if ( vertical )

				fixedSpace += info.prefSize.height;
			else
				fixedSpace += info.prefSize.width;
		}
	}

	int x = 0, y = 0;

	int strechedSize = 0, spaceTaken = 0;

	if ( numStreched > 0 )

		strechedSize = ( ( vertical ? mBounds.height : mBounds.width ) - fixedSpace ) 
					   / numStreched;

	for( i = 0; i != mChildren.Count(); ++i )
	{
		WidgetNode& node = *(WidgetNode*)mChildren[i];

		LayoutItemInfo& info = infos[i];

		if ( vertical )
		{
			if ( info.option == 1 )

				info.bounds.height = strechedSize;
			else
				info.bounds.height = info.prefSize.height;

			spaceTaken += info.bounds.height;

			if ( numStreched > 0 && i == mChildren.Count() - 1 )

				// assign the strech-round-off errors to the last element
				info.bounds.height += mBounds.height - spaceTaken;

			if ( info.flag & wxGROW )
			
				info.bounds.width = mBounds.width;
			else
			{
				info.bounds.width = info.prefSize.width;

				if ( info.flag & wxALIGN_RIGHT ) info.bounds.x = mBounds.width - info.prefSize.width;
				else
				if ( info.flag & wxALIGN_CENTRE ) info.bounds.x = ( mBounds.width - info.prefSize.width ) / 2;
				else
					info.bounds.x = 0;
			}

			info.bounds.y = y;

			y += info.bounds.height;

		}
		else
		{
			// otherwise, layout as horizontal-sizer does

			if ( info.option == 1 )

				info.bounds.width = strechedSize;
			else
				info.bounds.width = info.prefSize.width;

			spaceTaken += info.bounds.width;

			if ( numStreched > 0 && i == mChildren.Count() - 1 )

				// assign the round-off errors to the last element
				info.bounds.width += mBounds.width - spaceTaken;

			if ( info.flag & wxGROW )
			
				info.bounds.height = mBounds.height;
			else
			{
				info.bounds.height = info.prefSize.height;

				if ( info.flag & wxALIGN_BOTTOM ) info.bounds.y = mBounds.height - info.prefSize.height;
				else
				if ( info.flag & wxCENTRE ) info.bounds.y = ( mBounds.height - info.prefSize.height ) / 2;
				else
					info.bounds.y = 0;
			}

			info.bounds.x = x;

			x += info.bounds.width;
		}

		set_bounds_to_item( node, info, dc );
	}

	delete [] infos;

	/*
	for( size_t i = 0; i != mChildren.Count(); ++i )
	{
		WidgetNode& node = *(WidgetNode*)mChildren[i];

		XRect  bounds = node.GetBounds();
		XPoint relPos = node.GetRelativePos();

		bounds.x = mBounds.x + relPos.x;
		bounds.y = mBounds.y + relPos.y;

		node.SetBounds( bounds, dc );
	}
	*/
}

bool BoxSizerNode::IsVertical()
{
	return this->GetNodeAttrValue( "orient" ) == "wxVERTICAL";
}

/***** Implementation for class BoxSizerGlyph *****/

IMPLEMENT_DYNAMIC_CLASS( BoxSizerGlyph, WidgetGlyph )

void BoxSizerGlyph::Draw( wxDC& canvas)
{
	bool noHighlight = FALSE;

	if ( !mpNode->mpView->SizersShown() )
	{
		if ( !mpNode->IsSelected() )
		{
			size_t i = 0;
			for( ; i != mpNode->GetChildCount(); ++i )
			{
				if ( mpNode->GetChild(i)->IsSelected() )

					break;
			}

			// sizer's outline invisible when neither it,
			// nor any of it's direct children are selected

			noHighlight = i == mpNode->GetChildCount();
		}
	}
	// otherwise show all sizers anyway

	if ( noHighlight )
	{
		//wxBrush br( mpNode->GetBgColor(), wxSOLID );
		//canvas.SetBrush( br );
		//canvas.DrawRectangle( mPixBounds.x, mPixBounds.y, mPixBounds.width, mPixBounds.height );

	}
	else
	{
		wxPen pen = wxPen( mpNode->IsSelected()
						   ? *wxBLUE : *wxRED, 2, wxSOLID );

		canvas.SetPen( pen );

		int inset = 1;
		canvas.DrawLine( mPixBounds.x + inset, mPixBounds.y + inset, 
						 mPixBounds.x + mPixBounds.width - 1 - inset, mPixBounds.y + inset);

		canvas.DrawLine( mPixBounds.x + mPixBounds.width - 1 - inset, mPixBounds.y + inset,
						 mPixBounds.x + mPixBounds.width - 1 - inset, mPixBounds.y + mPixBounds.height - 1 - inset );

		canvas.DrawLine( mPixBounds.x + mPixBounds.width - 1 - inset, mPixBounds.y + mPixBounds.height - 1 - inset,
						 mPixBounds.x + inset, mPixBounds.y + mPixBounds.height - 1 - inset );

		canvas.DrawLine( mPixBounds.x + inset, mPixBounds.y + mPixBounds.height - 1 - inset,
						 mPixBounds.x + inset, mPixBounds.y + inset );
	}

	DrawGrid( canvas );
}

/***** Implementation for class BoxSizerGlyph *****/

IMPLEMENT_DYNAMIC_CLASS( SpacerGlyph, WidgetGlyph )

void SpacerGlyph::Draw( wxDC& canvas)
{
	if ( mpNode->mpView->SizersShown() )
	{
		wxPen pen( *wxWHITE, 2, wxSOLID );
		canvas.SetPen( pen );

		canvas.DrawLine( mPixBounds.x, mPixBounds.y, 
						 mPixBounds.x, mPixBounds.y );
		canvas.DrawLine( mPixBounds.x + mPixBounds.width, mPixBounds.y,
						 mPixBounds.x + mPixBounds.width, mPixBounds.y );
		canvas.DrawLine( mPixBounds.x + mPixBounds.width, mPixBounds.y + mPixBounds.height,
						 mPixBounds.x + mPixBounds.width, mPixBounds.y + mPixBounds.height );
		canvas.DrawLine( mPixBounds.x, mPixBounds.y + mPixBounds.height,
						 mPixBounds.x, mPixBounds.y + mPixBounds.height );
	}
}

/***** Implementation for class SpacerNode *****/

IMPLEMENT_DYNAMIC_CLASS( SpacerNode, WidgetNode )

void SpacerNode::RecalcPreferredSize( wxDC& dc )
{
	mPrefSize.width  = StrToInt( GetNodeAttrValue( "horiz_space" ) );
	mPrefSize.height = StrToInt( GetNodeAttrValue( "vert_space" ) );
}

wxDMNode* SpacerNode::Clone( bool uniqueCopy, bool cloneChildren )
{
	wxDMNode* pCopy = new SpacerNode();

	AssignData( pCopy, uniqueCopy, cloneChildren );

	return pCopy;
}

/***** Implementation for class SpacerNode *****/

IMPLEMENT_DYNAMIC_CLASS( GridSizerNode, SizerNodeBase )


GridSizerNode::GridSizerNode()

	: mRowHeights( NULL ),
	  mColWidths( NULL ),
	  mItemInfos( NULL )
{}

GridSizerNode::~GridSizerNode()
{
	CleanUp();
}

wxDMNode* GridSizerNode::Clone( bool uniqueCopy, bool cloneChildren )
{
	GridSizerNode* pCopy = new GridSizerNode();

	AssignData( pCopy, uniqueCopy, cloneChildren );

	return pCopy;
}

void GridSizerNode::RecalcPreferredSize( wxDC& dc )
{
	ExtractGridProps();
	CollectExtraProps();

	mRowHeights = new int[ mRows ];
	mColWidths  = new int[ mCols ];

	if ( mChildren.Count() == 0 )
	{
		mPrefSize.width  = 30;
		mPrefSize.height = 30;
		return;
	}

	size_t i = 0;
	for( ; i != mChildren.Count(); ++i )
	{
		WidgetNode& node = *(WidgetNode*)mChildren[i];

		node.RecalcPreferredSize( dc );
	}

	mPrefSize.width  = 0;
	mPrefSize.height = 0;

	int row = 0, col = 0, maxHeight = 0, maxWidth = 0;
	XDim sz;

	// calculate row-heights

	for( row = 0; row != mRows; ++row )
	{
		int rowHeight = 0;

		for( col = 0; col != mCols; ++col )
		{
			GetPrefSizeAt( col, row, sz );

			rowHeight = wxMax( rowHeight, sz.height );
		}

		if ( mIsFlex )
		{
			mPrefSize.height += rowHeight;
			mRowHeights[ row ] = rowHeight;

			if ( row != mRows - 1 ) mPrefSize.height += mVGap;
		}
		else
			maxHeight = wxMax( maxHeight, rowHeight );
	}

	// calculate column widths

	for( col = 0; col != mCols; ++col )
	{
		int colWidth = 0;

		for( row = 0; row != mRows; ++row )
		{
			GetPrefSizeAt( col, row, sz );

			colWidth = wxMax( colWidth, sz.width );
		}

		if ( mIsFlex )
		{
			mPrefSize.width += colWidth;
			mColWidths[ col ] = colWidth;

			if ( col != mCols - 1 ) mPrefSize.width += mHGap;
		}
		else
			maxWidth = wxMax( maxWidth, colWidth );
	}

	if ( !mIsFlex )
	{
		for( row = 0; row != mRows; ++row )
			mRowHeights[ row ] = maxHeight;

		for( col = 0; col != mCols; ++col )
			mColWidths[ col ] = maxWidth;

		mPrefSize.width  = maxWidth  * mCols;
		mPrefSize.height = maxHeight * mRows;
		
		if ( mCols > 1 ) mPrefSize.width  += ( mCols - 1 ) * mHGap;
		if ( mRows > 1 ) mPrefSize.height += ( mRows - 1 ) * mVGap;
	}
}

void GridSizerNode::DoLayoutChildren( wxDC& dc )
{
	RecalcPreferredSize( dc );

	int nItems = mChildren.Count();

	int x = 0;
	int y = 0;
	//int tillX = mBounds.x + mBounds.width;
	//int tillY = mBounds.y + mBounds.height;

	// FOR NOW:: no streching to fill the remaiding free-space

	for( int row = 0; row != mRows; ++row )
	{
		x = 0;

		for( int col = 0; col != mCols; ++col )
		{
			int n = col + row * mCols;

			if ( n < nItems )
			{
				LayoutItemInfo& info = mItemInfos[ n ];

				info.bounds.x = x;
				info.bounds.y = y;
				info.bounds.width  = mColWidths[ col ];
				info.bounds.height = mRowHeights[ row ];

				WidgetNode& node = *(WidgetNode*)mChildren[ n ];

				set_bounds_to_item( node, info, dc );

				x += info.bounds.width + mHGap;
			}
		}

		y += mRowHeights[ row ] + mVGap;
	}

	CleanUp();
}

/*** protected methods ***/

void GridSizerNode::ExtractGridProps()
{
	int cnt = mChildren.Count();

	mCols = atoi( this->GetNodeAttrValue( "cols" ) );
	mRows = atoi( this->GetNodeAttrValue( "rows" ) );
	mHGap = atoi( this->GetNodeAttrValue( "hgap" ) );
	mVGap = atoi( this->GetNodeAttrValue( "vgap" ) );

	if ( mRows == 1 && mCols == -1 )

		mCols = cnt;

	if ( mCols == -1 )

		mCols = 1;

	if ( mRows == -1 )

		mRows = cnt / mCols + ( cnt % mCols ? 1 : 0 );

	mIsFlex = this->mpTempl->mTag == "flexgridsizer";
}

void GridSizerNode::GetPrefSizeAt( int x, int y, XDim& size )
{
	unsigned int idx = y * mCols + x;

	if ( idx >= mChildren.Count() )
	{
		size.width  = 0;
		size.height = 0;
	}
	else
	{
		WidgetNode& node = *(WidgetNode*)mChildren[idx];

		size = node.GetPreferredSize();

		add_boder_envolpe( size, mItemInfos[idx] );
	}
}

void GridSizerNode::CollectExtraProps()
{
	CleanUp();

	mItemInfos = new LayoutItemInfo[ mChildren.Count() ];

	size_t i = 0, cnt = mChildren.Count();
	for( ; i != cnt; ++i )
	{
		WidgetNode& node = *(WidgetNode*)mChildren[i];

		LayoutItemInfo& info = mItemInfos[i];

		if ( !node.HasExtraProperties() )

			AddExtraPropertiesToChild( &node );

		GetLayoutProps( node, info.option, info.flag, info.border );
	}
}

void GridSizerNode::CleanUp()
{
	if ( mRowHeights ) delete [] mRowHeights;
	if ( mColWidths  ) delete [] mColWidths;
	if ( mItemInfos ) delete [] mItemInfos;
	mRowHeights = NULL;
	mColWidths = NULL;
	mItemInfos = NULL;
}