source-engine/vgui2/vgui_controls/KeyBoardEditorDialog.cpp

850 lines
21 KiB
C++
Raw Permalink Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "vgui_controls/KeyBoardEditorDialog.h"
#include "vgui_controls/ListPanel.h"
#include "vgui_controls/Button.h"
#include "vgui_controls/TextEntry.h"
#include "vgui/ISurface.h"
#include "vgui/IInput.h"
#include "vgui/IVGui.h"
#include "vgui/ILocalize.h"
#include "KeyValues.h"
#include "vgui/Cursor.h"
#include "tier1/utldict.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
using namespace vgui;
static char *CopyString( const char *in )
{
if ( !in )
return NULL;
int len = strlen( in );
char *n = new char[ len + 1 ];
Q_strncpy( n, in, len + 1 );
return n;
}
CKeyBoardEditorPage::SaveMapping_t::SaveMapping_t() : map( 0 )
{
}
CKeyBoardEditorPage::SaveMapping_t::SaveMapping_t( const SaveMapping_t& src )
{
map = src.map;
current = src.current;
original = src.original;
}
//-----------------------------------------------------------------------------
// Purpose: Special list subclass to handle drawing of trap mode prompt on top of
// lists client area
//-----------------------------------------------------------------------------
class VControlsListPanel : public ListPanel
{
DECLARE_CLASS_SIMPLE( VControlsListPanel, ListPanel );
public:
// Construction
VControlsListPanel( vgui::Panel *parent, const char *listName );
virtual ~VControlsListPanel();
// Start/end capturing
virtual void StartCaptureMode(vgui::HCursor hCursor = NULL);
virtual void EndCaptureMode(vgui::HCursor hCursor = NULL);
virtual bool IsCapturing();
// Set which item should be associated with the prompt
virtual void SetItemOfInterest(int itemID);
virtual int GetItemOfInterest();
virtual void OnMousePressed(vgui::MouseCode code);
virtual void OnMouseDoublePressed(vgui::MouseCode code);
KEYBINDING_FUNC( clearbinding, KEY_DELETE, 0, OnClearBinding, 0, 0 );
private:
void ApplySchemeSettings(vgui::IScheme *pScheme );
// Are we showing the prompt?
bool m_bCaptureMode;
// If so, where?
int m_nClickRow;
// Font to use for showing the prompt
vgui::HFont m_hFont;
// panel used to edit
class CInlineEditPanel *m_pInlineEditPanel;
int m_iMouseX, m_iMouseY;
};
//-----------------------------------------------------------------------------
// Purpose: panel used for inline editing of key bindings
//-----------------------------------------------------------------------------
class CInlineEditPanel : public vgui::Panel
{
DECLARE_CLASS_SIMPLE( CInlineEditPanel, vgui::Panel );
public:
CInlineEditPanel() : vgui::Panel(NULL, "InlineEditPanel")
{
}
virtual void Paint()
{
int wide, tall;
GetSize(wide, tall);
// Draw a white rectangle around that cell
vgui::surface()->DrawSetColor( 63, 63, 63, 255 );
vgui::surface()->DrawFilledRect( 0, 0, wide, tall );
vgui::surface()->DrawSetColor( 0, 255, 0, 255 );
vgui::surface()->DrawOutlinedRect( 0, 0, wide, tall );
}
virtual void OnKeyCodeTyped(KeyCode code)
{
// forward up
if (GetParent())
{
GetParent()->OnKeyCodeTyped(code);
}
}
virtual void ApplySchemeSettings(IScheme *pScheme)
{
Panel::ApplySchemeSettings(pScheme);
SetBorder(pScheme->GetBorder("DepressedButtonBorder"));
}
void OnMousePressed(vgui::MouseCode code)
{
// forward up mouse pressed messages to be handled by the key options
if (GetParent())
{
GetParent()->OnMousePressed(code);
}
}
};
//-----------------------------------------------------------------------------
// Purpose: Construction
//-----------------------------------------------------------------------------
VControlsListPanel::VControlsListPanel( vgui::Panel *parent, const char *listName ) : BaseClass( parent, listName )
{
m_bCaptureMode = false;
m_nClickRow = 0;
m_pInlineEditPanel = new CInlineEditPanel();
m_hFont = INVALID_FONT;
}
//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
VControlsListPanel::~VControlsListPanel()
{
m_pInlineEditPanel->MarkForDeletion();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void VControlsListPanel::ApplySchemeSettings(IScheme *pScheme )
{
BaseClass::ApplySchemeSettings( pScheme );
m_hFont = pScheme->GetFont("DefaultVerySmall", IsProportional() );
}
//-----------------------------------------------------------------------------
// Purpose: Start capture prompt display
//-----------------------------------------------------------------------------
void VControlsListPanel::StartCaptureMode( HCursor hCursor )
{
m_bCaptureMode = true;
EnterEditMode(m_nClickRow, 1, m_pInlineEditPanel);
input()->SetMouseFocus(m_pInlineEditPanel->GetVPanel());
input()->SetMouseCapture(m_pInlineEditPanel->GetVPanel());
if (hCursor)
{
m_pInlineEditPanel->SetCursor(hCursor);
// save off the cursor position so we can restore it
vgui::input()->GetCursorPos( m_iMouseX, m_iMouseY );
}
}
void VControlsListPanel::OnClearBinding()
{
if ( m_bCaptureMode )
return;
if ( GetItemOfInterest() < 0 )
return;
PostMessage( GetParent()->GetVPanel(), new KeyValues( "ClearBinding", "item", GetItemOfInterest() ) );
}
//-----------------------------------------------------------------------------
// Purpose: Finish capture prompt display
//-----------------------------------------------------------------------------
void VControlsListPanel::EndCaptureMode( HCursor hCursor )
{
m_bCaptureMode = false;
input()->SetMouseCapture(NULL);
LeaveEditMode();
RequestFocus();
input()->SetMouseFocus(GetVPanel());
if (hCursor)
{
m_pInlineEditPanel->SetCursor(hCursor);
surface()->SetCursor(hCursor);
if ( hCursor != dc_none )
{
vgui::input()->SetCursorPos ( m_iMouseX, m_iMouseY );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Set active row column
//-----------------------------------------------------------------------------
void VControlsListPanel::SetItemOfInterest(int itemID)
{
m_nClickRow = itemID;
}
//-----------------------------------------------------------------------------
// Purpose: Retrieve row, column of interest
//-----------------------------------------------------------------------------
int VControlsListPanel::GetItemOfInterest()
{
return m_nClickRow;
}
//-----------------------------------------------------------------------------
// Purpose: returns true if we're currently waiting to capture a key
//-----------------------------------------------------------------------------
bool VControlsListPanel::IsCapturing( void )
{
return m_bCaptureMode;
}
//-----------------------------------------------------------------------------
// Purpose: Forwards mouse pressed message up to keyboard page when in capture
//-----------------------------------------------------------------------------
void VControlsListPanel::OnMousePressed(vgui::MouseCode code)
{
if (IsCapturing())
{
// forward up mouse pressed messages to be handled by the key options
if (GetParent())
{
GetParent()->OnMousePressed(code);
}
}
else
{
BaseClass::OnMousePressed(code);
}
}
//-----------------------------------------------------------------------------
// Purpose: input handler
//-----------------------------------------------------------------------------
void VControlsListPanel::OnMouseDoublePressed( vgui::MouseCode code )
{
int c = GetSelectedItemsCount();
if ( c > 0 )
{
// enter capture mode
OnKeyCodeTyped(KEY_ENTER);
}
else
{
BaseClass::OnMouseDoublePressed(code);
}
}
CKeyBoardEditorPage::CKeyBoardEditorPage( Panel *parent, Panel *panelToEdit, KeyBindingContextHandle_t handle )
: BaseClass( parent, "KeyBoardEditorPage" ),
m_pPanel( panelToEdit ),
m_Handle( handle )
{
Assert( m_pPanel );
m_pList = new VControlsListPanel( this, "KeyBindings" );
m_pList->SetIgnoreDoubleClick( true );
m_pList->AddColumnHeader(0, "Action", "#KBEditorBindingName", 175, 0);
m_pList->AddColumnHeader(1, "Binding", "#KBEditorBinding", 175, 0);
m_pList->AddColumnHeader(2, "Description", "#KBEditorDescription", 300, 0);
LoadControlSettings( "resource/KeyBoardEditorPage.res" );
SaveMappings();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
CKeyBoardEditorPage::~CKeyBoardEditorPage()
{
int c = m_Save.Count();
for ( int i = 0 ; i < c; ++i )
{
delete m_Save[ i ];
}
m_Save.RemoveAll();
}
void CKeyBoardEditorPage::ApplySchemeSettings( IScheme *scheme )
{
BaseClass::ApplySchemeSettings( scheme );
PopulateList();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
void CKeyBoardEditorPage::SaveMappings()
{
Assert( m_Save.Count() == 0 );
CUtlVector< PanelKeyBindingMap * > maps;
GetMappingList( m_pPanel, maps );
// add header item
int c = maps.Count();
for ( int i = 0; i < c; ++i )
{
PanelKeyBindingMap *m = maps[ i ];
SaveMapping_t *sm = new SaveMapping_t;
sm->map = m;
sm->current = m->boundkeys;
sm->original = m->boundkeys;
m_Save.AddToTail( sm );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
void CKeyBoardEditorPage::UpdateCurrentMappings()
{
int c = m_Save.Count();
for ( int i = 0 ; i < c; ++i )
{
PanelKeyBindingMap *m = m_Save[ i ]->map;
Assert( m );
m_Save[ i ]->current = m->boundkeys;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
void CKeyBoardEditorPage::RestoreMappings()
{
int c = m_Save.Count();
for ( int i = 0; i < c; ++i )
{
SaveMapping_t *sm = m_Save[ i ];
sm->current = sm->original;
}
}
void CKeyBoardEditorPage::ApplyMappings()
{
int c = m_Save.Count();
for ( int i = 0; i < c; ++i )
{
SaveMapping_t *sm = m_Save[ i ];
sm->map->boundkeys = sm->current;
}
}
//-----------------------------------------------------------------------------
// Purpose: User clicked on item: remember where last active row/column was
//-----------------------------------------------------------------------------
void CKeyBoardEditorPage::ItemSelected()
{
int c = m_pList->GetSelectedItemsCount();
if ( c > 0 )
{
m_pList->SetItemOfInterest( m_pList->GetSelectedItem( 0 ) );
}
}
void CKeyBoardEditorPage::BindKey( KeyCode code )
{
bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT));
bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL));
bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT));
int modifiers = 0;
if ( shift )
{
modifiers |= MODIFIER_SHIFT;
}
if ( ctrl )
{
modifiers |= MODIFIER_CONTROL;
}
if ( alt )
{
modifiers |= MODIFIER_ALT;
}
int r = m_pList->GetItemOfInterest();
// Retrieve clicked row and column
m_pList->EndCaptureMode(dc_arrow);
// Find item for this row
KeyValues *item = m_pList->GetItem(r);
if ( item )
{
BoundKey_t *kbMap = reinterpret_cast< BoundKey_t * >( item->GetPtr( "Item", 0 ) );
if ( kbMap )
{
KeyBindingMap_t *binding = m_pPanel->LookupBindingByKeyCode( code, modifiers );
if ( binding && Q_stricmp( kbMap->bindingname, binding->bindingname ) )
{
// Key is already rebound!!!
Warning( "Can't bind to '%S', key is already bound to '%s'\n",
Panel::KeyCodeToDisplayString( code ), binding->bindingname );
return;
}
kbMap->keycode = code;
kbMap->modifiers = modifiers;
PopulateList();
}
KeyBindingMap_t *bindingMap = reinterpret_cast< KeyBindingMap_t * >( item->GetPtr( "Unbound", 0 ) );
if ( bindingMap )
{
KeyBindingMap_t *binding = m_pPanel->LookupBindingByKeyCode( code, modifiers );
if ( binding && Q_stricmp( bindingMap->bindingname, binding->bindingname ) )
{
// Key is already rebound!!!
Warning( "Can't bind to '%S', key is already bound to '%s'\n",
Panel::KeyCodeToDisplayString( code ), binding->bindingname );
return;
}
// Need to add to current entries
m_pPanel->AddKeyBinding( bindingMap->bindingname, code, modifiers );
UpdateCurrentMappings();
PopulateList();
}
}
}
void CKeyBoardEditorPage::OnPageHide()
{
if ( m_pList->IsCapturing() )
{
// Cancel capturing
m_pList->EndCaptureMode(dc_arrow);
}
}
//-----------------------------------------------------------------------------
// Purpose: binds double-clicking or hitting enter in the keybind list to changing the key
//-----------------------------------------------------------------------------
void CKeyBoardEditorPage::OnKeyCodeTyped(vgui::KeyCode code)
{
switch ( code )
{
case KEY_ENTER:
{
if ( !m_pList->IsCapturing() )
{
OnCommand( "ChangeKey" );
}
else
{
BindKey( code );
}
}
break;
case KEY_LSHIFT:
case KEY_RSHIFT:
case KEY_LALT:
case KEY_RALT:
case KEY_LCONTROL:
case KEY_RCONTROL:
{
// Swallow these
break;
}
break;
default:
{
if ( m_pList->IsCapturing() )
{
BindKey( code );
}
else
{
BaseClass::OnKeyCodeTyped(code);
}
}
}
}
void CKeyBoardEditorPage::OnCommand( char const *cmd )
{
if ( !m_pList->IsCapturing() && !Q_stricmp( cmd, "ChangeKey" ) )
{
m_pList->StartCaptureMode(dc_blank);
}
else
{
BaseClass::OnCommand( cmd );
}
}
void CKeyBoardEditorPage::OnSaveChanges()
{
ApplyMappings();
}
void CKeyBoardEditorPage::OnRevert()
{
RestoreMappings();
PopulateList();
}
void CKeyBoardEditorPage::OnUseDefaults()
{
m_pPanel->RevertKeyBindingsToDefault();
UpdateCurrentMappings();
PopulateList();
}
void CKeyBoardEditorPage::GetMappingList( Panel *panel, CUtlVector< PanelKeyBindingMap * >& maps )
{
PanelKeyBindingMap *map = panel->GetKBMap();
while ( map )
{
maps.AddToTail( map );
map = map->baseMap;
}
}
static bool BindingLessFunc( KeyValues * const & lhs, KeyValues * const &rhs )
{
KeyValues *p1, *p2;
p1 = const_cast< KeyValues * >( lhs );
p2 = const_cast< KeyValues * >( rhs );
return ( Q_stricmp( p1->GetString( "Action" ), p2->GetString( "Action" ) ) < 0 ) ? true : false;
}
void CKeyBoardEditorPage::AnsiText( char const *token, char *out, size_t buflen )
{
out[ 0 ] = 0;
wchar_t *str = g_pVGuiLocalize->Find( token );
if ( !str )
{
Q_strncpy( out, token, buflen );
}
else
{
g_pVGuiLocalize->ConvertUnicodeToANSI( str, out, buflen );
}
}
void CKeyBoardEditorPage::PopulateList()
{
m_pList->DeleteAllItems();
int i, j;
CUtlRBTree< KeyValues *, int > sorted( 0, 0, BindingLessFunc );
// add header item
int c = m_Save.Count();
for ( i = 0; i < c; ++i )
{
SaveMapping_t* sm = m_Save[ i ];
PanelKeyBindingMap *m = sm->map;
Assert( m );
int bindings = sm->current.Count();
for ( j = 0; j < bindings; ++j )
{
BoundKey_t *kbMap = &sm->current[ j ];
Assert( kbMap );
// Create a new: blank item
KeyValues *item = new KeyValues( "Item" );
// Fill in data
char loc[ 128 ];
Q_snprintf( loc, sizeof( loc ), "#%s", kbMap->bindingname );
char ansi[ 256 ];
AnsiText( loc, ansi, sizeof( ansi ) );
item->SetString( "Action", ansi );
item->SetWString( "Binding", Panel::KeyCodeModifiersToDisplayString( (KeyCode)kbMap->keycode, kbMap->modifiers ) );
// Find the binding
KeyBindingMap_t *bindingMap = m_pPanel->LookupBinding( kbMap->bindingname );
if ( bindingMap &&
bindingMap->helpstring )
{
AnsiText( bindingMap->helpstring, ansi, sizeof( ansi ) );
item->SetString( "Description", ansi);
}
item->SetPtr( "Item", kbMap );
sorted.Insert( item );
}
// Now try and find any "unbound" keys...
int mappings = m->entries.Count();
for ( j = 0; j < mappings; ++j )
{
KeyBindingMap_t *kbMap = &m->entries[ j ];
// See if it's bound
CUtlVector< BoundKey_t * > list;
m_pPanel->LookupBoundKeys( kbMap->bindingname, list );
if ( list.Count() > 0 )
continue;
// Not bound, add a placeholder entry
// Create a new: blank item
KeyValues *item = new KeyValues( "Item" );
// fill in data
char loc[ 128 ];
Q_snprintf( loc, sizeof( loc ), "#%s", kbMap->bindingname );
char ansi[ 256 ];
AnsiText( loc, ansi, sizeof( ansi ) );
item->SetString( "Action", ansi );
item->SetWString( "Binding", L"" );
if ( kbMap->helpstring )
{
AnsiText( kbMap->helpstring, ansi, sizeof( ansi ) );
item->SetString( "Description", ansi );
}
item->SetPtr( "Unbound", kbMap );
sorted.Insert( item );
}
}
for ( j = sorted.FirstInorder() ; j != sorted.InvalidIndex(); j = sorted.NextInorder( j ) )
{
KeyValues *item = sorted[ j ];
// Add to list
m_pList->AddItem( item, 0, false, false );
item->deleteThis();
}
sorted.RemoveAll();
}
void CKeyBoardEditorPage::OnClearBinding( int item )
{
// Find item for this row
KeyValues *kv = m_pList->GetItem(item );
if ( !kv )
{
return;
}
BoundKey_t *kbMap = reinterpret_cast< BoundKey_t * >( kv->GetPtr( "Item", 0 ) );
if ( !kbMap )
{
return;
}
kbMap->keycode = KEY_NONE;
kbMap->modifiers = 0;
PopulateList();
}
CKeyBoardEditorSheet::CKeyBoardEditorSheet( Panel *parent, Panel *panelToEdit, KeyBindingContextHandle_t handle )
: BaseClass( parent, "KeyBoardEditorSheet" ),
m_bSaveToExternalFile( false ),
m_Handle( handle ),
m_SaveFileName( UTL_INVAL_SYMBOL ),
m_SaveFilePathID( UTL_INVAL_SYMBOL )
{
m_hPanel = panelToEdit;
SetSmallTabs( true );
// Create this sheet and add the subcontrols
CKeyBoardEditorPage *active = NULL;
int subCount = Panel::GetPanelsWithKeyBindingsCount( handle );
for ( int i = 0; i < subCount; ++i )
{
Panel *p = Panel::GetPanelWithKeyBindings( handle, i );
if ( !p )
continue;
// Don't display panels with no keymappings
if ( p->GetKeyMappingCount() == 0 )
continue;
CKeyBoardEditorPage *newPage = new CKeyBoardEditorPage( this, p, handle );
AddPage( newPage, p->GetName() );
if ( p == panelToEdit )
{
active = newPage;
}
}
if ( active )
{
SetActivePage( active );
}
LoadControlSettings( "resource/KeyBoardEditorSheet.res" );
}
void CKeyBoardEditorSheet::SetKeybindingsSaveFile( char const *filename, char const *pathID /*= 0*/ )
{
Assert( filename );
m_bSaveToExternalFile = true;
m_SaveFileName = filename;
if ( pathID != NULL )
{
m_SaveFilePathID = pathID;
}
else
{
m_SaveFilePathID = UTL_INVAL_SYMBOL;
}
}
void CKeyBoardEditorSheet::OnSaveChanges()
{
int c = GetNumPages();
for ( int i = 0 ; i < c; ++i )
{
CKeyBoardEditorPage *page = static_cast< CKeyBoardEditorPage * >( GetPage( i ) );
page->OnSaveChanges();
}
if ( m_bSaveToExternalFile )
{
m_hPanel->SaveKeyBindingsToFile( m_Handle, m_SaveFileName.String(), m_SaveFilePathID.IsValid() ? m_SaveFilePathID.String() : NULL );
}
else
{
m_hPanel->SaveKeyBindings( m_Handle );
}
}
void CKeyBoardEditorSheet::OnRevert()
{
int c = GetNumPages();
for ( int i = 0 ; i < c; ++i )
{
CKeyBoardEditorPage *page = static_cast< CKeyBoardEditorPage * >( GetPage( i ) );
page->OnRevert();
}
}
void CKeyBoardEditorSheet::OnUseDefaults()
{
int c = GetNumPages();
for ( int i = 0 ; i < c; ++i )
{
CKeyBoardEditorPage *page = static_cast< CKeyBoardEditorPage * >( GetPage( i ) );
page->OnUseDefaults();
}
}
CKeyBoardEditorDialog::CKeyBoardEditorDialog( Panel *parent, Panel *panelToEdit, KeyBindingContextHandle_t handle )
: BaseClass( parent, "KeyBoardEditorDialog" )
{
m_pSave = new Button( this, "Save", "#KBEditorSave", this, "save" );
m_pCancel = new Button( this, "Cancel", "#KBEditorCancel", this, "cancel" );
m_pRevert = new Button( this, "Revert", "#KBEditorRevert", this, "revert" );
m_pUseDefaults = new Button( this, "Defaults", "#KBEditorUseDefaults", this, "defaults" );
m_pKBEditor = new CKeyBoardEditorSheet( this, panelToEdit, handle );
LoadControlSettings( "resource/KeyBoardEditorDialog.res" );
SetTitle( "#KBEditorTitle", true );
SetSmallCaption( true );
SetMinimumSize( 640, 200 );
SetMinimizeButtonVisible( false );
SetMaximizeButtonVisible( false );
SetSizeable( true );
SetMoveable( true );
SetMenuButtonVisible( false );
SetVisible( true );
MoveToCenterOfScreen();
}
void CKeyBoardEditorDialog::OnCommand( char const *cmd )
{
if ( !Q_stricmp( cmd, "save" ) )
{
m_pKBEditor->OnSaveChanges();
MarkForDeletion();
}
else if ( !Q_stricmp( cmd, "cancel" ) ||
!Q_stricmp( cmd, "Close" ) )
{
m_pKBEditor->OnRevert();
MarkForDeletion();
}
else if ( !Q_stricmp( cmd, "revert" ) )
{
m_pKBEditor->OnRevert();
}
else if ( !Q_stricmp( cmd, "defaults" ) )
{
m_pKBEditor->OnUseDefaults();
}
else
{
BaseClass::OnCommand( cmd );
}
}
void CKeyBoardEditorDialog::SetKeybindingsSaveFile( char const *filename, char const *pathID /*= 0*/ )
{
m_pKBEditor->SetKeybindingsSaveFile( filename, pathID );
}