hl2_src-leak-2017/src/vgui2/vgui_controls/ListPanel.cpp

3288 lines
88 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include <stdio.h>
#define PROTECTED_THINGS_DISABLE
#include <vgui/Cursor.h>
#include <vgui/IInput.h>
#include <vgui/ILocalize.h>
#include <vgui/IPanel.h>
#include <vgui/IScheme.h>
#include <vgui/ISystem.h>
#include <vgui/ISurface.h>
#include <vgui/IVGui.h>
#include <vgui/KeyCode.h>
#include <KeyValues.h>
#include <vgui/MouseCode.h>
#include <vgui_controls/Button.h>
#include <vgui_controls/Controls.h>
#include <vgui_controls/ImageList.h>
#include <vgui_controls/ImagePanel.h>
#include <vgui_controls/Label.h>
#include <vgui_controls/ListPanel.h>
#include <vgui_controls/ScrollBar.h>
#include <vgui_controls/TextImage.h>
#include <vgui_controls/Menu.h>
#include <vgui_controls/Tooltip.h>
// memdbgon must be the last include file in a .cpp file
#include "tier0/memdbgon.h"
using namespace vgui;
enum
{
WINDOW_BORDER_WIDTH=2 // the width of the window's border
};
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef clamp
#define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) )
#endif
//-----------------------------------------------------------------------------
//
// Button at the top of columns used to re-sort
//
//-----------------------------------------------------------------------------
class ColumnButton : public Button
{
public:
ColumnButton(vgui::Panel *parent, const char *name, const char *text);
// Inherited from Button
virtual void ApplySchemeSettings(IScheme *pScheme);
virtual void OnMousePressed(MouseCode code);
void OpenColumnChoiceMenu();
};
ColumnButton::ColumnButton(vgui::Panel *parent, const char *name, const char *text) : Button(parent, name, text)
{
SetBlockDragChaining( true );
}
void ColumnButton::ApplySchemeSettings(IScheme *pScheme)
{
Button::ApplySchemeSettings(pScheme);
SetContentAlignment(Label::a_west);
SetFont(pScheme->GetFont("DefaultSmall", IsProportional()));
}
// Don't request focus.
// This will keep items in the listpanel selected.
void ColumnButton::OnMousePressed(MouseCode code)
{
if (!IsEnabled())
return;
if (code == MOUSE_RIGHT)
{
OpenColumnChoiceMenu();
return;
}
if (!IsMouseClickEnabled(code))
return;
if (IsUseCaptureMouseEnabled())
{
{
SetSelected(true);
Repaint();
}
// lock mouse input to going to this button
input()->SetMouseCapture(GetVPanel());
}
}
void ColumnButton::OpenColumnChoiceMenu()
{
CallParentFunction(new KeyValues("OpenColumnChoiceMenu"));
}
//-----------------------------------------------------------------------------
//
// Purpose: Handles resizing of columns
//
//-----------------------------------------------------------------------------
class Dragger : public Panel
{
public:
Dragger(int column);
// Inherited from Panel
virtual void OnMousePressed(MouseCode code);
virtual void OnMouseDoublePressed(MouseCode code);
virtual void OnMouseReleased(MouseCode code);
virtual void OnCursorMoved(int x, int y);
virtual void SetMovable(bool state);
private:
int m_iDragger;
bool m_bDragging;
int m_iDragPos;
bool m_bMovable; // whether this dragger is movable using mouse or not
};
Dragger::Dragger(int column)
{
m_iDragger = column;
SetPaintBackgroundEnabled(false);
SetPaintEnabled(false);
SetPaintBorderEnabled(false);
SetCursor(dc_sizewe);
m_bDragging = false;
m_bMovable = true; // movable by default
m_iDragPos = 0;
SetBlockDragChaining( true );
}
void Dragger::OnMousePressed(MouseCode code)
{
if (m_bMovable)
{
input()->SetMouseCapture(GetVPanel());
int x, y;
input()->GetCursorPos(x, y);
m_iDragPos = x;
m_bDragging = true;
}
}
void Dragger::OnMouseDoublePressed(MouseCode code)
{
if (m_bMovable)
{
// resize the column to the size of it's contents
PostMessage(GetParent(), new KeyValues("ResizeColumnToContents", "column", m_iDragger));
}
}
void Dragger::OnMouseReleased(MouseCode code)
{
if (m_bMovable)
{
input()->SetMouseCapture(NULL);
m_bDragging = false;
}
}
void Dragger::OnCursorMoved(int x, int y)
{
if (m_bDragging)
{
input()->GetCursorPos(x, y);
KeyValues *msg = new KeyValues("ColumnResized");
msg->SetInt("column", m_iDragger);
msg->SetInt("delta", x - m_iDragPos);
m_iDragPos = x;
if (GetVParent())
{
ivgui()->PostMessage(GetVParent(), msg, GetVPanel());
}
}
}
void Dragger::SetMovable(bool state)
{
m_bMovable = state;
// disable cursor change if the dragger is not movable
if( IsVisible() )
{
if (state)
{
// if its not movable we stick with the default arrow
// if parent windows Start getting fancy cursors we should probably retrive a parent
// cursor and set it to that
SetCursor(dc_sizewe);
}
else
{
SetCursor(dc_arrow);
}
}
}
namespace vgui
{
// optimized for sorting
class FastSortListPanelItem : public ListPanelItem
{
public:
// index into accessing item to sort
CUtlVector<int> m_SortedTreeIndexes;
// visibility flag (for quick hide/filter)
bool visible;
// precalculated sort orders
int primarySortIndexValue;
int secondarySortIndexValue;
};
}
static ListPanel *s_pCurrentSortingListPanel = NULL;
static const char *s_pCurrentSortingColumn = NULL;
static bool s_currentSortingColumnTypeIsText = false;
static SortFunc *s_pSortFunc = NULL;
static bool s_bSortAscending = true;
static SortFunc *s_pSortFuncSecondary = NULL;
static bool s_bSortAscendingSecondary = true;
//-----------------------------------------------------------------------------
// Purpose: Basic sort function, for use in qsort
//-----------------------------------------------------------------------------
static int __cdecl AscendingSortFunc(const void *elem1, const void *elem2)
{
int itemID1 = *((int *) elem1);
int itemID2 = *((int *) elem2);
// convert the item index into the ListPanelItem pointers
vgui::ListPanelItem *p1, *p2;
p1 = s_pCurrentSortingListPanel->GetItemData(itemID1);
p2 = s_pCurrentSortingListPanel->GetItemData(itemID2);
int result = s_pSortFunc( s_pCurrentSortingListPanel, *p1, *p2 );
if (result == 0)
{
// use the secondary sort functino
result = s_pSortFuncSecondary( s_pCurrentSortingListPanel, *p1, *p2 );
if (!s_bSortAscendingSecondary)
{
result = -result;
}
if (result == 0)
{
// sort by the pointers to make sure we get consistent results
if (p1 > p2)
{
result = 1;
}
else
{
result = -1;
}
}
}
else
{
// flip result if not doing an ascending sort
if (!s_bSortAscending)
{
result = -result;
}
}
return result;
}
//-----------------------------------------------------------------------------
// Purpose: Default column sorting function, puts things in alpabetical order
// If images are the same returns 1, else 0
//-----------------------------------------------------------------------------
static int __cdecl DefaultSortFunc(
ListPanel *pPanel,
const ListPanelItem &item1,
const ListPanelItem &item2 )
{
const vgui::ListPanelItem *p1 = &item1;
const vgui::ListPanelItem *p2 = &item2;
if ( !p1 || !p2 ) // No meaningful comparison
{
return 0;
}
const char *col = s_pCurrentSortingColumn;
if (s_currentSortingColumnTypeIsText) // textImage column
{
if (p1->kv->FindKey(col, true)->GetDataType() == KeyValues::TYPE_INT)
{
// compare ints
int s1 = p1->kv->GetInt(col, 0);
int s2 = p2->kv->GetInt(col, 0);
if (s1 < s2)
{
return -1;
}
else if (s1 > s2)
{
return 1;
}
return 0;
}
else
{
// compare as string
const char *s1 = p1->kv->GetString(col, "");
const char *s2 = p2->kv->GetString(col, "");
return Q_stricmp(s1, s2);
}
}
else // its an imagePanel column
{
const ImagePanel *s1 = (const ImagePanel *)p1->kv->GetPtr(col, NULL);
const ImagePanel *s2 = (const ImagePanel *)p2->kv->GetPtr(col, NULL);
if (s1 < s2)
{
return -1;
}
else if (s1 > s2)
{
return 1;
}
return 0;
}
}
//-----------------------------------------------------------------------------
// Purpose: Sorts items by comparing precalculated list values
//-----------------------------------------------------------------------------
static int __cdecl FastSortFunc(
ListPanel *pPanel,
const ListPanelItem &item1,
const ListPanelItem &item2 )
{
const vgui::FastSortListPanelItem *p1 = (vgui::FastSortListPanelItem *)&item1;
const vgui::FastSortListPanelItem *p2 = (vgui::FastSortListPanelItem *)&item2;
Assert(p1 && p2);
// compare the precalculated indices
if (p1->primarySortIndexValue < p2->primarySortIndexValue)
{
return 1;
}
else if (p1->primarySortIndexValue > p2->primarySortIndexValue)
{
return -1;
}
// they're equal, compare the secondary indices
if (p1->secondarySortIndexValue < p2->secondarySortIndexValue)
{
return 1;
}
else if (p1->secondarySortIndexValue > p2->secondarySortIndexValue)
{
return -1;
}
// still equal; just compare the pointers (so we get deterministic results)
return (p1 < p2) ? 1 : -1;
}
static int s_iDuplicateIndex = 1;
//-----------------------------------------------------------------------------
// Purpose: sorting function used in the column index redblack tree
//-----------------------------------------------------------------------------
bool ListPanel::RBTreeLessFunc(vgui::ListPanel::IndexItem_t &item1, vgui::ListPanel::IndexItem_t &item2)
{
int result = s_pSortFunc( s_pCurrentSortingListPanel, *item1.dataItem, *item2.dataItem);
if (result == 0)
{
// they're the same value, set their duplicate index to reflect that
if (item1.duplicateIndex)
{
item2.duplicateIndex = item1.duplicateIndex;
}
else if (item2.duplicateIndex)
{
item1.duplicateIndex = item2.duplicateIndex;
}
else
{
item1.duplicateIndex = item2.duplicateIndex = s_iDuplicateIndex++;
}
}
return (result > 0);
}
DECLARE_BUILD_FACTORY( ListPanel );
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
ListPanel::ListPanel(Panel *parent, const char *panelName) : BaseClass(parent, panelName)
{
m_bIgnoreDoubleClick = false;
m_bMultiselectEnabled = true;
m_iEditModeItemID = 0;
m_iEditModeColumn = 0;
m_iHeaderHeight = 20;
m_iRowHeight = 20;
m_bCanSelectIndividualCells = false;
m_iSelectedColumn = -1;
m_bAllowUserAddDeleteColumns = false;
m_hbar = new ScrollBar(this, "HorizScrollBar", false);
m_hbar->AddActionSignalTarget(this);
m_hbar->SetVisible(false);
m_vbar = new ScrollBar(this, "VertScrollBar", true);
m_vbar->SetVisible(false);
m_vbar->AddActionSignalTarget(this);
m_pLabel = new Label(this, NULL, "");
m_pLabel->SetVisible(false);
m_pLabel->SetPaintBackgroundEnabled(false);
m_pLabel->SetContentAlignment(Label::a_west);
m_pTextImage = new TextImage( "" );
m_pImagePanel = new ImagePanel(NULL, "ListImage");
m_pImagePanel->SetAutoDelete(false);
m_iSortColumn = -1;
m_iSortColumnSecondary = -1;
m_bSortAscending = true;
m_bSortAscendingSecondary = true;
m_lastBarWidth = 0;
m_iColumnDraggerMoved = -1;
m_bNeedsSort = false;
m_LastItemSelected = -1;
m_pImageList = NULL;
m_bDeleteImageListWhenDone = false;
m_pEmptyListText = new TextImage("");
m_nUserConfigFileVersion = 1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
ListPanel::~ListPanel()
{
// free data from table
RemoveAll();
// free column headers
unsigned char i;
for ( i = m_ColumnsData.Head(); i != m_ColumnsData.InvalidIndex(); i= m_ColumnsData.Next( i ) )
{
m_ColumnsData[i].m_pHeader->MarkForDeletion();
m_ColumnsData[i].m_pResizer->MarkForDeletion();
}
m_ColumnsData.RemoveAll();
delete m_pTextImage;
delete m_pImagePanel;
delete m_vbar;
if ( m_bDeleteImageListWhenDone )
{
delete m_pImageList;
}
delete m_pEmptyListText;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SetImageList(ImageList *imageList, bool deleteImageListWhenDone)
{
// get rid of existing list image if there's one and we're supposed to get rid of it
if ( m_pImageList && m_bDeleteImageListWhenDone )
{
delete m_pImageList;
}
m_bDeleteImageListWhenDone = deleteImageListWhenDone;
m_pImageList = imageList;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SetColumnHeaderHeight( int height )
{
m_iHeaderHeight = height;
}
//-----------------------------------------------------------------------------
// Purpose: adds a column header.
// this->FindChildByName(columnHeaderName) can be used to retrieve a pointer to a header panel by name
//
// if minWidth and maxWidth are BOTH NOTRESIZABLE or RESIZABLE
// the min and max size will be calculated automatically for you with that attribute
// columns are resizable by default
// if min and max size are specified column is resizable
//
// A small note on passing numbers for minWidth and maxWidth,
// If the initial window size is larger than the sum of the original widths of the columns,
// you can wind up with the columns "snapping" to size after the first window focus
// This is because the dxPerBar being calculated in PerformLayout()
// is making resizable bounded headers exceed thier maxWidths at the Start.
// Solution is to either put in support for redistributing the extra dx being truncated and
// therefore added to the last column on window opening, which is what causes the snapping.
// OR to
// ensure the difference between the starting sum of widths is not too much smaller/bigger
// than the starting window size so the starting dx doesn't cause snapping to occur.
// The easiest thing is to simply set it so your column widths add up to the starting size of the window on opening.
//
// Another note: Always give bounds for the last column you add or make it not resizable.
//
// Columns can have text headers or images for headers (e.g. password icon)
//-----------------------------------------------------------------------------
void ListPanel::AddColumnHeader(int index, const char *columnName, const char *columnText, int width, int columnFlags)
{
if (columnFlags & COLUMN_FIXEDSIZE && !(columnFlags & COLUMN_RESIZEWITHWINDOW))
{
// for fixed size columns, set the min & max widths to be the same as the initial width
AddColumnHeader( index, columnName, columnText, width, width, width, columnFlags);
}
else
{
AddColumnHeader( index, columnName, columnText, width, 20, 10000, columnFlags);
}
}
//-----------------------------------------------------------------------------
// Purpose: Adds a new column
//-----------------------------------------------------------------------------
void ListPanel::AddColumnHeader(int index, const char *columnName, const char *columnText, int width, int minWidth, int maxWidth, int columnFlags)
{
Assert (minWidth <= width);
Assert (maxWidth >= width);
// get our permanent index
unsigned char columnDataIndex = m_ColumnsData.AddToTail();
// put this index on the tail, so all item's m_SortedTreeIndexes have a consistent mapping
m_ColumnsHistory.AddToTail(columnDataIndex);
// put this column in the right place visually
m_CurrentColumns.InsertBefore(index, columnDataIndex);
// create the actual column object
column_t &column = m_ColumnsData[columnDataIndex];
// create the column header button
Button *pButton = SETUP_PANEL(new ColumnButton(this, columnName, columnText)); // the cell rendering mucks with the button visibility during the solvetraverse loop,
//so force applyschemesettings to make sure its run
pButton->SetSize(width, 24);
pButton->AddActionSignalTarget(this);
pButton->SetContentAlignment(Label::a_west);
pButton->SetTextInset(5, 0);
column.m_pHeader = pButton;
column.m_iMinWidth = minWidth;
column.m_iMaxWidth = maxWidth;
column.m_bResizesWithWindow = columnFlags & COLUMN_RESIZEWITHWINDOW;
column.m_bTypeIsText = !(columnFlags & COLUMN_IMAGE);
column.m_bHidden = false;
column.m_bUnhidable = (columnFlags & COLUMN_UNHIDABLE);
column.m_nContentAlignment = Label::a_west;
Dragger *dragger = new Dragger(index);
dragger->SetParent(this);
dragger->AddActionSignalTarget(this);
dragger->MoveToFront();
if (minWidth == maxWidth || (columnFlags & COLUMN_FIXEDSIZE))
{
// not resizable so disable the slider
dragger->SetMovable(false);
}
column.m_pResizer = dragger;
// add default sort function
column.m_pSortFunc = NULL;
// Set the SortedTree less than func to the generic RBTreeLessThanFunc
m_ColumnsData[columnDataIndex].m_SortedTree.SetLessFunc((IndexRBTree_t::LessFunc_t)RBTreeLessFunc);
// go through all the headers and make sure their Command has the right column ID
ResetColumnHeaderCommands();
// create the new data index
ResortColumnRBTree(index);
// ensure scroll bar is topmost compared to column headers
m_vbar->MoveToFront();
// fix up our visibility
SetColumnVisible(index, !(columnFlags & COLUMN_HIDDEN));
InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose: Recreates a column's RB Sorted Tree
//-----------------------------------------------------------------------------
void ListPanel::ResortColumnRBTree(int col)
{
Assert(m_CurrentColumns.IsValidIndex(col));
unsigned char dataColumnIndex = m_CurrentColumns[col];
int columnHistoryIndex = m_ColumnsHistory.Find(dataColumnIndex);
column_t &column = m_ColumnsData[dataColumnIndex];
IndexRBTree_t &rbtree = column.m_SortedTree;
// remove all elements - we're going to create from scratch
rbtree.RemoveAll();
s_pCurrentSortingListPanel = this;
s_currentSortingColumnTypeIsText = column.m_bTypeIsText; // type of data in the column
SortFunc *sortFunc = column.m_pSortFunc;
if ( !sortFunc )
{
sortFunc = DefaultSortFunc;
}
s_pSortFunc = sortFunc;
s_bSortAscending = true;
s_pSortFuncSecondary = NULL;
// sort all current data items for this column
FOR_EACH_LL( m_DataItems, i )
{
IndexItem_t item;
item.dataItem = m_DataItems[i];
item.duplicateIndex = 0;
FastSortListPanelItem *dataItem = (FastSortListPanelItem*) m_DataItems[i];
// if this item doesn't already have a SortedTreeIndex for this column,
// if can only be because this is the brand new column, so add it to the SortedTreeIndexes
if (dataItem->m_SortedTreeIndexes.Count() == m_ColumnsHistory.Count() - 1 &&
columnHistoryIndex == m_ColumnsHistory.Count() - 1)
{
dataItem->m_SortedTreeIndexes.AddToTail();
}
Assert( dataItem->m_SortedTreeIndexes.IsValidIndex(columnHistoryIndex) );
dataItem->m_SortedTreeIndexes[columnHistoryIndex] = rbtree.Insert(item);
}
}
//-----------------------------------------------------------------------------
// Purpose: Resets the "SetSortColumn" command for each column - in case columns were added or removed
//-----------------------------------------------------------------------------
void ListPanel::ResetColumnHeaderCommands()
{
int i;
for ( i = 0 ; i < m_CurrentColumns.Count() ; i++ )
{
Button *pButton = m_ColumnsData[m_CurrentColumns[i]].m_pHeader;
pButton->SetCommand(new KeyValues("SetSortColumn", "column", i));
}
}
//-----------------------------------------------------------------------------
// Purpose: Sets the header text for a particular column.
//-----------------------------------------------------------------------------
void ListPanel::SetColumnHeaderText(int col, const char *text)
{
m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetText(text);
}
void ListPanel::SetColumnHeaderText(int col, wchar_t *text)
{
m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetText(text);
}
void ListPanel::SetColumnTextAlignment( int col, int align )
{
m_ColumnsData[m_CurrentColumns[col]].m_nContentAlignment = align;
}
//-----------------------------------------------------------------------------
// Purpose: Sets the column header to have an image instead of text
//-----------------------------------------------------------------------------
void ListPanel::SetColumnHeaderImage(int column, int imageListIndex)
{
Assert(m_pImageList);
m_ColumnsData[m_CurrentColumns[column]].m_pHeader->SetTextImageIndex(-1);
m_ColumnsData[m_CurrentColumns[column]].m_pHeader->SetImageAtIndex(0, m_pImageList->GetImage(imageListIndex), 0);
}
//-----------------------------------------------------------------------------
// Purpose: associates a tooltip with the column header
//-----------------------------------------------------------------------------
void ListPanel::SetColumnHeaderTooltip(int column, const char *tooltipText)
{
m_ColumnsData[m_CurrentColumns[column]].m_pHeader->GetTooltip()->SetText(tooltipText);
m_ColumnsData[m_CurrentColumns[column]].m_pHeader->GetTooltip()->SetTooltipFormatToSingleLine();
m_ColumnsData[m_CurrentColumns[column]].m_pHeader->GetTooltip()->SetTooltipDelay(0);
}
int ListPanel::GetNumColumnHeaders() const
{
return m_CurrentColumns.Count();
}
bool ListPanel::GetColumnHeaderText( int index, char *pOut, int maxLen )
{
if ( index < m_CurrentColumns.Count() )
{
m_ColumnsData[m_CurrentColumns[index]].m_pHeader->GetText( pOut, maxLen );
return true;
}
else
{
return false;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SetColumnSortable(int col, bool sortable)
{
if (sortable)
{
m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetCommand(new KeyValues("SetSortColumn", "column", col));
}
else
{
m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetCommand((const char *)NULL);
}
}
//-----------------------------------------------------------------------------
// Purpose: Changes the visibility of a column
//-----------------------------------------------------------------------------
void ListPanel::SetColumnVisible(int col, bool visible)
{
column_t &column = m_ColumnsData[m_CurrentColumns[col]];
bool bHidden = !visible;
if (column.m_bHidden == bHidden)
return;
if (column.m_bUnhidable)
return;
column.m_bHidden = bHidden;
if (bHidden)
{
column.m_pHeader->SetVisible(false);
column.m_pResizer->SetVisible(false);
}
else
{
column.m_pHeader->SetVisible(true);
column.m_pResizer->SetVisible(true);
}
InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::RemoveColumn(int col)
{
if ( !m_CurrentColumns.IsValidIndex( col ) )
return;
// find the appropriate column data
unsigned char columnDataIndex = m_CurrentColumns[col];
// remove it from the current columns
m_CurrentColumns.Remove(col);
// zero out this entry in m_ColumnsHistory
unsigned char i;
for ( i = 0 ; i < m_ColumnsHistory.Count() ; i++ )
{
if ( m_ColumnsHistory[i] == columnDataIndex )
{
m_ColumnsHistory[i] = m_ColumnsData.InvalidIndex();
break;
}
}
Assert( i != m_ColumnsHistory.Count() );
// delete and remove the column data
m_ColumnsData[columnDataIndex].m_SortedTree.RemoveAll();
m_ColumnsData[columnDataIndex].m_pHeader->MarkForDeletion();
m_ColumnsData[columnDataIndex].m_pResizer->MarkForDeletion();
m_ColumnsData.Remove(columnDataIndex);
ResetColumnHeaderCommands();
InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose: Returns the index of a column by column->GetName()
//-----------------------------------------------------------------------------
int ListPanel::FindColumn(const char *columnName)
{
for (int i = 0; i < m_CurrentColumns.Count(); i++)
{
if (!stricmp(columnName, m_ColumnsData[m_CurrentColumns[i]].m_pHeader->GetName()))
{
return i;
}
}
return -1;
}
//-----------------------------------------------------------------------------
// 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 ListPanel::AddItem( const KeyValues *item, unsigned int userData, bool bScrollToItem, bool bSortOnAdd)
{
FastSortListPanelItem *newitem = new FastSortListPanelItem;
newitem->kv = item->MakeCopy();
newitem->userData = userData;
newitem->m_pDragData = NULL;
newitem->m_bImage = newitem->kv->GetInt( "image" ) != 0 ? true : false;
newitem->m_nImageIndex = newitem->kv->GetInt( "image" );
newitem->m_nImageIndexSelected = newitem->kv->GetInt( "imageSelected" );
newitem->m_pIcon = reinterpret_cast< IImage * >( newitem->kv->GetPtr( "iconImage" ) );
int itemID = m_DataItems.AddToTail(newitem);
int displayRow = m_VisibleItems.AddToTail(itemID);
newitem->visible = true;
// put the item in each column's sorted Tree Index
IndexItem(itemID);
if ( bSortOnAdd )
{
m_bNeedsSort = true;
}
InvalidateLayout();
if ( bScrollToItem )
{
// scroll to last item
m_vbar->SetValue(displayRow);
}
return itemID;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SetUserData( int itemID, unsigned int userData )
{
if ( !m_DataItems.IsValidIndex(itemID) )
return;
m_DataItems[itemID]->userData = userData;
}
//-----------------------------------------------------------------------------
// Purpose: Finds the first itemID with a matching userData
//-----------------------------------------------------------------------------
int ListPanel::GetItemIDFromUserData( unsigned int userData )
{
FOR_EACH_LL( m_DataItems, itemID )
{
if (m_DataItems[itemID]->userData == userData)
return itemID;
}
// not found
return InvalidItemID();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int ListPanel::GetItemCount( void )
{
return m_VisibleItems.Count();
}
//-----------------------------------------------------------------------------
// Purpose: gets the item ID of an item by name (data->GetName())
//-----------------------------------------------------------------------------
int ListPanel::GetItem(const char *itemName)
{
FOR_EACH_LL( m_DataItems, i )
{
if (!stricmp(m_DataItems[i]->kv->GetName(), itemName))
{
return i;
}
}
// failure
return -1;
}
//-----------------------------------------------------------------------------
// Purpose: returns pointer to data the itemID holds
//-----------------------------------------------------------------------------
KeyValues *ListPanel::GetItem(int itemID)
{
if ( !m_DataItems.IsValidIndex(itemID) )
return NULL;
return m_DataItems[itemID]->kv;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int ListPanel::GetItemCurrentRow(int itemID)
{
return m_VisibleItems.Find(itemID);
}
//-----------------------------------------------------------------------------
// Attaches drag data to a particular item
//-----------------------------------------------------------------------------
void ListPanel::SetItemDragData( int itemID, const KeyValues *data )
{
ListPanelItem *pItem = m_DataItems[ itemID ];
if ( pItem->m_pDragData )
{
pItem->m_pDragData->deleteThis();
}
pItem->m_pDragData = data->MakeCopy();
}
//-----------------------------------------------------------------------------
// Attaches drag data to a particular item
//-----------------------------------------------------------------------------
void ListPanel::OnCreateDragData( KeyValues *msg )
{
int nCount = GetSelectedItemsCount();
if ( nCount == 0 )
return;
for ( int i = 0; i < nCount; ++i )
{
int nItemID = GetSelectedItem( i );
KeyValues *pDragData = m_DataItems[ nItemID ]->m_pDragData;
if ( pDragData )
{
KeyValues *pDragDataCopy = pDragData->MakeCopy();
msg->AddSubKey( pDragDataCopy );
}
}
// Add the keys of the last item directly into the root also
int nLastItemID = GetSelectedItem( nCount - 1 );
KeyValues *pLastItemDrag = m_DataItems[ nLastItemID ]->m_pDragData;
if ( pLastItemDrag )
{
pLastItemDrag->CopySubkeys( msg );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int ListPanel::GetItemIDFromRow(int currentRow)
{
if (!m_VisibleItems.IsValidIndex(currentRow))
return -1;
return m_VisibleItems[currentRow];
}
int ListPanel::FirstItem() const
{
return m_DataItems.Head();
}
int ListPanel::NextItem( int iItem ) const
{
return m_DataItems.Next( iItem );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int ListPanel::InvalidItemID() const
{
return m_DataItems.InvalidIndex();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool ListPanel::IsValidItemID(int itemID)
{
return m_DataItems.IsValidIndex(itemID);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
ListPanelItem *ListPanel::GetItemData( int itemID )
{
if ( !m_DataItems.IsValidIndex(itemID) )
return NULL;
return m_DataItems[ itemID ];
}
//-----------------------------------------------------------------------------
// Purpose: returns user data for itemID
//-----------------------------------------------------------------------------
unsigned int ListPanel::GetItemUserData(int itemID)
{
if ( !m_DataItems.IsValidIndex(itemID) )
return 0;
return m_DataItems[itemID]->userData;
}
//-----------------------------------------------------------------------------
// Purpose: updates the view with any changes to the data
// Input : itemID - index to update
//-----------------------------------------------------------------------------
void ListPanel::ApplyItemChanges(int itemID)
{
// reindex the item and then redraw
IndexItem(itemID);
InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose: Adds the item into the column indexes
//-----------------------------------------------------------------------------
void ListPanel::IndexItem(int itemID)
{
FastSortListPanelItem *newitem = (FastSortListPanelItem*) m_DataItems[itemID];
// remove the item from the indexes and re-add
int maxCount = min(m_ColumnsHistory.Count(), newitem->m_SortedTreeIndexes.Count());
for (int i = 0; i < maxCount; i++)
{
IndexRBTree_t &rbtree = m_ColumnsData[m_ColumnsHistory[i]].m_SortedTree;
rbtree.RemoveAt(newitem->m_SortedTreeIndexes[i]);
}
// make sure it's all free
newitem->m_SortedTreeIndexes.RemoveAll();
// reserve one index per historical column - pad it out
newitem->m_SortedTreeIndexes.AddMultipleToTail(m_ColumnsHistory.Count());
// set the current sorting list (since the insert will need to sort)
s_pCurrentSortingListPanel = this;
// add the item into the RB tree for each column
for (int i = 0; i < m_ColumnsHistory.Count(); i++)
{
// skip over any removed columns
if ( m_ColumnsHistory[i] == m_ColumnsData.InvalidIndex() )
continue;
column_t &column = m_ColumnsData[m_ColumnsHistory[i]];
IndexItem_t item;
item.dataItem = newitem;
item.duplicateIndex = 0;
IndexRBTree_t &rbtree = column.m_SortedTree;
// setup sort state
s_pCurrentSortingListPanel = this;
s_pCurrentSortingColumn = column.m_pHeader->GetName(); // name of current column for sorting
s_currentSortingColumnTypeIsText = column.m_bTypeIsText; // type of data in the column
SortFunc *sortFunc = column.m_pSortFunc;
if (!sortFunc)
{
sortFunc = DefaultSortFunc;
}
s_pSortFunc = sortFunc;
s_bSortAscending = true;
s_pSortFuncSecondary = NULL;
// insert index
newitem->m_SortedTreeIndexes[i] = rbtree.Insert(item);
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::RereadAllItems()
{
//!! need to make this more efficient
InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Cleans up allocations associated with a particular item
//-----------------------------------------------------------------------------
void ListPanel::CleanupItem( FastSortListPanelItem *data )
{
if ( data )
{
if (data->kv)
{
data->kv->deleteThis();
}
if (data->m_pDragData)
{
data->m_pDragData->deleteThis();
}
delete data;
}
}
//-----------------------------------------------------------------------------
// Purpose: Removes an item at the specified item
//-----------------------------------------------------------------------------
void ListPanel::RemoveItem(int itemID)
{
#ifdef _X360
bool renavigate = false;
if(HasFocus())
{
for(int i = 0; i < GetSelectedItemsCount(); ++i)
{
if(itemID == GetSelectedItem(i))
{
renavigate = true;
break;
}
}
}
#endif
FastSortListPanelItem *data = (FastSortListPanelItem*) m_DataItems[itemID];
if (!data)
return;
// remove from column sorted indexes
int i;
for ( i = 0; i < m_ColumnsHistory.Count(); i++ )
{
if ( m_ColumnsHistory[i] == m_ColumnsData.InvalidIndex())
continue;
IndexRBTree_t &rbtree = m_ColumnsData[m_ColumnsHistory[i]].m_SortedTree;
rbtree.RemoveAt(data->m_SortedTreeIndexes[i]);
}
// remove from selection
m_SelectedItems.FindAndRemove(itemID);
PostActionSignal( new KeyValues("ItemDeselected") );
// remove from visible items
m_VisibleItems.FindAndRemove(itemID);
// remove from data
m_DataItems.Remove(itemID);
CleanupItem( data );
InvalidateLayout();
#ifdef _X360
if(renavigate)
{
NavigateTo();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: clears and deletes all the memory used by the data items
//-----------------------------------------------------------------------------
void ListPanel::RemoveAll()
{
// remove all sort indexes
for (int i = 0; i < m_ColumnsHistory.Count(); i++)
{
m_ColumnsData[m_ColumnsHistory[i]].m_SortedTree.RemoveAll();
}
FOR_EACH_LL( m_DataItems, index )
{
FastSortListPanelItem *pItem = m_DataItems[index];
CleanupItem( pItem );
}
m_DataItems.RemoveAll();
m_VisibleItems.RemoveAll();
ClearSelectedItems();
InvalidateLayout();
#ifdef _X360
if(HasFocus())
{
NavigateTo();
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: obselete, use RemoveAll();
//-----------------------------------------------------------------------------
void ListPanel::DeleteAllItems()
{
RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::ResetScrollBar()
{
// delete and reallocate to besure the scroll bar's
// information is correct.
delete m_vbar;
m_vbar = new ScrollBar(this, "VertScrollBar", true);
m_vbar->SetVisible(false);
m_vbar->AddActionSignalTarget(this);
}
//-----------------------------------------------------------------------------
// Purpose: returns the count of selected rows
//-----------------------------------------------------------------------------
int ListPanel::GetSelectedItemsCount()
{
return m_SelectedItems.Count();
}
//-----------------------------------------------------------------------------
// Purpose: returns the selected item by selection index
// Input : selectionIndex - valid in range [0, GetNumSelectedRows)
// Output : int - itemID
//-----------------------------------------------------------------------------
int ListPanel::GetSelectedItem(int selectionIndex)
{
if ( m_SelectedItems.IsValidIndex(selectionIndex))
return m_SelectedItems[selectionIndex];
return -1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int ListPanel::GetSelectedColumn()
{
return m_iSelectedColumn;
}
//-----------------------------------------------------------------------------
// Purpose: Clears all selected rows
//-----------------------------------------------------------------------------
void ListPanel::ClearSelectedItems()
{
int nPrevCount = m_SelectedItems.Count();
m_SelectedItems.RemoveAll();
if ( nPrevCount > 0 )
{
PostActionSignal( new KeyValues("ItemDeselected") );
}
m_LastItemSelected = -1;
m_iSelectedColumn = -1;
}
//-----------------------------------------------------------------------------
bool ListPanel::IsItemSelected( int itemID )
{
return m_DataItems.IsValidIndex( itemID ) && m_SelectedItems.HasElement( itemID );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::AddSelectedItem( int itemID )
{
if ( !m_DataItems.IsValidIndex(itemID) )
return;
Assert( !m_SelectedItems.HasElement( itemID ) );
m_LastItemSelected = itemID;
m_SelectedItems.AddToTail( itemID );
PostActionSignal( new KeyValues("ItemSelected") );
Repaint();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SetSingleSelectedItem( int itemID )
{
ClearSelectedItems();
AddSelectedItem(itemID);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SetSelectedCell(int itemID, int col)
{
if ( !m_bCanSelectIndividualCells )
{
SetSingleSelectedItem(itemID);
return;
}
// make sure it's a valid cell
if ( !m_DataItems.IsValidIndex(itemID) )
return;
if ( !m_CurrentColumns.IsValidIndex(col) )
return;
SetSingleSelectedItem( itemID );
m_iSelectedColumn = col;
}
//-----------------------------------------------------------------------------
// Purpose: returns the data held by a specific cell
//-----------------------------------------------------------------------------
void ListPanel::GetCellText(int itemID, int col, wchar_t *wbuffer, int bufferSizeInBytes)
{
if ( !wbuffer || !bufferSizeInBytes )
return;
wcscpy( wbuffer, L"" );
KeyValues *itemData = GetItem( itemID );
if ( !itemData )
{
return;
}
// Look up column header
if ( col < 0 || col >= m_CurrentColumns.Count() )
{
return;
}
const char *key = m_ColumnsData[m_CurrentColumns[col]].m_pHeader->GetName();
if ( !key || !key[ 0 ] )
{
return;
}
char const *val = itemData->GetString( key, "" );
if ( !val || !key[ 0 ] )
return;
const wchar_t *wval = NULL;
if ( val[ 0 ] == '#' )
{
StringIndex_t si = g_pVGuiLocalize->FindIndex( val + 1 );
if ( si != INVALID_LOCALIZE_STRING_INDEX )
{
wval = g_pVGuiLocalize->GetValueByIndex( si );
}
}
if ( !wval )
{
wval = itemData->GetWString( key, L"" );
}
wcsncpy( wbuffer, wval, bufferSizeInBytes/sizeof(wchar_t) );
wbuffer[ (bufferSizeInBytes/sizeof(wchar_t)) - 1 ] = 0;
}
//-----------------------------------------------------------------------------
// Purpose: returns the data held by a specific cell
//-----------------------------------------------------------------------------
IImage *ListPanel::GetCellImage(int itemID, int col) //, ImagePanel *&buffer)
{
// if ( !buffer )
// return;
KeyValues *itemData = GetItem( itemID );
if ( !itemData )
{
return NULL;
}
// Look up column header
if ( col < 0 || col >= m_CurrentColumns.Count() )
{
return NULL;
}
const char *key = m_ColumnsData[m_CurrentColumns[col]].m_pHeader->GetName();
if ( !key || !key[ 0 ] )
{
return NULL;
}
if ( !m_pImageList )
{
return NULL;
}
int imageIndex = itemData->GetInt( key, 0 );
if ( m_pImageList->IsValidIndex(imageIndex) )
{
if ( imageIndex > 0 )
{
return m_pImageList->GetImage(imageIndex);
}
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the panel to use to render a cell
//-----------------------------------------------------------------------------
Panel *ListPanel::GetCellRenderer(int itemID, int col)
{
Assert( m_pTextImage );
Assert( m_pImagePanel );
column_t &column = m_ColumnsData[ m_CurrentColumns[col] ];
IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
m_pLabel->SetContentAlignment( (Label::Alignment)column.m_nContentAlignment );
if ( column.m_bTypeIsText )
{
wchar_t tempText[ 256 ];
// Grab cell text
GetCellText( itemID, col, tempText, 256 );
KeyValues *item = GetItem( itemID );
m_pTextImage->SetText(tempText);
int cw, tall;
m_pTextImage->GetContentSize(cw, tall);
// set cell size
Panel *header = column.m_pHeader;
int wide = header->GetWide();
m_pTextImage->SetSize( min( cw, wide - 5 ), tall);
m_pLabel->SetTextImageIndex( 0 );
m_pLabel->SetImageAtIndex(0, m_pTextImage, 3);
bool selected = false;
if ( m_SelectedItems.HasElement(itemID) && ( !m_bCanSelectIndividualCells || col == m_iSelectedColumn ) )
{
selected = true;
VPANEL focus = input()->GetFocus();
// if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected
if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent())))
{
m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedBgColor", pScheme));
// selection
}
else
{
m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedOutOfFocusBgColor", pScheme));
}
if ( item->IsEmpty("cellcolor") == false )
{
m_pTextImage->SetColor( item->GetColor( "cellcolor" ) );
}
else if ( item->GetInt("disabled", 0) == 0 )
{
m_pTextImage->SetColor(m_SelectionFgColor);
}
else
{
m_pTextImage->SetColor(m_DisabledSelectionFgColor);
}
m_pLabel->SetPaintBackgroundEnabled(true);
}
else
{
if ( item->IsEmpty("cellcolor") == false )
{
m_pTextImage->SetColor( item->GetColor( "cellcolor" ) );
}
else if ( item->GetInt("disabled", 0) == 0 )
{
m_pTextImage->SetColor(m_LabelFgColor);
}
else
{
m_pTextImage->SetColor(m_DisabledColor);
}
m_pLabel->SetPaintBackgroundEnabled(false);
}
FastSortListPanelItem *listItem = m_DataItems[ itemID ];
if ( col == 0 &&
listItem->m_bImage && m_pImageList )
{
IImage *pImage = NULL;
if ( listItem->m_pIcon )
{
pImage = listItem->m_pIcon;
}
else
{
int imageIndex = selected ? listItem->m_nImageIndexSelected : listItem->m_nImageIndex;
if ( m_pImageList->IsValidIndex(imageIndex) )
{
pImage = m_pImageList->GetImage(imageIndex);
}
}
if ( pImage )
{
m_pLabel->SetTextImageIndex( 1 );
m_pLabel->SetImageAtIndex(0, pImage, 0);
m_pLabel->SetImageAtIndex(1, m_pTextImage, 3);
}
}
return m_pLabel;
}
else // if its an Image Panel
{
if ( m_SelectedItems.HasElement(itemID) && ( !m_bCanSelectIndividualCells || col == m_iSelectedColumn ) )
{
VPANEL focus = input()->GetFocus();
// if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected
if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent())))
{
m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedBgColor", pScheme));
// selection
}
else
{
m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedOutOfFocusBgColor", pScheme));
}
// selection
m_pLabel->SetPaintBackgroundEnabled(true);
}
else
{
m_pLabel->SetPaintBackgroundEnabled(false);
}
IImage *pIImage = GetCellImage(itemID, col);
m_pLabel->SetImageAtIndex(0, pIImage, 0);
return m_pLabel;
}
}
//-----------------------------------------------------------------------------
// Purpose: relayouts out the panel after any internal changes
//-----------------------------------------------------------------------------
void ListPanel::PerformLayout()
{
if ( m_CurrentColumns.Count() == 0 )
return;
if (m_bNeedsSort)
{
SortList();
}
int rowsperpage = (int) GetRowsPerPage();
// count the number of visible items
int visibleItemCount = m_VisibleItems.Count();
//!! need to make it recalculate scroll positions
m_vbar->SetVisible(true);
m_vbar->SetEnabled(false);
m_vbar->SetRangeWindow( rowsperpage );
m_vbar->SetRange( 0, visibleItemCount);
m_vbar->SetButtonPressedScrollValue( 1 );
int wide, tall;
GetSize( wide, tall );
m_vbar->SetPos(wide - (m_vbar->GetWide()+WINDOW_BORDER_WIDTH), 0);
m_vbar->SetSize(m_vbar->GetWide(), tall - 2);
m_vbar->InvalidateLayout();
int buttonMaxXPos = wide - (m_vbar->GetWide()+WINDOW_BORDER_WIDTH);
int nColumns = m_CurrentColumns.Count();
// number of bars that can be resized
int numToResize=0;
if (m_iColumnDraggerMoved != -1) // we're resizing in response to a column dragger
{
numToResize = 1; // only one column will change size, the one we dragged
}
else // we're resizing in response to a window resize
{
for (int i = 0; i < nColumns; i++)
{
if ( m_ColumnsData[m_CurrentColumns[i]].m_bResizesWithWindow // column is resizable in response to window
&& !m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
{
numToResize++;
}
}
}
int dxPerBar; // zero on window first opening
// location of the last column resizer
int oldSizeX = 0, oldSizeY = 0;
int lastColumnIndex = nColumns-1;
for (int i = nColumns-1; i >= 0; --i)
{
if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
{
m_ColumnsData[m_CurrentColumns[i]].m_pHeader->GetPos(oldSizeX, oldSizeY);
lastColumnIndex = i;
break;
}
}
bool bForceShrink = false;
if ( numToResize == 0 )
{
// make sure we've got enough to be within minwidth
int minWidth=0;
for (int i = 0; i < nColumns; i++)
{
if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
{
minWidth += m_ColumnsData[m_CurrentColumns[i]].m_iMinWidth;
}
}
// if all the minimum widths cannot fit in the space given, then we will shrink ALL columns an equal amount
if (minWidth > buttonMaxXPos)
{
int dx = buttonMaxXPos - minWidth;
dxPerBar=(int)((float)dx/(float)nColumns);
bForceShrink = true;
}
else
{
dxPerBar = 0;
}
m_lastBarWidth = buttonMaxXPos;
}
else if ( oldSizeX != 0 ) // make sure this isnt the first time we opened the window
{
int dx = buttonMaxXPos - m_lastBarWidth; // this is how much we grew or shrank.
// see how many bars we have and now much each should grow/shrink
dxPerBar=(int)((float)dx/(float)numToResize);
m_lastBarWidth = buttonMaxXPos;
}
else // this is the first time we've opened the window, make sure all our colums fit! resize if needed
{
int startingBarWidth=0;
for (int i = 0; i < nColumns; i++)
{
if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
{
startingBarWidth += m_ColumnsData[m_CurrentColumns[i]].m_pHeader->GetWide();
}
}
int dx = buttonMaxXPos - startingBarWidth; // this is how much we grew or shrank.
// see how many bars we have and now much each should grow/shrink
dxPerBar=(int)((float)dx/(float)numToResize);
m_lastBarWidth = buttonMaxXPos;
}
// Make sure nothing is smaller than minwidth to start with or else we'll get into trouble below.
for ( int i=0; i < nColumns; i++ )
{
column_t &column = m_ColumnsData[m_CurrentColumns[i]];
Panel *header = column.m_pHeader;
if ( header->GetWide() < column.m_iMinWidth )
header->SetWide( column.m_iMinWidth );
}
// This was a while(1) loop and we hit an infinite loop case, so now we max out the # of times it can loop.
for ( int iLoopSanityCheck=0; iLoopSanityCheck < 1000; iLoopSanityCheck++ )
{
// try and place headers as is - before we have to force items to be minimum width
int x = -1;
int i;
for ( i = 0; i < nColumns; i++)
{
column_t &column = m_ColumnsData[m_CurrentColumns[i]];
Panel *header = column.m_pHeader;
if (column.m_bHidden)
{
header->SetVisible(false);
continue;
}
header->SetPos(x, 0);
header->SetVisible(true);
// if we couldn't fit this column - then we need to force items to be minimum width
if ( x+column.m_iMinWidth >= buttonMaxXPos && !bForceShrink )
{
break;
}
int hWide = header->GetWide();
// calculate the column's width
// make it so the last column always attaches to the scroll bar
if ( i == lastColumnIndex )
{
hWide = buttonMaxXPos-x;
}
else if (i == m_iColumnDraggerMoved ) // column resizing using dragger
{
hWide += dxPerBar; // adjust width of column
}
else if ( m_iColumnDraggerMoved == -1 ) // window is resizing
{
// either this column is allowed to resize OR we are forcing it because we're shrinking all columns
if ( column.m_bResizesWithWindow || bForceShrink )
{
Assert ( column.m_iMinWidth <= column.m_iMaxWidth );
hWide += dxPerBar; // adjust width of column
}
}
// enforce column mins and max's - unless we're FORCING it to shrink
if ( hWide < column.m_iMinWidth && !bForceShrink )
{
hWide = column.m_iMinWidth; // adjust width of column
}
else if ( hWide > column.m_iMaxWidth )
{
hWide = column.m_iMaxWidth;
}
header->SetSize(hWide, m_vbar->GetWide());
x += hWide;
// set the resizers
Panel *sizer = column.m_pResizer;
if ( i == lastColumnIndex )
{
sizer->SetVisible(false);
}
else
{
sizer->SetVisible(true);
}
sizer->MoveToFront();
sizer->SetPos(x - 4, 0);
sizer->SetSize(8, m_vbar->GetWide());
}
// we made it all the way through
if ( i == nColumns )
break;
// we do this AFTER trying first, to let as many columns as possible try and get to their
// desired width before we forcing the minimum width on them
// get the total desired width of all the columns
int totalDesiredWidth = 0;
for ( i = 0 ; i < nColumns ; i++ )
{
if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
{
Panel *pHeader = m_ColumnsData[m_CurrentColumns[i]].m_pHeader;
totalDesiredWidth += pHeader->GetWide();
}
}
// shrink from the most right column to minimum width until we can fit them all
Assert(totalDesiredWidth > buttonMaxXPos);
for ( i = nColumns-1; i >= 0 ; i--)
{
column_t &column = m_ColumnsData[m_CurrentColumns[i]];
if (!column.m_bHidden)
{
Panel *pHeader = column.m_pHeader;
totalDesiredWidth -= pHeader->GetWide();
if ( totalDesiredWidth + column.m_iMinWidth <= buttonMaxXPos )
{
int newWidth = buttonMaxXPos - totalDesiredWidth;
pHeader->SetSize( newWidth, m_vbar->GetWide() );
break;
}
totalDesiredWidth += column.m_iMinWidth;
pHeader->SetSize(column.m_iMinWidth, m_vbar->GetWide());
}
}
// If we don't allow this to shrink, then as we resize, it can get stuck in an infinite loop.
dxPerBar -= 5;
if ( dxPerBar < 0 )
dxPerBar = 0;
if ( i == -1 )
{
break;
}
}
// setup edit mode
if ( m_hEditModePanel.Get() )
{
m_iTableStartX = 0;
m_iTableStartY = m_iHeaderHeight + 1;
int nTotalRows = m_VisibleItems.Count();
int nRowsPerPage = GetRowsPerPage();
// find the first visible item to display
int nStartItem = 0;
if (nRowsPerPage <= nTotalRows)
{
nStartItem = m_vbar->GetValue();
}
bool bDone = false;
int drawcount = 0;
for (int i = nStartItem; i < nTotalRows && !bDone; i++)
{
int x = 0;
if (!m_VisibleItems.IsValidIndex(i))
continue;
int itemID = m_VisibleItems[i];
// iterate the columns
for (int j = 0; j < m_CurrentColumns.Count(); j++)
{
Panel *header = m_ColumnsData[m_CurrentColumns[j]].m_pHeader;
if (!header->IsVisible())
continue;
wide = header->GetWide();
if ( itemID == m_iEditModeItemID &&
j == m_iEditModeColumn )
{
m_hEditModePanel->SetPos( x + m_iTableStartX + 2, (drawcount * m_iRowHeight) + m_iTableStartY);
m_hEditModePanel->SetSize( wide, m_iRowHeight - 1 );
bDone = true;
}
x += wide;
}
drawcount++;
}
}
Repaint();
m_iColumnDraggerMoved = -1; // reset to invalid column
m_iHeaderHeight = m_ColumnsData[0].m_pHeader ? m_ColumnsData[0].m_pHeader->GetTall() : m_iHeaderHeight;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::OnSizeChanged(int wide, int tall)
{
BaseClass::OnSizeChanged(wide, tall);
InvalidateLayout();
Repaint();
}
//-----------------------------------------------------------------------------
// Purpose: Renders the cells
//-----------------------------------------------------------------------------
void ListPanel::Paint()
{
if (m_bNeedsSort)
{
SortList();
}
// draw selection areas if any
int wide, tall;
GetSize( wide, tall );
m_iTableStartX = 0;
m_iTableStartY = m_iHeaderHeight + 1;
int nTotalRows = m_VisibleItems.Count();
int nRowsPerPage = GetRowsPerPage();
// find the first visible item to display
int nStartItem = 0;
if (nRowsPerPage <= nTotalRows)
{
nStartItem = m_vbar->GetValue();
}
int vbarInset = m_vbar->IsVisible() ? m_vbar->GetWide() : 0;
int maxw = wide - vbarInset - 8;
// debug timing functions
// double startTime, endTime;
// startTime = system()->GetCurrentTime();
// iterate through and draw each cell
bool bDone = false;
int drawcount = 0;
for (int i = nStartItem; i < nTotalRows && !bDone; i++)
{
int x = 0;
if (!m_VisibleItems.IsValidIndex(i))
continue;
int itemID = m_VisibleItems[i];
// iterate the columns
for (int j = 0; j < m_CurrentColumns.Count(); j++)
{
Panel *header = m_ColumnsData[m_CurrentColumns[j]].m_pHeader;
Panel *render = GetCellRenderer(itemID, j);
if (!header->IsVisible())
continue;
int hWide = header->GetWide();
if (render)
{
// setup render panel
if (render->GetVParent() != GetVPanel())
{
render->SetParent(GetVPanel());
}
if (!render->IsVisible())
{
render->SetVisible(true);
}
int xpos = x + m_iTableStartX + 2;
render->SetPos( xpos, (drawcount * m_iRowHeight) + m_iTableStartY);
int right = min( xpos + hWide, maxw );
int usew = right - xpos;
render->SetSize( usew, m_iRowHeight - 1 );
// mark the panel to draw immediately (since it will probably be recycled to draw other cells)
render->Repaint();
surface()->SolveTraverse(render->GetVPanel());
int x0, y0, x1, y1;
render->GetClipRect(x0, y0, x1, y1);
if ((y1 - y0) < (m_iRowHeight - 3))
{
bDone = true;
break;
}
surface()->PaintTraverse(render->GetVPanel());
}
/*
// work in progress, optimized paint for text
else
{
// just paint it ourselves
char tempText[256];
// Grab cell text
GetCellText(i, j, tempText, sizeof(tempText));
surface()->DrawSetTextPos(x + m_iTableStartX + 2, (drawcount * m_iRowHeight) + m_iTableStartY);
for (const char *pText = tempText; *pText != 0; pText++)
{
surface()->DrawUnicodeChar((wchar_t)*pText);
}
}
*/
x += hWide;
}
drawcount++;
}
m_pLabel->SetVisible(false);
// if the list is empty, draw some help text
if (m_VisibleItems.Count() < 1 && m_pEmptyListText)
{
m_pEmptyListText->SetPos(m_iTableStartX + 8, m_iTableStartY + 4);
m_pEmptyListText->SetSize(wide - 8, m_iRowHeight);
m_pEmptyListText->Paint();
}
// endTime = system()->GetCurrentTime();
// ivgui()->DPrintf2("ListPanel::Paint() (%.3f sec)\n", (float)(endTime - startTime));
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::PaintBackground()
{
BaseClass::PaintBackground();
}
//-----------------------------------------------------------------------------
// Handles multiselect
//-----------------------------------------------------------------------------
void ListPanel::HandleMultiSelection( int itemID, int row, int column )
{
// deal with 'multiple' row selection
// convert the last item selected to a row so we can multiply select by rows NOT items
int lastSelectedRow = (m_LastItemSelected != -1) ? m_VisibleItems.Find( m_LastItemSelected ) : row;
int startRow, endRow;
if ( row < lastSelectedRow )
{
startRow = row;
endRow = lastSelectedRow;
}
else
{
startRow = lastSelectedRow;
endRow = row;
}
// clear the selection if neither control key was down - we are going to readd ALL selected items
// in case the user changed the 'direction' of the shift add
if ( !input()->IsKeyDown(KEY_LCONTROL) && !input()->IsKeyDown(KEY_RCONTROL) )
{
ClearSelectedItems();
}
// add any items that we haven't added
for (int i = startRow; i <= endRow; i++)
{
// get the item indexes for these rows
int selectedItemID = m_VisibleItems[i];
if ( !m_SelectedItems.HasElement(selectedItemID) )
{
AddSelectedItem( selectedItemID );
}
}
}
//-----------------------------------------------------------------------------
// Handles multiselect
//-----------------------------------------------------------------------------
void ListPanel::HandleAddSelection( int itemID, int row, int column )
{
// dealing with row selection
if ( m_SelectedItems.HasElement( itemID ) )
{
// this row is already selected, remove
m_SelectedItems.FindAndRemove( itemID );
PostActionSignal( new KeyValues("ItemDeselected") );
m_LastItemSelected = itemID;
}
else
{
// add the row to the selection
AddSelectedItem( itemID );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::UpdateSelection( MouseCode code, int x, int y, int row, int column )
{
// make sure we're clicking on a real item
if ( row < 0 || row >= m_VisibleItems.Count() )
{
ClearSelectedItems();
return;
}
int itemID = m_VisibleItems[ row ];
// if we've right-clicked on a selection, don't change the selection
if ( code == MOUSE_RIGHT && m_SelectedItems.HasElement( itemID ) )
return;
if ( m_bCanSelectIndividualCells )
{
if ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) )
{
// we're ctrl selecting the same cell, clear it
if ( ( m_LastItemSelected == itemID ) && ( m_iSelectedColumn == column ) && ( m_SelectedItems.Count() == 1 ) )
{
ClearSelectedItems();
}
else
{
SetSelectedCell( itemID, column );
}
}
else
{
SetSelectedCell( itemID, column );
}
return;
}
if ( !m_bMultiselectEnabled )
{
SetSingleSelectedItem( itemID );
return;
}
if ( input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT) )
{
// check for multi-select
HandleMultiSelection( itemID, row, column );
}
else if ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) )
{
// check for row-add select
HandleAddSelection( itemID, row, column );
}
else
{
// no CTRL or SHIFT keys
// reset the selection Start point
// if ( ( m_LastItemSelected != itemID ) || ( m_SelectedItems.Count() > 1 ) )
{
SetSingleSelectedItem( itemID );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::OnMousePressed( MouseCode code )
{
if (code == MOUSE_LEFT || code == MOUSE_RIGHT)
{
if ( m_VisibleItems.Count() > 0 )
{
// determine where we were pressed
int x, y, row, column;
input()->GetCursorPos(x, y);
GetCellAtPos(x, y, row, column);
UpdateSelection( code, x, y, row, column );
}
// get the key focus
RequestFocus();
}
// check for context menu open
if (code == MOUSE_RIGHT)
{
if ( m_SelectedItems.Count() > 0 )
{
PostActionSignal( new KeyValues("OpenContextMenu", "itemID", m_SelectedItems[0] ));
}
else
{
// post it, but with the invalid row
PostActionSignal( new KeyValues("OpenContextMenu", "itemID", -1 ));
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Scrolls the list according to the mouse wheel movement
//-----------------------------------------------------------------------------
void ListPanel::OnMouseWheeled(int delta)
{
if (m_hEditModePanel.Get())
{
// ignore mouse wheel in edit mode, forward right up to parent
CallParentFunction(new KeyValues("MouseWheeled", "delta", delta));
return;
}
int val = m_vbar->GetValue();
val -= (delta * 3);
m_vbar->SetValue(val);
}
//-----------------------------------------------------------------------------
// Purpose: Double-click act like the the item under the mouse was selected
// and then the enter key hit
//-----------------------------------------------------------------------------
void ListPanel::OnMouseDoublePressed(MouseCode code)
{
if (code == MOUSE_LEFT)
{
// select the item
OnMousePressed(code);
// post up an enter key being hit if anything was selected
if (GetSelectedItemsCount() > 0 && !m_bIgnoreDoubleClick )
{
OnKeyCodeTyped(KEY_ENTER);
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
#ifdef _X360
void ListPanel::OnKeyCodePressed(KeyCode code)
{
int nTotalRows = m_VisibleItems.Count();
int nTotalColumns = m_CurrentColumns.Count();
if ( nTotalRows == 0 )
return;
// calculate info for adjusting scrolling
int nStartItem = GetStartItem();
int nRowsPerPage = (int)GetRowsPerPage();
int nSelectedRow = 0;
if ( m_DataItems.IsValidIndex( m_LastItemSelected ) )
{
nSelectedRow = m_VisibleItems.Find( m_LastItemSelected );
}
int nSelectedColumn = m_iSelectedColumn;
switch(code)
{
case KEY_XBUTTON_UP:
case KEY_XSTICK1_UP:
case KEY_XSTICK2_UP:
if(GetItemCount() < 1 || nSelectedRow == nStartItem)
{
ClearSelectedItems();
BaseClass::OnKeyCodePressed(code);
return;
}
else
{
nSelectedRow -= 1;
}
break;
case KEY_XBUTTON_DOWN:
case KEY_XSTICK1_DOWN:
case KEY_XSTICK2_DOWN:
{
int itemId = GetSelectedItem(0);
if(itemId != -1 && GetItemCurrentRow(itemId) == (nTotalRows - 1))
{
ClearSelectedItems();
BaseClass::OnKeyCodePressed(code);
return;
}
else
{
nSelectedRow += 1;
}
}
break;
case KEY_XBUTTON_LEFT:
case KEY_XSTICK1_LEFT:
case KEY_XSTICK2_LEFT:
if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) )
{
nSelectedColumn--;
if (nSelectedColumn < 0)
{
nSelectedColumn = 0;
}
break;
}
break;
case KEY_XBUTTON_RIGHT:
case KEY_XSTICK1_RIGHT:
case KEY_XSTICK2_RIGHT:
if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) )
{
nSelectedColumn++;
if (nSelectedColumn >= nTotalColumns)
{
nSelectedColumn = nTotalColumns - 1;
}
break;
}
break;
case KEY_XBUTTON_A:
PostActionSignal( new KeyValues("ListPanelItemChosen", "itemID", m_SelectedItems[0] ));
break;
default:
BaseClass::OnKeyCodePressed(code);
break;
}
// make sure newly selected item is a valid range
nSelectedRow = clamp(nSelectedRow, 0, nTotalRows - 1);
int row = m_VisibleItems[ nSelectedRow ];
// This will select the cell if in single select mode, or the row in multiselect mode
if ( ( row != m_LastItemSelected ) || ( nSelectedColumn != m_iSelectedColumn ) || ( m_SelectedItems.Count() > 1 ) )
{
SetSelectedCell( row, nSelectedColumn );
}
// move the newly selected item to within the visible range
if ( nRowsPerPage < nTotalRows )
{
int nStartItem = m_vbar->GetValue();
if ( nSelectedRow < nStartItem )
{
// move the list back to match
m_vbar->SetValue( nSelectedRow );
}
else if ( nSelectedRow >= nStartItem + nRowsPerPage )
{
// move list forward to match
m_vbar->SetValue( nSelectedRow - nRowsPerPage + 1);
}
}
// redraw
InvalidateLayout();
}
#else
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::OnKeyCodePressed(KeyCode code)
{
if (m_hEditModePanel.Get())
{
// ignore arrow keys in edit mode
// forward right up to parent so that tab focus change doesn't occur
CallParentFunction(new KeyValues("KeyCodePressed", "code", code));
return;
}
int nTotalRows = m_VisibleItems.Count();
int nTotalColumns = m_CurrentColumns.Count();
if ( nTotalRows == 0 )
{
BaseClass::OnKeyCodePressed(code);
return;
}
// calculate info for adjusting scrolling
int nStartItem = GetStartItem();
int nRowsPerPage = (int)GetRowsPerPage();
int nSelectedRow = 0;
if ( m_DataItems.IsValidIndex( m_LastItemSelected ) )
{
nSelectedRow = m_VisibleItems.Find( m_LastItemSelected );
}
int nSelectedColumn = m_iSelectedColumn;
switch (code)
{
case KEY_HOME:
nSelectedRow = 0;
break;
case KEY_END:
nSelectedRow = nTotalRows - 1;
break;
case KEY_PAGEUP:
if (nSelectedRow <= nStartItem)
{
// move up a page
nSelectedRow -= (nRowsPerPage - 1);
}
else
{
// move to the top of the current page
nSelectedRow = nStartItem;
}
break;
case KEY_PAGEDOWN:
if (nSelectedRow >= (nStartItem + nRowsPerPage-1))
{
// move down a page
nSelectedRow += (nRowsPerPage - 1);
}
else
{
// move to the bottom of the current page
nSelectedRow = nStartItem + (nRowsPerPage - 1);
}
break;
case KEY_UP:
case KEY_XBUTTON_UP:
case KEY_XSTICK1_UP:
case KEY_XSTICK2_UP:
case STEAMCONTROLLER_DPAD_UP:
if ( nTotalRows > 0 )
{
nSelectedRow--;
break;
}
// fall through
case KEY_DOWN:
case KEY_XBUTTON_DOWN:
case KEY_XSTICK1_DOWN:
case KEY_XSTICK2_DOWN:
case STEAMCONTROLLER_DPAD_DOWN:
if ( nTotalRows > 0 )
{
nSelectedRow++;
break;
}
// fall through
case KEY_LEFT:
case KEY_XBUTTON_LEFT:
case KEY_XSTICK1_LEFT:
case KEY_XSTICK2_LEFT:
if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) )
{
nSelectedColumn--;
if (nSelectedColumn < 0)
{
nSelectedColumn = 0;
}
break;
}
// fall through
case KEY_RIGHT:
case KEY_XBUTTON_RIGHT:
case KEY_XSTICK1_RIGHT:
case KEY_XSTICK2_RIGHT:
if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) )
{
nSelectedColumn++;
if (nSelectedColumn >= nTotalColumns)
{
nSelectedColumn = nTotalColumns - 1;
}
break;
}
// fall through
default:
// chain back
BaseClass::OnKeyCodePressed(code);
return;
};
// make sure newly selected item is a valid range
nSelectedRow = clamp(nSelectedRow, 0, nTotalRows - 1);
int row = m_VisibleItems[ nSelectedRow ];
// This will select the cell if in single select mode, or the row in multiselect mode
if ( ( row != m_LastItemSelected ) || ( nSelectedColumn != m_iSelectedColumn ) || ( m_SelectedItems.Count() > 1 ) )
{
SetSelectedCell( row, nSelectedColumn );
}
// move the newly selected item to within the visible range
if ( nRowsPerPage < nTotalRows )
{
nStartItem = m_vbar->GetValue();
if ( nSelectedRow < nStartItem )
{
// move the list back to match
m_vbar->SetValue( nSelectedRow );
}
else if ( nSelectedRow >= nStartItem + nRowsPerPage )
{
// move list forward to match
m_vbar->SetValue( nSelectedRow - nRowsPerPage + 1);
}
}
// redraw
InvalidateLayout();
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool ListPanel::GetCellBounds( int row, int col, int& x, int& y, int& wide, int& tall )
{
if ( col < 0 || col >= m_CurrentColumns.Count() )
return false;
if ( row < 0 || row >= m_VisibleItems.Count() )
return false;
// Is row on screen?
int startitem = GetStartItem();
if ( row < startitem || row >= ( startitem + GetRowsPerPage() ) )
return false;
y = m_iTableStartY;
y += ( row - startitem ) * m_iRowHeight;
tall = m_iRowHeight;
// Compute column cell
x = m_iTableStartX;
// walk columns
int c = 0;
while ( c < col)
{
x += m_ColumnsData[m_CurrentColumns[c]].m_pHeader->GetWide();
c++;
}
wide = m_ColumnsData[m_CurrentColumns[c]].m_pHeader->GetWide();
return true;
}
//-----------------------------------------------------------------------------
// Purpose: returns true if any found, row and column are filled out
//-----------------------------------------------------------------------------
bool ListPanel::GetCellAtPos(int x, int y, int &row, int &col)
{
// convert to local
ScreenToLocal(x, y);
// move to Start of table
x -= m_iTableStartX;
y -= m_iTableStartY;
int startitem = GetStartItem();
// make sure it's still in valid area
if ( x >= 0 && y >= 0 )
{
// walk the rows (for when row height is independant each row)
// NOTE: if we do height independent rows, we will need to change GetCellBounds as well
for ( row = startitem ; row < m_VisibleItems.Count() ; row++ )
{
if ( y < ( ( ( row - startitem ) + 1 ) * m_iRowHeight ) )
break;
}
// walk columns
int startx = 0;
for ( col = 0 ; col < m_CurrentColumns.Count() ; col++ )
{
startx += m_ColumnsData[m_CurrentColumns[col]].m_pHeader->GetWide();
if ( x < startx )
break;
}
// make sure we're not out of range
if ( ! ( row == m_VisibleItems.Count() || col == m_CurrentColumns.Count() ) )
{
return true;
}
}
// out-of-bounds
row = col = -1;
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::ApplySchemeSettings(IScheme *pScheme)
{
// force label to apply scheme settings now so we can override it
m_pLabel->InvalidateLayout(true);
BaseClass::ApplySchemeSettings(pScheme);
SetBgColor(GetSchemeColor("ListPanel.BgColor", pScheme));
SetBorder(pScheme->GetBorder("ButtonDepressedBorder"));
m_pLabel->SetBgColor(GetSchemeColor("ListPanel.BgColor", pScheme));
m_LabelFgColor = GetSchemeColor("ListPanel.TextColor", pScheme);
m_DisabledColor = GetSchemeColor("ListPanel.DisabledTextColor", m_LabelFgColor, pScheme);
m_SelectionFgColor = GetSchemeColor("ListPanel.SelectedTextColor", m_LabelFgColor, pScheme);
m_DisabledSelectionFgColor = GetSchemeColor("ListPanel.DisabledSelectedTextColor", m_LabelFgColor, pScheme);
m_pEmptyListText->SetColor(GetSchemeColor("ListPanel.EmptyListInfoTextColor", pScheme));
SetFont( pScheme->GetFont("Default", IsProportional() ) );
m_pEmptyListText->SetFont( pScheme->GetFont( "Default", IsProportional() ) );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SetSortFunc(int col, SortFunc *func)
{
Assert(col < m_CurrentColumns.Count());
unsigned char dataColumnIndex = m_CurrentColumns[col];
if ( !m_ColumnsData[dataColumnIndex].m_bTypeIsText && func != NULL)
{
m_ColumnsData[dataColumnIndex].m_pHeader->SetMouseClickEnabled(MOUSE_LEFT, 1);
}
m_ColumnsData[dataColumnIndex].m_pSortFunc = func;
// resort this column according to new sort func
ResortColumnRBTree(col);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SetSortColumn(int column)
{
m_iSortColumn = column;
}
int ListPanel::GetSortColumn() const
{
return m_iSortColumn;
}
void ListPanel::SetSortColumnEx( int iPrimarySortColumn, int iSecondarySortColumn, bool bSortAscending )
{
m_iSortColumn = iPrimarySortColumn;
m_iSortColumnSecondary = iSecondarySortColumn;
m_bSortAscending = bSortAscending;
}
void ListPanel::GetSortColumnEx( int &iPrimarySortColumn, int &iSecondarySortColumn, bool &bSortAscending ) const
{
iPrimarySortColumn = m_iSortColumn;
iSecondarySortColumn = m_iSortColumnSecondary;
bSortAscending = m_bSortAscending;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SortList( void )
{
m_bNeedsSort = false;
if ( m_VisibleItems.Count() <= 1 )
{
return;
}
// check if the last selected item is on the screen - if so, we should try to maintain it on screen
int startItem = GetStartItem();
int rowsperpage = (int) GetRowsPerPage();
int screenPosition = -1;
if ( m_LastItemSelected != -1 && m_SelectedItems.Count() > 0 )
{
int selectedItemRow = m_VisibleItems.Find(m_LastItemSelected);
if ( selectedItemRow >= startItem && selectedItemRow <= ( startItem + rowsperpage ) )
{
screenPosition = selectedItemRow - startItem;
}
}
// get the required sorting functions
s_pCurrentSortingListPanel = this;
// setup globals for use in qsort
s_pSortFunc = FastSortFunc;
s_bSortAscending = m_bSortAscending;
s_pSortFuncSecondary = FastSortFunc;
s_bSortAscendingSecondary = m_bSortAscendingSecondary;
// walk the tree and set up the current indices
if (m_CurrentColumns.IsValidIndex(m_iSortColumn))
{
IndexRBTree_t &rbtree = m_ColumnsData[m_CurrentColumns[m_iSortColumn]].m_SortedTree;
unsigned int index = rbtree.FirstInorder();
unsigned int lastIndex = rbtree.LastInorder();
int prevDuplicateIndex = 0;
int sortValue = 1;
while (1)
{
FastSortListPanelItem *dataItem = (FastSortListPanelItem*) rbtree[index].dataItem;
if (dataItem->visible)
{
// only increment the sort value if we're a different token from the previous
if (!prevDuplicateIndex || prevDuplicateIndex != rbtree[index].duplicateIndex)
{
sortValue++;
}
dataItem->primarySortIndexValue = sortValue;
prevDuplicateIndex = rbtree[index].duplicateIndex;
}
if (index == lastIndex)
break;
index = rbtree.NextInorder(index);
}
}
// setup secondary indices
if (m_CurrentColumns.IsValidIndex(m_iSortColumnSecondary))
{
IndexRBTree_t &rbtree = m_ColumnsData[m_CurrentColumns[m_iSortColumnSecondary]].m_SortedTree;
unsigned int index = rbtree.FirstInorder();
unsigned int lastIndex = rbtree.LastInorder();
int sortValue = 1;
int prevDuplicateIndex = 0;
while (1)
{
FastSortListPanelItem *dataItem = (FastSortListPanelItem*) rbtree[index].dataItem;
if (dataItem->visible)
{
// only increment the sort value if we're a different token from the previous
if (!prevDuplicateIndex || prevDuplicateIndex != rbtree[index].duplicateIndex)
{
sortValue++;
}
dataItem->secondarySortIndexValue = sortValue;
prevDuplicateIndex = rbtree[index].duplicateIndex;
}
if (index == lastIndex)
break;
index = rbtree.NextInorder(index);
}
}
// quick sort the list
qsort(m_VisibleItems.Base(), (size_t) m_VisibleItems.Count(), (size_t) sizeof(int), AscendingSortFunc);
if ( screenPosition != -1 )
{
int selectedItemRow = m_VisibleItems.Find(m_LastItemSelected);
// if we can put the last selected item in exactly the same spot, put it there, otherwise
// we need to be at the top of the list
if (selectedItemRow > screenPosition)
{
m_vbar->SetValue(selectedItemRow - screenPosition);
}
else
{
m_vbar->SetValue(0);
}
}
InvalidateLayout();
Repaint();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::SetFont(HFont font)
{
Assert( font );
if ( !font )
return;
m_pTextImage->SetFont(font);
m_iRowHeight = surface()->GetFontTall(font) + 2;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ListPanel::OnSliderMoved()
{
InvalidateLayout();
Repaint();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : deltax - deltas from current position
//-----------------------------------------------------------------------------
void ListPanel::OnColumnResized(int col, int delta)
{
m_iColumnDraggerMoved = col;
column_t& column = m_ColumnsData[m_CurrentColumns[col]];
Panel *header = column.m_pHeader;
int wide, tall;
header->GetSize(wide, tall);
wide += delta;
// enforce minimum sizes for the header
if ( wide < column.m_iMinWidth )
{
wide = column.m_iMinWidth;
}
// enforce maximum sizes for the header
if ( wide > column.m_iMaxWidth )
{
wide = column.m_iMaxWidth;
}
// make sure we have enough space for the columns to our right
int panelWide, panelTall;
GetSize( panelWide, panelTall );
int x, y;
header->GetPos(x, y);
int restColumnsMinWidth = 0;
int i;
for ( i = col+1 ; i < m_CurrentColumns.Count() ; i++ )
{
column_t& nextCol = m_ColumnsData[m_CurrentColumns[i]];
restColumnsMinWidth += nextCol.m_iMinWidth;
}
panelWide -= ( x + restColumnsMinWidth + m_vbar->GetWide() + WINDOW_BORDER_WIDTH );
if ( wide > panelWide )
{
wide = panelWide;
}
header->SetSize(wide, tall);
// the adjacent header will be moved automatically in PerformLayout()
header->InvalidateLayout();
InvalidateLayout();
Repaint();
}
//-----------------------------------------------------------------------------
// Purpose: sets which column we should sort with
//-----------------------------------------------------------------------------
void ListPanel::OnSetSortColumn(int column)
{
// if it's the primary column already, flip the sort direction
if (m_iSortColumn == column)
{
m_bSortAscending = !m_bSortAscending;
}
else
{
// switching sort columns, keep the old one as the secondary sort
m_iSortColumnSecondary = m_iSortColumn;
m_bSortAscendingSecondary = m_bSortAscending;
}
SetSortColumn(column);
SortList();
}
//-----------------------------------------------------------------------------
// Purpose: sets whether the item is visible or not
//-----------------------------------------------------------------------------
void ListPanel::SetItemVisible(int itemID, bool state)
{
if ( !m_DataItems.IsValidIndex(itemID) )
return;
FastSortListPanelItem *data = (FastSortListPanelItem*) m_DataItems[itemID];
if (data->visible == state)
return;
m_bNeedsSort = true;
data->visible = state;
if (data->visible)
{
// add back to end of list
m_VisibleItems.AddToTail(itemID);
}
else
{
// remove from selection if it is there.
if (m_SelectedItems.HasElement(itemID))
{
m_SelectedItems.FindAndRemove(itemID);
PostActionSignal( new KeyValues("ItemDeselected") );
}
// remove from data
m_VisibleItems.FindAndRemove(itemID);
InvalidateLayout();
}
}
//-----------------------------------------------------------------------------
// Is the item visible?
//-----------------------------------------------------------------------------
bool ListPanel::IsItemVisible( int itemID )
{
if ( !m_DataItems.IsValidIndex(itemID) )
return false;
FastSortListPanelItem *data = (FastSortListPanelItem*) m_DataItems[itemID];
return data->visible;
}
//-----------------------------------------------------------------------------
// Purpose: sets whether the item is disabled or not (effects item color)
//-----------------------------------------------------------------------------
void ListPanel::SetItemDisabled(int itemID, bool state)
{
if ( !m_DataItems.IsValidIndex(itemID) )
return;
m_DataItems[itemID]->kv->SetInt( "disabled", state );
}
//-----------------------------------------------------------------------------
// Purpose: Calculate number of rows per page
//-----------------------------------------------------------------------------
float ListPanel::GetRowsPerPage()
{
float rowsperpage = (float)( GetTall() - m_iHeaderHeight ) / (float)m_iRowHeight;
return rowsperpage;
}
//-----------------------------------------------------------------------------
// Purpose: Calculate the item we should Start on
//-----------------------------------------------------------------------------
int ListPanel::GetStartItem()
{
// if rowsperpage < total number of rows
if ( GetRowsPerPage() < (float) m_VisibleItems.Count() )
{
return m_vbar->GetValue();
}
return 0; // otherwise Start at top
}
//-----------------------------------------------------------------------------
// Purpose: whether or not to select specific cells (off by default)
//-----------------------------------------------------------------------------
void ListPanel::SetSelectIndividualCells(bool state)
{
m_bCanSelectIndividualCells = state;
}
//-----------------------------------------------------------------------------
// whether or not multiple cells/rows can be selected
//-----------------------------------------------------------------------------
void ListPanel::SetMultiselectEnabled( bool bState )
{
m_bMultiselectEnabled = bState;
}
bool ListPanel::IsMultiselectEnabled() const
{
return m_bMultiselectEnabled;
}
//-----------------------------------------------------------------------------
// Purpose: Sets the text which is displayed when the list is empty
//-----------------------------------------------------------------------------
void ListPanel::SetEmptyListText(const char *text)
{
m_pEmptyListText->SetText(text);
Repaint();
}
//-----------------------------------------------------------------------------
// Purpose: Sets the text which is displayed when the list is empty
//-----------------------------------------------------------------------------
void ListPanel::SetEmptyListText(const wchar_t *text)
{
m_pEmptyListText->SetText(text);
Repaint();
}
//-----------------------------------------------------------------------------
// Purpose: opens the content menu
//-----------------------------------------------------------------------------
void ListPanel::OpenColumnChoiceMenu()
{
if (!m_bAllowUserAddDeleteColumns)
return;
Menu *menu = new Menu(this, "ContextMenu");
int x, y;
input()->GetCursorPos(x, y);
menu->SetPos(x, y);
// add all the column choices to the menu
for ( int i = 0 ; i < m_CurrentColumns.Count() ; i++ )
{
column_t &column = m_ColumnsData[m_CurrentColumns[i]];
char name[128];
column.m_pHeader->GetText(name, sizeof(name));
int itemID = menu->AddCheckableMenuItem(name, new KeyValues("ToggleColumnVisible", "col", m_CurrentColumns[i]), this);
menu->SetMenuItemChecked(itemID, !column.m_bHidden);
if (column.m_bUnhidable)
{
menu->SetItemEnabled(itemID, false);
}
}
menu->SetVisible(true);
}
//-----------------------------------------------------------------------------
// Purpose: Resizes a column
//-----------------------------------------------------------------------------
void ListPanel::ResizeColumnToContents(int column)
{
// iterate all the items in the column, getting the size of each
column_t &col = m_ColumnsData[m_CurrentColumns[column]];
if (!col.m_bTypeIsText)
return;
// start with the size of the column text
int wide = 0, minRequiredWidth = 0, tall = 0;
col.m_pHeader->GetContentSize( minRequiredWidth, tall );
// iterate every item
for (int i = 0; i < m_VisibleItems.Count(); i++)
{
if (!m_VisibleItems.IsValidIndex(i))
continue;
// get the cell
int itemID = m_VisibleItems[i];
// get the text
wchar_t tempText[ 256 ];
GetCellText( itemID, column, tempText, 256 );
m_pTextImage->SetText(tempText);
m_pTextImage->GetContentSize(wide, tall);
if ( wide > minRequiredWidth )
{
minRequiredWidth = wide;
}
}
// Introduce a slight buffer between columns
minRequiredWidth += 4;
// call the resize
col.m_pHeader->GetSize(wide, tall);
OnColumnResized(column, minRequiredWidth - wide);
}
//-----------------------------------------------------------------------------
// Purpose: Changes the visibilty of a column
//-----------------------------------------------------------------------------
void ListPanel::OnToggleColumnVisible(int col)
{
if (!m_CurrentColumns.IsValidIndex(col))
return;
// toggle the state of the column
column_t &column = m_ColumnsData[m_CurrentColumns[col]];
SetColumnVisible(col, column.m_bHidden);
}
//-----------------------------------------------------------------------------
// Purpose: sets user settings
//-----------------------------------------------------------------------------
void ListPanel::ApplyUserConfigSettings(KeyValues *userConfig)
{
// Check for version mismatch, then don't load settings. (Just revert to the defaults.)
int version = userConfig->GetInt( "configVersion", 1 );
if ( version != m_nUserConfigFileVersion )
{
return;
}
// We save/restore m_lastBarWidth because all of the column widths are saved relative to that size.
// If we don't save it, you can run into this case:
// - Window width is 500, load sizes setup relative to a 1000-width window
// - Set window size to 1000
// - In PerformLayout, it thinks the window has grown by 500 (since m_lastBarWidth is 500 and new window width is 1000)
// so it pushes out any COLUMN_RESIZEWITHWINDOW columns to their max extent and shrinks everything else to its min extent.
m_lastBarWidth = userConfig->GetInt( "lastBarWidth", 0 );
// read which columns are hidden
for ( int i = 0; i < m_CurrentColumns.Count(); i++ )
{
char name[64];
_snprintf(name, sizeof(name), "%d_hidden", i);
int hidden = userConfig->GetInt(name, -1);
if (hidden == 0)
{
SetColumnVisible(i, true);
}
else if (hidden == 1)
{
SetColumnVisible(i, false);
}
_snprintf(name, sizeof(name), "%d_width", i);
int nWidth = userConfig->GetInt( name, -1 );
if ( nWidth >= 0 )
{
column_t &column = m_ColumnsData[m_CurrentColumns[i]];
column.m_pHeader->SetWide( nWidth );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: returns user config settings for this control
//-----------------------------------------------------------------------------
void ListPanel::GetUserConfigSettings(KeyValues *userConfig)
{
if ( m_nUserConfigFileVersion != 1 )
{
userConfig->SetInt( "configVersion", m_nUserConfigFileVersion );
}
userConfig->SetInt( "lastBarWidth", m_lastBarWidth );
// save which columns are hidden
for ( int i = 0 ; i < m_CurrentColumns.Count() ; i++ )
{
column_t &column = m_ColumnsData[m_CurrentColumns[i]];
char name[64];
_snprintf(name, sizeof(name), "%d_hidden", i);
userConfig->SetInt(name, column.m_bHidden ? 1 : 0);
_snprintf(name, sizeof(name), "%d_width", i);
userConfig->SetInt( name, column.m_pHeader->GetWide() );
}
}
//-----------------------------------------------------------------------------
// Purpose: optimization, return true if this control has any user config settings
//-----------------------------------------------------------------------------
bool ListPanel::HasUserConfigSettings()
{
return true;
}
//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
void ListPanel::SetAllowUserModificationOfColumns(bool allowed)
{
m_bAllowUserAddDeleteColumns = allowed;
}
void ListPanel::SetIgnoreDoubleClick( bool state )
{
m_bIgnoreDoubleClick = state;
}
//-----------------------------------------------------------------------------
// Purpose: set up a field for editing
//-----------------------------------------------------------------------------
void ListPanel::EnterEditMode(int itemID, int column, vgui::Panel *editPanel)
{
m_hEditModePanel = editPanel;
m_iEditModeItemID = itemID;
m_iEditModeColumn = column;
editPanel->SetParent(this);
editPanel->SetVisible(true);
editPanel->RequestFocus();
editPanel->MoveToFront();
InvalidateLayout();
}
//-----------------------------------------------------------------------------
// Purpose: leaves editing mode
//-----------------------------------------------------------------------------
void ListPanel::LeaveEditMode()
{
if (m_hEditModePanel.Get())
{
m_hEditModePanel->SetVisible(false);
m_hEditModePanel->SetParent((Panel *)NULL);
m_hEditModePanel = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose: returns true if we are currently in inline editing mode
//-----------------------------------------------------------------------------
bool ListPanel::IsInEditMode()
{
return (m_hEditModePanel.Get() != NULL);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
#ifdef _X360
void ListPanel::NavigateTo()
{
BaseClass::NavigateTo();
// attempt to select the first item in the list when we get focus
if(GetItemCount())
{
SetSingleSelectedItem(FirstItem());
}
else // if we have no items, change focus
{
if(!NavigateDown())
{
NavigateUp();
}
}
}
#endif