openDecal/Native/DecalControls/List.cpp
erik d1442e3747 Initial commit: Complete open-source Decal rebuild
All 5 phases of the open-source Decal rebuild:

Phase 1: 14 decompiled .NET projects (Interop.*, Adapter, FileService, DecalUtil)
Phase 2: 10 native DLLs rewritten as C# COM servers with matching GUIDs
  - DecalDat, DHS, SpellFilter, DecalInput, DecalNet, DecalFilters
  - Decal.Core, DecalControls, DecalRender, D3DService
Phase 3: C++ shims for Inject.DLL (D3D9 hooking) and LauncherHook.DLL
Phase 4: DenAgent WinForms tray application
Phase 5: WiX installer and build script

25 C# projects building with 0 errors.
Native C++ projects require VS 2022 + Windows SDK (x86).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 18:27:56 +01:00

656 lines
14 KiB
C++

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