//========= 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 ); }