Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2666 lines
75 KiB

  1. //========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "vgui_controls/pch_vgui_controls.h"
  8. // memdbgon must be the last include file in a .cpp file
  9. #include "tier0/memdbgon.h"
  10. #define MENU_SEPARATOR_HEIGHT 3
  11. #if defined(_PS3) || defined(POSIX)
  12. //!!BUG!! "wcsnicmp unsupported on PS3"
  13. #ifdef wcsicmp
  14. #undef wcsicmp
  15. #endif
  16. #define wcsnicmp wcsncmp
  17. #endif
  18. using namespace vgui;
  19. //-----------------------------------------------------------------------------
  20. // Purpose: divider line in a menu
  21. //-----------------------------------------------------------------------------
  22. class vgui::MenuSeparator : public Panel
  23. {
  24. public:
  25. DECLARE_CLASS_SIMPLE( MenuSeparator, Panel );
  26. MenuSeparator( Panel *parent, char const *panelName ) :
  27. BaseClass( parent, panelName )
  28. {
  29. SetPaintEnabled( true );
  30. SetPaintBackgroundEnabled( true );
  31. SetPaintBorderEnabled( false );
  32. }
  33. virtual void Paint()
  34. {
  35. int w, h;
  36. GetSize( w, h );
  37. surface()->DrawSetColor( GetFgColor() );
  38. surface()->DrawFilledRect( 4, 1, w-1, 2 );
  39. }
  40. virtual void ApplySchemeSettings( IScheme *pScheme )
  41. {
  42. BaseClass::ApplySchemeSettings( pScheme );
  43. SetFgColor( pScheme->GetColor( "Menu.SeparatorColor", Color( 142, 142, 142, 255 ) ) );
  44. SetBgColor( pScheme->GetColor( "Menu.BgColor", Color( 0, 0, 0, 255 ) ) );
  45. }
  46. };
  47. DECLARE_BUILD_FACTORY( Menu );
  48. //-----------------------------------------------------------------------------
  49. // Purpose: Constructor
  50. //-----------------------------------------------------------------------------
  51. Menu::Menu(Panel *parent, const char *panelName) : Panel(parent, panelName)
  52. {
  53. m_Alignment = Label::a_west;
  54. m_iFixedWidth = 0;
  55. m_iMinimumWidth = 0;
  56. m_iNumVisibleLines = -1; // No limit
  57. m_iCurrentlySelectedItemID = m_MenuItems.InvalidIndex();
  58. m_pScroller = new ScrollBar(this, "MenuScrollBar", true);
  59. m_pScroller->SetVisible(false);
  60. m_pScroller->AddActionSignalTarget(this);
  61. _sizedForScrollBar = false;
  62. SetZPos(1);
  63. SetVisible(false);
  64. MakePopup(false);
  65. SetParent(parent);
  66. _recalculateWidth = true;
  67. m_iInputMode = MOUSE;
  68. m_iCheckImageWidth = 0;
  69. m_iActivatedItem = 0;
  70. m_bUseFallbackFont = false;
  71. m_hFallbackItemFont = INVALID_FONT;
  72. if (IsProportional())
  73. {
  74. m_iMenuItemHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), DEFAULT_MENU_ITEM_HEIGHT );
  75. }
  76. else
  77. {
  78. m_iMenuItemHeight = DEFAULT_MENU_ITEM_HEIGHT;
  79. }
  80. m_hItemFont = INVALID_FONT;
  81. m_eTypeAheadMode = COMPAT_MODE;
  82. m_szTypeAheadBuf[0] = '\0';
  83. m_iNumTypeAheadChars = 0;
  84. m_fLastTypeAheadTime = 0.0f;
  85. }
  86. //-----------------------------------------------------------------------------
  87. // Purpose: Destructor
  88. //-----------------------------------------------------------------------------
  89. Menu::~Menu()
  90. {
  91. delete m_pScroller;
  92. }
  93. //-----------------------------------------------------------------------------
  94. // Purpose: Remove all menu items from the menu.
  95. //-----------------------------------------------------------------------------
  96. void Menu::DeleteAllItems()
  97. {
  98. FOR_EACH_LL( m_MenuItems, i )
  99. {
  100. m_MenuItems[i]->MarkForDeletion();
  101. }
  102. m_MenuItems.RemoveAll();
  103. m_SortedItems.RemoveAll();
  104. m_VisibleSortedItems.RemoveAll();
  105. m_Separators.RemoveAll();
  106. int c = m_SeparatorPanels.Count();
  107. for ( int i = 0 ; i < c; ++i )
  108. {
  109. m_SeparatorPanels[ i ]->MarkForDeletion();
  110. }
  111. m_SeparatorPanels.RemoveAll();
  112. InvalidateLayout();
  113. }
  114. //-----------------------------------------------------------------------------
  115. // Purpose: Add a menu item to the menu.
  116. //-----------------------------------------------------------------------------
  117. int Menu::AddMenuItem( MenuItem *panel )
  118. {
  119. panel->SetParent( this );
  120. MEM_ALLOC_CREDIT();
  121. int itemID = m_MenuItems.AddToTail( panel );
  122. m_SortedItems.AddToTail(itemID);
  123. InvalidateLayout(false);
  124. _recalculateWidth = true;
  125. panel->SetContentAlignment( m_Alignment );
  126. if ( INVALID_FONT != m_hItemFont )
  127. {
  128. panel->SetFont( m_hItemFont );
  129. }
  130. if ( m_bUseFallbackFont && INVALID_FONT != m_hFallbackItemFont )
  131. {
  132. Label *l = panel;
  133. TextImage *ti = l->GetTextImage();
  134. if ( ti )
  135. {
  136. ti->SetUseFallbackFont( m_bUseFallbackFont, m_hFallbackItemFont );
  137. }
  138. }
  139. if ( panel->GetHotKey() )
  140. {
  141. SetTypeAheadMode( HOT_KEY_MODE );
  142. }
  143. return itemID;
  144. }
  145. //-----------------------------------------------------------------------------
  146. // Remove a single item
  147. //-----------------------------------------------------------------------------
  148. void Menu::DeleteItem( int itemID )
  149. {
  150. // FIXME: This doesn't work with separator panels yet
  151. Assert( m_SeparatorPanels.Count() == 0 );
  152. m_MenuItems[itemID]->MarkForDeletion();
  153. m_MenuItems.Remove( itemID );
  154. m_SortedItems.FindAndRemove( itemID );
  155. m_VisibleSortedItems.FindAndRemove( itemID );
  156. InvalidateLayout(false);
  157. _recalculateWidth = true;
  158. }
  159. //-----------------------------------------------------------------------------
  160. // Purpose: Add a menu item to the menu.
  161. // Input : *item - MenuItem
  162. // *command - Command text to be sent when menu item is selected
  163. // *target - Target panel of the command
  164. // *userData - any user data associated with this menu item
  165. // Output: itemID - ID of this item
  166. //-----------------------------------------------------------------------------
  167. int Menu::AddMenuItemCharCommand(MenuItem *item, const char *command, Panel *target, const KeyValues *userData)
  168. {
  169. item->SetCommand(command);
  170. item->AddActionSignalTarget( target );
  171. item->SetUserData(userData);
  172. return AddMenuItem( item );
  173. }
  174. //-----------------------------------------------------------------------------
  175. // Purpose: Add a menu item to the menu.
  176. // Input : *itemName - Name of item
  177. // *itemText - Name of item text that will appear in the manu.
  178. // *message - pointer to the message to send when the item is selected
  179. // *target - Target panel of the command
  180. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  181. // ptr to the menu that opens on selecting the item
  182. // Output: itemID - ID of this item
  183. //-----------------------------------------------------------------------------
  184. int Menu::AddMenuItemKeyValuesCommand( MenuItem *item, KeyValues *message, Panel *target, const KeyValues *userData )
  185. {
  186. item->SetCommand(message);
  187. item->AddActionSignalTarget(target);
  188. item->SetUserData(userData);
  189. return AddMenuItem(item);
  190. }
  191. //-----------------------------------------------------------------------------
  192. // Purpose: Add a menu item to the menu.
  193. // Input : *itemName - Name of item
  194. // *itemText - Name of item text that will appear in the manu.
  195. // *command - Command text to be sent when menu item is selected
  196. // *target - Target panel of the command
  197. // Output: itemID - ID of this item
  198. //-----------------------------------------------------------------------------
  199. int Menu::AddMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData )
  200. {
  201. MenuItem *item = new MenuItem(this, itemName, itemText );
  202. return AddMenuItemCharCommand(item, command, target, userData);
  203. }
  204. int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData )
  205. {
  206. MenuItem *item = new MenuItem(this, itemName, wszItemText );
  207. return AddMenuItemCharCommand(item, command, target, userData);
  208. }
  209. //-----------------------------------------------------------------------------
  210. // Purpose: Add a menu item to the menu.
  211. // Input : *itemText - Name of item text that will appear in the manu.
  212. // This will also be used as the name of the menu item panel.
  213. // *command - Command text to be sent when menu item is selected
  214. // *target - Target panel of the command
  215. // Output: itemID - ID of this item
  216. //-----------------------------------------------------------------------------
  217. int Menu::AddMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData )
  218. {
  219. return AddMenuItem(itemText, itemText, command, target, userData ) ;
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose: Add a menu item to the menu.
  223. // Input : *itemName - Name of item
  224. // *itemText - Name of item text that will appear in the manu.
  225. // *message - pointer to the message to send when the item is selected
  226. // *target - Target panel of the command
  227. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  228. // ptr to the menu that opens on selecting the item
  229. //-----------------------------------------------------------------------------
  230. int Menu::AddMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData )
  231. {
  232. MenuItem *item = new MenuItem(this, itemName, itemText );
  233. return AddMenuItemKeyValuesCommand(item, message, target, userData);
  234. }
  235. int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData )
  236. {
  237. MenuItem *item = new MenuItem(this, itemName, wszItemText );
  238. return AddMenuItemKeyValuesCommand(item, message, target, userData);
  239. }
  240. //-----------------------------------------------------------------------------
  241. // Purpose: Add a menu item to the menu.
  242. // Input : *itemText - Name of item text that will appear in the manu.
  243. // This will also be used as the name of the menu item panel.
  244. // *message - pointer to the message to send when the item is selected
  245. // *target - Target panel of the command
  246. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  247. // ptr to the menu that opens on selecting the item
  248. //-----------------------------------------------------------------------------
  249. int Menu::AddMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData )
  250. {
  251. return AddMenuItem(itemText, itemText, message, target, userData );
  252. }
  253. //-----------------------------------------------------------------------------
  254. // Purpose: Add a menu item to the menu.
  255. // Input : *itemText - Name of item text that will appear in the manu.
  256. // This will also be the text of the command sent when the
  257. // item is selected.
  258. // *target - Target panel of the command
  259. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  260. // ptr to the menu that opens on selecting the item
  261. //-----------------------------------------------------------------------------
  262. int Menu::AddMenuItem( const char *itemText, Panel *target , const KeyValues *userData )
  263. {
  264. return AddMenuItem(itemText, itemText, target, userData );
  265. }
  266. //-----------------------------------------------------------------------------
  267. // Purpose: Add a checkable menu item to the menu.
  268. // Input : *itemName - Name of item
  269. // *itemText - Name of item text that will appear in the manu.
  270. // *command - Command text to be sent when menu item is selected
  271. // *target - Target panel of the command
  272. //-----------------------------------------------------------------------------
  273. int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData )
  274. {
  275. MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true);
  276. return AddMenuItemCharCommand(item, command, target, userData);
  277. }
  278. int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData )
  279. {
  280. MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true);
  281. return AddMenuItemCharCommand(item, command, target, userData);
  282. }
  283. //-----------------------------------------------------------------------------
  284. // Purpose: Add a checkable menu item to the menu.
  285. // Input : *itemText - Name of item text that will appear in the manu.
  286. // This will also be used as the name of the menu item panel.
  287. // *command - Command text to be sent when menu item is selected
  288. // *target - Target panel of the command
  289. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  290. // ptr to the menu that opens on selecting the item
  291. //-----------------------------------------------------------------------------
  292. int Menu::AddCheckableMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData )
  293. {
  294. return AddCheckableMenuItem(itemText, itemText, command, target, userData );
  295. }
  296. //-----------------------------------------------------------------------------
  297. // Purpose: Add a checkable menu item to the menu.
  298. // Input : *itemName - Name of item
  299. // *itemText - Name of item text that will appear in the manu.
  300. // *message - pointer to the message to send when the item is selected
  301. // *target - Target panel of the command
  302. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  303. // ptr to the menu that opens on selecting the item
  304. //-----------------------------------------------------------------------------
  305. int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData )
  306. {
  307. MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true);
  308. return AddMenuItemKeyValuesCommand(item, message, target, userData);
  309. }
  310. int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData )
  311. {
  312. MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true);
  313. return AddMenuItemKeyValuesCommand(item, message, target, userData);
  314. }
  315. //-----------------------------------------------------------------------------
  316. // Purpose: Add a checkable menu item to the menu.
  317. // Input : *itemText - Name of item text that will appear in the manu.
  318. // This will also be used as the name of the menu item panel.
  319. // *message - pointer to the message to send when the item is selected
  320. // *target - Target panel of the command
  321. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  322. // ptr to the menu that opens on selecting the item
  323. //-----------------------------------------------------------------------------
  324. int Menu::AddCheckableMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData )
  325. {
  326. return AddCheckableMenuItem(itemText, itemText, message, target, userData );
  327. }
  328. //-----------------------------------------------------------------------------
  329. // Purpose: Add a checkable menu item to the menu.
  330. // Input : *itemText - Name of item text that will appear in the manu.
  331. // This will also be the text of the command sent when the
  332. // item is selected.
  333. // *target - Target panel of the command
  334. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  335. // ptr to the menu that opens on selecting the item
  336. //-----------------------------------------------------------------------------
  337. int Menu::AddCheckableMenuItem( const char *itemText, Panel *target, const KeyValues *userData )
  338. {
  339. return AddCheckableMenuItem(itemText, itemText, target, userData );
  340. }
  341. //-----------------------------------------------------------------------------
  342. // Purpose: Add a Cascading menu item to the menu.
  343. // Input : *itemName - Name of item
  344. // *itemText - Name of item text that will appear in the manu.
  345. // *command - Command text to be sent when menu item is selected
  346. // *target - Target panel of the command
  347. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  348. // ptr to the menu that opens on selecting the item
  349. //-----------------------------------------------------------------------------
  350. int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData )
  351. {
  352. MenuItem *item = new MenuItem(this, itemName, itemText, cascadeMenu );
  353. return AddMenuItemCharCommand(item, command, target, userData);
  354. }
  355. int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData )
  356. {
  357. MenuItem *item = new MenuItem(this, itemName, wszItemText, cascadeMenu );
  358. return AddMenuItemCharCommand(item, command, target, userData);
  359. }
  360. //-----------------------------------------------------------------------------
  361. // Purpose: Add a Cascading menu item to the menu.
  362. // Input : *itemText - Name of item text that will appear in the manu.
  363. // This will also be used as the name of the menu item panel.
  364. // *command - Command text to be sent when menu item is selected
  365. // *target - Target panel of the command
  366. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  367. // ptr to the menu that opens on selecting the item
  368. //-----------------------------------------------------------------------------
  369. int Menu::AddCascadingMenuItem( const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData )
  370. {
  371. return AddCascadingMenuItem( itemText, itemText, command, target, cascadeMenu, userData );
  372. }
  373. //-----------------------------------------------------------------------------
  374. // Purpose: Add a Cascading menu item to the menu.
  375. // Input : *itemName - Name of item
  376. // *itemText - Name of item text that will appear in the manu.
  377. // *message - pointer to the message to send when the item is selected
  378. // *target - Target panel of the command
  379. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  380. // ptr to the menu that opens on selecting the item
  381. //-----------------------------------------------------------------------------
  382. int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData )
  383. {
  384. MenuItem *item = new MenuItem( this, itemName, itemText, cascadeMenu);
  385. return AddMenuItemKeyValuesCommand(item, message, target, userData);
  386. }
  387. int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData )
  388. {
  389. MenuItem *item = new MenuItem( this, itemName, wszItemText, cascadeMenu);
  390. return AddMenuItemKeyValuesCommand(item, message, target, userData);
  391. }
  392. //-----------------------------------------------------------------------------
  393. // Purpose: Add a Cascading menu item to the menu.
  394. // Input : *itemText - Name of item text that will appear in the manu.
  395. // This will also be used as the name of the menu item panel.
  396. // *message - pointer to the message to send when the item is selected
  397. // *target - Target panel of the command
  398. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  399. // ptr to the menu that opens on selecting the item
  400. //-----------------------------------------------------------------------------
  401. int Menu::AddCascadingMenuItem( const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData )
  402. {
  403. return AddCascadingMenuItem(itemText, itemText, message, target, cascadeMenu, userData );
  404. }
  405. //-----------------------------------------------------------------------------
  406. // Purpose: Add a Cascading menu item to the menu.
  407. // Input : *itemText - Name of item text that will appear in the manu.
  408. // This will also be the text of the command sent when the
  409. // item is selected.
  410. // *target - Target panel of the command
  411. // *cascadeMenu - if the menu item opens a cascading menu, this is a
  412. // ptr to the menu that opens on selecting the item
  413. //-----------------------------------------------------------------------------
  414. int Menu::AddCascadingMenuItem( const char *itemText, Panel *target, Menu *cascadeMenu, const KeyValues *userData )
  415. {
  416. return AddCascadingMenuItem(itemText, itemText, target, cascadeMenu, userData);
  417. }
  418. //-----------------------------------------------------------------------------
  419. // Purpose: Sets the values of a menu item at the specified index
  420. // Input : index - the index of this item entry
  421. // *message - pointer to the message to send when the item is selected
  422. //-----------------------------------------------------------------------------
  423. void Menu::UpdateMenuItem(int itemID, const char *itemText, KeyValues *message, const KeyValues *userData)
  424. {
  425. Assert( m_MenuItems.IsValidIndex(itemID) );
  426. if ( m_MenuItems.IsValidIndex(itemID) )
  427. {
  428. MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
  429. // make sure its enabled since disabled items get highlighted.
  430. if (menuItem)
  431. {
  432. menuItem->SetText(itemText);
  433. menuItem->SetCommand(message);
  434. if(userData)
  435. {
  436. menuItem->SetUserData(userData);
  437. }
  438. }
  439. }
  440. _recalculateWidth = true;
  441. }
  442. //-----------------------------------------------------------------------------
  443. // Purpose: Sets the values of a menu item at the specified index
  444. //-----------------------------------------------------------------------------
  445. void Menu::UpdateMenuItem(int itemID, const wchar_t *wszItemText, KeyValues *message, const KeyValues *userData)
  446. {
  447. Assert( m_MenuItems.IsValidIndex(itemID) );
  448. if ( m_MenuItems.IsValidIndex(itemID) )
  449. {
  450. MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
  451. // make sure its enabled since disabled items get highlighted.
  452. if (menuItem)
  453. {
  454. menuItem->SetText(wszItemText);
  455. menuItem->SetCommand(message);
  456. if(userData)
  457. {
  458. menuItem->SetUserData(userData);
  459. }
  460. }
  461. }
  462. _recalculateWidth = true;
  463. }
  464. //-----------------------------------------------------------------------------
  465. // Sets the content alignment of all items in the menu
  466. //-----------------------------------------------------------------------------
  467. void Menu::SetContentAlignment( Label::Alignment alignment )
  468. {
  469. if ( m_Alignment != alignment )
  470. {
  471. m_Alignment = alignment;
  472. // Change the alignment of existing menu items
  473. int nCount = m_MenuItems.Count();
  474. for ( int i = 0; i < nCount; ++i )
  475. {
  476. MenuItem *pItem = m_MenuItems[ i ];
  477. pItem->SetContentAlignment( alignment );
  478. // Recurse on cascading menus
  479. Menu *pSubMenu = pItem->GetMenu();
  480. if ( pSubMenu )
  481. {
  482. pSubMenu->SetContentAlignment( alignment );
  483. }
  484. }
  485. }
  486. }
  487. //-----------------------------------------------------------------------------
  488. // Purpose: Locks down a specific width
  489. //-----------------------------------------------------------------------------
  490. void Menu::SetFixedWidth(int width)
  491. {
  492. // the padding makes it so the menu has the label padding on each side of the menu.
  493. // makes the menu items look centered.
  494. m_iFixedWidth = width;
  495. InvalidateLayout(false);
  496. }
  497. //-----------------------------------------------------------------------------
  498. // Purpose: sets the height of each menu item
  499. //-----------------------------------------------------------------------------
  500. void Menu::SetMenuItemHeight(int itemHeight)
  501. {
  502. m_iMenuItemHeight = itemHeight;
  503. }
  504. int Menu::GetMenuItemHeight() const
  505. {
  506. return m_iMenuItemHeight;
  507. }
  508. int Menu::CountVisibleItems()
  509. {
  510. int count = 0;
  511. int c = m_SortedItems.Count();
  512. for ( int i = 0 ; i < c; ++i )
  513. {
  514. if ( m_MenuItems[ m_SortedItems[ i ] ]->IsVisible() )
  515. ++count;
  516. }
  517. return count;
  518. }
  519. void Menu::ComputeWorkspaceSize( int& workWide, int& workTall )
  520. {
  521. // make sure we factor in insets
  522. int ileft, iright, itop, ibottom;
  523. GetInset(ileft, iright, itop, ibottom);
  524. int workX, workY;
  525. surface()->GetWorkspaceBounds(workX, workY, workWide, workTall);
  526. workTall -= 20;
  527. workTall -= itop;
  528. workTall -= ibottom;
  529. }
  530. // Assumes relative coords in screenspace
  531. void Menu::PositionRelativeToPanel( Panel *relative, MenuDirection_e direction, int nAdditionalYOffset /*=0*/, bool showMenu /*=false*/ )
  532. {
  533. Assert( relative );
  534. int rx, ry, rw, rh;
  535. relative->GetBounds( rx, ry, rw, rh );
  536. relative->LocalToScreen( rx, ry );
  537. if ( direction == CURSOR )
  538. {
  539. // force the menu to appear where the mouse button was pressed
  540. input()->GetCursorPos(rx, ry);
  541. rw = rh = 0;
  542. }
  543. else if ( direction == ALIGN_WITH_PARENT && relative->GetVParent() )
  544. {
  545. rx = 0, ry = 0;
  546. relative->ParentLocalToScreen(rx, ry);
  547. rx -= 1; // take border into account
  548. ry += rh + nAdditionalYOffset;
  549. rw = rh = 0;
  550. }
  551. else
  552. {
  553. rx = 0, ry = 0;
  554. relative->LocalToScreen(rx, ry);
  555. }
  556. int workWide, workTall;
  557. ComputeWorkspaceSize( workWide, workTall );
  558. // Final pos
  559. int x = 0, y = 0;
  560. int mWide, mTall;
  561. GetSize( mWide, mTall );
  562. switch( direction )
  563. {
  564. case Menu::UP: // Menu prefers to open upward
  565. {
  566. x = rx;
  567. int topOfReference = ry;
  568. y = topOfReference - mTall;
  569. if ( y < 0 )
  570. {
  571. int bottomOfReference = ry + rh + 1;
  572. int remainingPixels = workTall - bottomOfReference;
  573. // Can't fit on bottom, either, move to side
  574. if ( mTall >= remainingPixels )
  575. {
  576. y = workTall - mTall;
  577. x = rx + rw;
  578. // Try and place it to the left of the button
  579. if ( x + mWide > workWide )
  580. {
  581. x = rx - mWide;
  582. }
  583. }
  584. else
  585. {
  586. // Room at bottom
  587. y = bottomOfReference;
  588. }
  589. }
  590. }
  591. break;
  592. // Everyone else aligns downward...
  593. default:
  594. case Menu::LEFT:
  595. case Menu::RIGHT:
  596. case Menu::DOWN:
  597. {
  598. x = rx;
  599. int bottomOfReference = ry + rh + 1;
  600. y = bottomOfReference;
  601. if ( bottomOfReference + mTall >= workTall )
  602. {
  603. // See if there's run straight above
  604. if ( mTall >= ry ) // No room, try and push menu to right or left
  605. {
  606. y = workTall - mTall;
  607. x = rx + rw;
  608. // Try and place it to the left of the button
  609. if ( x + mWide > workWide )
  610. {
  611. x = rx - mWide;
  612. }
  613. }
  614. else
  615. {
  616. // Room at top
  617. y = ry - mTall;
  618. }
  619. }
  620. }
  621. break;
  622. }
  623. // Check left rightness
  624. if ( x + mWide > workWide )
  625. {
  626. x = workWide - mWide;
  627. Assert( x >= 0 ); // yikes!!!
  628. }
  629. else if ( x < 0 )
  630. {
  631. x = 0;
  632. }
  633. SetPos( x, y );
  634. if ( showMenu )
  635. {
  636. SetVisible( true );
  637. }
  638. }
  639. int Menu::ComputeFullMenuHeightWithInsets()
  640. {
  641. // make sure we factor in insets
  642. int ileft, iright, itop, ibottom;
  643. GetInset(ileft, iright, itop, ibottom);
  644. int separatorHeight = 3;
  645. // add up the size of all the child panels
  646. // move the child panels to the correct place in the menu
  647. int totalTall = itop + ibottom;
  648. int i;
  649. for ( i = 0 ; i < m_SortedItems.Count() ; i++ ) // use sortedItems instead of MenuItems due to SetPos()
  650. {
  651. int itemId = m_SortedItems[i];
  652. MenuItem *child = m_MenuItems[ itemId ];
  653. Assert( child );
  654. if ( !child )
  655. continue;
  656. // These should all be visible at this point
  657. if ( !child->IsVisible() )
  658. continue;
  659. totalTall += m_iMenuItemHeight;
  660. // Add a separator if needed...
  661. int sepIndex = m_Separators.Find( itemId );
  662. if ( sepIndex != m_Separators.InvalidIndex() )
  663. {
  664. totalTall += separatorHeight;
  665. }
  666. }
  667. return totalTall;
  668. }
  669. //-----------------------------------------------------------------------------
  670. // Purpose: Reformat according to the new layout
  671. //-----------------------------------------------------------------------------
  672. void Menu::PerformLayout()
  673. {
  674. MenuItem *parent = GetParentMenuItem();
  675. bool cascading = parent != NULL ? true : false;
  676. // make sure we factor in insets
  677. int ileft, iright, itop, ibottom;
  678. GetInset(ileft, iright, itop, ibottom);
  679. int workWide, workTall;
  680. ComputeWorkspaceSize( workWide, workTall );
  681. int fullHeightWouldRequire = ComputeFullMenuHeightWithInsets();
  682. bool bNeedScrollbar = fullHeightWouldRequire >= workTall;
  683. int maxVisibleItems = CountVisibleItems();
  684. if ( m_iNumVisibleLines > 0 &&
  685. maxVisibleItems > m_iNumVisibleLines )
  686. {
  687. bNeedScrollbar = true;
  688. maxVisibleItems = m_iNumVisibleLines;
  689. }
  690. // if we have a scroll bar
  691. if ( bNeedScrollbar )
  692. {
  693. // add it to the display
  694. AddScrollBar();
  695. // This fills in m_VisibleSortedItems as needed
  696. MakeItemsVisibleInScrollRange( m_iNumVisibleLines, MIN( fullHeightWouldRequire, workTall ) );
  697. }
  698. else
  699. {
  700. RemoveScrollBar();
  701. // Make everything visible
  702. m_VisibleSortedItems.RemoveAll();
  703. int i;
  704. int c = m_SortedItems.Count();
  705. for ( i = 0; i < c; ++i )
  706. {
  707. int itemID = m_SortedItems[ i ];
  708. MenuItem *child = m_MenuItems[ itemID ];
  709. if ( !child || !child->IsVisible() )
  710. continue;
  711. m_VisibleSortedItems.AddToTail( itemID );
  712. }
  713. // Hide the separators, the needed ones will be readded below
  714. c = m_SeparatorPanels.Count();
  715. for ( i = 0; i < c; ++i )
  716. {
  717. if ( m_SeparatorPanels[ i ] )
  718. {
  719. m_SeparatorPanels[ i ]->SetVisible( false );
  720. }
  721. }
  722. }
  723. // get the appropriate menu border
  724. LayoutMenuBorder();
  725. int trueW = GetWide();
  726. if ( bNeedScrollbar )
  727. {
  728. trueW -= m_pScroller->GetWide();
  729. }
  730. int separatorHeight = MENU_SEPARATOR_HEIGHT;
  731. // add up the size of all the child panels
  732. // move the child panels to the correct place in the menu
  733. int menuTall = 0;
  734. int totalTall = itop + ibottom;
  735. int i;
  736. for ( i = 0 ; i < m_VisibleSortedItems.Count() ; i++ ) // use sortedItems instead of MenuItems due to SetPos()
  737. {
  738. int itemId = m_VisibleSortedItems[i];
  739. MenuItem *child = m_MenuItems[ itemId ];
  740. Assert( child );
  741. if ( !child )
  742. continue;
  743. // These should all be visible at this point
  744. if ( !child->IsVisible() )
  745. continue;
  746. if ( totalTall >= workTall )
  747. break;
  748. if ( INVALID_FONT != m_hItemFont )
  749. {
  750. child->SetFont( m_hItemFont );
  751. }
  752. // take into account inset
  753. child->SetPos (0, menuTall);
  754. child->SetTall( m_iMenuItemHeight ); // Width is set in a second pass
  755. menuTall += m_iMenuItemHeight;
  756. totalTall += m_iMenuItemHeight;
  757. // this will make all the menuitems line up in a column with space for the checks to the left.
  758. if ( ( !child->IsCheckable() ) && ( m_iCheckImageWidth > 0 ) )
  759. {
  760. // Non checkable items have to move over
  761. child->SetTextInset( m_iCheckImageWidth, 0 );
  762. }
  763. else if ( child->IsCheckable() )
  764. {
  765. child->SetTextInset(0, 0); //TODO: for some reason I can't comment this out.
  766. }
  767. // Add a separator if needed...
  768. int sepIndex = m_Separators.Find( itemId );
  769. if ( sepIndex != m_Separators.InvalidIndex() )
  770. {
  771. MenuSeparator *sep = m_SeparatorPanels[ sepIndex ];
  772. Assert( sep );
  773. sep->SetVisible( true );
  774. sep->SetBounds( 0, menuTall, trueW, separatorHeight );
  775. menuTall += separatorHeight;
  776. totalTall += separatorHeight;
  777. }
  778. }
  779. if (!m_iFixedWidth)
  780. {
  781. _recalculateWidth = true;
  782. CalculateWidth();
  783. }
  784. else if (m_iFixedWidth)
  785. {
  786. _menuWide = m_iFixedWidth;
  787. // fixed width menus include the scroll bar in their width.
  788. if (_sizedForScrollBar)
  789. {
  790. _menuWide -= m_pScroller->GetWide();
  791. }
  792. }
  793. SizeMenuItems();
  794. int extraWidth = 0;
  795. if (_sizedForScrollBar)
  796. {
  797. extraWidth = m_pScroller->GetWide();
  798. }
  799. int mwide = _menuWide + extraWidth;
  800. if ( mwide > workWide )
  801. {
  802. mwide = workWide;
  803. }
  804. int mtall = menuTall + itop + ibottom;
  805. if ( mtall > workTall )
  806. {
  807. // Shouldn't happen
  808. mtall = workTall;
  809. }
  810. // set the new size of the menu
  811. SetSize( mwide, mtall );
  812. // move the menu to the correct position if it is a cascading menu.
  813. if ( cascading )
  814. {
  815. // move the menu to the correct position if it is a cascading menu.
  816. PositionCascadingMenu();
  817. }
  818. // set up scroll bar as appropriate
  819. if ( m_pScroller->IsVisible() )
  820. {
  821. LayoutScrollBar();
  822. }
  823. FOR_EACH_LL( m_MenuItems, j )
  824. {
  825. m_MenuItems[j]->InvalidateLayout(); // cause each menu item to redo its apply settings now we have sized ourselves
  826. }
  827. Repaint();
  828. }
  829. //-----------------------------------------------------------------------------
  830. // Purpose: Force the menu to work out how wide it should be
  831. //-----------------------------------------------------------------------------
  832. void Menu::ForceCalculateWidth()
  833. {
  834. _recalculateWidth = true;
  835. CalculateWidth();
  836. PerformLayout();
  837. }
  838. //-----------------------------------------------------------------------------
  839. // Purpose: Figure out how wide the menu should be if the menu is not fixed width
  840. //-----------------------------------------------------------------------------
  841. void Menu::CalculateWidth()
  842. {
  843. if (!_recalculateWidth)
  844. return;
  845. _menuWide = 0;
  846. if (!m_iFixedWidth)
  847. {
  848. // find the biggest menu item
  849. FOR_EACH_LL( m_MenuItems, i )
  850. {
  851. int wide, tall;
  852. m_MenuItems[i]->GetContentSize(wide, tall);
  853. if (wide > _menuWide - Label::Content)
  854. {
  855. _menuWide = wide + Label::Content;
  856. }
  857. }
  858. }
  859. // enfoce a minimumWidth
  860. if (_menuWide < m_iMinimumWidth)
  861. {
  862. _menuWide = m_iMinimumWidth;
  863. }
  864. _recalculateWidth = false;
  865. }
  866. //-----------------------------------------------------------------------------
  867. // Purpose: Set up the scroll bar attributes,size and location.
  868. //-----------------------------------------------------------------------------
  869. void Menu::LayoutScrollBar()
  870. {
  871. //!! need to make it recalculate scroll positions
  872. m_pScroller->SetEnabled(false);
  873. m_pScroller->SetRangeWindow( m_VisibleSortedItems.Count() );
  874. m_pScroller->SetRange( 0, CountVisibleItems() );
  875. m_pScroller->SetButtonPressedScrollValue( 1 );
  876. int wide, tall;
  877. GetSize (wide, tall);
  878. // make sure we factor in insets
  879. int ileft, iright, itop, ibottom;
  880. GetInset(ileft, iright, itop, ibottom);
  881. // with a scroll bar we take off the inset
  882. wide -= iright;
  883. m_pScroller->SetPos(wide - m_pScroller->GetWide(), 1);
  884. // scrollbar is inside the menu's borders.
  885. m_pScroller->SetSize(m_pScroller->GetWide(), tall - ibottom - itop);
  886. }
  887. //-----------------------------------------------------------------------------
  888. // Purpose: Figure out where to open menu if it is a cascading menu
  889. //-----------------------------------------------------------------------------
  890. void Menu::PositionCascadingMenu()
  891. {
  892. Assert(GetVParent());
  893. int parentX, parentY, parentWide, parentTall;
  894. // move the menu to the correct place below the menuItem
  895. ipanel()->GetSize(GetVParent(), parentWide, parentTall);
  896. ipanel()->GetPos(GetVParent(), parentX, parentY);
  897. parentX += parentWide, parentY = 0;
  898. ParentLocalToScreen(parentX, parentY);
  899. SetPos(parentX, parentY);
  900. // for cascading menus,
  901. // make sure we're on the screen
  902. int workX, workY, workWide, workTall, x, y, wide, tall;
  903. GetBounds(x, y, wide, tall);
  904. surface()->GetWorkspaceBounds(workX, workY, workWide, workTall);
  905. if (x + wide > workX + workWide)
  906. {
  907. // we're off the right, move the menu to the left side
  908. // orignalX - width of the parentmenuitem - width of this menu.
  909. // add 2 pixels to offset one pixel onto the parent menu.
  910. x -= (parentWide + wide);
  911. x -= 2;
  912. }
  913. else
  914. {
  915. // alignment move it in the amount of the insets.
  916. x += 1;
  917. }
  918. if ( y + tall > workY + workTall )
  919. {
  920. int lastWorkY = workY + workTall;
  921. int pixelsOffBottom = ( y + tall ) - lastWorkY;
  922. y -= pixelsOffBottom;
  923. y -= 2;
  924. }
  925. else
  926. {
  927. y -= 1;
  928. }
  929. SetPos(x, y);
  930. MoveToFront();
  931. }
  932. //-----------------------------------------------------------------------------
  933. // Purpose: Size the menu items so they are the width of the menu.
  934. // Also size the menu items with cascading menus so the arrow fits in there.
  935. //-----------------------------------------------------------------------------
  936. void Menu::SizeMenuItems()
  937. {
  938. int ileft, iright, itop, ibottom;
  939. GetInset(ileft, iright, itop, ibottom);
  940. // assign the sizes of all the menu item panels
  941. FOR_EACH_LL( m_MenuItems, i )
  942. {
  943. MenuItem *child = m_MenuItems[i];
  944. if (child )
  945. {
  946. // labels do thier own sizing. this will size the label to the width of the menu,
  947. // this will put the cascading menu arrow on the right side automatically.
  948. child->SetWide(_menuWide - ileft - iright);
  949. }
  950. }
  951. }
  952. //-----------------------------------------------------------------------------
  953. // Purpose: Makes menu items visible in relation to where the scroll bar is
  954. //-----------------------------------------------------------------------------
  955. void Menu::MakeItemsVisibleInScrollRange( int maxVisibleItems, int nNumPixelsAvailable )
  956. {
  957. // Detach all items from tree
  958. int i;
  959. FOR_EACH_LL( m_MenuItems, item )
  960. {
  961. m_MenuItems[ item ]->SetBounds( 0, 0, 0, 0 );
  962. }
  963. for ( i = 0; i < m_SeparatorPanels.Count(); ++i )
  964. {
  965. m_SeparatorPanels[ i ]->SetVisible( false );
  966. }
  967. m_VisibleSortedItems.RemoveAll();
  968. int tall = 0;
  969. int startItem = m_pScroller->GetValue();
  970. Assert( startItem >= 0 );
  971. do
  972. {
  973. if ( startItem >= m_SortedItems.Count() )
  974. break;
  975. int itemId = m_SortedItems[ startItem ];
  976. if ( !m_MenuItems[ itemId ]->IsVisible() )
  977. {
  978. ++startItem;
  979. continue;
  980. }
  981. int itemHeight = m_iMenuItemHeight;
  982. int sepIndex = m_Separators.Find( itemId );
  983. if ( sepIndex != m_Separators.InvalidIndex() )
  984. {
  985. itemHeight += MENU_SEPARATOR_HEIGHT;
  986. }
  987. if ( tall + itemHeight > nNumPixelsAvailable )
  988. break;
  989. // Too many items
  990. if ( maxVisibleItems > 0 )
  991. {
  992. if ( m_VisibleSortedItems.Count() >= maxVisibleItems )
  993. break;
  994. }
  995. tall += itemHeight;
  996. // Re-attach this one
  997. m_VisibleSortedItems.AddToTail( itemId );
  998. ++startItem;
  999. }
  1000. while ( true );
  1001. }
  1002. //-----------------------------------------------------------------------------
  1003. // Purpose: Get the approproate menu border
  1004. //-----------------------------------------------------------------------------
  1005. void Menu::LayoutMenuBorder()
  1006. {
  1007. IBorder *menuBorder;
  1008. IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
  1009. menuBorder = pScheme->GetBorder("MenuBorder");
  1010. if ( menuBorder )
  1011. {
  1012. SetBorder(menuBorder);
  1013. }
  1014. }
  1015. //-----------------------------------------------------------------------------
  1016. // Purpose: Draw a black border on the right side of the menu items
  1017. //-----------------------------------------------------------------------------
  1018. void Menu::Paint()
  1019. {
  1020. if ( m_pScroller->IsVisible() )
  1021. {
  1022. // draw black bar
  1023. int wide, tall;
  1024. GetSize (wide, tall);
  1025. surface()->DrawSetColor(_borderDark);
  1026. if( IsProportional() )
  1027. {
  1028. surface()->DrawFilledRect(wide - m_pScroller->GetWide(), -1, wide - m_pScroller->GetWide() + 1, tall);
  1029. }
  1030. else
  1031. {
  1032. surface()->DrawFilledRect(wide - m_pScroller->GetWide(), -1, wide - m_pScroller->GetWide() + 1, tall);
  1033. }
  1034. }
  1035. }
  1036. //-----------------------------------------------------------------------------
  1037. // Purpose: sets the max number of items visible (scrollbar appears with more)
  1038. // Input : numItems -
  1039. //-----------------------------------------------------------------------------
  1040. void Menu::SetNumberOfVisibleItems( int numItems )
  1041. {
  1042. m_iNumVisibleLines = numItems;
  1043. InvalidateLayout(false);
  1044. }
  1045. //-----------------------------------------------------------------------------
  1046. // Purpose:
  1047. //-----------------------------------------------------------------------------
  1048. MenuItem *Menu::GetMenuItem(int itemID)
  1049. {
  1050. if ( !m_MenuItems.IsValidIndex(itemID) )
  1051. return NULL;
  1052. return m_MenuItems[itemID];
  1053. }
  1054. //-----------------------------------------------------------------------------
  1055. // Purpose:
  1056. //-----------------------------------------------------------------------------
  1057. bool Menu::IsValidMenuID(int itemID)
  1058. {
  1059. return m_MenuItems.IsValidIndex(itemID);
  1060. }
  1061. //-----------------------------------------------------------------------------
  1062. // Purpose:
  1063. //-----------------------------------------------------------------------------
  1064. int Menu::GetInvalidMenuID()
  1065. {
  1066. return m_MenuItems.InvalidIndex();
  1067. }
  1068. //-----------------------------------------------------------------------------
  1069. // Purpose: When a menuItem is selected, close cascading menus
  1070. // if the menuItem selected has a cascading menu attached, we
  1071. // want to keep that one open so skip it.
  1072. // Passing NULL will close all cascading menus.
  1073. //-----------------------------------------------------------------------------
  1074. void Menu::CloseOtherMenus(MenuItem *item)
  1075. {
  1076. FOR_EACH_LL( m_MenuItems, i )
  1077. {
  1078. if (m_MenuItems[i] == item)
  1079. continue;
  1080. m_MenuItems[i]->CloseCascadeMenu();
  1081. }
  1082. }
  1083. //-----------------------------------------------------------------------------
  1084. // Purpose: Respond to string commands.
  1085. //-----------------------------------------------------------------------------
  1086. void Menu::OnCommand( const char *command )
  1087. {
  1088. // forward on the message
  1089. PostActionSignal(new KeyValues("Command", "command", command));
  1090. Panel::OnCommand(command);
  1091. }
  1092. //-----------------------------------------------------------------------------
  1093. // Purpose: Handle key presses, Activate shortcuts
  1094. //-----------------------------------------------------------------------------
  1095. void Menu::OnKeyCodeTyped(KeyCode keycode)
  1096. {
  1097. vgui::KeyCode code = GetBaseButtonCode( keycode );
  1098. // Don't allow key inputs when disabled!
  1099. if ( !IsEnabled() )
  1100. return;
  1101. bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT));
  1102. if (alt)
  1103. {
  1104. BaseClass::OnKeyCodeTyped( keycode );
  1105. // Ignore alt when in combobox mode
  1106. if (m_eTypeAheadMode != TYPE_AHEAD_MODE)
  1107. {
  1108. PostActionSignal(new KeyValues("MenuClose"));
  1109. }
  1110. }
  1111. switch (code)
  1112. {
  1113. case KEY_ESCAPE:
  1114. case KEY_XBUTTON_B:
  1115. {
  1116. // hide the menu on ESC
  1117. SetVisible(false);
  1118. break;
  1119. }
  1120. // arrow keys scroll through items on the list.
  1121. // they should also scroll the scroll bar if needed
  1122. case KEY_UP:
  1123. case KEY_XBUTTON_UP:
  1124. case KEY_XSTICK1_UP:
  1125. {
  1126. MoveAlongMenuItemList(MENU_UP, 0);
  1127. if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
  1128. {
  1129. m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
  1130. }
  1131. break;
  1132. }
  1133. case KEY_DOWN:
  1134. case KEY_XBUTTON_DOWN:
  1135. case KEY_XSTICK1_DOWN:
  1136. {
  1137. MoveAlongMenuItemList(MENU_DOWN, 0);
  1138. if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
  1139. {
  1140. m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
  1141. }
  1142. break;
  1143. }
  1144. // for now left and right arrows just open or close submenus if they are there.
  1145. case KEY_RIGHT:
  1146. case KEY_XBUTTON_RIGHT:
  1147. case KEY_XSTICK1_RIGHT:
  1148. {
  1149. // make sure a menuItem is currently selected
  1150. if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
  1151. {
  1152. if (m_MenuItems[m_iCurrentlySelectedItemID]->HasMenu())
  1153. {
  1154. ActivateItem(m_iCurrentlySelectedItemID);
  1155. }
  1156. else
  1157. {
  1158. BaseClass::OnKeyCodeTyped( keycode );
  1159. }
  1160. }
  1161. else
  1162. {
  1163. BaseClass::OnKeyCodeTyped( keycode );
  1164. }
  1165. break;
  1166. }
  1167. case KEY_LEFT:
  1168. case KEY_XBUTTON_LEFT:
  1169. case KEY_XSTICK1_LEFT:
  1170. {
  1171. // if our parent is a menu item then we are a submenu so close us.
  1172. if (GetParentMenuItem())
  1173. {
  1174. SetVisible(false);
  1175. }
  1176. else
  1177. {
  1178. BaseClass::OnKeyCodeTyped( keycode );
  1179. }
  1180. break;
  1181. }
  1182. case KEY_ENTER:
  1183. case KEY_XBUTTON_A:
  1184. {
  1185. // make sure a menuItem is currently selected
  1186. if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
  1187. {
  1188. ActivateItem(m_iCurrentlySelectedItemID);
  1189. }
  1190. else
  1191. {
  1192. BaseClass::OnKeyCodeTyped( keycode ); // chain up
  1193. }
  1194. break;
  1195. }
  1196. case KEY_PAGEUP:
  1197. {
  1198. if ( m_iNumVisibleLines > 1 )
  1199. {
  1200. if ( m_iCurrentlySelectedItemID < m_iNumVisibleLines )
  1201. {
  1202. MoveAlongMenuItemList( MENU_UP * m_iCurrentlySelectedItemID, 0 );
  1203. }
  1204. else
  1205. {
  1206. MoveAlongMenuItemList(MENU_UP * m_iNumVisibleLines - 1, 0);
  1207. }
  1208. }
  1209. else
  1210. {
  1211. MoveAlongMenuItemList(MENU_UP, 0);
  1212. }
  1213. if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
  1214. {
  1215. m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
  1216. }
  1217. break;
  1218. }
  1219. case KEY_PAGEDOWN:
  1220. {
  1221. if ( m_iNumVisibleLines > 1 )
  1222. {
  1223. if ( m_iCurrentlySelectedItemID + m_iNumVisibleLines >= GetItemCount() )
  1224. {
  1225. MoveAlongMenuItemList(MENU_DOWN * ( GetItemCount() - m_iCurrentlySelectedItemID - 1), 0);
  1226. }
  1227. else
  1228. {
  1229. MoveAlongMenuItemList(MENU_DOWN * m_iNumVisibleLines - 1, 0);
  1230. }
  1231. }
  1232. else
  1233. {
  1234. MoveAlongMenuItemList(MENU_DOWN, 0);
  1235. }
  1236. if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
  1237. {
  1238. m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
  1239. }
  1240. break;
  1241. }
  1242. case KEY_HOME:
  1243. {
  1244. MoveAlongMenuItemList( MENU_UP * m_iCurrentlySelectedItemID, 0 );
  1245. if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
  1246. {
  1247. m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
  1248. }
  1249. break;
  1250. }
  1251. case KEY_END:
  1252. {
  1253. MoveAlongMenuItemList(MENU_DOWN * ( GetItemCount() - m_iCurrentlySelectedItemID - 1), 0);
  1254. if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
  1255. {
  1256. m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
  1257. }
  1258. break;
  1259. }
  1260. }
  1261. // don't chain back
  1262. }
  1263. void Menu::OnHotKey(wchar_t unichar)
  1264. {
  1265. // iterate the menu items looking for one with the matching hotkey
  1266. FOR_EACH_LL( m_MenuItems, i )
  1267. {
  1268. MenuItem *panel = m_MenuItems[i];
  1269. if (panel->IsVisible())
  1270. {
  1271. Panel *hot = panel->HasHotkey(unichar);
  1272. if (hot)
  1273. {
  1274. // post a message to the menuitem telling it it's hotkey was pressed
  1275. PostMessage(hot, new KeyValues("Hotkey"));
  1276. return;
  1277. }
  1278. // if the menuitem is a cascading menuitem and it is open, check its hotkeys too
  1279. Menu *cascadingMenu = panel->GetMenu();
  1280. if (cascadingMenu && cascadingMenu->IsVisible())
  1281. {
  1282. cascadingMenu->OnKeyTyped(unichar);
  1283. }
  1284. }
  1285. }
  1286. }
  1287. void Menu::OnTypeAhead(wchar_t unichar)
  1288. {
  1289. // Don't do anything if the menu is empty since there cannot be a selected item.
  1290. if ( m_MenuItems.Count() <= 0)
  1291. return;
  1292. // expire the type ahead buffer after 0.5 seconds
  1293. double tCurrentTime = Sys_FloatTime();
  1294. if ( (tCurrentTime - m_fLastTypeAheadTime) > 0.5f )
  1295. {
  1296. m_iNumTypeAheadChars = 0;
  1297. m_szTypeAheadBuf[0] = '\0';
  1298. }
  1299. m_fLastTypeAheadTime = tCurrentTime;
  1300. // add current character to the type ahead buffer
  1301. if ( m_iNumTypeAheadChars+1 < TYPEAHEAD_BUFSIZE )
  1302. {
  1303. m_szTypeAheadBuf[m_iNumTypeAheadChars++] = unichar;
  1304. }
  1305. int itemToSelect = m_iCurrentlySelectedItemID;
  1306. if ( itemToSelect < 0 || itemToSelect >= m_MenuItems.Count())
  1307. {
  1308. itemToSelect = 0;
  1309. }
  1310. int i = itemToSelect;
  1311. do
  1312. {
  1313. wchar_t menuItemName[255];
  1314. m_MenuItems[i]->GetText(menuItemName, 254);
  1315. if ( wcsnicmp( m_szTypeAheadBuf, menuItemName, m_iNumTypeAheadChars) == 0 )
  1316. {
  1317. itemToSelect = i;
  1318. break;
  1319. }
  1320. i = (i+1) % m_MenuItems.Count();
  1321. } while ( i != itemToSelect );
  1322. if ( itemToSelect >= 0 )
  1323. {
  1324. SetCurrentlyHighlightedItem( itemToSelect );
  1325. InvalidateLayout();
  1326. }
  1327. }
  1328. //-----------------------------------------------------------------------------
  1329. // Purpose: Handle key presses, Activate shortcuts
  1330. // Input : code -
  1331. //-----------------------------------------------------------------------------
  1332. void Menu::OnKeyTyped(wchar_t unichar)
  1333. {
  1334. if (! unichar)
  1335. {
  1336. return;
  1337. }
  1338. switch( m_eTypeAheadMode )
  1339. {
  1340. case HOT_KEY_MODE:
  1341. OnHotKey(unichar);
  1342. return;
  1343. case TYPE_AHEAD_MODE:
  1344. OnTypeAhead(unichar);
  1345. return;
  1346. case COMPAT_MODE:
  1347. default:
  1348. break;
  1349. }
  1350. if ( m_MenuItems.Count() <= 0)
  1351. return;
  1352. int itemToSelect = m_iCurrentlySelectedItemID;
  1353. if ( itemToSelect < 0 || itemToSelect >= m_MenuItems.Count())
  1354. {
  1355. itemToSelect = 0;
  1356. }
  1357. int i = (itemToSelect+1) % m_MenuItems.Count();
  1358. while ( i != itemToSelect )
  1359. {
  1360. wchar_t menuItemName[255];
  1361. m_MenuItems[i]->GetText(menuItemName, 254);
  1362. if ( tolower( unichar ) == tolower( menuItemName[0] ) )
  1363. {
  1364. itemToSelect = i;
  1365. break;
  1366. }
  1367. i = (i+1) % m_MenuItems.Count();
  1368. }
  1369. if ( itemToSelect >= 0 )
  1370. {
  1371. SetCurrentlyHighlightedItem( itemToSelect );
  1372. InvalidateLayout();
  1373. }
  1374. // don't chain back
  1375. }
  1376. void Menu::SetTypeAheadMode(MenuTypeAheadMode mode)
  1377. {
  1378. m_eTypeAheadMode = mode;
  1379. }
  1380. int Menu::GetTypeAheadMode()
  1381. {
  1382. return m_eTypeAheadMode;
  1383. }
  1384. //-----------------------------------------------------------------------------
  1385. // Purpose: Handle the mouse wheel event, scroll the selection
  1386. //-----------------------------------------------------------------------------
  1387. void Menu::OnMouseWheeled(int delta)
  1388. {
  1389. if (!m_pScroller->IsVisible())
  1390. return;
  1391. int val = m_pScroller->GetValue();
  1392. val -= delta;
  1393. m_pScroller->SetValue(val);
  1394. // moving the slider redraws the scrollbar,
  1395. // and so we should redraw the menu since the
  1396. // menu draws the black border to the right of the scrollbar.
  1397. InvalidateLayout();
  1398. // don't chain back
  1399. }
  1400. //-----------------------------------------------------------------------------
  1401. // Purpose: Lose focus, hide menu
  1402. //-----------------------------------------------------------------------------
  1403. void Menu::OnKillFocus()
  1404. {
  1405. // check to see if it's a child taking it
  1406. if (!input()->GetFocus() || !ipanel()->HasParent(input()->GetFocus(), GetVPanel()))
  1407. {
  1408. // if we don't accept keyboard input, then we have to ignore the killfocus if it's not actually being stolen
  1409. if (!IsKeyBoardInputEnabled() && !input()->GetFocus())
  1410. return;
  1411. // get the parent of this menu.
  1412. MenuItem *item = GetParentMenuItem();
  1413. // if the parent is a menu item, this menu is a cascading menu
  1414. // if the panel that is getting focus is the parent menu, don't close this menu.
  1415. if ( (item) && (input()->GetFocus() == item->GetVParent()) )
  1416. {
  1417. // if we are in mouse mode and we clicked on the menuitem that
  1418. // triggers the cascading menu, leave it open.
  1419. if (m_iInputMode == MOUSE)
  1420. {
  1421. // return the focus to the cascading menu.
  1422. MoveToFront();
  1423. return;
  1424. }
  1425. }
  1426. // forward the message to the parent.
  1427. PostActionSignal(new KeyValues("MenuClose"));
  1428. // hide this menu
  1429. SetVisible(false);
  1430. }
  1431. }
  1432. namespace vgui
  1433. {
  1434. class CMenuManager
  1435. {
  1436. public:
  1437. void AddMenu( Menu *m )
  1438. {
  1439. if ( !m )
  1440. return;
  1441. int c = m_Menus.Count();
  1442. for ( int i = 0 ; i < c; ++i )
  1443. {
  1444. if ( m_Menus[ i ].Get() == m )
  1445. return;
  1446. }
  1447. DHANDLE< Menu > h;
  1448. h = m;
  1449. m_Menus.AddToTail( h );
  1450. }
  1451. void RemoveMenu( Menu *m )
  1452. {
  1453. if ( !m )
  1454. return;
  1455. int c = m_Menus.Count();
  1456. for ( int i = c - 1 ; i >= 0; --i )
  1457. {
  1458. if ( m_Menus[ i ].Get() == m )
  1459. {
  1460. m_Menus.Remove( i );
  1461. return;
  1462. }
  1463. }
  1464. }
  1465. void OnInternalMousePressed( Panel *other, MouseCode code )
  1466. {
  1467. int c = m_Menus.Count();
  1468. if ( !c )
  1469. return;
  1470. int x, y;
  1471. input()->GetCursorPos( x, y );
  1472. bool mouseInsideMenuRelatedPanel = false;
  1473. for ( int i = c - 1; i >= 0 ; --i )
  1474. {
  1475. Menu *m = m_Menus[ i ].Get();
  1476. if ( !m )
  1477. {
  1478. m_Menus.Remove( i );
  1479. continue;
  1480. }
  1481. // See if the mouse is within a menu
  1482. if ( IsWithinMenuOrRelative( m, x, y ) )
  1483. {
  1484. mouseInsideMenuRelatedPanel = true;
  1485. }
  1486. }
  1487. if ( mouseInsideMenuRelatedPanel )
  1488. {
  1489. return;
  1490. }
  1491. AbortMenus();
  1492. }
  1493. void AbortMenus()
  1494. {
  1495. // Close all of the menus
  1496. int c = m_Menus.Count();
  1497. for ( int i = c - 1; i >= 0 ; --i )
  1498. {
  1499. Menu *m = m_Menus[ i ].Get();
  1500. if ( !m )
  1501. {
  1502. continue;
  1503. }
  1504. m_Menus.Remove( i );
  1505. // Force it to close
  1506. m->SetVisible( false );
  1507. }
  1508. m_Menus.RemoveAll();
  1509. }
  1510. bool IsWithinMenuOrRelative( Panel *panel, int x, int y )
  1511. {
  1512. VPANEL topMost = panel->IsWithinTraverse( x, y, true );
  1513. if ( topMost )
  1514. {
  1515. // It's over the menu
  1516. if ( topMost == panel->GetVPanel() )
  1517. {
  1518. return true;
  1519. }
  1520. // It's over something which is parented to the menu (i.e., a menu item)
  1521. if ( ipanel()->HasParent( topMost, panel->GetVPanel() ) )
  1522. {
  1523. return true;
  1524. }
  1525. }
  1526. if ( panel->GetParent() )
  1527. {
  1528. Panel *parent = panel->GetParent();
  1529. topMost = parent->IsWithinTraverse( x, y, true );
  1530. if ( topMost )
  1531. {
  1532. if ( topMost == parent->GetVPanel() )
  1533. {
  1534. return true;
  1535. }
  1536. /*
  1537. // NOTE: this check used to not cast to MenuButton, but it seems wrong to me
  1538. // since if the mouse is over another child of the parent panel to the menu then
  1539. // the menu stays visible. I think this is bogus.
  1540. Panel *pTopMost = ipanel()->GetPanel(topMost, GetControlsModuleName());
  1541. if ( pTopMost &&
  1542. ipanel()->HasParent( topMost, parent->GetVPanel() ) &&
  1543. dynamic_cast< MenuButton * >( pTopMost ) )
  1544. {
  1545. Msg( "topMost %s has parent %s\n",
  1546. ipanel()->GetName( topMost ),
  1547. parent->GetName() );
  1548. return true;
  1549. }
  1550. */
  1551. }
  1552. }
  1553. return false;
  1554. }
  1555. #ifdef DBGFLAG_VALIDATE
  1556. void Validate( CValidator &validator, char *pchName )
  1557. {
  1558. validator.Push( "CMenuManager", this, pchName );
  1559. m_Menus.Validate( validator, "m_Menus" );
  1560. validator.Pop();
  1561. }
  1562. #endif
  1563. private:
  1564. // List of visible menus
  1565. CUtlVector< DHANDLE< Menu > > m_Menus;
  1566. };
  1567. // Singleton helper class
  1568. static CMenuManager g_MenuMgr;
  1569. } // end namespace vgui
  1570. //-----------------------------------------------------------------------------
  1571. // Purpose: Static method called on mouse released to see if Menu objects should be aborted
  1572. // Input : *other -
  1573. // code -
  1574. //-----------------------------------------------------------------------------
  1575. void Menu::OnInternalMousePressed( Panel *other, MouseCode code )
  1576. {
  1577. g_MenuMgr.OnInternalMousePressed( other, code );
  1578. }
  1579. //-----------------------------------------------------------------------------
  1580. // Purpose: Set visibility of menu and its children as appropriate.
  1581. //-----------------------------------------------------------------------------
  1582. void Menu::SetVisible(bool state)
  1583. {
  1584. if (state == IsVisible())
  1585. return;
  1586. if ( state == false )
  1587. {
  1588. PostActionSignal(new KeyValues("MenuClose"));
  1589. CloseOtherMenus(NULL);
  1590. // Clearing the selected item when hiding the menu caused keyboard selection
  1591. // of items within the combo box to not work properly because the combo box
  1592. // would try to change the selection from the current to the next or previous
  1593. // item, but the current was always -1 because of this line, so it always
  1594. // reset the first time you pressed a key.
  1595. //SetCurrentlySelectedItem(-1);
  1596. g_MenuMgr.RemoveMenu( this );
  1597. }
  1598. else if ( state == true )
  1599. {
  1600. MoveToFront();
  1601. RequestFocus();
  1602. g_MenuMgr.AddMenu( this );
  1603. }
  1604. // must be after movetofront()
  1605. BaseClass::SetVisible(state);
  1606. _sizedForScrollBar = false;
  1607. }
  1608. //-----------------------------------------------------------------------------
  1609. // Purpose:
  1610. //-----------------------------------------------------------------------------
  1611. void Menu::ApplySchemeSettings(IScheme *pScheme)
  1612. {
  1613. BaseClass::ApplySchemeSettings(pScheme);
  1614. SetFgColor(GetSchemeColor("Menu.TextColor", pScheme));
  1615. SetBgColor(GetSchemeColor("Menu.BgColor", pScheme));
  1616. _borderDark = pScheme->GetColor("BorderDark", Color(255, 255, 255, 0));
  1617. FOR_EACH_LL( m_MenuItems, i )
  1618. {
  1619. if( m_MenuItems[i]->IsCheckable() )
  1620. {
  1621. int wide, tall;
  1622. m_MenuItems[i]->GetCheckImageSize( wide, tall );
  1623. m_iCheckImageWidth = MAX( m_iCheckImageWidth, wide );
  1624. }
  1625. }
  1626. _recalculateWidth = true;
  1627. CalculateWidth();
  1628. InvalidateLayout();
  1629. }
  1630. void Menu::SetBgColor( Color newColor )
  1631. {
  1632. BaseClass::SetBgColor( newColor );
  1633. FOR_EACH_LL( m_MenuItems, i )
  1634. {
  1635. if( m_MenuItems[i]->HasMenu() )
  1636. {
  1637. m_MenuItems[i]->GetMenu()->SetBgColor( newColor );
  1638. }
  1639. }
  1640. }
  1641. void Menu::SetFgColor( Color newColor )
  1642. {
  1643. BaseClass::SetFgColor( newColor );
  1644. FOR_EACH_LL( m_MenuItems, i )
  1645. {
  1646. if( m_MenuItems[i]->HasMenu() )
  1647. {
  1648. m_MenuItems[i]->GetMenu()->SetFgColor( newColor );
  1649. }
  1650. }
  1651. }
  1652. //-----------------------------------------------------------------------------
  1653. // Purpose:
  1654. //-----------------------------------------------------------------------------
  1655. void Menu::SetBorder(class IBorder *border)
  1656. {
  1657. Panel::SetBorder(border);
  1658. }
  1659. //-----------------------------------------------------------------------------
  1660. // Purpose: returns a pointer to a MenuItem that is this menus parent, if it has one
  1661. //-----------------------------------------------------------------------------
  1662. MenuItem *Menu::GetParentMenuItem()
  1663. {
  1664. return dynamic_cast<MenuItem *>(GetParent());
  1665. }
  1666. //-----------------------------------------------------------------------------
  1667. // Purpose: Hide the menu when an item has been selected
  1668. //-----------------------------------------------------------------------------
  1669. void Menu::OnMenuItemSelected(Panel *panel)
  1670. {
  1671. SetVisible(false);
  1672. m_pScroller->SetVisible(false);
  1673. // chain this message up through the hierarchy so
  1674. // all the parent menus will close
  1675. // get the parent of this menu.
  1676. MenuItem *item = GetParentMenuItem();
  1677. // if the parent is a menu item, this menu is a cascading menu
  1678. if (item)
  1679. {
  1680. // get the parent of the menuitem. it should be a menu.
  1681. Menu *parentMenu = item->GetParentMenu();
  1682. if (parentMenu)
  1683. {
  1684. // send the message to this parent menu
  1685. KeyValues *kv = new KeyValues("MenuItemSelected");
  1686. kv->SetPtr("panel", panel);
  1687. ivgui()->PostMessage(parentMenu->GetVPanel(), kv, GetVPanel());
  1688. }
  1689. }
  1690. bool activeItemSet = false;
  1691. FOR_EACH_LL( m_MenuItems, i )
  1692. {
  1693. if( m_MenuItems[i] == panel )
  1694. {
  1695. activeItemSet = true;
  1696. m_iActivatedItem = i;
  1697. break;
  1698. }
  1699. }
  1700. if( !activeItemSet )
  1701. {
  1702. FOR_EACH_LL( m_MenuItems, i )
  1703. {
  1704. if(m_MenuItems[i]->HasMenu() )
  1705. {
  1706. /*
  1707. // GetActiveItem needs to return -1 or similar if it hasn't been set...
  1708. if( m_MenuItems[i]->GetActiveItem() )
  1709. {
  1710. m_iActivatedItem = m_MenuItems[i]->GetActiveItem();
  1711. }*/
  1712. }
  1713. }
  1714. }
  1715. // also pass it to the parent so they can respond if they like
  1716. if (GetVParent())
  1717. {
  1718. KeyValues *kv = new KeyValues("MenuItemSelected");
  1719. kv->SetPtr("panel", panel);
  1720. ivgui()->PostMessage(GetVParent(), kv, GetVPanel());
  1721. }
  1722. }
  1723. //-----------------------------------------------------------------------------
  1724. // Purpose:
  1725. //-----------------------------------------------------------------------------
  1726. int Menu::GetActiveItem()
  1727. {
  1728. return m_iActivatedItem;
  1729. }
  1730. //-----------------------------------------------------------------------------
  1731. // Purpose:
  1732. //-----------------------------------------------------------------------------
  1733. KeyValues *Menu::GetItemUserData(int itemID)
  1734. {
  1735. if ( m_MenuItems.IsValidIndex( itemID ) )
  1736. {
  1737. MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
  1738. // make sure its enabled since disabled items get highlighted.
  1739. if (menuItem && menuItem->IsEnabled())
  1740. {
  1741. return menuItem->GetUserData();
  1742. }
  1743. }
  1744. return NULL;
  1745. }
  1746. //-----------------------------------------------------------------------------
  1747. // Purpose: data accessor
  1748. //-----------------------------------------------------------------------------
  1749. void Menu::GetItemText(int itemID, wchar_t *text, int bufLenInBytes)
  1750. {
  1751. if ( m_MenuItems.IsValidIndex( itemID ) )
  1752. {
  1753. MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
  1754. if (menuItem)
  1755. {
  1756. menuItem->GetText(text, bufLenInBytes);
  1757. return;
  1758. }
  1759. }
  1760. text[0] = 0;
  1761. }
  1762. void Menu::GetItemText(int itemID, char *text, int bufLenInBytes)
  1763. {
  1764. if ( m_MenuItems.IsValidIndex( itemID ) )
  1765. {
  1766. MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
  1767. if (menuItem)
  1768. {
  1769. menuItem->GetText( text, bufLenInBytes );
  1770. return;
  1771. }
  1772. }
  1773. text[0] = 0;
  1774. }
  1775. //-----------------------------------------------------------------------------
  1776. // Purpose: Activate the n'th item in the menu list, as if that menu item had been selected by the user
  1777. //-----------------------------------------------------------------------------
  1778. void Menu::ActivateItem(int itemID)
  1779. {
  1780. if ( m_MenuItems.IsValidIndex( itemID ) )
  1781. {
  1782. MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
  1783. // make sure its enabled since disabled items get highlighted.
  1784. if (menuItem && menuItem->IsEnabled())
  1785. {
  1786. menuItem->FireActionSignal();
  1787. m_iActivatedItem = itemID;
  1788. }
  1789. }
  1790. }
  1791. //-----------------------------------------------------------------------------
  1792. // Purpose:
  1793. //-----------------------------------------------------------------------------
  1794. void Menu::ActivateItemByRow(int row)
  1795. {
  1796. if (m_SortedItems.IsValidIndex(row))
  1797. {
  1798. ActivateItem(m_SortedItems[row]);
  1799. }
  1800. }
  1801. //-----------------------------------------------------------------------------
  1802. // Purpose: Return the number of items currently in the menu list
  1803. //-----------------------------------------------------------------------------
  1804. int Menu::GetItemCount()
  1805. {
  1806. return m_MenuItems.Count();
  1807. }
  1808. //-----------------------------------------------------------------------------
  1809. // Purpose:
  1810. //-----------------------------------------------------------------------------
  1811. int Menu::GetMenuID(int index)
  1812. {
  1813. if ( !m_SortedItems.IsValidIndex(index) )
  1814. return m_MenuItems.InvalidIndex();
  1815. return m_SortedItems[index];
  1816. }
  1817. //-----------------------------------------------------------------------------
  1818. // Purpose: Return the number of items currently visible in the menu list
  1819. //-----------------------------------------------------------------------------
  1820. int Menu::GetCurrentlyVisibleItemsCount()
  1821. {
  1822. if (m_MenuItems.Count() < m_iNumVisibleLines)
  1823. {
  1824. int cMenuItems = 0;
  1825. FOR_EACH_LL(m_MenuItems, i)
  1826. {
  1827. if (m_MenuItems[i]->IsVisible())
  1828. {
  1829. ++cMenuItems;
  1830. }
  1831. }
  1832. return cMenuItems;
  1833. }
  1834. return m_iNumVisibleLines;
  1835. }
  1836. //-----------------------------------------------------------------------------
  1837. // Purpose: Enables/disables choices in the list
  1838. // itemText - string name of item in the list
  1839. // state - true enables, false disables
  1840. //-----------------------------------------------------------------------------
  1841. void Menu::SetItemEnabled(const char *itemName, bool state)
  1842. {
  1843. FOR_EACH_LL( m_MenuItems, i )
  1844. {
  1845. if ((Q_stricmp(itemName, m_MenuItems[i]->GetName())) == 0)
  1846. {
  1847. m_MenuItems[i]->SetEnabled(state);
  1848. }
  1849. }
  1850. }
  1851. //-----------------------------------------------------------------------------
  1852. // Purpose: Enables/disables choices in the list
  1853. //-----------------------------------------------------------------------------
  1854. void Menu::SetItemEnabled(int itemID, bool state)
  1855. {
  1856. if ( !m_MenuItems.IsValidIndex(itemID) )
  1857. return;
  1858. m_MenuItems[itemID]->SetEnabled(state);
  1859. }
  1860. //-----------------------------------------------------------------------------
  1861. // Purpose: shows/hides choices in the list
  1862. //-----------------------------------------------------------------------------
  1863. void Menu::SetItemVisible(const char *itemName, bool state)
  1864. {
  1865. FOR_EACH_LL( m_MenuItems, i )
  1866. {
  1867. if ((Q_stricmp(itemName, m_MenuItems[i]->GetName())) == 0)
  1868. {
  1869. m_MenuItems[i]->SetVisible(state);
  1870. InvalidateLayout();
  1871. }
  1872. }
  1873. }
  1874. //-----------------------------------------------------------------------------
  1875. // Purpose: shows/hides choices in the list
  1876. //-----------------------------------------------------------------------------
  1877. void Menu::SetItemVisible(int itemID, bool state)
  1878. {
  1879. if ( !m_MenuItems.IsValidIndex(itemID) )
  1880. return;
  1881. m_MenuItems[itemID]->SetVisible(state);
  1882. }
  1883. //-----------------------------------------------------------------------------
  1884. // Purpose: Make the scroll bar visible and narrow the menu
  1885. // also make items visible or invisible in the list as appropriate
  1886. //-----------------------------------------------------------------------------
  1887. void Menu::AddScrollBar()
  1888. {
  1889. m_pScroller->SetVisible(true);
  1890. _sizedForScrollBar = true;
  1891. }
  1892. //-----------------------------------------------------------------------------
  1893. // Purpose: Make the scroll bar invisible and widen the menu
  1894. //-----------------------------------------------------------------------------
  1895. void Menu::RemoveScrollBar()
  1896. {
  1897. m_pScroller->SetVisible(false);
  1898. _sizedForScrollBar = false;
  1899. }
  1900. //-----------------------------------------------------------------------------
  1901. // Purpose: Invalidate layout if the slider is moved so items scroll
  1902. //-----------------------------------------------------------------------------
  1903. void Menu::OnSliderMoved()
  1904. {
  1905. CloseOtherMenus(NULL); // close any cascading menus
  1906. // Invalidate so we redraw the menu!
  1907. InvalidateLayout();
  1908. Repaint();
  1909. }
  1910. //-----------------------------------------------------------------------------
  1911. // Purpose: Toggle into mouse mode.
  1912. //-----------------------------------------------------------------------------
  1913. void Menu::OnCursorMoved(int x, int y)
  1914. {
  1915. m_iInputMode = MOUSE;
  1916. // chain up
  1917. CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y));
  1918. RequestFocus();
  1919. InvalidateLayout();
  1920. }
  1921. //-----------------------------------------------------------------------------
  1922. // Purpose: Toggle into keyboard mode.
  1923. //-----------------------------------------------------------------------------
  1924. void Menu::OnKeyCodePressed(KeyCode code)
  1925. {
  1926. m_iInputMode = KEYBOARD;
  1927. // send the message to this parent in case this is a cascading menu
  1928. if (GetVParent())
  1929. {
  1930. ivgui()->PostMessage(GetVParent(), new KeyValues("KeyModeSet"), GetVPanel());
  1931. }
  1932. }
  1933. //-----------------------------------------------------------------------------
  1934. // Purpose: Sets the item currently highlighted in the menu by ptr
  1935. //-----------------------------------------------------------------------------
  1936. void Menu::SetCurrentlySelectedItem(MenuItem *item)
  1937. {
  1938. int itemNum = -1;
  1939. // find it in our list of menuitems
  1940. FOR_EACH_LL( m_MenuItems, i )
  1941. {
  1942. MenuItem *child = m_MenuItems[i];
  1943. if (child == item)
  1944. {
  1945. itemNum = i;
  1946. break;
  1947. }
  1948. }
  1949. Assert( itemNum >= 0 );
  1950. SetCurrentlySelectedItem(itemNum);
  1951. }
  1952. //-----------------------------------------------------------------------------
  1953. // Purpose:
  1954. //-----------------------------------------------------------------------------
  1955. void Menu::ClearCurrentlyHighlightedItem()
  1956. {
  1957. if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
  1958. {
  1959. m_MenuItems[m_iCurrentlySelectedItemID]->DisarmItem();
  1960. }
  1961. m_iCurrentlySelectedItemID = m_MenuItems.InvalidIndex();
  1962. }
  1963. //-----------------------------------------------------------------------------
  1964. // Purpose: Sets the item currently highlighted in the menu by index
  1965. //-----------------------------------------------------------------------------
  1966. void Menu::SetCurrentlySelectedItem(int itemID)
  1967. {
  1968. // dont deselect if its the same item
  1969. if (itemID == m_iCurrentlySelectedItemID)
  1970. return;
  1971. if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
  1972. {
  1973. m_MenuItems[m_iCurrentlySelectedItemID]->DisarmItem();
  1974. }
  1975. PostActionSignal(new KeyValues("MenuItemHighlight", "itemID", itemID));
  1976. m_iCurrentlySelectedItemID = itemID;
  1977. }
  1978. //-----------------------------------------------------------------------------
  1979. // This will set the item to be currenly selected and highlight it
  1980. // will not open cascading menu. This was added for comboboxes
  1981. // to have the combobox item highlighted in the menu when they open the
  1982. // dropdown.
  1983. //-----------------------------------------------------------------------------
  1984. void Menu::SetCurrentlyHighlightedItem(int itemID)
  1985. {
  1986. SetCurrentlySelectedItem(itemID);
  1987. int row = m_SortedItems.Find(itemID);
  1988. Assert(row != -1);
  1989. if ( row == -1 )
  1990. return;
  1991. // if there is a scroll bar, and we scroll off lets move it.
  1992. if ( m_pScroller->IsVisible() )
  1993. {
  1994. // now if we are off the scroll bar, it means we moved the scroll bar
  1995. // by hand or set the item off the list
  1996. // so just snap the scroll bar straight to the item.
  1997. if ( ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1 ) ||
  1998. ( row < m_pScroller->GetValue() ) )
  1999. {
  2000. if ( !m_pScroller->IsVisible() )
  2001. return;
  2002. m_pScroller->SetValue(row);
  2003. }
  2004. }
  2005. if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
  2006. {
  2007. if ( !m_MenuItems[m_iCurrentlySelectedItemID]->IsArmed() )
  2008. {
  2009. m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
  2010. }
  2011. }
  2012. }
  2013. //-----------------------------------------------------------------------------
  2014. // Purpose:
  2015. //-----------------------------------------------------------------------------
  2016. int Menu::GetCurrentlyHighlightedItem()
  2017. {
  2018. return m_iCurrentlySelectedItemID;
  2019. }
  2020. //-----------------------------------------------------------------------------
  2021. // Purpose: Respond to cursor entering a menuItem.
  2022. //-----------------------------------------------------------------------------
  2023. void Menu::OnCursorEnteredMenuItem(vgui::Panel* VPanel)
  2024. {
  2025. VPANEL menuItem = (VPANEL)VPanel;
  2026. // if we are in mouse mode
  2027. if (m_iInputMode == MOUSE)
  2028. {
  2029. MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName()));
  2030. // arm the menu
  2031. item->ArmItem();
  2032. // open the cascading menu if there is one.
  2033. item->OpenCascadeMenu();
  2034. SetCurrentlySelectedItem(item);
  2035. }
  2036. }
  2037. //-----------------------------------------------------------------------------
  2038. // Purpose: Respond to cursor exiting a menuItem
  2039. //-----------------------------------------------------------------------------
  2040. void Menu::OnCursorExitedMenuItem(vgui::Panel* VPanel)
  2041. {
  2042. VPANEL menuItem = (VPANEL)VPanel;
  2043. // only care if we are in mouse mode
  2044. if (m_iInputMode == MOUSE)
  2045. {
  2046. MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName()));
  2047. // unhighlight the item.
  2048. // note menuItems with cascading menus will stay lit.
  2049. item->DisarmItem();
  2050. }
  2051. }
  2052. //-----------------------------------------------------------------------------
  2053. // Purpose: Move up or down one in the list of items in the menu
  2054. // Direction is MENU_UP or MENU_DOWN
  2055. //-----------------------------------------------------------------------------
  2056. void Menu::MoveAlongMenuItemList(int direction, int loopCount)
  2057. {
  2058. // Early out if no menu items to scroll through
  2059. if (m_MenuItems.Count() <= 0)
  2060. return;
  2061. int itemID = m_iCurrentlySelectedItemID;
  2062. int row = m_SortedItems.Find(itemID);
  2063. row += direction;
  2064. if ( row > m_SortedItems.Count() - 1 )
  2065. {
  2066. if ( m_pScroller->IsVisible() )
  2067. {
  2068. // stop at bottom of scrolled list
  2069. row = m_SortedItems.Count() - 1;
  2070. }
  2071. else
  2072. {
  2073. // if no scroll bar we circle around
  2074. row = 0;
  2075. }
  2076. }
  2077. else if (row < 0)
  2078. {
  2079. if ( m_pScroller->IsVisible() )
  2080. {
  2081. // stop at top of scrolled list
  2082. row = m_pScroller->GetValue();
  2083. }
  2084. else
  2085. {
  2086. // if no scroll bar circle around
  2087. row = m_SortedItems.Count()-1;
  2088. }
  2089. }
  2090. // if there is a scroll bar, and we scroll off lets move it.
  2091. if ( m_pScroller->IsVisible() )
  2092. {
  2093. if ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1)
  2094. {
  2095. int val = m_pScroller->GetValue();
  2096. val -= -direction;
  2097. m_pScroller->SetValue(val);
  2098. // moving the slider redraws the scrollbar,
  2099. // and so we should redraw the menu since the
  2100. // menu draws the black border to the right of the scrollbar.
  2101. InvalidateLayout();
  2102. }
  2103. else if ( row < m_pScroller->GetValue() )
  2104. {
  2105. int val = m_pScroller->GetValue();
  2106. val -= -direction;
  2107. m_pScroller->SetValue(val);
  2108. // moving the slider redraws the scrollbar,
  2109. // and so we should redraw the menu since the
  2110. // menu draws the black border to the right of the scrollbar.
  2111. InvalidateLayout();
  2112. }
  2113. // now if we are still off the scroll bar, it means we moved the scroll bar
  2114. // by hand and created a situation in which we moved an item down, but the
  2115. // scroll bar is already too far down and should scroll up or vice versa
  2116. // so just snap the scroll bar straight to the item.
  2117. if ( ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1) ||
  2118. ( row < m_pScroller->GetValue() ) )
  2119. {
  2120. m_pScroller->SetValue(row);
  2121. }
  2122. }
  2123. // switch it back to an itemID from row
  2124. if ( m_SortedItems.IsValidIndex( row ) )
  2125. {
  2126. SetCurrentlySelectedItem( m_SortedItems[row] );
  2127. }
  2128. // don't allow us to loop around more than once
  2129. if (loopCount < m_MenuItems.Count())
  2130. {
  2131. // see if the text is empty, if so skip
  2132. wchar_t text[256];
  2133. m_MenuItems[m_iCurrentlySelectedItemID]->GetText(text, 255);
  2134. if (text[0] == 0 || !m_MenuItems[m_iCurrentlySelectedItemID]->IsVisible())
  2135. {
  2136. // menu item is empty, keep moving along
  2137. MoveAlongMenuItemList(direction, loopCount + 1);
  2138. }
  2139. }
  2140. }
  2141. //-----------------------------------------------------------------------------
  2142. // Purpose: Return which type of events the menu is currently interested in
  2143. // MenuItems need to know because behaviour is different depending on mode.
  2144. //-----------------------------------------------------------------------------
  2145. int Menu::GetMenuMode()
  2146. {
  2147. return m_iInputMode;
  2148. }
  2149. //-----------------------------------------------------------------------------
  2150. // Purpose: Set the menu to key mode if a child menu goes into keymode
  2151. // This mode change has to be chained up through the menu heirarchy
  2152. // so cascading menus will work when you do a bunch of stuff in keymode
  2153. // in high level menus and then switch to keymode in lower level menus.
  2154. //-----------------------------------------------------------------------------
  2155. void Menu::OnKeyModeSet()
  2156. {
  2157. m_iInputMode = KEYBOARD;
  2158. }
  2159. //-----------------------------------------------------------------------------
  2160. // Purpose: Set the checked state of a menuItem
  2161. //-----------------------------------------------------------------------------
  2162. void Menu::SetMenuItemChecked(int itemID, bool state)
  2163. {
  2164. m_MenuItems[itemID]->SetChecked(state);
  2165. }
  2166. //-----------------------------------------------------------------------------
  2167. // Purpose: Check if item is checked.
  2168. //-----------------------------------------------------------------------------
  2169. bool Menu::IsChecked(int itemID)
  2170. {
  2171. return m_MenuItems[itemID]->IsChecked();
  2172. }
  2173. //-----------------------------------------------------------------------------
  2174. // Purpose: Set the minmum width the menu has to be. This
  2175. // is useful if you have a menu that is sized to the largest item in it
  2176. // but you don't want the menu to be thinner than the menu button
  2177. //-----------------------------------------------------------------------------
  2178. void Menu::SetMinimumWidth(int width)
  2179. {
  2180. m_iMinimumWidth = width;
  2181. }
  2182. //-----------------------------------------------------------------------------
  2183. // Purpose: Get the minmum width the menu
  2184. //-----------------------------------------------------------------------------
  2185. int Menu::GetMinimumWidth()
  2186. {
  2187. return m_iMinimumWidth;
  2188. }
  2189. //-----------------------------------------------------------------------------
  2190. // Purpose:
  2191. // Input : -
  2192. //-----------------------------------------------------------------------------
  2193. void Menu::AddSeparator()
  2194. {
  2195. int lastID = m_MenuItems.Count() - 1;
  2196. m_Separators.AddToTail( lastID );
  2197. m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) );
  2198. }
  2199. void Menu::AddSeparatorAfterItem( int itemID )
  2200. {
  2201. Assert( m_MenuItems.IsValidIndex( itemID ) );
  2202. m_Separators.AddToTail( itemID );
  2203. m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) );
  2204. }
  2205. void Menu::MoveMenuItem( int itemID, int moveBeforeThisItemID )
  2206. {
  2207. int c = m_SortedItems.Count();
  2208. int i;
  2209. for ( i = 0; i < c; ++i )
  2210. {
  2211. if ( m_SortedItems[i] == itemID )
  2212. {
  2213. m_SortedItems.Remove( i );
  2214. break;
  2215. }
  2216. }
  2217. // Didn't find it
  2218. if ( i >= c )
  2219. {
  2220. return;
  2221. }
  2222. // Now find insert pos
  2223. c = m_SortedItems.Count();
  2224. for ( i = 0; i < c; ++i )
  2225. {
  2226. if ( m_SortedItems[i] == moveBeforeThisItemID )
  2227. {
  2228. m_SortedItems.InsertBefore( i, itemID );
  2229. break;
  2230. }
  2231. }
  2232. }
  2233. void Menu::SetFont( HFont font )
  2234. {
  2235. m_hItemFont = font;
  2236. if ( font )
  2237. {
  2238. m_iMenuItemHeight = surface()->GetFontTall( font ) + 2;
  2239. }
  2240. InvalidateLayout();
  2241. }
  2242. void Menu::SetCurrentKeyBinding( int itemID, char const *hotkey )
  2243. {
  2244. if ( m_MenuItems.IsValidIndex( itemID ) )
  2245. {
  2246. MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
  2247. menuItem->SetCurrentKeyBinding( hotkey );
  2248. }
  2249. }
  2250. //-----------------------------------------------------------------------------
  2251. // Purpose: Static method to display a context menu
  2252. // Input : *parent -
  2253. // *menu -
  2254. //-----------------------------------------------------------------------------
  2255. void Menu::PlaceContextMenu( Panel *parent, Menu *menu )
  2256. {
  2257. Assert( parent );
  2258. Assert( menu );
  2259. if ( !menu || !parent )
  2260. return;
  2261. menu->SetVisible(false);
  2262. menu->SetParent( parent );
  2263. menu->AddActionSignalTarget( parent );
  2264. // get cursor position, this is local to this text edit window
  2265. int cursorX, cursorY;
  2266. input()->GetCursorPos(cursorX, cursorY);
  2267. menu->SetVisible(true);
  2268. // relayout the menu immediately so that we know it's size
  2269. menu->InvalidateLayout(true);
  2270. int menuWide, menuTall;
  2271. menu->GetSize(menuWide, menuTall);
  2272. // work out where the cursor is and therefore the best place to put the menu
  2273. int wide, tall;
  2274. surface()->GetScreenSize(wide, tall);
  2275. if (wide - menuWide > cursorX)
  2276. {
  2277. // menu hanging right
  2278. if (tall - menuTall > cursorY)
  2279. {
  2280. // menu hanging down
  2281. menu->SetPos(cursorX, cursorY);
  2282. }
  2283. else
  2284. {
  2285. // menu hanging up
  2286. menu->SetPos(cursorX, cursorY - menuTall);
  2287. }
  2288. }
  2289. else
  2290. {
  2291. // menu hanging left
  2292. if (tall - menuTall > cursorY)
  2293. {
  2294. // menu hanging down
  2295. menu->SetPos(cursorX - menuWide, cursorY);
  2296. }
  2297. else
  2298. {
  2299. // menu hanging up
  2300. menu->SetPos(cursorX - menuWide, cursorY - menuTall);
  2301. }
  2302. }
  2303. menu->RequestFocus();
  2304. }
  2305. void Menu::SetUseFallbackFont( bool bState, HFont hFallback )
  2306. {
  2307. m_hFallbackItemFont = hFallback;
  2308. m_bUseFallbackFont = bState;
  2309. }
  2310. #ifdef DBGFLAG_VALIDATE
  2311. //-----------------------------------------------------------------------------
  2312. // Purpose: Run a global validation pass on all of our data structures and memory
  2313. // allocations.
  2314. // Input: validator - Our global validator object
  2315. // pchName - Our name (typically a member var in our container)
  2316. //-----------------------------------------------------------------------------
  2317. void Menu::Validate( CValidator &validator, char *pchName )
  2318. {
  2319. validator.Push( "vgui::Menu", this, pchName );
  2320. m_MenuItems.Validate( validator, "m_MenuItems" );
  2321. m_SortedItems.Validate( validator, "m_SortedItems" );
  2322. BaseClass::Validate( validator, "vgui::Menu" );
  2323. validator.Pop();
  2324. }
  2325. #endif // DBGFLAG_VALIDATE