diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/vgui2/vgui_controls/Menu.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/vgui2/vgui_controls/Menu.cpp')
| -rw-r--r-- | mp/src/vgui2/vgui_controls/Menu.cpp | 5406 |
1 files changed, 2703 insertions, 2703 deletions
diff --git a/mp/src/vgui2/vgui_controls/Menu.cpp b/mp/src/vgui2/vgui_controls/Menu.cpp index ceb875a4..695d3523 100644 --- a/mp/src/vgui2/vgui_controls/Menu.cpp +++ b/mp/src/vgui2/vgui_controls/Menu.cpp @@ -1,2703 +1,2703 @@ -//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-// $NoKeywords: $
-//=============================================================================//
-
-#include "vgui_controls/pch_vgui_controls.h"
-
-// memdbgon must be the last include file in a .cpp file
-#include "tier0/memdbgon.h"
-#define MENU_SEPARATOR_HEIGHT 3
-
-using namespace vgui;
-
-//-----------------------------------------------------------------------------
-// Purpose: divider line in a menu
-//-----------------------------------------------------------------------------
-class vgui::MenuSeparator : public Panel
-{
-public:
- DECLARE_CLASS_SIMPLE( MenuSeparator, Panel );
-
- MenuSeparator( Panel *parent, char const *panelName ) :
- BaseClass( parent, panelName )
- {
- SetPaintEnabled( true );
- SetPaintBackgroundEnabled( true );
- SetPaintBorderEnabled( false );
- }
-
- virtual void Paint()
- {
- int w, h;
- GetSize( w, h );
-
- surface()->DrawSetColor( GetFgColor() );
- surface()->DrawFilledRect( 4, 1, w-1, 2 );
- }
-
- virtual void ApplySchemeSettings( IScheme *pScheme )
- {
- BaseClass::ApplySchemeSettings( pScheme );
-
- SetFgColor( pScheme->GetColor( "Menu.SeparatorColor", Color( 142, 142, 142, 255 ) ) );
- SetBgColor( pScheme->GetColor( "Menu.BgColor", Color( 0, 0, 0, 255 ) ) );
- }
-};
-
-DECLARE_BUILD_FACTORY( Menu );
-
-//-----------------------------------------------------------------------------
-// Purpose: Constructor
-//-----------------------------------------------------------------------------
-Menu::Menu(Panel *parent, const char *panelName) : Panel(parent, panelName)
-{
- m_Alignment = Label::a_west;
- m_iFixedWidth = 0;
- m_iMinimumWidth = 0;
- m_iNumVisibleLines = -1; // No limit
- m_iCurrentlySelectedItemID = m_MenuItems.InvalidIndex();
- m_pScroller = new ScrollBar(this, "MenuScrollBar", true);
- m_pScroller->SetVisible(false);
- m_pScroller->AddActionSignalTarget(this);
- _sizedForScrollBar = false;
- SetZPos(1);
- SetVisible(false);
- MakePopup(false);
- SetParent(parent);
- _recalculateWidth = true;
- m_bUseMenuManager = true;
- m_iInputMode = MOUSE;
- m_iCheckImageWidth = 0;
- m_iActivatedItem = 0;
-
- m_bUseFallbackFont = false;
- m_hFallbackItemFont = INVALID_FONT;
-
- if (IsProportional())
- {
- m_iMenuItemHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), DEFAULT_MENU_ITEM_HEIGHT );
- }
- else
- {
- m_iMenuItemHeight = DEFAULT_MENU_ITEM_HEIGHT;
- }
- m_hItemFont = INVALID_FONT;
-
-
- m_eTypeAheadMode = COMPAT_MODE;
- m_szTypeAheadBuf[0] = '\0';
- m_iNumTypeAheadChars = 0;
- m_fLastTypeAheadTime = 0.0f;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Destructor
-//-----------------------------------------------------------------------------
-Menu::~Menu()
-{
- delete m_pScroller;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Remove all menu items from the menu.
-//-----------------------------------------------------------------------------
-void Menu::DeleteAllItems()
-{
- FOR_EACH_LL( m_MenuItems, i )
- {
- m_MenuItems[i]->MarkForDeletion();
- }
-
- m_MenuItems.RemoveAll();
- m_SortedItems.RemoveAll();
- m_VisibleSortedItems.RemoveAll();
- m_Separators.RemoveAll();
- int c = m_SeparatorPanels.Count();
- for ( int i = 0 ; i < c; ++i )
- {
- m_SeparatorPanels[ i ]->MarkForDeletion();
- }
- m_SeparatorPanels.RemoveAll();
- InvalidateLayout();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a menu item to the menu.
-//-----------------------------------------------------------------------------
-int Menu::AddMenuItem( MenuItem *panel )
-{
- panel->SetParent( this );
- MEM_ALLOC_CREDIT();
- int itemID = m_MenuItems.AddToTail( panel );
- m_SortedItems.AddToTail(itemID);
- InvalidateLayout(false);
- _recalculateWidth = true;
- panel->SetContentAlignment( m_Alignment );
- if ( INVALID_FONT != m_hItemFont )
- {
- panel->SetFont( m_hItemFont );
- }
- if ( m_bUseFallbackFont && INVALID_FONT != m_hFallbackItemFont )
- {
- Label *l = panel;
- TextImage *ti = l->GetTextImage();
- if ( ti )
- {
- ti->SetUseFallbackFont( m_bUseFallbackFont, m_hFallbackItemFont );
- }
- }
-
- if ( panel->GetHotKey() )
- {
- SetTypeAheadMode( HOT_KEY_MODE );
- }
-
- return itemID;
-}
-
-
-//-----------------------------------------------------------------------------
-// Remove a single item
-//-----------------------------------------------------------------------------
-void Menu::DeleteItem( int itemID )
-{
- // FIXME: This doesn't work with separator panels yet
- Assert( m_SeparatorPanels.Count() == 0 );
-
- m_MenuItems[itemID]->MarkForDeletion();
- m_MenuItems.Remove( itemID );
-
- m_SortedItems.FindAndRemove( itemID );
- m_VisibleSortedItems.FindAndRemove( itemID );
-
- InvalidateLayout(false);
- _recalculateWidth = true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a menu item to the menu.
-// Input : *item - MenuItem
-// *command - Command text to be sent when menu item is selected
-// *target - Target panel of the command
-// *userData - any user data associated with this menu item
-// Output: itemID - ID of this item
-//-----------------------------------------------------------------------------
-int Menu::AddMenuItemCharCommand(MenuItem *item, const char *command, Panel *target, const KeyValues *userData)
-{
- item->SetCommand(command);
- item->AddActionSignalTarget( target );
- item->SetUserData(userData);
- return AddMenuItem( item );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a menu item to the menu.
-// Input : *itemName - Name of item
-// *itemText - Name of item text that will appear in the manu.
-// *message - pointer to the message to send when the item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-// Output: itemID - ID of this item
-//-----------------------------------------------------------------------------
-int Menu::AddMenuItemKeyValuesCommand( MenuItem *item, KeyValues *message, Panel *target, const KeyValues *userData )
-{
- item->SetCommand(message);
- item->AddActionSignalTarget(target);
- item->SetUserData(userData);
- return AddMenuItem(item);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a menu item to the menu.
-// Input : *itemName - Name of item
-// *itemText - Name of item text that will appear in the manu.
-// *command - Command text to be sent when menu item is selected
-// *target - Target panel of the command
-// Output: itemID - ID of this item
-//-----------------------------------------------------------------------------
-int Menu::AddMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, itemText );
- return AddMenuItemCharCommand(item, command, target, userData);
-}
-
-int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, wszItemText );
- return AddMenuItemCharCommand(item, command, target, userData);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a menu item to the menu.
-// Input : *itemText - Name of item text that will appear in the manu.
-// This will also be used as the name of the menu item panel.
-// *command - Command text to be sent when menu item is selected
-// *target - Target panel of the command
-// Output: itemID - ID of this item
-//-----------------------------------------------------------------------------
-int Menu::AddMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData )
-{
- return AddMenuItem(itemText, itemText, command, target, userData ) ;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a menu item to the menu.
-// Input : *itemName - Name of item
-// *itemText - Name of item text that will appear in the manu.
-// *message - pointer to the message to send when the item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, itemText );
- return AddMenuItemKeyValuesCommand(item, message, target, userData);
-}
-
-int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, wszItemText );
- return AddMenuItemKeyValuesCommand(item, message, target, userData);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a menu item to the menu.
-// Input : *itemText - Name of item text that will appear in the manu.
-// This will also be used as the name of the menu item panel.
-// *message - pointer to the message to send when the item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData )
-{
- return AddMenuItem(itemText, itemText, message, target, userData );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a menu item to the menu.
-// Input : *itemText - Name of item text that will appear in the manu.
-// This will also be the text of the command sent when the
-// item is selected.
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddMenuItem( const char *itemText, Panel *target , const KeyValues *userData )
-{
- return AddMenuItem(itemText, itemText, target, userData );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a checkable menu item to the menu.
-// Input : *itemName - Name of item
-// *itemText - Name of item text that will appear in the manu.
-// *command - Command text to be sent when menu item is selected
-// *target - Target panel of the command
-//-----------------------------------------------------------------------------
-int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true);
- return AddMenuItemCharCommand(item, command, target, userData);
-}
-
-int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true);
- return AddMenuItemCharCommand(item, command, target, userData);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a checkable menu item to the menu.
-// Input : *itemText - Name of item text that will appear in the manu.
-// This will also be used as the name of the menu item panel.
-// *command - Command text to be sent when menu item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddCheckableMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData )
-{
- return AddCheckableMenuItem(itemText, itemText, command, target, userData );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a checkable menu item to the menu.
-// Input : *itemName - Name of item
-// *itemText - Name of item text that will appear in the manu.
-// *message - pointer to the message to send when the item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true);
- return AddMenuItemKeyValuesCommand(item, message, target, userData);
-}
-
-int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true);
- return AddMenuItemKeyValuesCommand(item, message, target, userData);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a checkable menu item to the menu.
-// Input : *itemText - Name of item text that will appear in the manu.
-// This will also be used as the name of the menu item panel.
-// *message - pointer to the message to send when the item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddCheckableMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData )
-{
- return AddCheckableMenuItem(itemText, itemText, message, target, userData );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a checkable menu item to the menu.
-// Input : *itemText - Name of item text that will appear in the manu.
-// This will also be the text of the command sent when the
-// item is selected.
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddCheckableMenuItem( const char *itemText, Panel *target, const KeyValues *userData )
-{
- return AddCheckableMenuItem(itemText, itemText, target, userData );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a Cascading menu item to the menu.
-// Input : *itemName - Name of item
-// *itemText - Name of item text that will appear in the manu.
-// *command - Command text to be sent when menu item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, itemText, cascadeMenu );
- return AddMenuItemCharCommand(item, command, target, userData);
-}
-
-int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData )
-{
- MenuItem *item = new MenuItem(this, itemName, wszItemText, cascadeMenu );
- return AddMenuItemCharCommand(item, command, target, userData);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a Cascading menu item to the menu.
-// Input : *itemText - Name of item text that will appear in the manu.
-// This will also be used as the name of the menu item panel.
-// *command - Command text to be sent when menu item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddCascadingMenuItem( const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData )
-{
- return AddCascadingMenuItem( itemText, itemText, command, target, cascadeMenu, userData );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a Cascading menu item to the menu.
-// Input : *itemName - Name of item
-// *itemText - Name of item text that will appear in the manu.
-// *message - pointer to the message to send when the item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem( this, itemName, itemText, cascadeMenu);
- return AddMenuItemKeyValuesCommand(item, message, target, userData);
-}
-
-int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData )
-{
- MenuItem *item = new MenuItem( this, itemName, wszItemText, cascadeMenu);
- return AddMenuItemKeyValuesCommand(item, message, target, userData);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a Cascading menu item to the menu.
-// Input : *itemText - Name of item text that will appear in the manu.
-// This will also be used as the name of the menu item panel.
-// *message - pointer to the message to send when the item is selected
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddCascadingMenuItem( const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData )
-{
- return AddCascadingMenuItem(itemText, itemText, message, target, cascadeMenu, userData );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Add a Cascading menu item to the menu.
-// Input : *itemText - Name of item text that will appear in the manu.
-// This will also be the text of the command sent when the
-// item is selected.
-// *target - Target panel of the command
-// *cascadeMenu - if the menu item opens a cascading menu, this is a
-// ptr to the menu that opens on selecting the item
-//-----------------------------------------------------------------------------
-int Menu::AddCascadingMenuItem( const char *itemText, Panel *target, Menu *cascadeMenu, const KeyValues *userData )
-{
- return AddCascadingMenuItem(itemText, itemText, target, cascadeMenu, userData);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets the values of a menu item at the specified index
-// Input : index - the index of this item entry
-// *message - pointer to the message to send when the item is selected
-//-----------------------------------------------------------------------------
-void Menu::UpdateMenuItem(int itemID, const char *itemText, KeyValues *message, const KeyValues *userData)
-{
- Assert( m_MenuItems.IsValidIndex(itemID) );
- if ( m_MenuItems.IsValidIndex(itemID) )
- {
- MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
- // make sure its enabled since disabled items get highlighted.
- if (menuItem)
- {
- menuItem->SetText(itemText);
- menuItem->SetCommand(message);
- if(userData)
- {
- menuItem->SetUserData(userData);
- }
- }
- }
- _recalculateWidth = true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets the values of a menu item at the specified index
-//-----------------------------------------------------------------------------
-void Menu::UpdateMenuItem(int itemID, const wchar_t *wszItemText, KeyValues *message, const KeyValues *userData)
-{
- Assert( m_MenuItems.IsValidIndex(itemID) );
- if ( m_MenuItems.IsValidIndex(itemID) )
- {
- MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
- // make sure its enabled since disabled items get highlighted.
- if (menuItem)
- {
- menuItem->SetText(wszItemText);
- menuItem->SetCommand(message);
- if(userData)
- {
- menuItem->SetUserData(userData);
- }
- }
- }
- _recalculateWidth = true;
-}
-
-
-//-----------------------------------------------------------------------------
-// Sets the content alignment of all items in the menu
-//-----------------------------------------------------------------------------
-void Menu::SetContentAlignment( Label::Alignment alignment )
-{
- if ( m_Alignment != alignment )
- {
- m_Alignment = alignment;
-
- // Change the alignment of existing menu items
- int nCount = m_MenuItems.Count();
- for ( int i = 0; i < nCount; ++i )
- {
- m_MenuItems[i]->SetContentAlignment( alignment );
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Locks down a specific width
-//-----------------------------------------------------------------------------
-void Menu::SetFixedWidth(int width)
-{
- // the padding makes it so the menu has the label padding on each side of the menu.
- // makes the menu items look centered.
- m_iFixedWidth = width;
- InvalidateLayout(false);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets the height of each menu item
-//-----------------------------------------------------------------------------
-void Menu::SetMenuItemHeight(int itemHeight)
-{
- m_iMenuItemHeight = itemHeight;
-}
-
-int Menu::GetMenuItemHeight() const
-{
- return m_iMenuItemHeight;
-}
-
-int Menu::CountVisibleItems()
-{
- int count = 0;
- int c = m_SortedItems.Count();
- for ( int i = 0 ; i < c; ++i )
- {
- if ( m_MenuItems[ m_SortedItems[ i ] ]->IsVisible() )
- ++count;
- }
- return count;
-}
-
-void Menu::ComputeWorkspaceSize( int& workWide, int& workTall )
-{
- // make sure we factor in insets
- int ileft, iright, itop, ibottom;
- GetInset(ileft, iright, itop, ibottom);
-
- int workX, workY;
- surface()->GetWorkspaceBounds(workX, workY, workWide, workTall);
- workTall -= 20;
- workTall -= itop;
- workTall -= ibottom;
-}
-
-// Assumes relative coords in screenspace
-void Menu::PositionRelativeToPanel( Panel *relative, MenuDirection_e direction, int nAdditionalYOffset /*=0*/, bool showMenu /*=false*/ )
-{
- Assert( relative );
- int rx, ry, rw, rh;
- relative->GetBounds( rx, ry, rw, rh );
- relative->LocalToScreen( rx, ry );
-
- if ( direction == CURSOR )
- {
- // force the menu to appear where the mouse button was pressed
- input()->GetCursorPos(rx, ry);
- rw = rh = 0;
- }
- else if ( direction == ALIGN_WITH_PARENT && relative->GetVParent() )
- {
- rx = 0, ry = 0;
- relative->ParentLocalToScreen(rx, ry);
- rx -= 1; // take border into account
- ry += rh + nAdditionalYOffset;
- rw = rh = 0;
- }
- else
- {
- rx = 0, ry = 0;
- relative->LocalToScreen(rx, ry);
- }
-
- int workWide, workTall;
- ComputeWorkspaceSize( workWide, workTall );
-
- // Final pos
- int x = 0, y = 0;
-
- int mWide, mTall;
- GetSize( mWide, mTall );
-
- switch( direction )
- {
- case Menu::UP: // Menu prefers to open upward
- {
- x = rx;
- int topOfReference = ry;
- y = topOfReference - mTall;
- if ( y < 0 )
- {
- int bottomOfReference = ry + rh + 1;
- int remainingPixels = workTall - bottomOfReference;
-
- // Can't fit on bottom, either, move to side
- if ( mTall >= remainingPixels )
- {
- y = workTall - mTall;
- x = rx + rw;
- // Try and place it to the left of the button
- if ( x + mWide > workWide )
- {
- x = rx - mWide;
- }
- }
- else
- {
- // Room at bottom
- y = bottomOfReference;
- }
- }
- }
- break;
- // Everyone else aligns downward...
- default:
- case Menu::LEFT:
- case Menu::RIGHT:
- case Menu::DOWN:
- {
- x = rx;
- int bottomOfReference = ry + rh + 1;
- y = bottomOfReference;
- if ( bottomOfReference + mTall >= workTall )
- {
- // See if there's run straight above
- if ( mTall >= ry ) // No room, try and push menu to right or left
- {
- y = workTall - mTall;
- x = rx + rw;
- // Try and place it to the left of the button
- if ( x + mWide > workWide )
- {
- x = rx - mWide;
- }
- }
- else
- {
- // Room at top
- y = ry - mTall;
- }
- }
- }
- break;
- }
-
- // Check left rightness
- if ( x + mWide > workWide )
- {
- x = workWide - mWide;
- Assert( x >= 0 ); // yikes!!!
- }
- else if ( x < 0 )
- {
- x = 0;
- }
-
- SetPos( x, y );
- if ( showMenu )
- {
- SetVisible( true );
- }
-}
-
-int Menu::ComputeFullMenuHeightWithInsets()
-{
- // make sure we factor in insets
- int ileft, iright, itop, ibottom;
- GetInset(ileft, iright, itop, ibottom);
-
- int separatorHeight = 3;
-
- // add up the size of all the child panels
- // move the child panels to the correct place in the menu
- int totalTall = itop + ibottom;
- int i;
- for ( i = 0 ; i < m_SortedItems.Count() ; i++ ) // use sortedItems instead of MenuItems due to SetPos()
- {
- int itemId = m_SortedItems[i];
-
- MenuItem *child = m_MenuItems[ itemId ];
- Assert( child );
- if ( !child )
- continue;
- // These should all be visible at this point
- if ( !child->IsVisible() )
- continue;
-
- totalTall += m_iMenuItemHeight;
-
- // Add a separator if needed...
- int sepIndex = m_Separators.Find( itemId );
- if ( sepIndex != m_Separators.InvalidIndex() )
- {
- totalTall += separatorHeight;
- }
- }
-
- return totalTall;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Reformat according to the new layout
-//-----------------------------------------------------------------------------
-void Menu::PerformLayout()
-{
- MenuItem *parent = GetParentMenuItem();
- bool cascading = parent != NULL ? true : false;
-
- // make sure we factor in insets
- int ileft, iright, itop, ibottom;
- GetInset(ileft, iright, itop, ibottom);
-
- int workWide, workTall;
-
- ComputeWorkspaceSize( workWide, workTall );
-
- int fullHeightWouldRequire = ComputeFullMenuHeightWithInsets();
-
- bool bNeedScrollbar = fullHeightWouldRequire >= workTall;
-
- int maxVisibleItems = CountVisibleItems();
-
- if ( m_iNumVisibleLines > 0 &&
- maxVisibleItems > m_iNumVisibleLines )
- {
- bNeedScrollbar = true;
- maxVisibleItems = m_iNumVisibleLines;
- }
-
- // if we have a scroll bar
- if ( bNeedScrollbar )
- {
- // add it to the display
- AddScrollBar();
-
- // This fills in m_VisibleSortedItems as needed
- MakeItemsVisibleInScrollRange( m_iNumVisibleLines, min( fullHeightWouldRequire, workTall ) );
- }
- else
- {
- RemoveScrollBar();
- // Make everything visible
- m_VisibleSortedItems.RemoveAll();
- int i;
- int c = m_SortedItems.Count();
- for ( i = 0; i < c; ++i )
- {
- int itemID = m_SortedItems[ i ];
- MenuItem *child = m_MenuItems[ itemID ];
- if ( !child || !child->IsVisible() )
- continue;
-
- m_VisibleSortedItems.AddToTail( itemID );
- }
-
- // Hide the separators, the needed ones will be readded below
- c = m_SeparatorPanels.Count();
- for ( i = 0; i < c; ++i )
- {
- if ( m_SeparatorPanels[ i ] )
- {
- m_SeparatorPanels[ i ]->SetVisible( false );
- }
- }
- }
-
- // get the appropriate menu border
- LayoutMenuBorder();
-
- int trueW = GetWide();
- if ( bNeedScrollbar )
- {
- trueW -= m_pScroller->GetWide();
- }
- int separatorHeight = MENU_SEPARATOR_HEIGHT;
-
- // add up the size of all the child panels
- // move the child panels to the correct place in the menu
- int menuTall = 0;
- int totalTall = itop + ibottom;
- int i;
- for ( i = 0 ; i < m_VisibleSortedItems.Count() ; i++ ) // use sortedItems instead of MenuItems due to SetPos()
- {
- int itemId = m_VisibleSortedItems[i];
-
- MenuItem *child = m_MenuItems[ itemId ];
- Assert( child );
- if ( !child )
- continue;
- // These should all be visible at this point
- if ( !child->IsVisible() )
- continue;
-
- if ( totalTall >= workTall )
- break;
-
- if ( INVALID_FONT != m_hItemFont )
- {
- child->SetFont( m_hItemFont );
- }
-
- // take into account inset
- child->SetPos (0, menuTall);
- child->SetTall( m_iMenuItemHeight ); // Width is set in a second pass
- menuTall += m_iMenuItemHeight;
- totalTall += m_iMenuItemHeight;
-
- // this will make all the menuitems line up in a column with space for the checks to the left.
- if ( ( !child->IsCheckable() ) && ( m_iCheckImageWidth > 0 ) )
- {
- // Non checkable items have to move over
- child->SetTextInset( m_iCheckImageWidth, 0 );
- }
- else if ( child->IsCheckable() )
- {
- child->SetTextInset(0, 0); //TODO: for some reason I can't comment this out.
- }
-
- // Add a separator if needed...
- int sepIndex = m_Separators.Find( itemId );
- if ( sepIndex != m_Separators.InvalidIndex() )
- {
- MenuSeparator *sep = m_SeparatorPanels[ sepIndex ];
- Assert( sep );
- sep->SetVisible( true );
- sep->SetBounds( 0, menuTall, trueW, separatorHeight );
- menuTall += separatorHeight;
- totalTall += separatorHeight;
- }
- }
-
- if (!m_iFixedWidth)
- {
- _recalculateWidth = true;
- CalculateWidth();
- }
- else if (m_iFixedWidth)
- {
- _menuWide = m_iFixedWidth;
- // fixed width menus include the scroll bar in their width.
- if (_sizedForScrollBar)
- {
- _menuWide -= m_pScroller->GetWide();
- }
- }
-
- SizeMenuItems();
-
- int extraWidth = 0;
- if (_sizedForScrollBar)
- {
- extraWidth = m_pScroller->GetWide();
- }
-
- int mwide = _menuWide + extraWidth;
- if ( mwide > workWide )
- {
- mwide = workWide;
- }
- int mtall = menuTall + itop + ibottom;
- if ( mtall > workTall )
- {
- // Shouldn't happen
- mtall = workTall;
- }
-
- // set the new size of the menu
- SetSize( mwide, mtall );
-
- // move the menu to the correct position if it is a cascading menu.
- if ( cascading )
- {
- // move the menu to the correct position if it is a cascading menu.
- PositionCascadingMenu();
- }
-
- // set up scroll bar as appropriate
- if ( m_pScroller->IsVisible() )
- {
- LayoutScrollBar();
- }
-
- FOR_EACH_LL( m_MenuItems, j )
- {
- m_MenuItems[j]->InvalidateLayout(); // cause each menu item to redo its apply settings now we have sized ourselves
- }
-
- Repaint();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Force the menu to work out how wide it should be
-//-----------------------------------------------------------------------------
-void Menu::ForceCalculateWidth()
-{
- _recalculateWidth = true;
- CalculateWidth();
- PerformLayout();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Figure out how wide the menu should be if the menu is not fixed width
-//-----------------------------------------------------------------------------
-void Menu::CalculateWidth()
-{
- if (!_recalculateWidth)
- return;
-
- _menuWide = 0;
- if (!m_iFixedWidth)
- {
- // find the biggest menu item
- FOR_EACH_LL( m_MenuItems, i )
- {
- int wide, tall;
- m_MenuItems[i]->GetContentSize(wide, tall);
- if (wide > _menuWide - Label::Content)
- {
- _menuWide = wide + Label::Content;
- }
- }
- }
-
- // enfoce a minimumWidth
- if (_menuWide < m_iMinimumWidth)
- {
- _menuWide = m_iMinimumWidth;
- }
-
- _recalculateWidth = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set up the scroll bar attributes,size and location.
-//-----------------------------------------------------------------------------
-void Menu::LayoutScrollBar()
-{
- //!! need to make it recalculate scroll positions
- m_pScroller->SetEnabled(false);
- m_pScroller->SetRangeWindow( m_VisibleSortedItems.Count() );
- m_pScroller->SetRange( 0, CountVisibleItems() );
- m_pScroller->SetButtonPressedScrollValue( 1 );
-
- int wide, tall;
- GetSize (wide, tall);
-
- // make sure we factor in insets
- int ileft, iright, itop, ibottom;
- GetInset(ileft, iright, itop, ibottom);
-
- // with a scroll bar we take off the inset
- wide -= iright;
-
- m_pScroller->SetPos(wide - m_pScroller->GetWide(), 1);
-
- // scrollbar is inside the menu's borders.
- m_pScroller->SetSize(m_pScroller->GetWide(), tall - ibottom - itop);
-
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Figure out where to open menu if it is a cascading menu
-//-----------------------------------------------------------------------------
-void Menu::PositionCascadingMenu()
-{
- Assert(GetVParent());
- int parentX, parentY, parentWide, parentTall;
- // move the menu to the correct place below the menuItem
- ipanel()->GetSize(GetVParent(), parentWide, parentTall);
- ipanel()->GetPos(GetVParent(), parentX, parentY);
-
- parentX += parentWide, parentY = 0;
-
- ParentLocalToScreen(parentX, parentY);
-
- SetPos(parentX, parentY);
-
- // for cascading menus,
- // make sure we're on the screen
- int workX, workY, workWide, workTall, x, y, wide, tall;
- GetBounds(x, y, wide, tall);
- surface()->GetWorkspaceBounds(workX, workY, workWide, workTall);
-
- if (x + wide > workX + workWide)
- {
- // we're off the right, move the menu to the left side
- // orignalX - width of the parentmenuitem - width of this menu.
- // add 2 pixels to offset one pixel onto the parent menu.
- x -= (parentWide + wide);
- x -= 2;
- }
- else
- {
- // alignment move it in the amount of the insets.
- x += 1;
- }
-
- if ( y + tall > workY + workTall )
- {
- int lastWorkY = workY + workTall;
- int pixelsOffBottom = ( y + tall ) - lastWorkY;
-
- y -= pixelsOffBottom;
- y -= 2;
- }
- else
- {
- y -= 1;
- }
- SetPos(x, y);
-
- MoveToFront();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Size the menu items so they are the width of the menu.
-// Also size the menu items with cascading menus so the arrow fits in there.
-//-----------------------------------------------------------------------------
-void Menu::SizeMenuItems()
-{
- int ileft, iright, itop, ibottom;
- GetInset(ileft, iright, itop, ibottom);
-
- // assign the sizes of all the menu item panels
- FOR_EACH_LL( m_MenuItems, i )
- {
- MenuItem *child = m_MenuItems[i];
- if (child )
- {
- // labels do thier own sizing. this will size the label to the width of the menu,
- // this will put the cascading menu arrow on the right side automatically.
- child->SetWide(_menuWide - ileft - iright);
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Makes menu items visible in relation to where the scroll bar is
-//-----------------------------------------------------------------------------
-void Menu::MakeItemsVisibleInScrollRange( int maxVisibleItems, int nNumPixelsAvailable )
-{
- // Detach all items from tree
- int i;
- FOR_EACH_LL( m_MenuItems, item )
- {
- m_MenuItems[ item ]->SetBounds( 0, 0, 0, 0 );
- }
- for ( i = 0; i < m_SeparatorPanels.Count(); ++i )
- {
- m_SeparatorPanels[ i ]->SetVisible( false );
- }
-
- m_VisibleSortedItems.RemoveAll();
-
- int tall = 0;
-
- int startItem = m_pScroller->GetValue();
- Assert( startItem >= 0 );
- do
- {
- if ( startItem >= m_SortedItems.Count() )
- break;
-
- int itemId = m_SortedItems[ startItem ];
-
- if ( !m_MenuItems[ itemId ]->IsVisible() )
- {
- ++startItem;
- continue;
- }
-
- int itemHeight = m_iMenuItemHeight;
- int sepIndex = m_Separators.Find( itemId );
- if ( sepIndex != m_Separators.InvalidIndex() )
- {
- itemHeight += MENU_SEPARATOR_HEIGHT;
- }
-
- if ( tall + itemHeight > nNumPixelsAvailable )
- break;
-
- // Too many items
- if ( maxVisibleItems > 0 )
- {
- if ( m_VisibleSortedItems.Count() >= maxVisibleItems )
- break;
- }
-
- tall += itemHeight;
- // Re-attach this one
- m_VisibleSortedItems.AddToTail( itemId );
- ++startItem;
- }
- while ( true );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Get the approproate menu border
-//-----------------------------------------------------------------------------
-void Menu::LayoutMenuBorder()
-{
- IBorder *menuBorder;
- IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
-
- menuBorder = pScheme->GetBorder("MenuBorder");
-
- if ( menuBorder )
- {
- SetBorder(menuBorder);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Draw a black border on the right side of the menu items
-//-----------------------------------------------------------------------------
-void Menu::Paint()
-{
- if ( m_pScroller->IsVisible() )
- {
- // draw black bar
- int wide, tall;
- GetSize (wide, tall);
- surface()->DrawSetColor(_borderDark);
- if( IsProportional() )
- {
- surface()->DrawFilledRect(wide - m_pScroller->GetWide(), -1, wide - m_pScroller->GetWide() + 1, tall);
- }
- else
- {
- surface()->DrawFilledRect(wide - m_pScroller->GetWide(), -1, wide - m_pScroller->GetWide() + 1, tall);
- }
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: sets the max number of items visible (scrollbar appears with more)
-// Input : numItems -
-//-----------------------------------------------------------------------------
-void Menu::SetNumberOfVisibleItems( int numItems )
-{
- m_iNumVisibleLines = numItems;
- InvalidateLayout(false);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void Menu::EnableUseMenuManager( bool bUseMenuManager )
-{
- m_bUseMenuManager = bUseMenuManager;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-MenuItem *Menu::GetMenuItem(int itemID)
-{
- if ( !m_MenuItems.IsValidIndex(itemID) )
- return NULL;
-
- return m_MenuItems[itemID];
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool Menu::IsValidMenuID(int itemID)
-{
- return m_MenuItems.IsValidIndex(itemID);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int Menu::GetInvalidMenuID()
-{
- return m_MenuItems.InvalidIndex();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: When a menuItem is selected, close cascading menus
-// if the menuItem selected has a cascading menu attached, we
-// want to keep that one open so skip it.
-// Passing NULL will close all cascading menus.
-//-----------------------------------------------------------------------------
-void Menu::CloseOtherMenus(MenuItem *item)
-{
- FOR_EACH_LL( m_MenuItems, i )
- {
- if (m_MenuItems[i] == item)
- continue;
-
- m_MenuItems[i]->CloseCascadeMenu();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Respond to string commands.
-//-----------------------------------------------------------------------------
-void Menu::OnCommand( const char *command )
-{
- // forward on the message
- PostActionSignal(new KeyValues("Command", "command", command));
-
- Panel::OnCommand(command);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle key presses, Activate shortcuts
-//-----------------------------------------------------------------------------
-void Menu::OnKeyCodeTyped(KeyCode keycode)
-{
- vgui::KeyCode code = GetBaseButtonCode( keycode );
-
- // Don't allow key inputs when disabled!
- if ( !IsEnabled() )
- return;
-
- bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT));
- if (alt)
- {
- BaseClass::OnKeyCodeTyped( keycode );
- // Ignore alt when in combobox mode
- if (m_eTypeAheadMode != TYPE_AHEAD_MODE)
- {
- PostActionSignal(new KeyValues("MenuClose"));
- }
- }
-
- switch (code)
- {
- case KEY_ESCAPE:
- case KEY_XBUTTON_B:
- {
- // hide the menu on ESC
- SetVisible(false);
- break;
- }
- // arrow keys scroll through items on the list.
- // they should also scroll the scroll bar if needed
- case KEY_UP:
- case KEY_XBUTTON_UP:
- case KEY_XSTICK1_UP:
- {
- MoveAlongMenuItemList(MENU_UP, 0);
- if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
- {
- m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
- }
- else
- {
- BaseClass::OnKeyCodeTyped( keycode ); // chain up
- }
- break;
- }
- case KEY_DOWN:
- case KEY_XBUTTON_DOWN:
- case KEY_XSTICK1_DOWN:
- {
- MoveAlongMenuItemList(MENU_DOWN, 0);
- if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
- {
- m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
- }
- else
- {
- BaseClass::OnKeyCodeTyped( keycode ); // chain up
- }
- break;
- }
- // for now left and right arrows just open or close submenus if they are there.
- case KEY_RIGHT:
- case KEY_XBUTTON_RIGHT:
- case KEY_XSTICK1_RIGHT:
- {
- // make sure a menuItem is currently selected
- if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
- {
- if (m_MenuItems[m_iCurrentlySelectedItemID]->HasMenu())
- {
- ActivateItem(m_iCurrentlySelectedItemID);
- }
- else
- {
- BaseClass::OnKeyCodeTyped( keycode );
- }
- }
- else
- {
- BaseClass::OnKeyCodeTyped( keycode );
- }
- break;
- }
- case KEY_LEFT:
- case KEY_XBUTTON_LEFT:
- case KEY_XSTICK1_LEFT:
- {
- // if our parent is a menu item then we are a submenu so close us.
- if (GetParentMenuItem())
- {
- SetVisible(false);
- }
- else
- {
- BaseClass::OnKeyCodeTyped( keycode );
- }
- break;
- }
- case KEY_ENTER:
- case KEY_XBUTTON_A:
- {
- // make sure a menuItem is currently selected
- if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
- {
- ActivateItem(m_iCurrentlySelectedItemID);
- }
- else
- {
- BaseClass::OnKeyCodeTyped( keycode ); // chain up
- }
- break;
- }
-
- case KEY_PAGEUP:
- {
- if ( m_iNumVisibleLines > 1 )
- {
- if ( m_iCurrentlySelectedItemID < m_iNumVisibleLines )
- {
- MoveAlongMenuItemList( MENU_UP * m_iCurrentlySelectedItemID, 0 );
- }
- else
- {
- MoveAlongMenuItemList(MENU_UP * m_iNumVisibleLines - 1, 0);
- }
- }
- else
- {
- MoveAlongMenuItemList(MENU_UP, 0);
- }
-
- if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
- {
- m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
- }
- break;
- }
-
-
- case KEY_PAGEDOWN:
- {
- if ( m_iNumVisibleLines > 1 )
- {
- if ( m_iCurrentlySelectedItemID + m_iNumVisibleLines >= GetItemCount() )
- {
- MoveAlongMenuItemList(MENU_DOWN * ( GetItemCount() - m_iCurrentlySelectedItemID - 1), 0);
- }
- else
- {
- MoveAlongMenuItemList(MENU_DOWN * m_iNumVisibleLines - 1, 0);
- }
- }
- else
- {
- MoveAlongMenuItemList(MENU_DOWN, 0);
- }
-
- if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
- {
- m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
- }
- break;
- }
-
- case KEY_HOME:
- {
- MoveAlongMenuItemList( MENU_UP * m_iCurrentlySelectedItemID, 0 );
- if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
- {
- m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
- }
- break;
- }
-
-
- case KEY_END:
- {
- MoveAlongMenuItemList(MENU_DOWN * ( GetItemCount() - m_iCurrentlySelectedItemID - 1), 0);
- if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) )
- {
- m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
- }
- break;
- }
- }
-
- // don't chain back
-}
-
-void Menu::OnHotKey(wchar_t unichar)
-{
- // iterate the menu items looking for one with the matching hotkey
- FOR_EACH_LL( m_MenuItems, i )
- {
- MenuItem *panel = m_MenuItems[i];
- if (panel->IsVisible())
- {
- Panel *hot = panel->HasHotkey(unichar);
- if (hot)
- {
- // post a message to the menuitem telling it it's hotkey was pressed
- PostMessage(hot, new KeyValues("Hotkey"));
- return;
- }
- // if the menuitem is a cascading menuitem and it is open, check its hotkeys too
- Menu *cascadingMenu = panel->GetMenu();
- if (cascadingMenu && cascadingMenu->IsVisible())
- {
- cascadingMenu->OnKeyTyped(unichar);
- }
- }
- }
-}
-
-void Menu::OnTypeAhead(wchar_t unichar)
-{
- // Don't do anything if the menu is empty since there cannot be a selected item.
- if ( m_MenuItems.Count() <= 0)
- return;
-
- // expire the type ahead buffer after 0.5 seconds
- double tCurrentTime = Sys_FloatTime();
- if ( (tCurrentTime - m_fLastTypeAheadTime) > 0.5f )
- {
- m_iNumTypeAheadChars = 0;
- m_szTypeAheadBuf[0] = '\0';
- }
- m_fLastTypeAheadTime = tCurrentTime;
-
- // add current character to the type ahead buffer
- if ( m_iNumTypeAheadChars+1 < TYPEAHEAD_BUFSIZE )
- {
- m_szTypeAheadBuf[m_iNumTypeAheadChars++] = unichar;
- }
-
- int itemToSelect = m_iCurrentlySelectedItemID;
- if ( itemToSelect < 0 || itemToSelect >= m_MenuItems.Count())
- {
- itemToSelect = 0;
- }
-
- int i = itemToSelect;
- do
- {
- wchar_t menuItemName[255];
- m_MenuItems[i]->GetText(menuItemName, 254);
-
- // This is supposed to be case insensitive but we don't have a portable case
- // insensitive wide-character routine.
- if ( wcsncmp( m_szTypeAheadBuf, menuItemName, m_iNumTypeAheadChars) == 0 )
- {
- itemToSelect = i;
- break;
- }
-
- i = (i+1) % m_MenuItems.Count();
- } while ( i != itemToSelect );
-
- if ( itemToSelect >= 0 )
- {
- SetCurrentlyHighlightedItem( itemToSelect );
- InvalidateLayout();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle key presses, Activate shortcuts
-// Input : code -
-//-----------------------------------------------------------------------------
-void Menu::OnKeyTyped(wchar_t unichar)
-{
- if (! unichar)
- {
- return;
- }
-
- switch( m_eTypeAheadMode )
- {
- case HOT_KEY_MODE:
- OnHotKey(unichar);
- return;
-
- case TYPE_AHEAD_MODE:
- OnTypeAhead(unichar);
- return;
-
- case COMPAT_MODE:
- default:
- break;
- }
-
- int itemToSelect = m_iCurrentlySelectedItemID;
- if ( itemToSelect < 0 )
- {
- itemToSelect = 0;
- }
-
- int i;
- wchar_t menuItemName[255];
-
- i = itemToSelect + 1;
- if ( i >= m_MenuItems.Count() )
- {
- i = 0;
- }
-
- while ( i != itemToSelect )
- {
- m_MenuItems[i]->GetText(menuItemName, 254);
-
- if ( tolower( unichar ) == tolower( menuItemName[0] ) )
- {
- itemToSelect = i;
- break;
- }
-
- i++;
- if ( i >= m_MenuItems.Count() )
- {
- i = 0;
- }
- }
-
- if ( itemToSelect >= 0 )
- {
- SetCurrentlyHighlightedItem( itemToSelect );
- InvalidateLayout();
- }
-
- // don't chain back
-}
-
-
-void Menu::SetTypeAheadMode(MenuTypeAheadMode mode)
-{
- m_eTypeAheadMode = mode;
-}
-
-int Menu::GetTypeAheadMode()
-{
- return m_eTypeAheadMode;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle the mouse wheel event, scroll the selection
-//-----------------------------------------------------------------------------
-void Menu::OnMouseWheeled(int delta)
-{
- if (!m_pScroller->IsVisible())
- return;
-
- int val = m_pScroller->GetValue();
- val -= delta;
-
- m_pScroller->SetValue(val);
-
- // moving the slider redraws the scrollbar,
- // and so we should redraw the menu since the
- // menu draws the black border to the right of the scrollbar.
- InvalidateLayout();
-
- // don't chain back
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Lose focus, hide menu
-//-----------------------------------------------------------------------------
-void Menu::OnKillFocus()
-{
- // check to see if it's a child taking it
- if (!input()->GetFocus() || !ipanel()->HasParent(input()->GetFocus(), GetVPanel()))
- {
- // if we don't accept keyboard input, then we have to ignore the killfocus if it's not actually being stolen
- if (!IsKeyBoardInputEnabled() && !input()->GetFocus())
- return;
-
- // get the parent of this menu.
- MenuItem *item = GetParentMenuItem();
- // if the parent is a menu item, this menu is a cascading menu
- // if the panel that is getting focus is the parent menu, don't close this menu.
- if ( (item) && (input()->GetFocus() == item->GetVParent()) )
- {
- // if we are in mouse mode and we clicked on the menuitem that
- // triggers the cascading menu, leave it open.
- if (m_iInputMode == MOUSE)
- {
- // return the focus to the cascading menu.
- MoveToFront();
- return;
- }
- }
-
- // forward the message to the parent.
- PostActionSignal(new KeyValues("MenuClose"));
-
- // hide this menu
- SetVisible(false);
- }
-
-}
-
-namespace vgui
-{
-
-class CMenuManager
-{
-public:
- void AddMenu( Menu *m )
- {
- if ( !m )
- return;
-
- int c = m_Menus.Count();
- for ( int i = 0 ; i < c; ++i )
- {
- if ( m_Menus[ i ].Get() == m )
- return;
- }
-
- DHANDLE< Menu > h;
- h = m;
- m_Menus.AddToTail( h );
- }
-
- void RemoveMenu( Menu *m )
- {
- if ( !m )
- return;
-
- int c = m_Menus.Count();
- for ( int i = c - 1 ; i >= 0; --i )
- {
- if ( m_Menus[ i ].Get() == m )
- {
- m_Menus.Remove( i );
- return;
- }
- }
- }
-
- void OnInternalMousePressed( Panel *other, MouseCode code )
- {
- int c = m_Menus.Count();
- if ( !c )
- return;
-
- int x, y;
- input()->GetCursorPos( x, y );
-
- bool mouseInsideMenuRelatedPanel = false;
-
- for ( int i = c - 1; i >= 0 ; --i )
- {
- Menu *m = m_Menus[ i ].Get();
- if ( !m )
- {
- m_Menus.Remove( i );
- continue;
- }
-
- // See if the mouse is within a menu
- if ( IsWithinMenuOrRelative( m, x, y ) )
- {
- mouseInsideMenuRelatedPanel = true;
- }
- }
-
- if ( mouseInsideMenuRelatedPanel )
- {
- return;
- }
-
- AbortMenus();
- }
-
- void AbortMenus()
- {
- // Close all of the menus
- int c = m_Menus.Count();
- for ( int i = c - 1; i >= 0 ; --i )
- {
- Menu *m = m_Menus[ i ].Get();
- if ( !m )
- {
- continue;
- }
-
- m_Menus.Remove( i );
-
- // Force it to close
- m->SetVisible( false );
- }
-
- m_Menus.RemoveAll();
- }
-
- bool IsWithinMenuOrRelative( Panel *panel, int x, int y )
- {
- VPANEL topMost = panel->IsWithinTraverse( x, y, true );
- if ( topMost )
- {
- // It's over the menu
- if ( topMost == panel->GetVPanel() )
- {
- return true;
- }
-
- // It's over something which is parented to the menu (i.e., a menu item)
- if ( ipanel()->HasParent( topMost, panel->GetVPanel() ) )
- {
- return true;
- }
- }
-
- if ( panel->GetParent() )
- {
- Panel *parent = panel->GetParent();
-
- topMost = parent->IsWithinTraverse( x, y, true );
-
- if ( topMost )
- {
- if ( topMost == parent->GetVPanel() )
- {
- return true;
- }
-
- /*
- // NOTE: this check used to not cast to MenuButton, but it seems wrong to me
- // since if the mouse is over another child of the parent panel to the menu then
- // the menu stays visible. I think this is bogus.
- Panel *pTopMost = ipanel()->GetPanel(topMost, GetControlsModuleName());
-
- if ( pTopMost &&
- ipanel()->HasParent( topMost, parent->GetVPanel() ) &&
- dynamic_cast< MenuButton * >( pTopMost ) )
- {
- Msg( "topMost %s has parent %s\n",
- ipanel()->GetName( topMost ),
- parent->GetName() );
-
- return true;
- }
- */
- }
- }
-
- return false;
- }
-
-#ifdef DBGFLAG_VALIDATE
- void Validate( CValidator &validator, char *pchName )
- {
- validator.Push( "CMenuManager", this, pchName );
- m_Menus.Validate( validator, "m_Menus" );
- validator.Pop();
- }
-#endif
-
-private:
-
- // List of visible menus
- CUtlVector< DHANDLE< Menu > > m_Menus;
-};
-
-
-// Singleton helper class
-static CMenuManager g_MenuMgr;
-
-void ValidateMenuGlobals( CValidator &validator )
-{
-#ifdef DBGFLAG_VALIDATE
- g_MenuMgr.Validate( validator, "g_MenuMgr" );
-#endif
-}
-
-} // end namespace vgui
-
-//-----------------------------------------------------------------------------
-// Purpose: Static method called on mouse released to see if Menu objects should be aborted
-// Input : *other -
-// code -
-//-----------------------------------------------------------------------------
-void Menu::OnInternalMousePressed( Panel *other, MouseCode code )
-{
- g_MenuMgr.OnInternalMousePressed( other, code );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set visibility of menu and its children as appropriate.
-//-----------------------------------------------------------------------------
-void Menu::SetVisible(bool state)
-{
- if (state == IsVisible())
- return;
-
- if ( state == false )
- {
- PostActionSignal(new KeyValues("MenuClose"));
- CloseOtherMenus(NULL);
-
- SetCurrentlySelectedItem(-1);
-
- g_MenuMgr.RemoveMenu( this );
- }
- else if ( state == true )
- {
- MoveToFront();
- RequestFocus();
-
- // Add to menu manager?
- if ( m_bUseMenuManager )
- {
- g_MenuMgr.AddMenu( this );
- }
- }
-
- // must be after movetofront()
- BaseClass::SetVisible(state);
- _sizedForScrollBar = false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void Menu::ApplySchemeSettings(IScheme *pScheme)
-{
- BaseClass::ApplySchemeSettings(pScheme);
-
- SetFgColor(GetSchemeColor("Menu.TextColor", pScheme));
- SetBgColor(GetSchemeColor("Menu.BgColor", pScheme));
-
- _borderDark = pScheme->GetColor("BorderDark", Color(255, 255, 255, 0));
-
- FOR_EACH_LL( m_MenuItems, i )
- {
- if( m_MenuItems[i]->IsCheckable() )
- {
- int wide, tall;
- m_MenuItems[i]->GetCheckImageSize( wide, tall );
-
- m_iCheckImageWidth = max ( m_iCheckImageWidth, wide );
- }
- }
- _recalculateWidth = true;
- CalculateWidth();
-
- InvalidateLayout();
-}
-
-void Menu::SetBgColor( Color newColor )
-{
- BaseClass::SetBgColor( newColor );
- FOR_EACH_LL( m_MenuItems, i )
- {
- if( m_MenuItems[i]->HasMenu() )
- {
- m_MenuItems[i]->GetMenu()->SetBgColor( newColor );
- }
- }
-}
-
-void Menu::SetFgColor( Color newColor )
-{
- BaseClass::SetFgColor( newColor );
- FOR_EACH_LL( m_MenuItems, i )
- {
- if( m_MenuItems[i]->HasMenu() )
- {
- m_MenuItems[i]->GetMenu()->SetFgColor( newColor );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void Menu::SetBorder(class IBorder *border)
-{
- Panel::SetBorder(border);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: returns a pointer to a MenuItem that is this menus parent, if it has one
-//-----------------------------------------------------------------------------
-MenuItem *Menu::GetParentMenuItem()
-{
- return dynamic_cast<MenuItem *>(GetParent());
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Hide the menu when an item has been selected
-//-----------------------------------------------------------------------------
-void Menu::OnMenuItemSelected(Panel *panel)
-{
- SetVisible(false);
- m_pScroller->SetVisible(false);
-
- // chain this message up through the hierarchy so
- // all the parent menus will close
-
- // get the parent of this menu.
- MenuItem *item = GetParentMenuItem();
- // if the parent is a menu item, this menu is a cascading menu
- if (item)
- {
- // get the parent of the menuitem. it should be a menu.
- Menu *parentMenu = item->GetParentMenu();
- if (parentMenu)
- {
- // send the message to this parent menu
- KeyValues *kv = new KeyValues("MenuItemSelected");
- kv->SetPtr("panel", panel);
- ivgui()->PostMessage(parentMenu->GetVPanel(), kv, GetVPanel());
- }
- }
-
- bool activeItemSet = false;
-
- FOR_EACH_LL( m_MenuItems, i )
- {
- if( m_MenuItems[i] == panel )
- {
- activeItemSet = true;
- m_iActivatedItem = i;
- break;
- }
- }
- if( !activeItemSet )
- {
- FOR_EACH_LL( m_MenuItems, i )
- {
- if(m_MenuItems[i]->HasMenu() )
- {
- /*
- // GetActiveItem needs to return -1 or similar if it hasn't been set...
- if( m_MenuItems[i]->GetActiveItem() )
- {
- m_iActivatedItem = m_MenuItems[i]->GetActiveItem();
- }*/
- }
- }
- }
-
- // also pass it to the parent so they can respond if they like
- if (GetVParent())
- {
- KeyValues *kv = new KeyValues("MenuItemSelected");
- kv->SetPtr("panel", panel);
-
- ivgui()->PostMessage(GetVParent(), kv, GetVPanel());
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int Menu::GetActiveItem()
-{
- return m_iActivatedItem;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-KeyValues *Menu::GetItemUserData(int itemID)
-{
- if ( m_MenuItems.IsValidIndex( itemID ) )
- {
- MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
- // make sure its enabled since disabled items get highlighted.
- if (menuItem && menuItem->IsEnabled())
- {
- return menuItem->GetUserData();
- }
- }
- return NULL;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: data accessor
-//-----------------------------------------------------------------------------
-void Menu::GetItemText(int itemID, wchar_t *text, int bufLenInBytes)
-{
- if ( m_MenuItems.IsValidIndex( itemID ) )
- {
- MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
- if (menuItem)
- {
- menuItem->GetText(text, bufLenInBytes);
- return;
- }
- }
- text[0] = 0;
-}
-
-void Menu::GetItemText(int itemID, char *text, int bufLenInBytes)
-{
- if ( m_MenuItems.IsValidIndex( itemID ) )
- {
- MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
- if (menuItem)
- {
- menuItem->GetText( text, bufLenInBytes );
- return;
- }
- }
- text[0] = 0;
-}
-
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Activate the n'th item in the menu list, as if that menu item had been selected by the user
-//-----------------------------------------------------------------------------
-void Menu::ActivateItem(int itemID)
-{
- if ( m_MenuItems.IsValidIndex( itemID ) )
- {
- MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
- // make sure its enabled since disabled items get highlighted.
- if (menuItem && menuItem->IsEnabled())
- {
- menuItem->FireActionSignal();
- m_iActivatedItem = itemID;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void Menu::SilentActivateItem(int itemID)
-{
- if ( m_MenuItems.IsValidIndex( itemID ) )
- {
- MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
- // make sure its enabled since disabled items get highlighted.
- if (menuItem && menuItem->IsEnabled())
- {
- m_iActivatedItem = itemID;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void Menu::ActivateItemByRow(int row)
-{
- if (m_SortedItems.IsValidIndex(row))
- {
- ActivateItem(m_SortedItems[row]);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Return the number of items currently in the menu list
-//-----------------------------------------------------------------------------
-int Menu::GetItemCount()
-{
- return m_MenuItems.Count();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int Menu::GetMenuID(int index)
-{
- if ( !m_SortedItems.IsValidIndex(index) )
- return m_MenuItems.InvalidIndex();
-
- return m_SortedItems[index];
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Return the number of items currently visible in the menu list
-//-----------------------------------------------------------------------------
-int Menu::GetCurrentlyVisibleItemsCount()
-{
- if (m_MenuItems.Count() < m_iNumVisibleLines)
- {
- int cMenuItems = 0;
- FOR_EACH_LL(m_MenuItems, i)
- {
- if (m_MenuItems[i]->IsVisible())
- {
- ++cMenuItems;
- }
- }
-
- return cMenuItems;
- }
- return m_iNumVisibleLines;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Enables/disables choices in the list
-// itemText - string name of item in the list
-// state - true enables, false disables
-//-----------------------------------------------------------------------------
-void Menu::SetItemEnabled(const char *itemName, bool state)
-{
- FOR_EACH_LL( m_MenuItems, i )
- {
- if ((Q_stricmp(itemName, m_MenuItems[i]->GetName())) == 0)
- {
- m_MenuItems[i]->SetEnabled(state);
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Enables/disables choices in the list
-//-----------------------------------------------------------------------------
-void Menu::SetItemEnabled(int itemID, bool state)
-{
- if ( !m_MenuItems.IsValidIndex(itemID) )
- return;
-
- m_MenuItems[itemID]->SetEnabled(state);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: shows/hides choices in the list
-//-----------------------------------------------------------------------------
-void Menu::SetItemVisible(const char *itemName, bool state)
-{
- FOR_EACH_LL( m_MenuItems, i )
- {
- if ((Q_stricmp(itemName, m_MenuItems[i]->GetName())) == 0)
- {
- m_MenuItems[i]->SetVisible(state);
- InvalidateLayout();
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: shows/hides choices in the list
-//-----------------------------------------------------------------------------
-void Menu::SetItemVisible(int itemID, bool state)
-{
- if ( !m_MenuItems.IsValidIndex(itemID) )
- return;
-
- m_MenuItems[itemID]->SetVisible(state);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Make the scroll bar visible and narrow the menu
-// also make items visible or invisible in the list as appropriate
-//-----------------------------------------------------------------------------
-void Menu::AddScrollBar()
-{
- m_pScroller->SetVisible(true);
- _sizedForScrollBar = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Make the scroll bar invisible and widen the menu
-//-----------------------------------------------------------------------------
-void Menu::RemoveScrollBar()
-{
- m_pScroller->SetVisible(false);
- _sizedForScrollBar = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Invalidate layout if the slider is moved so items scroll
-//-----------------------------------------------------------------------------
-void Menu::OnSliderMoved()
-{
- CloseOtherMenus(NULL); // close any cascading menus
-
- // Invalidate so we redraw the menu!
- InvalidateLayout();
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Toggle into mouse mode.
-//-----------------------------------------------------------------------------
-void Menu::OnCursorMoved(int x, int y)
-{
- m_iInputMode = MOUSE;
-
- // chain up
- CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y));
- RequestFocus();
- InvalidateLayout();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Toggle into keyboard mode.
-//-----------------------------------------------------------------------------
-void Menu::OnKeyCodePressed(KeyCode code)
-{
- m_iInputMode = KEYBOARD;
- // send the message to this parent in case this is a cascading menu
- if (GetVParent())
- {
- ivgui()->PostMessage(GetVParent(), new KeyValues("KeyModeSet"), GetVPanel());
- }
-
- BaseClass::OnKeyCodePressed( code );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets the item currently highlighted in the menu by ptr
-//-----------------------------------------------------------------------------
-void Menu::SetCurrentlySelectedItem(MenuItem *item)
-{
- int itemNum = -1;
- // find it in our list of menuitems
- FOR_EACH_LL( m_MenuItems, i )
- {
- MenuItem *child = m_MenuItems[i];
- if (child == item)
- {
- itemNum = i;
- break;
- }
- }
- Assert( itemNum >= 0 );
-
- SetCurrentlySelectedItem(itemNum);
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void Menu::ClearCurrentlyHighlightedItem()
-{
- if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
- {
- m_MenuItems[m_iCurrentlySelectedItemID]->DisarmItem();
- }
- m_iCurrentlySelectedItemID = m_MenuItems.InvalidIndex();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets the item currently highlighted in the menu by index
-//-----------------------------------------------------------------------------
-void Menu::SetCurrentlySelectedItem(int itemID)
-{
- // dont deselect if its the same item
- if (itemID == m_iCurrentlySelectedItemID)
- return;
-
- if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
- {
- m_MenuItems[m_iCurrentlySelectedItemID]->DisarmItem();
- }
-
- PostActionSignal(new KeyValues("MenuItemHighlight", "itemID", itemID));
- m_iCurrentlySelectedItemID = itemID;
-}
-
-//-----------------------------------------------------------------------------
-// This will set the item to be currenly selected and highlight it
-// will not open cascading menu. This was added for comboboxes
-// to have the combobox item highlighted in the menu when they open the
-// dropdown.
-//-----------------------------------------------------------------------------
-void Menu::SetCurrentlyHighlightedItem(int itemID)
-{
- SetCurrentlySelectedItem(itemID);
- int row = m_SortedItems.Find(itemID);
- // If we have no items, then row will be -1. The dev console, for example...
- Assert( ( m_SortedItems.Count() == 0 ) || ( row != -1 ) );
- if ( row == -1 )
- return;
-
- // if there is a scroll bar, and we scroll off lets move it.
- if ( m_pScroller->IsVisible() )
- {
- // now if we are off the scroll bar, it means we moved the scroll bar
- // by hand or set the item off the list
- // so just snap the scroll bar straight to the item.
- if ( ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1 ) ||
- ( row < m_pScroller->GetValue() ) )
- {
- if ( !m_pScroller->IsVisible() )
- return;
-
- m_pScroller->SetValue(row);
- }
- }
-
- if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) )
- {
- if ( !m_MenuItems[m_iCurrentlySelectedItemID]->IsArmed() )
- {
- m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem();
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int Menu::GetCurrentlyHighlightedItem()
-{
- return m_iCurrentlySelectedItemID;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Respond to cursor entering a menuItem.
-//-----------------------------------------------------------------------------
-void Menu::OnCursorEnteredMenuItem(int VPanel)
-{
- VPANEL menuItem = (VPANEL)VPanel;
- // if we are in mouse mode
- if (m_iInputMode == MOUSE)
- {
- MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName()));
- // arm the menu
- item->ArmItem();
- // open the cascading menu if there is one.
- item->OpenCascadeMenu();
- SetCurrentlySelectedItem(item);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Respond to cursor exiting a menuItem
-//-----------------------------------------------------------------------------
-void Menu::OnCursorExitedMenuItem(int VPanel)
-{
- VPANEL menuItem = (VPANEL)VPanel;
- // only care if we are in mouse mode
- if (m_iInputMode == MOUSE)
- {
- MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName()));
- // unhighlight the item.
- // note menuItems with cascading menus will stay lit.
- item->DisarmItem();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Move up or down one in the list of items in the menu
-// Direction is MENU_UP or MENU_DOWN
-//-----------------------------------------------------------------------------
-void Menu::MoveAlongMenuItemList(int direction, int loopCount)
-{
- // Early out if no menu items to scroll through
- if (m_MenuItems.Count() <= 0)
- return;
-
- int itemID = m_iCurrentlySelectedItemID;
- int row = m_SortedItems.Find(itemID);
- row += direction;
-
- if ( row > m_SortedItems.Count() - 1 )
- {
- if ( m_pScroller->IsVisible() )
- {
- // stop at bottom of scrolled list
- row = m_SortedItems.Count() - 1;
- }
- else
- {
- // if no scroll bar we circle around
- row = 0;
- }
- }
- else if (row < 0)
- {
- if ( m_pScroller->IsVisible() )
- {
- // stop at top of scrolled list
- row = m_pScroller->GetValue();
- }
- else
- {
- // if no scroll bar circle around
- row = m_SortedItems.Count()-1;
- }
- }
-
- // if there is a scroll bar, and we scroll off lets move it.
- if ( m_pScroller->IsVisible() )
- {
- if ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1)
- {
- int val = m_pScroller->GetValue();
- val -= -direction;
-
- m_pScroller->SetValue(val);
-
- // moving the slider redraws the scrollbar,
- // and so we should redraw the menu since the
- // menu draws the black border to the right of the scrollbar.
- InvalidateLayout();
- }
- else if ( row < m_pScroller->GetValue() )
- {
- int val = m_pScroller->GetValue();
- val -= -direction;
-
- m_pScroller->SetValue(val);
-
- // moving the slider redraws the scrollbar,
- // and so we should redraw the menu since the
- // menu draws the black border to the right of the scrollbar.
- InvalidateLayout();
- }
-
- // now if we are still off the scroll bar, it means we moved the scroll bar
- // by hand and created a situation in which we moved an item down, but the
- // scroll bar is already too far down and should scroll up or vice versa
- // so just snap the scroll bar straight to the item.
- if ( ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1) ||
- ( row < m_pScroller->GetValue() ) )
- {
- m_pScroller->SetValue(row);
- }
- }
-
- // switch it back to an itemID from row
- if ( m_SortedItems.IsValidIndex( row ) )
- {
- SetCurrentlySelectedItem( m_SortedItems[row] );
- }
-
- // don't allow us to loop around more than once
- if (loopCount < m_MenuItems.Count())
- {
- // see if the text is empty, if so skip
- wchar_t text[256];
- m_MenuItems[m_iCurrentlySelectedItemID]->GetText(text, 255);
- if (text[0] == 0 || !m_MenuItems[m_iCurrentlySelectedItemID]->IsVisible())
- {
- // menu item is empty, keep moving along
- MoveAlongMenuItemList(direction, loopCount + 1);
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Return which type of events the menu is currently interested in
-// MenuItems need to know because behaviour is different depending on mode.
-//-----------------------------------------------------------------------------
-int Menu::GetMenuMode()
-{
- return m_iInputMode;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set the menu to key mode if a child menu goes into keymode
-// This mode change has to be chained up through the menu heirarchy
-// so cascading menus will work when you do a bunch of stuff in keymode
-// in high level menus and then switch to keymode in lower level menus.
-//-----------------------------------------------------------------------------
-void Menu::OnKeyModeSet()
-{
- m_iInputMode = KEYBOARD;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set the checked state of a menuItem
-//-----------------------------------------------------------------------------
-void Menu::SetMenuItemChecked(int itemID, bool state)
-{
- m_MenuItems[itemID]->SetChecked(state);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Check if item is checked.
-//-----------------------------------------------------------------------------
-bool Menu::IsChecked(int itemID)
-{
- return m_MenuItems[itemID]->IsChecked();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set the minmum width the menu has to be. This
-// is useful if you have a menu that is sized to the largest item in it
-// but you don't want the menu to be thinner than the menu button
-//-----------------------------------------------------------------------------
-void Menu::SetMinimumWidth(int width)
-{
- m_iMinimumWidth = width;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Get the minmum width the menu
-//-----------------------------------------------------------------------------
-int Menu::GetMinimumWidth()
-{
- return m_iMinimumWidth;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-// Input : -
-//-----------------------------------------------------------------------------
-void Menu::AddSeparator()
-{
- int lastID = m_MenuItems.Count() - 1;
- m_Separators.AddToTail( lastID );
- m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) );
-}
-
-void Menu::AddSeparatorAfterItem( int itemID )
-{
- Assert( m_MenuItems.IsValidIndex( itemID ) );
- m_Separators.AddToTail( itemID );
- m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) );
-}
-
-void Menu::MoveMenuItem( int itemID, int moveBeforeThisItemID )
-{
- int c = m_SortedItems.Count();
- int i;
- for ( i = 0; i < c; ++i )
- {
- if ( m_SortedItems[i] == itemID )
- {
- m_SortedItems.Remove( i );
- break;
- }
- }
-
- // Didn't find it
- if ( i >= c )
- {
- return;
- }
-
- // Now find insert pos
- c = m_SortedItems.Count();
- for ( i = 0; i < c; ++i )
- {
- if ( m_SortedItems[i] == moveBeforeThisItemID )
- {
- m_SortedItems.InsertBefore( i, itemID );
- break;
- }
- }
-}
-
-void Menu::SetFont( HFont font )
-{
- m_hItemFont = font;
- if ( font )
- {
- m_iMenuItemHeight = surface()->GetFontTall( font ) + 2;
- }
- InvalidateLayout();
-}
-
-
-void Menu::SetCurrentKeyBinding( int itemID, char const *hotkey )
-{
- if ( m_MenuItems.IsValidIndex( itemID ) )
- {
- MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]);
- menuItem->SetCurrentKeyBinding( hotkey );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Static method to display a context menu
-// Input : *parent -
-// *menu -
-//-----------------------------------------------------------------------------
-void Menu::PlaceContextMenu( Panel *parent, Menu *menu )
-{
- Assert( parent );
- Assert( menu );
- if ( !menu || !parent )
- return;
-
- menu->SetVisible(false);
- menu->SetParent( parent );
- menu->AddActionSignalTarget( parent );
-
- // get cursor position, this is local to this text edit window
- int cursorX, cursorY;
- input()->GetCursorPos(cursorX, cursorY);
-
- menu->SetVisible(true);
-
- // relayout the menu immediately so that we know it's size
- menu->InvalidateLayout(true);
- int menuWide, menuTall;
- menu->GetSize(menuWide, menuTall);
-
- // work out where the cursor is and therefore the best place to put the menu
- int wide, tall;
- surface()->GetScreenSize(wide, tall);
-
- if (wide - menuWide > cursorX)
- {
- // menu hanging right
- if (tall - menuTall > cursorY)
- {
- // menu hanging down
- menu->SetPos(cursorX, cursorY);
- }
- else
- {
- // menu hanging up
- menu->SetPos(cursorX, cursorY - menuTall);
- }
- }
- else
- {
- // menu hanging left
- if (tall - menuTall > cursorY)
- {
- // menu hanging down
- menu->SetPos(cursorX - menuWide, cursorY);
- }
- else
- {
- // menu hanging up
- menu->SetPos(cursorX - menuWide, cursorY - menuTall);
- }
- }
-
- menu->RequestFocus();
-}
-
-void Menu::SetUseFallbackFont( bool bState, HFont hFallback )
-{
- m_hFallbackItemFont = hFallback;
- m_bUseFallbackFont = bState;
-}
-
-#ifdef DBGFLAG_VALIDATE
-//-----------------------------------------------------------------------------
-// Purpose: Run a global validation pass on all of our data structures and memory
-// allocations.
-// Input: validator - Our global validator object
-// pchName - Our name (typically a member var in our container)
-//-----------------------------------------------------------------------------
-void Menu::Validate( CValidator &validator, char *pchName )
-{
- validator.Push( "vgui::Menu", this, pchName );
-
- m_MenuItems.Validate( validator, "m_MenuItems" );
- m_SortedItems.Validate( validator, "m_SortedItems" );
-
- BaseClass::Validate( validator, "vgui::Menu" );
-
- validator.Pop();
-}
-#endif // DBGFLAG_VALIDATE
+//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui_controls/pch_vgui_controls.h" + +// memdbgon must be the last include file in a .cpp file +#include "tier0/memdbgon.h" +#define MENU_SEPARATOR_HEIGHT 3 + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: divider line in a menu +//----------------------------------------------------------------------------- +class vgui::MenuSeparator : public Panel +{ +public: + DECLARE_CLASS_SIMPLE( MenuSeparator, Panel ); + + MenuSeparator( Panel *parent, char const *panelName ) : + BaseClass( parent, panelName ) + { + SetPaintEnabled( true ); + SetPaintBackgroundEnabled( true ); + SetPaintBorderEnabled( false ); + } + + virtual void Paint() + { + int w, h; + GetSize( w, h ); + + surface()->DrawSetColor( GetFgColor() ); + surface()->DrawFilledRect( 4, 1, w-1, 2 ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + SetFgColor( pScheme->GetColor( "Menu.SeparatorColor", Color( 142, 142, 142, 255 ) ) ); + SetBgColor( pScheme->GetColor( "Menu.BgColor", Color( 0, 0, 0, 255 ) ) ); + } +}; + +DECLARE_BUILD_FACTORY( Menu ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Menu::Menu(Panel *parent, const char *panelName) : Panel(parent, panelName) +{ + m_Alignment = Label::a_west; + m_iFixedWidth = 0; + m_iMinimumWidth = 0; + m_iNumVisibleLines = -1; // No limit + m_iCurrentlySelectedItemID = m_MenuItems.InvalidIndex(); + m_pScroller = new ScrollBar(this, "MenuScrollBar", true); + m_pScroller->SetVisible(false); + m_pScroller->AddActionSignalTarget(this); + _sizedForScrollBar = false; + SetZPos(1); + SetVisible(false); + MakePopup(false); + SetParent(parent); + _recalculateWidth = true; + m_bUseMenuManager = true; + m_iInputMode = MOUSE; + m_iCheckImageWidth = 0; + m_iActivatedItem = 0; + + m_bUseFallbackFont = false; + m_hFallbackItemFont = INVALID_FONT; + + if (IsProportional()) + { + m_iMenuItemHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), DEFAULT_MENU_ITEM_HEIGHT ); + } + else + { + m_iMenuItemHeight = DEFAULT_MENU_ITEM_HEIGHT; + } + m_hItemFont = INVALID_FONT; + + + m_eTypeAheadMode = COMPAT_MODE; + m_szTypeAheadBuf[0] = '\0'; + m_iNumTypeAheadChars = 0; + m_fLastTypeAheadTime = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Menu::~Menu() +{ + delete m_pScroller; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all menu items from the menu. +//----------------------------------------------------------------------------- +void Menu::DeleteAllItems() +{ + FOR_EACH_LL( m_MenuItems, i ) + { + m_MenuItems[i]->MarkForDeletion(); + } + + m_MenuItems.RemoveAll(); + m_SortedItems.RemoveAll(); + m_VisibleSortedItems.RemoveAll(); + m_Separators.RemoveAll(); + int c = m_SeparatorPanels.Count(); + for ( int i = 0 ; i < c; ++i ) + { + m_SeparatorPanels[ i ]->MarkForDeletion(); + } + m_SeparatorPanels.RemoveAll(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( MenuItem *panel ) +{ + panel->SetParent( this ); + MEM_ALLOC_CREDIT(); + int itemID = m_MenuItems.AddToTail( panel ); + m_SortedItems.AddToTail(itemID); + InvalidateLayout(false); + _recalculateWidth = true; + panel->SetContentAlignment( m_Alignment ); + if ( INVALID_FONT != m_hItemFont ) + { + panel->SetFont( m_hItemFont ); + } + if ( m_bUseFallbackFont && INVALID_FONT != m_hFallbackItemFont ) + { + Label *l = panel; + TextImage *ti = l->GetTextImage(); + if ( ti ) + { + ti->SetUseFallbackFont( m_bUseFallbackFont, m_hFallbackItemFont ); + } + } + + if ( panel->GetHotKey() ) + { + SetTypeAheadMode( HOT_KEY_MODE ); + } + + return itemID; +} + + +//----------------------------------------------------------------------------- +// Remove a single item +//----------------------------------------------------------------------------- +void Menu::DeleteItem( int itemID ) +{ + // FIXME: This doesn't work with separator panels yet + Assert( m_SeparatorPanels.Count() == 0 ); + + m_MenuItems[itemID]->MarkForDeletion(); + m_MenuItems.Remove( itemID ); + + m_SortedItems.FindAndRemove( itemID ); + m_VisibleSortedItems.FindAndRemove( itemID ); + + InvalidateLayout(false); + _recalculateWidth = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *item - MenuItem +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *userData - any user data associated with this menu item +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItemCharCommand(MenuItem *item, const char *command, Panel *target, const KeyValues *userData) +{ + item->SetCommand(command); + item->AddActionSignalTarget( target ); + item->SetUserData(userData); + return AddMenuItem( item ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItemKeyValuesCommand( MenuItem *item, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + item->SetCommand(message); + item->AddActionSignalTarget(target); + item->SetUserData(userData); + return AddMenuItem(item); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + return AddMenuItem(itemText, itemText, command, target, userData ) ; +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText ); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText ); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + return AddMenuItem(itemText, itemText, message, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be the text of the command sent when the +// item is selected. +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemText, Panel *target , const KeyValues *userData ) +{ + return AddMenuItem(itemText, itemText, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true); + return AddMenuItemCharCommand(item, command, target, userData); +} + +int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true); + return AddMenuItemCharCommand(item, command, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + return AddCheckableMenuItem(itemText, itemText, command, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + return AddCheckableMenuItem(itemText, itemText, message, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be the text of the command sent when the +// item is selected. +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemText, Panel *target, const KeyValues *userData ) +{ + return AddCheckableMenuItem(itemText, itemText, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText, cascadeMenu ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText, cascadeMenu ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) +{ + return AddCascadingMenuItem( itemText, itemText, command, target, cascadeMenu, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem( this, itemName, itemText, cascadeMenu); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem( this, itemName, wszItemText, cascadeMenu); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + return AddCascadingMenuItem(itemText, itemText, message, target, cascadeMenu, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be the text of the command sent when the +// item is selected. +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemText, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + return AddCascadingMenuItem(itemText, itemText, target, cascadeMenu, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the values of a menu item at the specified index +// Input : index - the index of this item entry +// *message - pointer to the message to send when the item is selected +//----------------------------------------------------------------------------- +void Menu::UpdateMenuItem(int itemID, const char *itemText, KeyValues *message, const KeyValues *userData) +{ + Assert( m_MenuItems.IsValidIndex(itemID) ); + if ( m_MenuItems.IsValidIndex(itemID) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem) + { + menuItem->SetText(itemText); + menuItem->SetCommand(message); + if(userData) + { + menuItem->SetUserData(userData); + } + } + } + _recalculateWidth = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the values of a menu item at the specified index +//----------------------------------------------------------------------------- +void Menu::UpdateMenuItem(int itemID, const wchar_t *wszItemText, KeyValues *message, const KeyValues *userData) +{ + Assert( m_MenuItems.IsValidIndex(itemID) ); + if ( m_MenuItems.IsValidIndex(itemID) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem) + { + menuItem->SetText(wszItemText); + menuItem->SetCommand(message); + if(userData) + { + menuItem->SetUserData(userData); + } + } + } + _recalculateWidth = true; +} + + +//----------------------------------------------------------------------------- +// Sets the content alignment of all items in the menu +//----------------------------------------------------------------------------- +void Menu::SetContentAlignment( Label::Alignment alignment ) +{ + if ( m_Alignment != alignment ) + { + m_Alignment = alignment; + + // Change the alignment of existing menu items + int nCount = m_MenuItems.Count(); + for ( int i = 0; i < nCount; ++i ) + { + m_MenuItems[i]->SetContentAlignment( alignment ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Locks down a specific width +//----------------------------------------------------------------------------- +void Menu::SetFixedWidth(int width) +{ + // the padding makes it so the menu has the label padding on each side of the menu. + // makes the menu items look centered. + m_iFixedWidth = width; + InvalidateLayout(false); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the height of each menu item +//----------------------------------------------------------------------------- +void Menu::SetMenuItemHeight(int itemHeight) +{ + m_iMenuItemHeight = itemHeight; +} + +int Menu::GetMenuItemHeight() const +{ + return m_iMenuItemHeight; +} + +int Menu::CountVisibleItems() +{ + int count = 0; + int c = m_SortedItems.Count(); + for ( int i = 0 ; i < c; ++i ) + { + if ( m_MenuItems[ m_SortedItems[ i ] ]->IsVisible() ) + ++count; + } + return count; +} + +void Menu::ComputeWorkspaceSize( int& workWide, int& workTall ) +{ + // make sure we factor in insets + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + int workX, workY; + surface()->GetWorkspaceBounds(workX, workY, workWide, workTall); + workTall -= 20; + workTall -= itop; + workTall -= ibottom; +} + +// Assumes relative coords in screenspace +void Menu::PositionRelativeToPanel( Panel *relative, MenuDirection_e direction, int nAdditionalYOffset /*=0*/, bool showMenu /*=false*/ ) +{ + Assert( relative ); + int rx, ry, rw, rh; + relative->GetBounds( rx, ry, rw, rh ); + relative->LocalToScreen( rx, ry ); + + if ( direction == CURSOR ) + { + // force the menu to appear where the mouse button was pressed + input()->GetCursorPos(rx, ry); + rw = rh = 0; + } + else if ( direction == ALIGN_WITH_PARENT && relative->GetVParent() ) + { + rx = 0, ry = 0; + relative->ParentLocalToScreen(rx, ry); + rx -= 1; // take border into account + ry += rh + nAdditionalYOffset; + rw = rh = 0; + } + else + { + rx = 0, ry = 0; + relative->LocalToScreen(rx, ry); + } + + int workWide, workTall; + ComputeWorkspaceSize( workWide, workTall ); + + // Final pos + int x = 0, y = 0; + + int mWide, mTall; + GetSize( mWide, mTall ); + + switch( direction ) + { + case Menu::UP: // Menu prefers to open upward + { + x = rx; + int topOfReference = ry; + y = topOfReference - mTall; + if ( y < 0 ) + { + int bottomOfReference = ry + rh + 1; + int remainingPixels = workTall - bottomOfReference; + + // Can't fit on bottom, either, move to side + if ( mTall >= remainingPixels ) + { + y = workTall - mTall; + x = rx + rw; + // Try and place it to the left of the button + if ( x + mWide > workWide ) + { + x = rx - mWide; + } + } + else + { + // Room at bottom + y = bottomOfReference; + } + } + } + break; + // Everyone else aligns downward... + default: + case Menu::LEFT: + case Menu::RIGHT: + case Menu::DOWN: + { + x = rx; + int bottomOfReference = ry + rh + 1; + y = bottomOfReference; + if ( bottomOfReference + mTall >= workTall ) + { + // See if there's run straight above + if ( mTall >= ry ) // No room, try and push menu to right or left + { + y = workTall - mTall; + x = rx + rw; + // Try and place it to the left of the button + if ( x + mWide > workWide ) + { + x = rx - mWide; + } + } + else + { + // Room at top + y = ry - mTall; + } + } + } + break; + } + + // Check left rightness + if ( x + mWide > workWide ) + { + x = workWide - mWide; + Assert( x >= 0 ); // yikes!!! + } + else if ( x < 0 ) + { + x = 0; + } + + SetPos( x, y ); + if ( showMenu ) + { + SetVisible( true ); + } +} + +int Menu::ComputeFullMenuHeightWithInsets() +{ + // make sure we factor in insets + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + int separatorHeight = 3; + + // add up the size of all the child panels + // move the child panels to the correct place in the menu + int totalTall = itop + ibottom; + int i; + for ( i = 0 ; i < m_SortedItems.Count() ; i++ ) // use sortedItems instead of MenuItems due to SetPos() + { + int itemId = m_SortedItems[i]; + + MenuItem *child = m_MenuItems[ itemId ]; + Assert( child ); + if ( !child ) + continue; + // These should all be visible at this point + if ( !child->IsVisible() ) + continue; + + totalTall += m_iMenuItemHeight; + + // Add a separator if needed... + int sepIndex = m_Separators.Find( itemId ); + if ( sepIndex != m_Separators.InvalidIndex() ) + { + totalTall += separatorHeight; + } + } + + return totalTall; +} + +//----------------------------------------------------------------------------- +// Purpose: Reformat according to the new layout +//----------------------------------------------------------------------------- +void Menu::PerformLayout() +{ + MenuItem *parent = GetParentMenuItem(); + bool cascading = parent != NULL ? true : false; + + // make sure we factor in insets + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + int workWide, workTall; + + ComputeWorkspaceSize( workWide, workTall ); + + int fullHeightWouldRequire = ComputeFullMenuHeightWithInsets(); + + bool bNeedScrollbar = fullHeightWouldRequire >= workTall; + + int maxVisibleItems = CountVisibleItems(); + + if ( m_iNumVisibleLines > 0 && + maxVisibleItems > m_iNumVisibleLines ) + { + bNeedScrollbar = true; + maxVisibleItems = m_iNumVisibleLines; + } + + // if we have a scroll bar + if ( bNeedScrollbar ) + { + // add it to the display + AddScrollBar(); + + // This fills in m_VisibleSortedItems as needed + MakeItemsVisibleInScrollRange( m_iNumVisibleLines, min( fullHeightWouldRequire, workTall ) ); + } + else + { + RemoveScrollBar(); + // Make everything visible + m_VisibleSortedItems.RemoveAll(); + int i; + int c = m_SortedItems.Count(); + for ( i = 0; i < c; ++i ) + { + int itemID = m_SortedItems[ i ]; + MenuItem *child = m_MenuItems[ itemID ]; + if ( !child || !child->IsVisible() ) + continue; + + m_VisibleSortedItems.AddToTail( itemID ); + } + + // Hide the separators, the needed ones will be readded below + c = m_SeparatorPanels.Count(); + for ( i = 0; i < c; ++i ) + { + if ( m_SeparatorPanels[ i ] ) + { + m_SeparatorPanels[ i ]->SetVisible( false ); + } + } + } + + // get the appropriate menu border + LayoutMenuBorder(); + + int trueW = GetWide(); + if ( bNeedScrollbar ) + { + trueW -= m_pScroller->GetWide(); + } + int separatorHeight = MENU_SEPARATOR_HEIGHT; + + // add up the size of all the child panels + // move the child panels to the correct place in the menu + int menuTall = 0; + int totalTall = itop + ibottom; + int i; + for ( i = 0 ; i < m_VisibleSortedItems.Count() ; i++ ) // use sortedItems instead of MenuItems due to SetPos() + { + int itemId = m_VisibleSortedItems[i]; + + MenuItem *child = m_MenuItems[ itemId ]; + Assert( child ); + if ( !child ) + continue; + // These should all be visible at this point + if ( !child->IsVisible() ) + continue; + + if ( totalTall >= workTall ) + break; + + if ( INVALID_FONT != m_hItemFont ) + { + child->SetFont( m_hItemFont ); + } + + // take into account inset + child->SetPos (0, menuTall); + child->SetTall( m_iMenuItemHeight ); // Width is set in a second pass + menuTall += m_iMenuItemHeight; + totalTall += m_iMenuItemHeight; + + // this will make all the menuitems line up in a column with space for the checks to the left. + if ( ( !child->IsCheckable() ) && ( m_iCheckImageWidth > 0 ) ) + { + // Non checkable items have to move over + child->SetTextInset( m_iCheckImageWidth, 0 ); + } + else if ( child->IsCheckable() ) + { + child->SetTextInset(0, 0); //TODO: for some reason I can't comment this out. + } + + // Add a separator if needed... + int sepIndex = m_Separators.Find( itemId ); + if ( sepIndex != m_Separators.InvalidIndex() ) + { + MenuSeparator *sep = m_SeparatorPanels[ sepIndex ]; + Assert( sep ); + sep->SetVisible( true ); + sep->SetBounds( 0, menuTall, trueW, separatorHeight ); + menuTall += separatorHeight; + totalTall += separatorHeight; + } + } + + if (!m_iFixedWidth) + { + _recalculateWidth = true; + CalculateWidth(); + } + else if (m_iFixedWidth) + { + _menuWide = m_iFixedWidth; + // fixed width menus include the scroll bar in their width. + if (_sizedForScrollBar) + { + _menuWide -= m_pScroller->GetWide(); + } + } + + SizeMenuItems(); + + int extraWidth = 0; + if (_sizedForScrollBar) + { + extraWidth = m_pScroller->GetWide(); + } + + int mwide = _menuWide + extraWidth; + if ( mwide > workWide ) + { + mwide = workWide; + } + int mtall = menuTall + itop + ibottom; + if ( mtall > workTall ) + { + // Shouldn't happen + mtall = workTall; + } + + // set the new size of the menu + SetSize( mwide, mtall ); + + // move the menu to the correct position if it is a cascading menu. + if ( cascading ) + { + // move the menu to the correct position if it is a cascading menu. + PositionCascadingMenu(); + } + + // set up scroll bar as appropriate + if ( m_pScroller->IsVisible() ) + { + LayoutScrollBar(); + } + + FOR_EACH_LL( m_MenuItems, j ) + { + m_MenuItems[j]->InvalidateLayout(); // cause each menu item to redo its apply settings now we have sized ourselves + } + + Repaint(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Force the menu to work out how wide it should be +//----------------------------------------------------------------------------- +void Menu::ForceCalculateWidth() +{ + _recalculateWidth = true; + CalculateWidth(); + PerformLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Figure out how wide the menu should be if the menu is not fixed width +//----------------------------------------------------------------------------- +void Menu::CalculateWidth() +{ + if (!_recalculateWidth) + return; + + _menuWide = 0; + if (!m_iFixedWidth) + { + // find the biggest menu item + FOR_EACH_LL( m_MenuItems, i ) + { + int wide, tall; + m_MenuItems[i]->GetContentSize(wide, tall); + if (wide > _menuWide - Label::Content) + { + _menuWide = wide + Label::Content; + } + } + } + + // enfoce a minimumWidth + if (_menuWide < m_iMinimumWidth) + { + _menuWide = m_iMinimumWidth; + } + + _recalculateWidth = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Set up the scroll bar attributes,size and location. +//----------------------------------------------------------------------------- +void Menu::LayoutScrollBar() +{ + //!! need to make it recalculate scroll positions + m_pScroller->SetEnabled(false); + m_pScroller->SetRangeWindow( m_VisibleSortedItems.Count() ); + m_pScroller->SetRange( 0, CountVisibleItems() ); + m_pScroller->SetButtonPressedScrollValue( 1 ); + + int wide, tall; + GetSize (wide, tall); + + // make sure we factor in insets + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + // with a scroll bar we take off the inset + wide -= iright; + + m_pScroller->SetPos(wide - m_pScroller->GetWide(), 1); + + // scrollbar is inside the menu's borders. + m_pScroller->SetSize(m_pScroller->GetWide(), tall - ibottom - itop); + +} + +//----------------------------------------------------------------------------- +// Purpose: Figure out where to open menu if it is a cascading menu +//----------------------------------------------------------------------------- +void Menu::PositionCascadingMenu() +{ + Assert(GetVParent()); + int parentX, parentY, parentWide, parentTall; + // move the menu to the correct place below the menuItem + ipanel()->GetSize(GetVParent(), parentWide, parentTall); + ipanel()->GetPos(GetVParent(), parentX, parentY); + + parentX += parentWide, parentY = 0; + + ParentLocalToScreen(parentX, parentY); + + SetPos(parentX, parentY); + + // for cascading menus, + // make sure we're on the screen + int workX, workY, workWide, workTall, x, y, wide, tall; + GetBounds(x, y, wide, tall); + surface()->GetWorkspaceBounds(workX, workY, workWide, workTall); + + if (x + wide > workX + workWide) + { + // we're off the right, move the menu to the left side + // orignalX - width of the parentmenuitem - width of this menu. + // add 2 pixels to offset one pixel onto the parent menu. + x -= (parentWide + wide); + x -= 2; + } + else + { + // alignment move it in the amount of the insets. + x += 1; + } + + if ( y + tall > workY + workTall ) + { + int lastWorkY = workY + workTall; + int pixelsOffBottom = ( y + tall ) - lastWorkY; + + y -= pixelsOffBottom; + y -= 2; + } + else + { + y -= 1; + } + SetPos(x, y); + + MoveToFront(); +} + +//----------------------------------------------------------------------------- +// Purpose: Size the menu items so they are the width of the menu. +// Also size the menu items with cascading menus so the arrow fits in there. +//----------------------------------------------------------------------------- +void Menu::SizeMenuItems() +{ + int ileft, iright, itop, ibottom; + GetInset(ileft, iright, itop, ibottom); + + // assign the sizes of all the menu item panels + FOR_EACH_LL( m_MenuItems, i ) + { + MenuItem *child = m_MenuItems[i]; + if (child ) + { + // labels do thier own sizing. this will size the label to the width of the menu, + // this will put the cascading menu arrow on the right side automatically. + child->SetWide(_menuWide - ileft - iright); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Makes menu items visible in relation to where the scroll bar is +//----------------------------------------------------------------------------- +void Menu::MakeItemsVisibleInScrollRange( int maxVisibleItems, int nNumPixelsAvailable ) +{ + // Detach all items from tree + int i; + FOR_EACH_LL( m_MenuItems, item ) + { + m_MenuItems[ item ]->SetBounds( 0, 0, 0, 0 ); + } + for ( i = 0; i < m_SeparatorPanels.Count(); ++i ) + { + m_SeparatorPanels[ i ]->SetVisible( false ); + } + + m_VisibleSortedItems.RemoveAll(); + + int tall = 0; + + int startItem = m_pScroller->GetValue(); + Assert( startItem >= 0 ); + do + { + if ( startItem >= m_SortedItems.Count() ) + break; + + int itemId = m_SortedItems[ startItem ]; + + if ( !m_MenuItems[ itemId ]->IsVisible() ) + { + ++startItem; + continue; + } + + int itemHeight = m_iMenuItemHeight; + int sepIndex = m_Separators.Find( itemId ); + if ( sepIndex != m_Separators.InvalidIndex() ) + { + itemHeight += MENU_SEPARATOR_HEIGHT; + } + + if ( tall + itemHeight > nNumPixelsAvailable ) + break; + + // Too many items + if ( maxVisibleItems > 0 ) + { + if ( m_VisibleSortedItems.Count() >= maxVisibleItems ) + break; + } + + tall += itemHeight; + // Re-attach this one + m_VisibleSortedItems.AddToTail( itemId ); + ++startItem; + } + while ( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Get the approproate menu border +//----------------------------------------------------------------------------- +void Menu::LayoutMenuBorder() +{ + IBorder *menuBorder; + IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); + + menuBorder = pScheme->GetBorder("MenuBorder"); + + if ( menuBorder ) + { + SetBorder(menuBorder); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw a black border on the right side of the menu items +//----------------------------------------------------------------------------- +void Menu::Paint() +{ + if ( m_pScroller->IsVisible() ) + { + // draw black bar + int wide, tall; + GetSize (wide, tall); + surface()->DrawSetColor(_borderDark); + if( IsProportional() ) + { + surface()->DrawFilledRect(wide - m_pScroller->GetWide(), -1, wide - m_pScroller->GetWide() + 1, tall); + } + else + { + surface()->DrawFilledRect(wide - m_pScroller->GetWide(), -1, wide - m_pScroller->GetWide() + 1, tall); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: sets the max number of items visible (scrollbar appears with more) +// Input : numItems - +//----------------------------------------------------------------------------- +void Menu::SetNumberOfVisibleItems( int numItems ) +{ + m_iNumVisibleLines = numItems; + InvalidateLayout(false); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::EnableUseMenuManager( bool bUseMenuManager ) +{ + m_bUseMenuManager = bUseMenuManager; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +MenuItem *Menu::GetMenuItem(int itemID) +{ + if ( !m_MenuItems.IsValidIndex(itemID) ) + return NULL; + + return m_MenuItems[itemID]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool Menu::IsValidMenuID(int itemID) +{ + return m_MenuItems.IsValidIndex(itemID); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Menu::GetInvalidMenuID() +{ + return m_MenuItems.InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: When a menuItem is selected, close cascading menus +// if the menuItem selected has a cascading menu attached, we +// want to keep that one open so skip it. +// Passing NULL will close all cascading menus. +//----------------------------------------------------------------------------- +void Menu::CloseOtherMenus(MenuItem *item) +{ + FOR_EACH_LL( m_MenuItems, i ) + { + if (m_MenuItems[i] == item) + continue; + + m_MenuItems[i]->CloseCascadeMenu(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to string commands. +//----------------------------------------------------------------------------- +void Menu::OnCommand( const char *command ) +{ + // forward on the message + PostActionSignal(new KeyValues("Command", "command", command)); + + Panel::OnCommand(command); +} + +//----------------------------------------------------------------------------- +// Purpose: Handle key presses, Activate shortcuts +//----------------------------------------------------------------------------- +void Menu::OnKeyCodeTyped(KeyCode keycode) +{ + vgui::KeyCode code = GetBaseButtonCode( keycode ); + + // Don't allow key inputs when disabled! + if ( !IsEnabled() ) + return; + + bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT)); + if (alt) + { + BaseClass::OnKeyCodeTyped( keycode ); + // Ignore alt when in combobox mode + if (m_eTypeAheadMode != TYPE_AHEAD_MODE) + { + PostActionSignal(new KeyValues("MenuClose")); + } + } + + switch (code) + { + case KEY_ESCAPE: + case KEY_XBUTTON_B: + { + // hide the menu on ESC + SetVisible(false); + break; + } + // arrow keys scroll through items on the list. + // they should also scroll the scroll bar if needed + case KEY_UP: + case KEY_XBUTTON_UP: + case KEY_XSTICK1_UP: + { + MoveAlongMenuItemList(MENU_UP, 0); + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); // chain up + } + break; + } + case KEY_DOWN: + case KEY_XBUTTON_DOWN: + case KEY_XSTICK1_DOWN: + { + MoveAlongMenuItemList(MENU_DOWN, 0); + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); // chain up + } + break; + } + // for now left and right arrows just open or close submenus if they are there. + case KEY_RIGHT: + case KEY_XBUTTON_RIGHT: + case KEY_XSTICK1_RIGHT: + { + // make sure a menuItem is currently selected + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + if (m_MenuItems[m_iCurrentlySelectedItemID]->HasMenu()) + { + ActivateItem(m_iCurrentlySelectedItemID); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); + } + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); + } + break; + } + case KEY_LEFT: + case KEY_XBUTTON_LEFT: + case KEY_XSTICK1_LEFT: + { + // if our parent is a menu item then we are a submenu so close us. + if (GetParentMenuItem()) + { + SetVisible(false); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); + } + break; + } + case KEY_ENTER: + case KEY_XBUTTON_A: + { + // make sure a menuItem is currently selected + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + ActivateItem(m_iCurrentlySelectedItemID); + } + else + { + BaseClass::OnKeyCodeTyped( keycode ); // chain up + } + break; + } + + case KEY_PAGEUP: + { + if ( m_iNumVisibleLines > 1 ) + { + if ( m_iCurrentlySelectedItemID < m_iNumVisibleLines ) + { + MoveAlongMenuItemList( MENU_UP * m_iCurrentlySelectedItemID, 0 ); + } + else + { + MoveAlongMenuItemList(MENU_UP * m_iNumVisibleLines - 1, 0); + } + } + else + { + MoveAlongMenuItemList(MENU_UP, 0); + } + + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + break; + } + + + case KEY_PAGEDOWN: + { + if ( m_iNumVisibleLines > 1 ) + { + if ( m_iCurrentlySelectedItemID + m_iNumVisibleLines >= GetItemCount() ) + { + MoveAlongMenuItemList(MENU_DOWN * ( GetItemCount() - m_iCurrentlySelectedItemID - 1), 0); + } + else + { + MoveAlongMenuItemList(MENU_DOWN * m_iNumVisibleLines - 1, 0); + } + } + else + { + MoveAlongMenuItemList(MENU_DOWN, 0); + } + + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + break; + } + + case KEY_HOME: + { + MoveAlongMenuItemList( MENU_UP * m_iCurrentlySelectedItemID, 0 ); + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + break; + } + + + case KEY_END: + { + MoveAlongMenuItemList(MENU_DOWN * ( GetItemCount() - m_iCurrentlySelectedItemID - 1), 0); + if ( m_MenuItems.IsValidIndex( m_iCurrentlySelectedItemID ) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + break; + } + } + + // don't chain back +} + +void Menu::OnHotKey(wchar_t unichar) +{ + // iterate the menu items looking for one with the matching hotkey + FOR_EACH_LL( m_MenuItems, i ) + { + MenuItem *panel = m_MenuItems[i]; + if (panel->IsVisible()) + { + Panel *hot = panel->HasHotkey(unichar); + if (hot) + { + // post a message to the menuitem telling it it's hotkey was pressed + PostMessage(hot, new KeyValues("Hotkey")); + return; + } + // if the menuitem is a cascading menuitem and it is open, check its hotkeys too + Menu *cascadingMenu = panel->GetMenu(); + if (cascadingMenu && cascadingMenu->IsVisible()) + { + cascadingMenu->OnKeyTyped(unichar); + } + } + } +} + +void Menu::OnTypeAhead(wchar_t unichar) +{ + // Don't do anything if the menu is empty since there cannot be a selected item. + if ( m_MenuItems.Count() <= 0) + return; + + // expire the type ahead buffer after 0.5 seconds + double tCurrentTime = Sys_FloatTime(); + if ( (tCurrentTime - m_fLastTypeAheadTime) > 0.5f ) + { + m_iNumTypeAheadChars = 0; + m_szTypeAheadBuf[0] = '\0'; + } + m_fLastTypeAheadTime = tCurrentTime; + + // add current character to the type ahead buffer + if ( m_iNumTypeAheadChars+1 < TYPEAHEAD_BUFSIZE ) + { + m_szTypeAheadBuf[m_iNumTypeAheadChars++] = unichar; + } + + int itemToSelect = m_iCurrentlySelectedItemID; + if ( itemToSelect < 0 || itemToSelect >= m_MenuItems.Count()) + { + itemToSelect = 0; + } + + int i = itemToSelect; + do + { + wchar_t menuItemName[255]; + m_MenuItems[i]->GetText(menuItemName, 254); + + // This is supposed to be case insensitive but we don't have a portable case + // insensitive wide-character routine. + if ( wcsncmp( m_szTypeAheadBuf, menuItemName, m_iNumTypeAheadChars) == 0 ) + { + itemToSelect = i; + break; + } + + i = (i+1) % m_MenuItems.Count(); + } while ( i != itemToSelect ); + + if ( itemToSelect >= 0 ) + { + SetCurrentlyHighlightedItem( itemToSelect ); + InvalidateLayout(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handle key presses, Activate shortcuts +// Input : code - +//----------------------------------------------------------------------------- +void Menu::OnKeyTyped(wchar_t unichar) +{ + if (! unichar) + { + return; + } + + switch( m_eTypeAheadMode ) + { + case HOT_KEY_MODE: + OnHotKey(unichar); + return; + + case TYPE_AHEAD_MODE: + OnTypeAhead(unichar); + return; + + case COMPAT_MODE: + default: + break; + } + + int itemToSelect = m_iCurrentlySelectedItemID; + if ( itemToSelect < 0 ) + { + itemToSelect = 0; + } + + int i; + wchar_t menuItemName[255]; + + i = itemToSelect + 1; + if ( i >= m_MenuItems.Count() ) + { + i = 0; + } + + while ( i != itemToSelect ) + { + m_MenuItems[i]->GetText(menuItemName, 254); + + if ( tolower( unichar ) == tolower( menuItemName[0] ) ) + { + itemToSelect = i; + break; + } + + i++; + if ( i >= m_MenuItems.Count() ) + { + i = 0; + } + } + + if ( itemToSelect >= 0 ) + { + SetCurrentlyHighlightedItem( itemToSelect ); + InvalidateLayout(); + } + + // don't chain back +} + + +void Menu::SetTypeAheadMode(MenuTypeAheadMode mode) +{ + m_eTypeAheadMode = mode; +} + +int Menu::GetTypeAheadMode() +{ + return m_eTypeAheadMode; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle the mouse wheel event, scroll the selection +//----------------------------------------------------------------------------- +void Menu::OnMouseWheeled(int delta) +{ + if (!m_pScroller->IsVisible()) + return; + + int val = m_pScroller->GetValue(); + val -= delta; + + m_pScroller->SetValue(val); + + // moving the slider redraws the scrollbar, + // and so we should redraw the menu since the + // menu draws the black border to the right of the scrollbar. + InvalidateLayout(); + + // don't chain back +} + + +//----------------------------------------------------------------------------- +// Purpose: Lose focus, hide menu +//----------------------------------------------------------------------------- +void Menu::OnKillFocus() +{ + // check to see if it's a child taking it + if (!input()->GetFocus() || !ipanel()->HasParent(input()->GetFocus(), GetVPanel())) + { + // if we don't accept keyboard input, then we have to ignore the killfocus if it's not actually being stolen + if (!IsKeyBoardInputEnabled() && !input()->GetFocus()) + return; + + // get the parent of this menu. + MenuItem *item = GetParentMenuItem(); + // if the parent is a menu item, this menu is a cascading menu + // if the panel that is getting focus is the parent menu, don't close this menu. + if ( (item) && (input()->GetFocus() == item->GetVParent()) ) + { + // if we are in mouse mode and we clicked on the menuitem that + // triggers the cascading menu, leave it open. + if (m_iInputMode == MOUSE) + { + // return the focus to the cascading menu. + MoveToFront(); + return; + } + } + + // forward the message to the parent. + PostActionSignal(new KeyValues("MenuClose")); + + // hide this menu + SetVisible(false); + } + +} + +namespace vgui +{ + +class CMenuManager +{ +public: + void AddMenu( Menu *m ) + { + if ( !m ) + return; + + int c = m_Menus.Count(); + for ( int i = 0 ; i < c; ++i ) + { + if ( m_Menus[ i ].Get() == m ) + return; + } + + DHANDLE< Menu > h; + h = m; + m_Menus.AddToTail( h ); + } + + void RemoveMenu( Menu *m ) + { + if ( !m ) + return; + + int c = m_Menus.Count(); + for ( int i = c - 1 ; i >= 0; --i ) + { + if ( m_Menus[ i ].Get() == m ) + { + m_Menus.Remove( i ); + return; + } + } + } + + void OnInternalMousePressed( Panel *other, MouseCode code ) + { + int c = m_Menus.Count(); + if ( !c ) + return; + + int x, y; + input()->GetCursorPos( x, y ); + + bool mouseInsideMenuRelatedPanel = false; + + for ( int i = c - 1; i >= 0 ; --i ) + { + Menu *m = m_Menus[ i ].Get(); + if ( !m ) + { + m_Menus.Remove( i ); + continue; + } + + // See if the mouse is within a menu + if ( IsWithinMenuOrRelative( m, x, y ) ) + { + mouseInsideMenuRelatedPanel = true; + } + } + + if ( mouseInsideMenuRelatedPanel ) + { + return; + } + + AbortMenus(); + } + + void AbortMenus() + { + // Close all of the menus + int c = m_Menus.Count(); + for ( int i = c - 1; i >= 0 ; --i ) + { + Menu *m = m_Menus[ i ].Get(); + if ( !m ) + { + continue; + } + + m_Menus.Remove( i ); + + // Force it to close + m->SetVisible( false ); + } + + m_Menus.RemoveAll(); + } + + bool IsWithinMenuOrRelative( Panel *panel, int x, int y ) + { + VPANEL topMost = panel->IsWithinTraverse( x, y, true ); + if ( topMost ) + { + // It's over the menu + if ( topMost == panel->GetVPanel() ) + { + return true; + } + + // It's over something which is parented to the menu (i.e., a menu item) + if ( ipanel()->HasParent( topMost, panel->GetVPanel() ) ) + { + return true; + } + } + + if ( panel->GetParent() ) + { + Panel *parent = panel->GetParent(); + + topMost = parent->IsWithinTraverse( x, y, true ); + + if ( topMost ) + { + if ( topMost == parent->GetVPanel() ) + { + return true; + } + + /* + // NOTE: this check used to not cast to MenuButton, but it seems wrong to me + // since if the mouse is over another child of the parent panel to the menu then + // the menu stays visible. I think this is bogus. + Panel *pTopMost = ipanel()->GetPanel(topMost, GetControlsModuleName()); + + if ( pTopMost && + ipanel()->HasParent( topMost, parent->GetVPanel() ) && + dynamic_cast< MenuButton * >( pTopMost ) ) + { + Msg( "topMost %s has parent %s\n", + ipanel()->GetName( topMost ), + parent->GetName() ); + + return true; + } + */ + } + } + + return false; + } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, char *pchName ) + { + validator.Push( "CMenuManager", this, pchName ); + m_Menus.Validate( validator, "m_Menus" ); + validator.Pop(); + } +#endif + +private: + + // List of visible menus + CUtlVector< DHANDLE< Menu > > m_Menus; +}; + + +// Singleton helper class +static CMenuManager g_MenuMgr; + +void ValidateMenuGlobals( CValidator &validator ) +{ +#ifdef DBGFLAG_VALIDATE + g_MenuMgr.Validate( validator, "g_MenuMgr" ); +#endif +} + +} // end namespace vgui + +//----------------------------------------------------------------------------- +// Purpose: Static method called on mouse released to see if Menu objects should be aborted +// Input : *other - +// code - +//----------------------------------------------------------------------------- +void Menu::OnInternalMousePressed( Panel *other, MouseCode code ) +{ + g_MenuMgr.OnInternalMousePressed( other, code ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set visibility of menu and its children as appropriate. +//----------------------------------------------------------------------------- +void Menu::SetVisible(bool state) +{ + if (state == IsVisible()) + return; + + if ( state == false ) + { + PostActionSignal(new KeyValues("MenuClose")); + CloseOtherMenus(NULL); + + SetCurrentlySelectedItem(-1); + + g_MenuMgr.RemoveMenu( this ); + } + else if ( state == true ) + { + MoveToFront(); + RequestFocus(); + + // Add to menu manager? + if ( m_bUseMenuManager ) + { + g_MenuMgr.AddMenu( this ); + } + } + + // must be after movetofront() + BaseClass::SetVisible(state); + _sizedForScrollBar = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::ApplySchemeSettings(IScheme *pScheme) +{ + BaseClass::ApplySchemeSettings(pScheme); + + SetFgColor(GetSchemeColor("Menu.TextColor", pScheme)); + SetBgColor(GetSchemeColor("Menu.BgColor", pScheme)); + + _borderDark = pScheme->GetColor("BorderDark", Color(255, 255, 255, 0)); + + FOR_EACH_LL( m_MenuItems, i ) + { + if( m_MenuItems[i]->IsCheckable() ) + { + int wide, tall; + m_MenuItems[i]->GetCheckImageSize( wide, tall ); + + m_iCheckImageWidth = max ( m_iCheckImageWidth, wide ); + } + } + _recalculateWidth = true; + CalculateWidth(); + + InvalidateLayout(); +} + +void Menu::SetBgColor( Color newColor ) +{ + BaseClass::SetBgColor( newColor ); + FOR_EACH_LL( m_MenuItems, i ) + { + if( m_MenuItems[i]->HasMenu() ) + { + m_MenuItems[i]->GetMenu()->SetBgColor( newColor ); + } + } +} + +void Menu::SetFgColor( Color newColor ) +{ + BaseClass::SetFgColor( newColor ); + FOR_EACH_LL( m_MenuItems, i ) + { + if( m_MenuItems[i]->HasMenu() ) + { + m_MenuItems[i]->GetMenu()->SetFgColor( newColor ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::SetBorder(class IBorder *border) +{ + Panel::SetBorder(border); +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to a MenuItem that is this menus parent, if it has one +//----------------------------------------------------------------------------- +MenuItem *Menu::GetParentMenuItem() +{ + return dynamic_cast<MenuItem *>(GetParent()); +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the menu when an item has been selected +//----------------------------------------------------------------------------- +void Menu::OnMenuItemSelected(Panel *panel) +{ + SetVisible(false); + m_pScroller->SetVisible(false); + + // chain this message up through the hierarchy so + // all the parent menus will close + + // get the parent of this menu. + MenuItem *item = GetParentMenuItem(); + // if the parent is a menu item, this menu is a cascading menu + if (item) + { + // get the parent of the menuitem. it should be a menu. + Menu *parentMenu = item->GetParentMenu(); + if (parentMenu) + { + // send the message to this parent menu + KeyValues *kv = new KeyValues("MenuItemSelected"); + kv->SetPtr("panel", panel); + ivgui()->PostMessage(parentMenu->GetVPanel(), kv, GetVPanel()); + } + } + + bool activeItemSet = false; + + FOR_EACH_LL( m_MenuItems, i ) + { + if( m_MenuItems[i] == panel ) + { + activeItemSet = true; + m_iActivatedItem = i; + break; + } + } + if( !activeItemSet ) + { + FOR_EACH_LL( m_MenuItems, i ) + { + if(m_MenuItems[i]->HasMenu() ) + { + /* + // GetActiveItem needs to return -1 or similar if it hasn't been set... + if( m_MenuItems[i]->GetActiveItem() ) + { + m_iActivatedItem = m_MenuItems[i]->GetActiveItem(); + }*/ + } + } + } + + // also pass it to the parent so they can respond if they like + if (GetVParent()) + { + KeyValues *kv = new KeyValues("MenuItemSelected"); + kv->SetPtr("panel", panel); + + ivgui()->PostMessage(GetVParent(), kv, GetVPanel()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Menu::GetActiveItem() +{ + return m_iActivatedItem; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +KeyValues *Menu::GetItemUserData(int itemID) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem && menuItem->IsEnabled()) + { + return menuItem->GetUserData(); + } + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +void Menu::GetItemText(int itemID, wchar_t *text, int bufLenInBytes) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + if (menuItem) + { + menuItem->GetText(text, bufLenInBytes); + return; + } + } + text[0] = 0; +} + +void Menu::GetItemText(int itemID, char *text, int bufLenInBytes) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + if (menuItem) + { + menuItem->GetText( text, bufLenInBytes ); + return; + } + } + text[0] = 0; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Activate the n'th item in the menu list, as if that menu item had been selected by the user +//----------------------------------------------------------------------------- +void Menu::ActivateItem(int itemID) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem && menuItem->IsEnabled()) + { + menuItem->FireActionSignal(); + m_iActivatedItem = itemID; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::SilentActivateItem(int itemID) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + // make sure its enabled since disabled items get highlighted. + if (menuItem && menuItem->IsEnabled()) + { + m_iActivatedItem = itemID; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::ActivateItemByRow(int row) +{ + if (m_SortedItems.IsValidIndex(row)) + { + ActivateItem(m_SortedItems[row]); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return the number of items currently in the menu list +//----------------------------------------------------------------------------- +int Menu::GetItemCount() +{ + return m_MenuItems.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Menu::GetMenuID(int index) +{ + if ( !m_SortedItems.IsValidIndex(index) ) + return m_MenuItems.InvalidIndex(); + + return m_SortedItems[index]; +} + +//----------------------------------------------------------------------------- +// Purpose: Return the number of items currently visible in the menu list +//----------------------------------------------------------------------------- +int Menu::GetCurrentlyVisibleItemsCount() +{ + if (m_MenuItems.Count() < m_iNumVisibleLines) + { + int cMenuItems = 0; + FOR_EACH_LL(m_MenuItems, i) + { + if (m_MenuItems[i]->IsVisible()) + { + ++cMenuItems; + } + } + + return cMenuItems; + } + return m_iNumVisibleLines; +} + +//----------------------------------------------------------------------------- +// Purpose: Enables/disables choices in the list +// itemText - string name of item in the list +// state - true enables, false disables +//----------------------------------------------------------------------------- +void Menu::SetItemEnabled(const char *itemName, bool state) +{ + FOR_EACH_LL( m_MenuItems, i ) + { + if ((Q_stricmp(itemName, m_MenuItems[i]->GetName())) == 0) + { + m_MenuItems[i]->SetEnabled(state); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Enables/disables choices in the list +//----------------------------------------------------------------------------- +void Menu::SetItemEnabled(int itemID, bool state) +{ + if ( !m_MenuItems.IsValidIndex(itemID) ) + return; + + m_MenuItems[itemID]->SetEnabled(state); +} + +//----------------------------------------------------------------------------- +// Purpose: shows/hides choices in the list +//----------------------------------------------------------------------------- +void Menu::SetItemVisible(const char *itemName, bool state) +{ + FOR_EACH_LL( m_MenuItems, i ) + { + if ((Q_stricmp(itemName, m_MenuItems[i]->GetName())) == 0) + { + m_MenuItems[i]->SetVisible(state); + InvalidateLayout(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: shows/hides choices in the list +//----------------------------------------------------------------------------- +void Menu::SetItemVisible(int itemID, bool state) +{ + if ( !m_MenuItems.IsValidIndex(itemID) ) + return; + + m_MenuItems[itemID]->SetVisible(state); +} + +//----------------------------------------------------------------------------- +// Purpose: Make the scroll bar visible and narrow the menu +// also make items visible or invisible in the list as appropriate +//----------------------------------------------------------------------------- +void Menu::AddScrollBar() +{ + m_pScroller->SetVisible(true); + _sizedForScrollBar = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Make the scroll bar invisible and widen the menu +//----------------------------------------------------------------------------- +void Menu::RemoveScrollBar() +{ + m_pScroller->SetVisible(false); + _sizedForScrollBar = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Invalidate layout if the slider is moved so items scroll +//----------------------------------------------------------------------------- +void Menu::OnSliderMoved() +{ + CloseOtherMenus(NULL); // close any cascading menus + + // Invalidate so we redraw the menu! + InvalidateLayout(); + Repaint(); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle into mouse mode. +//----------------------------------------------------------------------------- +void Menu::OnCursorMoved(int x, int y) +{ + m_iInputMode = MOUSE; + + // chain up + CallParentFunction(new KeyValues("OnCursorMoved", "x", x, "y", y)); + RequestFocus(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Toggle into keyboard mode. +//----------------------------------------------------------------------------- +void Menu::OnKeyCodePressed(KeyCode code) +{ + m_iInputMode = KEYBOARD; + // send the message to this parent in case this is a cascading menu + if (GetVParent()) + { + ivgui()->PostMessage(GetVParent(), new KeyValues("KeyModeSet"), GetVPanel()); + } + + BaseClass::OnKeyCodePressed( code ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the item currently highlighted in the menu by ptr +//----------------------------------------------------------------------------- +void Menu::SetCurrentlySelectedItem(MenuItem *item) +{ + int itemNum = -1; + // find it in our list of menuitems + FOR_EACH_LL( m_MenuItems, i ) + { + MenuItem *child = m_MenuItems[i]; + if (child == item) + { + itemNum = i; + break; + } + } + Assert( itemNum >= 0 ); + + SetCurrentlySelectedItem(itemNum); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Menu::ClearCurrentlyHighlightedItem() +{ + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->DisarmItem(); + } + m_iCurrentlySelectedItemID = m_MenuItems.InvalidIndex(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the item currently highlighted in the menu by index +//----------------------------------------------------------------------------- +void Menu::SetCurrentlySelectedItem(int itemID) +{ + // dont deselect if its the same item + if (itemID == m_iCurrentlySelectedItemID) + return; + + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->DisarmItem(); + } + + PostActionSignal(new KeyValues("MenuItemHighlight", "itemID", itemID)); + m_iCurrentlySelectedItemID = itemID; +} + +//----------------------------------------------------------------------------- +// This will set the item to be currenly selected and highlight it +// will not open cascading menu. This was added for comboboxes +// to have the combobox item highlighted in the menu when they open the +// dropdown. +//----------------------------------------------------------------------------- +void Menu::SetCurrentlyHighlightedItem(int itemID) +{ + SetCurrentlySelectedItem(itemID); + int row = m_SortedItems.Find(itemID); + // If we have no items, then row will be -1. The dev console, for example... + Assert( ( m_SortedItems.Count() == 0 ) || ( row != -1 ) ); + if ( row == -1 ) + return; + + // if there is a scroll bar, and we scroll off lets move it. + if ( m_pScroller->IsVisible() ) + { + // now if we are off the scroll bar, it means we moved the scroll bar + // by hand or set the item off the list + // so just snap the scroll bar straight to the item. + if ( ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1 ) || + ( row < m_pScroller->GetValue() ) ) + { + if ( !m_pScroller->IsVisible() ) + return; + + m_pScroller->SetValue(row); + } + } + + if ( m_MenuItems.IsValidIndex(m_iCurrentlySelectedItemID) ) + { + if ( !m_MenuItems[m_iCurrentlySelectedItemID]->IsArmed() ) + { + m_MenuItems[m_iCurrentlySelectedItemID]->ArmItem(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int Menu::GetCurrentlyHighlightedItem() +{ + return m_iCurrentlySelectedItemID; +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to cursor entering a menuItem. +//----------------------------------------------------------------------------- +void Menu::OnCursorEnteredMenuItem(int VPanel) +{ + VPANEL menuItem = (VPANEL)VPanel; + // if we are in mouse mode + if (m_iInputMode == MOUSE) + { + MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName())); + // arm the menu + item->ArmItem(); + // open the cascading menu if there is one. + item->OpenCascadeMenu(); + SetCurrentlySelectedItem(item); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to cursor exiting a menuItem +//----------------------------------------------------------------------------- +void Menu::OnCursorExitedMenuItem(int VPanel) +{ + VPANEL menuItem = (VPANEL)VPanel; + // only care if we are in mouse mode + if (m_iInputMode == MOUSE) + { + MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName())); + // unhighlight the item. + // note menuItems with cascading menus will stay lit. + item->DisarmItem(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Move up or down one in the list of items in the menu +// Direction is MENU_UP or MENU_DOWN +//----------------------------------------------------------------------------- +void Menu::MoveAlongMenuItemList(int direction, int loopCount) +{ + // Early out if no menu items to scroll through + if (m_MenuItems.Count() <= 0) + return; + + int itemID = m_iCurrentlySelectedItemID; + int row = m_SortedItems.Find(itemID); + row += direction; + + if ( row > m_SortedItems.Count() - 1 ) + { + if ( m_pScroller->IsVisible() ) + { + // stop at bottom of scrolled list + row = m_SortedItems.Count() - 1; + } + else + { + // if no scroll bar we circle around + row = 0; + } + } + else if (row < 0) + { + if ( m_pScroller->IsVisible() ) + { + // stop at top of scrolled list + row = m_pScroller->GetValue(); + } + else + { + // if no scroll bar circle around + row = m_SortedItems.Count()-1; + } + } + + // if there is a scroll bar, and we scroll off lets move it. + if ( m_pScroller->IsVisible() ) + { + if ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1) + { + int val = m_pScroller->GetValue(); + val -= -direction; + + m_pScroller->SetValue(val); + + // moving the slider redraws the scrollbar, + // and so we should redraw the menu since the + // menu draws the black border to the right of the scrollbar. + InvalidateLayout(); + } + else if ( row < m_pScroller->GetValue() ) + { + int val = m_pScroller->GetValue(); + val -= -direction; + + m_pScroller->SetValue(val); + + // moving the slider redraws the scrollbar, + // and so we should redraw the menu since the + // menu draws the black border to the right of the scrollbar. + InvalidateLayout(); + } + + // now if we are still off the scroll bar, it means we moved the scroll bar + // by hand and created a situation in which we moved an item down, but the + // scroll bar is already too far down and should scroll up or vice versa + // so just snap the scroll bar straight to the item. + if ( ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1) || + ( row < m_pScroller->GetValue() ) ) + { + m_pScroller->SetValue(row); + } + } + + // switch it back to an itemID from row + if ( m_SortedItems.IsValidIndex( row ) ) + { + SetCurrentlySelectedItem( m_SortedItems[row] ); + } + + // don't allow us to loop around more than once + if (loopCount < m_MenuItems.Count()) + { + // see if the text is empty, if so skip + wchar_t text[256]; + m_MenuItems[m_iCurrentlySelectedItemID]->GetText(text, 255); + if (text[0] == 0 || !m_MenuItems[m_iCurrentlySelectedItemID]->IsVisible()) + { + // menu item is empty, keep moving along + MoveAlongMenuItemList(direction, loopCount + 1); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Return which type of events the menu is currently interested in +// MenuItems need to know because behaviour is different depending on mode. +//----------------------------------------------------------------------------- +int Menu::GetMenuMode() +{ + return m_iInputMode; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the menu to key mode if a child menu goes into keymode +// This mode change has to be chained up through the menu heirarchy +// so cascading menus will work when you do a bunch of stuff in keymode +// in high level menus and then switch to keymode in lower level menus. +//----------------------------------------------------------------------------- +void Menu::OnKeyModeSet() +{ + m_iInputMode = KEYBOARD; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the checked state of a menuItem +//----------------------------------------------------------------------------- +void Menu::SetMenuItemChecked(int itemID, bool state) +{ + m_MenuItems[itemID]->SetChecked(state); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if item is checked. +//----------------------------------------------------------------------------- +bool Menu::IsChecked(int itemID) +{ + return m_MenuItems[itemID]->IsChecked(); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the minmum width the menu has to be. This +// is useful if you have a menu that is sized to the largest item in it +// but you don't want the menu to be thinner than the menu button +//----------------------------------------------------------------------------- +void Menu::SetMinimumWidth(int width) +{ + m_iMinimumWidth = width; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the minmum width the menu +//----------------------------------------------------------------------------- +int Menu::GetMinimumWidth() +{ + return m_iMinimumWidth; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +//----------------------------------------------------------------------------- +void Menu::AddSeparator() +{ + int lastID = m_MenuItems.Count() - 1; + m_Separators.AddToTail( lastID ); + m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) ); +} + +void Menu::AddSeparatorAfterItem( int itemID ) +{ + Assert( m_MenuItems.IsValidIndex( itemID ) ); + m_Separators.AddToTail( itemID ); + m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) ); +} + +void Menu::MoveMenuItem( int itemID, int moveBeforeThisItemID ) +{ + int c = m_SortedItems.Count(); + int i; + for ( i = 0; i < c; ++i ) + { + if ( m_SortedItems[i] == itemID ) + { + m_SortedItems.Remove( i ); + break; + } + } + + // Didn't find it + if ( i >= c ) + { + return; + } + + // Now find insert pos + c = m_SortedItems.Count(); + for ( i = 0; i < c; ++i ) + { + if ( m_SortedItems[i] == moveBeforeThisItemID ) + { + m_SortedItems.InsertBefore( i, itemID ); + break; + } + } +} + +void Menu::SetFont( HFont font ) +{ + m_hItemFont = font; + if ( font ) + { + m_iMenuItemHeight = surface()->GetFontTall( font ) + 2; + } + InvalidateLayout(); +} + + +void Menu::SetCurrentKeyBinding( int itemID, char const *hotkey ) +{ + if ( m_MenuItems.IsValidIndex( itemID ) ) + { + MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); + menuItem->SetCurrentKeyBinding( hotkey ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Static method to display a context menu +// Input : *parent - +// *menu - +//----------------------------------------------------------------------------- +void Menu::PlaceContextMenu( Panel *parent, Menu *menu ) +{ + Assert( parent ); + Assert( menu ); + if ( !menu || !parent ) + return; + + menu->SetVisible(false); + menu->SetParent( parent ); + menu->AddActionSignalTarget( parent ); + + // get cursor position, this is local to this text edit window + int cursorX, cursorY; + input()->GetCursorPos(cursorX, cursorY); + + menu->SetVisible(true); + + // relayout the menu immediately so that we know it's size + menu->InvalidateLayout(true); + int menuWide, menuTall; + menu->GetSize(menuWide, menuTall); + + // work out where the cursor is and therefore the best place to put the menu + int wide, tall; + surface()->GetScreenSize(wide, tall); + + if (wide - menuWide > cursorX) + { + // menu hanging right + if (tall - menuTall > cursorY) + { + // menu hanging down + menu->SetPos(cursorX, cursorY); + } + else + { + // menu hanging up + menu->SetPos(cursorX, cursorY - menuTall); + } + } + else + { + // menu hanging left + if (tall - menuTall > cursorY) + { + // menu hanging down + menu->SetPos(cursorX - menuWide, cursorY); + } + else + { + // menu hanging up + menu->SetPos(cursorX - menuWide, cursorY - menuTall); + } + } + + menu->RequestFocus(); +} + +void Menu::SetUseFallbackFont( bool bState, HFont hFallback ) +{ + m_hFallbackItemFont = hFallback; + m_bUseFallbackFont = bState; +} + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Run a global validation pass on all of our data structures and memory +// allocations. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void Menu::Validate( CValidator &validator, char *pchName ) +{ + validator.Push( "vgui::Menu", this, pchName ); + + m_MenuItems.Validate( validator, "m_MenuItems" ); + m_SortedItems.Validate( validator, "m_SortedItems" ); + + BaseClass::Validate( validator, "vgui::Menu" ); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE |