// List.cpp : Implementation of cList
#include "stdafx.h"
#include "DecalControls.h"
#include "List.h"

#include "ListView.h"

enum eListChild
{
   eListScroller = 1,
   eListView = 1000
};

/////////////////////////////////////////////////////////////////////////////
// cList

cList::cRow::cRow( long nDataWidth )
: m_pData( new VARIANT[ nDataWidth ] ),
m_pEndData( m_pData + nDataWidth ),
m_bInvalid( true ),
m_colors( NULL ) /* cyn - 07/08/2002 */
{
   for( VARIANT *i_var = m_pData; i_var != m_pEndData; ++ i_var )
      ::VariantInit( i_var );

}

cList::cRow::cRow( const cRow &row )
: m_pData( row.m_pData ),
m_pEndData( row.m_pEndData ),
m_bInvalid( row.m_bInvalid ),
m_colors( row.m_colors ) /* cyn - 07/08/2002 -- This is the real fix, the rest of */
                         /*                     the changes just make it faster */
{
   _ASSERTE( _CrtIsValidHeapPointer( m_pData ) );
   row.m_pData = NULL;
   row.m_colors = NULL;
}

cList::cRow::~cRow()
{
	if( m_pData != NULL )
	{
		_ASSERTE( _CrtIsValidHeapPointer( m_pData ) );
		for( VARIANT *i_var = m_pData; i_var != m_pEndData; ++ i_var )
		{
			HRESULT hRes = ::VariantClear( i_var );
			_ASSERTE( SUCCEEDED( hRes ) );
		}

		delete[] m_pData;
	}

  if (m_colors) { /* cyn - 07/08/2002 */
    delete[] m_colors;
  }
}

cList::cRow &cList::cRow::operator =( const cRow &row )
{
	

  // Clear out our current data - if any
	if( m_pData != NULL )
	{
		_ASSERTE( _CrtIsValidHeapPointer( m_pData ) );
		for( VARIANT *i_var = m_pData; i_var != m_pEndData; ++ i_var )
		{
			 HRESULT hRes = ::VariantClear( i_var );
			 _ASSERTE( SUCCEEDED( hRes ) );
		}

		delete[] m_pData;
		m_pData = NULL;
	}

	_ASSERTE( _CrtIsValidHeapPointer( row.m_pData ) );

	m_pData = row.m_pData;
	m_pEndData = row.m_pEndData;
	m_bInvalid = row.m_bInvalid;

  if (m_colors) { /* cyn - 07/08/2002 */
    delete[] m_colors;
  }
	m_colors = row.m_colors;
  row.m_colors = NULL;

  row.m_pData = NULL;

	_ASSERTMEM( _CrtCheckMemory() );

	return *this;
}

STDMETHODIMP cList::AddColumn(IListColumn *pNewColumn, long *nIndex)
{
	_ASSERTE( pNewColumn != NULL );
	_ASSERTE( nIndex != NULL );

	// We can only add columns before there are data rows
	_ASSERTE( m_rows.size() == 0 );

	// Looking good, make up the record
	cColumn c;
	c.m_pColumn = pNewColumn;
	c.m_nDataLeft = ( m_cols.size() == 0 ) ? 0 : m_cols.back().m_nDataRight;

	CComPtr< IPluginSite > pPluginSite;
	m_pSite->get_PluginSite( &pPluginSite );

	long nDataSize;
	pNewColumn->get_DataColumns( &nDataSize );
	c.m_nDataRight = c.m_nDataLeft + nDataSize;

	pNewColumn->Initialize( this, pPluginSite );

	CComPtr< ILayerMouse > pMouse;
	c.m_bSupportsMouse = !!SUCCEEDED( pNewColumn->QueryInterface( &pMouse ) );

	m_cols.push_back( c );

	// Reformat will fill in column lefts and rights
	m_pSite->Reformat();

	*nIndex = ( m_cols.size() - 1 );
	return S_OK;
}

STDMETHODIMP cList::AddRow(long *pnNewIndex)
{
	_ASSERTE( pnNewIndex != NULL );

	// There must be at least one column defined
	_ASSERTE( m_cols.size() > 0 );

	m_rows.push_back( cRow( m_cols.back().m_nDataRight ) );
	long nSize = m_rows.size();

	Reformat();
	m_pSite->Reformat();

	if( m_bAutoScroll == true )
	{
		POINT ptCurrent;
		m_pScroller->get_Offset( &ptCurrent );

		ptCurrent.y = m_rows.size();

		m_pScroller->ScrollTo( &ptCurrent );  

		Reformat();
		m_pSite->Reformat();
	}

	*pnNewIndex = ( nSize - 1 );

	return S_OK;
}

STDMETHODIMP cList::DeleteRow(long nIndex)
{
	_ASSERTE( nIndex >= 0 );
	_ASSERTE( nIndex < m_rows.size() );

	m_rows.erase( m_rows.begin() + nIndex );

	// Start at that index and invalidate all rows below that point
	m_pView->InvalidateFrom( nIndex );
	for( cRowList::iterator i_row = m_rows.begin() + nIndex; i_row != m_rows.end(); ++ i_row )
		i_row->m_bInvalid = true;

	m_pSite->Invalidate();
	m_pSite->Reformat();

	POINT offset;
	::memset (&offset, 0, sizeof (offset));

	m_pScroller->get_Offset (&offset);

	if (offset.y > nIndex )
	{
		// TODO: This isn't perfect, if items are removed from the bottom of the list when
		// you're scrolled to the middle, this will cause it to scroll up a line.  It does
		// solve the problem of lists 'losing' items when the thumb goes away though.

		// Note by Haz - I changed the above from if( offset.y > 0 )   - seems to fix problem :)
		offset.y--;
		m_pScroller->ScrollTo (&offset);
	}

	return S_OK;
}

STDMETHODIMP cList::get_Data(long nX, long nY, long nSubValue, VARIANT *pVal)
{
	_ASSERTE( m_cols.size() > 0 );
	_ASSERTE( nX >= 0 );
	_ASSERTE( nX < m_cols.back().m_nDataRight );
	_ASSERTE( nSubValue >= 0 );
	_ASSERTE( nSubValue < ( m_cols[ nX ].m_nDataRight - m_cols[ nX ].m_nDataLeft ) );
	_ASSERTE( nY >= 0 );
	_ASSERTE( nY < m_rows.size() );
	_ASSERTE( pVal != NULL );

	cRow &rowData = m_rows[ nY ];
	HRESULT hRes = ::VariantCopy( pVal, rowData.m_pData + ( m_cols[ nX ].m_nDataLeft + nSubValue ) );
	_ASSERTE( SUCCEEDED( hRes ) );

	return S_OK;
}

STDMETHODIMP cList::put_Data(long nX, long nY, long nSubValue, VARIANT *newVal)
{
	_ASSERTE( m_cols.size() > 0 );
	_ASSERTE( nX >= 0 );
	_ASSERTE( nX < m_cols.back().m_nDataRight ); 
	_ASSERTE( nSubValue >= 0 );
	_ASSERTE( nSubValue < ( m_cols[ nX ].m_nDataRight - m_cols[ nX ].m_nDataLeft ) );
	_ASSERTE( nY >= 0 );
	_ASSERTE( nY < m_rows.size() );
	_ASSERTE( newVal != NULL );

	if( nY >= m_rows.size() )
		return E_FAIL;

	cRow &rowData = m_rows[ nY ];

	long lDataColumns = 0;
	m_cols[ nX ].m_pColumn->get_DataColumns( &lDataColumns );

	if( nSubValue > lDataColumns )
		return E_FAIL;

	HRESULT hRes = ::VariantCopy( rowData.m_pData + ( m_cols[ nX ].m_nDataLeft + nSubValue ), newVal );
	_ASSERTE( SUCCEEDED( hRes ) );
	rowData.m_bInvalid = true;

	m_pSite->Invalidate();

	return S_OK;
}

STDMETHODIMP cList::get_CellRect(LPPOINT pt, LPRECT pVal)
{
   _ASSERTE( pt != NULL );
   _ASSERTE( m_cols.size() > 0 );
   _ASSERTE( pt->x >= 0 );
   _ASSERTE( pt->x < m_cols.back().m_nDataRight );
   _ASSERTE( pt->y >= 0 );
   _ASSERTE( pt->y < m_rows.size() );
   _ASSERTE( pVal != NULL );

   pVal->top = pt->y * m_nRowHeight;
   pVal->bottom = ( pt->y + 1 ) * m_nRowHeight;
   pVal->left = m_cols[ pt->x ].m_nLeft;
   pVal->right = m_cols[ pt->y ].m_nRight;
   
	return S_OK;
}

void cList::onCreate()
{
   // Create the scoller and the scroller client object
   CComPtr< ILayer > pScrollerLayer;

   HRESULT hRes = ::CoCreateInstance( CLSID_Scroller, NULL, CLSCTX_INPROC_SERVER,
      __uuidof( ILayer ), reinterpret_cast< void ** >( &pScrollerLayer ) );

   _ASSERTE( SUCCEEDED( hRes ) );

   // Will reposition on reformat
   LayerParams p = { eListScroller, { 0, 0, 0, 0 }, eRenderClipped };

   m_pSite->CreateChild( &p, pScrollerLayer );
   pScrollerLayer->QueryInterface( &m_pScroller );

   CComObject< cListView > *pView;
   CComObject< cListView >::CreateInstance( &pView );

   m_pScroller->CreateClient( pView );
   m_pView = pView;

   // Configure the scroller

   SIZE szIncrement = { 100, 20 };
   m_pScroller->put_Increments( &szIncrement );
   m_pScroller->put_HorizontalEnabled( VARIANT_FALSE );
   m_pScroller->put_VerticalEnabled( VARIANT_TRUE );

   pView->m_pList = this;

   m_pSite->put_Transparent( VARIANT_FALSE );
}

void cList::onDestroy()
{
   // Release all of our references
   m_pView.Release();
   m_pScroller.Release();

   // Kill the data
   m_cols.clear();
   m_rows.clear();
}

STDMETHODIMP cList::Reformat()
{
   // Recalculate the column widths and reposition the child control
   RECT rc;
   m_pSite->get_Position( &rc );

   RECT rcScroller = { 0, 0, rc.right - rc.left, rc.bottom - rc.top };
   CComPtr< ILayerSite > pScrollerSite;
   HRESULT hRes = m_pSite->get_Child( eListScroller, ePositionByID, &pScrollerSite );
   _ASSERTE( SUCCEEDED( hRes ) );

   pScrollerSite->put_Position( &rcScroller );

   SIZE szClient;
   m_pScroller->get_Viewport( &szClient );

   // Ok, setup the list view objects (note there is cache for a partial row at the top and bottom)
   m_pView->SetCacheInfo( m_nRowHeight, ( szClient.cy / m_nRowHeight ) + 2 );

   szClient.cy = m_nRowHeight * m_rows.size();

   m_pScroller->put_Area( &szClient );

   // Last, calculate the column widths
   long nFixedWidthsTotal = 0,
      nVariableCount = 0;

   // First pass adds up the fixed widths
   {
      for( cColumnList::iterator i = m_cols.begin(); i != m_cols.end(); ++ i )
      {
         VARIANT_BOOL bFixed;
         i->m_pColumn->get_FixedWidth( &bFixed );

         if( bFixed )
         {
            long nWidth;
            i->m_pColumn->get_Width( &nWidth );

            nFixedWidthsTotal += nWidth;
         }
         else
            ++ nVariableCount;
      }
   }
   
   // Next pass sets the widths
   //_ASSERTE( nFixedWidthsTotal < szClient.cx );

   long nPosition = 0;

   for( cColumnList::iterator i = m_cols.begin(); i != m_cols.end(); ++ i )
   {
      VARIANT_BOOL bFixed;
      i->m_pColumn->get_FixedWidth( &bFixed );

      i->m_nLeft = nPosition;

      if( bFixed )
      {
         long nWidth;
         i->m_pColumn->get_Width( &nWidth );
         i->m_nRight = i->m_nLeft + nWidth;
      }
      else
         i->m_nRight = i->m_nLeft + ( szClient.cx - nFixedWidthsTotal ) / nVariableCount;

      nPosition = i->m_nRight;
   }

   _ASSERTMEM( _CrtCheckMemory( ) );

	_ASSERT(0==0);
   return S_OK;
}

STDMETHODIMP cList::put_RowEstimate(long newVal)
{
	m_rows.reserve( newVal );

	return S_OK;
}

STDMETHODIMP cList::SchemaLoad(IView *, IUnknown *pSchema)
{
   MSXML::IXMLDOMElementPtr pElement = pSchema;

   _variant_t vRowHeight = pElement->getAttribute( _T( "rowheight" ) );
   
   if( vRowHeight.vt != VT_NULL )
   {
      try
      {
         m_nRowHeight = static_cast< long >( vRowHeight );
      }
      catch( ... )
      {
         // Type conversion error
         _ASSERTE( FALSE );
      }
   }
   else
	   m_nRowHeight = 20;

	_variant_t vAutoScroll = pElement->getAttribute( _T( "autoscroll" ) );
	if( vAutoScroll.vt != VT_NULL )
	{
      try
      {
         m_bAutoScroll = static_cast< bool >( vAutoScroll );
      }
      catch( ... )
      {
         // Type conversion error
         _ASSERTE( FALSE );
      }
	}


   // Walk the list of columns under this element
   MSXML::IXMLDOMElementPtr pColumn;
   for( MSXML::IXMLDOMNodeListPtr pColumns = pElement->selectNodes( _T( "column" ) ); ( pColumn = pColumns->nextNode() ).GetInterfacePtr() != NULL; )
   {
      _variant_t vProgID = pColumn->getAttribute( _T( "progid" ) );

      _ASSERTE( vProgID.vt == VT_BSTR );

      CLSID clsid;
      HRESULT hRes = ::CLSIDFromProgID( vProgID.bstrVal, &clsid );

      _ASSERTE( SUCCEEDED( hRes ) );

      // Looking good, create the column object
      CComPtr< IListColumn > pColumnInst;

      hRes = ::CoCreateInstance( clsid, NULL, CLSCTX_INPROC_SERVER, IID_IListColumn,
         reinterpret_cast< void ** >( &pColumnInst ) );

      _ASSERTE( SUCCEEDED( hRes ) );

      // Have the column load any special internal values
      pColumnInst->SchemaLoad( pColumn );

      long nIndex;
      AddColumn( pColumnInst, &nIndex );
   }
   
   SIZE szIncrement = { 100, m_nRowHeight };
   m_pScroller->put_Increments( &szIncrement );
   
   return S_OK;
}

STDMETHODIMP cList::get_Count( long *pnCount )
{
   _ASSERTE( pnCount != NULL );

   *pnCount = m_rows.size();

   return S_OK;
}

STDMETHODIMP cList::get_CountCols( long *pnCount )
{
   _ASSERTE( pnCount != NULL );

   *pnCount = m_cols.size();

   return S_OK;
}

STDMETHODIMP cList::Clear()
{
   m_rows.clear();
   m_pView->InvalidateFrom( 0 );

   m_pScroller->ResetScroller();

   m_pSite->Reformat();
   m_pSite->Invalidate();

	return S_OK;
}

STDMETHODIMP cList::InsertRow(long lIndex)
{
	_ASSERTE (lIndex >= 0);
	// There must be at least one column defined
	_ASSERTE( m_cols.size() > 0 );

	cRowList::iterator it = m_rows.begin ();

	if (lIndex > 0)
	{
		it += lIndex;
	}

	m_rows.insert( it, cRow( m_cols.back().m_nDataRight ) );

	// Start at that index and invalidate all rows below that point
	m_pView->InvalidateFrom( lIndex );
	for( cRowList::iterator i_row = m_rows.begin() + lIndex; i_row != m_rows.end(); ++ i_row )
	{
		i_row->m_bInvalid = true;
	}

	m_pSite->Invalidate();
	m_pSite->Reformat();

	return S_OK;
}

STDMETHODIMP cList::put_ColumnWidth(long nColumn, long nWidth)
{
	m_cols[ nColumn ].m_pColumn->put_Width(nWidth);
	m_pSite->Invalidate();

	return S_OK;
}

STDMETHODIMP cList::get_ColumnWidth(long nColumn, long *nWidth)
{
	m_cols[ nColumn ].m_pColumn->get_Width(nWidth);

	return S_OK;
}

STDMETHODIMP cList::get_Color(long nX, long nY, long *pVal)
{

  if (!pVal) { /* cyn - 07/08/2002 */
    return E_FAIL;
  }

  *pVal = 0x00FFFFFF;
	cRow &rowData = m_rows[nY];

  if (nX >= m_cols.size()) {
    return E_FAIL;
  }

  if (rowData.m_colors) {
    if (rowData.m_colors[nX]) {
      *pVal = rowData.m_colors[nX];
    }
  }

	return S_OK;
}

STDMETHODIMP cList::put_Color(long nX, long nY, long newVal)
{
  int i; 

  if (nX >= m_cols.size()) { /* cyn - 07/08/2002 */
    return E_FAIL;
  }
  
	cRow &rowData = m_rows[nY];

  if (!rowData.m_colors) {
    i = m_cols.size();
    rowData.m_colors = new long[ i ];
    if (!rowData.m_colors) {
      return E_OUTOFMEMORY;
    }

	// Madar 9/8/2002: Initialize colors to white.
	for (long loop = 0; loop < m_cols.size(); ++loop) {
		rowData.m_colors[loop] = 0x00ffffff;
	}
  }

  rowData.m_colors[nX] = newVal;

  m_pSite->Invalidate();
  rowData.m_bInvalid = true;

	return S_OK;
}

STDMETHODIMP cList::get_AutoScroll(VARIANT_BOOL *pVal)
{
	if( m_bAutoScroll == true )
		*pVal = VARIANT_TRUE;

	else if( m_bAutoScroll == false )
		*pVal = VARIANT_FALSE;

	else
		return E_FAIL;

	return S_OK;
}

STDMETHODIMP cList::put_AutoScroll(VARIANT_BOOL newVal)
{
	if( newVal == VARIANT_TRUE )
		m_bAutoScroll = true;

	else if( newVal == VARIANT_FALSE )
		m_bAutoScroll = false;

	else
		return E_FAIL;

	return S_OK;
}

STDMETHODIMP cList::get_ScrollPosition(long *pVal)
{
	tagPOINT tpOffset;
	tagSIZE tsSize;
	m_pScroller->get_Offset( &tpOffset );
	m_pScroller->get_Increments( &tsSize );

	*pVal = tpOffset.y; 

	return S_OK;
}

STDMETHODIMP cList::put_ScrollPosition(long newVal)
{
	tagPOINT tpOffset;
	tagSIZE tsSize;
	m_pScroller->get_Offset( &tpOffset );
	m_pScroller->get_Increments( &tsSize );

	tpOffset.y = newVal;
	
	m_pScroller->ScrollTo( &tpOffset );
	
	return S_OK;
}

STDMETHODIMP cList::JumpToPosition(long newVal)
{
	_ASSERTE( newVal >= 0 );
	_ASSERTE( newVal < m_rows.size() );

	tagPOINT tpOffset;
	m_pScroller->get_Offset( &tpOffset );

	tpOffset.y = newVal;
	
	m_pScroller->put_Offset( &tpOffset );
	m_pScroller->ScrollTo( &tpOffset ); // ensures the thumb gets updated
	
	return S_OK;
}
