// 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; }