1372 lines
34 KiB
C++
1372 lines
34 KiB
C++
//===== Copyright <20> 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 <ctype.h>
|
||
#include <stdlib.h>
|
||
|
||
#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<ConVar_ServerBounded*>( 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<char *>( 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<char *>( 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( );
|
||
}
|
||
|
||
|