Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3287 lines
88 KiB

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