From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/vgui2/vgui_controls/Menu.cpp | 5406 +++++++++++++++++------------------ 1 file changed, 2703 insertions(+), 2703 deletions(-) (limited to 'mp/src/vgui2/vgui_controls/Menu.cpp') 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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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 -- cgit v1.2.3