Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1255 lines
30 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "vgui_controls/consoledialog.h"
  8. #include "vgui/IInput.h"
  9. #include "vgui/IScheme.h"
  10. #include "vgui/IVGui.h"
  11. #include "vgui/ISurface.h"
  12. #include "vgui/ILocalize.h"
  13. #include "KeyValues.h"
  14. #include "vgui_controls/Button.h"
  15. #include "vgui/KeyCode.h"
  16. #include "vgui_controls/Menu.h"
  17. #include "vgui_controls/TextEntry.h"
  18. #include "vgui_controls/RichText.h"
  19. #include "tier1/convar.h"
  20. #include "tier1/convar_serverbounded.h"
  21. #include "icvar.h"
  22. #include "filesystem.h"
  23. #include <stdlib.h>
  24. #if defined( _X360 )
  25. #include "xbox/xbox_win32stubs.h"
  26. #endif
  27. // memdbgon must be the last include file in a .cpp file!!!
  28. #include "tier0/memdbgon.h"
  29. using namespace vgui;
  30. //-----------------------------------------------------------------------------
  31. // Used by the autocompletion system
  32. //-----------------------------------------------------------------------------
  33. class CNonFocusableMenu : public Menu
  34. {
  35. DECLARE_CLASS_SIMPLE( CNonFocusableMenu, Menu );
  36. public:
  37. CNonFocusableMenu( Panel *parent, const char *panelName )
  38. : BaseClass( parent, panelName ),
  39. m_pFocus( 0 )
  40. {
  41. }
  42. void SetFocusPanel( Panel *panel )
  43. {
  44. m_pFocus = panel;
  45. }
  46. VPANEL GetCurrentKeyFocus()
  47. {
  48. if ( !m_pFocus )
  49. return GetVPanel();
  50. return m_pFocus->GetVPanel();
  51. }
  52. private:
  53. Panel *m_pFocus;
  54. };
  55. //-----------------------------------------------------------------------------
  56. // Purpose: forwards tab key presses up from the text entry so we can do autocomplete
  57. //-----------------------------------------------------------------------------
  58. class TabCatchingTextEntry : public TextEntry
  59. {
  60. public:
  61. TabCatchingTextEntry(Panel *parent, const char *name, VPANEL comp) : TextEntry(parent, name), m_pCompletionList( comp )
  62. {
  63. SetAllowNonAsciiCharacters( true );
  64. SetDragEnabled( true );
  65. }
  66. virtual void OnKeyCodeTyped(KeyCode code)
  67. {
  68. if (code == KEY_TAB)
  69. {
  70. GetParent()->OnKeyCodeTyped(code);
  71. }
  72. else if ( code == KEY_ENTER )
  73. {
  74. // submit is the default button whose click event will have been called already
  75. }
  76. else
  77. {
  78. TextEntry::OnKeyCodeTyped(code);
  79. }
  80. }
  81. virtual void OnKillFocus()
  82. {
  83. if ( input()->GetFocus() != m_pCompletionList ) // if its not the completion window trying to steal our focus
  84. {
  85. PostMessage(GetParent(), new KeyValues("CloseCompletionList"));
  86. }
  87. }
  88. private:
  89. VPANEL m_pCompletionList;
  90. };
  91. // Things the user typed in and hit submit/return with
  92. CHistoryItem::CHistoryItem( void )
  93. {
  94. m_text = NULL;
  95. m_extraText = NULL;
  96. m_bHasExtra = false;
  97. }
  98. CHistoryItem::CHistoryItem( const char *text, const char *extra )
  99. {
  100. Assert( text );
  101. m_text = NULL;
  102. m_extraText = NULL;
  103. m_bHasExtra = false;
  104. SetText( text , extra );
  105. }
  106. CHistoryItem::CHistoryItem( const CHistoryItem& src )
  107. {
  108. m_text = NULL;
  109. m_extraText = NULL;
  110. m_bHasExtra = false;
  111. SetText( src.GetText(), src.GetExtra() );
  112. }
  113. CHistoryItem::~CHistoryItem( void )
  114. {
  115. delete[] m_text;
  116. delete[] m_extraText;
  117. m_text = NULL;
  118. }
  119. const char *CHistoryItem::GetText() const
  120. {
  121. if ( m_text )
  122. {
  123. return m_text;
  124. }
  125. else
  126. {
  127. return "";
  128. }
  129. }
  130. const char *CHistoryItem::GetExtra() const
  131. {
  132. if ( m_extraText )
  133. {
  134. return m_extraText;
  135. }
  136. else
  137. {
  138. return NULL;
  139. }
  140. }
  141. void CHistoryItem::SetText( const char *text, const char *extra )
  142. {
  143. delete[] m_text;
  144. int len = strlen( text ) + 1;
  145. m_text = new char[ len ];
  146. Q_memset( m_text, 0x0, len );
  147. Q_strncpy( m_text, text, len );
  148. if ( extra )
  149. {
  150. m_bHasExtra = true;
  151. delete[] m_extraText;
  152. int elen = strlen( extra ) + 1;
  153. m_extraText = new char[ elen ];
  154. Q_memset( m_extraText, 0x0, elen);
  155. Q_strncpy( m_extraText, extra, elen );
  156. }
  157. else
  158. {
  159. m_bHasExtra = false;
  160. }
  161. }
  162. //-----------------------------------------------------------------------------
  163. //
  164. // Console page completion item starts here
  165. //
  166. //-----------------------------------------------------------------------------
  167. CConsolePanel::CompletionItem::CompletionItem( void )
  168. {
  169. m_bIsCommand = true;
  170. m_pCommand = NULL;
  171. m_pText = NULL;
  172. }
  173. CConsolePanel::CompletionItem::CompletionItem( const CompletionItem& src )
  174. {
  175. m_bIsCommand = src.m_bIsCommand;
  176. m_pCommand = src.m_pCommand;
  177. if ( src.m_pText )
  178. {
  179. m_pText = new CHistoryItem( (const CHistoryItem& )src.m_pText );
  180. }
  181. else
  182. {
  183. m_pText = NULL;
  184. }
  185. }
  186. CConsolePanel::CompletionItem& CConsolePanel::CompletionItem::operator =( const CompletionItem& src )
  187. {
  188. if ( this == &src )
  189. return *this;
  190. m_bIsCommand = src.m_bIsCommand;
  191. m_pCommand = src.m_pCommand;
  192. if ( src.m_pText )
  193. {
  194. m_pText = new CHistoryItem( (const CHistoryItem& )*src.m_pText );
  195. }
  196. else
  197. {
  198. m_pText = NULL;
  199. }
  200. return *this;
  201. }
  202. CConsolePanel::CompletionItem::~CompletionItem( void )
  203. {
  204. if ( m_pText )
  205. {
  206. delete m_pText;
  207. m_pText = NULL;
  208. }
  209. }
  210. const char *CConsolePanel::CompletionItem::GetName() const
  211. {
  212. if ( m_bIsCommand )
  213. return m_pCommand->GetName();
  214. return m_pCommand ? m_pCommand->GetName() : GetCommand();
  215. }
  216. const char *CConsolePanel::CompletionItem::GetItemText( void )
  217. {
  218. static char text[256];
  219. text[0] = 0;
  220. if ( m_pText )
  221. {
  222. if ( m_pText->HasExtra() )
  223. {
  224. Q_snprintf( text, sizeof( text ), "%s %s", m_pText->GetText(), m_pText->GetExtra() );
  225. }
  226. else
  227. {
  228. Q_strncpy( text, m_pText->GetText(), sizeof( text ) );
  229. }
  230. }
  231. return text;
  232. }
  233. const char *CConsolePanel::CompletionItem::GetCommand( void ) const
  234. {
  235. static char text[256];
  236. text[0] = 0;
  237. if ( m_pText )
  238. {
  239. Q_strncpy( text, m_pText->GetText(), sizeof( text ) );
  240. }
  241. return text;
  242. }
  243. //-----------------------------------------------------------------------------
  244. //
  245. // Console page starts here
  246. //
  247. //-----------------------------------------------------------------------------
  248. //-----------------------------------------------------------------------------
  249. // Purpose: Constructor, destuctor
  250. //-----------------------------------------------------------------------------
  251. CConsolePanel::CConsolePanel( vgui::Panel *pParent, const char *pName, bool bStatusVersion ) :
  252. BaseClass( pParent, pName ), m_bStatusVersion( bStatusVersion )
  253. {
  254. SetKeyBoardInputEnabled( true );
  255. if ( !m_bStatusVersion )
  256. {
  257. SetMinimumSize(100,100);
  258. }
  259. // create controls
  260. m_pHistory = new RichText(this, "ConsoleHistory");
  261. m_pHistory->SetAllowKeyBindingChainToParent( false );
  262. SETUP_PANEL( m_pHistory );
  263. m_pHistory->SetVerticalScrollbar( !m_bStatusVersion );
  264. if ( m_bStatusVersion )
  265. {
  266. m_pHistory->SetDrawOffsets( 3, 3 );
  267. }
  268. m_pHistory->GotoTextEnd();
  269. m_pSubmit = new Button(this, "ConsoleSubmit", "#Console_Submit");
  270. m_pSubmit->SetCommand("submit");
  271. m_pSubmit->SetVisible( !m_bStatusVersion );
  272. CNonFocusableMenu *pCompletionList = new CNonFocusableMenu( this, "CompletionList" );
  273. m_pCompletionList = pCompletionList;
  274. m_pCompletionList->SetVisible(false);
  275. m_pEntry = new TabCatchingTextEntry(this, "ConsoleEntry", m_pCompletionList->GetVPanel() );
  276. m_pEntry->AddActionSignalTarget(this);
  277. m_pEntry->SendNewLine(true);
  278. pCompletionList->SetFocusPanel( m_pEntry );
  279. // need to set up default colors, since ApplySchemeSettings won't be called until later
  280. m_PrintColor = Color(216, 222, 211, 255);
  281. m_DPrintColor = Color(196, 181, 80, 255);
  282. m_pEntry->SetTabPosition(1);
  283. m_bAutoCompleteMode = false;
  284. m_szPartialText[0] = 0;
  285. m_szPreviousPartialText[0]=0;
  286. // Add to global console list
  287. g_pCVar->InstallConsoleDisplayFunc( this );
  288. }
  289. //-----------------------------------------------------------------------------
  290. // Purpose: Destructor
  291. //-----------------------------------------------------------------------------
  292. CConsolePanel::~CConsolePanel()
  293. {
  294. ClearCompletionList();
  295. m_CommandHistory.Purge();
  296. g_pCVar->RemoveConsoleDisplayFunc( this );
  297. }
  298. //-----------------------------------------------------------------------------
  299. // Updates the completion list
  300. //-----------------------------------------------------------------------------
  301. void CConsolePanel::OnThink()
  302. {
  303. BaseClass::OnThink();
  304. if ( !IsVisible() )
  305. return;
  306. if ( !m_pCompletionList->IsVisible() )
  307. return;
  308. UpdateCompletionListPosition();
  309. }
  310. //-----------------------------------------------------------------------------
  311. // Purpose: Clears the console
  312. //-----------------------------------------------------------------------------
  313. void CConsolePanel::Clear()
  314. {
  315. m_pHistory->SetText("");
  316. m_pHistory->GotoTextEnd();
  317. }
  318. //-----------------------------------------------------------------------------
  319. // Purpose: color text print
  320. //-----------------------------------------------------------------------------
  321. void CConsolePanel::ColorPrint( const Color& clr, const char *msg )
  322. {
  323. if ( m_bStatusVersion )
  324. {
  325. Clear();
  326. }
  327. m_pHistory->InsertColorChange( clr );
  328. m_pHistory->InsertString( msg );
  329. }
  330. //-----------------------------------------------------------------------------
  331. // Purpose: normal text print
  332. //-----------------------------------------------------------------------------
  333. void CConsolePanel::Print(const char *msg)
  334. {
  335. ColorPrint( m_PrintColor, msg );
  336. }
  337. //-----------------------------------------------------------------------------
  338. // Purpose: debug text print
  339. //-----------------------------------------------------------------------------
  340. void CConsolePanel::DPrint( const char *msg )
  341. {
  342. ColorPrint( m_DPrintColor, msg );
  343. }
  344. void CConsolePanel::ClearCompletionList()
  345. {
  346. int c = m_CompletionList.Count();
  347. int i;
  348. for ( i = c - 1; i >= 0; i-- )
  349. {
  350. delete m_CompletionList[ i ];
  351. }
  352. m_CompletionList.Purge();
  353. }
  354. static ConCommand *FindAutoCompleteCommmandFromPartial( const char *partial )
  355. {
  356. char command[ 256 ];
  357. Q_strncpy( command, partial, sizeof( command ) );
  358. char *space = Q_strstr( command, " " );
  359. if ( space )
  360. {
  361. *space = 0;
  362. }
  363. ConCommand *cmd = g_pCVar->FindCommand( command );
  364. if ( !cmd )
  365. return NULL;
  366. if ( !cmd->CanAutoComplete() )
  367. return NULL;
  368. return cmd;
  369. }
  370. //-----------------------------------------------------------------------------
  371. // Purpose: rebuilds the list of possible completions from the current entered text
  372. //-----------------------------------------------------------------------------
  373. void CConsolePanel::RebuildCompletionList(const char *text)
  374. {
  375. ClearCompletionList();
  376. // we need the length of the text for the partial string compares
  377. int len = Q_strlen(text);
  378. if ( len < 1 )
  379. {
  380. // Fill the completion list with history instead
  381. for ( int i = 0 ; i < m_CommandHistory.Count(); i++ )
  382. {
  383. CHistoryItem *item = &m_CommandHistory[ i ];
  384. CompletionItem *comp = new CompletionItem();
  385. m_CompletionList.AddToTail( comp );
  386. comp->m_bIsCommand = false;
  387. comp->m_pCommand = NULL;
  388. comp->m_pText = new CHistoryItem( *item );
  389. }
  390. return;
  391. }
  392. bool bNormalBuild = true;
  393. // if there is a space in the text, and the command isn't of the type to know how to autocomplet, then command completion is over
  394. const char *space = strstr( text, " " );
  395. if ( space )
  396. {
  397. ConCommand *pCommand = FindAutoCompleteCommmandFromPartial( text );
  398. if ( !pCommand )
  399. return;
  400. bNormalBuild = false;
  401. CUtlVector< CUtlString > commands;
  402. int count = pCommand->AutoCompleteSuggest( text, commands );
  403. Assert( count <= COMMAND_COMPLETION_MAXITEMS );
  404. int i;
  405. for ( i = 0; i < count; i++ )
  406. {
  407. // match found, add to list
  408. CompletionItem *item = new CompletionItem();
  409. m_CompletionList.AddToTail( item );
  410. item->m_bIsCommand = false;
  411. item->m_pCommand = NULL;
  412. item->m_pText = new CHistoryItem( commands[ i ].String() );
  413. }
  414. }
  415. if ( bNormalBuild )
  416. {
  417. // look through the command list for all matches
  418. ConCommandBase const *cmd = (ConCommandBase const *)cvar->GetCommands();
  419. while (cmd)
  420. {
  421. if ( cmd->IsFlagSet( FCVAR_DEVELOPMENTONLY ) || cmd->IsFlagSet( FCVAR_HIDDEN ) )
  422. {
  423. cmd = cmd->GetNext();
  424. continue;
  425. }
  426. if ( !strnicmp(text, cmd->GetName(), len))
  427. {
  428. // match found, add to list
  429. CompletionItem *item = new CompletionItem();
  430. m_CompletionList.AddToTail( item );
  431. item->m_pCommand = (ConCommandBase *)cmd;
  432. const char *tst = cmd->GetName();
  433. if ( !cmd->IsCommand() )
  434. {
  435. item->m_bIsCommand = false;
  436. ConVar *var = ( ConVar * )cmd;
  437. ConVar_ServerBounded *pBounded = dynamic_cast<ConVar_ServerBounded*>( var );
  438. if ( pBounded || var->IsFlagSet( FCVAR_NEVER_AS_STRING ) )
  439. {
  440. char strValue[512];
  441. int intVal = pBounded ? pBounded->GetInt() : var->GetInt();
  442. float floatVal = pBounded ? pBounded->GetFloat() : var->GetFloat();
  443. if ( floatVal == intVal )
  444. Q_snprintf( strValue, sizeof( strValue ), "%d", intVal );
  445. else
  446. Q_snprintf( strValue, sizeof( strValue ), "%f", floatVal );
  447. item->m_pText = new CHistoryItem( var->GetName(), strValue );
  448. }
  449. else
  450. {
  451. item->m_pText = new CHistoryItem( var->GetName(), var->GetString() );
  452. }
  453. }
  454. else
  455. {
  456. item->m_bIsCommand = true;
  457. item->m_pText = new CHistoryItem( tst );
  458. }
  459. }
  460. cmd = cmd->GetNext();
  461. }
  462. // Now sort the list by command name
  463. if ( m_CompletionList.Count() >= 2 )
  464. {
  465. for ( int i = 0 ; i < m_CompletionList.Count(); i++ )
  466. {
  467. for ( int j = i + 1; j < m_CompletionList.Count(); j++ )
  468. {
  469. const CompletionItem *i1, *i2;
  470. i1 = m_CompletionList[ i ];
  471. i2 = m_CompletionList[ j ];
  472. if ( Q_stricmp( i1->GetName(), i2->GetName() ) > 0 )
  473. {
  474. CompletionItem *temp = m_CompletionList[ i ];
  475. m_CompletionList[ i ] = m_CompletionList[ j ];
  476. m_CompletionList[ j ] = temp;
  477. }
  478. }
  479. }
  480. }
  481. }
  482. }
  483. //-----------------------------------------------------------------------------
  484. // Purpose: auto completes current text
  485. //-----------------------------------------------------------------------------
  486. void CConsolePanel::OnAutoComplete(bool reverse)
  487. {
  488. if (!m_bAutoCompleteMode)
  489. {
  490. // we're not in auto-complete mode, Start
  491. m_iNextCompletion = 0;
  492. m_bAutoCompleteMode = true;
  493. }
  494. // if we're in reverse, move back to before the current
  495. if (reverse)
  496. {
  497. m_iNextCompletion -= 2;
  498. if (m_iNextCompletion < 0)
  499. {
  500. // loop around in reverse
  501. m_iNextCompletion = m_CompletionList.Size() - 1;
  502. }
  503. }
  504. // get the next completion
  505. if (!m_CompletionList.IsValidIndex(m_iNextCompletion))
  506. {
  507. // loop completion list
  508. m_iNextCompletion = 0;
  509. }
  510. // make sure everything is still valid
  511. if (!m_CompletionList.IsValidIndex(m_iNextCompletion))
  512. return;
  513. // match found, set text
  514. char completedText[256];
  515. CompletionItem *item = m_CompletionList[m_iNextCompletion];
  516. Assert( item );
  517. if ( !item->m_bIsCommand && item->m_pCommand )
  518. {
  519. Q_strncpy(completedText, item->GetCommand(), sizeof(completedText) - 2 );
  520. }
  521. else
  522. {
  523. Q_strncpy(completedText, item->GetItemText(), sizeof(completedText) - 2 );
  524. }
  525. if ( !Q_strstr( completedText, " " ) )
  526. {
  527. Q_strncat(completedText, " ", sizeof(completedText), COPY_ALL_CHARACTERS );
  528. }
  529. m_pEntry->SetText(completedText);
  530. m_pEntry->GotoTextEnd();
  531. m_pEntry->SelectNone();
  532. m_iNextCompletion++;
  533. }
  534. //-----------------------------------------------------------------------------
  535. // Purpose: Called whenever the user types text
  536. //-----------------------------------------------------------------------------
  537. void CConsolePanel::OnTextChanged(Panel *panel)
  538. {
  539. if (panel != m_pEntry)
  540. return;
  541. Q_strncpy( m_szPreviousPartialText, m_szPartialText, sizeof( m_szPreviousPartialText ) );
  542. // get the partial text the user type
  543. m_pEntry->GetText(m_szPartialText, sizeof(m_szPartialText));
  544. // see if they've hit the tilde key (which opens & closes the console)
  545. int len = Q_strlen(m_szPartialText);
  546. bool hitTilde = ( m_szPartialText[len - 1] == '~' || m_szPartialText[len - 1] == '`' ) ? true : false;
  547. bool altKeyDown = ( vgui::input()->IsKeyDown( KEY_LALT ) || vgui::input()->IsKeyDown( KEY_RALT ) ) ? true : false;
  548. bool ctrlKeyDown = ( vgui::input()->IsKeyDown( KEY_LCONTROL ) || vgui::input()->IsKeyDown( KEY_RCONTROL ) ) ? true : false;
  549. // Alt-Tilde toggles Japanese IME on/off!!!
  550. if ( ( len > 0 ) && hitTilde )
  551. {
  552. // Strip the last character (tilde)
  553. m_szPartialText[ len - 1 ] = L'\0';
  554. if( !altKeyDown && !ctrlKeyDown )
  555. {
  556. m_pEntry->SetText( "" );
  557. // close the console
  558. PostMessage( this, new KeyValues( "Close" ) );
  559. PostActionSignal( new KeyValues( "ClosedByHittingTilde" ) );
  560. }
  561. else
  562. {
  563. m_pEntry->SetText( m_szPartialText );
  564. }
  565. return;
  566. }
  567. // clear auto-complete state since the user has typed
  568. m_bAutoCompleteMode = false;
  569. RebuildCompletionList(m_szPartialText);
  570. // build the menu
  571. if ( m_CompletionList.Count() < 1 )
  572. {
  573. m_pCompletionList->SetVisible(false);
  574. }
  575. else
  576. {
  577. m_pCompletionList->SetVisible(true);
  578. m_pCompletionList->DeleteAllItems();
  579. const int MAX_MENU_ITEMS = 10;
  580. // add the first ten items to the list
  581. for (int i = 0; i < m_CompletionList.Count() && i < MAX_MENU_ITEMS; i++)
  582. {
  583. char text[256];
  584. text[0] = 0;
  585. if (i == MAX_MENU_ITEMS - 1)
  586. {
  587. Q_strncpy(text, "...", sizeof( text ) );
  588. }
  589. else
  590. {
  591. Assert( m_CompletionList[i] );
  592. Q_strncpy(text, m_CompletionList[i]->GetItemText(), sizeof( text ) );
  593. }
  594. KeyValues *kv = new KeyValues("CompletionCommand");
  595. kv->SetString("command",text);
  596. m_pCompletionList->AddMenuItem(text, kv, this);
  597. }
  598. UpdateCompletionListPosition();
  599. }
  600. RequestFocus();
  601. m_pEntry->RequestFocus();
  602. }
  603. //-----------------------------------------------------------------------------
  604. // Purpose: generic vgui command handler
  605. //-----------------------------------------------------------------------------
  606. void CConsolePanel::OnCommand(const char *command)
  607. {
  608. if ( !Q_stricmp( command, "Submit" ) )
  609. {
  610. // submit the entry as a console commmand
  611. char szCommand[256];
  612. m_pEntry->GetText(szCommand, sizeof(szCommand));
  613. PostActionSignal( new KeyValues( "CommandSubmitted", "command", szCommand ) );
  614. // add to the history
  615. Print("] ");
  616. Print(szCommand);
  617. Print("\n");
  618. // clear the field
  619. m_pEntry->SetText("");
  620. // clear the completion state
  621. OnTextChanged(m_pEntry);
  622. // always go the end of the buffer when the user has typed something
  623. m_pHistory->GotoTextEnd();
  624. // Add the command to the history
  625. char *extra = strchr(szCommand, ' ');
  626. if ( extra )
  627. {
  628. *extra = '\0';
  629. extra++;
  630. }
  631. if ( Q_strlen( szCommand ) > 0 )
  632. {
  633. AddToHistory( szCommand, extra );
  634. }
  635. m_pCompletionList->SetVisible(false);
  636. }
  637. else
  638. {
  639. BaseClass::OnCommand(command);
  640. }
  641. }
  642. //-----------------------------------------------------------------------------
  643. // Focus related methods
  644. //-----------------------------------------------------------------------------
  645. bool CConsolePanel::TextEntryHasFocus() const
  646. {
  647. return ( input()->GetFocus() == m_pEntry->GetVPanel() );
  648. }
  649. void CConsolePanel::TextEntryRequestFocus()
  650. {
  651. m_pEntry->RequestFocus();
  652. }
  653. //-----------------------------------------------------------------------------
  654. // Purpose: swallows tab key pressed
  655. //-----------------------------------------------------------------------------
  656. void CConsolePanel::OnKeyCodeTyped(KeyCode code)
  657. {
  658. BaseClass::OnKeyCodeTyped(code);
  659. // check for processing
  660. if ( TextEntryHasFocus() )
  661. {
  662. if (code == KEY_TAB)
  663. {
  664. bool reverse = false;
  665. if (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT))
  666. {
  667. reverse = true;
  668. }
  669. // attempt auto-completion
  670. OnAutoComplete(reverse);
  671. m_pEntry->RequestFocus();
  672. }
  673. else if (code == KEY_DOWN)
  674. {
  675. OnAutoComplete(false);
  676. // UpdateCompletionListPosition();
  677. // m_pCompletionList->SetVisible(true);
  678. m_pEntry->RequestFocus();
  679. }
  680. else if (code == KEY_UP)
  681. {
  682. OnAutoComplete(true);
  683. m_pEntry->RequestFocus();
  684. }
  685. }
  686. }
  687. //-----------------------------------------------------------------------------
  688. // Purpose: lays out controls
  689. //-----------------------------------------------------------------------------
  690. void CConsolePanel::PerformLayout()
  691. {
  692. BaseClass::PerformLayout();
  693. // setup tab ordering
  694. GetFocusNavGroup().SetDefaultButton(m_pSubmit);
  695. IScheme *pScheme = scheme()->GetIScheme( GetScheme() );
  696. m_pEntry->SetBorder(pScheme->GetBorder("DepressedButtonBorder"));
  697. m_pHistory->SetBorder(pScheme->GetBorder("DepressedButtonBorder"));
  698. // layout controls
  699. int wide, tall;
  700. GetSize(wide, tall);
  701. if ( !m_bStatusVersion )
  702. {
  703. const int inset = 8;
  704. const int entryHeight = 24;
  705. const int topHeight = 4;
  706. const int entryInset = 4;
  707. const int submitWide = 64;
  708. const int submitInset = 7; // x inset to pull the submit button away from the frame grab
  709. m_pHistory->SetPos(inset, inset + topHeight);
  710. m_pHistory->SetSize(wide - (inset * 2), tall - (entryInset * 2 + inset * 2 + topHeight + entryHeight));
  711. m_pHistory->InvalidateLayout();
  712. int nSubmitXPos = wide - ( inset + submitWide + submitInset );
  713. m_pSubmit->SetPos( nSubmitXPos, tall - (entryInset * 2 + entryHeight));
  714. m_pSubmit->SetSize( submitWide, entryHeight);
  715. m_pEntry->SetPos( inset, tall - (entryInset * 2 + entryHeight) );
  716. m_pEntry->SetSize( nSubmitXPos - entryInset - 2 * inset, entryHeight);
  717. }
  718. else
  719. {
  720. const int inset = 2;
  721. int entryWidth = wide / 2;
  722. if ( wide > 400 )
  723. {
  724. entryWidth = 200;
  725. }
  726. m_pEntry->SetBounds( inset, inset, entryWidth, tall - 2 * inset );
  727. m_pHistory->SetBounds( inset + entryWidth + inset, inset, ( wide - entryWidth ) - inset, tall - 2 * inset );
  728. }
  729. UpdateCompletionListPosition();
  730. }
  731. //-----------------------------------------------------------------------------
  732. // Purpose: Sets the position of the completion list popup
  733. //-----------------------------------------------------------------------------
  734. void CConsolePanel::UpdateCompletionListPosition()
  735. {
  736. int ex, ey;
  737. m_pEntry->GetPos(ex, ey);
  738. if ( !m_bStatusVersion )
  739. {
  740. // Position below text entry
  741. ey += m_pEntry->GetTall();
  742. }
  743. else
  744. {
  745. // Position above text entry
  746. int menuwide, menutall;
  747. m_pCompletionList->GetSize( menuwide, menutall );
  748. ey -= ( menutall + 4 );
  749. }
  750. LocalToScreen( ex, ey );
  751. m_pCompletionList->SetPos( ex, ey );
  752. if ( m_pCompletionList->IsVisible() )
  753. {
  754. m_pEntry->RequestFocus();
  755. MoveToFront();
  756. m_pCompletionList->MoveToFront();
  757. }
  758. }
  759. //-----------------------------------------------------------------------------
  760. // Purpose: Closes the completion list
  761. //-----------------------------------------------------------------------------
  762. void CConsolePanel::CloseCompletionList()
  763. {
  764. m_pCompletionList->SetVisible(false);
  765. }
  766. //-----------------------------------------------------------------------------
  767. // Purpose: sets up colors
  768. //-----------------------------------------------------------------------------
  769. void CConsolePanel::ApplySchemeSettings(IScheme *pScheme)
  770. {
  771. BaseClass::ApplySchemeSettings(pScheme);
  772. m_PrintColor = GetSchemeColor("Console.TextColor", pScheme);
  773. m_DPrintColor = GetSchemeColor("Console.DevTextColor", pScheme);
  774. m_pHistory->SetFont( pScheme->GetFont( "ConsoleText", IsProportional() ) );
  775. m_pCompletionList->SetFont( pScheme->GetFont( "DefaultSmall", IsProportional() ) );
  776. InvalidateLayout();
  777. }
  778. //-----------------------------------------------------------------------------
  779. // Purpose: Handles autocompletion menu input
  780. //-----------------------------------------------------------------------------
  781. void CConsolePanel::OnMenuItemSelected(const char *command)
  782. {
  783. if ( strstr( command, "..." ) ) // stop the menu going away if you click on ...
  784. {
  785. m_pCompletionList->SetVisible( true );
  786. }
  787. else
  788. {
  789. m_pEntry->SetText(command);
  790. m_pEntry->GotoTextEnd();
  791. m_pEntry->InsertChar(' ');
  792. m_pEntry->GotoTextEnd();
  793. }
  794. }
  795. void CConsolePanel::Hide()
  796. {
  797. OnClose();
  798. m_iNextCompletion = 0;
  799. RebuildCompletionList("");
  800. }
  801. void CConsolePanel::AddToHistory( const char *commandText, const char *extraText )
  802. {
  803. // Newest at end, oldest at head
  804. while ( m_CommandHistory.Count() >= MAX_HISTORY_ITEMS )
  805. {
  806. // Remove from head until size is reasonable
  807. m_CommandHistory.Remove( 0 );
  808. }
  809. // strip the space off the end of the command before adding it to the history
  810. // If this code gets cleaned up then we should remove the redundant calls to strlen,
  811. // the check for whether _alloca succeeded, and should use V_strncpy instead of the
  812. // error prone memset/strncpy sequence.
  813. char *command = static_cast<char *>( _alloca( (strlen( commandText ) + 1 ) * sizeof( char ) ));
  814. if ( command )
  815. {
  816. memset( command, 0x0, strlen( commandText ) + 1 );
  817. strncpy( command, commandText, strlen( commandText ));
  818. // There is no actual bug here, just some sloppy/odd code.
  819. // src\vgui2\vgui_controls\consoledialog.cpp(974): warning C6053: The prior call to 'strncpy' might not zero-terminate string 'command'
  820. ANALYZE_SUPPRESS( 6053 )
  821. if ( command[ strlen( command ) -1 ] == ' ' )
  822. {
  823. command[ strlen( command ) -1 ] = '\0';
  824. }
  825. }
  826. // strip the quotes off the extra text
  827. char *extra = NULL;
  828. if ( extraText )
  829. {
  830. extra = static_cast<char *>( malloc( (strlen( extraText ) + 1 ) * sizeof( char ) ));
  831. if ( extra )
  832. {
  833. memset( extra, 0x0, strlen( extraText ) + 1 );
  834. strncpy( extra, extraText, strlen( extraText )); // +1 to dodge the starting quote
  835. // Strip trailing spaces
  836. int i = strlen( extra ) - 1;
  837. while ( i >= 0 && // Check I before referencing i == -1 into the extra array!
  838. extra[ i ] == ' ' )
  839. {
  840. extra[ i ] = '\0';
  841. i--;
  842. }
  843. }
  844. }
  845. // If it's already there, then remove since we'll add it to the end instead
  846. CHistoryItem *item = NULL;
  847. for ( int i = m_CommandHistory.Count() - 1; i >= 0; i-- )
  848. {
  849. item = &m_CommandHistory[ i ];
  850. if ( !item )
  851. continue;
  852. if ( stricmp( item->GetText(), command ) )
  853. continue;
  854. if ( extra || item->GetExtra() )
  855. {
  856. if ( !extra || !item->GetExtra() )
  857. continue;
  858. // stricmp so two commands with the same starting text get added
  859. if ( stricmp( item->GetExtra(), extra ) )
  860. continue;
  861. }
  862. m_CommandHistory.Remove( i );
  863. }
  864. item = &m_CommandHistory[ m_CommandHistory.AddToTail() ];
  865. Assert( item );
  866. item->SetText( command, extra );
  867. m_iNextCompletion = 0;
  868. RebuildCompletionList( m_szPartialText );
  869. free( extra );
  870. }
  871. void CConsolePanel::GetConsoleText( char *pchText, size_t bufSize ) const
  872. {
  873. wchar_t *temp = new wchar_t[ bufSize ];
  874. m_pHistory->GetText( 0, temp, bufSize * sizeof( wchar_t ) );
  875. g_pVGuiLocalize->ConvertUnicodeToANSI( temp, pchText, bufSize );
  876. delete[] temp;
  877. }
  878. //-----------------------------------------------------------------------------
  879. // Purpose: writes out console to disk
  880. //-----------------------------------------------------------------------------
  881. void CConsolePanel::DumpConsoleTextToFile()
  882. {
  883. const int CONDUMP_FILES_MAX_NUM = 1000;
  884. FileHandle_t handle;
  885. bool found = false;
  886. char szfile[ 512 ];
  887. // we don't want to overwrite other condump.txt files
  888. for ( int i = 0 ; i < CONDUMP_FILES_MAX_NUM ; ++i )
  889. {
  890. _snprintf( szfile, sizeof(szfile), "condump%03d.txt", i );
  891. if ( !g_pFullFileSystem->FileExists(szfile) )
  892. {
  893. found = true;
  894. break;
  895. }
  896. }
  897. if ( !found )
  898. {
  899. Print( "Can't condump! Too many existing condump output files in the gamedir!\n" );
  900. return;
  901. }
  902. handle = g_pFullFileSystem->Open( szfile, "wb" );
  903. if ( handle != FILESYSTEM_INVALID_HANDLE )
  904. {
  905. int pos = 0;
  906. while (1)
  907. {
  908. wchar_t buf[512];
  909. m_pHistory->GetText(pos, buf, sizeof(buf));
  910. pos += sizeof(buf) / sizeof(wchar_t);
  911. // don't continue if none left
  912. if (buf[0] == 0)
  913. break;
  914. // convert to ansi
  915. char ansi[512];
  916. g_pVGuiLocalize->ConvertUnicodeToANSI(buf, ansi, sizeof(ansi));
  917. // write to disk
  918. int len = strlen(ansi);
  919. for (int i = 0; i < len; i++)
  920. {
  921. // preceed newlines with a return
  922. if (ansi[i] == '\n')
  923. {
  924. char ret = '\r';
  925. g_pFullFileSystem->Write( &ret, 1, handle );
  926. }
  927. g_pFullFileSystem->Write( ansi + i, 1, handle );
  928. }
  929. }
  930. g_pFullFileSystem->Close( handle );
  931. Print( "console dumped to " );
  932. Print( szfile );
  933. Print( "\n" );
  934. }
  935. else
  936. {
  937. Print( "Unable to condump to " );
  938. Print( szfile );
  939. Print( "\n" );
  940. }
  941. }
  942. //-----------------------------------------------------------------------------
  943. //
  944. // Console dialog starts here
  945. //
  946. //-----------------------------------------------------------------------------
  947. CConsoleDialog::CConsoleDialog( vgui::Panel *pParent, const char *pName, bool bStatusVersion ) :
  948. BaseClass( pParent, pName )
  949. {
  950. // initialize dialog
  951. SetVisible( false );
  952. SetTitle( "#Console_Title", true );
  953. m_pConsolePanel = new CConsolePanel( this, "ConsolePage", bStatusVersion );
  954. m_pConsolePanel->AddActionSignalTarget( this );
  955. }
  956. void CConsoleDialog::OnScreenSizeChanged( int iOldWide, int iOldTall )
  957. {
  958. BaseClass::OnScreenSizeChanged( iOldWide, iOldTall );
  959. int sx, sy;
  960. surface()->GetScreenSize( sx, sy );
  961. int w, h;
  962. GetSize( w, h );
  963. if ( w > sx || h > sy )
  964. {
  965. if ( w > sx )
  966. {
  967. w = sx;
  968. }
  969. if ( h > sy )
  970. {
  971. h = sy;
  972. }
  973. // Try and lower the size to match the screen bounds
  974. SetSize( w, h );
  975. }
  976. }
  977. //-----------------------------------------------------------------------------
  978. // Purpose: brings dialog to the fore
  979. //-----------------------------------------------------------------------------
  980. void CConsoleDialog::PerformLayout()
  981. {
  982. BaseClass::PerformLayout();
  983. int x, y, w, h;
  984. GetClientArea( x, y, w, h );
  985. m_pConsolePanel->SetBounds( x, y, w, h );
  986. }
  987. //-----------------------------------------------------------------------------
  988. // Purpose: brings dialog to the fore
  989. //-----------------------------------------------------------------------------
  990. void CConsoleDialog::Activate()
  991. {
  992. BaseClass::Activate();
  993. m_pConsolePanel->m_pEntry->RequestFocus();
  994. }
  995. //-----------------------------------------------------------------------------
  996. // Hides the dialog
  997. //-----------------------------------------------------------------------------
  998. void CConsoleDialog::Hide()
  999. {
  1000. OnClose();
  1001. m_pConsolePanel->Hide();
  1002. }
  1003. //-----------------------------------------------------------------------------
  1004. // Close just hides the dialog
  1005. //-----------------------------------------------------------------------------
  1006. void CConsoleDialog::Close()
  1007. {
  1008. Hide();
  1009. }
  1010. //-----------------------------------------------------------------------------
  1011. // Submits commands
  1012. //-----------------------------------------------------------------------------
  1013. void CConsoleDialog::OnCommandSubmitted( const char *pCommand )
  1014. {
  1015. PostActionSignal( new KeyValues( "CommandSubmitted", "command", pCommand ) );
  1016. }
  1017. //-----------------------------------------------------------------------------
  1018. // Chain to the page
  1019. //-----------------------------------------------------------------------------
  1020. void CConsoleDialog::Print( const char *pMessage )
  1021. {
  1022. m_pConsolePanel->Print( pMessage );
  1023. }
  1024. void CConsoleDialog::DPrint( const char *pMessage )
  1025. {
  1026. m_pConsolePanel->DPrint( pMessage );
  1027. }
  1028. void CConsoleDialog::ColorPrint( const Color& clr, const char *msg )
  1029. {
  1030. m_pConsolePanel->ColorPrint( clr, msg );
  1031. }
  1032. void CConsoleDialog::Clear()
  1033. {
  1034. m_pConsolePanel->Clear( );
  1035. }
  1036. void CConsoleDialog::DumpConsoleTextToFile()
  1037. {
  1038. m_pConsolePanel->DumpConsoleTextToFile( );
  1039. }
  1040. void CConsoleDialog::OnKeyCodePressed( vgui::KeyCode code )
  1041. {
  1042. if ( code == KEY_XBUTTON_B )
  1043. {
  1044. Hide();
  1045. }
  1046. else
  1047. {
  1048. BaseClass::OnKeyCodePressed(code);
  1049. }
  1050. }