aboutsummaryrefslogtreecommitdiff
path: root/mp/src/vgui2/vgui_controls/Menu.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/vgui2/vgui_controls/Menu.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/vgui2/vgui_controls/Menu.cpp')
-rw-r--r--mp/src/vgui2/vgui_controls/Menu.cpp2703
1 files changed, 2703 insertions, 0 deletions
diff --git a/mp/src/vgui2/vgui_controls/Menu.cpp b/mp/src/vgui2/vgui_controls/Menu.cpp
new file mode 100644
index 00000000..ceb875a4
--- /dev/null
+++ b/mp/src/vgui2/vgui_controls/Menu.cpp
@@ -0,0 +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