|
|
//========= Copyright (c) 1996-2005, 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
#if defined(_PS3) || defined(POSIX)
//!!BUG!! "wcsnicmp unsupported on PS3"
#ifdef wcsicmp
#undef wcsicmp
#endif
#define wcsnicmp wcsncmp
#endif
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_iInputMode = MOUSE; m_iCheckImageWidth = 0; m_iActivatedItem = 0;
m_bUseFallbackFont = false; m_hFallbackItemFont = INVALID_FONT;
if (IsProportional()) { m_iMenuItemHeight = scheme()->GetProportionalScaledValueEx( GetScheme(), DEFAULT_MENU_ITEM_HEIGHT ); } else { m_iMenuItemHeight = DEFAULT_MENU_ITEM_HEIGHT; } m_hItemFont = INVALID_FONT;
m_eTypeAheadMode = COMPAT_MODE; m_szTypeAheadBuf[0] = '\0'; m_iNumTypeAheadChars = 0; m_fLastTypeAheadTime = 0.0f; }
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
Menu::~Menu() { delete m_pScroller; }
//-----------------------------------------------------------------------------
// Purpose: Remove all menu items from the menu.
//-----------------------------------------------------------------------------
void Menu::DeleteAllItems() { FOR_EACH_LL( m_MenuItems, i ) { m_MenuItems[i]->MarkForDeletion(); } m_MenuItems.RemoveAll(); m_SortedItems.RemoveAll(); m_VisibleSortedItems.RemoveAll(); m_Separators.RemoveAll(); int c = m_SeparatorPanels.Count(); for ( int i = 0 ; i < c; ++i ) { m_SeparatorPanels[ i ]->MarkForDeletion(); } m_SeparatorPanels.RemoveAll(); InvalidateLayout(); }
//-----------------------------------------------------------------------------
// Purpose: Add a menu item to the menu.
//-----------------------------------------------------------------------------
int Menu::AddMenuItem( MenuItem *panel ) { panel->SetParent( this ); MEM_ALLOC_CREDIT(); int itemID = m_MenuItems.AddToTail( panel ); m_SortedItems.AddToTail(itemID); InvalidateLayout(false); _recalculateWidth = true; panel->SetContentAlignment( m_Alignment ); if ( INVALID_FONT != m_hItemFont ) { panel->SetFont( m_hItemFont ); } if ( m_bUseFallbackFont && INVALID_FONT != m_hFallbackItemFont ) { Label *l = panel; TextImage *ti = l->GetTextImage(); if ( ti ) { ti->SetUseFallbackFont( m_bUseFallbackFont, m_hFallbackItemFont ); } }
if ( panel->GetHotKey() ) { SetTypeAheadMode( HOT_KEY_MODE ); }
return itemID; }
//-----------------------------------------------------------------------------
// Remove a single item
//-----------------------------------------------------------------------------
void Menu::DeleteItem( int itemID ) { // FIXME: This doesn't work with separator panels yet
Assert( m_SeparatorPanels.Count() == 0 );
m_MenuItems[itemID]->MarkForDeletion(); m_MenuItems.Remove( itemID );
m_SortedItems.FindAndRemove( itemID ); m_VisibleSortedItems.FindAndRemove( itemID );
InvalidateLayout(false); _recalculateWidth = true; }
//-----------------------------------------------------------------------------
// Purpose: Add a menu item to the menu.
// Input : *item - MenuItem
// *command - Command text to be sent when menu item is selected
// *target - Target panel of the command
// *userData - any user data associated with this menu item
// Output: itemID - ID of this item
//-----------------------------------------------------------------------------
int Menu::AddMenuItemCharCommand(MenuItem *item, const char *command, Panel *target, const KeyValues *userData) { item->SetCommand(command); item->AddActionSignalTarget( target ); item->SetUserData(userData); return AddMenuItem( item ); }
//-----------------------------------------------------------------------------
// Purpose: Add a menu item to the menu.
// Input : *itemName - Name of item
// *itemText - Name of item text that will appear in the manu.
// *message - pointer to the message to send when the item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
// Output: itemID - ID of this item
//-----------------------------------------------------------------------------
int Menu::AddMenuItemKeyValuesCommand( MenuItem *item, KeyValues *message, Panel *target, const KeyValues *userData ) { item->SetCommand(message); item->AddActionSignalTarget(target); item->SetUserData(userData); return AddMenuItem(item); }
//-----------------------------------------------------------------------------
// Purpose: Add a menu item to the menu.
// Input : *itemName - Name of item
// *itemText - Name of item text that will appear in the manu.
// *command - Command text to be sent when menu item is selected
// *target - Target panel of the command
// Output: itemID - ID of this item
//-----------------------------------------------------------------------------
int Menu::AddMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, itemText ); return AddMenuItemCharCommand(item, command, target, userData); }
int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, wszItemText ); return AddMenuItemCharCommand(item, command, target, userData); }
//-----------------------------------------------------------------------------
// Purpose: Add a menu item to the menu.
// Input : *itemText - Name of item text that will appear in the manu.
// This will also be used as the name of the menu item panel.
// *command - Command text to be sent when menu item is selected
// *target - Target panel of the command
// Output: itemID - ID of this item
//-----------------------------------------------------------------------------
int Menu::AddMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData ) { return AddMenuItem(itemText, itemText, command, target, userData ) ; }
//-----------------------------------------------------------------------------
// Purpose: Add a menu item to the menu.
// Input : *itemName - Name of item
// *itemText - Name of item text that will appear in the manu.
// *message - pointer to the message to send when the item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, itemText ); return AddMenuItemKeyValuesCommand(item, message, target, userData); }
int Menu::AddMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, wszItemText ); return AddMenuItemKeyValuesCommand(item, message, target, userData); }
//-----------------------------------------------------------------------------
// Purpose: Add a menu item to the menu.
// Input : *itemText - Name of item text that will appear in the manu.
// This will also be used as the name of the menu item panel.
// *message - pointer to the message to send when the item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) { return AddMenuItem(itemText, itemText, message, target, userData ); }
//-----------------------------------------------------------------------------
// Purpose: Add a menu item to the menu.
// Input : *itemText - Name of item text that will appear in the manu.
// This will also be the text of the command sent when the
// item is selected.
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddMenuItem( const char *itemText, Panel *target , const KeyValues *userData ) { return AddMenuItem(itemText, itemText, target, userData ); }
//-----------------------------------------------------------------------------
// Purpose: Add a checkable menu item to the menu.
// Input : *itemName - Name of item
// *itemText - Name of item text that will appear in the manu.
// *command - Command text to be sent when menu item is selected
// *target - Target panel of the command
//-----------------------------------------------------------------------------
int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true); return AddMenuItemCharCommand(item, command, target, userData); }
int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true); return AddMenuItemCharCommand(item, command, target, userData); }
//-----------------------------------------------------------------------------
// Purpose: Add a checkable menu item to the menu.
// Input : *itemText - Name of item text that will appear in the manu.
// This will also be used as the name of the menu item panel.
// *command - Command text to be sent when menu item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddCheckableMenuItem( const char *itemText, const char *command, Panel *target, const KeyValues *userData ) { return AddCheckableMenuItem(itemText, itemText, command, target, userData ); }
//-----------------------------------------------------------------------------
// Purpose: Add a checkable menu item to the menu.
// Input : *itemName - Name of item
// *itemText - Name of item text that will appear in the manu.
// *message - pointer to the message to send when the item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddCheckableMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, itemText, NULL, true); return AddMenuItemKeyValuesCommand(item, message, target, userData); }
int Menu::AddCheckableMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, wszItemText, NULL, true); return AddMenuItemKeyValuesCommand(item, message, target, userData); }
//-----------------------------------------------------------------------------
// Purpose: Add a checkable menu item to the menu.
// Input : *itemText - Name of item text that will appear in the manu.
// This will also be used as the name of the menu item panel.
// *message - pointer to the message to send when the item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddCheckableMenuItem( const char *itemText, KeyValues *message, Panel *target, const KeyValues *userData ) { return AddCheckableMenuItem(itemText, itemText, message, target, userData ); }
//-----------------------------------------------------------------------------
// Purpose: Add a checkable menu item to the menu.
// Input : *itemText - Name of item text that will appear in the manu.
// This will also be the text of the command sent when the
// item is selected.
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddCheckableMenuItem( const char *itemText, Panel *target, const KeyValues *userData ) { return AddCheckableMenuItem(itemText, itemText, target, userData ); }
//-----------------------------------------------------------------------------
// Purpose: Add a Cascading menu item to the menu.
// Input : *itemName - Name of item
// *itemText - Name of item text that will appear in the manu.
// *command - Command text to be sent when menu item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, itemText, cascadeMenu ); return AddMenuItemCharCommand(item, command, target, userData); }
int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) { MenuItem *item = new MenuItem(this, itemName, wszItemText, cascadeMenu ); return AddMenuItemCharCommand(item, command, target, userData); }
//-----------------------------------------------------------------------------
// Purpose: Add a Cascading menu item to the menu.
// Input : *itemText - Name of item text that will appear in the manu.
// This will also be used as the name of the menu item panel.
// *command - Command text to be sent when menu item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddCascadingMenuItem( const char *itemText, const char *command, Panel *target, Menu *cascadeMenu , const KeyValues *userData ) { return AddCascadingMenuItem( itemText, itemText, command, target, cascadeMenu, userData ); }
//-----------------------------------------------------------------------------
// Purpose: Add a Cascading menu item to the menu.
// Input : *itemName - Name of item
// *itemText - Name of item text that will appear in the manu.
// *message - pointer to the message to send when the item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddCascadingMenuItem( const char *itemName, const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) { MenuItem *item = new MenuItem( this, itemName, itemText, cascadeMenu); return AddMenuItemKeyValuesCommand(item, message, target, userData); }
int Menu::AddCascadingMenuItem( const char *itemName, const wchar_t *wszItemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) { MenuItem *item = new MenuItem( this, itemName, wszItemText, cascadeMenu); return AddMenuItemKeyValuesCommand(item, message, target, userData); }
//-----------------------------------------------------------------------------
// Purpose: Add a Cascading menu item to the menu.
// Input : *itemText - Name of item text that will appear in the manu.
// This will also be used as the name of the menu item panel.
// *message - pointer to the message to send when the item is selected
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddCascadingMenuItem( const char *itemText, KeyValues *message, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) { return AddCascadingMenuItem(itemText, itemText, message, target, cascadeMenu, userData ); }
//-----------------------------------------------------------------------------
// Purpose: Add a Cascading menu item to the menu.
// Input : *itemText - Name of item text that will appear in the manu.
// This will also be the text of the command sent when the
// item is selected.
// *target - Target panel of the command
// *cascadeMenu - if the menu item opens a cascading menu, this is a
// ptr to the menu that opens on selecting the item
//-----------------------------------------------------------------------------
int Menu::AddCascadingMenuItem( const char *itemText, Panel *target, Menu *cascadeMenu, const KeyValues *userData ) { return AddCascadingMenuItem(itemText, itemText, target, cascadeMenu, userData); }
//-----------------------------------------------------------------------------
// Purpose: Sets the values of a menu item at the specified index
// Input : index - the index of this item entry
// *message - pointer to the message to send when the item is selected
//-----------------------------------------------------------------------------
void Menu::UpdateMenuItem(int itemID, const char *itemText, KeyValues *message, const KeyValues *userData) { Assert( m_MenuItems.IsValidIndex(itemID) ); if ( m_MenuItems.IsValidIndex(itemID) ) { MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); // make sure its enabled since disabled items get highlighted.
if (menuItem) { menuItem->SetText(itemText); menuItem->SetCommand(message); if(userData) { menuItem->SetUserData(userData); } } } _recalculateWidth = true; }
//-----------------------------------------------------------------------------
// Purpose: Sets the values of a menu item at the specified index
//-----------------------------------------------------------------------------
void Menu::UpdateMenuItem(int itemID, const wchar_t *wszItemText, KeyValues *message, const KeyValues *userData) { Assert( m_MenuItems.IsValidIndex(itemID) ); if ( m_MenuItems.IsValidIndex(itemID) ) { MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); // make sure its enabled since disabled items get highlighted.
if (menuItem) { menuItem->SetText(wszItemText); menuItem->SetCommand(message); if(userData) { menuItem->SetUserData(userData); } } } _recalculateWidth = true; }
//-----------------------------------------------------------------------------
// Sets the content alignment of all items in the menu
//-----------------------------------------------------------------------------
void Menu::SetContentAlignment( Label::Alignment alignment ) { if ( m_Alignment != alignment ) { m_Alignment = alignment;
// Change the alignment of existing menu items
int nCount = m_MenuItems.Count(); for ( int i = 0; i < nCount; ++i ) { MenuItem *pItem = m_MenuItems[ i ]; pItem->SetContentAlignment( alignment ); // Recurse on cascading menus
Menu *pSubMenu = pItem->GetMenu(); if ( pSubMenu ) { pSubMenu->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:
//-----------------------------------------------------------------------------
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(); } 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(); } 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);
if ( wcsnicmp( 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; }
if ( m_MenuItems.Count() <= 0) return;
int itemToSelect = m_iCurrentlySelectedItemID; if ( itemToSelect < 0 || itemToSelect >= m_MenuItems.Count()) { itemToSelect = 0; }
int i = (itemToSelect+1) % m_MenuItems.Count(); while ( i != itemToSelect ) { wchar_t menuItemName[255]; m_MenuItems[i]->GetText(menuItemName, 254);
if ( tolower( unichar ) == tolower( menuItemName[0] ) ) { itemToSelect = i; break; }
i = (i+1) % m_MenuItems.Count(); }
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;
} // 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);
// Clearing the selected item when hiding the menu caused keyboard selection
// of items within the combo box to not work properly because the combo box
// would try to change the selection from the current to the next or previous
// item, but the current was always -1 because of this line, so it always
// reset the first time you pressed a key.
//SetCurrentlySelectedItem(-1);
g_MenuMgr.RemoveMenu( this ); } else if ( state == true ) { MoveToFront(); RequestFocus();
g_MenuMgr.AddMenu( this ); } // must be after movetofront()
BaseClass::SetVisible(state); _sizedForScrollBar = false; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Menu::ApplySchemeSettings(IScheme *pScheme) { BaseClass::ApplySchemeSettings(pScheme); SetFgColor(GetSchemeColor("Menu.TextColor", pScheme)); SetBgColor(GetSchemeColor("Menu.BgColor", pScheme));
_borderDark = pScheme->GetColor("BorderDark", Color(255, 255, 255, 0));
FOR_EACH_LL( m_MenuItems, i ) { if( m_MenuItems[i]->IsCheckable() ) { int wide, tall; m_MenuItems[i]->GetCheckImageSize( wide, tall );
m_iCheckImageWidth = MAX( m_iCheckImageWidth, wide ); } } _recalculateWidth = true; CalculateWidth();
InvalidateLayout(); }
void Menu::SetBgColor( Color newColor ) { BaseClass::SetBgColor( newColor ); FOR_EACH_LL( m_MenuItems, i ) { if( m_MenuItems[i]->HasMenu() ) { m_MenuItems[i]->GetMenu()->SetBgColor( newColor ); } } }
void Menu::SetFgColor( Color newColor ) { BaseClass::SetFgColor( newColor ); FOR_EACH_LL( m_MenuItems, i ) { if( m_MenuItems[i]->HasMenu() ) { m_MenuItems[i]->GetMenu()->SetFgColor( newColor ); } } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Menu::SetBorder(class IBorder *border) { Panel::SetBorder(border); }
//-----------------------------------------------------------------------------
// Purpose: returns a pointer to a MenuItem that is this menus parent, if it has one
//-----------------------------------------------------------------------------
MenuItem *Menu::GetParentMenuItem() { return dynamic_cast<MenuItem *>(GetParent()); }
//-----------------------------------------------------------------------------
// Purpose: Hide the menu when an item has been selected
//-----------------------------------------------------------------------------
void Menu::OnMenuItemSelected(Panel *panel) { SetVisible(false); m_pScroller->SetVisible(false); // chain this message up through the hierarchy so
// all the parent menus will close
// get the parent of this menu.
MenuItem *item = GetParentMenuItem(); // if the parent is a menu item, this menu is a cascading menu
if (item) { // get the parent of the menuitem. it should be a menu.
Menu *parentMenu = item->GetParentMenu(); if (parentMenu) { // send the message to this parent menu
KeyValues *kv = new KeyValues("MenuItemSelected"); kv->SetPtr("panel", panel); ivgui()->PostMessage(parentMenu->GetVPanel(), kv, GetVPanel()); } }
bool activeItemSet = false; FOR_EACH_LL( m_MenuItems, i ) { if( m_MenuItems[i] == panel ) { activeItemSet = true; m_iActivatedItem = i; break; } } if( !activeItemSet ) { FOR_EACH_LL( m_MenuItems, i ) { if(m_MenuItems[i]->HasMenu() ) { /*
// GetActiveItem needs to return -1 or similar if it hasn't been set...
if( m_MenuItems[i]->GetActiveItem() ) { m_iActivatedItem = m_MenuItems[i]->GetActiveItem(); }*/ } } }
// also pass it to the parent so they can respond if they like
if (GetVParent()) { KeyValues *kv = new KeyValues("MenuItemSelected"); kv->SetPtr("panel", panel);
ivgui()->PostMessage(GetVParent(), kv, GetVPanel()); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int Menu::GetActiveItem() { return m_iActivatedItem; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
KeyValues *Menu::GetItemUserData(int itemID) { if ( m_MenuItems.IsValidIndex( itemID ) ) { MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); // make sure its enabled since disabled items get highlighted.
if (menuItem && menuItem->IsEnabled()) { return menuItem->GetUserData(); } } return NULL; }
//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
void Menu::GetItemText(int itemID, wchar_t *text, int bufLenInBytes) { if ( m_MenuItems.IsValidIndex( itemID ) ) { MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); if (menuItem) { menuItem->GetText(text, bufLenInBytes); return; } } text[0] = 0; }
void Menu::GetItemText(int itemID, char *text, int bufLenInBytes) { if ( m_MenuItems.IsValidIndex( itemID ) ) { MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); if (menuItem) { menuItem->GetText( text, bufLenInBytes ); return; } } text[0] = 0; }
//-----------------------------------------------------------------------------
// Purpose: Activate the n'th item in the menu list, as if that menu item had been selected by the user
//-----------------------------------------------------------------------------
void Menu::ActivateItem(int itemID) { if ( m_MenuItems.IsValidIndex( itemID ) ) { MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); // make sure its enabled since disabled items get highlighted.
if (menuItem && menuItem->IsEnabled()) { menuItem->FireActionSignal(); m_iActivatedItem = itemID; } } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Menu::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()); } }
//-----------------------------------------------------------------------------
// 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); Assert(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(vgui::Panel* VPanel) { VPANEL menuItem = (VPANEL)VPanel; // if we are in mouse mode
if (m_iInputMode == MOUSE) { MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName())); // arm the menu
item->ArmItem(); // open the cascading menu if there is one.
item->OpenCascadeMenu(); SetCurrentlySelectedItem(item); } }
//-----------------------------------------------------------------------------
// Purpose: Respond to cursor exiting a menuItem
//-----------------------------------------------------------------------------
void Menu::OnCursorExitedMenuItem(vgui::Panel* VPanel) { VPANEL menuItem = (VPANEL)VPanel; // only care if we are in mouse mode
if (m_iInputMode == MOUSE) { MenuItem *item = static_cast<MenuItem *>(ipanel()->GetPanel(menuItem, GetModuleName())); // unhighlight the item.
// note menuItems with cascading menus will stay lit.
item->DisarmItem(); } }
//-----------------------------------------------------------------------------
// Purpose: Move up or down one in the list of items in the menu
// Direction is MENU_UP or MENU_DOWN
//-----------------------------------------------------------------------------
void Menu::MoveAlongMenuItemList(int direction, int loopCount) { // Early out if no menu items to scroll through
if (m_MenuItems.Count() <= 0) return;
int itemID = m_iCurrentlySelectedItemID; int row = m_SortedItems.Find(itemID); row += direction; if ( row > m_SortedItems.Count() - 1 ) { if ( m_pScroller->IsVisible() ) { // stop at bottom of scrolled list
row = m_SortedItems.Count() - 1; } else { // if no scroll bar we circle around
row = 0; } } else if (row < 0) { if ( m_pScroller->IsVisible() ) { // stop at top of scrolled list
row = m_pScroller->GetValue(); } else { // if no scroll bar circle around
row = m_SortedItems.Count()-1; } }
// if there is a scroll bar, and we scroll off lets move it.
if ( m_pScroller->IsVisible() ) { if ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1) { int val = m_pScroller->GetValue(); val -= -direction; m_pScroller->SetValue(val); // moving the slider redraws the scrollbar,
// and so we should redraw the menu since the
// menu draws the black border to the right of the scrollbar.
InvalidateLayout(); } else if ( row < m_pScroller->GetValue() ) { int val = m_pScroller->GetValue(); val -= -direction; m_pScroller->SetValue(val); // moving the slider redraws the scrollbar,
// and so we should redraw the menu since the
// menu draws the black border to the right of the scrollbar.
InvalidateLayout(); } // now if we are still off the scroll bar, it means we moved the scroll bar
// by hand and created a situation in which we moved an item down, but the
// scroll bar is already too far down and should scroll up or vice versa
// so just snap the scroll bar straight to the item.
if ( ( row > m_pScroller->GetValue() + m_iNumVisibleLines - 1) || ( row < m_pScroller->GetValue() ) ) { m_pScroller->SetValue(row); } }
// switch it back to an itemID from row
if ( m_SortedItems.IsValidIndex( row ) ) { SetCurrentlySelectedItem( m_SortedItems[row] ); }
// don't allow us to loop around more than once
if (loopCount < m_MenuItems.Count()) { // see if the text is empty, if so skip
wchar_t text[256]; m_MenuItems[m_iCurrentlySelectedItemID]->GetText(text, 255); if (text[0] == 0 || !m_MenuItems[m_iCurrentlySelectedItemID]->IsVisible()) { // menu item is empty, keep moving along
MoveAlongMenuItemList(direction, loopCount + 1); } } }
//-----------------------------------------------------------------------------
// Purpose: Return which type of events the menu is currently interested in
// MenuItems need to know because behaviour is different depending on mode.
//-----------------------------------------------------------------------------
int Menu::GetMenuMode() { return m_iInputMode; }
//-----------------------------------------------------------------------------
// Purpose: Set the menu to key mode if a child menu goes into keymode
// This mode change has to be chained up through the menu heirarchy
// so cascading menus will work when you do a bunch of stuff in keymode
// in high level menus and then switch to keymode in lower level menus.
//-----------------------------------------------------------------------------
void Menu::OnKeyModeSet() { m_iInputMode = KEYBOARD; }
//-----------------------------------------------------------------------------
// Purpose: Set the checked state of a menuItem
//-----------------------------------------------------------------------------
void Menu::SetMenuItemChecked(int itemID, bool state) { m_MenuItems[itemID]->SetChecked(state); }
//-----------------------------------------------------------------------------
// Purpose: Check if item is checked.
//-----------------------------------------------------------------------------
bool Menu::IsChecked(int itemID) { return m_MenuItems[itemID]->IsChecked(); }
//-----------------------------------------------------------------------------
// Purpose: Set the minmum width the menu has to be. This
// is useful if you have a menu that is sized to the largest item in it
// but you don't want the menu to be thinner than the menu button
//-----------------------------------------------------------------------------
void Menu::SetMinimumWidth(int width) { m_iMinimumWidth = width; }
//-----------------------------------------------------------------------------
// Purpose: Get the minmum width the menu
//-----------------------------------------------------------------------------
int Menu::GetMinimumWidth() { return m_iMinimumWidth; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
void Menu::AddSeparator() { int lastID = m_MenuItems.Count() - 1; m_Separators.AddToTail( lastID ); m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) ); }
void Menu::AddSeparatorAfterItem( int itemID ) { Assert( m_MenuItems.IsValidIndex( itemID ) ); m_Separators.AddToTail( itemID ); m_SeparatorPanels.AddToTail( new MenuSeparator( this, "MenuSeparator" ) ); }
void Menu::MoveMenuItem( int itemID, int moveBeforeThisItemID ) { int c = m_SortedItems.Count(); int i; for ( i = 0; i < c; ++i ) { if ( m_SortedItems[i] == itemID ) { m_SortedItems.Remove( i ); break; } }
// Didn't find it
if ( i >= c ) { return; }
// Now find insert pos
c = m_SortedItems.Count(); for ( i = 0; i < c; ++i ) { if ( m_SortedItems[i] == moveBeforeThisItemID ) { m_SortedItems.InsertBefore( i, itemID ); break; } } }
void Menu::SetFont( HFont font ) { m_hItemFont = font; if ( font ) { m_iMenuItemHeight = surface()->GetFontTall( font ) + 2; } InvalidateLayout(); }
void Menu::SetCurrentKeyBinding( int itemID, char const *hotkey ) { if ( m_MenuItems.IsValidIndex( itemID ) ) { MenuItem *menuItem = dynamic_cast<MenuItem *>(m_MenuItems[itemID]); menuItem->SetCurrentKeyBinding( hotkey ); } }
//-----------------------------------------------------------------------------
// Purpose: Static method to display a context menu
// Input : *parent -
// *menu -
//-----------------------------------------------------------------------------
void Menu::PlaceContextMenu( Panel *parent, Menu *menu ) { Assert( parent ); Assert( menu ); if ( !menu || !parent ) return;
menu->SetVisible(false); menu->SetParent( parent ); menu->AddActionSignalTarget( parent );
// get cursor position, this is local to this text edit window
int cursorX, cursorY; input()->GetCursorPos(cursorX, cursorY);
menu->SetVisible(true); // relayout the menu immediately so that we know it's size
menu->InvalidateLayout(true); int menuWide, menuTall; menu->GetSize(menuWide, menuTall); // work out where the cursor is and therefore the best place to put the menu
int wide, tall; surface()->GetScreenSize(wide, tall); if (wide - menuWide > cursorX) { // menu hanging right
if (tall - menuTall > cursorY) { // menu hanging down
menu->SetPos(cursorX, cursorY); } else { // menu hanging up
menu->SetPos(cursorX, cursorY - menuTall); } } else { // menu hanging left
if (tall - menuTall > cursorY) { // menu hanging down
menu->SetPos(cursorX - menuWide, cursorY); } else { // menu hanging up
menu->SetPos(cursorX - menuWide, cursorY - menuTall); } } menu->RequestFocus(); }
void Menu::SetUseFallbackFont( bool bState, HFont hFallback ) { m_hFallbackItemFont = hFallback; m_bUseFallbackFont = bState; }
#ifdef DBGFLAG_VALIDATE
//-----------------------------------------------------------------------------
// Purpose: Run a global validation pass on all of our data structures and memory
// allocations.
// Input: validator - Our global validator object
// pchName - Our name (typically a member var in our container)
//-----------------------------------------------------------------------------
void Menu::Validate( CValidator &validator, char *pchName ) { validator.Push( "vgui::Menu", this, pchName );
m_MenuItems.Validate( validator, "m_MenuItems" ); m_SortedItems.Validate( validator, "m_SortedItems" );
BaseClass::Validate( validator, "vgui::Menu" );
validator.Pop(); } #endif // DBGFLAG_VALIDATE
|