Counter Strike : Global Offensive Source Code
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.

3268 lines
88 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include <stdio.h>
  8. #define PROTECTED_THINGS_DISABLE
  9. #include <vgui/Cursor.h>
  10. #include <vgui/IInput.h>
  11. #include <vgui/ILocalize.h>
  12. #include <vgui/IPanel.h>
  13. #include <vgui/IScheme.h>
  14. #include <vgui/ISystem.h>
  15. #include <vgui/ISurface.h>
  16. #include <vgui/IVGui.h>
  17. #include <vgui/KeyCode.h>
  18. #include <keyvalues.h>
  19. #include <vgui/MouseCode.h>
  20. #include <vgui_controls/Button.h>
  21. #include <vgui_controls/Controls.h>
  22. #include <vgui_controls/ImageList.h>
  23. #include <vgui_controls/ImagePanel.h>
  24. #include <vgui_controls/Label.h>
  25. #include <vgui_controls/ListPanel.h>
  26. #include <vgui_controls/ScrollBar.h>
  27. #include <vgui_controls/TextImage.h>
  28. #include <vgui_controls/Menu.h>
  29. #include <vgui_controls/Tooltip.h>
  30. // memdbgon must be the last include file in a .cpp file
  31. #include "tier0/memdbgon.h"
  32. using namespace vgui;
  33. enum
  34. {
  35. WINDOW_BORDER_WIDTH=2 // the width of the window's border
  36. };
  37. #ifndef max
  38. #define max(a,b) (((a) > (b)) ? (a) : (b))
  39. #endif
  40. #ifndef min
  41. #define min(a,b) (((a) < (b)) ? (a) : (b))
  42. #endif
  43. #ifndef clamp
  44. #define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) )
  45. #endif
  46. //-----------------------------------------------------------------------------
  47. //
  48. // Button at the top of columns used to re-sort
  49. //
  50. //-----------------------------------------------------------------------------
  51. class ColumnButton : public Button
  52. {
  53. public:
  54. ColumnButton(vgui::Panel *parent, const char *name, const char *text);
  55. // Inherited from Button
  56. virtual void ApplySchemeSettings(IScheme *pScheme);
  57. virtual void OnMousePressed(MouseCode code);
  58. void OpenColumnChoiceMenu();
  59. };
  60. ColumnButton::ColumnButton(vgui::Panel *parent, const char *name, const char *text) : Button(parent, name, text)
  61. {
  62. SetBlockDragChaining( true );
  63. }
  64. void ColumnButton::ApplySchemeSettings(IScheme *pScheme)
  65. {
  66. Button::ApplySchemeSettings(pScheme);
  67. SetContentAlignment(Label::a_west);
  68. SetFont(pScheme->GetFont("DefaultSmall", IsProportional()));
  69. }
  70. // Don't request focus.
  71. // This will keep items in the listpanel selected.
  72. void ColumnButton::OnMousePressed(MouseCode code)
  73. {
  74. if (!IsEnabled())
  75. return;
  76. if (code == MOUSE_RIGHT)
  77. {
  78. OpenColumnChoiceMenu();
  79. return;
  80. }
  81. if (!IsMouseClickEnabled(code))
  82. return;
  83. if (IsUseCaptureMouseEnabled())
  84. {
  85. {
  86. SetSelected(true);
  87. Repaint();
  88. }
  89. // lock mouse input to going to this button
  90. input()->SetMouseCapture(GetVPanel());
  91. }
  92. }
  93. void ColumnButton::OpenColumnChoiceMenu()
  94. {
  95. CallParentFunction(new KeyValues("OpenColumnChoiceMenu"));
  96. }
  97. //-----------------------------------------------------------------------------
  98. //
  99. // Purpose: Handles resizing of columns
  100. //
  101. //-----------------------------------------------------------------------------
  102. class Dragger : public Panel
  103. {
  104. public:
  105. Dragger(int column);
  106. // Inherited from Panel
  107. virtual void OnMousePressed(MouseCode code);
  108. virtual void OnMouseDoublePressed(MouseCode code);
  109. virtual void OnMouseReleased(MouseCode code);
  110. virtual void OnCursorMoved(int x, int y);
  111. virtual void SetMovable(bool state);
  112. private:
  113. int m_iDragger;
  114. bool m_bDragging;
  115. int m_iDragPos;
  116. bool m_bMovable; // whether this dragger is movable using mouse or not
  117. };
  118. Dragger::Dragger(int column)
  119. {
  120. m_iDragger = column;
  121. SetPaintBackgroundEnabled(false);
  122. SetPaintEnabled(false);
  123. SetPaintBorderEnabled(false);
  124. SetCursor(dc_sizewe);
  125. m_bDragging = false;
  126. m_bMovable = true; // movable by default
  127. m_iDragPos = 0;
  128. SetBlockDragChaining( true );
  129. }
  130. void Dragger::OnMousePressed(MouseCode code)
  131. {
  132. if (m_bMovable)
  133. {
  134. input()->SetMouseCapture(GetVPanel());
  135. int x, y;
  136. input()->GetCursorPos(x, y);
  137. m_iDragPos = x;
  138. m_bDragging = true;
  139. }
  140. }
  141. void Dragger::OnMouseDoublePressed(MouseCode code)
  142. {
  143. if (m_bMovable)
  144. {
  145. // resize the column to the size of it's contents
  146. PostMessage(GetParent(), new KeyValues("ResizeColumnToContents", "column", m_iDragger));
  147. }
  148. }
  149. void Dragger::OnMouseReleased(MouseCode code)
  150. {
  151. if (m_bMovable)
  152. {
  153. input()->SetMouseCapture(NULL);
  154. m_bDragging = false;
  155. }
  156. }
  157. void Dragger::OnCursorMoved(int x, int y)
  158. {
  159. if (m_bDragging)
  160. {
  161. input()->GetCursorPos(x, y);
  162. KeyValues *msg = new KeyValues("ColumnResized");
  163. msg->SetInt("column", m_iDragger);
  164. msg->SetInt("delta", x - m_iDragPos);
  165. m_iDragPos = x;
  166. if (GetVParent())
  167. {
  168. ivgui()->PostMessage(GetVParent(), msg, GetVPanel());
  169. }
  170. }
  171. }
  172. void Dragger::SetMovable(bool state)
  173. {
  174. m_bMovable = state;
  175. // disable cursor change if the dragger is not movable
  176. if( IsVisible() )
  177. {
  178. if (state)
  179. {
  180. // if its not movable we stick with the default arrow
  181. // if parent windows Start getting fancy cursors we should probably retrive a parent
  182. // cursor and set it to that
  183. SetCursor(dc_sizewe);
  184. }
  185. else
  186. {
  187. SetCursor(dc_arrow);
  188. }
  189. }
  190. }
  191. namespace vgui
  192. {
  193. // optimized for sorting
  194. class FastSortListPanelItem : public ListPanelItem
  195. {
  196. public:
  197. // index into accessing item to sort
  198. CUtlVector<int> m_SortedTreeIndexes;
  199. // visibility flag (for quick hide/filter)
  200. bool visible;
  201. // precalculated sort orders
  202. int primarySortIndexValue;
  203. int secondarySortIndexValue;
  204. };
  205. }
  206. static ListPanel *s_pCurrentSortingListPanel = NULL;
  207. static const char *s_pCurrentSortingColumn = NULL;
  208. static bool s_currentSortingColumnTypeIsText = false;
  209. static SortFunc *s_pSortFunc = NULL;
  210. static bool s_bSortAscending = true;
  211. static SortFunc *s_pSortFuncSecondary = NULL;
  212. static bool s_bSortAscendingSecondary = true;
  213. //-----------------------------------------------------------------------------
  214. // Purpose: Basic sort function, for use in qsort
  215. //-----------------------------------------------------------------------------
  216. static int __cdecl AscendingSortFunc(const void *elem1, const void *elem2)
  217. {
  218. int itemID1 = *((int *) elem1);
  219. int itemID2 = *((int *) elem2);
  220. // convert the item index into the ListPanelItem pointers
  221. vgui::ListPanelItem *p1, *p2;
  222. p1 = s_pCurrentSortingListPanel->GetItemData(itemID1);
  223. p2 = s_pCurrentSortingListPanel->GetItemData(itemID2);
  224. int result = s_pSortFunc( s_pCurrentSortingListPanel, *p1, *p2 );
  225. if (result == 0)
  226. {
  227. // use the secondary sort functino
  228. result = s_pSortFuncSecondary( s_pCurrentSortingListPanel, *p1, *p2 );
  229. if (!s_bSortAscendingSecondary)
  230. {
  231. result = -result;
  232. }
  233. if (result == 0)
  234. {
  235. // sort by the pointers to make sure we get consistent results
  236. if (p1 > p2)
  237. {
  238. result = 1;
  239. }
  240. else
  241. {
  242. result = -1;
  243. }
  244. }
  245. }
  246. else
  247. {
  248. // flip result if not doing an ascending sort
  249. if (!s_bSortAscending)
  250. {
  251. result = -result;
  252. }
  253. }
  254. return result;
  255. }
  256. //-----------------------------------------------------------------------------
  257. // Purpose: Default column sorting function, puts things in alpabetical order
  258. // If images are the same returns 1, else 0
  259. //-----------------------------------------------------------------------------
  260. static int __cdecl DefaultSortFunc(
  261. ListPanel *pPanel,
  262. const ListPanelItem &item1,
  263. const ListPanelItem &item2 )
  264. {
  265. const vgui::ListPanelItem *p1 = &item1;
  266. const vgui::ListPanelItem *p2 = &item2;
  267. if ( !p1 || !p2 ) // No meaningful comparison
  268. {
  269. return 0;
  270. }
  271. const char *col = s_pCurrentSortingColumn;
  272. if (s_currentSortingColumnTypeIsText) // textImage column
  273. {
  274. if (p1->kv->FindKey(col, true)->GetDataType() == KeyValues::TYPE_INT)
  275. {
  276. // compare ints
  277. int s1 = p1->kv->GetInt(col, 0);
  278. int s2 = p2->kv->GetInt(col, 0);
  279. if (s1 < s2)
  280. {
  281. return -1;
  282. }
  283. else if (s1 > s2)
  284. {
  285. return 1;
  286. }
  287. return 0;
  288. }
  289. else
  290. {
  291. // compare as string
  292. const char *s1 = p1->kv->GetString(col, "");
  293. const char *s2 = p2->kv->GetString(col, "");
  294. return Q_stricmp(s1, s2);
  295. }
  296. }
  297. else // its an imagePanel column
  298. {
  299. const ImagePanel *s1 = (const ImagePanel *)p1->kv->GetPtr(col, NULL);
  300. const ImagePanel *s2 = (const ImagePanel *)p2->kv->GetPtr(col, NULL);
  301. if (s1 < s2)
  302. {
  303. return -1;
  304. }
  305. else if (s1 > s2)
  306. {
  307. return 1;
  308. }
  309. return 0;
  310. }
  311. }
  312. //-----------------------------------------------------------------------------
  313. // Purpose: Sorts items by comparing precalculated list values
  314. //-----------------------------------------------------------------------------
  315. static int __cdecl FastSortFunc(
  316. ListPanel *pPanel,
  317. const ListPanelItem &item1,
  318. const ListPanelItem &item2 )
  319. {
  320. const vgui::FastSortListPanelItem *p1 = (vgui::FastSortListPanelItem *)&item1;
  321. const vgui::FastSortListPanelItem *p2 = (vgui::FastSortListPanelItem *)&item2;
  322. Assert(p1 && p2);
  323. // compare the precalculated indices
  324. if (p1->primarySortIndexValue < p2->primarySortIndexValue)
  325. {
  326. return 1;
  327. }
  328. else if (p1->primarySortIndexValue > p2->primarySortIndexValue)
  329. {
  330. return -1;
  331. }
  332. // they're equal, compare the secondary indices
  333. if (p1->secondarySortIndexValue < p2->secondarySortIndexValue)
  334. {
  335. return 1;
  336. }
  337. else if (p1->secondarySortIndexValue > p2->secondarySortIndexValue)
  338. {
  339. return -1;
  340. }
  341. // still equal; just compare the pointers (so we get deterministic results)
  342. return (p1 < p2) ? 1 : -1;
  343. }
  344. static int s_iDuplicateIndex = 1;
  345. //-----------------------------------------------------------------------------
  346. // Purpose: sorting function used in the column index redblack tree
  347. //-----------------------------------------------------------------------------
  348. bool ListPanel::RBTreeLessFunc(vgui::ListPanel::IndexItem_t &item1, vgui::ListPanel::IndexItem_t &item2)
  349. {
  350. int result = s_pSortFunc( s_pCurrentSortingListPanel, *item1.dataItem, *item2.dataItem);
  351. if (result == 0)
  352. {
  353. // they're the same value, set their duplicate index to reflect that
  354. if (item1.duplicateIndex)
  355. {
  356. item2.duplicateIndex = item1.duplicateIndex;
  357. }
  358. else if (item2.duplicateIndex)
  359. {
  360. item1.duplicateIndex = item2.duplicateIndex;
  361. }
  362. else
  363. {
  364. item1.duplicateIndex = item2.duplicateIndex = s_iDuplicateIndex++;
  365. }
  366. }
  367. return (result > 0);
  368. }
  369. DECLARE_BUILD_FACTORY( ListPanel );
  370. //-----------------------------------------------------------------------------
  371. // Purpose: Constructor
  372. //-----------------------------------------------------------------------------
  373. ListPanel::ListPanel(Panel *parent, const char *panelName) : BaseClass(parent, panelName)
  374. {
  375. m_bIgnoreDoubleClick = false;
  376. m_bMultiselectEnabled = true;
  377. m_iEditModeItemID = 0;
  378. m_iEditModeColumn = 0;
  379. m_iHeaderHeight = 20;
  380. m_iRowHeight = 20;
  381. m_bCanSelectIndividualCells = false;
  382. m_iSelectedColumn = -1;
  383. m_bAllowUserAddDeleteColumns = false;
  384. m_hbar = SETUP_PANEL( new ScrollBar(this, "HorizScrollBar", false) );
  385. m_hbar->AddActionSignalTarget(this);
  386. m_hbar->SetVisible(false);
  387. m_vbar = SETUP_PANEL( new ScrollBar(this, "VertScrollBar", true) );
  388. m_vbar->SetVisible(false);
  389. m_vbar->AddActionSignalTarget(this);
  390. m_pLabel = new Label(this, NULL, "");
  391. m_pLabel->SetVisible(false);
  392. m_pLabel->SetPaintBackgroundEnabled(false);
  393. m_pLabel->SetContentAlignment(Label::a_west);
  394. m_pTextImage = new TextImage( "" );
  395. m_pImagePanel = new ImagePanel(NULL, "ListImage");
  396. m_pImagePanel->SetAutoDelete(false);
  397. m_iSortColumn = -1;
  398. m_iSortColumnSecondary = -1;
  399. m_bSortAscending = true;
  400. m_bSortAscendingSecondary = true;
  401. m_lastBarWidth = 0;
  402. m_iColumnDraggerMoved = -1;
  403. m_bNeedsSort = false;
  404. m_LastItemSelected = -1;
  405. m_pImageList = NULL;
  406. m_bDeleteImageListWhenDone = false;
  407. m_pEmptyListText = new TextImage("");
  408. }
  409. //-----------------------------------------------------------------------------
  410. // Purpose:
  411. //-----------------------------------------------------------------------------
  412. ListPanel::~ListPanel()
  413. {
  414. // free data from table
  415. RemoveAll();
  416. // free column headers
  417. unsigned char i;
  418. for ( i = m_ColumnsData.Head(); i != m_ColumnsData.InvalidIndex(); i= m_ColumnsData.Next( i ) )
  419. {
  420. m_ColumnsData[i].m_pHeader->MarkForDeletion();
  421. m_ColumnsData[i].m_pResizer->MarkForDeletion();
  422. }
  423. m_ColumnsData.RemoveAll();
  424. delete m_pTextImage;
  425. delete m_pImagePanel;
  426. delete m_vbar;
  427. if ( m_bDeleteImageListWhenDone )
  428. {
  429. delete m_pImageList;
  430. }
  431. delete m_pEmptyListText;
  432. }
  433. //-----------------------------------------------------------------------------
  434. // Purpose:
  435. //-----------------------------------------------------------------------------
  436. void ListPanel::SetImageList(ImageList *imageList, bool deleteImageListWhenDone)
  437. {
  438. // get rid of existing list image if there's one and we're supposed to get rid of it
  439. if ( m_pImageList && m_bDeleteImageListWhenDone )
  440. {
  441. delete m_pImageList;
  442. }
  443. m_bDeleteImageListWhenDone = deleteImageListWhenDone;
  444. m_pImageList = imageList;
  445. }
  446. //-----------------------------------------------------------------------------
  447. // Purpose:
  448. //-----------------------------------------------------------------------------
  449. void ListPanel::SetColumnHeaderHeight( int height )
  450. {
  451. m_iHeaderHeight = height;
  452. }
  453. //-----------------------------------------------------------------------------
  454. // Purpose: adds a column header.
  455. // this->FindChildByName(columnHeaderName) can be used to retrieve a pointer to a header panel by name
  456. //
  457. // if minWidth and maxWidth are BOTH NOTRESIZABLE or RESIZABLE
  458. // the min and max size will be calculated automatically for you with that attribute
  459. // columns are resizable by default
  460. // if min and max size are specified column is resizable
  461. //
  462. // A small note on passing numbers for minWidth and maxWidth,
  463. // If the initial window size is larger than the sum of the original widths of the columns,
  464. // you can wind up with the columns "snapping" to size after the first window focus
  465. // This is because the dxPerBar being calculated in PerformLayout()
  466. // is making resizable bounded headers exceed thier maxWidths at the Start.
  467. // Solution is to either put in support for redistributing the extra dx being truncated and
  468. // therefore added to the last column on window opening, which is what causes the snapping.
  469. // OR to
  470. // ensure the difference between the starting sum of widths is not too much smaller/bigger
  471. // than the starting window size so the starting dx doesn't cause snapping to occur.
  472. // The easiest thing is to simply set it so your column widths add up to the starting size of the window on opening.
  473. //
  474. // Another note: Always give bounds for the last column you add or make it not resizable.
  475. //
  476. // Columns can have text headers or images for headers (e.g. password icon)
  477. //-----------------------------------------------------------------------------
  478. void ListPanel::AddColumnHeader(int index, const char *columnName, const char *columnText, int width, int columnFlags)
  479. {
  480. if (columnFlags & COLUMN_FIXEDSIZE && !(columnFlags & COLUMN_RESIZEWITHWINDOW))
  481. {
  482. // for fixed size columns, set the min & max widths to be the same as the initial width
  483. AddColumnHeader( index, columnName, columnText, width, width, width, columnFlags);
  484. }
  485. else
  486. {
  487. AddColumnHeader( index, columnName, columnText, width, 20, 10000, columnFlags);
  488. }
  489. }
  490. //-----------------------------------------------------------------------------
  491. // Purpose: Adds a new column
  492. //-----------------------------------------------------------------------------
  493. void ListPanel::AddColumnHeader(int index, const char *columnName, const char *columnText, int width, int minWidth, int maxWidth, int columnFlags)
  494. {
  495. Assert (minWidth <= width);
  496. Assert (maxWidth >= width);
  497. // get our permanent index
  498. unsigned char columnDataIndex = m_ColumnsData.AddToTail();
  499. // put this index on the tail, so all item's m_SortedTreeIndexes have a consistent mapping
  500. m_ColumnsHistory.AddToTail(columnDataIndex);
  501. // put this column in the right place visually
  502. m_CurrentColumns.InsertBefore(index, columnDataIndex);
  503. // create the actual column object
  504. column_t &column = m_ColumnsData[columnDataIndex];
  505. // create the column header button
  506. Button *pButton = SETUP_PANEL(new ColumnButton(this, columnName, columnText)); // the cell rendering mucks with the button visibility during the solvetraverse loop,
  507. //so force applyschemesettings to make sure its run
  508. pButton->SetSize(width, 24);
  509. pButton->AddActionSignalTarget(this);
  510. pButton->SetContentAlignment(Label::a_west);
  511. pButton->SetTextInset(5, 0);
  512. column.m_pHeader = pButton;
  513. column.m_iMinWidth = minWidth;
  514. column.m_iMaxWidth = maxWidth;
  515. column.m_bResizesWithWindow = ( columnFlags & COLUMN_RESIZEWITHWINDOW ) ? true : false;
  516. column.m_bTypeIsText = !(columnFlags & COLUMN_IMAGE);
  517. column.m_bHidden = false;
  518. column.m_bUnhidable = (columnFlags & COLUMN_UNHIDABLE) ? true : false;
  519. column.m_nContentAlignment = Label::a_west;
  520. Dragger *dragger = new Dragger(index);
  521. dragger->SetParent(this);
  522. dragger->AddActionSignalTarget(this);
  523. dragger->MoveToFront();
  524. if (minWidth == maxWidth || (columnFlags & COLUMN_FIXEDSIZE))
  525. {
  526. // not resizable so disable the slider
  527. dragger->SetMovable(false);
  528. }
  529. column.m_pResizer = dragger;
  530. // add default sort function
  531. column.m_pSortFunc = NULL;
  532. // Set the SortedTree less than func to the generic RBTreeLessThanFunc
  533. m_ColumnsData[columnDataIndex].m_SortedTree.SetLessFunc((IndexRBTree_t::LessFunc_t)RBTreeLessFunc);
  534. // go through all the headers and make sure their Command has the right column ID
  535. ResetColumnHeaderCommands();
  536. // create the new data index
  537. ResortColumnRBTree(index);
  538. // ensure scroll bar is topmost compared to column headers
  539. m_vbar->MoveToFront();
  540. // fix up our visibility
  541. SetColumnVisible(index, !(columnFlags & COLUMN_HIDDEN));
  542. InvalidateLayout();
  543. }
  544. //-----------------------------------------------------------------------------
  545. // Purpose: Recreates a column's RB Sorted Tree
  546. //-----------------------------------------------------------------------------
  547. void ListPanel::ResortColumnRBTree(int col)
  548. {
  549. Assert(m_CurrentColumns.IsValidIndex(col));
  550. unsigned char dataColumnIndex = m_CurrentColumns[col];
  551. int columnHistoryIndex = m_ColumnsHistory.Find(dataColumnIndex);
  552. column_t &column = m_ColumnsData[dataColumnIndex];
  553. IndexRBTree_t &rbtree = column.m_SortedTree;
  554. // remove all elements - we're going to create from scratch
  555. rbtree.RemoveAll();
  556. s_pCurrentSortingListPanel = this;
  557. s_currentSortingColumnTypeIsText = column.m_bTypeIsText; // type of data in the column
  558. SortFunc *sortFunc = column.m_pSortFunc;
  559. if ( !sortFunc )
  560. {
  561. sortFunc = DefaultSortFunc;
  562. }
  563. s_pSortFunc = sortFunc;
  564. s_bSortAscending = true;
  565. s_pSortFuncSecondary = NULL;
  566. // sort all current data items for this column
  567. FOR_EACH_LL( m_DataItems, i )
  568. {
  569. IndexItem_t item;
  570. item.dataItem = m_DataItems[i];
  571. item.duplicateIndex = 0;
  572. FastSortListPanelItem *dataItem = (FastSortListPanelItem*) m_DataItems[i];
  573. // if this item doesn't already have a SortedTreeIndex for this column,
  574. // if can only be because this is the brand new column, so add it to the SortedTreeIndexes
  575. if (dataItem->m_SortedTreeIndexes.Count() == m_ColumnsHistory.Count() - 1 &&
  576. columnHistoryIndex == m_ColumnsHistory.Count() - 1)
  577. {
  578. dataItem->m_SortedTreeIndexes.AddToTail();
  579. }
  580. Assert( dataItem->m_SortedTreeIndexes.IsValidIndex(columnHistoryIndex) );
  581. dataItem->m_SortedTreeIndexes[columnHistoryIndex] = rbtree.Insert(item);
  582. }
  583. }
  584. //-----------------------------------------------------------------------------
  585. // Purpose: Resets the "SetSortColumn" command for each column - in case columns were added or removed
  586. //-----------------------------------------------------------------------------
  587. void ListPanel::ResetColumnHeaderCommands()
  588. {
  589. int i;
  590. for ( i = 0 ; i < m_CurrentColumns.Count() ; i++ )
  591. {
  592. Button *pButton = m_ColumnsData[m_CurrentColumns[i]].m_pHeader;
  593. pButton->SetCommand(new KeyValues("SetSortColumn", "column", i));
  594. }
  595. }
  596. //-----------------------------------------------------------------------------
  597. // Purpose: Sets the header text for a particular column.
  598. //-----------------------------------------------------------------------------
  599. void ListPanel::SetColumnHeaderText(int col, const char *text)
  600. {
  601. m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetText(text);
  602. }
  603. void ListPanel::SetColumnHeaderText(int col, wchar_t *text)
  604. {
  605. m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetText(text);
  606. }
  607. void ListPanel::SetColumnTextAlignment( int col, int align )
  608. {
  609. m_ColumnsData[m_CurrentColumns[col]].m_nContentAlignment = align;
  610. }
  611. //-----------------------------------------------------------------------------
  612. // Purpose: Sets the column header to have an image instead of text
  613. //-----------------------------------------------------------------------------
  614. void ListPanel::SetColumnHeaderImage(int column, int imageListIndex)
  615. {
  616. Assert(m_pImageList);
  617. m_ColumnsData[m_CurrentColumns[column]].m_pHeader->SetTextImageIndex(-1);
  618. m_ColumnsData[m_CurrentColumns[column]].m_pHeader->SetImageAtIndex(0, m_pImageList->GetImage(imageListIndex), 0);
  619. }
  620. //-----------------------------------------------------------------------------
  621. // Purpose: associates a tooltip with the column header
  622. //-----------------------------------------------------------------------------
  623. void ListPanel::SetColumnHeaderTooltip(int column, const char *tooltipText)
  624. {
  625. m_ColumnsData[m_CurrentColumns[column]].m_pHeader->GetTooltip()->SetText(tooltipText);
  626. m_ColumnsData[m_CurrentColumns[column]].m_pHeader->GetTooltip()->SetTooltipFormatToSingleLine();
  627. m_ColumnsData[m_CurrentColumns[column]].m_pHeader->GetTooltip()->SetTooltipDelay(0);
  628. }
  629. int ListPanel::GetNumColumnHeaders() const
  630. {
  631. return m_CurrentColumns.Count();
  632. }
  633. bool ListPanel::GetColumnHeaderText( int index, char *pOut, int maxLen )
  634. {
  635. if ( index < m_CurrentColumns.Count() )
  636. {
  637. m_ColumnsData[m_CurrentColumns[index]].m_pHeader->GetText( pOut, maxLen );
  638. return true;
  639. }
  640. else
  641. {
  642. return false;
  643. }
  644. }
  645. //-----------------------------------------------------------------------------
  646. // Purpose:
  647. //-----------------------------------------------------------------------------
  648. void ListPanel::SetColumnSortable(int col, bool sortable)
  649. {
  650. if (sortable)
  651. {
  652. m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetCommand(new KeyValues("SetSortColumn", "column", col));
  653. }
  654. else
  655. {
  656. m_ColumnsData[m_CurrentColumns[col]].m_pHeader->SetCommand((const char *)NULL);
  657. }
  658. }
  659. //-----------------------------------------------------------------------------
  660. // Purpose: Changes the visibility of a column
  661. //-----------------------------------------------------------------------------
  662. void ListPanel::SetColumnVisible(int col, bool visible)
  663. {
  664. column_t &column = m_ColumnsData[m_CurrentColumns[col]];
  665. bool bHidden = !visible;
  666. if (column.m_bHidden == bHidden)
  667. return;
  668. if (column.m_bUnhidable)
  669. return;
  670. column.m_bHidden = bHidden;
  671. if (bHidden)
  672. {
  673. column.m_pHeader->SetVisible(false);
  674. column.m_pResizer->SetVisible(false);
  675. }
  676. else
  677. {
  678. column.m_pHeader->SetVisible(true);
  679. column.m_pResizer->SetVisible(true);
  680. }
  681. InvalidateLayout();
  682. }
  683. //-----------------------------------------------------------------------------
  684. // Purpose:
  685. //-----------------------------------------------------------------------------
  686. void ListPanel::RemoveColumn(int col)
  687. {
  688. if ( !m_CurrentColumns.IsValidIndex( col ) )
  689. return;
  690. // find the appropriate column data
  691. unsigned char columnDataIndex = m_CurrentColumns[col];
  692. // remove it from the current columns
  693. m_CurrentColumns.Remove(col);
  694. // zero out this entry in m_ColumnsHistory
  695. unsigned char i;
  696. for ( i = 0 ; i < m_ColumnsHistory.Count() ; i++ )
  697. {
  698. if ( m_ColumnsHistory[i] == columnDataIndex )
  699. {
  700. m_ColumnsHistory[i] = m_ColumnsData.InvalidIndex();
  701. break;
  702. }
  703. }
  704. Assert( i != m_ColumnsHistory.Count() );
  705. // delete and remove the column data
  706. m_ColumnsData[columnDataIndex].m_SortedTree.RemoveAll();
  707. m_ColumnsData[columnDataIndex].m_pHeader->MarkForDeletion();
  708. m_ColumnsData[columnDataIndex].m_pResizer->MarkForDeletion();
  709. m_ColumnsData.Remove(columnDataIndex);
  710. ResetColumnHeaderCommands();
  711. InvalidateLayout();
  712. }
  713. //-----------------------------------------------------------------------------
  714. // Purpose: Returns the index of a column by column->GetName()
  715. //-----------------------------------------------------------------------------
  716. int ListPanel::FindColumn(const char *columnName)
  717. {
  718. for (int i = 0; i < m_CurrentColumns.Count(); i++)
  719. {
  720. if (!stricmp(columnName, m_ColumnsData[m_CurrentColumns[i]].m_pHeader->GetName()))
  721. {
  722. return i;
  723. }
  724. }
  725. return -1;
  726. }
  727. //-----------------------------------------------------------------------------
  728. // Purpose: adds an item to the view
  729. // data->GetName() is used to uniquely identify an item
  730. // data sub items are matched against column header name to be used in the table
  731. //-----------------------------------------------------------------------------
  732. int ListPanel::AddItem( const KeyValues *item, uintp userData, bool bScrollToItem, bool bSortOnAdd)
  733. {
  734. FastSortListPanelItem *newitem = new FastSortListPanelItem;
  735. newitem->kv = item->MakeCopy();
  736. newitem->userData = userData;
  737. newitem->m_pDragData = NULL;
  738. newitem->m_bImage = newitem->kv->GetBool( "image" );
  739. newitem->m_nImageIndex = newitem->kv->GetInt( "image" );
  740. newitem->m_nImageIndexSelected = newitem->kv->GetInt( "imageSelected" );
  741. newitem->m_pIcon = reinterpret_cast< IImage * >( newitem->kv->GetPtr( "iconImage" ) );
  742. int itemID = m_DataItems.AddToTail(newitem);
  743. int displayRow = m_VisibleItems.AddToTail(itemID);
  744. newitem->visible = true;
  745. // put the item in each column's sorted Tree Index
  746. IndexItem(itemID);
  747. if ( bSortOnAdd )
  748. {
  749. m_bNeedsSort = true;
  750. }
  751. InvalidateLayout();
  752. if ( bScrollToItem )
  753. {
  754. // scroll to last item
  755. m_vbar->SetValue(displayRow);
  756. }
  757. return itemID;
  758. }
  759. //-----------------------------------------------------------------------------
  760. // Purpose:
  761. //-----------------------------------------------------------------------------
  762. void ListPanel::SetUserData( int itemID, uintp userData )
  763. {
  764. if ( !m_DataItems.IsValidIndex(itemID) )
  765. return;
  766. m_DataItems[itemID]->userData = userData;
  767. }
  768. //-----------------------------------------------------------------------------
  769. // Purpose: Finds the first itemID with a matching userData
  770. //-----------------------------------------------------------------------------
  771. int ListPanel::GetItemIDFromUserData( uintp userData )
  772. {
  773. FOR_EACH_LL( m_DataItems, itemID )
  774. {
  775. if (m_DataItems[itemID]->userData == userData)
  776. return itemID;
  777. }
  778. // not found
  779. return InvalidItemID();
  780. }
  781. //-----------------------------------------------------------------------------
  782. // Purpose:
  783. //-----------------------------------------------------------------------------
  784. int ListPanel::GetItemCount( void )
  785. {
  786. return m_VisibleItems.Count();
  787. }
  788. //-----------------------------------------------------------------------------
  789. // Purpose: gets the item ID of an item by name (data->GetName())
  790. //-----------------------------------------------------------------------------
  791. int ListPanel::GetItem(const char *itemName)
  792. {
  793. FOR_EACH_LL( m_DataItems, i )
  794. {
  795. if (!stricmp(m_DataItems[i]->kv->GetName(), itemName))
  796. {
  797. return i;
  798. }
  799. }
  800. // failure
  801. return -1;
  802. }
  803. //-----------------------------------------------------------------------------
  804. // Purpose: returns pointer to data the itemID holds
  805. //-----------------------------------------------------------------------------
  806. KeyValues *ListPanel::GetItem(int itemID)
  807. {
  808. if ( !m_DataItems.IsValidIndex(itemID) )
  809. return NULL;
  810. return m_DataItems[itemID]->kv;
  811. }
  812. //-----------------------------------------------------------------------------
  813. // Purpose:
  814. //-----------------------------------------------------------------------------
  815. int ListPanel::GetItemCurrentRow(int itemID)
  816. {
  817. return m_VisibleItems.Find(itemID);
  818. }
  819. //-----------------------------------------------------------------------------
  820. // Attaches drag data to a particular item. This takes over ownership of the item and the calling code should NOT call deleteThis()!!!
  821. //-----------------------------------------------------------------------------
  822. void ListPanel::SetItemDragData( int itemID, const KeyValues *data )
  823. {
  824. ListPanelItem *pItem = m_DataItems[ itemID ];
  825. if ( pItem->m_pDragData )
  826. {
  827. pItem->m_pDragData->deleteThis();
  828. }
  829. pItem->m_pDragData = (KeyValues *)data;
  830. }
  831. //-----------------------------------------------------------------------------
  832. // Attaches drag data to a particular item
  833. //-----------------------------------------------------------------------------
  834. void ListPanel::OnCreateDragData( KeyValues *msg )
  835. {
  836. int nCount = GetSelectedItemsCount();
  837. if ( nCount == 0 )
  838. return;
  839. for ( int i = 0; i < nCount; ++i )
  840. {
  841. int nItemID = GetSelectedItem( i );
  842. KeyValues *pDragData = m_DataItems[ nItemID ]->m_pDragData;
  843. if ( pDragData )
  844. {
  845. KeyValues *pDragDataCopy = pDragData->MakeCopy();
  846. msg->AddSubKey( pDragDataCopy );
  847. }
  848. }
  849. // Add the keys of the last item directly into the root also
  850. int nLastItemID = GetSelectedItem( nCount - 1 );
  851. KeyValues *pLastItemDrag = m_DataItems[ nLastItemID ]->m_pDragData;
  852. if ( pLastItemDrag )
  853. {
  854. pLastItemDrag->CopySubkeys( msg );
  855. }
  856. }
  857. //-----------------------------------------------------------------------------
  858. // Purpose:
  859. //-----------------------------------------------------------------------------
  860. int ListPanel::GetItemIDFromRow(int currentRow)
  861. {
  862. if (!m_VisibleItems.IsValidIndex(currentRow))
  863. return -1;
  864. return m_VisibleItems[currentRow];
  865. }
  866. int ListPanel::FirstItem() const
  867. {
  868. return m_DataItems.Head();
  869. }
  870. int ListPanel::NextItem( int iItem ) const
  871. {
  872. return m_DataItems.Next( iItem );
  873. }
  874. //-----------------------------------------------------------------------------
  875. // Purpose:
  876. //-----------------------------------------------------------------------------
  877. int ListPanel::InvalidItemID() const
  878. {
  879. return m_DataItems.InvalidIndex();
  880. }
  881. //-----------------------------------------------------------------------------
  882. // Purpose:
  883. //-----------------------------------------------------------------------------
  884. bool ListPanel::IsValidItemID(int itemID)
  885. {
  886. return m_DataItems.IsValidIndex(itemID);
  887. }
  888. //-----------------------------------------------------------------------------
  889. // Purpose:
  890. //-----------------------------------------------------------------------------
  891. ListPanelItem *ListPanel::GetItemData( int itemID )
  892. {
  893. if ( !m_DataItems.IsValidIndex(itemID) )
  894. return NULL;
  895. return m_DataItems[ itemID ];
  896. }
  897. //-----------------------------------------------------------------------------
  898. // Purpose: returns user data for itemID
  899. //-----------------------------------------------------------------------------
  900. uintp ListPanel::GetItemUserData(int itemID)
  901. {
  902. if ( !m_DataItems.IsValidIndex(itemID) )
  903. return 0;
  904. return m_DataItems[itemID]->userData;
  905. }
  906. //-----------------------------------------------------------------------------
  907. // Purpose: updates the view with any changes to the data
  908. // Input : itemID - index to update
  909. //-----------------------------------------------------------------------------
  910. void ListPanel::ApplyItemChanges(int itemID)
  911. {
  912. // reindex the item and then redraw
  913. IndexItem(itemID);
  914. InvalidateLayout();
  915. }
  916. //-----------------------------------------------------------------------------
  917. // Purpose: Adds the item into the column indexes
  918. //-----------------------------------------------------------------------------
  919. void ListPanel::IndexItem(int itemID)
  920. {
  921. FastSortListPanelItem *newitem = (FastSortListPanelItem*) m_DataItems[itemID];
  922. // remove the item from the indexes and re-add
  923. int maxCount = min(m_ColumnsHistory.Count(), newitem->m_SortedTreeIndexes.Count());
  924. for (int i = 0; i < maxCount; i++)
  925. {
  926. IndexRBTree_t &rbtree = m_ColumnsData[m_ColumnsHistory[i]].m_SortedTree;
  927. rbtree.RemoveAt(newitem->m_SortedTreeIndexes[i]);
  928. }
  929. // make sure it's all free
  930. newitem->m_SortedTreeIndexes.RemoveAll();
  931. // reserve one index per historical column - pad it out
  932. newitem->m_SortedTreeIndexes.AddMultipleToTail(m_ColumnsHistory.Count());
  933. // set the current sorting list (since the insert will need to sort)
  934. s_pCurrentSortingListPanel = this;
  935. // add the item into the RB tree for each column
  936. for (int i = 0; i < m_ColumnsHistory.Count(); i++)
  937. {
  938. // skip over any removed columns
  939. if ( m_ColumnsHistory[i] == m_ColumnsData.InvalidIndex() )
  940. continue;
  941. column_t &column = m_ColumnsData[m_ColumnsHistory[i]];
  942. IndexItem_t item;
  943. item.dataItem = newitem;
  944. item.duplicateIndex = 0;
  945. IndexRBTree_t &rbtree = column.m_SortedTree;
  946. // setup sort state
  947. s_pCurrentSortingListPanel = this;
  948. s_pCurrentSortingColumn = column.m_pHeader->GetName(); // name of current column for sorting
  949. s_currentSortingColumnTypeIsText = column.m_bTypeIsText; // type of data in the column
  950. SortFunc *sortFunc = column.m_pSortFunc;
  951. if (!sortFunc)
  952. {
  953. sortFunc = DefaultSortFunc;
  954. }
  955. s_pSortFunc = sortFunc;
  956. s_bSortAscending = true;
  957. s_pSortFuncSecondary = NULL;
  958. // insert index
  959. newitem->m_SortedTreeIndexes[i] = rbtree.Insert(item);
  960. }
  961. }
  962. //-----------------------------------------------------------------------------
  963. // Purpose:
  964. //-----------------------------------------------------------------------------
  965. void ListPanel::RereadAllItems()
  966. {
  967. //!! need to make this more efficient
  968. InvalidateLayout();
  969. }
  970. //-----------------------------------------------------------------------------
  971. // Cleans up allocations associated with a particular item
  972. //-----------------------------------------------------------------------------
  973. void ListPanel::CleanupItem( FastSortListPanelItem *data )
  974. {
  975. if ( data )
  976. {
  977. if (data->kv)
  978. {
  979. data->kv->deleteThis();
  980. }
  981. if (data->m_pDragData)
  982. {
  983. data->m_pDragData->deleteThis();
  984. }
  985. delete data;
  986. }
  987. }
  988. //-----------------------------------------------------------------------------
  989. // Purpose: Removes an item at the specified item
  990. //-----------------------------------------------------------------------------
  991. void ListPanel::RemoveItem(int itemID)
  992. {
  993. #ifdef _GAMECONSOLE
  994. bool renavigate = false;
  995. if(HasFocus())
  996. {
  997. for(int i = 0; i < GetSelectedItemsCount(); ++i)
  998. {
  999. if(itemID == GetSelectedItem(i))
  1000. {
  1001. renavigate = true;
  1002. break;
  1003. }
  1004. }
  1005. }
  1006. #endif
  1007. FastSortListPanelItem *data = (FastSortListPanelItem*) m_DataItems[itemID];
  1008. if (!data)
  1009. return;
  1010. // remove from column sorted indexes
  1011. int i;
  1012. for ( i = 0; i < m_ColumnsHistory.Count(); i++ )
  1013. {
  1014. if ( m_ColumnsHistory[i] == m_ColumnsData.InvalidIndex())
  1015. continue;
  1016. IndexRBTree_t &rbtree = m_ColumnsData[m_ColumnsHistory[i]].m_SortedTree;
  1017. rbtree.RemoveAt(data->m_SortedTreeIndexes[i]);
  1018. }
  1019. // remove from selection
  1020. m_SelectedItems.FindAndRemove(itemID);
  1021. PostActionSignal( new KeyValues("ItemDeselected") );
  1022. // remove from visible items
  1023. m_VisibleItems.FindAndRemove(itemID);
  1024. // remove from data
  1025. m_DataItems.Remove(itemID);
  1026. CleanupItem( data );
  1027. InvalidateLayout();
  1028. #ifdef _GAMECONSOLE
  1029. if(renavigate)
  1030. {
  1031. NavigateTo();
  1032. }
  1033. #endif
  1034. }
  1035. //-----------------------------------------------------------------------------
  1036. // Purpose: clears and deletes all the memory used by the data items
  1037. //-----------------------------------------------------------------------------
  1038. void ListPanel::RemoveAll()
  1039. {
  1040. // remove all sort indexes
  1041. for (int i = 0; i < m_ColumnsHistory.Count(); i++)
  1042. {
  1043. m_ColumnsData[m_ColumnsHistory[i]].m_SortedTree.RemoveAll();
  1044. }
  1045. FOR_EACH_LL( m_DataItems, index )
  1046. {
  1047. FastSortListPanelItem *pItem = m_DataItems[index];
  1048. CleanupItem( pItem );
  1049. }
  1050. m_DataItems.RemoveAll();
  1051. m_VisibleItems.RemoveAll();
  1052. ClearSelectedItems();
  1053. InvalidateLayout();
  1054. #ifdef _GAMECONSOLE
  1055. if(HasFocus())
  1056. {
  1057. NavigateTo();
  1058. }
  1059. #endif
  1060. }
  1061. //-----------------------------------------------------------------------------
  1062. // Purpose: obselete, use RemoveAll();
  1063. //-----------------------------------------------------------------------------
  1064. void ListPanel::DeleteAllItems()
  1065. {
  1066. RemoveAll();
  1067. }
  1068. //-----------------------------------------------------------------------------
  1069. // Purpose:
  1070. //-----------------------------------------------------------------------------
  1071. void ListPanel::ResetScrollBar()
  1072. {
  1073. // delete and reallocate to besure the scroll bar's
  1074. // information is correct.
  1075. delete m_vbar;
  1076. m_vbar = new ScrollBar(this, "VertScrollBar", true);
  1077. m_vbar->SetVisible(false);
  1078. m_vbar->AddActionSignalTarget(this);
  1079. }
  1080. void ListPanel::ScrollToItem( int nItemID )
  1081. {
  1082. int row = GetItemCurrentRow( nItemID );
  1083. int top = m_vbar->GetValue();
  1084. int rowsperpage = (int) GetRowsPerPage();
  1085. if ( row < top )
  1086. {
  1087. m_vbar->SetValue( row );
  1088. }
  1089. else if ( row >= top + rowsperpage )
  1090. {
  1091. m_vbar->SetValue( row - rowsperpage + 1 );
  1092. }
  1093. }
  1094. //-----------------------------------------------------------------------------
  1095. // Purpose: returns the count of selected rows
  1096. //-----------------------------------------------------------------------------
  1097. int ListPanel::GetSelectedItemsCount()
  1098. {
  1099. return m_SelectedItems.Count();
  1100. }
  1101. //-----------------------------------------------------------------------------
  1102. // Purpose: returns the selected item by selection index
  1103. // Input : selectionIndex - valid in range [0, GetNumSelectedRows)
  1104. // Output : int - itemID
  1105. //-----------------------------------------------------------------------------
  1106. int ListPanel::GetSelectedItem(int selectionIndex)
  1107. {
  1108. if ( m_SelectedItems.IsValidIndex(selectionIndex))
  1109. return m_SelectedItems[selectionIndex];
  1110. return -1;
  1111. }
  1112. //-----------------------------------------------------------------------------
  1113. // Purpose:
  1114. //-----------------------------------------------------------------------------
  1115. int ListPanel::GetSelectedColumn()
  1116. {
  1117. return m_iSelectedColumn;
  1118. }
  1119. //-----------------------------------------------------------------------------
  1120. // Purpose: Clears all selected rows
  1121. //-----------------------------------------------------------------------------
  1122. void ListPanel::ClearSelectedItems()
  1123. {
  1124. int nPrevCount = m_SelectedItems.Count();
  1125. m_SelectedItems.RemoveAll();
  1126. if ( nPrevCount > 0 )
  1127. {
  1128. PostActionSignal( new KeyValues("ItemDeselected") );
  1129. }
  1130. m_LastItemSelected = -1;
  1131. m_iSelectedColumn = -1;
  1132. }
  1133. //-----------------------------------------------------------------------------
  1134. bool ListPanel::IsItemSelected( int itemID )
  1135. {
  1136. return m_DataItems.IsValidIndex( itemID ) && m_SelectedItems.HasElement( itemID );
  1137. }
  1138. //-----------------------------------------------------------------------------
  1139. // Purpose:
  1140. //-----------------------------------------------------------------------------
  1141. void ListPanel::AddSelectedItem( int itemID )
  1142. {
  1143. if ( !m_DataItems.IsValidIndex(itemID) )
  1144. return;
  1145. Assert( !m_SelectedItems.HasElement( itemID ) );
  1146. m_LastItemSelected = itemID;
  1147. m_SelectedItems.AddToTail( itemID );
  1148. PostActionSignal( new KeyValues("ItemSelected") );
  1149. Repaint();
  1150. }
  1151. //-----------------------------------------------------------------------------
  1152. // Purpose:
  1153. //-----------------------------------------------------------------------------
  1154. void ListPanel::SetSingleSelectedItem( int itemID )
  1155. {
  1156. ClearSelectedItems();
  1157. AddSelectedItem(itemID);
  1158. }
  1159. //-----------------------------------------------------------------------------
  1160. // Purpose:
  1161. //-----------------------------------------------------------------------------
  1162. void ListPanel::SetSelectedCell(int itemID, int col)
  1163. {
  1164. if ( !m_bCanSelectIndividualCells )
  1165. {
  1166. SetSingleSelectedItem(itemID);
  1167. return;
  1168. }
  1169. // make sure it's a valid cell
  1170. if ( !m_DataItems.IsValidIndex(itemID) )
  1171. return;
  1172. if ( !m_CurrentColumns.IsValidIndex(col) )
  1173. return;
  1174. SetSingleSelectedItem( itemID );
  1175. m_iSelectedColumn = col;
  1176. }
  1177. //-----------------------------------------------------------------------------
  1178. // Purpose: returns the data held by a specific cell
  1179. //-----------------------------------------------------------------------------
  1180. void ListPanel::GetCellText(int itemID, int col, wchar_t *wbuffer, int bufferSize)
  1181. {
  1182. if ( !wbuffer || !bufferSize )
  1183. return;
  1184. wcscpy( wbuffer, L"" );
  1185. KeyValues *itemData = GetItem( itemID );
  1186. if ( !itemData )
  1187. {
  1188. return;
  1189. }
  1190. // Look up column header
  1191. if ( col < 0 || col >= m_CurrentColumns.Count() )
  1192. {
  1193. return;
  1194. }
  1195. const char *key = m_ColumnsData[m_CurrentColumns[col]].m_pHeader->GetName();
  1196. if ( !key || !key[ 0 ] )
  1197. {
  1198. return;
  1199. }
  1200. char const *val = itemData->GetString( key, "" );
  1201. if ( !val || !key[ 0 ] )
  1202. return;
  1203. const wchar_t *wval = NULL;
  1204. if ( val[ 0 ] == '#' )
  1205. {
  1206. StringIndex_t si = g_pVGuiLocalize->FindIndex( val + 1 );
  1207. if ( si != INVALID_STRING_INDEX )
  1208. {
  1209. wval = g_pVGuiLocalize->GetValueByIndex( si );
  1210. }
  1211. }
  1212. if ( !wval )
  1213. {
  1214. wval = itemData->GetWString( key, L"" );
  1215. }
  1216. wcsncpy( wbuffer, wval, bufferSize/sizeof(wchar_t) );
  1217. wbuffer[ (bufferSize/sizeof(wchar_t)) - 1 ] = 0;
  1218. }
  1219. //-----------------------------------------------------------------------------
  1220. // Purpose: returns the data held by a specific cell
  1221. //-----------------------------------------------------------------------------
  1222. IImage *ListPanel::GetCellImage(int itemID, int col) //, ImagePanel *&buffer)
  1223. {
  1224. // if ( !buffer )
  1225. // return;
  1226. KeyValues *itemData = GetItem( itemID );
  1227. if ( !itemData )
  1228. {
  1229. return NULL;
  1230. }
  1231. // Look up column header
  1232. if ( col < 0 || col >= m_CurrentColumns.Count() )
  1233. {
  1234. return NULL;
  1235. }
  1236. const char *key = m_ColumnsData[m_CurrentColumns[col]].m_pHeader->GetName();
  1237. if ( !key || !key[ 0 ] )
  1238. {
  1239. return NULL;
  1240. }
  1241. if ( !m_pImageList )
  1242. {
  1243. return NULL;
  1244. }
  1245. int imageIndex = itemData->GetInt( key, 0 );
  1246. if ( m_pImageList->IsValidIndex(imageIndex) )
  1247. {
  1248. if ( imageIndex > 0 )
  1249. {
  1250. return m_pImageList->GetImage(imageIndex);
  1251. }
  1252. }
  1253. return NULL;
  1254. }
  1255. //-----------------------------------------------------------------------------
  1256. // Purpose: Returns the panel to use to render a cell
  1257. //-----------------------------------------------------------------------------
  1258. Panel *ListPanel::GetCellRenderer(int itemID, int col)
  1259. {
  1260. Assert( m_pTextImage );
  1261. Assert( m_pImagePanel );
  1262. column_t &column = m_ColumnsData[ m_CurrentColumns[col] ];
  1263. IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
  1264. m_pLabel->SetContentAlignment( (Label::Alignment)column.m_nContentAlignment );
  1265. if ( column.m_bTypeIsText )
  1266. {
  1267. wchar_t tempText[ 256 ];
  1268. // Grab cell text
  1269. GetCellText( itemID, col, tempText, 256 );
  1270. KeyValues *item = GetItem( itemID );
  1271. m_pTextImage->SetText(tempText);
  1272. int cw, tall;
  1273. m_pTextImage->GetContentSize(cw, tall);
  1274. // set cell size
  1275. Panel *header = column.m_pHeader;
  1276. int wide = header->GetWide();
  1277. m_pTextImage->SetSize( min( cw, wide - 5 ), tall);
  1278. m_pLabel->SetTextImageIndex( 0 );
  1279. m_pLabel->SetImageAtIndex(0, m_pTextImage, 3);
  1280. bool selected = false;
  1281. if ( m_SelectedItems.HasElement(itemID) && ( !m_bCanSelectIndividualCells || col == m_iSelectedColumn ) )
  1282. {
  1283. selected = true;
  1284. VPANEL focus = input()->GetFocus();
  1285. // if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected
  1286. if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent())))
  1287. {
  1288. m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedBgColor", pScheme));
  1289. // selection
  1290. }
  1291. else
  1292. {
  1293. m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedOutOfFocusBgColor", pScheme));
  1294. }
  1295. if ( item->IsEmpty("cellcolor") == false )
  1296. {
  1297. m_pTextImage->SetColor( item->GetColor( "cellcolor" ) );
  1298. }
  1299. else if ( item->GetInt("disabled", 0) == 0 )
  1300. {
  1301. m_pTextImage->SetColor(m_SelectionFgColor);
  1302. }
  1303. else
  1304. {
  1305. m_pTextImage->SetColor(m_DisabledSelectionFgColor);
  1306. }
  1307. m_pLabel->SetPaintBackgroundEnabled(true);
  1308. }
  1309. else
  1310. {
  1311. if ( item->IsEmpty("cellcolor") == false )
  1312. {
  1313. m_pTextImage->SetColor( item->GetColor( "cellcolor" ) );
  1314. }
  1315. else if ( item->GetInt("disabled", 0) == 0 )
  1316. {
  1317. m_pTextImage->SetColor(m_LabelFgColor);
  1318. }
  1319. else
  1320. {
  1321. m_pTextImage->SetColor(m_DisabledColor);
  1322. }
  1323. m_pLabel->SetPaintBackgroundEnabled(false);
  1324. }
  1325. FastSortListPanelItem *listItem = m_DataItems[ itemID ];
  1326. if ( col == 0 &&
  1327. listItem->m_bImage && m_pImageList )
  1328. {
  1329. IImage *pImage = NULL;
  1330. if ( listItem->m_pIcon )
  1331. {
  1332. pImage = listItem->m_pIcon;
  1333. }
  1334. else
  1335. {
  1336. int imageIndex = selected ? listItem->m_nImageIndexSelected : listItem->m_nImageIndex;
  1337. if ( m_pImageList->IsValidIndex(imageIndex) )
  1338. {
  1339. pImage = m_pImageList->GetImage(imageIndex);
  1340. }
  1341. }
  1342. if ( pImage )
  1343. {
  1344. m_pLabel->SetTextImageIndex( 1 );
  1345. m_pLabel->SetImageAtIndex(0, pImage, 0);
  1346. m_pLabel->SetImageAtIndex(1, m_pTextImage, 3);
  1347. }
  1348. }
  1349. return m_pLabel;
  1350. }
  1351. else // if its an Image Panel
  1352. {
  1353. if ( m_SelectedItems.HasElement(itemID) && ( !m_bCanSelectIndividualCells || col == m_iSelectedColumn ) )
  1354. {
  1355. VPANEL focus = input()->GetFocus();
  1356. // if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected
  1357. if (HasFocus() || (focus && ipanel()->HasParent(focus, GetVParent())))
  1358. {
  1359. m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedBgColor", pScheme));
  1360. // selection
  1361. }
  1362. else
  1363. {
  1364. m_pLabel->SetBgColor(GetSchemeColor("ListPanel.SelectedOutOfFocusBgColor", pScheme));
  1365. }
  1366. // selection
  1367. m_pLabel->SetPaintBackgroundEnabled(true);
  1368. }
  1369. else
  1370. {
  1371. m_pLabel->SetPaintBackgroundEnabled(false);
  1372. }
  1373. IImage *pIImage = GetCellImage(itemID, col);
  1374. m_pLabel->SetImageAtIndex(0, pIImage, 0);
  1375. return m_pLabel;
  1376. }
  1377. }
  1378. //-----------------------------------------------------------------------------
  1379. // Purpose: relayouts out the panel after any internal changes
  1380. //-----------------------------------------------------------------------------
  1381. void ListPanel::PerformLayout()
  1382. {
  1383. if ( m_CurrentColumns.Count() == 0 )
  1384. return;
  1385. if (m_bNeedsSort)
  1386. {
  1387. SortList();
  1388. }
  1389. int rowsperpage = (int) GetRowsPerPage();
  1390. // count the number of visible items
  1391. int visibleItemCount = m_VisibleItems.Count();
  1392. //!! need to make it recalculate scroll positions
  1393. m_vbar->SetVisible( visibleItemCount > rowsperpage );
  1394. m_vbar->SetEnabled(false);
  1395. m_vbar->SetRangeWindow( rowsperpage );
  1396. m_vbar->SetRange( 0, visibleItemCount);
  1397. m_vbar->SetButtonPressedScrollValue( 1 );
  1398. int wide, tall;
  1399. GetSize( wide, tall );
  1400. m_vbar->SetPos(wide - (m_vbar->GetWide()+WINDOW_BORDER_WIDTH), 0);
  1401. m_vbar->SetSize(m_vbar->GetWide(), tall - 2);
  1402. m_vbar->InvalidateLayout();
  1403. int buttonMaxXPos = wide - (m_vbar->GetWide()+WINDOW_BORDER_WIDTH);
  1404. int nColumns = m_CurrentColumns.Count();
  1405. // number of bars that can be resized
  1406. int numToResize=0;
  1407. if (m_iColumnDraggerMoved != -1) // we're resizing in response to a column dragger
  1408. {
  1409. numToResize = 1; // only one column will change size, the one we dragged
  1410. }
  1411. else // we're resizing in response to a window resize
  1412. {
  1413. for (int i = 0; i < nColumns; i++)
  1414. {
  1415. if ( m_ColumnsData[m_CurrentColumns[i]].m_bResizesWithWindow // column is resizable in response to window
  1416. && !m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
  1417. {
  1418. numToResize++;
  1419. }
  1420. }
  1421. }
  1422. int dxPerBar; // zero on window first opening
  1423. // location of the last column resizer
  1424. int oldSizeX = 0, oldSizeY = 0;
  1425. int lastColumnIndex = nColumns-1;
  1426. for (int i = nColumns-1; i >= 0; --i)
  1427. {
  1428. if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
  1429. {
  1430. m_ColumnsData[m_CurrentColumns[i]].m_pHeader->GetPos(oldSizeX, oldSizeY);
  1431. lastColumnIndex = i;
  1432. break;
  1433. }
  1434. }
  1435. bool bForceShrink = false;
  1436. if ( numToResize == 0 )
  1437. {
  1438. // make sure we've got enough to be within minwidth
  1439. int minWidth=0;
  1440. for (int i = 0; i < nColumns; i++)
  1441. {
  1442. if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
  1443. {
  1444. minWidth += m_ColumnsData[m_CurrentColumns[i]].m_iMinWidth;
  1445. }
  1446. }
  1447. // if all the minimum widths cannot fit in the space given, then we will shrink ALL columns an equal amount
  1448. if (minWidth > buttonMaxXPos)
  1449. {
  1450. int dx = buttonMaxXPos - minWidth;
  1451. dxPerBar=(int)((float)dx/(float)nColumns);
  1452. bForceShrink = true;
  1453. }
  1454. else
  1455. {
  1456. dxPerBar = 0;
  1457. }
  1458. m_lastBarWidth = buttonMaxXPos;
  1459. }
  1460. else if ( oldSizeX != 0 ) // make sure this isnt the first time we opened the window
  1461. {
  1462. int dx = buttonMaxXPos - m_lastBarWidth; // this is how much we grew or shrank.
  1463. // see how many bars we have and now much each should grow/shrink
  1464. dxPerBar=(int)((float)dx/(float)numToResize);
  1465. m_lastBarWidth = buttonMaxXPos;
  1466. }
  1467. else // this is the first time we've opened the window, make sure all our colums fit! resize if needed
  1468. {
  1469. int startingBarWidth=0;
  1470. for (int i = 0; i < nColumns; i++)
  1471. {
  1472. if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
  1473. {
  1474. startingBarWidth += m_ColumnsData[m_CurrentColumns[i]].m_pHeader->GetWide();
  1475. }
  1476. }
  1477. int dx = buttonMaxXPos - startingBarWidth; // this is how much we grew or shrank.
  1478. // see how many bars we have and now much each should grow/shrink
  1479. dxPerBar=(int)((float)dx/(float)numToResize);
  1480. m_lastBarWidth = buttonMaxXPos;
  1481. }
  1482. // Make sure nothing is smaller than minwidth to start with or else we'll get into trouble below.
  1483. for ( int i=0; i < nColumns; i++ )
  1484. {
  1485. column_t &column = m_ColumnsData[m_CurrentColumns[i]];
  1486. Panel *header = column.m_pHeader;
  1487. if ( header->GetWide() < column.m_iMinWidth )
  1488. header->SetWide( column.m_iMinWidth );
  1489. }
  1490. // This was a while(1) loop and we hit an infinite loop case, so now we max out the # of times it can loop.
  1491. for ( int iLoopSanityCheck=0; iLoopSanityCheck < 1000; iLoopSanityCheck++ )
  1492. {
  1493. // try and place headers as is - before we have to force items to be minimum width
  1494. int x = -1;
  1495. int i;
  1496. for ( i = 0; i < nColumns; i++)
  1497. {
  1498. column_t &column = m_ColumnsData[m_CurrentColumns[i]];
  1499. Panel *header = column.m_pHeader;
  1500. if (column.m_bHidden)
  1501. {
  1502. header->SetVisible(false);
  1503. continue;
  1504. }
  1505. header->SetPos(x, 0);
  1506. header->SetVisible(true);
  1507. // if we couldn't fit this column - then we need to force items to be minimum width
  1508. if ( x+column.m_iMinWidth >= buttonMaxXPos && !bForceShrink )
  1509. {
  1510. break;
  1511. }
  1512. int hWide = header->GetWide();
  1513. // calculate the column's width
  1514. // make it so the last column always attaches to the scroll bar
  1515. if ( i == lastColumnIndex )
  1516. {
  1517. hWide = buttonMaxXPos-x;
  1518. }
  1519. else if (i == m_iColumnDraggerMoved ) // column resizing using dragger
  1520. {
  1521. hWide += dxPerBar; // adjust width of column
  1522. }
  1523. else if ( m_iColumnDraggerMoved == -1 ) // window is resizing
  1524. {
  1525. // either this column is allowed to resize OR we are forcing it because we're shrinking all columns
  1526. if ( column.m_bResizesWithWindow || bForceShrink )
  1527. {
  1528. Assert ( column.m_iMinWidth <= column.m_iMaxWidth );
  1529. hWide += dxPerBar; // adjust width of column
  1530. }
  1531. }
  1532. // enforce column mins and max's - unless we're FORCING it to shrink
  1533. if ( hWide < column.m_iMinWidth && !bForceShrink )
  1534. {
  1535. hWide = column.m_iMinWidth; // adjust width of column
  1536. }
  1537. else if ( hWide > column.m_iMaxWidth )
  1538. {
  1539. hWide = column.m_iMaxWidth;
  1540. }
  1541. header->SetSize(hWide, m_vbar->GetWide());
  1542. x += hWide;
  1543. // set the resizers
  1544. Panel *sizer = column.m_pResizer;
  1545. if ( i == lastColumnIndex )
  1546. {
  1547. sizer->SetVisible(false);
  1548. }
  1549. else
  1550. {
  1551. sizer->SetVisible(true);
  1552. }
  1553. sizer->MoveToFront();
  1554. sizer->SetPos(x - 4, 0);
  1555. sizer->SetSize(8, m_vbar->GetWide());
  1556. }
  1557. // we made it all the way through
  1558. if ( i == nColumns )
  1559. break;
  1560. // we do this AFTER trying first, to let as many columns as possible try and get to their
  1561. // desired width before we forcing the minimum width on them
  1562. // get the total desired width of all the columns
  1563. int totalDesiredWidth = 0;
  1564. for ( i = 0 ; i < nColumns ; i++ )
  1565. {
  1566. if (!m_ColumnsData[m_CurrentColumns[i]].m_bHidden)
  1567. {
  1568. Panel *pHeader = m_ColumnsData[m_CurrentColumns[i]].m_pHeader;
  1569. totalDesiredWidth += pHeader->GetWide();
  1570. }
  1571. }
  1572. // shrink from the most right column to minimum width until we can fit them all
  1573. Assert(totalDesiredWidth > buttonMaxXPos);
  1574. for ( i = nColumns-1; i >= 0 ; i--)
  1575. {
  1576. column_t &column = m_ColumnsData[m_CurrentColumns[i]];
  1577. if (!column.m_bHidden)
  1578. {
  1579. Panel *pHeader = column.m_pHeader;
  1580. totalDesiredWidth -= pHeader->GetWide();
  1581. if ( totalDesiredWidth + column.m_iMinWidth <= buttonMaxXPos )
  1582. {
  1583. int newWidth = buttonMaxXPos - totalDesiredWidth;
  1584. pHeader->SetSize( newWidth, m_vbar->GetWide() );
  1585. break;
  1586. }
  1587. totalDesiredWidth += column.m_iMinWidth;
  1588. pHeader->SetSize(column.m_iMinWidth, m_vbar->GetWide());
  1589. }
  1590. }
  1591. // If we don't allow this to shrink, then as we resize, it can get stuck in an infinite loop.
  1592. dxPerBar -= 5;
  1593. if ( dxPerBar < 0 )
  1594. dxPerBar = 0;
  1595. if ( i == -1 )
  1596. {
  1597. break;
  1598. }
  1599. }
  1600. // setup edit mode
  1601. if ( m_hEditModePanel.Get() )
  1602. {
  1603. m_iTableStartX = 0;
  1604. m_iTableStartY = m_iHeaderHeight + 1;
  1605. int nTotalRows = m_VisibleItems.Count();
  1606. int nRowsPerPage = GetRowsPerPage();
  1607. // find the first visible item to display
  1608. int nStartItem = 0;
  1609. if (nRowsPerPage <= nTotalRows)
  1610. {
  1611. nStartItem = m_vbar->GetValue();
  1612. }
  1613. bool bDone = false;
  1614. int drawcount = 0;
  1615. for (int i = nStartItem; i < nTotalRows && !bDone; i++)
  1616. {
  1617. int x = 0;
  1618. if (!m_VisibleItems.IsValidIndex(i))
  1619. continue;
  1620. int itemID = m_VisibleItems[i];
  1621. // iterate the columns
  1622. for (int j = 0; j < m_CurrentColumns.Count(); j++)
  1623. {
  1624. Panel *header = m_ColumnsData[m_CurrentColumns[j]].m_pHeader;
  1625. if (!header->IsVisible())
  1626. continue;
  1627. int wide = header->GetWide();
  1628. if ( itemID == m_iEditModeItemID &&
  1629. j == m_iEditModeColumn )
  1630. {
  1631. m_hEditModePanel->SetPos( x + m_iTableStartX + 2, (drawcount * m_iRowHeight) + m_iTableStartY);
  1632. m_hEditModePanel->SetSize( wide, m_iRowHeight - 1 );
  1633. bDone = true;
  1634. }
  1635. x += wide;
  1636. }
  1637. drawcount++;
  1638. }
  1639. }
  1640. Repaint();
  1641. m_iColumnDraggerMoved = -1; // reset to invalid column
  1642. }
  1643. //-----------------------------------------------------------------------------
  1644. // Purpose:
  1645. //-----------------------------------------------------------------------------
  1646. void ListPanel::OnSizeChanged(int wide, int tall)
  1647. {
  1648. BaseClass::OnSizeChanged(wide, tall);
  1649. InvalidateLayout();
  1650. Repaint();
  1651. }
  1652. //-----------------------------------------------------------------------------
  1653. // Purpose: Renders the cells
  1654. //-----------------------------------------------------------------------------
  1655. void ListPanel::Paint()
  1656. {
  1657. if (m_bNeedsSort)
  1658. {
  1659. SortList();
  1660. }
  1661. // draw selection areas if any
  1662. int wide, tall;
  1663. GetSize( wide, tall );
  1664. m_iTableStartX = 0;
  1665. m_iTableStartY = m_iHeaderHeight + 1;
  1666. int nTotalRows = m_VisibleItems.Count();
  1667. int nRowsPerPage = GetRowsPerPage();
  1668. // find the first visible item to display
  1669. int nStartItem = 0;
  1670. if (nRowsPerPage <= nTotalRows)
  1671. {
  1672. nStartItem = m_vbar->GetValue();
  1673. }
  1674. int vbarInset = m_vbar->IsVisible() ? m_vbar->GetWide() : 0;
  1675. int maxw = wide - vbarInset - 8;
  1676. // debug timing functions
  1677. // double startTime, endTime;
  1678. // startTime = system()->GetCurrentTime();
  1679. // iterate through and draw each cell
  1680. bool bDone = false;
  1681. int drawcount = 0;
  1682. for (int i = nStartItem; i < nTotalRows && !bDone; i++)
  1683. {
  1684. int x = 0;
  1685. if (!m_VisibleItems.IsValidIndex(i))
  1686. continue;
  1687. int itemID = m_VisibleItems[i];
  1688. // iterate the columns
  1689. for (int j = 0; j < m_CurrentColumns.Count(); j++)
  1690. {
  1691. Panel *header = m_ColumnsData[m_CurrentColumns[j]].m_pHeader;
  1692. Panel *render = GetCellRenderer(itemID, j);
  1693. if (!header->IsVisible())
  1694. continue;
  1695. int wide = header->GetWide();
  1696. if (render)
  1697. {
  1698. // setup render panel
  1699. if (render->GetVParent() != GetVPanel())
  1700. {
  1701. render->SetParent(GetVPanel());
  1702. }
  1703. if (!render->IsVisible())
  1704. {
  1705. render->SetVisible(true);
  1706. }
  1707. int xpos = x + m_iTableStartX + 2;
  1708. render->SetPos( xpos, (drawcount * m_iRowHeight) + m_iTableStartY);
  1709. int right = min( xpos + wide, maxw );
  1710. int usew = right - xpos;
  1711. render->SetSize( usew, m_iRowHeight - 1 );
  1712. // mark the panel to draw immediately (since it will probably be recycled to draw other cells)
  1713. render->Repaint();
  1714. surface()->SolveTraverse(render->GetVPanel());
  1715. int x0, y0, x1, y1;
  1716. render->GetClipRect(x0, y0, x1, y1);
  1717. if ((y1 - y0) < (m_iRowHeight - 3))
  1718. {
  1719. bDone = true;
  1720. break;
  1721. }
  1722. surface()->PaintTraverse(render->GetVPanel());
  1723. }
  1724. /*
  1725. // work in progress, optimized paint for text
  1726. else
  1727. {
  1728. // just paint it ourselves
  1729. char tempText[256];
  1730. // Grab cell text
  1731. GetCellText(i, j, tempText, sizeof(tempText));
  1732. surface()->DrawSetTextPos(x + m_iTableStartX + 2, (drawcount * m_iRowHeight) + m_iTableStartY);
  1733. for (const char *pText = tempText; *pText != 0; pText++)
  1734. {
  1735. surface()->DrawUnicodeChar((wchar_t)*pText);
  1736. }
  1737. }
  1738. */
  1739. x += wide;
  1740. }
  1741. drawcount++;
  1742. }
  1743. m_pLabel->SetVisible(false);
  1744. // if the list is empty, draw some help text
  1745. if (m_VisibleItems.Count() < 1 && m_pEmptyListText)
  1746. {
  1747. m_pEmptyListText->SetPos(m_iTableStartX + 8, m_iTableStartY + 4);
  1748. m_pEmptyListText->SetSize(wide - 8, m_iRowHeight);
  1749. m_pEmptyListText->Paint();
  1750. }
  1751. // endTime = system()->GetCurrentTime();
  1752. // ivgui()->DPrintf2("ListPanel::Paint() (%.3f sec)\n", (float)(endTime - startTime));
  1753. }
  1754. //-----------------------------------------------------------------------------
  1755. // Purpose:
  1756. //-----------------------------------------------------------------------------
  1757. void ListPanel::PaintBackground()
  1758. {
  1759. BaseClass::PaintBackground();
  1760. }
  1761. //-----------------------------------------------------------------------------
  1762. // Handles multiselect
  1763. //-----------------------------------------------------------------------------
  1764. void ListPanel::HandleMultiSelection( int itemID, int row, int column )
  1765. {
  1766. // deal with 'multiple' row selection
  1767. // convert the last item selected to a row so we can multiply select by rows NOT items
  1768. int lastSelectedRow = (m_LastItemSelected != -1) ? m_VisibleItems.Find( m_LastItemSelected ) : row;
  1769. int startRow, endRow;
  1770. if ( row < lastSelectedRow )
  1771. {
  1772. startRow = row;
  1773. endRow = lastSelectedRow;
  1774. }
  1775. else
  1776. {
  1777. startRow = lastSelectedRow;
  1778. endRow = row;
  1779. }
  1780. // clear the selection if neither control key was down - we are going to readd ALL selected items
  1781. // in case the user changed the 'direction' of the shift add
  1782. if ( !input()->IsKeyDown(KEY_LCONTROL) && !input()->IsKeyDown(KEY_RCONTROL) )
  1783. {
  1784. ClearSelectedItems();
  1785. }
  1786. // add any items that we haven't added
  1787. for (int i = startRow; i <= endRow; i++)
  1788. {
  1789. // get the item indexes for these rows
  1790. int selectedItemID = m_VisibleItems[i];
  1791. if ( !m_SelectedItems.HasElement(selectedItemID) )
  1792. {
  1793. AddSelectedItem( selectedItemID );
  1794. }
  1795. }
  1796. }
  1797. //-----------------------------------------------------------------------------
  1798. // Handles multiselect
  1799. //-----------------------------------------------------------------------------
  1800. void ListPanel::HandleAddSelection( int itemID, int row, int column )
  1801. {
  1802. // dealing with row selection
  1803. if ( m_SelectedItems.HasElement( itemID ) )
  1804. {
  1805. // this row is already selected, remove
  1806. m_SelectedItems.FindAndRemove( itemID );
  1807. PostActionSignal( new KeyValues("ItemDeselected") );
  1808. m_LastItemSelected = itemID;
  1809. }
  1810. else
  1811. {
  1812. // add the row to the selection
  1813. AddSelectedItem( itemID );
  1814. }
  1815. }
  1816. //-----------------------------------------------------------------------------
  1817. // Purpose:
  1818. //-----------------------------------------------------------------------------
  1819. void ListPanel::UpdateSelection( MouseCode code, int x, int y, int row, int column )
  1820. {
  1821. // make sure we're clicking on a real item
  1822. if ( row < 0 || row >= m_VisibleItems.Count() )
  1823. {
  1824. ClearSelectedItems();
  1825. return;
  1826. }
  1827. int itemID = m_VisibleItems[ row ];
  1828. // if we've right-clicked on a selection, don't change the selection
  1829. if ( code == MOUSE_RIGHT && m_SelectedItems.HasElement( itemID ) )
  1830. return;
  1831. if ( m_bCanSelectIndividualCells )
  1832. {
  1833. if ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) )
  1834. {
  1835. // we're ctrl selecting the same cell, clear it
  1836. if ( ( m_LastItemSelected == itemID ) && ( m_iSelectedColumn == column ) && ( m_SelectedItems.Count() == 1 ) )
  1837. {
  1838. ClearSelectedItems();
  1839. }
  1840. else
  1841. {
  1842. SetSelectedCell( itemID, column );
  1843. }
  1844. }
  1845. else
  1846. {
  1847. SetSelectedCell( itemID, column );
  1848. }
  1849. return;
  1850. }
  1851. if ( !m_bMultiselectEnabled )
  1852. {
  1853. SetSingleSelectedItem( itemID );
  1854. return;
  1855. }
  1856. if ( input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT) )
  1857. {
  1858. // check for multi-select
  1859. HandleMultiSelection( itemID, row, column );
  1860. }
  1861. else if ( input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL) )
  1862. {
  1863. // check for row-add select
  1864. HandleAddSelection( itemID, row, column );
  1865. }
  1866. else
  1867. {
  1868. // no CTRL or SHIFT keys
  1869. // reset the selection Start point
  1870. // if ( ( m_LastItemSelected != itemID ) || ( m_SelectedItems.Count() > 1 ) )
  1871. {
  1872. SetSingleSelectedItem( itemID );
  1873. }
  1874. }
  1875. }
  1876. //-----------------------------------------------------------------------------
  1877. // Purpose:
  1878. //-----------------------------------------------------------------------------
  1879. void ListPanel::OnMousePressed( MouseCode code )
  1880. {
  1881. if (code == MOUSE_LEFT || code == MOUSE_RIGHT)
  1882. {
  1883. if ( m_VisibleItems.Count() > 0 )
  1884. {
  1885. // determine where we were pressed
  1886. int x, y, row, column;
  1887. input()->GetCursorPos(x, y);
  1888. GetCellAtPos(x, y, row, column);
  1889. UpdateSelection( code, x, y, row, column );
  1890. }
  1891. // get the key focus
  1892. RequestFocus();
  1893. }
  1894. // check for context menu open
  1895. if (code == MOUSE_RIGHT)
  1896. {
  1897. if ( m_SelectedItems.Count() > 0 )
  1898. {
  1899. PostActionSignal( new KeyValues("OpenContextMenu", "itemID", m_SelectedItems[0] ));
  1900. }
  1901. else
  1902. {
  1903. // post it, but with the invalid row
  1904. PostActionSignal( new KeyValues("OpenContextMenu", "itemID", -1 ));
  1905. }
  1906. }
  1907. }
  1908. //-----------------------------------------------------------------------------
  1909. // Purpose: Scrolls the list according to the mouse wheel movement
  1910. //-----------------------------------------------------------------------------
  1911. void ListPanel::OnMouseWheeled(int delta)
  1912. {
  1913. if (m_hEditModePanel.Get())
  1914. {
  1915. // ignore mouse wheel in edit mode, forward right up to parent
  1916. CallParentFunction(new KeyValues("MouseWheeled", "delta", delta));
  1917. return;
  1918. }
  1919. int val = m_vbar->GetValue();
  1920. val -= (delta * 3);
  1921. m_vbar->SetValue(val);
  1922. }
  1923. //-----------------------------------------------------------------------------
  1924. // Purpose: Double-click act like the the item under the mouse was selected
  1925. // and then the enter key hit
  1926. //-----------------------------------------------------------------------------
  1927. void ListPanel::OnMouseDoublePressed(MouseCode code)
  1928. {
  1929. if (code == MOUSE_LEFT)
  1930. {
  1931. // select the item
  1932. OnMousePressed(code);
  1933. // post up an enter key being hit if anything was selected
  1934. if (GetSelectedItemsCount() > 0 && !m_bIgnoreDoubleClick )
  1935. {
  1936. OnKeyCodeTyped(KEY_ENTER);
  1937. }
  1938. }
  1939. }
  1940. //-----------------------------------------------------------------------------
  1941. // Purpose:
  1942. //-----------------------------------------------------------------------------
  1943. #ifdef _GAMECONSOLE
  1944. void ListPanel::OnKeyCodePressed(KeyCode code)
  1945. {
  1946. int nTotalRows = m_VisibleItems.Count();
  1947. int nTotalColumns = m_CurrentColumns.Count();
  1948. if ( nTotalRows == 0 )
  1949. return;
  1950. // calculate info for adjusting scrolling
  1951. int nStartItem = GetStartItem();
  1952. int nRowsPerPage = (int)GetRowsPerPage();
  1953. int nSelectedRow = 0;
  1954. if ( m_DataItems.IsValidIndex( m_LastItemSelected ) )
  1955. {
  1956. nSelectedRow = m_VisibleItems.Find( m_LastItemSelected );
  1957. }
  1958. int nSelectedColumn = m_iSelectedColumn;
  1959. switch(code)
  1960. {
  1961. case KEY_XBUTTON_UP:
  1962. case KEY_XSTICK1_UP:
  1963. case KEY_XSTICK2_UP:
  1964. if(GetItemCount() < 1 || nSelectedRow == nStartItem)
  1965. {
  1966. ClearSelectedItems();
  1967. BaseClass::OnKeyCodePressed(code);
  1968. return;
  1969. }
  1970. else
  1971. {
  1972. nSelectedRow -= 1;
  1973. }
  1974. break;
  1975. case KEY_XBUTTON_DOWN:
  1976. case KEY_XSTICK1_DOWN:
  1977. case KEY_XSTICK2_DOWN:
  1978. {
  1979. int itemId = GetSelectedItem(0);
  1980. if(itemId != -1 && GetItemCurrentRow(itemId) == (nTotalRows - 1))
  1981. {
  1982. ClearSelectedItems();
  1983. BaseClass::OnKeyCodePressed(code);
  1984. return;
  1985. }
  1986. else
  1987. {
  1988. nSelectedRow += 1;
  1989. }
  1990. }
  1991. break;
  1992. case KEY_XBUTTON_LEFT:
  1993. case KEY_XSTICK1_LEFT:
  1994. case KEY_XSTICK2_LEFT:
  1995. if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) )
  1996. {
  1997. nSelectedColumn--;
  1998. if (nSelectedColumn < 0)
  1999. {
  2000. nSelectedColumn = 0;
  2001. }
  2002. break;
  2003. }
  2004. break;
  2005. case KEY_XBUTTON_RIGHT:
  2006. case KEY_XSTICK1_RIGHT:
  2007. case KEY_XSTICK2_RIGHT:
  2008. if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) )
  2009. {
  2010. nSelectedColumn++;
  2011. if (nSelectedColumn >= nTotalColumns)
  2012. {
  2013. nSelectedColumn = nTotalColumns - 1;
  2014. }
  2015. break;
  2016. }
  2017. break;
  2018. case KEY_XBUTTON_A:
  2019. PostActionSignal( new KeyValues("ListPanelItemChosen", "itemID", m_SelectedItems[0] ));
  2020. break;
  2021. default:
  2022. BaseClass::OnKeyCodePressed(code);
  2023. break;
  2024. }
  2025. // make sure newly selected item is a valid range
  2026. nSelectedRow = clamp(nSelectedRow, 0, nTotalRows - 1);
  2027. int row = m_VisibleItems[ nSelectedRow ];
  2028. // This will select the cell if in single select mode, or the row in multiselect mode
  2029. if ( ( row != m_LastItemSelected ) || ( nSelectedColumn != m_iSelectedColumn ) || ( m_SelectedItems.Count() > 1 ) )
  2030. {
  2031. SetSelectedCell( row, nSelectedColumn );
  2032. }
  2033. // move the newly selected item to within the visible range
  2034. if ( nRowsPerPage < nTotalRows )
  2035. {
  2036. int nStartItem = m_vbar->GetValue();
  2037. if ( nSelectedRow < nStartItem )
  2038. {
  2039. // move the list back to match
  2040. m_vbar->SetValue( nSelectedRow );
  2041. }
  2042. else if ( nSelectedRow >= nStartItem + nRowsPerPage )
  2043. {
  2044. // move list forward to match
  2045. m_vbar->SetValue( nSelectedRow - nRowsPerPage + 1);
  2046. }
  2047. }
  2048. // redraw
  2049. InvalidateLayout();
  2050. }
  2051. #endif
  2052. //-----------------------------------------------------------------------------
  2053. // Purpose:
  2054. //-----------------------------------------------------------------------------
  2055. void ListPanel::OnKeyCodeTyped(KeyCode code)
  2056. {
  2057. if (m_hEditModePanel.Get())
  2058. {
  2059. // ignore arrow keys in edit mode
  2060. // forward right up to parent so that tab focus change doesn't occur
  2061. CallParentFunction(new KeyValues("KeyCodeTyped", "code", code));
  2062. return;
  2063. }
  2064. int nTotalRows = m_VisibleItems.Count();
  2065. int nTotalColumns = m_CurrentColumns.Count();
  2066. if ( nTotalRows == 0 )
  2067. return;
  2068. // calculate info for adjusting scrolling
  2069. int nStartItem = GetStartItem();
  2070. int nRowsPerPage = (int)GetRowsPerPage();
  2071. int nSelectedRow = 0;
  2072. if ( m_DataItems.IsValidIndex( m_LastItemSelected ) )
  2073. {
  2074. nSelectedRow = m_VisibleItems.Find( m_LastItemSelected );
  2075. }
  2076. int nSelectedColumn = m_iSelectedColumn;
  2077. switch (code)
  2078. {
  2079. case KEY_HOME:
  2080. nSelectedRow = 0;
  2081. break;
  2082. case KEY_END:
  2083. nSelectedRow = nTotalRows - 1;
  2084. break;
  2085. case KEY_PAGEUP:
  2086. if (nSelectedRow <= nStartItem)
  2087. {
  2088. // move up a page
  2089. nSelectedRow -= (nRowsPerPage - 1);
  2090. }
  2091. else
  2092. {
  2093. // move to the top of the current page
  2094. nSelectedRow = nStartItem;
  2095. }
  2096. break;
  2097. case KEY_PAGEDOWN:
  2098. if (nSelectedRow >= (nStartItem + nRowsPerPage-1))
  2099. {
  2100. // move down a page
  2101. nSelectedRow += (nRowsPerPage - 1);
  2102. }
  2103. else
  2104. {
  2105. // move to the bottom of the current page
  2106. nSelectedRow = nStartItem + (nRowsPerPage - 1);
  2107. }
  2108. break;
  2109. case KEY_UP:
  2110. nSelectedRow -= 1;
  2111. break;
  2112. case KEY_DOWN:
  2113. nSelectedRow += 1;
  2114. break;
  2115. case KEY_LEFT:
  2116. if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) )
  2117. {
  2118. nSelectedColumn--;
  2119. if (nSelectedColumn < 0)
  2120. {
  2121. nSelectedColumn = 0;
  2122. }
  2123. break;
  2124. }
  2125. // fall through
  2126. case KEY_RIGHT:
  2127. if (m_bCanSelectIndividualCells && (GetSelectedItemsCount() == 1) && (nSelectedColumn >= 0) )
  2128. {
  2129. nSelectedColumn++;
  2130. if (nSelectedColumn >= nTotalColumns)
  2131. {
  2132. nSelectedColumn = nTotalColumns - 1;
  2133. }
  2134. break;
  2135. }
  2136. // fall through
  2137. default:
  2138. // chain back
  2139. BaseClass::OnKeyCodeTyped(code);
  2140. return;
  2141. };
  2142. // make sure newly selected item is a valid range
  2143. nSelectedRow = clamp(nSelectedRow, 0, nTotalRows - 1);
  2144. int row = m_VisibleItems[ nSelectedRow ];
  2145. // This will select the cell if in single select mode, or the row in multiselect mode
  2146. if ( ( row != m_LastItemSelected ) || ( nSelectedColumn != m_iSelectedColumn ) || ( m_SelectedItems.Count() > 1 ) )
  2147. {
  2148. SetSelectedCell( row, nSelectedColumn );
  2149. }
  2150. // move the newly selected item to within the visible range
  2151. if ( nRowsPerPage < nTotalRows )
  2152. {
  2153. int nStartItem = m_vbar->GetValue();
  2154. if ( nSelectedRow < nStartItem )
  2155. {
  2156. // move the list back to match
  2157. m_vbar->SetValue( nSelectedRow );
  2158. }
  2159. else if ( nSelectedRow >= nStartItem + nRowsPerPage )
  2160. {
  2161. // move list forward to match
  2162. m_vbar->SetValue( nSelectedRow - nRowsPerPage + 1);
  2163. }
  2164. }
  2165. // redraw
  2166. InvalidateLayout();
  2167. }
  2168. //-----------------------------------------------------------------------------
  2169. // Purpose:
  2170. //-----------------------------------------------------------------------------
  2171. bool ListPanel::GetCellBounds( int row, int col, int& x, int& y, int& wide, int& tall )
  2172. {
  2173. if ( col < 0 || col >= m_CurrentColumns.Count() )
  2174. return false;
  2175. if ( row < 0 || row >= m_VisibleItems.Count() )
  2176. return false;
  2177. // Is row on screen?
  2178. int startitem = GetStartItem();
  2179. if ( row < startitem || row >= ( startitem + GetRowsPerPage() ) )
  2180. return false;
  2181. y = m_iTableStartY;
  2182. y += ( row - startitem ) * m_iRowHeight;
  2183. tall = m_iRowHeight;
  2184. // Compute column cell
  2185. x = m_iTableStartX;
  2186. // walk columns
  2187. int c = 0;
  2188. while ( c < col)
  2189. {
  2190. x += m_ColumnsData[m_CurrentColumns[c]].m_pHeader->GetWide();
  2191. c++;
  2192. }
  2193. wide = m_ColumnsData[m_CurrentColumns[c]].m_pHeader->GetWide();
  2194. return true;
  2195. }
  2196. //-----------------------------------------------------------------------------
  2197. // Purpose: returns true if any found, row and column are filled out
  2198. //-----------------------------------------------------------------------------
  2199. bool ListPanel::GetCellAtPos(int x, int y, int &row, int &col)
  2200. {
  2201. // convert to local
  2202. ScreenToLocal(x, y);
  2203. // move to Start of table
  2204. x -= m_iTableStartX;
  2205. y -= m_iTableStartY;
  2206. int startitem = GetStartItem();
  2207. // make sure it's still in valid area
  2208. if ( x >= 0 && y >= 0 )
  2209. {
  2210. // walk the rows (for when row height is independant each row)
  2211. // NOTE: if we do height independent rows, we will need to change GetCellBounds as well
  2212. for ( row = startitem ; row < m_VisibleItems.Count() ; row++ )
  2213. {
  2214. if ( y < ( ( ( row - startitem ) + 1 ) * m_iRowHeight ) )
  2215. break;
  2216. }
  2217. // walk columns
  2218. int startx = 0;
  2219. for ( col = 0 ; col < m_CurrentColumns.Count() ; col++ )
  2220. {
  2221. startx += m_ColumnsData[m_CurrentColumns[col]].m_pHeader->GetWide();
  2222. if ( x < startx )
  2223. break;
  2224. }
  2225. // make sure we're not out of range
  2226. if ( ! ( row == m_VisibleItems.Count() || col == m_CurrentColumns.Count() ) )
  2227. {
  2228. return true;
  2229. }
  2230. }
  2231. // out-of-bounds
  2232. row = col = -1;
  2233. return false;
  2234. }
  2235. //-----------------------------------------------------------------------------
  2236. // Purpose:
  2237. //-----------------------------------------------------------------------------
  2238. void ListPanel::ApplySchemeSettings(IScheme *pScheme)
  2239. {
  2240. // force label to apply scheme settings now so we can override it
  2241. m_pLabel->InvalidateLayout(true);
  2242. BaseClass::ApplySchemeSettings(pScheme);
  2243. SetBgColor(GetSchemeColor("ListPanel.BgColor", pScheme));
  2244. SetBorder(pScheme->GetBorder("ButtonDepressedBorder"));
  2245. m_pLabel->SetBgColor(GetSchemeColor("ListPanel.BgColor", pScheme));
  2246. m_LabelFgColor = GetSchemeColor("ListPanel.TextColor", pScheme);
  2247. m_DisabledColor = GetSchemeColor("ListPanel.DisabledTextColor", m_LabelFgColor, pScheme);
  2248. m_SelectionFgColor = GetSchemeColor("ListPanel.SelectedTextColor", m_LabelFgColor, pScheme);
  2249. m_DisabledSelectionFgColor = GetSchemeColor("ListPanel.DisabledSelectedTextColor", m_LabelFgColor, pScheme);
  2250. m_pEmptyListText->SetColor(GetSchemeColor("ListPanel.EmptyListInfoTextColor", pScheme));
  2251. SetFont( pScheme->GetFont("Default", IsProportional() ) );
  2252. m_pEmptyListText->SetFont( pScheme->GetFont( "Default", IsProportional() ) );
  2253. }
  2254. //-----------------------------------------------------------------------------
  2255. // Purpose:
  2256. //-----------------------------------------------------------------------------
  2257. void ListPanel::SetSortFunc(int col, SortFunc *func)
  2258. {
  2259. Assert(col < m_CurrentColumns.Count());
  2260. unsigned char dataColumnIndex = m_CurrentColumns[col];
  2261. if ( !m_ColumnsData[dataColumnIndex].m_bTypeIsText && func != NULL)
  2262. {
  2263. m_ColumnsData[dataColumnIndex].m_pHeader->SetMouseClickEnabled(MOUSE_LEFT, 1);
  2264. }
  2265. m_ColumnsData[dataColumnIndex].m_pSortFunc = func;
  2266. // resort this column according to new sort func
  2267. ResortColumnRBTree(col);
  2268. }
  2269. //-----------------------------------------------------------------------------
  2270. // Purpose:
  2271. //-----------------------------------------------------------------------------
  2272. void ListPanel::SetSortColumn(int column)
  2273. {
  2274. m_iSortColumn = column;
  2275. }
  2276. int ListPanel::GetSortColumn() const
  2277. {
  2278. return m_iSortColumn;
  2279. }
  2280. void ListPanel::SetSortColumnEx( int iPrimarySortColumn, int iSecondarySortColumn, bool bSortAscending )
  2281. {
  2282. m_iSortColumn = iPrimarySortColumn;
  2283. m_iSortColumnSecondary = iSecondarySortColumn;
  2284. m_bSortAscending = bSortAscending;
  2285. }
  2286. void ListPanel::GetSortColumnEx( int &iPrimarySortColumn, int &iSecondarySortColumn, bool &bSortAscending ) const
  2287. {
  2288. iPrimarySortColumn = m_iSortColumn;
  2289. iSecondarySortColumn = m_iSortColumnSecondary;
  2290. bSortAscending = m_bSortAscending;
  2291. }
  2292. //-----------------------------------------------------------------------------
  2293. // Purpose:
  2294. //-----------------------------------------------------------------------------
  2295. void ListPanel::SortList( void )
  2296. {
  2297. m_bNeedsSort = false;
  2298. if ( m_VisibleItems.Count() <= 1 )
  2299. {
  2300. return;
  2301. }
  2302. // check if the last selected item is on the screen - if so, we should try to maintain it on screen
  2303. int startItem = GetStartItem();
  2304. int rowsperpage = (int) GetRowsPerPage();
  2305. int screenPosition = -1;
  2306. if ( m_LastItemSelected != -1 && m_SelectedItems.Count() > 0 )
  2307. {
  2308. int selectedItemRow = m_VisibleItems.Find(m_LastItemSelected);
  2309. if ( selectedItemRow >= startItem && selectedItemRow <= ( startItem + rowsperpage ) )
  2310. {
  2311. screenPosition = selectedItemRow - startItem;
  2312. }
  2313. }
  2314. // get the required sorting functions
  2315. s_pCurrentSortingListPanel = this;
  2316. // setup globals for use in qsort
  2317. s_pSortFunc = FastSortFunc;
  2318. s_bSortAscending = m_bSortAscending;
  2319. s_pSortFuncSecondary = FastSortFunc;
  2320. s_bSortAscendingSecondary = m_bSortAscendingSecondary;
  2321. // walk the tree and set up the current indices
  2322. if (m_CurrentColumns.IsValidIndex(m_iSortColumn))
  2323. {
  2324. IndexRBTree_t &rbtree = m_ColumnsData[m_CurrentColumns[m_iSortColumn]].m_SortedTree;
  2325. unsigned int index = rbtree.FirstInorder();
  2326. unsigned int lastIndex = rbtree.LastInorder();
  2327. int prevDuplicateIndex = 0;
  2328. int sortValue = 1;
  2329. while (1)
  2330. {
  2331. FastSortListPanelItem *dataItem = (FastSortListPanelItem*) rbtree[index].dataItem;
  2332. if (dataItem->visible)
  2333. {
  2334. // only increment the sort value if we're a different token from the previous
  2335. if (!prevDuplicateIndex || prevDuplicateIndex != rbtree[index].duplicateIndex)
  2336. {
  2337. sortValue++;
  2338. }
  2339. dataItem->primarySortIndexValue = sortValue;
  2340. prevDuplicateIndex = rbtree[index].duplicateIndex;
  2341. }
  2342. if (index == lastIndex)
  2343. break;
  2344. index = rbtree.NextInorder(index);
  2345. }
  2346. }
  2347. // setup secondary indices
  2348. if (m_CurrentColumns.IsValidIndex(m_iSortColumnSecondary))
  2349. {
  2350. IndexRBTree_t &rbtree = m_ColumnsData[m_CurrentColumns[m_iSortColumnSecondary]].m_SortedTree;
  2351. unsigned int index = rbtree.FirstInorder();
  2352. unsigned int lastIndex = rbtree.LastInorder();
  2353. int sortValue = 1;
  2354. int prevDuplicateIndex = 0;
  2355. while (1)
  2356. {
  2357. FastSortListPanelItem *dataItem = (FastSortListPanelItem*) rbtree[index].dataItem;
  2358. if (dataItem->visible)
  2359. {
  2360. // only increment the sort value if we're a different token from the previous
  2361. if (!prevDuplicateIndex || prevDuplicateIndex != rbtree[index].duplicateIndex)
  2362. {
  2363. sortValue++;
  2364. }
  2365. dataItem->secondarySortIndexValue = sortValue;
  2366. prevDuplicateIndex = rbtree[index].duplicateIndex;
  2367. }
  2368. if (index == lastIndex)
  2369. break;
  2370. index = rbtree.NextInorder(index);
  2371. }
  2372. }
  2373. // quick sort the list
  2374. qsort(m_VisibleItems.Base(), (size_t) m_VisibleItems.Count(), (size_t) sizeof(int), AscendingSortFunc);
  2375. if ( screenPosition != -1 )
  2376. {
  2377. int selectedItemRow = m_VisibleItems.Find(m_LastItemSelected);
  2378. // if we can put the last selected item in exactly the same spot, put it there, otherwise
  2379. // we need to be at the top of the list
  2380. if (selectedItemRow > screenPosition)
  2381. {
  2382. m_vbar->SetValue(selectedItemRow - screenPosition);
  2383. }
  2384. else
  2385. {
  2386. m_vbar->SetValue(0);
  2387. }
  2388. }
  2389. InvalidateLayout();
  2390. Repaint();
  2391. }
  2392. //-----------------------------------------------------------------------------
  2393. // Purpose:
  2394. //-----------------------------------------------------------------------------
  2395. void ListPanel::SetFont(HFont font)
  2396. {
  2397. Assert( font );
  2398. if ( !font )
  2399. return;
  2400. m_pTextImage->SetFont(font);
  2401. m_iRowHeight = surface()->GetFontTall(font) + 2;
  2402. }
  2403. //-----------------------------------------------------------------------------
  2404. // Purpose:
  2405. //-----------------------------------------------------------------------------
  2406. void ListPanel::OnSliderMoved()
  2407. {
  2408. InvalidateLayout();
  2409. Repaint();
  2410. }
  2411. //-----------------------------------------------------------------------------
  2412. // Purpose:
  2413. // Input : deltax - deltas from current position
  2414. //-----------------------------------------------------------------------------
  2415. void ListPanel::OnColumnResized(int col, int delta)
  2416. {
  2417. m_iColumnDraggerMoved = col;
  2418. column_t& column = m_ColumnsData[m_CurrentColumns[col]];
  2419. Panel *header = column.m_pHeader;
  2420. int wide, tall;
  2421. header->GetSize(wide, tall);
  2422. wide += delta;
  2423. // enforce minimum sizes for the header
  2424. if ( wide < column.m_iMinWidth )
  2425. {
  2426. wide = column.m_iMinWidth;
  2427. }
  2428. // enforce maximum sizes for the header
  2429. if ( wide > column.m_iMaxWidth )
  2430. {
  2431. wide = column.m_iMaxWidth;
  2432. }
  2433. // make sure we have enough space for the columns to our right
  2434. int panelWide, panelTall;
  2435. GetSize( panelWide, panelTall );
  2436. int x, y;
  2437. header->GetPos(x, y);
  2438. int restColumnsMinWidth = 0;
  2439. int i;
  2440. for ( i = col+1 ; i < m_CurrentColumns.Count() ; i++ )
  2441. {
  2442. column_t& nextCol = m_ColumnsData[m_CurrentColumns[i]];
  2443. restColumnsMinWidth += nextCol.m_iMinWidth;
  2444. }
  2445. panelWide -= ( x + restColumnsMinWidth + m_vbar->GetWide() + WINDOW_BORDER_WIDTH );
  2446. if ( wide > panelWide )
  2447. {
  2448. wide = panelWide;
  2449. }
  2450. header->SetSize(wide, tall);
  2451. // the adjacent header will be moved automatically in PerformLayout()
  2452. header->InvalidateLayout();
  2453. InvalidateLayout();
  2454. Repaint();
  2455. }
  2456. //-----------------------------------------------------------------------------
  2457. // Purpose: sets which column we should sort with
  2458. //-----------------------------------------------------------------------------
  2459. void ListPanel::OnSetSortColumn(int column)
  2460. {
  2461. // if it's the primary column already, flip the sort direction
  2462. if (m_iSortColumn == column)
  2463. {
  2464. m_bSortAscending = !m_bSortAscending;
  2465. }
  2466. else
  2467. {
  2468. // switching sort columns, keep the old one as the secondary sort
  2469. m_iSortColumnSecondary = m_iSortColumn;
  2470. m_bSortAscendingSecondary = m_bSortAscending;
  2471. }
  2472. SetSortColumn(column);
  2473. SortList();
  2474. }
  2475. //-----------------------------------------------------------------------------
  2476. // Purpose: sets whether the item is visible or not
  2477. //-----------------------------------------------------------------------------
  2478. void ListPanel::SetItemVisible(int itemID, bool state)
  2479. {
  2480. if ( !m_DataItems.IsValidIndex(itemID) )
  2481. return;
  2482. FastSortListPanelItem *data = (FastSortListPanelItem*) m_DataItems[itemID];
  2483. if (data->visible == state)
  2484. return;
  2485. m_bNeedsSort = true;
  2486. data->visible = state;
  2487. if (data->visible)
  2488. {
  2489. // add back to end of list
  2490. m_VisibleItems.AddToTail(itemID);
  2491. }
  2492. else
  2493. {
  2494. // remove from selection if it is there.
  2495. if (m_SelectedItems.HasElement(itemID))
  2496. {
  2497. m_SelectedItems.FindAndRemove(itemID);
  2498. PostActionSignal( new KeyValues("ItemDeselected") );
  2499. }
  2500. // remove from data
  2501. m_VisibleItems.FindAndRemove(itemID);
  2502. InvalidateLayout();
  2503. }
  2504. }
  2505. void ListPanel::SetAllVisible( bool state )
  2506. {
  2507. FOR_EACH_LL( m_DataItems, i)
  2508. {
  2509. SetItemVisible( i, state );
  2510. }
  2511. }
  2512. //-----------------------------------------------------------------------------
  2513. // Is the item visible?
  2514. //-----------------------------------------------------------------------------
  2515. bool ListPanel::IsItemVisible( int itemID )
  2516. {
  2517. if ( !m_DataItems.IsValidIndex(itemID) )
  2518. return false;
  2519. FastSortListPanelItem *data = (FastSortListPanelItem*) m_DataItems[itemID];
  2520. return data->visible;
  2521. }
  2522. //-----------------------------------------------------------------------------
  2523. // Purpose: sets whether the item is disabled or not (effects item color)
  2524. //-----------------------------------------------------------------------------
  2525. void ListPanel::SetItemDisabled(int itemID, bool state)
  2526. {
  2527. if ( !m_DataItems.IsValidIndex(itemID) )
  2528. return;
  2529. m_DataItems[itemID]->kv->SetInt( "disabled", state );
  2530. }
  2531. //-----------------------------------------------------------------------------
  2532. // Purpose: Calculate number of rows per page
  2533. //-----------------------------------------------------------------------------
  2534. float ListPanel::GetRowsPerPage()
  2535. {
  2536. float rowsperpage = (float)( GetTall() - m_iHeaderHeight ) / (float)m_iRowHeight;
  2537. return rowsperpage;
  2538. }
  2539. //-----------------------------------------------------------------------------
  2540. // Purpose: Calculate the item we should Start on
  2541. //-----------------------------------------------------------------------------
  2542. int ListPanel::GetStartItem()
  2543. {
  2544. // if rowsperpage < total number of rows
  2545. if ( GetRowsPerPage() < (float) m_VisibleItems.Count() )
  2546. {
  2547. return m_vbar->GetValue();
  2548. }
  2549. return 0; // otherwise Start at top
  2550. }
  2551. //-----------------------------------------------------------------------------
  2552. // Purpose: whether or not to select specific cells (off by default)
  2553. //-----------------------------------------------------------------------------
  2554. void ListPanel::SetSelectIndividualCells(bool state)
  2555. {
  2556. m_bCanSelectIndividualCells = state;
  2557. }
  2558. //-----------------------------------------------------------------------------
  2559. // whether or not multiple cells/rows can be selected
  2560. //-----------------------------------------------------------------------------
  2561. void ListPanel::SetMultiselectEnabled( bool bState )
  2562. {
  2563. m_bMultiselectEnabled = bState;
  2564. }
  2565. bool ListPanel::IsMultiselectEnabled() const
  2566. {
  2567. return m_bMultiselectEnabled;
  2568. }
  2569. //-----------------------------------------------------------------------------
  2570. // Purpose: Sets the text which is displayed when the list is empty
  2571. //-----------------------------------------------------------------------------
  2572. void ListPanel::SetEmptyListText(const char *text)
  2573. {
  2574. m_pEmptyListText->SetText(text);
  2575. Repaint();
  2576. }
  2577. //-----------------------------------------------------------------------------
  2578. // Purpose: Sets the text which is displayed when the list is empty
  2579. //-----------------------------------------------------------------------------
  2580. void ListPanel::SetEmptyListText(const wchar_t *text)
  2581. {
  2582. m_pEmptyListText->SetText(text);
  2583. Repaint();
  2584. }
  2585. //-----------------------------------------------------------------------------
  2586. // Purpose: opens the content menu
  2587. //-----------------------------------------------------------------------------
  2588. void ListPanel::OpenColumnChoiceMenu()
  2589. {
  2590. if (!m_bAllowUserAddDeleteColumns)
  2591. return;
  2592. Menu *menu = new Menu(this, "ContextMenu");
  2593. int x, y;
  2594. input()->GetCursorPos(x, y);
  2595. menu->SetPos(x, y);
  2596. // add all the column choices to the menu
  2597. for ( int i = 0 ; i < m_CurrentColumns.Count() ; i++ )
  2598. {
  2599. column_t &column = m_ColumnsData[m_CurrentColumns[i]];
  2600. char name[128];
  2601. column.m_pHeader->GetText(name, sizeof(name));
  2602. int itemID = menu->AddCheckableMenuItem(name, new KeyValues("ToggleColumnVisible", "col", m_CurrentColumns[i]), this);
  2603. menu->SetMenuItemChecked(itemID, !column.m_bHidden);
  2604. if (column.m_bUnhidable)
  2605. {
  2606. menu->SetItemEnabled(itemID, false);
  2607. }
  2608. }
  2609. menu->SetVisible(true);
  2610. }
  2611. //-----------------------------------------------------------------------------
  2612. // Purpose: Resizes a column
  2613. //-----------------------------------------------------------------------------
  2614. void ListPanel::ResizeColumnToContents(int column)
  2615. {
  2616. // iterate all the items in the column, getting the size of each
  2617. column_t &col = m_ColumnsData[m_CurrentColumns[column]];
  2618. if (!col.m_bTypeIsText)
  2619. return;
  2620. // start with the size of the column text
  2621. int wide = 0, minRequiredWidth = 0, tall = 0;
  2622. col.m_pHeader->GetContentSize( minRequiredWidth, tall );
  2623. // iterate every item
  2624. for (int i = 0; i < m_VisibleItems.Count(); i++)
  2625. {
  2626. if (!m_VisibleItems.IsValidIndex(i))
  2627. continue;
  2628. // get the cell
  2629. int itemID = m_VisibleItems[i];
  2630. // get the text
  2631. wchar_t tempText[ 256 ];
  2632. GetCellText( itemID, column, tempText, 256 );
  2633. m_pTextImage->SetText(tempText);
  2634. m_pTextImage->GetContentSize(wide, tall);
  2635. if ( wide > minRequiredWidth )
  2636. {
  2637. minRequiredWidth = wide;
  2638. }
  2639. }
  2640. // Introduce a slight buffer between columns
  2641. minRequiredWidth += 4;
  2642. // call the resize
  2643. col.m_pHeader->GetSize(wide, tall);
  2644. OnColumnResized(column, minRequiredWidth - wide);
  2645. }
  2646. //-----------------------------------------------------------------------------
  2647. // Purpose: Changes the visibilty of a column
  2648. //-----------------------------------------------------------------------------
  2649. void ListPanel::OnToggleColumnVisible(int col)
  2650. {
  2651. if (!m_CurrentColumns.IsValidIndex(col))
  2652. return;
  2653. // toggle the state of the column
  2654. column_t &column = m_ColumnsData[m_CurrentColumns[col]];
  2655. SetColumnVisible(col, column.m_bHidden);
  2656. }
  2657. //-----------------------------------------------------------------------------
  2658. // Purpose: sets user settings
  2659. //-----------------------------------------------------------------------------
  2660. void ListPanel::ApplyUserConfigSettings(KeyValues *userConfig)
  2661. {
  2662. // We save/restore m_lastBarWidth because all of the column widths are saved relative to that size.
  2663. // If we don't save it, you can run into this case:
  2664. // - Window width is 500, load sizes setup relative to a 1000-width window
  2665. // - Set window size to 1000
  2666. // - In PerformLayout, it thinks the window has grown by 500 (since m_lastBarWidth is 500 and new window width is 1000)
  2667. // so it pushes out any COLUMN_RESIZEWITHWINDOW columns to their max extent and shrinks everything else to its min extent.
  2668. m_lastBarWidth = userConfig->GetInt( "lastBarWidth", 0 );
  2669. // read which columns are hidden
  2670. for ( int i = 0; i < m_CurrentColumns.Count(); i++ )
  2671. {
  2672. char name[64];
  2673. _snprintf(name, sizeof(name), "%d_hidden", i);
  2674. int hidden = userConfig->GetInt(name, -1);
  2675. if (hidden == 0)
  2676. {
  2677. SetColumnVisible(i, true);
  2678. }
  2679. else if (hidden == 1)
  2680. {
  2681. SetColumnVisible(i, false);
  2682. }
  2683. _snprintf(name, sizeof(name), "%d_width", i);
  2684. int nWidth = userConfig->GetInt( name, -1 );
  2685. if ( nWidth >= 0 )
  2686. {
  2687. column_t &column = m_ColumnsData[m_CurrentColumns[i]];
  2688. column.m_pHeader->SetWide( nWidth );
  2689. }
  2690. }
  2691. }
  2692. //-----------------------------------------------------------------------------
  2693. // Purpose: returns user config settings for this control
  2694. //-----------------------------------------------------------------------------
  2695. void ListPanel::GetUserConfigSettings(KeyValues *userConfig)
  2696. {
  2697. userConfig->SetInt( "lastBarWidth", m_lastBarWidth );
  2698. // save which columns are hidden
  2699. for ( int i = 0 ; i < m_CurrentColumns.Count() ; i++ )
  2700. {
  2701. column_t &column = m_ColumnsData[m_CurrentColumns[i]];
  2702. char name[64];
  2703. _snprintf(name, sizeof(name), "%d_hidden", i);
  2704. userConfig->SetBool(name, column.m_bHidden );
  2705. _snprintf(name, sizeof(name), "%d_width", i);
  2706. userConfig->SetInt( name, column.m_pHeader->GetWide() );
  2707. }
  2708. }
  2709. //-----------------------------------------------------------------------------
  2710. // Purpose: optimization, return true if this control has any user config settings
  2711. //-----------------------------------------------------------------------------
  2712. bool ListPanel::HasUserConfigSettings()
  2713. {
  2714. return true;
  2715. }
  2716. //-----------------------------------------------------------------------------
  2717. // Purpose: data accessor
  2718. //-----------------------------------------------------------------------------
  2719. void ListPanel::SetAllowUserModificationOfColumns(bool allowed)
  2720. {
  2721. m_bAllowUserAddDeleteColumns = allowed;
  2722. }
  2723. void ListPanel::SetIgnoreDoubleClick( bool state )
  2724. {
  2725. m_bIgnoreDoubleClick = state;
  2726. }
  2727. //-----------------------------------------------------------------------------
  2728. // Purpose: set up a field for editing
  2729. //-----------------------------------------------------------------------------
  2730. void ListPanel::EnterEditMode(int itemID, int column, vgui::Panel *editPanel)
  2731. {
  2732. m_hEditModePanel = editPanel;
  2733. m_iEditModeItemID = itemID;
  2734. m_iEditModeColumn = column;
  2735. editPanel->SetParent(this);
  2736. editPanel->SetVisible(true);
  2737. editPanel->RequestFocus();
  2738. editPanel->MoveToFront();
  2739. InvalidateLayout();
  2740. }
  2741. //-----------------------------------------------------------------------------
  2742. // Purpose: leaves editing mode
  2743. //-----------------------------------------------------------------------------
  2744. void ListPanel::LeaveEditMode()
  2745. {
  2746. if (m_hEditModePanel.Get())
  2747. {
  2748. m_hEditModePanel->SetVisible(false);
  2749. m_hEditModePanel->SetParent((Panel *)NULL);
  2750. m_hEditModePanel = NULL;
  2751. }
  2752. }
  2753. //-----------------------------------------------------------------------------
  2754. // Purpose: returns true if we are currently in inline editing mode
  2755. //-----------------------------------------------------------------------------
  2756. bool ListPanel::IsInEditMode()
  2757. {
  2758. return (m_hEditModePanel.Get() != NULL);
  2759. }
  2760. //-----------------------------------------------------------------------------
  2761. // Purpose:
  2762. //-----------------------------------------------------------------------------
  2763. #ifdef _GAMECONSOLE
  2764. void ListPanel::NavigateTo()
  2765. {
  2766. BaseClass::NavigateTo();
  2767. // attempt to select the first item in the list when we get focus
  2768. if(GetItemCount())
  2769. {
  2770. SetSingleSelectedItem(FirstItem());
  2771. }
  2772. else // if we have no items, change focus
  2773. {
  2774. if(!NavigateDown())
  2775. {
  2776. NavigateUp();
  2777. }
  2778. }
  2779. }
  2780. #endif