Counter Strike : Global Offensive Source Code
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.

2656 lines
74 KiB

  1. //========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "vgui_controls/pch_vgui_controls.h"
  8. #include "vgui/ILocalize.h"
  9. #include <vstdlib/vstrtools.h>
  10. #ifdef _PS3
  11. #include <wctype.h>
  12. #endif
  13. // memdbgon must be the last include file in a .cpp file
  14. #include "tier0/memdbgon.h"
  15. enum
  16. {
  17. MAX_BUFFER_SIZE = 999999, // maximum size of text buffer
  18. DRAW_OFFSET_X = 3,
  19. DRAW_OFFSET_Y = 1,
  20. };
  21. using namespace vgui;
  22. #ifndef max
  23. #define max(a,b) (((a) > (b)) ? (a) : (b))
  24. #endif
  25. namespace vgui
  26. {
  27. // #define DRAW_CLICK_PANELS
  28. //-----------------------------------------------------------------------------
  29. // Purpose: Panel used for clickable URL's
  30. //-----------------------------------------------------------------------------
  31. class ClickPanel : public Panel
  32. {
  33. DECLARE_CLASS_SIMPLE( ClickPanel, Panel );
  34. public:
  35. ClickPanel(Panel *parent)
  36. {
  37. _textIndex = 0;
  38. SetParent(parent);
  39. AddActionSignalTarget(parent);
  40. SetCursor(dc_hand);
  41. SetPaintBackgroundEnabled(false);
  42. SetPaintEnabled(false);
  43. // SetPaintAppearanceEnabled(false);
  44. #if defined( DRAW_CLICK_PANELS )
  45. SetPaintEnabled(true);
  46. #endif
  47. }
  48. void SetTextIndex(int index)
  49. {
  50. _textIndex = index;
  51. }
  52. #if defined( DRAW_CLICK_PANELS )
  53. virtual void Paint()
  54. {
  55. surface()->DrawSetColor( Color( 255, 0, 0, 255 ) );
  56. surface()->DrawOutlinedRect( 0, 0, GetWide(), GetTall() );
  57. }
  58. #endif
  59. int GetTextIndex()
  60. {
  61. return _textIndex;
  62. }
  63. void OnMousePressed(MouseCode code)
  64. {
  65. if (code == MOUSE_LEFT)
  66. {
  67. PostActionSignal(new KeyValues("ClickPanel", "index", _textIndex));
  68. }
  69. }
  70. private:
  71. int _textIndex;
  72. };
  73. //-----------------------------------------------------------------------------
  74. // Purpose: Panel used only to draw the interior border region
  75. //-----------------------------------------------------------------------------
  76. class RichTextInterior : public Panel
  77. {
  78. DECLARE_CLASS_SIMPLE( RichTextInterior, Panel );
  79. public:
  80. RichTextInterior( RichText *pParent, const char *pchName ) : BaseClass( pParent, pchName )
  81. {
  82. SetKeyBoardInputEnabled( false );
  83. SetMouseInputEnabled( false );
  84. SetPaintBackgroundEnabled( false );
  85. SetPaintEnabled( false );
  86. m_pRichText = pParent;
  87. }
  88. /* virtual IAppearance *GetAppearance()
  89. {
  90. if ( m_pRichText->IsScrollbarVisible() )
  91. return m_pAppearanceScrollbar;
  92. return BaseClass::GetAppearance();
  93. }*/
  94. virtual void ApplySchemeSettings( IScheme *pScheme )
  95. {
  96. BaseClass::ApplySchemeSettings( pScheme );
  97. // m_pAppearanceScrollbar = FindSchemeAppearance( pScheme, "scrollbar_visible" );
  98. }
  99. private:
  100. RichText *m_pRichText;
  101. // IAppearance *m_pAppearanceScrollbar;
  102. };
  103. }; // namespace vgui
  104. DECLARE_BUILD_FACTORY( RichText );
  105. //-----------------------------------------------------------------------------
  106. // Purpose: Constructor
  107. //-----------------------------------------------------------------------------
  108. RichText::RichText(Panel *parent, const char *panelName) : BaseClass(parent, panelName)
  109. {
  110. m_bAllTextAlphaIsZero = false;
  111. _font = INVALID_FONT;
  112. m_hFontUnderline = INVALID_FONT;
  113. m_bRecalcLineBreaks = true;
  114. m_pszInitialText = NULL;
  115. _cursorPos = 0;
  116. _mouseSelection = false;
  117. _mouseDragSelection = false;
  118. _vertScrollBar = new ScrollBar(this, "ScrollBar", true);
  119. _vertScrollBar->AddActionSignalTarget(this);
  120. _recalcSavedRenderState = true;
  121. _maxCharCount = (64 * 1024);
  122. AddActionSignalTarget(this);
  123. m_pInterior = new RichTextInterior( this, NULL );
  124. //a -1 for _select[0] means that the selection is empty
  125. _select[0] = -1;
  126. _select[1] = -1;
  127. m_pEditMenu = NULL;
  128. SetCursor(dc_ibeam);
  129. //position the cursor so it is at the end of the text
  130. GotoTextEnd();
  131. // set default foreground color to black
  132. _defaultTextColor = Color(0, 0, 0, 0);
  133. // initialize the line break array
  134. InvalidateLineBreakStream();
  135. if ( IsProportional() )
  136. {
  137. int width, height;
  138. int sw,sh;
  139. surface()->GetProportionalBase( width, height );
  140. surface()->GetScreenSize(sw, sh);
  141. _drawOffsetX = static_cast<int>( static_cast<float>( DRAW_OFFSET_X )*( static_cast<float>( sw )/ static_cast<float>( width )));
  142. _drawOffsetY = static_cast<int>( static_cast<float>( DRAW_OFFSET_Y )*( static_cast<float>( sw )/ static_cast<float>( width )));
  143. }
  144. else
  145. {
  146. _drawOffsetX = DRAW_OFFSET_X;
  147. _drawOffsetY = DRAW_OFFSET_Y;
  148. }
  149. // add a basic format string
  150. TFormatStream stream;
  151. stream.color = _defaultTextColor;
  152. stream.fade.flFadeStartTime = 0.0f;
  153. stream.fade.flFadeLength = -1.0f;
  154. stream.pixelsIndent = 0;
  155. stream.textStreamIndex = 0;
  156. stream.textClickable = false;
  157. m_FormatStream.AddToTail(stream);
  158. m_bResetFades = false;
  159. m_bInteractive = true;
  160. m_bUnusedScrollbarInvis = false;
  161. }
  162. //-----------------------------------------------------------------------------
  163. // Purpose: Destructor
  164. //-----------------------------------------------------------------------------
  165. RichText::~RichText()
  166. {
  167. delete [] m_pszInitialText;
  168. delete m_pEditMenu;
  169. }
  170. //-----------------------------------------------------------------------------
  171. // Purpose:
  172. //-----------------------------------------------------------------------------
  173. void RichText::SetDrawOffsets( int ofsx, int ofsy )
  174. {
  175. _drawOffsetX = ofsx;
  176. _drawOffsetY = ofsy;
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Purpose: sets it as drawing text only - used for embedded RichText control into other text drawing situations
  180. //-----------------------------------------------------------------------------
  181. void RichText::SetDrawTextOnly()
  182. {
  183. SetDrawOffsets( 0, 0 );
  184. SetPaintBackgroundEnabled( false );
  185. // SetPaintAppearanceEnabled( false );
  186. SetPostChildPaintEnabled( false );
  187. m_pInterior->SetVisible( false );
  188. SetVerticalScrollbar( false );
  189. }
  190. //-----------------------------------------------------------------------------
  191. // Purpose: configures colors
  192. //-----------------------------------------------------------------------------
  193. void RichText::ApplySchemeSettings(IScheme *pScheme)
  194. {
  195. BaseClass::ApplySchemeSettings(pScheme);
  196. _font = pScheme->GetFont("Default", IsProportional() );
  197. m_hFontUnderline = pScheme->GetFont("DefaultUnderline", IsProportional() );
  198. SetFgColor(GetSchemeColor("RichText.TextColor", pScheme));
  199. SetBgColor(GetSchemeColor("RichText.BgColor", pScheme));
  200. _selectionTextColor = GetSchemeColor("RichText.SelectedTextColor", GetFgColor(), pScheme);
  201. _selectionColor = GetSchemeColor("RichText.SelectedBgColor", pScheme);
  202. if ( Q_strlen( pScheme->GetResourceString( "RichText.InsetX" ) ) )
  203. {
  204. SetDrawOffsets( atoi( pScheme->GetResourceString( "RichText.InsetX" ) ), atoi( pScheme->GetResourceString( "RichText.InsetY" ) ) );
  205. }
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose: if the default format color isn't set then set it
  209. //-----------------------------------------------------------------------------
  210. void RichText::SetFgColor( Color color )
  211. {
  212. // Replace default format color if
  213. // the stream is empty and the color is the default ( or the previous FgColor )
  214. if ( m_FormatStream.Count() == 1 &&
  215. ( m_FormatStream[0].color == _defaultTextColor || m_FormatStream[0].color == GetFgColor() ) )
  216. {
  217. m_FormatStream[0].color = color;
  218. }
  219. BaseClass::SetFgColor( color );
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose: Sends a message if the data has changed
  223. // Turns off any selected text in the window if we are not using the edit menu
  224. //-----------------------------------------------------------------------------
  225. void RichText::OnKillFocus()
  226. {
  227. // check if we clicked the right mouse button or if it is down
  228. bool mouseRightClicked = input()->WasMousePressed(MOUSE_RIGHT);
  229. bool mouseRightUp = input()->WasMouseReleased(MOUSE_RIGHT);
  230. bool mouseRightDown = input()->IsMouseDown(MOUSE_RIGHT);
  231. if (mouseRightClicked || mouseRightDown || mouseRightUp )
  232. {
  233. // get the start and ends of the selection area
  234. int start, end;
  235. if (GetSelectedRange(start, end)) // we have selected text
  236. {
  237. // see if we clicked in the selection area
  238. int startX, startY;
  239. CursorToPixelSpace(start, startX, startY);
  240. int endX, endY;
  241. CursorToPixelSpace(end, endX, endY);
  242. int cursorX, cursorY;
  243. input()->GetCursorPos(cursorX, cursorY);
  244. ScreenToLocal(cursorX, cursorY);
  245. // check the area vertically
  246. // we need to handle the horizontal edge cases eventually
  247. int fontTall = surface()->GetFontTall(_font);
  248. endY = endY + fontTall;
  249. if ((startY < cursorY) && (endY > cursorY))
  250. {
  251. // if we clicked in the selection area, leave the text highlighted
  252. return;
  253. }
  254. }
  255. }
  256. // clear any selection
  257. SelectNone();
  258. // chain
  259. BaseClass::OnKillFocus();
  260. }
  261. //-----------------------------------------------------------------------------
  262. // Purpose: Wipe line breaks after the size of a panel has been changed
  263. //-----------------------------------------------------------------------------
  264. void RichText::OnSizeChanged( int wide, int tall )
  265. {
  266. BaseClass::OnSizeChanged( wide, tall );
  267. // blow away the line breaks list
  268. _invalidateVerticalScrollbarSlider = true;
  269. InvalidateLineBreakStream();
  270. InvalidateLayout();
  271. if ( _vertScrollBar->IsVisible() )
  272. {
  273. _vertScrollBar->MakeReadyForUse();
  274. m_pInterior->SetBounds( 0, 0, wide - _vertScrollBar->GetWide(), tall );
  275. }
  276. else
  277. {
  278. m_pInterior->SetBounds( 0, 0, wide, tall );
  279. }
  280. }
  281. const wchar_t *RichText::ResolveLocalizedTextAndVariables( char const *pchLookup, wchar_t *outbuf, size_t outbufsizeinbytes )
  282. {
  283. if ( pchLookup[ 0 ] == '#' )
  284. {
  285. // try lookup in localization tables
  286. StringIndex_t index = g_pVGuiLocalize->FindIndex( pchLookup + 1 );
  287. if ( index == INVALID_STRING_INDEX )
  288. {
  289. /* // if it's not found, maybe it's a special expanded variable - look for an expansion
  290. char rgchT[MAX_PATH];
  291. // get the variables
  292. KeyValues *variables = GetDialogVariables_R();
  293. if ( variables )
  294. {
  295. // see if any are any special vars to put in
  296. for ( KeyValues *pkv = variables->GetFirstSubKey(); pkv != NULL; pkv = pkv->GetNextKey() )
  297. {
  298. if ( !Q_strncmp( pkv->GetName(), "$", 1 ) )
  299. {
  300. // make a new lookup, with this key appended
  301. Q_snprintf( rgchT, sizeof( rgchT ), "%s%s=%s", pchLookup, pkv->GetName(), pkv->GetString() );
  302. index = localize()->FindIndex( rgchT );
  303. break;
  304. }
  305. }
  306. }
  307. */
  308. }
  309. // see if we have a valid string
  310. if ( index != INVALID_STRING_INDEX )
  311. {
  312. wchar_t *format = g_pVGuiLocalize->GetValueByIndex( index );
  313. Assert( format );
  314. if ( format )
  315. {
  316. /*// Try and substitute variables if any
  317. KeyValues *variables = GetDialogVariables_R();
  318. if ( variables )
  319. {
  320. localize()->ConstructString( outbuf, outbufsizeinbytes, index, variables );
  321. return outbuf;
  322. }*/
  323. }
  324. V_wcsncpy( outbuf, format, outbufsizeinbytes );
  325. return outbuf;
  326. }
  327. }
  328. Q_UTF8ToUnicode( pchLookup, outbuf, outbufsizeinbytes );
  329. return outbuf;
  330. }
  331. //-----------------------------------------------------------------------------
  332. // Purpose: Set the text array
  333. // Using this function will cause all lineBreaks to be discarded.
  334. // This is because this fxn replaces the contents of the text buffer.
  335. // For modifying large buffers use insert functions.
  336. //-----------------------------------------------------------------------------
  337. void RichText::SetText(const char *text)
  338. {
  339. wchar_t unicode[1024];
  340. if ( text && *text )
  341. {
  342. if ( text[0] == '#' )
  343. {
  344. ResolveLocalizedTextAndVariables( text, unicode, sizeof( unicode ) );
  345. SetText( unicode );
  346. return;
  347. }
  348. // convert to unicode
  349. Q_UTF8ToUnicode(text, unicode, sizeof(unicode));
  350. SetText(unicode);
  351. }
  352. else
  353. {
  354. SetText( (const wchar_t *)NULL );
  355. }
  356. }
  357. //-----------------------------------------------------------------------------
  358. // Purpose:
  359. //-----------------------------------------------------------------------------
  360. void RichText::SetText(const wchar_t *text)
  361. {
  362. // reset the formatting stream
  363. m_FormatStream.RemoveAll();
  364. TFormatStream stream;
  365. stream.color = GetFgColor();
  366. stream.fade.flFadeLength = -1.0f;
  367. stream.fade.flFadeStartTime = 0.0f;
  368. stream.pixelsIndent = 0;
  369. stream.textStreamIndex = 0;
  370. stream.textClickable = false;
  371. m_FormatStream.AddToTail(stream);
  372. // set the new text stream
  373. m_TextStream.RemoveAll();
  374. if ( text && *text )
  375. {
  376. int textLen = wcslen(text) + 1;
  377. m_TextStream.EnsureCapacity(textLen);
  378. for(int i = 0; i < textLen; i++)
  379. {
  380. m_TextStream.AddToTail(text[i]);
  381. }
  382. }
  383. GotoTextStart();
  384. SelectNone();
  385. // blow away the line breaks list
  386. InvalidateLineBreakStream();
  387. InvalidateLayout();
  388. }
  389. //-----------------------------------------------------------------------------
  390. // Purpose: Given cursor's position in the text buffer, convert it to
  391. // the local window's x and y pixel coordinates
  392. // Input: cursorPos: cursor index
  393. // Output: cx, cy, the corresponding coords in the local window
  394. //-----------------------------------------------------------------------------
  395. void RichText::CursorToPixelSpace(int cursorPos, int &cx, int &cy)
  396. {
  397. int yStart = _drawOffsetY;
  398. int x = _drawOffsetX, y = yStart;
  399. _pixelsIndent = 0;
  400. int lineBreakIndexIndex = 0;
  401. for (int i = GetStartDrawIndex(lineBreakIndexIndex); i < m_TextStream.Count(); i++)
  402. {
  403. wchar_t ch = m_TextStream[i];
  404. // if we've found the position, break
  405. if (cursorPos == i)
  406. {
  407. // if we've passed a line break go to that
  408. if (m_LineBreaks[lineBreakIndexIndex] == i)
  409. {
  410. // add another line
  411. AddAnotherLine(x, y);
  412. lineBreakIndexIndex++;
  413. }
  414. break;
  415. }
  416. // if we've passed a line break go to that
  417. if (m_LineBreaks[lineBreakIndexIndex] == i)
  418. {
  419. // add another line
  420. AddAnotherLine(x, y);
  421. lineBreakIndexIndex++;
  422. }
  423. // add to the current position
  424. x += surface()->GetCharacterWidth(_font, ch);
  425. }
  426. cx = x;
  427. cy = y;
  428. }
  429. //-----------------------------------------------------------------------------
  430. // Purpose: Converts local pixel coordinates to an index in the text buffer
  431. //-----------------------------------------------------------------------------
  432. int RichText::PixelToCursorSpace(int cx, int cy)
  433. {
  434. int fontTall = surface()->GetFontTall(_font);
  435. // where to start reading
  436. int yStart = _drawOffsetY;
  437. int x = _drawOffsetX, y = yStart;
  438. _pixelsIndent = 0;
  439. int lineBreakIndexIndex = 0;
  440. int startIndex = GetStartDrawIndex(lineBreakIndexIndex);
  441. if (_recalcSavedRenderState)
  442. {
  443. RecalculateDefaultState(startIndex);
  444. }
  445. _pixelsIndent = m_CachedRenderState.pixelsIndent;
  446. _currentTextClickable = m_CachedRenderState.textClickable;
  447. TRenderState renderState = m_CachedRenderState;
  448. bool onRightLine = false;
  449. int i;
  450. for (i = startIndex; i < m_TextStream.Count(); i++)
  451. {
  452. wchar_t ch = m_TextStream[i];
  453. renderState.x = x;
  454. if ( UpdateRenderState( i, renderState ) )
  455. {
  456. x = renderState.x;
  457. }
  458. // if we are on the right line but off the end of if put the cursor at the end of the line
  459. if (m_LineBreaks[lineBreakIndexIndex] == i)
  460. {
  461. // add another line
  462. AddAnotherLine(x, y);
  463. lineBreakIndexIndex++;
  464. if (onRightLine)
  465. break;
  466. }
  467. // check to see if we're on the right line
  468. if (cy < yStart)
  469. {
  470. // cursor is above panel
  471. onRightLine = true;
  472. }
  473. else if (cy >= y && (cy < (y + fontTall + _drawOffsetY)))
  474. {
  475. onRightLine = true;
  476. }
  477. int wide = surface()->GetCharacterWidth(_font, ch);
  478. // if we've found the position, break
  479. if (onRightLine)
  480. {
  481. if (cx > GetWide()) // off right side of window
  482. {
  483. }
  484. else if (cx < (_drawOffsetX + renderState.pixelsIndent) || cy < yStart) // off left side of window
  485. {
  486. // Msg( "PixelToCursorSpace() off left size, returning %d '%c'\n", i, m_TextStream[i] );
  487. return i; // move cursor one to left
  488. }
  489. if (cx >= x && cx < (x + wide))
  490. {
  491. // check which side of the letter they're on
  492. if (cx < (x + (wide * 0.5))) // left side
  493. {
  494. // Msg( "PixelToCursorSpace() on the left size, returning %d '%c'\n", i, m_TextStream[i] );
  495. return i;
  496. }
  497. else // right side
  498. {
  499. // Msg( "PixelToCursorSpace() on the right size, returning %d '%c'\n", i + 1, m_TextStream[i + 1] );
  500. return i + 1;
  501. }
  502. }
  503. }
  504. x += wide;
  505. }
  506. // Msg( "PixelToCursorSpace() never hit, returning %d\n", i );
  507. return i;
  508. }
  509. //-----------------------------------------------------------------------------
  510. // Purpose: Draws a string of characters in the panel
  511. // Input: iFirst - Index of the first character to draw
  512. // iLast - Index of the last character to draw
  513. // renderState - Render state to use
  514. // font- font to use
  515. // Output: returns the width of the character drawn
  516. //-----------------------------------------------------------------------------
  517. int RichText::DrawString(int iFirst, int iLast, TRenderState &renderState, HFont font)
  518. {
  519. // VPROF( "RichText::DrawString" );
  520. // Calculate the render size
  521. int fontTall = surface()->GetFontTall(font);
  522. // BUGBUG John: This won't exactly match the rendered size
  523. int charWide = 0;
  524. for ( int i = iFirst; i <= iLast; i++ )
  525. {
  526. wchar_t ch = m_TextStream[i];
  527. #if defined( POSIX ) && !defined( _PS3 )
  528. wchar_t chBefore = 0;
  529. wchar_t chAfter = 0;
  530. if ( i > 0 )
  531. chBefore = m_TextStream[i-1];
  532. if ( i < iLast )
  533. chAfter = m_TextStream[i+1];
  534. float flWide = 0.0f, flabcA = 0.0f, flabcC = 0.0f;
  535. surface()->GetKernedCharWidth(font, ch, chBefore, chAfter, flWide, flabcA, flabcC);
  536. charWide += floor( flabcA + flWide + flabcC + 0.6f );
  537. #else
  538. charWide += surface()->GetCharacterWidth(font, ch);
  539. #endif
  540. }
  541. // draw selection, if any
  542. int selection0 = -1, selection1 = -1;
  543. GetSelectedRange(selection0, selection1);
  544. if (iFirst >= selection0 && iFirst < selection1)
  545. {
  546. // draw background selection color
  547. surface()->DrawSetColor(_selectionColor);
  548. surface()->DrawFilledRect(renderState.x, renderState.y, renderState.x + charWide, renderState.y + 1 + fontTall);
  549. // reset text color
  550. surface()->DrawSetTextColor(_selectionTextColor);
  551. m_bAllTextAlphaIsZero = false;
  552. }
  553. else
  554. {
  555. surface()->DrawSetTextColor(renderState.textColor);
  556. }
  557. if ( renderState.textColor.a() != 0 )
  558. {
  559. m_bAllTextAlphaIsZero = false;
  560. surface()->DrawSetTextPos(renderState.x, renderState.y);
  561. surface()->DrawPrintText(&m_TextStream[iFirst], iLast - iFirst + 1);
  562. }
  563. return charWide;
  564. }
  565. //-----------------------------------------------------------------------------
  566. // Purpose: Finish drawing url
  567. //-----------------------------------------------------------------------------
  568. void RichText::FinishingURL(int x, int y)
  569. {
  570. // finishing URL
  571. ClickPanel *clickPanel = _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ? _clickableTextPanels[_clickableTextIndex] : NULL;
  572. if ( clickPanel )
  573. {
  574. int px, py;
  575. clickPanel->GetPos(px, py);
  576. int fontTall = surface()->GetFontTall(_font);
  577. clickPanel->SetSize( max( x - px, 6 ), y - py + fontTall );
  578. clickPanel->SetVisible(true);
  579. // if we haven't actually advanced any, step back and ignore this one
  580. // this is probably a data input problem though, need to find root cause
  581. if ( x - px <= 0 )
  582. {
  583. --_clickableTextIndex;
  584. clickPanel->SetVisible(false);
  585. }
  586. }
  587. }
  588. void RichText::CalculateFade( TRenderState &renderState )
  589. {
  590. if ( m_FormatStream.IsValidIndex( renderState.formatStreamIndex ) )
  591. {
  592. if ( m_bResetFades == false )
  593. {
  594. if ( m_FormatStream[renderState.formatStreamIndex].fade.flFadeLength != -1.0f )
  595. {
  596. float frac = ( m_FormatStream[renderState.formatStreamIndex].fade.flFadeStartTime - system()->GetCurrentTime() ) / m_FormatStream[renderState.formatStreamIndex].fade.flFadeLength;
  597. int alpha = frac * m_FormatStream[renderState.formatStreamIndex].fade.iOriginalAlpha;
  598. alpha = clamp( alpha, 0, m_FormatStream[renderState.formatStreamIndex].fade.iOriginalAlpha );
  599. renderState.textColor.SetColor( renderState.textColor.r(), renderState.textColor.g(), renderState.textColor.b(), alpha );
  600. }
  601. }
  602. }
  603. }
  604. //-----------------------------------------------------------------------------
  605. // Purpose: Draws the text in the panel
  606. //-----------------------------------------------------------------------------
  607. void RichText::Paint()
  608. {
  609. // Assume the worst
  610. m_bAllTextAlphaIsZero = true;
  611. // hide all the clickable panels until we know where they are to reside
  612. for (int j = 0; j < _clickableTextPanels.Count(); j++)
  613. {
  614. _clickableTextPanels[j]->SetVisible(false);
  615. }
  616. if ( !HasText() )
  617. return;
  618. int wide, tall;
  619. GetSize( wide, tall );
  620. int lineBreakIndexIndex = 0;
  621. int startIndex = GetStartDrawIndex(lineBreakIndexIndex);
  622. _currentTextClickable = false;
  623. _clickableTextIndex = GetClickableTextIndexStart(startIndex);
  624. // recalculate and cache the render state at the render start
  625. if (_recalcSavedRenderState)
  626. {
  627. RecalculateDefaultState(startIndex);
  628. }
  629. // copy off the cached render state
  630. TRenderState renderState = m_CachedRenderState;
  631. _pixelsIndent = m_CachedRenderState.pixelsIndent;
  632. _currentTextClickable = m_CachedRenderState.textClickable;
  633. renderState.textClickable = _currentTextClickable;
  634. renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color;
  635. CalculateFade( renderState );
  636. renderState.formatStreamIndex++;
  637. if ( _currentTextClickable )
  638. {
  639. _clickableTextIndex = startIndex;
  640. }
  641. // where to start drawing
  642. renderState.x = _drawOffsetX + _pixelsIndent;
  643. renderState.y = _drawOffsetY;
  644. // draw the text
  645. int selection0 = -1, selection1 = -1;
  646. GetSelectedRange(selection0, selection1);
  647. surface()->DrawSetTextFont(_font);
  648. for (int i = startIndex; i < m_TextStream.Count() && renderState.y < tall; )
  649. {
  650. // 1.
  651. // Update our current render state based on the formatting and color streams
  652. int nXBeforeStateChange = renderState.x;
  653. if (UpdateRenderState(i, renderState))
  654. {
  655. // check for url state change
  656. if (renderState.textClickable != _currentTextClickable)
  657. {
  658. if (renderState.textClickable)
  659. {
  660. // entering new URL
  661. _clickableTextIndex++;
  662. surface()->DrawSetTextFont( m_hFontUnderline );
  663. // set up the panel
  664. ClickPanel *clickPanel = _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ? _clickableTextPanels[_clickableTextIndex] : NULL;
  665. if (clickPanel)
  666. {
  667. clickPanel->SetPos(renderState.x, renderState.y);
  668. }
  669. }
  670. else
  671. {
  672. FinishingURL(nXBeforeStateChange, renderState.y);
  673. surface()->DrawSetTextFont( _font );
  674. }
  675. _currentTextClickable = renderState.textClickable;
  676. }
  677. }
  678. // 2.
  679. // if we've passed a line break go to that
  680. if (m_LineBreaks[lineBreakIndexIndex] == i)
  681. {
  682. if (_currentTextClickable)
  683. {
  684. FinishingURL(renderState.x, renderState.y);
  685. }
  686. // add another line
  687. AddAnotherLine(renderState.x, renderState.y);
  688. lineBreakIndexIndex++;
  689. if (renderState.textClickable)
  690. {
  691. // move to the next URL
  692. _clickableTextIndex++;
  693. ClickPanel *clickPanel = _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ? _clickableTextPanels[_clickableTextIndex] : NULL;
  694. if (clickPanel)
  695. {
  696. clickPanel->SetPos(renderState.x, renderState.y);
  697. }
  698. }
  699. }
  700. // 3.
  701. // Calculate the range of text to draw all at once
  702. int iLast = m_TextStream.Count() - 1;
  703. // Stop at the next line break
  704. if ( m_LineBreaks[lineBreakIndexIndex] <= iLast )
  705. iLast = m_LineBreaks[lineBreakIndexIndex] - 1;
  706. // Stop at the next format change
  707. if ( m_FormatStream.IsValidIndex(renderState.formatStreamIndex) &&
  708. m_FormatStream[renderState.formatStreamIndex].textStreamIndex <= iLast )
  709. {
  710. iLast = m_FormatStream[renderState.formatStreamIndex].textStreamIndex - 1;
  711. }
  712. // Stop when entering or exiting the selected range
  713. if ( i < selection0 && iLast >= selection0 )
  714. iLast = selection0 - 1;
  715. if ( i >= selection0 && i < selection1 && iLast >= selection1 )
  716. iLast = selection1 - 1;
  717. // Handle non-drawing characters specially
  718. for ( int iT = i; iT <= iLast; iT++ )
  719. {
  720. if ( iswcntrl(m_TextStream[iT]) )
  721. {
  722. iLast = iT - 1;
  723. break;
  724. }
  725. }
  726. // 4.
  727. // Draw the current text range
  728. if ( iLast < i )
  729. {
  730. if ( m_TextStream[i] == '\t' )
  731. {
  732. int dxTabWidth = 8 * surface()->GetCharacterWidth(_font, ' ');
  733. dxTabWidth = max( 1, dxTabWidth );
  734. renderState.x = ( dxTabWidth * ( 1 + ( renderState.x / dxTabWidth ) ) );
  735. }
  736. i++;
  737. }
  738. else
  739. {
  740. renderState.x += DrawString(i, iLast, renderState, _font);
  741. i = iLast + 1;
  742. }
  743. }
  744. if (renderState.textClickable)
  745. {
  746. FinishingURL(renderState.x, renderState.y);
  747. }
  748. }
  749. //-----------------------------------------------------------------------------
  750. // Purpose:
  751. //-----------------------------------------------------------------------------
  752. int RichText::GetClickableTextIndexStart(int startIndex)
  753. {
  754. // cycle to the right url panel for what is visible after the startIndex.
  755. for (int i = 0; i < _clickableTextPanels.Count(); i++)
  756. {
  757. if (_clickableTextPanels[i]->GetTextIndex() > startIndex)
  758. {
  759. return i - 1;
  760. }
  761. }
  762. return -1;
  763. }
  764. //-----------------------------------------------------------------------------
  765. // Purpose: Recalcultes the formatting state from the specified index
  766. //-----------------------------------------------------------------------------
  767. void RichText::RecalculateDefaultState(int startIndex)
  768. {
  769. if (!HasText() )
  770. return;
  771. Assert(startIndex < m_TextStream.Count());
  772. m_CachedRenderState.textColor = GetFgColor();
  773. _pixelsIndent = 0;
  774. _currentTextClickable = false;
  775. _clickableTextIndex = GetClickableTextIndexStart(startIndex);
  776. // find where in the formatting stream we need to be
  777. GenerateRenderStateForTextStreamIndex(startIndex, m_CachedRenderState);
  778. _recalcSavedRenderState = false;
  779. }
  780. //-----------------------------------------------------------------------------
  781. // Purpose: updates a render state based on the formatting and color streams
  782. // Output: true if we changed the render state
  783. //-----------------------------------------------------------------------------
  784. bool RichText::UpdateRenderState(int textStreamPos, TRenderState &renderState)
  785. {
  786. // check the color stream
  787. if (m_FormatStream.IsValidIndex(renderState.formatStreamIndex) &&
  788. m_FormatStream[renderState.formatStreamIndex].textStreamIndex == textStreamPos)
  789. {
  790. // set the current formatting
  791. renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color;
  792. renderState.textClickable = m_FormatStream[renderState.formatStreamIndex].textClickable;
  793. CalculateFade( renderState );
  794. int indentChange = m_FormatStream[renderState.formatStreamIndex].pixelsIndent - renderState.pixelsIndent;
  795. renderState.pixelsIndent = m_FormatStream[renderState.formatStreamIndex].pixelsIndent;
  796. if (indentChange)
  797. {
  798. renderState.x = renderState.pixelsIndent + _drawOffsetX;
  799. }
  800. //!! for supporting old functionality, store off state in globals
  801. _pixelsIndent = renderState.pixelsIndent;
  802. // move to the next position in the color stream
  803. renderState.formatStreamIndex++;
  804. return true;
  805. }
  806. return false;
  807. }
  808. //-----------------------------------------------------------------------------
  809. // Purpose: Returns the index in the format stream for the specified text stream index
  810. //-----------------------------------------------------------------------------
  811. int RichText::FindFormatStreamIndexForTextStreamPos(int textStreamIndex)
  812. {
  813. int formatStreamIndex = 0;
  814. for (; m_FormatStream.IsValidIndex(formatStreamIndex); formatStreamIndex++)
  815. {
  816. if (m_FormatStream[formatStreamIndex].textStreamIndex > textStreamIndex)
  817. break;
  818. }
  819. // step back to the color change before the new line
  820. formatStreamIndex--;
  821. if (!m_FormatStream.IsValidIndex(formatStreamIndex))
  822. {
  823. formatStreamIndex = 0;
  824. }
  825. return formatStreamIndex;
  826. }
  827. //-----------------------------------------------------------------------------
  828. // Purpose: Generates a base renderstate given a index into the text stream
  829. //-----------------------------------------------------------------------------
  830. void RichText::GenerateRenderStateForTextStreamIndex(int textStreamIndex, TRenderState &renderState)
  831. {
  832. // find where in the format stream we need to be given the specified place in the text stream
  833. renderState.formatStreamIndex = FindFormatStreamIndexForTextStreamPos(textStreamIndex);
  834. // copy the state data
  835. renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color;
  836. renderState.pixelsIndent = m_FormatStream[renderState.formatStreamIndex].pixelsIndent;
  837. renderState.textClickable = m_FormatStream[renderState.formatStreamIndex].textClickable;
  838. }
  839. //-----------------------------------------------------------------------------
  840. // Purpose: Called pre render
  841. //-----------------------------------------------------------------------------
  842. void RichText::OnThink()
  843. {
  844. if (m_bRecalcLineBreaks)
  845. {
  846. _recalcSavedRenderState = true;
  847. RecalculateLineBreaks();
  848. // recalculate scrollbar position
  849. if (_invalidateVerticalScrollbarSlider)
  850. {
  851. LayoutVerticalScrollBarSlider();
  852. }
  853. }
  854. }
  855. //-----------------------------------------------------------------------------
  856. // Purpose: Called when data changes or panel size changes
  857. //-----------------------------------------------------------------------------
  858. void RichText::PerformLayout()
  859. {
  860. BaseClass::PerformLayout();
  861. // force a Repaint
  862. Repaint();
  863. }
  864. //-----------------------------------------------------------------------------
  865. // Purpose: inserts a color change into the formatting stream
  866. //-----------------------------------------------------------------------------
  867. void RichText::InsertColorChange(Color col)
  868. {
  869. // see if color already exists in text stream
  870. TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
  871. if (prevItem.color == col)
  872. {
  873. // inserting same color into stream, just ignore
  874. }
  875. else if (prevItem.textStreamIndex == m_TextStream.Count())
  876. {
  877. // this item is in the same place; update values
  878. prevItem.color = col;
  879. }
  880. else
  881. {
  882. // add to text stream, based off existing item
  883. TFormatStream streamItem = prevItem;
  884. streamItem.color = col;
  885. streamItem.textStreamIndex = m_TextStream.Count();
  886. m_FormatStream.AddToTail(streamItem);
  887. }
  888. }
  889. //-----------------------------------------------------------------------------
  890. // Purpose: inserts a fade into the formatting stream
  891. //-----------------------------------------------------------------------------
  892. void RichText::InsertFade( float flSustain, float flLength )
  893. {
  894. // see if color already exists in text stream
  895. TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
  896. if (prevItem.textStreamIndex == m_TextStream.Count())
  897. {
  898. // this item is in the same place; update values
  899. prevItem.fade.flFadeStartTime = system()->GetCurrentTime() + flSustain;
  900. prevItem.fade.flFadeSustain = flSustain;
  901. prevItem.fade.flFadeLength = flLength;
  902. prevItem.fade.iOriginalAlpha = prevItem.color.a();
  903. }
  904. else
  905. {
  906. // add to text stream, based off existing item
  907. TFormatStream streamItem = prevItem;
  908. prevItem.fade.flFadeStartTime = system()->GetCurrentTime() + flSustain;
  909. prevItem.fade.flFadeLength = flLength;
  910. prevItem.fade.flFadeSustain = flSustain;
  911. prevItem.fade.iOriginalAlpha = prevItem.color.a();
  912. streamItem.textStreamIndex = m_TextStream.Count();
  913. m_FormatStream.AddToTail(streamItem);
  914. }
  915. }
  916. void RichText::ResetAllFades( bool bHold, bool bOnlyExpired, float flNewSustain )
  917. {
  918. m_bResetFades = bHold;
  919. if ( m_bResetFades == false )
  920. {
  921. for (int i = 1; i < m_FormatStream.Count(); i++)
  922. {
  923. if ( bOnlyExpired == true )
  924. {
  925. if ( m_FormatStream[i].fade.flFadeStartTime >= system()->GetCurrentTime() )
  926. continue;
  927. }
  928. if ( flNewSustain == -1.0f )
  929. {
  930. flNewSustain = m_FormatStream[i].fade.flFadeSustain;
  931. }
  932. m_FormatStream[i].fade.flFadeStartTime = system()->GetCurrentTime() + flNewSustain;
  933. }
  934. }
  935. }
  936. //-----------------------------------------------------------------------------
  937. // Purpose: inserts an indent change into the formatting stream
  938. //-----------------------------------------------------------------------------
  939. void RichText::InsertIndentChange(int pixelsIndent)
  940. {
  941. if (pixelsIndent < 0)
  942. {
  943. pixelsIndent = 0;
  944. }
  945. else if (pixelsIndent > 255)
  946. {
  947. pixelsIndent = 255;
  948. }
  949. // see if indent change already exists in text stream
  950. TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
  951. if (prevItem.pixelsIndent == pixelsIndent)
  952. {
  953. // inserting same indent into stream, just ignore
  954. }
  955. else if (prevItem.textStreamIndex == m_TextStream.Count())
  956. {
  957. // this item is in the same place; update
  958. prevItem.pixelsIndent = pixelsIndent;
  959. }
  960. else
  961. {
  962. // add to text stream, based off existing item
  963. TFormatStream streamItem = prevItem;
  964. streamItem.pixelsIndent = pixelsIndent;
  965. streamItem.textStreamIndex = m_TextStream.Count();
  966. m_FormatStream.AddToTail(streamItem);
  967. }
  968. }
  969. //-----------------------------------------------------------------------------
  970. // Purpose: Inserts character Start for clickable text, eg. URLS
  971. //-----------------------------------------------------------------------------
  972. void RichText::InsertClickableTextStart( const char *pchClickAction )
  973. {
  974. // see if indent change already exists in text stream
  975. TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
  976. TFormatStream *pFormatStream = &prevItem;
  977. if (prevItem.textStreamIndex == m_TextStream.Count())
  978. {
  979. // this item is in the same place; update
  980. prevItem.textClickable = true;
  981. pFormatStream->m_sClickableTextAction = pchClickAction;
  982. }
  983. else
  984. {
  985. // add to text stream, based off existing item
  986. TFormatStream formatStreamCopy = prevItem;
  987. int iFormatStream = m_FormatStream.AddToTail( formatStreamCopy );
  988. // set the new params
  989. pFormatStream = &m_FormatStream[iFormatStream];
  990. pFormatStream->textStreamIndex = m_TextStream.Count();
  991. pFormatStream->textClickable = true;
  992. pFormatStream->m_sClickableTextAction = pchClickAction;
  993. }
  994. // invalidate the layout to recalculate where the click panels should go
  995. InvalidateLineBreakStream();
  996. InvalidateLayout();
  997. }
  998. //-----------------------------------------------------------------------------
  999. // Purpose: Inserts character end for clickable text, eg. URLS
  1000. //-----------------------------------------------------------------------------
  1001. void RichText::InsertClickableTextEnd()
  1002. {
  1003. // see if indent change already exists in text stream
  1004. TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
  1005. if (!prevItem.textClickable)
  1006. {
  1007. // inserting same indent into stream, just ignore
  1008. }
  1009. else if (prevItem.textStreamIndex == m_TextStream.Count())
  1010. {
  1011. // this item is in the same place; update
  1012. prevItem.textClickable = false;
  1013. }
  1014. else
  1015. {
  1016. // add to text stream, based off existing item
  1017. TFormatStream streamItem = prevItem;
  1018. streamItem.textClickable = false;
  1019. streamItem.textStreamIndex = m_TextStream.Count();
  1020. m_FormatStream.AddToTail(streamItem);
  1021. }
  1022. }
  1023. //-----------------------------------------------------------------------------
  1024. // Purpose: moves x,y to the Start of the next line of text
  1025. //-----------------------------------------------------------------------------
  1026. void RichText::AddAnotherLine(int &cx, int &cy)
  1027. {
  1028. cx = _drawOffsetX + _pixelsIndent;
  1029. cy += (surface()->GetFontTall(_font) + _drawOffsetY);
  1030. }
  1031. //-----------------------------------------------------------------------------
  1032. // Purpose: Recalculates line breaks
  1033. //-----------------------------------------------------------------------------
  1034. void RichText::RecalculateLineBreaks()
  1035. {
  1036. m_bRecalcLineBreaks = false;
  1037. _recalcSavedRenderState = true;
  1038. if (!HasText())
  1039. return;
  1040. HFont font = _font;
  1041. int wide = GetWide();
  1042. // subtract the scrollbar width
  1043. if (_vertScrollBar->IsVisible())
  1044. {
  1045. wide -= _vertScrollBar->GetWide();
  1046. }
  1047. int charWidth;
  1048. int x = _drawOffsetX, y = _drawOffsetY;
  1049. int wordStartIndex = 0;
  1050. int wordLength = 0;
  1051. bool hasWord = false;
  1052. bool justStartedNewLine = true;
  1053. bool wordStartedOnNewLine = true;
  1054. int startChar = 0;
  1055. if (_recalculateBreaksIndex <= 0)
  1056. {
  1057. m_LineBreaks.RemoveAll();
  1058. }
  1059. else
  1060. {
  1061. // remove the rest of the linebreaks list since its out of date.
  1062. for (int i = _recalculateBreaksIndex + 1; i < m_LineBreaks.Count(); ++i)
  1063. {
  1064. m_LineBreaks.Remove(i);
  1065. --i; // removing shrinks the list!
  1066. }
  1067. startChar = m_LineBreaks[_recalculateBreaksIndex];
  1068. }
  1069. // handle the case where this char is a new line, in that case
  1070. // we have already taken its break index into account above so skip it.
  1071. if (m_TextStream[startChar] == '\r' || m_TextStream[startChar] == '\n')
  1072. {
  1073. startChar++;
  1074. }
  1075. // cycle to the right url panel for what is visible after the startIndex.
  1076. int clickableTextNum = GetClickableTextIndexStart(startChar);
  1077. clickableTextNum++;
  1078. // initialize the renderstate with the start
  1079. TRenderState renderState;
  1080. GenerateRenderStateForTextStreamIndex(startChar, renderState);
  1081. _currentTextClickable = false;
  1082. // loop through all the characters
  1083. for (int i = startChar; i < m_TextStream.Count(); ++i)
  1084. {
  1085. wchar_t ch = m_TextStream[i];
  1086. renderState.x = x;
  1087. if (UpdateRenderState(i, renderState))
  1088. {
  1089. x = renderState.x;
  1090. int preI = i;
  1091. // check for clickable text
  1092. if (renderState.textClickable != _currentTextClickable)
  1093. {
  1094. if (renderState.textClickable)
  1095. {
  1096. // make a new clickable text panel
  1097. if (clickableTextNum >= _clickableTextPanels.Count())
  1098. {
  1099. _clickableTextPanels.AddToTail(new ClickPanel(this));
  1100. }
  1101. ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++];
  1102. clickPanel->SetTextIndex(preI);
  1103. }
  1104. // url state change
  1105. _currentTextClickable = renderState.textClickable;
  1106. }
  1107. }
  1108. // line break only on whitespace characters
  1109. if (!iswspace(ch))
  1110. {
  1111. if (hasWord)
  1112. {
  1113. // append to the current word
  1114. }
  1115. else
  1116. {
  1117. // Start a new word
  1118. wordStartIndex = i;
  1119. hasWord = true;
  1120. wordStartedOnNewLine = justStartedNewLine;
  1121. wordLength = 0;
  1122. }
  1123. }
  1124. else
  1125. {
  1126. // whitespace/punctuation character
  1127. // end the word
  1128. hasWord = false;
  1129. }
  1130. // get the width
  1131. #if defined( POSIX ) && !defined( _PS3 )
  1132. wchar_t chBefore = 0;
  1133. wchar_t chAfter = 0;
  1134. if ( i > 0 )
  1135. chBefore = m_TextStream[i-1];
  1136. if ( ( i + 1 ) < m_TextStream.Count() )
  1137. chAfter = m_TextStream[i+1];
  1138. float flWide = 0.0f, flabcA = 0.0f, flabcC;
  1139. surface()->GetKernedCharWidth(font, ch, chBefore, chAfter, flWide, flabcA, flabcC );
  1140. // don't include a negative c measure, slightly over estimate the length of the string
  1141. charWidth = floor( flabcA + flWide + ( flabcC > 0.0 ? flabcC : 0.0 ) + 0.6f );
  1142. #else
  1143. charWidth = surface()->GetCharacterWidth(font, ch);
  1144. #endif
  1145. if (!iswcntrl(ch))
  1146. {
  1147. justStartedNewLine = false;
  1148. }
  1149. // check to see if the word is past the end of the line [wordStartIndex, i)
  1150. if ((x + charWidth) >= wide || ch == '\r' || ch == '\n')
  1151. {
  1152. // add another line
  1153. AddAnotherLine(x, y);
  1154. justStartedNewLine = true;
  1155. hasWord = false;
  1156. if (ch == '\r' || ch == '\n')
  1157. {
  1158. // set the break at the current character
  1159. m_LineBreaks.AddToTail(i);
  1160. }
  1161. else if (wordStartedOnNewLine || iswspace(ch) ) // catch the "blah " case wrapping around a line
  1162. {
  1163. // word is longer than a line, so set the break at the current cursor
  1164. m_LineBreaks.AddToTail(i);
  1165. if (renderState.textClickable)
  1166. {
  1167. // need to split the url into two panels
  1168. int oldIndex = _clickableTextPanels[clickableTextNum - 1]->GetTextIndex();
  1169. // make a new clickable text panel
  1170. if (clickableTextNum >= _clickableTextPanels.Count())
  1171. {
  1172. _clickableTextPanels.AddToTail(new ClickPanel(this));
  1173. }
  1174. ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++];
  1175. clickPanel->SetTextIndex(oldIndex);
  1176. }
  1177. }
  1178. else
  1179. {
  1180. // set it at the last word Start
  1181. m_LineBreaks.AddToTail(wordStartIndex);
  1182. // just back to reparse the next line of text
  1183. i = wordStartIndex;
  1184. }
  1185. // reset word length
  1186. wordLength = 0;
  1187. }
  1188. // add to the size
  1189. x += charWidth;
  1190. wordLength += charWidth;
  1191. }
  1192. // end the list
  1193. m_LineBreaks.AddToTail(MAX_BUFFER_SIZE);
  1194. // set up the scrollbar
  1195. _invalidateVerticalScrollbarSlider = true;
  1196. }
  1197. //-----------------------------------------------------------------------------
  1198. // Purpose: Recalculate where the vertical scroll bar slider should be
  1199. // based on the current cursor line we are on.
  1200. //-----------------------------------------------------------------------------
  1201. void RichText::LayoutVerticalScrollBarSlider()
  1202. {
  1203. _invalidateVerticalScrollbarSlider = false;
  1204. // set up the scrollbar
  1205. //if (!_vertScrollBar->IsVisible())
  1206. // return;
  1207. // see where the scrollbar currently is
  1208. int previousValue = _vertScrollBar->GetValue();
  1209. bool bCurrentlyAtEnd = false;
  1210. int rmin, rmax;
  1211. _vertScrollBar->GetRange(rmin, rmax);
  1212. if (rmax && (previousValue + rmin + _vertScrollBar->GetRangeWindow() == rmax))
  1213. {
  1214. bCurrentlyAtEnd = true;
  1215. }
  1216. // work out position to put scrollbar, factoring in insets
  1217. int wide, tall;
  1218. GetSize( wide, tall );
  1219. _vertScrollBar->SetPos( wide - _vertScrollBar->GetWide(), 0 );
  1220. // scrollbar is inside the borders.
  1221. _vertScrollBar->SetSize( _vertScrollBar->GetWide(), tall );
  1222. // calculate how many lines we can fully display
  1223. int displayLines = tall / (surface()->GetFontTall(_font) + _drawOffsetY);
  1224. int numLines = m_LineBreaks.Count();
  1225. if (numLines <= displayLines)
  1226. {
  1227. // disable the scrollbar
  1228. _vertScrollBar->SetEnabled(false);
  1229. _vertScrollBar->SetRange(0, numLines);
  1230. _vertScrollBar->SetRangeWindow(numLines);
  1231. _vertScrollBar->SetValue(0);
  1232. if ( m_bUnusedScrollbarInvis )
  1233. {
  1234. SetVerticalScrollbar( false );
  1235. }
  1236. }
  1237. else
  1238. {
  1239. if ( m_bUnusedScrollbarInvis )
  1240. {
  1241. SetVerticalScrollbar( true );
  1242. }
  1243. // set the scrollbars range
  1244. _vertScrollBar->SetRange(0, numLines);
  1245. _vertScrollBar->SetRangeWindow(displayLines);
  1246. _vertScrollBar->SetEnabled(true);
  1247. // this should make it scroll one line at a time
  1248. _vertScrollBar->SetButtonPressedScrollValue(1);
  1249. if (bCurrentlyAtEnd)
  1250. {
  1251. _vertScrollBar->SetValue(numLines - displayLines);
  1252. }
  1253. _vertScrollBar->InvalidateLayout();
  1254. _vertScrollBar->Repaint();
  1255. }
  1256. }
  1257. //-----------------------------------------------------------------------------
  1258. // Purpose: Sets whether a vertical scrollbar is visible
  1259. //-----------------------------------------------------------------------------
  1260. void RichText::SetVerticalScrollbar(bool state)
  1261. {
  1262. if (_vertScrollBar->IsVisible() != state)
  1263. {
  1264. _vertScrollBar->SetVisible(state);
  1265. InvalidateLineBreakStream();
  1266. InvalidateLayout();
  1267. }
  1268. }
  1269. //-----------------------------------------------------------------------------
  1270. // Purpose: Create cut/copy/paste dropdown menu
  1271. //-----------------------------------------------------------------------------
  1272. void RichText::CreateEditMenu()
  1273. {
  1274. // create a drop down cut/copy/paste menu appropriate for this object's states
  1275. if (m_pEditMenu)
  1276. delete m_pEditMenu;
  1277. m_pEditMenu = new Menu(this, "EditMenu");
  1278. // add cut/copy/paste drop down options if its editable, just copy if it is not
  1279. m_pEditMenu->AddMenuItem("C&opy", new KeyValues("DoCopySelected"), this);
  1280. m_pEditMenu->SetVisible(false);
  1281. m_pEditMenu->SetParent(this);
  1282. m_pEditMenu->AddActionSignalTarget(this);
  1283. }
  1284. //-----------------------------------------------------------------------------
  1285. // Purpose: We want single line windows to scroll horizontally and select text
  1286. // in response to clicking and holding outside window
  1287. //-----------------------------------------------------------------------------
  1288. void RichText::OnMouseFocusTicked()
  1289. {
  1290. // if a button is down move the scrollbar slider the appropriate direction
  1291. if (_mouseDragSelection) // text is being selected via mouse clicking and dragging
  1292. {
  1293. OnCursorMoved(0,0); // we want the text to scroll as if we were dragging
  1294. }
  1295. }
  1296. //-----------------------------------------------------------------------------
  1297. // Purpose: If a cursor enters the window, we are not elegible for
  1298. // MouseFocusTicked events
  1299. //-----------------------------------------------------------------------------
  1300. void RichText::OnCursorEntered()
  1301. {
  1302. _mouseDragSelection = false; // outside of window dont recieve drag scrolling ticks
  1303. }
  1304. //-----------------------------------------------------------------------------
  1305. // Purpose: When the cursor is outside the window, if we are holding the mouse
  1306. // button down, then we want the window to scroll the text one char at a time using Ticks
  1307. //-----------------------------------------------------------------------------
  1308. void RichText::OnCursorExited()
  1309. {
  1310. // outside of window recieve drag scrolling ticks
  1311. if (_mouseSelection)
  1312. {
  1313. _mouseDragSelection = true;
  1314. }
  1315. }
  1316. //-----------------------------------------------------------------------------
  1317. // Purpose: Handle selection of text by mouse
  1318. //-----------------------------------------------------------------------------
  1319. void RichText::OnCursorMoved(int x, int y)
  1320. {
  1321. if (_mouseSelection)
  1322. {
  1323. // update the cursor position
  1324. int x, y;
  1325. input()->GetCursorPos(x, y);
  1326. ScreenToLocal(x, y);
  1327. _cursorPos = PixelToCursorSpace(x, y);
  1328. if (_cursorPos != _select[1])
  1329. {
  1330. _select[1] = _cursorPos;
  1331. Repaint();
  1332. }
  1333. // Msg( "selecting range [%d..%d]\n", _select[0], _select[1] );
  1334. }
  1335. }
  1336. //-----------------------------------------------------------------------------
  1337. // Purpose: Handle mouse button down events.
  1338. //-----------------------------------------------------------------------------
  1339. void RichText::OnMousePressed(MouseCode code)
  1340. {
  1341. if (code == MOUSE_LEFT)
  1342. {
  1343. // clear current selection
  1344. SelectNone();
  1345. // move the cursor to where the mouse was pressed
  1346. int x, y;
  1347. input()->GetCursorPos(x, y);
  1348. ScreenToLocal(x, y);
  1349. _cursorPos = PixelToCursorSpace(x, y);
  1350. if ( m_bInteractive )
  1351. {
  1352. // enter selection mode
  1353. input()->SetMouseCapture(GetVPanel());
  1354. _mouseSelection = true;
  1355. if (_select[0] < 0)
  1356. {
  1357. // if no initial selection position, Start selection position at cursor
  1358. _select[0] = _cursorPos;
  1359. }
  1360. _select[1] = _cursorPos;
  1361. }
  1362. RequestFocus();
  1363. Repaint();
  1364. }
  1365. else if (code == MOUSE_RIGHT) // check for context menu open
  1366. {
  1367. if ( m_bInteractive )
  1368. {
  1369. CreateEditMenu();
  1370. Assert(m_pEditMenu);
  1371. OpenEditMenu();
  1372. }
  1373. }
  1374. }
  1375. //-----------------------------------------------------------------------------
  1376. // Purpose: Handle mouse button up events
  1377. //-----------------------------------------------------------------------------
  1378. void RichText::OnMouseReleased(MouseCode code)
  1379. {
  1380. _mouseSelection = false;
  1381. input()->SetMouseCapture(NULL);
  1382. // make sure something has been selected
  1383. int cx0, cx1;
  1384. if (GetSelectedRange(cx0, cx1))
  1385. {
  1386. if (cx1 - cx0 == 0)
  1387. {
  1388. // nullify selection
  1389. _select[0] = -1;
  1390. }
  1391. }
  1392. }
  1393. //-----------------------------------------------------------------------------
  1394. // Purpose: Handle mouse double clicks
  1395. //-----------------------------------------------------------------------------
  1396. void RichText::OnMouseDoublePressed(MouseCode code)
  1397. {
  1398. if ( !m_bInteractive )
  1399. return;
  1400. // left double clicking on a word selects the word
  1401. if (code == MOUSE_LEFT)
  1402. {
  1403. // move the cursor just as if you single clicked.
  1404. OnMousePressed(code);
  1405. // then find the start and end of the word we are in to highlight it.
  1406. int selectSpot[2];
  1407. GotoWordLeft();
  1408. selectSpot[0] = _cursorPos;
  1409. GotoWordRight();
  1410. selectSpot[1] = _cursorPos;
  1411. if ( _cursorPos > 0 && (_cursorPos-1) < m_TextStream.Count() )
  1412. {
  1413. if (iswspace(m_TextStream[_cursorPos-1]))
  1414. {
  1415. selectSpot[1]--;
  1416. _cursorPos--;
  1417. }
  1418. }
  1419. _select[0] = selectSpot[0];
  1420. _select[1] = selectSpot[1];
  1421. _mouseSelection = true;
  1422. }
  1423. }
  1424. //-----------------------------------------------------------------------------
  1425. // Purpose: Turn off text selection code when mouse button is not down
  1426. //-----------------------------------------------------------------------------
  1427. void RichText::OnMouseCaptureLost()
  1428. {
  1429. _mouseSelection = false;
  1430. }
  1431. //-----------------------------------------------------------------------------
  1432. // Purpose: Masks which keys get chained up
  1433. // Maps keyboard input to text window functions.
  1434. //-----------------------------------------------------------------------------
  1435. void RichText::OnKeyCodeTyped(KeyCode code)
  1436. {
  1437. bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT));
  1438. bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL));
  1439. bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT));
  1440. bool winkey = (input()->IsKeyDown(KEY_LWIN) || input()->IsKeyDown(KEY_RWIN));
  1441. bool fallThrough = false;
  1442. REFERENCE( winkey );
  1443. if ( ctrl || ( IsOSX() && winkey ) )
  1444. {
  1445. switch(code)
  1446. {
  1447. case KEY_INSERT:
  1448. case KEY_C:
  1449. case KEY_X:
  1450. {
  1451. CopySelected();
  1452. break;
  1453. }
  1454. case KEY_PAGEUP:
  1455. case KEY_HOME:
  1456. {
  1457. GotoTextStart();
  1458. break;
  1459. }
  1460. case KEY_PAGEDOWN:
  1461. case KEY_END:
  1462. {
  1463. GotoTextEnd();
  1464. break;
  1465. }
  1466. default:
  1467. {
  1468. fallThrough = true;
  1469. break;
  1470. }
  1471. }
  1472. }
  1473. else if (alt)
  1474. {
  1475. // do nothing with ALT-x keys
  1476. fallThrough = true;
  1477. }
  1478. else
  1479. {
  1480. switch(code)
  1481. {
  1482. case KEY_TAB:
  1483. case KEY_LSHIFT:
  1484. case KEY_RSHIFT:
  1485. case KEY_ESCAPE:
  1486. case KEY_ENTER:
  1487. {
  1488. fallThrough = true;
  1489. break;
  1490. }
  1491. case KEY_DELETE:
  1492. {
  1493. if (shift)
  1494. {
  1495. // shift-delete is cut
  1496. CopySelected();
  1497. }
  1498. break;
  1499. }
  1500. case KEY_HOME:
  1501. {
  1502. GotoTextStart();
  1503. break;
  1504. }
  1505. case KEY_END:
  1506. {
  1507. GotoTextEnd();
  1508. break;
  1509. }
  1510. case KEY_PAGEUP:
  1511. {
  1512. // if there is a scroll bar scroll down one rangewindow
  1513. if (_vertScrollBar->IsVisible())
  1514. {
  1515. int window = _vertScrollBar->GetRangeWindow();
  1516. int newval = _vertScrollBar->GetValue();
  1517. _vertScrollBar->SetValue(newval - window - 1);
  1518. }
  1519. break;
  1520. }
  1521. case KEY_PAGEDOWN:
  1522. {
  1523. // if there is a scroll bar scroll down one rangewindow
  1524. if (_vertScrollBar->IsVisible())
  1525. {
  1526. int window = _vertScrollBar->GetRangeWindow();
  1527. int newval = _vertScrollBar->GetValue();
  1528. _vertScrollBar->SetValue(newval + window + 1);
  1529. }
  1530. break;
  1531. }
  1532. default:
  1533. {
  1534. // return if any other char is pressed.
  1535. // as it will be a unicode char.
  1536. // and we don't want select[1] changed unless a char was pressed that this fxn handles
  1537. return;
  1538. }
  1539. }
  1540. }
  1541. // select[1] is the location in the line where the blinking cursor started
  1542. _select[1] = _cursorPos;
  1543. // chain back on some keys
  1544. if (fallThrough)
  1545. {
  1546. BaseClass::OnKeyCodeTyped(code);
  1547. }
  1548. }
  1549. //-----------------------------------------------------------------------------
  1550. // Purpose: Scrolls the list according to the mouse wheel movement
  1551. //-----------------------------------------------------------------------------
  1552. void RichText::OnMouseWheeled(int delta)
  1553. {
  1554. MoveScrollBar(delta);
  1555. }
  1556. //-----------------------------------------------------------------------------
  1557. // Purpose: Scrolls the list
  1558. // Input : delta - amount to move scrollbar up
  1559. //-----------------------------------------------------------------------------
  1560. void RichText::MoveScrollBar(int delta)
  1561. {
  1562. MoveScrollBarDirect( delta * 3 );
  1563. }
  1564. //-----------------------------------------------------------------------------
  1565. // Purpose: Scrolls the list
  1566. // Input : delta - amount to move scrollbar up
  1567. //-----------------------------------------------------------------------------
  1568. void RichText::MoveScrollBarDirect(int delta)
  1569. {
  1570. if (_vertScrollBar->IsVisible())
  1571. {
  1572. int val = _vertScrollBar->GetValue();
  1573. val -= delta;
  1574. _vertScrollBar->SetValue(val);
  1575. _recalcSavedRenderState = true;
  1576. }
  1577. }
  1578. //-----------------------------------------------------------------------------
  1579. // Purpose: set the maximum number of chars in the text buffer
  1580. //-----------------------------------------------------------------------------
  1581. void RichText::SetMaximumCharCount(int maxChars)
  1582. {
  1583. _maxCharCount = maxChars;
  1584. }
  1585. //-----------------------------------------------------------------------------
  1586. // Purpose: Find out what line the cursor is on
  1587. //-----------------------------------------------------------------------------
  1588. int RichText::GetCursorLine()
  1589. {
  1590. // always returns the last place
  1591. int pos = m_LineBreaks[m_LineBreaks.Count() - 1];
  1592. Assert(pos == MAX_BUFFER_SIZE);
  1593. return pos;
  1594. }
  1595. //-----------------------------------------------------------------------------
  1596. // Purpose: Move the cursor over to the Start of the next word to the right
  1597. //-----------------------------------------------------------------------------
  1598. void RichText::GotoWordRight()
  1599. {
  1600. // search right until we hit a whitespace character or a newline
  1601. while (++_cursorPos < m_TextStream.Count())
  1602. {
  1603. if (iswspace(m_TextStream[_cursorPos]))
  1604. break;
  1605. }
  1606. // search right until we hit an nonspace character
  1607. while (++_cursorPos < m_TextStream.Count())
  1608. {
  1609. if (!iswspace(m_TextStream[_cursorPos]))
  1610. break;
  1611. }
  1612. if (_cursorPos > m_TextStream.Count())
  1613. {
  1614. _cursorPos = m_TextStream.Count();
  1615. }
  1616. // now we are at the start of the next word
  1617. Repaint();
  1618. }
  1619. //-----------------------------------------------------------------------------
  1620. // Purpose: Move the cursor over to the Start of the next word to the left
  1621. //-----------------------------------------------------------------------------
  1622. void RichText::GotoWordLeft()
  1623. {
  1624. if (_cursorPos < 1)
  1625. return;
  1626. // search left until we hit an nonspace character
  1627. while (--_cursorPos >= 0)
  1628. {
  1629. if (!iswspace(m_TextStream[_cursorPos]))
  1630. break;
  1631. }
  1632. // search left until we hit a whitespace character
  1633. while (--_cursorPos >= 0)
  1634. {
  1635. if (iswspace(m_TextStream[_cursorPos]))
  1636. {
  1637. break;
  1638. }
  1639. }
  1640. // we end one character off
  1641. _cursorPos++;
  1642. // now we are at the start of the previous word
  1643. Repaint();
  1644. }
  1645. //-----------------------------------------------------------------------------
  1646. // Purpose: Move cursor to the Start of the text buffer
  1647. //-----------------------------------------------------------------------------
  1648. void RichText::GotoTextStart()
  1649. {
  1650. _cursorPos = 0; // set cursor to start
  1651. _invalidateVerticalScrollbarSlider = true;
  1652. // force scrollbar to the top
  1653. _vertScrollBar->SetValue(0);
  1654. Repaint();
  1655. }
  1656. //-----------------------------------------------------------------------------
  1657. // Purpose: Move cursor to the end of the text buffer
  1658. //-----------------------------------------------------------------------------
  1659. void RichText::GotoTextEnd()
  1660. {
  1661. _cursorPos = m_TextStream.Count(); // set cursor to end of buffer
  1662. _invalidateVerticalScrollbarSlider = true;
  1663. // force the scrollbar to the bottom
  1664. int min, max;
  1665. _vertScrollBar->GetRange(min, max);
  1666. _vertScrollBar->SetValue(max);
  1667. Repaint();
  1668. }
  1669. //-----------------------------------------------------------------------------
  1670. // Purpose: Culls the text stream down to a managable size
  1671. //-----------------------------------------------------------------------------
  1672. void RichText::TruncateTextStream()
  1673. {
  1674. if (_maxCharCount < 1)
  1675. return;
  1676. // choose a point to cull at
  1677. int cullPos = _maxCharCount / 2;
  1678. // kill half the buffer
  1679. m_TextStream.RemoveMultiple(0, cullPos);
  1680. // work out where in the format stream we can start
  1681. int formatIndex = FindFormatStreamIndexForTextStreamPos(cullPos);
  1682. if (formatIndex > 0)
  1683. {
  1684. // take a copy, make it first
  1685. m_FormatStream[0] = m_FormatStream[formatIndex];
  1686. m_FormatStream[0].textStreamIndex = 0;
  1687. // kill the others
  1688. m_FormatStream.RemoveMultiple(1, formatIndex);
  1689. }
  1690. // renormalize the remainder of the format stream
  1691. for (int i = 1; i < m_FormatStream.Count(); i++)
  1692. {
  1693. Assert(m_FormatStream[i].textStreamIndex > cullPos);
  1694. m_FormatStream[i].textStreamIndex -= cullPos;
  1695. }
  1696. // mark everything to be recalculated
  1697. InvalidateLineBreakStream();
  1698. InvalidateLayout();
  1699. _invalidateVerticalScrollbarSlider = true;
  1700. }
  1701. //-----------------------------------------------------------------------------
  1702. // Purpose: Insert a character into the text buffer
  1703. //-----------------------------------------------------------------------------
  1704. void RichText::InsertChar(wchar_t wch)
  1705. {
  1706. // throw away redundant linefeed characters
  1707. if ( wch == '\r' )
  1708. return;
  1709. if (_maxCharCount > 0 && m_TextStream.Count() > _maxCharCount)
  1710. {
  1711. TruncateTextStream();
  1712. }
  1713. // insert the new char at the end of the buffer
  1714. m_TextStream.AddToTail(wch);
  1715. // mark the linebreak steam as needing recalculating from that point
  1716. _recalculateBreaksIndex = m_LineBreaks.Count() - 2;
  1717. Repaint();
  1718. }
  1719. //-----------------------------------------------------------------------------
  1720. // Purpose: Insert a string into the text buffer, this is just a series
  1721. // of char inserts because we have to check each char is ok to insert
  1722. //-----------------------------------------------------------------------------
  1723. void RichText::InsertString(const char *text)
  1724. {
  1725. if (text[0] == '#')
  1726. {
  1727. wchar_t unicode[ 1024 ];
  1728. ResolveLocalizedTextAndVariables( text, unicode, sizeof( unicode ) );
  1729. InsertString( unicode );
  1730. return;
  1731. }
  1732. // upgrade the ansi text to unicode to display it
  1733. int len = strlen(text);
  1734. wchar_t *unicode = (wchar_t *)stackalloc((len + 1) * sizeof(wchar_t));
  1735. Q_UTF8ToUnicode(text, unicode, ((len + 1) * sizeof(wchar_t)));
  1736. InsertString(unicode);
  1737. }
  1738. //-----------------------------------------------------------------------------
  1739. // Purpose: Insertsa a unicode string into the buffer
  1740. //-----------------------------------------------------------------------------
  1741. void RichText::InsertString(const wchar_t *wszText)
  1742. {
  1743. // insert the whole string
  1744. for (const wchar_t *ch = wszText; *ch != 0; ++ch)
  1745. {
  1746. InsertChar(*ch);
  1747. }
  1748. InvalidateLayout();
  1749. m_bRecalcLineBreaks = true;
  1750. Repaint();
  1751. }
  1752. //-----------------------------------------------------------------------------
  1753. // Purpose: Declare a selection empty
  1754. //-----------------------------------------------------------------------------
  1755. void RichText::SelectNone()
  1756. {
  1757. // tag the selection as empty
  1758. _select[0] = -1;
  1759. Repaint();
  1760. }
  1761. //-----------------------------------------------------------------------------
  1762. // Purpose: Load in the selection range so cx0 is the Start and cx1 is the end
  1763. // from smallest to highest (right to left)
  1764. //-----------------------------------------------------------------------------
  1765. bool RichText::GetSelectedRange(int &cx0, int &cx1)
  1766. {
  1767. // if there is nothing selected return false
  1768. if (_select[0] == -1)
  1769. return false;
  1770. // sort the two position so cx0 is the smallest
  1771. cx0 = _select[0];
  1772. cx1 = _select[1];
  1773. if (cx1 < cx0)
  1774. {
  1775. int temp = cx0;
  1776. cx0 = cx1;
  1777. cx1 = temp;
  1778. }
  1779. return true;
  1780. }
  1781. //-----------------------------------------------------------------------------
  1782. // Purpose: Opens the cut/copy/paste dropdown menu
  1783. //-----------------------------------------------------------------------------
  1784. void RichText::OpenEditMenu()
  1785. {
  1786. // get cursor position, this is local to this text edit window
  1787. // so we need to adjust it relative to the parent
  1788. int cursorX, cursorY;
  1789. input()->GetCursorPos(cursorX, cursorY);
  1790. /* !! disabled since it recursively gets panel pointers, potentially across dll boundaries,
  1791. and doesn't need to be necessary (it's just for handling windowed mode)
  1792. // find the frame that has no parent (the one on the desktop)
  1793. Panel *panel = this;
  1794. while ( panel->GetParent() != NULL)
  1795. {
  1796. panel = panel->GetParent();
  1797. }
  1798. panel->ScreenToLocal(cursorX, cursorY);
  1799. int x, y;
  1800. // get base panel's postition
  1801. panel->GetPos(x, y);
  1802. // adjust our cursor position accordingly
  1803. cursorX += x;
  1804. cursorY += y;
  1805. */
  1806. int x0, x1;
  1807. if (GetSelectedRange(x0, x1)) // there is something selected
  1808. {
  1809. m_pEditMenu->SetItemEnabled("&Cut", true);
  1810. m_pEditMenu->SetItemEnabled("C&opy", true);
  1811. }
  1812. else // there is nothing selected, disable cut/copy options
  1813. {
  1814. m_pEditMenu->SetItemEnabled("&Cut", false);
  1815. m_pEditMenu->SetItemEnabled("C&opy", false);
  1816. }
  1817. m_pEditMenu->SetVisible(true);
  1818. m_pEditMenu->RequestFocus();
  1819. // relayout the menu immediately so that we know it's size
  1820. m_pEditMenu->InvalidateLayout(true);
  1821. int menuWide, menuTall;
  1822. m_pEditMenu->GetSize(menuWide, menuTall);
  1823. // work out where the cursor is and therefore the best place to put the menu
  1824. int wide, tall;
  1825. surface()->GetScreenSize(wide, tall);
  1826. if (wide - menuWide > cursorX)
  1827. {
  1828. // menu hanging right
  1829. if (tall - menuTall > cursorY)
  1830. {
  1831. // menu hanging down
  1832. m_pEditMenu->SetPos(cursorX, cursorY);
  1833. }
  1834. else
  1835. {
  1836. // menu hanging up
  1837. m_pEditMenu->SetPos(cursorX, cursorY - menuTall);
  1838. }
  1839. }
  1840. else
  1841. {
  1842. // menu hanging left
  1843. if (tall - menuTall > cursorY)
  1844. {
  1845. // menu hanging down
  1846. m_pEditMenu->SetPos(cursorX - menuWide, cursorY);
  1847. }
  1848. else
  1849. {
  1850. // menu hanging up
  1851. m_pEditMenu->SetPos(cursorX - menuWide, cursorY - menuTall);
  1852. }
  1853. }
  1854. m_pEditMenu->RequestFocus();
  1855. }
  1856. //-----------------------------------------------------------------------------
  1857. // Purpose: Cuts the selected chars from the buffer and
  1858. // copies them into the clipboard
  1859. //-----------------------------------------------------------------------------
  1860. void RichText::CutSelected()
  1861. {
  1862. CopySelected();
  1863. // have to request focus if we used the menu
  1864. RequestFocus();
  1865. }
  1866. //-----------------------------------------------------------------------------
  1867. // Purpose: Copies the selected chars into the clipboard
  1868. //-----------------------------------------------------------------------------
  1869. void RichText::CopySelected()
  1870. {
  1871. int x0, x1;
  1872. if (GetSelectedRange(x0, x1))
  1873. {
  1874. CUtlVector<wchar_t> buf;
  1875. for (int i = x0; i < x1; i++)
  1876. {
  1877. if ( m_TextStream.IsValidIndex(i) == false )
  1878. continue;
  1879. if (m_TextStream[i] == '\n')
  1880. {
  1881. if ( buf.Count() == 0 )
  1882. {
  1883. // Don't put an end line at the beginning
  1884. // It makes it really difficult to copy paste from the console into
  1885. // single line dialogs
  1886. continue;
  1887. }
  1888. buf.AddToTail( '\r' );
  1889. }
  1890. // remove any rich edit commands
  1891. buf.AddToTail(m_TextStream[i]);
  1892. }
  1893. buf.AddToTail('\0');
  1894. system()->SetClipboardText(buf.Base(), buf.Count() - 1);
  1895. }
  1896. // have to request focus if we used the menu
  1897. RequestFocus();
  1898. }
  1899. //-----------------------------------------------------------------------------
  1900. // Purpose: Returns the index in the text buffer of the
  1901. // character the drawing should Start at
  1902. //-----------------------------------------------------------------------------
  1903. int RichText::GetStartDrawIndex(int &lineBreakIndexIndex)
  1904. {
  1905. int startIndex = 0;
  1906. int startLine = _vertScrollBar->GetValue();
  1907. if ( startLine >= m_LineBreaks.Count() ) // incase the line breaks got reset and the scroll bar hasn't
  1908. {
  1909. startLine = m_LineBreaks.Count() - 1;
  1910. }
  1911. lineBreakIndexIndex = startLine;
  1912. if (startLine && startLine < m_LineBreaks.Count())
  1913. {
  1914. startIndex = m_LineBreaks[startLine - 1];
  1915. }
  1916. return startIndex;
  1917. }
  1918. //-----------------------------------------------------------------------------
  1919. // Purpose: Get a string from text buffer
  1920. // Input: offset - index to Start reading from
  1921. // bufLen - length of string
  1922. //-----------------------------------------------------------------------------
  1923. void RichText::GetText(int offset, wchar_t *buf, int bufLenInBytes)
  1924. {
  1925. if (!buf)
  1926. return;
  1927. int bufLen = bufLenInBytes / sizeof(wchar_t);
  1928. int i;
  1929. for (i = offset; i < (offset + bufLen - 1); i++)
  1930. {
  1931. if (i >= m_TextStream.Count())
  1932. break;
  1933. buf[i-offset] = m_TextStream[i];
  1934. }
  1935. buf[(i-offset)] = 0;
  1936. buf[bufLen-1] = 0;
  1937. }
  1938. //-----------------------------------------------------------------------------
  1939. // Purpose: gets text from the buffer
  1940. //-----------------------------------------------------------------------------
  1941. void RichText::GetText(int offset, char *pch, int bufLenInBytes)
  1942. {
  1943. wchar_t rgwchT[4096];
  1944. GetText(offset, rgwchT, sizeof(rgwchT));
  1945. Q_UnicodeToUTF8(rgwchT, pch, bufLenInBytes);
  1946. }
  1947. //-----------------------------------------------------------------------------
  1948. // Purpose: Set the font of the buffer text
  1949. //-----------------------------------------------------------------------------
  1950. void RichText::SetFont(HFont font)
  1951. {
  1952. _font = font;
  1953. InvalidateLayout();
  1954. m_bRecalcLineBreaks = true;
  1955. Repaint();
  1956. }
  1957. //-----------------------------------------------------------------------------
  1958. // Purpose: Called when the scrollbar slider is moved
  1959. //-----------------------------------------------------------------------------
  1960. void RichText::OnSliderMoved()
  1961. {
  1962. _recalcSavedRenderState = true;
  1963. Repaint();
  1964. }
  1965. //-----------------------------------------------------------------------------
  1966. // Purpose:
  1967. //-----------------------------------------------------------------------------
  1968. bool RichText::RequestInfo(KeyValues *outputData)
  1969. {
  1970. if (!stricmp(outputData->GetName(), "GetText"))
  1971. {
  1972. wchar_t wbuf[512];
  1973. GetText(0, wbuf, sizeof(wbuf));
  1974. outputData->SetWString("text", wbuf);
  1975. return true;
  1976. }
  1977. return BaseClass::RequestInfo(outputData);
  1978. }
  1979. //-----------------------------------------------------------------------------
  1980. // Purpose:
  1981. //-----------------------------------------------------------------------------
  1982. void RichText::OnSetText(const wchar_t *text)
  1983. {
  1984. SetText(text);
  1985. }
  1986. //-----------------------------------------------------------------------------
  1987. // Purpose: Called when a URL, etc has been clicked on
  1988. //-----------------------------------------------------------------------------
  1989. void RichText::OnClickPanel(int index)
  1990. {
  1991. wchar_t wBuf[512];
  1992. int outIndex = 0;
  1993. // parse out the clickable text, and send it to our listeners
  1994. _currentTextClickable = true;
  1995. TRenderState renderState;
  1996. GenerateRenderStateForTextStreamIndex(index, renderState);
  1997. for (int i = index; i < (sizeof(wBuf) - 1) && i < m_TextStream.Count(); i++)
  1998. {
  1999. // stop getting characters when text is no longer clickable
  2000. UpdateRenderState(i, renderState);
  2001. if (!renderState.textClickable)
  2002. break;
  2003. // copy out the character
  2004. wBuf[outIndex++] = m_TextStream[i];
  2005. }
  2006. wBuf[outIndex] = 0;
  2007. int iFormatSteam = FindFormatStreamIndexForTextStreamPos( index );
  2008. if ( m_FormatStream[iFormatSteam].m_sClickableTextAction )
  2009. {
  2010. Q_UTF8ToUnicode( m_FormatStream[iFormatSteam].m_sClickableTextAction.String(), wBuf, sizeof( wBuf ) );
  2011. }
  2012. PostActionSignal(new KeyValues("TextClicked", "text", wBuf));
  2013. OnTextClicked(wBuf);
  2014. }
  2015. //-----------------------------------------------------------------------------
  2016. // Purpose:
  2017. //-----------------------------------------------------------------------------
  2018. void RichText::ApplySettings(KeyValues *inResourceData)
  2019. {
  2020. BaseClass::ApplySettings(inResourceData);
  2021. SetMaximumCharCount(inResourceData->GetInt("maxchars", -1));
  2022. SetVerticalScrollbar(inResourceData->GetBool("scrollbar", true));
  2023. // get the starting text, if any
  2024. const char *text = inResourceData->GetString("text", "");
  2025. if (*text)
  2026. {
  2027. delete [] m_pszInitialText;
  2028. int len = Q_strlen(text) + 1;
  2029. m_pszInitialText = new char[ len ];
  2030. Q_strncpy( m_pszInitialText, text, len );
  2031. SetText(text);
  2032. }
  2033. else
  2034. {
  2035. const char *textfilename = inResourceData->GetString("textfile", NULL);
  2036. if ( textfilename )
  2037. {
  2038. FileHandle_t f = g_pFullFileSystem->Open( textfilename, "rt" );
  2039. if (!f)
  2040. {
  2041. Warning( "RichText: textfile parameter '%s' not found.\n", textfilename );
  2042. return;
  2043. }
  2044. int len = g_pFullFileSystem->Size( f );
  2045. delete [] m_pszInitialText;
  2046. m_pszInitialText = new char[ len + 1 ];
  2047. g_pFullFileSystem->Read( m_pszInitialText, len, f );
  2048. m_pszInitialText[len - 1] = 0;
  2049. SetText( m_pszInitialText );
  2050. g_pFullFileSystem->Close( f );
  2051. }
  2052. }
  2053. }
  2054. //-----------------------------------------------------------------------------
  2055. // Purpose:
  2056. //-----------------------------------------------------------------------------
  2057. void RichText::GetSettings(KeyValues *outResourceData)
  2058. {
  2059. BaseClass::GetSettings(outResourceData);
  2060. outResourceData->SetInt("maxchars", _maxCharCount);
  2061. outResourceData->SetBool("scrollbar", _vertScrollBar->IsVisible() );
  2062. if (m_pszInitialText)
  2063. {
  2064. outResourceData->SetString("text", m_pszInitialText);
  2065. }
  2066. }
  2067. //-----------------------------------------------------------------------------
  2068. // Purpose:
  2069. //-----------------------------------------------------------------------------
  2070. const char *RichText::GetDescription()
  2071. {
  2072. static char buf[1024];
  2073. Q_snprintf(buf, sizeof(buf), "%s, string text, bool scrollbar", BaseClass::GetDescription());
  2074. return buf;
  2075. }
  2076. //-----------------------------------------------------------------------------
  2077. // Purpose: Get the number of lines in the window
  2078. //-----------------------------------------------------------------------------
  2079. int RichText::GetNumLines()
  2080. {
  2081. return m_LineBreaks.Count();
  2082. }
  2083. //-----------------------------------------------------------------------------
  2084. // Purpose: Sets the height of the text entry window so all text will fit inside
  2085. //-----------------------------------------------------------------------------
  2086. void RichText::SetToFullHeight()
  2087. {
  2088. PerformLayout();
  2089. int wide, tall;
  2090. GetSize(wide, tall);
  2091. tall = GetNumLines() * (surface()->GetFontTall(_font) + _drawOffsetY) + _drawOffsetY + 2;
  2092. SetSize (wide, tall);
  2093. PerformLayout();
  2094. }
  2095. //-----------------------------------------------------------------------------
  2096. // Purpose: Select all the text.
  2097. //-----------------------------------------------------------------------------
  2098. void RichText::SelectAllText()
  2099. {
  2100. _cursorPos = 0;
  2101. _select[0] = 0;
  2102. _select[1] = m_TextStream.Count();
  2103. }
  2104. //-----------------------------------------------------------------------------
  2105. // Purpose: Select all the text.
  2106. //-----------------------------------------------------------------------------
  2107. void RichText::SelectNoText()
  2108. {
  2109. _select[0] = 0;
  2110. _select[1] = 0;
  2111. }
  2112. //-----------------------------------------------------------------------------
  2113. // Purpose:
  2114. //-----------------------------------------------------------------------------
  2115. void RichText::OnSetFocus()
  2116. {
  2117. BaseClass::OnSetFocus();
  2118. }
  2119. //-----------------------------------------------------------------------------
  2120. // Purpose: Invalidates the current linebreak stream
  2121. //-----------------------------------------------------------------------------
  2122. void RichText::InvalidateLineBreakStream()
  2123. {
  2124. // clear the buffer
  2125. m_LineBreaks.RemoveAll();
  2126. m_LineBreaks.AddToTail(MAX_BUFFER_SIZE);
  2127. _recalculateBreaksIndex = 0;
  2128. m_bRecalcLineBreaks = true;
  2129. }
  2130. //-----------------------------------------------------------------------------
  2131. // Purpose: Inserts a text string while making URLs clickable/different color
  2132. // Input : *text - string that may contain URLs to make clickable/color coded
  2133. // URLTextColor - color for URL text
  2134. // normalTextColor - color for normal text
  2135. //-----------------------------------------------------------------------------
  2136. void RichText::InsertPossibleURLString(const char* text, Color URLTextColor, Color normalTextColor)
  2137. {
  2138. InsertColorChange(normalTextColor);
  2139. // parse out the string for URL's
  2140. int len = Q_strlen(text), pos = 0;
  2141. bool clickable = false;
  2142. char *pchURLText = (char *)stackalloc( len + 1 );
  2143. char *pchURL = (char *)stackalloc( len + 1 );
  2144. while (pos < len)
  2145. {
  2146. pos = ParseTextStringForUrls( text, pos, pchURLText, len, pchURL, len, clickable );
  2147. if ( clickable )
  2148. {
  2149. InsertClickableTextStart( pchURL );
  2150. InsertColorChange( URLTextColor );
  2151. }
  2152. InsertString( pchURLText );
  2153. if ( clickable )
  2154. {
  2155. InsertColorChange(normalTextColor);
  2156. InsertClickableTextEnd();
  2157. }
  2158. }
  2159. }
  2160. //-----------------------------------------------------------------------------
  2161. // Purpose: looks for URLs in the string and returns information about the URL
  2162. //-----------------------------------------------------------------------------
  2163. int RichText::ParseTextStringForUrls( const char *text, int startPos, char *pchURLText, int cchURLText, char *pchURL, int cchURL, bool &clickable )
  2164. {
  2165. // scan for text that looks like a URL
  2166. int i = startPos;
  2167. while (text[i] != 0)
  2168. {
  2169. bool bURLFound = false;
  2170. if ( !Q_strnicmp(text + i, "<a href=", 8) )
  2171. {
  2172. if (i > startPos)
  2173. break;
  2174. // embedded link
  2175. bURLFound = true;
  2176. clickable = true;
  2177. // get the url
  2178. i += Q_strlen( "<a href=" );
  2179. const char *pchURLEnd = Q_strstr( text + i, ">" );
  2180. Q_strncpy( pchURL, text + i, MIN( pchURLEnd - text - i + 1, cchURL ) );
  2181. i += ( pchURLEnd - text - i + 1 );
  2182. // get the url text
  2183. pchURLEnd = Q_strstr( text, "</a>" );
  2184. Q_strncpy( pchURLText, text + i, MIN( pchURLEnd - text - i + 1, cchURLText ) );
  2185. i += ( pchURLEnd - text - i );
  2186. i += Q_strlen( "</a>" );
  2187. // we're done
  2188. return i;
  2189. }
  2190. else if (!Q_strnicmp(text + i, "www.", 4))
  2191. {
  2192. // scan ahead for another '.'
  2193. bool bPeriodFound = false;
  2194. for (const char *ch = text + i + 5; ch != 0; ch++)
  2195. {
  2196. if (*ch == '.')
  2197. {
  2198. bPeriodFound = true;
  2199. break;
  2200. }
  2201. }
  2202. // URL found
  2203. if (bPeriodFound)
  2204. {
  2205. bURLFound = true;
  2206. }
  2207. }
  2208. else if (!Q_strnicmp(text + i, "http://", 7))
  2209. {
  2210. bURLFound = true;
  2211. }
  2212. else if (!Q_strnicmp(text + i, "ftp://", 6))
  2213. {
  2214. bURLFound = true;
  2215. }
  2216. else if (!Q_strnicmp(text + i, "steam://", 8))
  2217. {
  2218. bURLFound = true;
  2219. }
  2220. else if (!Q_strnicmp(text + i, "steambeta://", 12))
  2221. {
  2222. bURLFound = true;
  2223. }
  2224. else if (!Q_strnicmp(text + i, "mailto:", 7))
  2225. {
  2226. bURLFound = true;
  2227. }
  2228. else if (!Q_strnicmp(text + i, "\\\\", 2))
  2229. {
  2230. bURLFound = true;
  2231. }
  2232. if (bURLFound)
  2233. {
  2234. if (i == startPos)
  2235. {
  2236. // we're at the Start of a URL, so parse that out
  2237. clickable = true;
  2238. int outIndex = 0;
  2239. while (text[i] != 0 && !V_isspace(text[i]))
  2240. {
  2241. pchURLText[outIndex++] = text[i++];
  2242. }
  2243. pchURLText[outIndex] = 0;
  2244. Q_strncpy( pchURL, pchURLText, cchURL );
  2245. return i;
  2246. }
  2247. else
  2248. {
  2249. // no url
  2250. break;
  2251. }
  2252. }
  2253. // increment and loop
  2254. i++;
  2255. }
  2256. // nothing found;
  2257. // parse out the text before the end
  2258. clickable = false;
  2259. int outIndex = 0;
  2260. int fromIndex = startPos;
  2261. while ( fromIndex < i && outIndex < cchURLText )
  2262. {
  2263. pchURLText[outIndex++] = text[fromIndex++];
  2264. }
  2265. pchURLText[outIndex] = 0;
  2266. Q_strncpy( pchURL, pchURLText, cchURL );
  2267. return i;
  2268. }
  2269. //-----------------------------------------------------------------------------
  2270. // Purpose: Opens the web browser with the text
  2271. //-----------------------------------------------------------------------------
  2272. void RichText::OnTextClicked(const wchar_t *wszText)
  2273. {
  2274. char ansi[512];
  2275. Q_UnicodeToUTF8(wszText, ansi, sizeof(ansi));
  2276. system()->ShellExecute("open", ansi);
  2277. }
  2278. //-----------------------------------------------------------------------------
  2279. // Purpose: data accessor
  2280. //-----------------------------------------------------------------------------
  2281. bool RichText::IsScrollbarVisible()
  2282. {
  2283. return _vertScrollBar->IsVisible();
  2284. }
  2285. void RichText::SetUnderlineFont( HFont font )
  2286. {
  2287. m_hFontUnderline = font;
  2288. }
  2289. bool RichText::IsAllTextAlphaZero() const
  2290. {
  2291. return m_bAllTextAlphaIsZero;
  2292. }
  2293. bool RichText::HasText() const
  2294. {
  2295. int c = m_TextStream.Count();
  2296. if ( c == 0 )
  2297. {
  2298. return false;
  2299. }
  2300. return true;
  2301. }
  2302. #ifdef DBGFLAG_VALIDATE
  2303. //-----------------------------------------------------------------------------
  2304. // Purpose: Run a global validation pass on all of our data structures and memory
  2305. // allocations.
  2306. // Input: validator - Our global validator object
  2307. // pchName - Our name (typically a member var in our container)
  2308. //-----------------------------------------------------------------------------
  2309. void RichText::Validate( CValidator &validator, char *pchName )
  2310. {
  2311. validator.Push( "vgui::RichText", this, pchName );
  2312. ValidateObj( m_TextStream );
  2313. ValidateObj( m_FormatStream );
  2314. ValidateObj( m_LineBreaks );
  2315. ValidateObj( _clickableTextPanels );
  2316. validator.ClaimMemory( m_pszInitialText );
  2317. BaseClass::Validate( validator, "vgui::RichText" );
  2318. validator.Pop();
  2319. }
  2320. #endif // DBGFLAG_VALIDATE