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.

2751 lines
77 KiB

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