//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ //===========================================================================// #include "vgui_controls/consoledialog.h" #include "vgui/IInput.h" #include "vgui/IScheme.h" #include "vgui/IVGui.h" #include "vgui/ISurface.h" #include "vgui/ILocalize.h" #include "keyvalues.h" #include "vgui_controls/Button.h" #include "vgui/KeyCode.h" #include "vgui_controls/Menu.h" #include "vgui_controls/TextEntry.h" #include "vgui_controls/RichText.h" #include "tier1/convar.h" #include "tier1/convar_serverbounded.h" #include "icvar.h" #include "filesystem.h" #include #include #if defined( _X360 ) #include "xbox/xbox_win32stubs.h" #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" using namespace vgui; //----------------------------------------------------------------------------- // Used by the autocompletion system //----------------------------------------------------------------------------- class CNonFocusableMenu : public Menu { DECLARE_CLASS_SIMPLE( CNonFocusableMenu, Menu ); public: CNonFocusableMenu( Panel *parent, const char *panelName ) : BaseClass( parent, panelName ), m_pFocus( 0 ) { } void SetFocusPanel( Panel *panel ) { m_pFocus = panel; } VPANEL GetCurrentKeyFocus() { if ( !m_pFocus ) return GetVPanel(); return m_pFocus->GetVPanel(); } private: Panel *m_pFocus; }; //----------------------------------------------------------------------------- // Purpose: forwards tab key presses up from the text entry so we can do autocomplete //----------------------------------------------------------------------------- class TabCatchingTextEntry : public TextEntry { public: TabCatchingTextEntry(Panel *parent, const char *name, VPANEL comp) : TextEntry(parent, name), m_pCompletionList( comp ) { SetAllowNonAsciiCharacters( true ); SetDragEnabled( true ); } virtual void OnKeyCodeTyped(KeyCode code) { if (code == KEY_TAB) { GetParent()->OnKeyCodeTyped(code); } else if ( code == KEY_ENTER ) { PostMessage( GetParent()->GetVPanel(), new KeyValues( "Command", "command", "submit" ) ); } else { TextEntry::OnKeyCodeTyped(code); } } virtual void OnKillFocus() { if ( input()->GetFocus() != m_pCompletionList ) // if its not the completion window trying to steal our focus { PostMessage(GetParent(), new KeyValues("CloseCompletionList")); } } private: VPANEL m_pCompletionList; }; // Things the user typed in and hit submit/return with CHistoryItem::CHistoryItem( void ) { m_text = NULL; m_extraText = NULL; m_bHasExtra = false; } CHistoryItem::CHistoryItem( const char *text, const char *extra ) { Assert( text ); m_text = NULL; m_extraText = NULL; m_bHasExtra = false; SetText( text , extra ); } CHistoryItem::CHistoryItem( const CHistoryItem& src ) { m_text = NULL; m_extraText = NULL; m_bHasExtra = false; SetText( src.GetText(), src.GetExtra() ); } CHistoryItem::~CHistoryItem( void ) { delete[] m_text; delete[] m_extraText; m_text = NULL; } const char *CHistoryItem::GetText() const { if ( m_text ) { return m_text; } else { return ""; } } const char *CHistoryItem::GetExtra() const { if ( m_extraText ) { return m_extraText; } else { return NULL; } } void CHistoryItem::SetText( const char *text, const char *extra ) { delete[] m_text; int len = strlen( text ) + 1; m_text = new char[ len ]; Q_memset( m_text, 0x0, len ); Q_strncpy( m_text, text, len ); if ( extra ) { m_bHasExtra = true; delete[] m_extraText; int elen = strlen( extra ) + 1; m_extraText = new char[ elen ]; Q_memset( m_extraText, 0x0, elen); Q_strncpy( m_extraText, extra, elen ); } else { m_bHasExtra = false; } } //----------------------------------------------------------------------------- // // Console page completion item starts here // //----------------------------------------------------------------------------- CConsolePanel::CompletionItem::CompletionItem( void ) { m_bIsCommand = true; m_pCommand = NULL; m_pText = NULL; } CConsolePanel::CompletionItem::CompletionItem( const CompletionItem& src ) { m_bIsCommand = src.m_bIsCommand; m_pCommand = src.m_pCommand; if ( src.m_pText ) { m_pText = new CHistoryItem( (const CHistoryItem& )src.m_pText ); } else { m_pText = NULL; } } CConsolePanel::CompletionItem& CConsolePanel::CompletionItem::operator =( const CompletionItem& src ) { if ( this == &src ) return *this; m_bIsCommand = src.m_bIsCommand; m_pCommand = src.m_pCommand; if ( src.m_pText ) { m_pText = new CHistoryItem( (const CHistoryItem& )*src.m_pText ); } else { m_pText = NULL; } return *this; } CConsolePanel::CompletionItem::~CompletionItem( void ) { if ( m_pText ) { delete m_pText; m_pText = NULL; } } const char *CConsolePanel::CompletionItem::GetName() const { if ( m_bIsCommand ) return m_pCommand->GetName(); return m_pCommand ? m_pCommand->GetName() : GetCommand(); } const char *CConsolePanel::CompletionItem::GetItemText( void ) { static char text[256]; text[0] = 0; if ( m_pText ) { if ( m_pText->HasExtra() ) { Q_snprintf( text, sizeof( text ), "%s %s", m_pText->GetText(), m_pText->GetExtra() ); } else { Q_strncpy( text, m_pText->GetText(), sizeof( text ) ); } } return text; } const char *CConsolePanel::CompletionItem::GetCommand( void ) const { static char text[256]; text[0] = 0; if ( m_pText ) { Q_strncpy( text, m_pText->GetText(), sizeof( text ) ); } return text; } //----------------------------------------------------------------------------- // // Console page starts here // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Purpose: Constructor, destuctor //----------------------------------------------------------------------------- CConsolePanel::CConsolePanel( vgui::Panel *pParent, const char *pName, bool bStatusVersion ) : BaseClass( pParent, pName ), m_bStatusVersion( bStatusVersion ) { SetKeyBoardInputEnabled( true ); if ( !m_bStatusVersion ) { SetMinimumSize(100,100); } // create controls m_pHistory = new RichText(this, "ConsoleHistory"); m_pHistory->SetAllowKeyBindingChainToParent( false ); SETUP_PANEL( m_pHistory ); m_pHistory->SetVerticalScrollbar( !m_bStatusVersion ); if ( m_bStatusVersion ) { m_pHistory->SetDrawOffsets( 3, 3 ); } m_pHistory->GotoTextEnd(); m_pSubmit = new Button(this, "ConsoleSubmit", "#Console_Submit"); m_pSubmit->SetCommand("submit"); m_pSubmit->SetVisible( !m_bStatusVersion ); CNonFocusableMenu *pCompletionList = new CNonFocusableMenu( this, "CompletionList" ); m_pCompletionList = pCompletionList; m_pCompletionList->SetVisible(false); m_pEntry = new TabCatchingTextEntry(this, "ConsoleEntry", m_pCompletionList->GetVPanel() ); m_pEntry->AddActionSignalTarget(this); m_pEntry->SendNewLine(true); pCompletionList->SetFocusPanel( m_pEntry ); // need to set up default colors, since ApplySchemeSettings won't be called until later m_PrintColor = Color(216, 222, 211, 255); m_DPrintColor = Color(196, 181, 80, 255); m_pEntry->SetTabPosition(1); m_bAutoCompleteMode = false; m_szPartialText[0] = 0; m_szPreviousPartialText[0]=0; // Add to global console list g_pCVar->InstallConsoleDisplayFunc( this ); } //----------------------------------------------------------------------------- // Purpose: Destructor //----------------------------------------------------------------------------- CConsolePanel::~CConsolePanel() { ClearCompletionList(); m_CommandHistory.Purge(); g_pCVar->RemoveConsoleDisplayFunc( this ); } //----------------------------------------------------------------------------- // Updates the completion list //----------------------------------------------------------------------------- void CConsolePanel::OnThink() { BaseClass::OnThink(); if ( !IsVisible() ) return; if ( !m_pCompletionList->IsVisible() ) return; UpdateCompletionListPosition(); } //----------------------------------------------------------------------------- // Purpose: Clears the console //----------------------------------------------------------------------------- void CConsolePanel::Clear() { m_pHistory->SetText(""); m_pHistory->GotoTextEnd(); } //----------------------------------------------------------------------------- // Purpose: color text print //----------------------------------------------------------------------------- void CConsolePanel::ColorPrint( const Color& clr, const char *msg ) { if ( m_bStatusVersion ) { Clear(); } m_pHistory->InsertColorChange( clr ); m_pHistory->InsertString( msg ); } //----------------------------------------------------------------------------- // Purpose: normal text print //----------------------------------------------------------------------------- void CConsolePanel::Print(const char *msg) { ColorPrint( m_PrintColor, msg ); } //----------------------------------------------------------------------------- // Purpose: debug text print //----------------------------------------------------------------------------- void CConsolePanel::DPrint( const char *msg ) { ColorPrint( m_DPrintColor, msg ); } void CConsolePanel::ClearCompletionList() { int c = m_CompletionList.Count(); int i; for ( i = c - 1; i >= 0; i-- ) { delete m_CompletionList[ i ]; } m_CompletionList.Purge(); } static ConCommand *FindAutoCompleteCommmandFromPartial( const char *partial ) { char command[ 256 ]; Q_strncpy( command, partial, sizeof( command ) ); char *space = Q_strstr( command, " " ); if ( space ) { *space = 0; } ConCommand *cmd = g_pCVar->FindCommand( command ); if ( !cmd ) return NULL; if ( !cmd->CanAutoComplete() ) return NULL; return cmd; } //----------------------------------------------------------------------------- // Purpose: depending on our input mode will match the command or substrings in the command. //----------------------------------------------------------------------------- bool CConsolePanel::CommandMatchesText(const char *command, const char *text, bool bCheckSubstrings) { if (bCheckSubstrings) { int textLeft = Q_strlen( text ); int length = 0; char uprCommand[ 256 ]; char substring[ 256 ]; Q_strncpy( substring, text, sizeof( substring ) ); Q_strncpy( uprCommand, command, sizeof( uprCommand ) ); Q_strupr( uprCommand ); Q_strupr( substring ); char *strStart = substring; char *space; // split the search string based on spaces, keep searching for the substrings until we are out of text do { space = Q_strstr( strStart, " " ); if ( space ) { *space = 0; // replace the space with an end of string char } if( !Q_strstr(uprCommand, strStart) ) { return false; } length = Q_strlen(strStart) + 1; // need to do an extra to account for the space if( textLeft > length ) { textLeft -= length; strStart += length; } else // we hit the end of our substrings - abort { space = NULL; } } while (space); return true; } else if ( !strnicmp(text, command, Q_strlen(text))) // just try to match the whole string. { return true; } return false; } //----------------------------------------------------------------------------- // Purpose: rebuilds the list of possible completions from the current entered text //----------------------------------------------------------------------------- void CConsolePanel::RebuildCompletionList(const char *text) { ClearCompletionList(); // we need the length of the text for the partial string compares int len = Q_strlen(text); if ( len < 1 ) { // Fill the completion list with history instead for ( int i = 0 ; i < m_CommandHistory.Count(); i++ ) { CHistoryItem *item = &m_CommandHistory[ i ]; CompletionItem *comp = new CompletionItem(); m_CompletionList.AddToTail( comp ); comp->m_bIsCommand = false; comp->m_pCommand = NULL; comp->m_pText = new CHistoryItem( *item ); } return; } bool bNormalBuild = true; bool bCheckSubstrings = false; const char *space = strstr( text, " " ); if ( space ) { ConCommand *pCommand = FindAutoCompleteCommmandFromPartial( text ); if ( pCommand ) { bNormalBuild = false; CUtlVector< CUtlString > commands; int count = pCommand->AutoCompleteSuggest( text, commands ); Assert( count <= COMMAND_COMPLETION_MAXITEMS ); int i; for ( i = 0; i < count; i++ ) { // match found, add to list CompletionItem *item = new CompletionItem(); m_CompletionList.AddToTail( item ); item->m_bIsCommand = false; item->m_pCommand = NULL; item->m_pText = new CHistoryItem( commands[ i ].String() ); } } else { bCheckSubstrings = true; } } if ( bNormalBuild ) { // look through the command list for all matches ICvar::Iterator iter( g_pCVar ); for ( iter.SetFirst() ; iter.IsValid() ; iter.Next() ) { ConCommandBase *cmd = iter.Get(); if ( cmd->IsFlagSet( FCVAR_DEVELOPMENTONLY ) || cmd->IsFlagSet( FCVAR_HIDDEN ) ) { continue; } if (CommandMatchesText(cmd->GetName(), text, bCheckSubstrings )) { // match found, add to list CompletionItem *item = new CompletionItem(); m_CompletionList.AddToTail( item ); item->m_pCommand = (ConCommandBase *)cmd; const char *tst = cmd->GetName(); if ( !cmd->IsCommand() ) { item->m_bIsCommand = false; ConVar *var = ( ConVar * )cmd; ConVar_ServerBounded *pBounded = dynamic_cast( var ); if ( pBounded || var->IsFlagSet( FCVAR_NEVER_AS_STRING ) ) { char strValue[512]; int intVal = pBounded ? pBounded->GetInt() : var->GetInt(); float floatVal = pBounded ? pBounded->GetFloat() : var->GetFloat(); if ( floatVal == intVal ) Q_snprintf( strValue, sizeof( strValue ), "%d", intVal ); else Q_snprintf( strValue, sizeof( strValue ), "%f", floatVal ); item->m_pText = new CHistoryItem( var->GetName(), strValue ); } else { item->m_pText = new CHistoryItem( var->GetName(), var->GetString() ); } } else { item->m_bIsCommand = true; item->m_pText = new CHistoryItem( tst ); } } } // Now sort the list by command name if ( m_CompletionList.Count() >= 2 ) { m_CompletionList.Sort( &CompletionItemCompare ); } } } bool CConsolePanel::GetCompletionItemText(char *pDest, int completionIndex, int maxLen) { pDest[0] = 0; if (m_CompletionList.IsValidIndex(completionIndex)) { CompletionItem *item = m_CompletionList[completionIndex]; Assert(item); if ( !item->m_bIsCommand && item->m_pCommand ) { Q_strncpy(pDest, item->GetCommand(), maxLen ); } else { Q_strncpy(pDest, item->GetItemText(), maxLen ); } return true; } return false; } //----------------------------------------------------------------------------- // Purpose: auto completes current text //----------------------------------------------------------------------------- void CConsolePanel::OnAutoComplete(eCompletionType completionType) { if (!m_bAutoCompleteMode) { // we're not in auto-complete mode, Start m_iNextCompletion = 0; m_bAutoCompleteMode = true; } // if we're in reverse, move back to before the current if (completionType == COMPLETE_TYPE_REVERSE) { m_iNextCompletion -= 2; if (m_iNextCompletion < 0) { // loop around in reverse m_iNextCompletion = m_CompletionList.Count() - 1; } } // get the next completion if (!m_CompletionList.IsValidIndex(m_iNextCompletion)) { // loop completion list m_iNextCompletion = 0; } // make sure everything is still valid if (!m_CompletionList.IsValidIndex(m_iNextCompletion)) return; char completedText[256]; // are we trying to tab-complete our command? We need to have at least two valid entries for that if (completionType == COMPLETE_TYPE_COMMON_STRING && m_CompletionList.Count() > 1 ) { // see how many of our characters match between the first and last items in our list. // this should be all of the common characters for all items in our list. char lastMatchText[256]; GetCompletionItemText(completedText, 0, sizeof(completedText) - 2 ); GetCompletionItemText(lastMatchText, m_CompletionList.Count() - 1, sizeof(lastMatchText) - 2 ); unsigned int i = 0; // make sure that we aren't doing a sub-string match, we won't be able to tab-complete in that case. if( !Q_strncasecmp(m_szPartialText, completedText, strlen(m_szPartialText)) && !Q_strncasecmp(m_szPartialText, lastMatchText, strlen(m_szPartialText)) ) { for( ; i < strlen(completedText); i++ ) { if( toupper( completedText[i] ) != toupper( lastMatchText[i] ) ) { break; } } } // terminate where we differ completedText[i] = 0; } else { GetCompletionItemText(completedText, m_iNextCompletion, sizeof(completedText) - 2 ); if ( !Q_strstr( completedText, " " ) ) { Q_strncat(completedText, " ", sizeof(completedText), COPY_ALL_CHARACTERS ); } m_iNextCompletion++; } // Only set our text if we actually changed something if( strlen(completedText) > strlen(m_szPartialText) ) { m_pEntry->SetText(completedText); // only do this for tab completion, we don't want to mess up our cycling by rebuilding the completion list. if( completionType == COMPLETE_TYPE_COMMON_STRING ) { OnTextChanged( m_pEntry ); } } m_pEntry->GotoTextEnd(); m_pEntry->SelectNone(); } //----------------------------------------------------------------------------- // Purpose: Called whenever the user types text //----------------------------------------------------------------------------- void CConsolePanel::OnTextChanged(Panel *panel) { if (panel != m_pEntry) return; Q_strncpy( m_szPreviousPartialText, m_szPartialText, sizeof( m_szPreviousPartialText ) ); // get the partial text the user type m_pEntry->GetText(m_szPartialText, sizeof(m_szPartialText)); // see if they've hit the tilde key (which opens & closes the console) int len = Q_strlen(m_szPartialText); bool hitTilde = ( m_szPartialText[len - 1] == '~' || m_szPartialText[len - 1] == '`' ) ? true : false; bool altKeyDown = ( vgui::input()->IsKeyDown( KEY_LALT ) || vgui::input()->IsKeyDown( KEY_RALT ) ) ? true : false; bool ctrlKeyDown = ( vgui::input()->IsKeyDown( KEY_LCONTROL ) || vgui::input()->IsKeyDown( KEY_RCONTROL ) ) ? true : false; // Alt-Tilde toggles Japanese IME on/off!!! if ( ( len > 0 ) && hitTilde ) { // Strip the last character (tilde) m_szPartialText[ len - 1 ] = L'\0'; if( !altKeyDown && !ctrlKeyDown ) { m_pEntry->SetText( "" ); // close the console PostMessage( this, new KeyValues( "Close" ) ); PostActionSignal( new KeyValues( "ClosedByHittingTilde" ) ); } else { m_pEntry->SetText( m_szPartialText ); } return; } // clear auto-complete state since the user has typed m_bAutoCompleteMode = false; RebuildCompletionList(m_szPartialText); // build the menu if ( m_CompletionList.Count() < 1 ) { m_pCompletionList->SetVisible(false); } else { m_pCompletionList->SetVisible(true); m_pCompletionList->DeleteAllItems(); const int MAX_MENU_ITEMS = 10; // add the first ten items to the list for (int i = 0; i < m_CompletionList.Count() && i < MAX_MENU_ITEMS; i++) { char text[256]; text[0] = 0; if (i == MAX_MENU_ITEMS - 1) { Q_strncpy(text, "...", sizeof( text ) ); } else { Assert( m_CompletionList[i] ); Q_strncpy(text, m_CompletionList[i]->GetItemText(), sizeof( text ) ); } KeyValues *kv = new KeyValues("CompletionCommand"); kv->SetString("command",text); m_pCompletionList->AddMenuItem(text, kv, this); } UpdateCompletionListPosition(); } RequestFocus(); m_pEntry->RequestFocus(); } //----------------------------------------------------------------------------- // Purpose: generic vgui command handler //----------------------------------------------------------------------------- void CConsolePanel::OnCommand(const char *command) { if ( !Q_stricmp( command, "Submit" ) ) { // submit the entry as a console commmand char szCommand[256]; m_pEntry->GetText(szCommand, sizeof(szCommand)); PostActionSignal( new KeyValues( "CommandSubmitted", "command", szCommand ) ); // add to the history Print("] "); Print(szCommand); Print("\n"); // clear the field m_pEntry->SetText(""); // clear the completion state OnTextChanged(m_pEntry); // always go the end of the buffer when the user has typed something m_pHistory->GotoTextEnd(); // Add the command to the history char *extra = strchr(szCommand, ' '); if ( extra ) { *extra = '\0'; extra++; } if ( Q_strlen( szCommand ) > 0 ) { AddToHistory( szCommand, extra ); } m_pCompletionList->SetVisible(false); } else { BaseClass::OnCommand(command); } } //----------------------------------------------------------------------------- // Focus related methods //----------------------------------------------------------------------------- bool CConsolePanel::TextEntryHasFocus() const { return ( input()->GetFocus() == m_pEntry->GetVPanel() ); } void CConsolePanel::TextEntryRequestFocus() { m_pEntry->RequestFocus(); } //----------------------------------------------------------------------------- // Purpose: swallows tab key pressed //----------------------------------------------------------------------------- void CConsolePanel::OnKeyCodeTyped(KeyCode code) { BaseClass::OnKeyCodeTyped(code); // check for processing if ( TextEntryHasFocus() ) { if (code == KEY_TAB) { if (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)) { OnAutoComplete( COMPLETE_TYPE_REVERSE ); } else { OnAutoComplete( COMPLETE_TYPE_FORWARD ); } m_pEntry->RequestFocus(); } else if (code == KEY_DOWN) { OnAutoComplete( COMPLETE_TYPE_FORWARD ); m_pEntry->RequestFocus(); } else if (code == KEY_UP) { OnAutoComplete( COMPLETE_TYPE_REVERSE ); m_pEntry->RequestFocus(); } } } //----------------------------------------------------------------------------- // Purpose: modifies the style of our text entry if we are in substring mode. //----------------------------------------------------------------------------- void CConsolePanel::UpdateEntryStyle() { IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); m_pEntry->SetBorder( pScheme->GetBorder("DepressedButtonBorder")); } //----------------------------------------------------------------------------- // Purpose: lays out controls //----------------------------------------------------------------------------- void CConsolePanel::PerformLayout() { BaseClass::PerformLayout(); // setup tab ordering GetFocusNavGroup().SetDefaultButton(m_pSubmit); IScheme *pScheme = scheme()->GetIScheme( GetScheme() ); m_pEntry->SetBorder(pScheme->GetBorder("DepressedButtonBorder")); m_pHistory->SetBorder(pScheme->GetBorder("DepressedButtonBorder")); // layout controls int wide, tall; GetSize(wide, tall); if ( !m_bStatusVersion ) { const int inset = 8; const int entryHeight = 24; const int topHeight = 4; const int entryInset = 4; const int submitWide = 64; const int submitInset = 7; // x inset to pull the submit button away from the frame grab m_pHistory->SetPos(inset, inset + topHeight); m_pHistory->SetSize(wide - (inset * 2), tall - (entryInset * 2 + inset * 2 + topHeight + entryHeight)); m_pHistory->InvalidateLayout(); int nSubmitXPos = wide - ( inset + submitWide + submitInset ); m_pSubmit->SetPos( nSubmitXPos, tall - (entryInset * 2 + entryHeight)); m_pSubmit->SetSize( submitWide, entryHeight); m_pEntry->SetPos( inset, tall - (entryInset * 2 + entryHeight) ); m_pEntry->SetSize( nSubmitXPos - entryInset - 2 * inset, entryHeight); } else { const int inset = 2; int entryWidth = wide / 2; if ( wide > 400 ) { entryWidth = 200; } m_pEntry->SetBounds( inset, inset, entryWidth, tall - 2 * inset ); m_pHistory->SetBounds( inset + entryWidth + inset, inset, ( wide - entryWidth ) - inset, tall - 2 * inset ); } UpdateCompletionListPosition(); } //----------------------------------------------------------------------------- // Purpose: Sets the position of the completion list popup //----------------------------------------------------------------------------- void CConsolePanel::UpdateCompletionListPosition() { int ex, ey; m_pEntry->GetPos(ex, ey); if ( !m_bStatusVersion ) { // Position below text entry ey += m_pEntry->GetTall(); } else { // Position above text entry int menuwide, menutall; m_pCompletionList->GetSize( menuwide, menutall ); ey -= ( menutall + 4 ); } LocalToScreen( ex, ey ); m_pCompletionList->SetPos( ex, ey ); if ( m_pCompletionList->IsVisible() ) { m_pEntry->RequestFocus(); MoveToFront(); m_pCompletionList->MoveToFront(); } } //----------------------------------------------------------------------------- // Purpose: Closes the completion list //----------------------------------------------------------------------------- void CConsolePanel::CloseCompletionList() { m_pCompletionList->SetVisible(false); } //----------------------------------------------------------------------------- // Purpose: sets up colors //----------------------------------------------------------------------------- void CConsolePanel::ApplySchemeSettings(IScheme *pScheme) { BaseClass::ApplySchemeSettings(pScheme); m_PrintColor = GetSchemeColor("Console.TextColor", pScheme); m_DPrintColor = GetSchemeColor("Console.DevTextColor", pScheme); m_pHistory->SetFont( pScheme->GetFont( "ConsoleText", IsProportional() ) ); m_pEntry->SetFont( pScheme->GetFont( "DefaultSmall", IsProportional() ) ); m_pCompletionList->SetFont( pScheme->GetFont( "DefaultSmall", IsProportional() ) ); m_pSubmit->SetFont( pScheme->GetFont( "DefaultSmall", IsProportional() ) ); if ( true ) // make the console opaque { Color bgColor( 64,64,64, 255 ); SetBgColor( bgColor ); m_pHistory->SetBgColor( bgColor ); m_pEntry->SetBgColor( bgColor ); m_pCompletionList->SetBgColor( bgColor ); } InvalidateLayout(); } //----------------------------------------------------------------------------- // Purpose: Handles autocompletion menu input //----------------------------------------------------------------------------- void CConsolePanel::OnMenuItemSelected(const char *command) { if ( strstr( command, "..." ) ) // stop the menu going away if you click on ... { m_pCompletionList->SetVisible( true ); } else { m_pEntry->SetText(command); m_pEntry->GotoTextEnd(); m_pEntry->InsertChar(' '); m_pEntry->GotoTextEnd(); } } void CConsolePanel::Hide() { OnClose(); m_iNextCompletion = 0; RebuildCompletionList(""); } void CConsolePanel::AddToHistory( const char *commandText, const char *extraText ) { // Newest at end, oldest at head while ( m_CommandHistory.Count() >= MAX_HISTORY_ITEMS ) { // Remove from head until size is reasonable m_CommandHistory.Remove( 0 ); } // strip the space off the end of the command before adding it to the history char *command = static_cast( stackalloc( (strlen( commandText ) + 1 ) * sizeof( char ) )); if ( command ) { memset( command, 0x0, strlen( commandText ) + 1 ); strncpy( command, commandText, strlen( commandText )); if ( command[ strlen( commandText ) -1 ] == ' ' ) { command[ strlen( commandText ) -1 ] = '\0'; } } // strip the quotes off the extra text char *extra = NULL; if ( extraText ) { extra = static_cast( malloc( (strlen( extraText ) + 1 ) * sizeof( char ) )); if ( extra ) { memset( extra, 0x0, strlen( extraText ) + 1 ); strncpy( extra, extraText, strlen( extraText )); // +1 to dodge the starting quote // Strip trailing spaces int i = strlen( extra ) - 1; while ( i >= 0 && // Check I before referencing i == -1 into the extra array! extra[ i ] == ' ' ) { extra[ i ] = '\0'; i--; } } } // If it's already there, then remove since we'll add it to the end instead CHistoryItem *item = NULL; for ( int i = m_CommandHistory.Count() - 1; i >= 0; i-- ) { item = &m_CommandHistory[ i ]; if ( !item ) continue; if ( stricmp( item->GetText(), command ) ) continue; if ( extra || item->GetExtra() ) { if ( !extra || !item->GetExtra() ) continue; // stricmp so two commands with the same starting text get added if ( stricmp( item->GetExtra(), extra ) ) continue; } m_CommandHistory.Remove( i ); } item = &m_CommandHistory[ m_CommandHistory.AddToTail() ]; Assert( item ); item->SetText( command, extra ); m_iNextCompletion = 0; RebuildCompletionList( m_szPartialText ); free( extra ); } void CConsolePanel::GetConsoleText( char *pchText, size_t bufSize ) const { wchar_t *temp = new wchar_t[ bufSize ]; m_pHistory->GetText( 0, temp, bufSize * sizeof( wchar_t ) ); g_pVGuiLocalize->ConvertUnicodeToANSI( temp, pchText, bufSize ); delete[] temp; } //----------------------------------------------------------------------------- // Purpose: writes out console to disk //----------------------------------------------------------------------------- void CConsolePanel::DumpConsoleTextToFile() { const int CONDUMP_FILES_MAX_NUM = 1000; FileHandle_t handle; bool found = false; char szfile[ 512 ]; // we don't want to overwrite other condump.txt files for ( int i = 0 ; i < CONDUMP_FILES_MAX_NUM ; ++i ) { _snprintf( szfile, sizeof(szfile), "condump%03d.txt", i ); if ( !g_pFullFileSystem->FileExists(szfile) ) { found = true; break; } } if ( !found ) { Print( "Can't condump! Too many existing condump output files in the gamedir!\n" ); return; } handle = g_pFullFileSystem->Open( szfile, "wb" ); if ( handle != FILESYSTEM_INVALID_HANDLE ) { int pos = 0; while (1) { wchar_t buf[512]; m_pHistory->GetText(pos, buf, sizeof(buf)); pos += (sizeof(buf) / sizeof(wchar_t)) - 1; //-1 to compensate for null terminator // don't continue if none left if (buf[0] == 0) break; // convert to ansi char ansi[512]; g_pVGuiLocalize->ConvertUnicodeToANSI(buf, ansi, sizeof(ansi)); // write to disk int len = strlen(ansi); for (int i = 0; i < len; i++) { // preceed newlines with a return if (ansi[i] == '\n') { char ret = '\r'; g_pFullFileSystem->Write( &ret, 1, handle ); } g_pFullFileSystem->Write( ansi + i, 1, handle ); } } g_pFullFileSystem->Close( handle ); Print( "console dumped to " ); Print( szfile ); Print( "\n" ); } else { Print( "Unable to condump to " ); Print( szfile ); Print( "\n" ); } } //----------------------------------------------------------------------------- // // Console dialog starts here // //----------------------------------------------------------------------------- CConsoleDialog::CConsoleDialog( vgui::Panel *pParent, const char *pName, bool bStatusVersion ) : BaseClass( pParent, pName ) { // initialize dialog SetVisible( false ); SetTitle( "#Console_Title", true ); m_pConsolePanel = new CConsolePanel( this, "ConsolePage", bStatusVersion ); m_pConsolePanel->AddActionSignalTarget( this ); } void CConsoleDialog::OnScreenSizeChanged( int iOldWide, int iOldTall ) { BaseClass::OnScreenSizeChanged( iOldWide, iOldTall ); int sx, sy; surface()->GetScreenSize( sx, sy ); int w, h; GetSize( w, h ); if ( w > sx || h > sy ) { if ( w > sx ) { w = sx; } if ( h > sy ) { h = sy; } // Try and lower the size to match the screen bounds SetSize( w, h ); } } //----------------------------------------------------------------------------- // Purpose: brings dialog to the fore //----------------------------------------------------------------------------- void CConsoleDialog::PerformLayout() { BaseClass::PerformLayout(); int x, y, w, h; GetClientArea( x, y, w, h ); m_pConsolePanel->SetBounds( x, y, w, h ); } //----------------------------------------------------------------------------- // Purpose: brings dialog to the fore //----------------------------------------------------------------------------- void CConsoleDialog::Activate() { BaseClass::Activate(); m_pConsolePanel->m_pEntry->RequestFocus(); static ConVarRef cv_vguipanel_active( "vgui_panel_active" ); static ConVarRef cv_console_window_open( "console_window_open" ); if ( !cv_console_window_open.GetBool() ) cv_vguipanel_active.SetValue( cv_vguipanel_active.GetInt() + 1 ); cv_console_window_open.SetValue( true ); } //----------------------------------------------------------------------------- // Hides the dialog //----------------------------------------------------------------------------- void CConsoleDialog::Hide() { static ConVarRef cv_vguipanel_active( "vgui_panel_active" ); static ConVarRef cv_console_window_open( "console_window_open" ); if ( cv_console_window_open.GetBool() ) cv_vguipanel_active.SetValue( cv_vguipanel_active.GetInt() - 1 ); cv_console_window_open.SetValue( false ); OnClose(); m_pConsolePanel->Hide(); } //----------------------------------------------------------------------------- // Close just hides the dialog //----------------------------------------------------------------------------- void CConsoleDialog::Close() { Hide(); } //----------------------------------------------------------------------------- // Submits commands //----------------------------------------------------------------------------- void CConsoleDialog::OnCommandSubmitted( const char *pCommand ) { PostActionSignal( new KeyValues( "CommandSubmitted", "command", pCommand ) ); } //----------------------------------------------------------------------------- // Chain to the page //----------------------------------------------------------------------------- void CConsoleDialog::Print( const char *pMessage ) { m_pConsolePanel->Print( pMessage ); } void CConsoleDialog::DPrint( const char *pMessage ) { m_pConsolePanel->DPrint( pMessage ); } void CConsoleDialog::ColorPrint( const Color& clr, const char *msg ) { m_pConsolePanel->ColorPrint( clr, msg ); } void CConsoleDialog::Clear() { m_pConsolePanel->Clear( ); } void CConsoleDialog::DumpConsoleTextToFile() { m_pConsolePanel->DumpConsoleTextToFile( ); }