You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3287 lines
88 KiB
3287 lines
88 KiB
//========= 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
|
|
|