From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/vgui2/vgui_controls/Menu.cpp | 2703 +++++++++++++++++++++++++++++++++++ 1 file changed, 2703 insertions(+) create mode 100644 mp/src/vgui2/vgui_controls/Menu.cpp (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 new file mode 100644 index 00000000..ceb875a4 --- /dev/null +++ b/mp/src/vgui2/vgui_controls/Menu.cpp @@ -0,0 +1,2703 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vgui_controls/pch_vgui_controls.h" + +// memdbgon must be the last include file in a .cpp file +#include "tier0/memdbgon.h" +#define MENU_SEPARATOR_HEIGHT 3 + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Purpose: divider line in a menu +//----------------------------------------------------------------------------- +class vgui::MenuSeparator : public Panel +{ +public: + DECLARE_CLASS_SIMPLE( MenuSeparator, Panel ); + + MenuSeparator( Panel *parent, char const *panelName ) : + BaseClass( parent, panelName ) + { + SetPaintEnabled( true ); + SetPaintBackgroundEnabled( true ); + SetPaintBorderEnabled( false ); + } + + virtual void Paint() + { + int w, h; + GetSize( w, h ); + + surface()->DrawSetColor( GetFgColor() ); + surface()->DrawFilledRect( 4, 1, w-1, 2 ); + } + + virtual void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + SetFgColor( pScheme->GetColor( "Menu.SeparatorColor", Color( 142, 142, 142, 255 ) ) ); + SetBgColor( pScheme->GetColor( "Menu.BgColor", Color( 0, 0, 0, 255 ) ) ); + } +}; + +DECLARE_BUILD_FACTORY( Menu ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +Menu::Menu(Panel *parent, const char *panelName) : Panel(parent, panelName) +{ + m_Alignment = Label::a_west; + m_iFixedWidth = 0; + m_iMinimumWidth = 0; + m_iNumVisibleLines = -1; // No limit + m_iCurrentlySelectedItemID = m_MenuItems.InvalidIndex(); + m_pScroller = new ScrollBar(this, "MenuScrollBar", true); + m_pScroller->SetVisible(false); + m_pScroller->AddActionSignalTarget(this); + _sizedForScrollBar = false; + SetZPos(1); + SetVisible(false); + MakePopup(false); + SetParent(parent); + _recalculateWidth = true; + m_bUseMenuManager = true; + m_iInputMode = MOUSE; + m_iCheckImageWidth = 0; + m_iActivatedItem = 0; + + m_bUseFallbackFont = false; + m_hFallbackItemFont = INVALID_FONT; + + if (IsProportional()) + { + m_iMenuItemHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), DEFAULT_MENU_ITEM_HEIGHT ); + } + else + { + m_iMenuItemHeight = DEFAULT_MENU_ITEM_HEIGHT; + } + m_hItemFont = INVALID_FONT; + + + m_eTypeAheadMode = COMPAT_MODE; + m_szTypeAheadBuf[0] = '\0'; + m_iNumTypeAheadChars = 0; + m_fLastTypeAheadTime = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +Menu::~Menu() +{ + delete m_pScroller; +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all menu items from the menu. +//----------------------------------------------------------------------------- +void Menu::DeleteAllItems() +{ + FOR_EACH_LL( m_MenuItems, i ) + { + m_MenuItems[i]->MarkForDeletion(); + } + + m_MenuItems.RemoveAll(); + m_SortedItems.RemoveAll(); + m_VisibleSortedItems.RemoveAll(); + m_Separators.RemoveAll(); + int c = m_SeparatorPanels.Count(); + for ( int i = 0 ; i < c; ++i ) + { + m_SeparatorPanels[ i ]->MarkForDeletion(); + } + m_SeparatorPanels.RemoveAll(); + InvalidateLayout(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( MenuItem *panel ) +{ + panel->SetParent( this ); + MEM_ALLOC_CREDIT(); + int itemID = m_MenuItems.AddToTail( panel ); + m_SortedItems.AddToTail(itemID); + InvalidateLayout(false); + _recalculateWidth = true; + panel->SetContentAlignment( m_Alignment ); + if ( INVALID_FONT != m_hItemFont ) + { + panel->SetFont( m_hItemFont ); + } + if ( m_bUseFallbackFont && INVALID_FONT != m_hFallbackItemFont ) + { + Label *l = panel; + TextImage *ti = l->GetTextImage(); + if ( ti ) + { + ti->SetUseFallbackFont( m_bUseFallbackFont, m_hFallbackItemFont ); + } + } + + if ( panel->GetHotKey() ) + { + SetTypeAheadMode( HOT_KEY_MODE ); + } + + return itemID; +} + + +//----------------------------------------------------------------------------- +// Remove a single item +//----------------------------------------------------------------------------- +void Menu::DeleteItem( int itemID ) +{ + // FIXME: This doesn't work with separator panels yet + Assert( m_SeparatorPanels.Count() == 0 ); + + m_MenuItems[itemID]->MarkForDeletion(); + m_MenuItems.Remove( itemID ); + + m_SortedItems.FindAndRemove( itemID ); + m_VisibleSortedItems.FindAndRemove( itemID ); + + InvalidateLayout(false); + _recalculateWidth = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *item - MenuItem +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *userData - any user data associated with this menu item +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItemCharCommand(MenuItem *item, const char *command, Panel *target, const KeyValues *userData) +{ + item->SetCommand(command); + item->AddActionSignalTarget( target ); + item->SetUserData(userData); + return AddMenuItem( item ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItemKeyValuesCommand( MenuItem *item, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + item->SetCommand(message); + item->AddActionSignalTarget(target); + item->SetUserData(userData); + return AddMenuItem(item); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// Output: itemID - ID of this item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + return AddMenuItem(itemText, itemText, command, target, userData ) ; +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText ); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText ); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + return AddMenuItem(itemText, itemText, message, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be the text of the command sent when the +// item is selected. +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddMenuItem( const char *itemText, Panel *target , const KeyValues *userData ) +{ + return AddMenuItem(itemText, itemText, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true); + return AddMenuItemCharCommand(item, command, target, userData); +} + +int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true); + return AddMenuItemCharCommand(item, command, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData ) +{ + return AddCheckableMenuItem(itemText, itemText, command, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) +{ + return AddCheckableMenuItem(itemText, itemText, message, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a checkable menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be the text of the command sent when the +// item is selected. +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCheckableMenuItem( const char *itemText, Panel *target, const KeyValues *userData ) +{ + return AddCheckableMenuItem(itemText, itemText, target, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, itemText, cascadeMenu ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) +{ + MenuItem *item = new MenuItem(this, itemName, wszItemText, cascadeMenu ); + return AddMenuItemCharCommand(item, command, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *command - Command text to be sent when menu item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) +{ + return AddCascadingMenuItem( itemText, itemText, command, target, cascadeMenu, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemName - Name of item +// *itemText - Name of item text that will appear in the manu. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem( this, itemName, itemText, cascadeMenu); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + MenuItem *item = new MenuItem( this, itemName, wszItemText, cascadeMenu); + return AddMenuItemKeyValuesCommand(item, message, target, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be used as the name of the menu item panel. +// *message - pointer to the message to send when the item is selected +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + return AddCascadingMenuItem(itemText, itemText, message, target, cascadeMenu, userData ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a Cascading menu item to the menu. +// Input : *itemText - Name of item text that will appear in the manu. +// This will also be the text of the command sent when the +// item is selected. +// *target - Target panel of the command +// *cascadeMenu - if the menu item opens a cascading menu, this is a +// ptr to the menu that opens on selecting the item +//----------------------------------------------------------------------------- +int Menu::AddCascadingMenuItem( const char *itemText, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) +{ + return AddCascadingMenuItem(itemText, itemText, target, cascadeMenu, userData); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the values of a menu item at the specified index +// Input : index - the index of this item entry +// *message - pointer to the message to send when the item is selected +//----------------------------------------------------------------------------- +void Menu::UpdateMenuItem(int itemID, const char *itemText, KeyValues *message, const KeyValues *userData) +{ + Assert( m_MenuItems.IsValidIndex(itemID) ); + if ( m_MenuItems.IsValidIndex(itemID) ) + { + MenuItem *menuItem = dynamic_cast(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