VGUI ControlGrid

This is a modified VGUI_PanelListPanel by User:Ring2ding, made to bolster the class' abilities. 
 
== Usage ==
* I switched AddItem's arguments around: ( Panel *panel, Panel *labelPanel = NULL ). Label is now the second argument, and is set to NULL by default.
* Use SetShouldDrawLabels to tell it whether or not you're using labels.
* You can adjust the label height (default is 20) to allow for larger or smaller labels by calling SetLabelHeight.
== vgui_controlgrid.h ==
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#ifndef CONTROLGRID_H
#define CONTROLGRID_H
#ifdef _WIN32
#pragma once
#endif
#include <UtlLinkedList.h>
#include <UtlVector.h>
#include <vgui/VGUI.h>
#include <vgui_controls/Panel.h>
class KeyValues;
namespace vgui
{
//-----------------------------------------------------------------------------
// Purpose: A list of variable height child panels
// each list item consists of a label-panel pair. Height of the item is
// determined from the lable.
//-----------------------------------------------------------------------------
class ControlGrid : public Panel
{
	DECLARE_CLASS_SIMPLE( ControlGrid, Panel );
public:
	ControlGrid( vgui::Panel *parent, char const *panelName );
	~ControlGrid();
	// DATA & ROW HANDLING
	// The list now owns the panel
	virtual int AddItem( Panel *panel, Panel *labelPanel = NULL );
	virtual int	GetItemCount() const;
	virtual Panel *GetItemLabel(int itemID); 
	virtual Panel *GetItemPanel(int itemID); 
	virtual void RemoveItem(int itemID); // removes an item from the table (changing the indices of all following items)
	virtual void DeleteAllItems(); // clears and deletes all the memory used by the data items
	void RemoveAll();
	// painting
	virtual vgui::Panel *GetCellRenderer( int row );
	// layout
	void MoveScrollBarToTop();
	// selection
	void SetSelectedPanel( Panel *panel );
	Panel *GetSelectedPanel();
	/*
		On a panel being selected, a message gets sent to it
			"PanelSelected"		int "state"
		where state is 1 on selection, 0 on deselection
	*/
	void		SetVerticalBufferPixels( int buffer );
	//added functionality
	int GetItemIDFromRow( int nRow ) const;
	int FirstItem() const;
	int NextItem( int nItemID ) const;
	int InvalidItemID() const;
	void SetNumColumns( int iNumColumns );
	int GetNumColumns( void );
	void ScrollToItem( int itemNumber );
	//void CalcBestWidth( int LargestControlWidth = 0 );
	void SetShouldDrawLabels(bool b) { m_bShouldDrawLabels = b; }
	bool GetShouldDrawLabels() { return m_bShouldDrawLabels; }
	void SetLabelHeight(int i) { m_iLabelHeight = i; }
	int GetLabelHeight() { return m_iLabelHeight; }
	//default is 20
protected:
	// overrides
	virtual void OnSizeChanged(int wide, int tall);
	MESSAGE_FUNC_INT( OnSliderMoved, "ScrollBarSliderMoved", position );
	virtual void PerformLayout();
	virtual void ApplySchemeSettings(vgui::IScheme *pScheme);
	virtual void OnMouseWheeled(int delta);
private:
	int	ComputeVPixelsNeeded();
	enum { DEFAULT_HEIGHT = 24, PANELBUFFER = 5 };
	typedef struct dataitem_s
	{
		// Always store a panel pointer
		Panel *panel;
		Panel *labelPanel;
	} DATAITEM;
	// list of the column headers
	CUtlLinkedList<DATAITEM, int>		m_DataItems;
	CUtlVector<int>						m_SortedItems;
	ScrollBar				*m_vbar;
	Panel					*m_pPanelEmbedded;
	PHandle					m_hSelectedItem;
	int						m_iDefaultHeight;
	int						m_iPanelBuffer;
	int						m_iNumColumns;
	int						m_iLabelHeight;
	bool					m_bShouldDrawLabels;
};
}
#endif // CONTROLGRID_H
== vgui_controlgrid.cpp ==
#include "cbase.h"
#include "vgui/MouseCode.h"
#include "vgui/IInput.h"
#include "vgui/IScheme.h"
#include "vgui/ISurface.h"
#include "vgui_controls/EditablePanel.h"
#include "vgui_controls/ScrollBar.h"
#include "vgui_controls/Label.h"
#include "vgui_controls/Button.h"
#include "vgui_controls/Controls.h"
#include "vgui_controlgrid.h"
#include "KeyValues.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace vgui;
//Adds to "VGUI Build Mode Editor"
DECLARE_BUILD_FACTORY( ControlGrid );
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
ControlGrid::ControlGrid( vgui::Panel *parent, char const *panelName ) : Panel( parent, panelName )
{
	SetBounds( 0, 0, 100, 100 );
	m_vbar = new ScrollBar(this, "PanelListPanelVScroll", true);
	m_vbar->SetVisible(false);
	m_vbar->AddActionSignalTarget( this );
	m_pPanelEmbedded = new EditablePanel(this, "PanelListEmbedded");
	m_pPanelEmbedded->SetBounds(0, 0, 20, 20);
	m_pPanelEmbedded->SetPaintBackgroundEnabled( false );
	m_pPanelEmbedded->SetPaintBorderEnabled(false);
	m_bShouldDrawLabels = false;
	m_iLabelHeight = 20; //default room to leave for labels
	//m_iFirstColumnWidth = 100; // default width
	m_iNumColumns = 1; // 1 column by default
	if ( IsProportional() )
	{
		m_iDefaultHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), DEFAULT_HEIGHT );
		m_iPanelBuffer = scheme()->GetProportionalScaledValueEx( GetScheme(), PANELBUFFER );
	}
	else
	{
		m_iDefaultHeight = DEFAULT_HEIGHT;
		m_iPanelBuffer = PANELBUFFER;
	}
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
ControlGrid::~ControlGrid()
{
	// free data from table
	DeleteAllItems();
}
void ControlGrid::SetVerticalBufferPixels( int buffer )
{
	m_iPanelBuffer = buffer;
	InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose: counts the total vertical pixels
//-----------------------------------------------------------------------------
int	ControlGrid::ComputeVPixelsNeeded()
{
	int iLargestH = 0;
	int w, h;
	int pixels = 0;
	for ( int iCurrentItem = 0; iCurrentItem < m_SortedItems.Count(); iCurrentItem++ )
	{
		Panel *panel = m_DataItems[ m_SortedItems[iCurrentItem] ].panel;
		if ( !panel )
			continue;
		int iCurrentColumn = iCurrentItem % m_iNumColumns;
		panel->GetSize( w, h );
		if ( iLargestH < h )
			iLargestH = h;
		if ( iCurrentColumn == 0 && (!m_bShouldDrawLabels || iCurrentItem == 0) )
			pixels += m_iPanelBuffer; // add in buffer. between rows.
		if ( iCurrentColumn == m_iNumColumns - 1 && iCurrentItem != m_SortedItems.Count()-1 )
		{
			if (m_bShouldDrawLabels)
				pixels += m_iLabelHeight;
			pixels += iLargestH;
			iLargestH = 0;
		}
	}
	// Add in remaining height
	if (m_bShouldDrawLabels)
		pixels += m_iLabelHeight;
	pixels += iLargestH;
	pixels += m_iPanelBuffer; // add in buffer below last item
	return pixels;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the panel to use to render a cell
//-----------------------------------------------------------------------------
Panel *ControlGrid::GetCellRenderer( int row )
{
	if ( !m_SortedItems.IsValidIndex(row) )
		return NULL;
	Panel *panel = m_DataItems[ m_SortedItems[row] ].panel;
	return panel;
}
//-----------------------------------------------------------------------------
// Purpose: adds an item to the view
//			data->GetName() is used to uniquely identify an item
//			data sub items are matched against column header name to be used in the table
//-----------------------------------------------------------------------------
int ControlGrid::AddItem( Panel *panel, Panel *labelPanel)
{
	Assert(panel);
	if ( labelPanel )
	{
		labelPanel->SetParent( m_pPanelEmbedded );
	}
	panel->SetParent( m_pPanelEmbedded );
	int itemID = m_DataItems.AddToTail();
	DATAITEM &newitem = m_DataItems[itemID];
	newitem.labelPanel = labelPanel;
	newitem.panel = panel;
	m_SortedItems.AddToTail(itemID);
	InvalidateLayout();
	return itemID;
}
//-----------------------------------------------------------------------------
// Purpose: iteration accessor
//-----------------------------------------------------------------------------
int	ControlGrid::GetItemCount() const
{
	return m_DataItems.Count();
}
int ControlGrid::GetItemIDFromRow( int nRow ) const
{
	if ( nRow < 0 || nRow >= GetItemCount() )
		return m_DataItems.InvalidIndex();
	return m_SortedItems[ nRow ];
}
//-----------------------------------------------------------------------------
// Iteration. Use these until they return InvalidItemID to iterate all the items.
//-----------------------------------------------------------------------------
int ControlGrid::FirstItem() const
{
	return m_DataItems.Head();
}
int ControlGrid::NextItem( int nItemID ) const
{
	return m_DataItems.Next( nItemID );
}
int ControlGrid::InvalidItemID() const
{
	return m_DataItems.InvalidIndex( );
}
//-----------------------------------------------------------------------------
// Purpose: returns label panel for this itemID
//-----------------------------------------------------------------------------
Panel *ControlGrid::GetItemLabel(int itemID)
{
	if ( !m_DataItems.IsValidIndex(itemID) )
		return NULL;
	return m_DataItems[itemID].labelPanel;
}
//-----------------------------------------------------------------------------
// Purpose: returns label panel for this itemID
//-----------------------------------------------------------------------------
Panel *ControlGrid::GetItemPanel(int itemID)
{
	if ( !m_DataItems.IsValidIndex(itemID) )
		return NULL;
	return m_DataItems[itemID].panel;
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void ControlGrid::RemoveItem(int itemID)
{
	if ( !m_DataItems.IsValidIndex(itemID) )
		return;
	DATAITEM &item = m_DataItems[itemID];
	if ( item.panel )
	{
		item.panel->MarkForDeletion();
	}
	if ( item.labelPanel )
	{
		item.labelPanel->MarkForDeletion();
	}
	m_DataItems.Remove(itemID);
	m_SortedItems.FindAndRemove(itemID);
	InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose: clears and deletes all the memory used by the data items
//-----------------------------------------------------------------------------
void ControlGrid::DeleteAllItems()
{
	FOR_EACH_LL( m_DataItems, i )
	{
		if ( m_DataItems[i].panel )
		{
			delete m_DataItems[i].panel;
		}
	}
	m_DataItems.RemoveAll();
	m_SortedItems.RemoveAll();
	InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose: clears and deletes all the memory used by the data items
//-----------------------------------------------------------------------------
void ControlGrid::RemoveAll()
{
	m_DataItems.RemoveAll();
	m_SortedItems.RemoveAll();
	// move the scrollbar to the top of the list
	m_vbar->SetValue(0);
	InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void ControlGrid::OnSizeChanged(int wide, int tall)
{
	BaseClass::OnSizeChanged(wide, tall);
	InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose: relayouts out the panel after any internal changes
//-----------------------------------------------------------------------------
void ControlGrid::PerformLayout()
{
	int wide, tall;
	GetSize( wide, tall );
	int vpixels = ComputeVPixelsNeeded();
	m_vbar->SetVisible( true );
	m_vbar->SetRange( 0, vpixels );
	m_vbar->SetRangeWindow( tall );
	m_vbar->SetButtonPressedScrollValue( tall / 4 ); // standard height of labels/buttons etc.
	m_vbar->SetPos( wide - m_vbar->GetWide() - 2, 0 );
	m_vbar->SetSize( m_vbar->GetWide(), tall - 2 );
	int top = m_vbar->GetValue();
	m_pPanelEmbedded->SetPos( 1, -top );
	m_pPanelEmbedded->SetSize( wide - m_vbar->GetWide() - 2, vpixels );
	int sliderPos = m_vbar->GetValue();
	
	// Now lay out the controls on the embedded panel
	int y = 0;
	int h = 0;
	int totalh = 0;
	int xpos = m_iPanelBuffer;
	int iColumnWidth = ( wide - xpos - m_vbar->GetWide() - 12 ) / m_iNumColumns;
	for ( int i = 0; i < m_SortedItems.Count(); i++ )
	{
		int iCurrentColumn = i % m_iNumColumns;
		// add in a little buffer between panels
		if ( iCurrentColumn == 0 && (!m_bShouldDrawLabels || i == 0) )
		{
			y += m_iPanelBuffer;
		}
		DATAITEM &item = m_DataItems[ m_SortedItems[i] ];
		if ( h < item.panel->GetTall() )
			h = item.panel->GetTall();
		if (totalh >= sliderPos)
		{
			if ( item.labelPanel && m_bShouldDrawLabels )
			{
				item.labelPanel->SetBounds( xpos + iCurrentColumn*iColumnWidth, y + item.panel->GetTall(), iColumnWidth, m_iLabelHeight );
			}
			item.panel->SetBounds( xpos + iCurrentColumn * iColumnWidth, y, iColumnWidth, item.panel->GetTall() );
		}
		if ( iCurrentColumn >= m_iNumColumns - 1 )
		{
			if (m_bShouldDrawLabels)
				h += m_iLabelHeight;
			y += h;
			totalh += h;
			h = 0;
		}
	}
}
//------------------------------------------------------------------------------------------------------
// Purpose: determines the best width for the control based on the width of inserted elements
// Note: This function is commented out because control elements do not always give accurate information
// from their control->GetWidth() functions. (who knew...) Otherwise, it works fine.
//------------------------------------------------------------------------------------------------------
/*
void ControlGrid::CalcBestWidth(int LargestControlWidth)
{
	int width = m_iPanelBuffer;
	CUtlVector<int> rowWidths;
	int w, h;
	//create vector containing widths of rows
	for ( int i = 0; i < m_SortedItems.Count(); i++ )
	{
		int iCurrentColumn = i % m_iNumColumns;
		DATAITEM &item = m_DataItems[ m_SortedItems[i] ];
		int trueWidth = item.panel->GetWide();
		if (trueWidth < LargestControlWidth)
			trueWidth = LargestControlWidth;
		
		width += trueWidth + m_iPanelBuffer;
		if ( iCurrentColumn >= m_iNumColumns - 1 )
		{
			rowWidths.AddToTail(width);
			width = 0;
		}
	}
	//compare widths of rows, see which is largest and use it
	for ( int i = 0; i < rowWidths.Count(); i++ )
	{
		bool isLargest = true;
		for ( int j = 0; j < rowWidths.Count(); j++ )
		{
			if ( rowWidths.Element(i) < rowWidths.Element(j) )
			{
				isLargest = false;
				break;
			}
		}
		if (isLargest)
		{
			width = rowWidths.Element(i);
			break;
		}
	}
	SetSize( width ? width : 100, GetTall() );
}
*/
//-----------------------------------------------------------------------------
// Purpose: scheme settings
//-----------------------------------------------------------------------------
void ControlGrid::ApplySchemeSettings(IScheme *pScheme)
{
	BaseClass::ApplySchemeSettings(pScheme);
	SetBorder(pScheme->GetBorder("ButtonDepressedBorder"));
	SetBgColor(GetSchemeColor("ListPanel.BgColor", GetBgColor(), pScheme));
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void ControlGrid::OnSliderMoved( int position )
{
	InvalidateLayout();
	Repaint();
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void ControlGrid::MoveScrollBarToTop()
{
	m_vbar->SetValue(0);
}
void ControlGrid::SetNumColumns( int iNumColumns )
{
	m_iNumColumns = iNumColumns;
}
int ControlGrid::GetNumColumns( void )
{
	return m_iNumColumns;
}
//-----------------------------------------------------------------------------
// Purpose: moves the scrollbar with the mousewheel
//-----------------------------------------------------------------------------
void ControlGrid::OnMouseWheeled(int delta)
{
	int val = m_vbar->GetValue();
	val -= (delta * DEFAULT_HEIGHT);
	m_vbar->SetValue(val);	
}
//-----------------------------------------------------------------------------
// Purpose: selection handler
//-----------------------------------------------------------------------------
void ControlGrid::SetSelectedPanel( Panel *panel )
{
	if ( panel != m_hSelectedItem )
	{
		// notify the panels of the selection change
		if ( m_hSelectedItem )
		{
			PostMessage( m_hSelectedItem, new KeyValues("PanelSelected", "state", 0) );
		}
		if ( panel )
		{
			PostMessage( panel, new KeyValues("PanelSelected", "state", 1) );
		}
		m_hSelectedItem = panel;
	}
}
//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
Panel *ControlGrid::GetSelectedPanel()
{
	return m_hSelectedItem;
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void ControlGrid::ScrollToItem( int itemNumber )
{
	if (!m_vbar->IsVisible())
	{
		return;
	}
	DATAITEM& item = m_DataItems[ m_SortedItems[ itemNumber ] ];
	if ( !item.panel )
		return;
	int x, y;
	item.panel->GetPos( x, y );
	int lx, ly;
	lx = x;
	ly = y;
	m_pPanelEmbedded->LocalToScreen( lx, ly );
	ScreenToLocal( lx, ly );
	int h = item.panel->GetTall();
	if ( ly >= 0 && ly + h < GetTall() )
		return;
	m_vbar->SetValue( y );
	InvalidateLayout();
}
C