aboutsummaryrefslogtreecommitdiff
path: root/mp/src/vgui2/vgui_controls/RichText.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/vgui2/vgui_controls/RichText.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/vgui2/vgui_controls/RichText.cpp')
-rw-r--r--mp/src/vgui2/vgui_controls/RichText.cpp5488
1 files changed, 2744 insertions, 2744 deletions
diff --git a/mp/src/vgui2/vgui_controls/RichText.cpp b/mp/src/vgui2/vgui_controls/RichText.cpp
index c4c76682..8d28c4ee 100644
--- a/mp/src/vgui2/vgui_controls/RichText.cpp
+++ b/mp/src/vgui2/vgui_controls/RichText.cpp
@@ -1,2744 +1,2744 @@
-//========= Copyright Valve Corporation, All rights reserved. ============//
-//
-// Purpose:
-//
-// $NoKeywords: $
-//=============================================================================//
-
-#include "vgui_controls/pch_vgui_controls.h"
-#include "vgui/ILocalize.h"
-
-// memdbgon must be the last include file in a .cpp file
-#include "tier0/memdbgon.h"
-
-enum
-{
- MAX_BUFFER_SIZE = 999999, // maximum size of text buffer
- DRAW_OFFSET_X = 3,
- DRAW_OFFSET_Y = 1,
-};
-
-using namespace vgui;
-
-#ifndef max
-#define max(a,b) (((a) > (b)) ? (a) : (b))
-#endif
-
-namespace vgui
-{
-
-//#define DRAW_CLICK_PANELS
-
-//-----------------------------------------------------------------------------
-// Purpose: Panel used for clickable URL's
-//-----------------------------------------------------------------------------
-class ClickPanel : public Panel
-{
- DECLARE_CLASS_SIMPLE( ClickPanel, Panel );
-
-public:
- ClickPanel(Panel *parent)
- {
- _viewIndex = 0;
- _textIndex = 0;
- SetParent(parent);
- AddActionSignalTarget(parent);
-
- SetCursor(dc_hand);
-
- SetPaintBackgroundEnabled(false);
- SetPaintEnabled(false);
-// SetPaintAppearanceEnabled(false);
-
-#if defined( DRAW_CLICK_PANELS )
- SetPaintEnabled(true);
-#endif
- }
-
- void SetTextIndex( int linkStartIndex, int viewStartIndex )
- {
- _textIndex = linkStartIndex;
- _viewIndex = viewStartIndex;
- }
-
-#if defined( DRAW_CLICK_PANELS )
- virtual void Paint()
- {
- surface()->DrawSetColor( Color( 255, 0, 0, 255 ) );
- surface()->DrawOutlinedRect( 0, 0, GetWide(), GetTall() );
- }
-#endif
-
- int GetTextIndex()
- {
- return _textIndex;
- }
-
- int GetViewTextIndex()
- {
- return _viewIndex;
- }
-
- void OnMousePressed(MouseCode code)
- {
- if (code == MOUSE_LEFT)
- {
- PostActionSignal(new KeyValues("ClickPanel", "index", _textIndex));
- }
- else
- {
- GetParent()->OnMousePressed( code );
- }
- }
-
-private:
- int _textIndex;
- int _viewIndex;
-};
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Panel used only to draw the interior border region
-//-----------------------------------------------------------------------------
-class RichTextInterior : public Panel
-{
- DECLARE_CLASS_SIMPLE( RichTextInterior, Panel );
-
-public:
- RichTextInterior( RichText *pParent, const char *pchName ) : BaseClass( pParent, pchName )
- {
- SetKeyBoardInputEnabled( false );
- SetMouseInputEnabled( false );
- SetPaintBackgroundEnabled( false );
- SetPaintEnabled( false );
- m_pRichText = pParent;
- }
-
-/* virtual IAppearance *GetAppearance()
- {
- if ( m_pRichText->IsScrollbarVisible() )
- return m_pAppearanceScrollbar;
-
- return BaseClass::GetAppearance();
- }*/
-
- virtual void ApplySchemeSettings( IScheme *pScheme )
- {
- BaseClass::ApplySchemeSettings( pScheme );
-// m_pAppearanceScrollbar = FindSchemeAppearance( pScheme, "scrollbar_visible" );
- }
-
-private:
- RichText *m_pRichText;
-// IAppearance *m_pAppearanceScrollbar;
-};
-
-}; // namespace vgui
-
-DECLARE_BUILD_FACTORY( RichText );
-
-//-----------------------------------------------------------------------------
-// Purpose: Constructor
-//-----------------------------------------------------------------------------
-RichText::RichText(Panel *parent, const char *panelName) : BaseClass(parent, panelName)
-{
- m_bAllTextAlphaIsZero = false;
- _font = INVALID_FONT;
- m_hFontUnderline = INVALID_FONT;
-
- m_bRecalcLineBreaks = true;
- m_pszInitialText = NULL;
- _cursorPos = 0;
- _mouseSelection = false;
- _mouseDragSelection = false;
- _vertScrollBar = new ScrollBar(this, "ScrollBar", true);
- _vertScrollBar->AddActionSignalTarget(this);
- _recalcSavedRenderState = true;
- _maxCharCount = (64 * 1024);
- AddActionSignalTarget(this);
- m_pInterior = new RichTextInterior( this, NULL );
-
- //a -1 for _select[0] means that the selection is empty
- _select[0] = -1;
- _select[1] = -1;
- m_pEditMenu = NULL;
-
- SetCursor(dc_ibeam);
-
- //position the cursor so it is at the end of the text
- GotoTextEnd();
-
- // set default foreground color to black
- _defaultTextColor = Color(0, 0, 0, 0);
-
- // initialize the line break array
- InvalidateLineBreakStream();
-
- if ( IsProportional() )
- {
- int width, height;
- int sw,sh;
- surface()->GetProportionalBase( width, height );
- surface()->GetScreenSize(sw, sh);
-
- _drawOffsetX = static_cast<int>( static_cast<float>( DRAW_OFFSET_X )*( static_cast<float>( sw )/ static_cast<float>( width )));
- _drawOffsetY = static_cast<int>( static_cast<float>( DRAW_OFFSET_Y )*( static_cast<float>( sw )/ static_cast<float>( width )));
- }
- else
- {
- _drawOffsetX = DRAW_OFFSET_X;
- _drawOffsetY = DRAW_OFFSET_Y;
- }
-
- // add a basic format string
- TFormatStream stream;
- stream.color = _defaultTextColor;
- stream.fade.flFadeStartTime = 0.0f;
- stream.fade.flFadeLength = -1.0f;
- stream.pixelsIndent = 0;
- stream.textStreamIndex = 0;
- stream.textClickable = false;
- m_FormatStream.AddToTail(stream);
-
- m_bResetFades = false;
- m_bInteractive = true;
- m_bUnusedScrollbarInvis = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Destructor
-//-----------------------------------------------------------------------------
-RichText::~RichText()
-{
- delete [] m_pszInitialText;
- delete m_pEditMenu;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void RichText::SetDrawOffsets( int ofsx, int ofsy )
-{
- _drawOffsetX = ofsx;
- _drawOffsetY = ofsy;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: sets it as drawing text only - used for embedded RichText control into other text drawing situations
-//-----------------------------------------------------------------------------
-void RichText::SetDrawTextOnly()
-{
- SetDrawOffsets( 0, 0 );
- SetPaintBackgroundEnabled( false );
-// SetPaintAppearanceEnabled( false );
- SetPostChildPaintEnabled( false );
- m_pInterior->SetVisible( false );
- SetVerticalScrollbar( false );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: configures colors
-//-----------------------------------------------------------------------------
-void RichText::ApplySchemeSettings(IScheme *pScheme)
-{
- BaseClass::ApplySchemeSettings(pScheme);
-
- _font = pScheme->GetFont("Default", IsProportional() );
- m_hFontUnderline = pScheme->GetFont("DefaultUnderline", IsProportional() );
-
- SetFgColor(GetSchemeColor("RichText.TextColor", pScheme));
- SetBgColor(GetSchemeColor("RichText.BgColor", pScheme));
-
- _selectionTextColor = GetSchemeColor("RichText.SelectedTextColor", GetFgColor(), pScheme);
- _selectionColor = GetSchemeColor("RichText.SelectedBgColor", pScheme);
-
- if ( Q_strlen( pScheme->GetResourceString( "RichText.InsetX" ) ) )
- {
- SetDrawOffsets( atoi( pScheme->GetResourceString( "RichText.InsetX" ) ), atoi( pScheme->GetResourceString( "RichText.InsetY" ) ) );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: if the default format color isn't set then set it
-//-----------------------------------------------------------------------------
-void RichText::SetFgColor( Color color )
-{
- // Replace default format color if
- // the stream is empty and the color is the default ( or the previous FgColor )
- if ( m_FormatStream.Size() == 1 &&
- ( m_FormatStream[0].color == _defaultTextColor || m_FormatStream[0].color == GetFgColor() ) )
- {
- m_FormatStream[0].color = color;
- }
-
- BaseClass::SetFgColor( color );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sends a message if the data has changed
-// Turns off any selected text in the window if we are not using the edit menu
-//-----------------------------------------------------------------------------
-void RichText::OnKillFocus()
-{
- // check if we clicked the right mouse button or if it is down
- bool mouseRightClicked = input()->WasMousePressed(MOUSE_RIGHT);
- bool mouseRightUp = input()->WasMouseReleased(MOUSE_RIGHT);
- bool mouseRightDown = input()->IsMouseDown(MOUSE_RIGHT);
-
- if (mouseRightClicked || mouseRightDown || mouseRightUp )
- {
- // get the start and ends of the selection area
- int start, end;
- if (GetSelectedRange(start, end)) // we have selected text
- {
- // see if we clicked in the selection area
- int startX, startY;
- CursorToPixelSpace(start, startX, startY);
- int endX, endY;
- CursorToPixelSpace(end, endX, endY);
- int cursorX, cursorY;
- input()->GetCursorPos(cursorX, cursorY);
- ScreenToLocal(cursorX, cursorY);
-
- // check the area vertically
- // we need to handle the horizontal edge cases eventually
- int fontTall = GetLineHeight();
- endY = endY + fontTall;
- if ((startY < cursorY) && (endY > cursorY))
- {
- // if we clicked in the selection area, leave the text highlighted
- return;
- }
- }
- }
-
- // clear any selection
- SelectNone();
-
- // chain
- BaseClass::OnKillFocus();
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Wipe line breaks after the size of a panel has been changed
-//-----------------------------------------------------------------------------
-void RichText::OnSizeChanged( int wide, int tall )
-{
- BaseClass::OnSizeChanged( wide, tall );
-
- // blow away the line breaks list
- _invalidateVerticalScrollbarSlider = true;
- InvalidateLineBreakStream();
- InvalidateLayout();
-
- if ( _vertScrollBar->IsVisible() )
- {
- _vertScrollBar->MakeReadyForUse();
- m_pInterior->SetBounds( 0, 0, wide - _vertScrollBar->GetWide(), tall );
- }
- else
- {
- m_pInterior->SetBounds( 0, 0, wide, tall );
- }
-}
-
-
-const wchar_t *RichText::ResolveLocalizedTextAndVariables( char const *pchLookup, wchar_t *outbuf, size_t outbufsizeinbytes )
-{
- if ( pchLookup[ 0 ] == '#' )
- {
- // try lookup in localization tables
- StringIndex_t index = g_pVGuiLocalize->FindIndex( pchLookup + 1 );
- if ( index == INVALID_LOCALIZE_STRING_INDEX )
- {
-/* // if it's not found, maybe it's a special expanded variable - look for an expansion
- char rgchT[MAX_PATH];
-
- // get the variables
- KeyValues *variables = GetDialogVariables_R();
- if ( variables )
- {
- // see if any are any special vars to put in
- for ( KeyValues *pkv = variables->GetFirstSubKey(); pkv != NULL; pkv = pkv->GetNextKey() )
- {
- if ( !Q_strncmp( pkv->GetName(), "$", 1 ) )
- {
- // make a new lookup, with this key appended
- Q_snprintf( rgchT, sizeof( rgchT ), "%s%s=%s", pchLookup, pkv->GetName(), pkv->GetString() );
- index = localize()->FindIndex( rgchT );
- break;
- }
- }
- }
- */
- }
-
- // see if we have a valid string
- if ( index != INVALID_LOCALIZE_STRING_INDEX )
- {
- wchar_t *format = g_pVGuiLocalize->GetValueByIndex( index );
- Assert( format );
- if ( format )
- {
- /*// Try and substitute variables if any
- KeyValues *variables = GetDialogVariables_R();
- if ( variables )
- {
- localize()->ConstructString( outbuf, outbufsizeinbytes, index, variables );
- return outbuf;
- }*/
- }
- V_wcsncpy( outbuf, format, outbufsizeinbytes );
- return outbuf;
- }
- }
-
- Q_UTF8ToUnicode( pchLookup, outbuf, outbufsizeinbytes );
- return outbuf;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set the text array
-// Using this function will cause all lineBreaks to be discarded.
-// This is because this fxn replaces the contents of the text buffer.
-// For modifying large buffers use insert functions.
-//-----------------------------------------------------------------------------
-void RichText::SetText(const char *text)
-{
- if (!text)
- {
- text = "";
- }
-
- wchar_t unicode[1024];
-
- if (text[0] == '#')
- {
- ResolveLocalizedTextAndVariables( text, unicode, sizeof( unicode ) );
- SetText( unicode );
- return;
- }
-
- // convert to unicode
- Q_UTF8ToUnicode(text, unicode, sizeof(unicode));
- SetText(unicode);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void RichText::SetText(const wchar_t *text)
-{
- // reset the formatting stream
- m_FormatStream.RemoveAll();
- TFormatStream stream;
- stream.color = GetFgColor();
- stream.fade.flFadeLength = -1.0f;
- stream.fade.flFadeStartTime = 0.0f;
- stream.pixelsIndent = 0;
- stream.textStreamIndex = 0;
- stream.textClickable = false;
- m_FormatStream.AddToTail(stream);
-
- // set the new text stream
- m_TextStream.RemoveAll();
- if ( text && *text )
- {
- int textLen = wcslen(text) + 1;
- m_TextStream.EnsureCapacity(textLen);
- for(int i = 0; i < textLen; i++)
- {
- m_TextStream.AddToTail(text[i]);
- }
- }
- GotoTextStart();
- SelectNone();
-
- // blow away the line breaks list
- InvalidateLineBreakStream();
- InvalidateLayout();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Given cursor's position in the text buffer, convert it to
-// the local window's x and y pixel coordinates
-// Input: cursorPos: cursor index
-// Output: cx, cy, the corresponding coords in the local window
-//-----------------------------------------------------------------------------
-void RichText::CursorToPixelSpace(int cursorPos, int &cx, int &cy)
-{
- int yStart = _drawOffsetY;
- int x = _drawOffsetX, y = yStart;
- _pixelsIndent = 0;
- int lineBreakIndexIndex = 0;
-
- for (int i = GetStartDrawIndex(lineBreakIndexIndex); i < m_TextStream.Count(); i++)
- {
- wchar_t ch = m_TextStream[i];
-
- // if we've found the position, break
- if (cursorPos == i)
- {
- // if we've passed a line break go to that
- if (m_LineBreaks[lineBreakIndexIndex] == i)
- {
- // add another line
- AddAnotherLine(x, y);
- lineBreakIndexIndex++;
- }
- break;
- }
-
- // if we've passed a line break go to that
- if (m_LineBreaks[lineBreakIndexIndex] == i)
- {
- // add another line
- AddAnotherLine(x, y);
- lineBreakIndexIndex++;
- }
-
- // add to the current position
- x += surface()->GetCharacterWidth(_font, ch);
- }
-
- cx = x;
- cy = y;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Converts local pixel coordinates to an index in the text buffer
-//-----------------------------------------------------------------------------
-int RichText::PixelToCursorSpace(int cx, int cy)
-{
- int fontTall = GetLineHeight();
-
- // where to start reading
- int yStart = _drawOffsetY;
- int x = _drawOffsetX, y = yStart;
- _pixelsIndent = 0;
- int lineBreakIndexIndex = 0;
-
- int startIndex = GetStartDrawIndex(lineBreakIndexIndex);
- if (_recalcSavedRenderState)
- {
- RecalculateDefaultState(startIndex);
- }
-
- _pixelsIndent = m_CachedRenderState.pixelsIndent;
- _currentTextClickable = m_CachedRenderState.textClickable;
- TRenderState renderState = m_CachedRenderState;
-
- bool onRightLine = false;
- int i;
- for (i = startIndex; i < m_TextStream.Count(); i++)
- {
- wchar_t ch = m_TextStream[i];
-
- renderState.x = x;
- if ( UpdateRenderState( i, renderState ) )
- {
- x = renderState.x;
- }
-
- // if we are on the right line but off the end of if put the cursor at the end of the line
- if (m_LineBreaks[lineBreakIndexIndex] == i)
- {
- // add another line
- AddAnotherLine(x, y);
- lineBreakIndexIndex++;
-
- if (onRightLine)
- break;
- }
-
- // check to see if we're on the right line
- if (cy < yStart)
- {
- // cursor is above panel
- onRightLine = true;
- }
- else if (cy >= y && (cy < (y + fontTall + _drawOffsetY)))
- {
- onRightLine = true;
- }
-
- int wide = surface()->GetCharacterWidth(_font, ch);
-
- // if we've found the position, break
- if (onRightLine)
- {
- if (cx > GetWide()) // off right side of window
- {
- }
- else if (cx < (_drawOffsetX + renderState.pixelsIndent) || cy < yStart) // off left side of window
- {
- // Msg( "PixelToCursorSpace() off left size, returning %d '%c'\n", i, m_TextStream[i] );
- return i; // move cursor one to left
- }
-
- if (cx >= x && cx < (x + wide))
- {
- // check which side of the letter they're on
- if (cx < (x + (wide * 0.5))) // left side
- {
- // Msg( "PixelToCursorSpace() on the left size, returning %d '%c'\n", i, m_TextStream[i] );
- return i;
- }
- else // right side
- {
- // Msg( "PixelToCursorSpace() on the right size, returning %d '%c'\n", i + 1, m_TextStream[i + 1] );
- return i + 1;
- }
- }
- }
- x += wide;
- }
-
- // Msg( "PixelToCursorSpace() never hit, returning %d\n", i );
- return i;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Draws a string of characters in the panel
-// Input: iFirst - Index of the first character to draw
-// iLast - Index of the last character to draw
-// renderState - Render state to use
-// font- font to use
-// Output: returns the width of the character drawn
-//-----------------------------------------------------------------------------
-int RichText::DrawString(int iFirst, int iLast, TRenderState &renderState, HFont font)
-{
-// VPROF( "RichText::DrawString" );
-
- // Calculate the render size
- int fontTall = surface()->GetFontTall(font);
- // BUGBUG John: This won't exactly match the rendered size
- int charWide = 0;
- for ( int i = iFirst; i <= iLast; i++ )
- {
- wchar_t ch = m_TextStream[i];
-#if USE_GETKERNEDCHARWIDTH
- wchar_t chBefore = 0;
- wchar_t chAfter = 0;
- if ( i > 0 )
- chBefore = m_TextStream[i-1];
- if ( i < iLast )
- chAfter = m_TextStream[i+1];
- float flWide = 0.0f, flabcA = 0.0f;
- surface()->GetKernedCharWidth(font, ch, chBefore, chAfter, flWide, flabcA);
- if ( ch == L' ' )
- flWide = ceil( flWide );
- charWide += floor( flWide + 0.6 );
-#else
- charWide += surface()->GetCharacterWidth(font, ch);
-#endif
- }
-
- // draw selection, if any
- int selection0 = -1, selection1 = -1;
- GetSelectedRange(selection0, selection1);
-
- if (iFirst >= selection0 && iFirst < selection1)
- {
- // draw background selection color
- surface()->DrawSetColor(_selectionColor);
- surface()->DrawFilledRect(renderState.x, renderState.y, renderState.x + charWide, renderState.y + 1 + fontTall);
-
- // reset text color
- surface()->DrawSetTextColor(_selectionTextColor);
- m_bAllTextAlphaIsZero = false;
- }
- else
- {
- surface()->DrawSetTextColor(renderState.textColor);
- }
-
- if ( renderState.textColor.a() != 0 )
- {
- m_bAllTextAlphaIsZero = false;
- surface()->DrawSetTextPos(renderState.x, renderState.y);
- surface()->DrawPrintText(&m_TextStream[iFirst], iLast - iFirst + 1);
- }
-
- return charWide;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Finish drawing url
-//-----------------------------------------------------------------------------
-void RichText::FinishingURL(int x, int y)
-{
- // finishing URL
- if ( _clickableTextPanels.IsValidIndex( _clickableTextIndex ) )
- {
- ClickPanel *clickPanel = _clickableTextPanels[ _clickableTextIndex ];
- int px, py;
- clickPanel->GetPos(px, py);
- int fontTall = GetLineHeight();
- clickPanel->SetSize( MAX( x - px, 6 ), y - py + fontTall );
- clickPanel->SetVisible(true);
-
- // if we haven't actually advanced any, step back and ignore this one
- // this is probably a data input problem though, need to find root cause
- if ( x - px <= 0 )
- {
- --_clickableTextIndex;
- clickPanel->SetVisible(false);
- }
- }
-}
-
-void RichText::CalculateFade( TRenderState &renderState )
-{
- if ( m_FormatStream.IsValidIndex( renderState.formatStreamIndex ) )
- {
- if ( m_bResetFades == false )
- {
- if ( m_FormatStream[renderState.formatStreamIndex].fade.flFadeLength != -1.0f )
- {
- float frac = ( m_FormatStream[renderState.formatStreamIndex].fade.flFadeStartTime - system()->GetCurrentTime() ) / m_FormatStream[renderState.formatStreamIndex].fade.flFadeLength;
-
- int alpha = frac * m_FormatStream[renderState.formatStreamIndex].fade.iOriginalAlpha;
- alpha = clamp( alpha, 0, m_FormatStream[renderState.formatStreamIndex].fade.iOriginalAlpha );
-
- renderState.textColor.SetColor( renderState.textColor.r(), renderState.textColor.g(), renderState.textColor.b(), alpha );
- }
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Draws the text in the panel
-//-----------------------------------------------------------------------------
-void RichText::Paint()
-{
- // Assume the worst
- m_bAllTextAlphaIsZero = true;
-
- HFont hFontCurrent = _font;
-
- // hide all the clickable panels until we know where they are to reside
- for (int j = 0; j < _clickableTextPanels.Count(); j++)
- {
- _clickableTextPanels[j]->SetVisible(false);
- }
-
- if ( !HasText() )
- return;
-
- int wide, tall;
- GetSize( wide, tall );
-
- int lineBreakIndexIndex = 0;
- int startIndex = GetStartDrawIndex(lineBreakIndexIndex);
- _currentTextClickable = false;
-
- _clickableTextIndex = GetClickableTextIndexStart(startIndex);
-
- // recalculate and cache the render state at the render start
- if (_recalcSavedRenderState)
- {
- RecalculateDefaultState(startIndex);
- }
- // copy off the cached render state
- TRenderState renderState = m_CachedRenderState;
-
- _pixelsIndent = m_CachedRenderState.pixelsIndent;
- _currentTextClickable = m_CachedRenderState.textClickable;
-
- renderState.textClickable = _currentTextClickable;
- renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color;
- CalculateFade( renderState );
-
- renderState.formatStreamIndex++;
-
- if ( _currentTextClickable )
- {
- _clickableTextIndex = startIndex;
- }
-
- // where to start drawing
- renderState.x = _drawOffsetX + _pixelsIndent;
- renderState.y = _drawOffsetY;
-
- // draw the text
- int selection0 = -1, selection1 = -1;
- GetSelectedRange(selection0, selection1);
-
- surface()->DrawSetTextFont( hFontCurrent );
-
- for (int i = startIndex; i < m_TextStream.Count() && renderState.y < tall; )
- {
- // 1.
- // Update our current render state based on the formatting and color streams,
- // this has to happen if it's our very first iteration, or if we are actually changing
- // state.
- int nXBeforeStateChange = renderState.x;
- if ( UpdateRenderState(i, renderState) || i == startIndex )
- {
- // check for url state change
- if (renderState.textClickable != _currentTextClickable)
- {
- if (renderState.textClickable)
- {
- // entering new URL
- _clickableTextIndex++;
- hFontCurrent = m_hFontUnderline;
- surface()->DrawSetTextFont( hFontCurrent );
-
- // set up the panel
- ClickPanel *clickPanel = _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ? _clickableTextPanels[_clickableTextIndex] : NULL;
-
- if (clickPanel)
- {
- clickPanel->SetPos(renderState.x, renderState.y);
- }
- }
- else
- {
- FinishingURL(nXBeforeStateChange, renderState.y);
- hFontCurrent = _font;
- surface()->DrawSetTextFont( hFontCurrent );
- }
- _currentTextClickable = renderState.textClickable;
- }
- }
-
- // 2.
- // if we've passed a line break go to that
- if ( m_LineBreaks.IsValidIndex( lineBreakIndexIndex ) && m_LineBreaks[lineBreakIndexIndex] <= i )
- {
- if (_currentTextClickable)
- {
- FinishingURL(renderState.x, renderState.y);
- }
-
- // add another line
- AddAnotherLine(renderState.x, renderState.y);
- lineBreakIndexIndex++;
-
- // Skip white space unless the previous line ended from the hard carriage return
- if ( i && ( m_TextStream[i-1] != '\n' ) && ( m_TextStream[i-1] != '\r') )
- {
- while ( m_TextStream[i] == L' ' )
- {
- if ( i+1 < m_TextStream.Count() )
- ++i;
- else
- break;
- }
- }
-
- if (renderState.textClickable)
- {
- // move to the next URL
- _clickableTextIndex++;
- ClickPanel *clickPanel = _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ? _clickableTextPanels[_clickableTextIndex] : NULL;
- if (clickPanel)
- {
- clickPanel->SetPos(renderState.x, renderState.y);
- }
- }
- }
-
- // 3.
- // Calculate the range of text to draw all at once
- int iLim = m_TextStream.Count();
-
- // Stop at the next format change
- if ( m_FormatStream.IsValidIndex(renderState.formatStreamIndex) &&
- m_FormatStream[renderState.formatStreamIndex].textStreamIndex < iLim &&
- m_FormatStream[renderState.formatStreamIndex].textStreamIndex >= i &&
- m_FormatStream[renderState.formatStreamIndex].textStreamIndex )
- {
- iLim = m_FormatStream[renderState.formatStreamIndex].textStreamIndex;
- }
-
- // Stop at the next line break
- if ( m_LineBreaks.IsValidIndex( lineBreakIndexIndex ) && m_LineBreaks[lineBreakIndexIndex] < iLim )
- iLim = m_LineBreaks[lineBreakIndexIndex];
-
- // Handle non-drawing characters specially
- for ( int iT = i; iT < iLim; iT++ )
- {
- if ( iswcntrl(m_TextStream[iT]) )
- {
- iLim = iT;
- break;
- }
- }
-
- // 4.
- // Draw the current text range
- if ( iLim <= i )
- {
- if ( m_TextStream[i] == '\t' )
- {
- int dxTabWidth = 8 * surface()->GetCharacterWidth(hFontCurrent, ' ');
- dxTabWidth = MAX( 1, dxTabWidth );
-
- renderState.x = ( dxTabWidth * ( 1 + ( renderState.x / dxTabWidth ) ) );
- }
- i++;
- }
- else
- {
- renderState.x += DrawString(i, iLim - 1, renderState, hFontCurrent );
- i = iLim;
- }
- }
-
- if (renderState.textClickable)
- {
- FinishingURL(renderState.x, renderState.y);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-int RichText::GetClickableTextIndexStart(int startIndex)
-{
- // cycle to the right url panel for what is visible after the startIndex.
- for (int i = 0; i < _clickableTextPanels.Count(); i++)
- {
- if (_clickableTextPanels[i]->GetViewTextIndex() >= startIndex)
- {
- return i - 1;
- }
- }
- return -1;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Recalcultes the formatting state from the specified index
-//-----------------------------------------------------------------------------
-void RichText::RecalculateDefaultState(int startIndex)
-{
- if (!HasText() )
- return;
-
- Assert(startIndex < m_TextStream.Count());
-
- m_CachedRenderState.textColor = GetFgColor();
- _pixelsIndent = 0;
- _currentTextClickable = false;
- _clickableTextIndex = GetClickableTextIndexStart(startIndex);
-
- // find where in the formatting stream we need to be
- GenerateRenderStateForTextStreamIndex(startIndex, m_CachedRenderState);
- _recalcSavedRenderState = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: updates a render state based on the formatting and color streams
-// Output: true if we changed the render state
-//-----------------------------------------------------------------------------
-bool RichText::UpdateRenderState(int textStreamPos, TRenderState &renderState)
-{
- // check the color stream
- if (m_FormatStream.IsValidIndex(renderState.formatStreamIndex) &&
- m_FormatStream[renderState.formatStreamIndex].textStreamIndex == textStreamPos)
- {
- // set the current formatting
- renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color;
- renderState.textClickable = m_FormatStream[renderState.formatStreamIndex].textClickable;
-
- CalculateFade( renderState );
-
- int indentChange = m_FormatStream[renderState.formatStreamIndex].pixelsIndent - renderState.pixelsIndent;
- renderState.pixelsIndent = m_FormatStream[renderState.formatStreamIndex].pixelsIndent;
-
- if (indentChange)
- {
- renderState.x = renderState.pixelsIndent + _drawOffsetX;
- }
-
- //!! for supporting old functionality, store off state in globals
- _pixelsIndent = renderState.pixelsIndent;
-
- // move to the next position in the color stream
- renderState.formatStreamIndex++;
- return true;
- }
-
- return false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns the index in the format stream for the specified text stream index
-//-----------------------------------------------------------------------------
-int RichText::FindFormatStreamIndexForTextStreamPos(int textStreamIndex)
-{
- int formatStreamIndex = 0;
- for (; m_FormatStream.IsValidIndex(formatStreamIndex); formatStreamIndex++)
- {
- if (m_FormatStream[formatStreamIndex].textStreamIndex > textStreamIndex)
- break;
- }
-
- // step back to the color change before the new line
- formatStreamIndex--;
- if (!m_FormatStream.IsValidIndex(formatStreamIndex))
- {
- formatStreamIndex = 0;
- }
- return formatStreamIndex;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Generates a base renderstate given a index into the text stream
-//-----------------------------------------------------------------------------
-void RichText::GenerateRenderStateForTextStreamIndex(int textStreamIndex, TRenderState &renderState)
-{
- // find where in the format stream we need to be given the specified place in the text stream
- renderState.formatStreamIndex = FindFormatStreamIndexForTextStreamPos(textStreamIndex);
-
- // copy the state data
- renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color;
- renderState.pixelsIndent = m_FormatStream[renderState.formatStreamIndex].pixelsIndent;
- renderState.textClickable = m_FormatStream[renderState.formatStreamIndex].textClickable;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called pre render
-//-----------------------------------------------------------------------------
-void RichText::OnThink()
-{
- if (m_bRecalcLineBreaks)
- {
- _recalcSavedRenderState = true;
- RecalculateLineBreaks();
-
- // recalculate scrollbar position
- if (_invalidateVerticalScrollbarSlider)
- {
- LayoutVerticalScrollBarSlider();
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called when data changes or panel size changes
-//-----------------------------------------------------------------------------
-void RichText::PerformLayout()
-{
- BaseClass::PerformLayout();
-
- // force a Repaint
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: inserts a color change into the formatting stream
-//-----------------------------------------------------------------------------
-void RichText::InsertColorChange(Color col)
-{
- // see if color already exists in text stream
- TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
- if (prevItem.color == col)
- {
- // inserting same color into stream, just ignore
- }
- else if (prevItem.textStreamIndex == m_TextStream.Count())
- {
- // this item is in the same place; update values
- prevItem.color = col;
- }
- else
- {
- // add to text stream, based off existing item
- TFormatStream streamItem = prevItem;
- streamItem.color = col;
- streamItem.textStreamIndex = m_TextStream.Count();
- m_FormatStream.AddToTail(streamItem);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: inserts a fade into the formatting stream
-//-----------------------------------------------------------------------------
-void RichText::InsertFade( float flSustain, float flLength )
-{
- // see if color already exists in text stream
- TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
- if (prevItem.textStreamIndex == m_TextStream.Count())
- {
- // this item is in the same place; update values
- prevItem.fade.flFadeStartTime = system()->GetCurrentTime() + flSustain;
- prevItem.fade.flFadeSustain = flSustain;
- prevItem.fade.flFadeLength = flLength;
- prevItem.fade.iOriginalAlpha = prevItem.color.a();
- }
- else
- {
- // add to text stream, based off existing item
- TFormatStream streamItem = prevItem;
-
- prevItem.fade.flFadeStartTime = system()->GetCurrentTime() + flSustain;
- prevItem.fade.flFadeLength = flLength;
- prevItem.fade.flFadeSustain = flSustain;
- prevItem.fade.iOriginalAlpha = prevItem.color.a();
-
- streamItem.textStreamIndex = m_TextStream.Count();
- m_FormatStream.AddToTail(streamItem);
- }
-}
-
-void RichText::ResetAllFades( bool bHold, bool bOnlyExpired, float flNewSustain )
-{
- m_bResetFades = bHold;
-
- if ( m_bResetFades == false )
- {
- for (int i = 1; i < m_FormatStream.Count(); i++)
- {
- if ( bOnlyExpired == true )
- {
- if ( m_FormatStream[i].fade.flFadeStartTime >= system()->GetCurrentTime() )
- continue;
- }
-
- if ( flNewSustain == -1.0f )
- {
- flNewSustain = m_FormatStream[i].fade.flFadeSustain;
- }
-
- m_FormatStream[i].fade.flFadeStartTime = system()->GetCurrentTime() + flNewSustain;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: inserts an indent change into the formatting stream
-//-----------------------------------------------------------------------------
-void RichText::InsertIndentChange(int pixelsIndent)
-{
- if (pixelsIndent < 0)
- {
- pixelsIndent = 0;
- }
- else if (pixelsIndent > 255)
- {
- pixelsIndent = 255;
- }
-
- // see if indent change already exists in text stream
- TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
- if (prevItem.pixelsIndent == pixelsIndent)
- {
- // inserting same indent into stream, just ignore
- }
- else if (prevItem.textStreamIndex == m_TextStream.Count())
- {
- // this item is in the same place; update
- prevItem.pixelsIndent = pixelsIndent;
- }
- else
- {
- // add to text stream, based off existing item
- TFormatStream streamItem = prevItem;
- streamItem.pixelsIndent = pixelsIndent;
- streamItem.textStreamIndex = m_TextStream.Count();
- m_FormatStream.AddToTail(streamItem);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Inserts character Start for clickable text, eg. URLS
-//-----------------------------------------------------------------------------
-void RichText::InsertClickableTextStart( const char *pchClickAction )
-{
- // see if indent change already exists in text stream
- TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
- TFormatStream *pFormatStream = &prevItem;
- if (prevItem.textStreamIndex == m_TextStream.Count())
- {
- // this item is in the same place; update
- prevItem.textClickable = true;
- pFormatStream->m_sClickableTextAction = pchClickAction;
- }
- else
- {
- // add to text stream, based off existing item
- TFormatStream formatStreamCopy = prevItem;
- int iFormatStream = m_FormatStream.AddToTail( formatStreamCopy );
-
- // set the new params
- pFormatStream = &m_FormatStream[iFormatStream];
- pFormatStream->textStreamIndex = m_TextStream.Count();
- pFormatStream->textClickable = true;
- pFormatStream->m_sClickableTextAction = pchClickAction;
- }
-
- // invalidate the layout to recalculate where the click panels should go
- InvalidateLineBreakStream();
- InvalidateLayout();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Inserts character end for clickable text, eg. URLS
-//-----------------------------------------------------------------------------
-void RichText::InsertClickableTextEnd()
-{
- // see if indent change already exists in text stream
- TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
- if (!prevItem.textClickable)
- {
- // inserting same indent into stream, just ignore
- }
- else if (prevItem.textStreamIndex == m_TextStream.Count())
- {
- // this item is in the same place; update
- prevItem.textClickable = false;
- }
- else
- {
- // add to text stream, based off existing item
- TFormatStream streamItem = prevItem;
- streamItem.textClickable = false;
- streamItem.textStreamIndex = m_TextStream.Count();
- m_FormatStream.AddToTail(streamItem);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: moves x,y to the Start of the next line of text
-//-----------------------------------------------------------------------------
-void RichText::AddAnotherLine(int &cx, int &cy)
-{
- cx = _drawOffsetX + _pixelsIndent;
- cy += (GetLineHeight() + _drawOffsetY);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Recalculates line breaks
-//-----------------------------------------------------------------------------
-void RichText::RecalculateLineBreaks()
-{
- if ( !m_bRecalcLineBreaks )
- return;
-
- int wide = GetWide();
- if (!wide)
- return;
-
- wide -= _drawOffsetX;
-
- m_bRecalcLineBreaks = false;
- _recalcSavedRenderState = true;
- if (!HasText())
- return;
-
- int selection0 = -1, selection1 = -1;
-
- // subtract the scrollbar width
- if (_vertScrollBar->IsVisible())
- {
- wide -= _vertScrollBar->GetWide();
- }
-
- int x = _drawOffsetX, y = _drawOffsetY;
-
- HFont fontWordStart = INVALID_FONT;
- int wordStartIndex = 0;
- int lineStartIndex = 0;
- bool hasWord = false;
- bool justStartedNewLine = true;
- bool wordStartedOnNewLine = true;
-
- int startChar = 0;
- if (_recalculateBreaksIndex <= 0)
- {
- m_LineBreaks.RemoveAll();
- }
- else
- {
- // remove the rest of the linebreaks list since its out of date.
- for (int i = _recalculateBreaksIndex + 1; i < m_LineBreaks.Count(); ++i)
- {
- m_LineBreaks.Remove(i);
- --i; // removing shrinks the list!
- }
- startChar = m_LineBreaks[_recalculateBreaksIndex];
- lineStartIndex = m_LineBreaks[_recalculateBreaksIndex];
- wordStartIndex = lineStartIndex;
- }
-
- // handle the case where this char is a new line, in that case
- // we have already taken its break index into account above so skip it.
- if (m_TextStream[startChar] == '\r' || m_TextStream[startChar] == '\n')
- {
- startChar++;
- lineStartIndex = startChar;
- }
-
- // cycle to the right url panel for what is visible after the startIndex.
- int clickableTextNum = GetClickableTextIndexStart(startChar);
- clickableTextNum++;
-
- // initialize the renderstate with the start
- TRenderState renderState;
- GenerateRenderStateForTextStreamIndex(startChar, renderState);
- _currentTextClickable = false;
-
- HFont font = _font;
-
- bool bForceBreak = false;
- float flLineWidthSoFar = 0;
-
- // loop through all the characters
- for (int i = startChar; i < m_TextStream.Count(); ++i)
- {
- wchar_t ch = m_TextStream[i];
- renderState.x = x;
- if (UpdateRenderState(i, renderState))
- {
- x = renderState.x;
- int preI = i;
-
- // check for clickable text
- if (renderState.textClickable != _currentTextClickable)
- {
- if (renderState.textClickable)
- {
- // make a new clickable text panel
- if (clickableTextNum >= _clickableTextPanels.Count())
- {
- _clickableTextPanels.AddToTail(new ClickPanel(this));
- }
-
- ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++];
- clickPanel->SetTextIndex(preI, preI);
- }
-
- // url state change
- _currentTextClickable = renderState.textClickable;
- }
- }
-
- bool bIsWSpace = iswspace( ch ) ? true : false;
-
- bool bPreviousWordStartedOnNewLine = wordStartedOnNewLine;
- int iPreviousWordStartIndex = wordStartIndex;
- if ( !bIsWSpace && ch != L'\t' && ch != L'\n' && ch != L'\r' )
- {
- if (!hasWord)
- {
- // Start a new word
- wordStartIndex = i;
- hasWord = true;
- wordStartedOnNewLine = justStartedNewLine;
- fontWordStart = font;
- }
- // else append to the current word
- }
- else
- {
- // whitespace/punctuation character
- // end the word
- hasWord = false;
- }
-
- float w = 0;
- wchar_t wchBefore = 0;
- wchar_t wchAfter = 0;
-
- if ( i > 0 && i > lineStartIndex && i != selection0 && i-1 != selection1 )
- wchBefore = m_TextStream[i-1];
- if ( i < m_TextStream.Count() - 1 && i+1 != selection0 && i != selection1 )
- wchAfter = m_TextStream[i+1];
-
- float flabcA;
- surface()->GetKernedCharWidth( font, ch, wchBefore, wchAfter, w, flabcA );
- flLineWidthSoFar += w;
-
- // See if we've exceeded the width we have available, with
- if ( floor(flLineWidthSoFar + 0.6) + x > wide )
- {
- bForceBreak = true;
- }
-
- if (!iswcntrl(ch))
- {
- justStartedNewLine = false;
- }
-
- if ( bForceBreak || ch == '\r' || ch == '\n' )
- {
- bForceBreak = false;
- // add another line
- AddAnotherLine(x, y);
-
- if ( ch == '\r' || ch == '\n' )
- {
- // skip the newline so it's not at the beginning of the new line
- lineStartIndex = i + 1;
- m_LineBreaks.AddToTail(i + 1);
- }
- else if ( bPreviousWordStartedOnNewLine || iPreviousWordStartIndex <= lineStartIndex )
- {
- lineStartIndex = i;
- m_LineBreaks.AddToTail( i );
-
- if (renderState.textClickable)
- {
- // need to split the url into two panels
- int oldIndex = _clickableTextPanels[clickableTextNum - 1]->GetTextIndex();
-
- // make a new clickable text panel
- if (clickableTextNum >= _clickableTextPanels.Count())
- {
- _clickableTextPanels.AddToTail(new ClickPanel(this));
- }
-
- ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++];
- clickPanel->SetTextIndex(oldIndex, i);
- }
- }
- else
- {
- m_LineBreaks.AddToTail( iPreviousWordStartIndex );
- lineStartIndex = iPreviousWordStartIndex;
- i = iPreviousWordStartIndex;
-
- TRenderState renderStateAtLastWord;
- GenerateRenderStateForTextStreamIndex( i, renderStateAtLastWord );
-
- // If the word is clickable, and that started prior to the beginning of the word, then we must split the click panel
- if ( renderStateAtLastWord.textClickable && m_FormatStream[ renderStateAtLastWord.formatStreamIndex ].textStreamIndex < i )
- {
- // need to split the url into two panels
- int oldIndex = _clickableTextPanels[clickableTextNum - 1]->GetTextIndex();
-
- // make a new clickable text panel
- if (clickableTextNum >= _clickableTextPanels.Count())
- {
- _clickableTextPanels.AddToTail(new ClickPanel(this));
- }
-
- ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++];
- clickPanel->SetTextIndex(oldIndex, i);
- }
- }
-
- flLineWidthSoFar = 0;
- justStartedNewLine = true;
- hasWord = false;
- wordStartedOnNewLine = false;
- _currentTextClickable = false;
- continue;
- }
- }
-
- // end the list
- m_LineBreaks.AddToTail(MAX_BUFFER_SIZE);
-
- // set up the scrollbar
- _invalidateVerticalScrollbarSlider = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Recalculate where the vertical scroll bar slider should be
-// based on the current cursor line we are on.
-//-----------------------------------------------------------------------------
-void RichText::LayoutVerticalScrollBarSlider()
-{
- _invalidateVerticalScrollbarSlider = false;
-
- // set up the scrollbar
- //if (!_vertScrollBar->IsVisible())
- // return;
-
-
- // see where the scrollbar currently is
- int previousValue = _vertScrollBar->GetValue();
- bool bCurrentlyAtEnd = false;
- int rmin, rmax;
- _vertScrollBar->GetRange(rmin, rmax);
- if (rmax && (previousValue + rmin + _vertScrollBar->GetRangeWindow() == rmax))
- {
- bCurrentlyAtEnd = true;
- }
-
- // work out position to put scrollbar, factoring in insets
- int wide, tall;
- GetSize( wide, tall );
-
- _vertScrollBar->SetPos( wide - _vertScrollBar->GetWide(), 0 );
- // scrollbar is inside the borders.
- _vertScrollBar->SetSize( _vertScrollBar->GetWide(), tall );
-
- // calculate how many lines we can fully display
- int displayLines = tall / (GetLineHeight() + _drawOffsetY);
- int numLines = m_LineBreaks.Count();
-
- if (numLines <= displayLines)
- {
- // disable the scrollbar
- _vertScrollBar->SetEnabled(false);
- _vertScrollBar->SetRange(0, numLines);
- _vertScrollBar->SetRangeWindow(numLines);
- _vertScrollBar->SetValue(0);
-
- if ( m_bUnusedScrollbarInvis )
- {
- SetVerticalScrollbar( false );
- }
- }
- else
- {
- if ( m_bUnusedScrollbarInvis )
- {
- SetVerticalScrollbar( true );
- }
-
- // set the scrollbars range
- _vertScrollBar->SetRange(0, numLines);
- _vertScrollBar->SetRangeWindow(displayLines);
- _vertScrollBar->SetEnabled(true);
-
- // this should make it scroll one line at a time
- _vertScrollBar->SetButtonPressedScrollValue(1);
- if (bCurrentlyAtEnd)
- {
- _vertScrollBar->SetValue(numLines - displayLines);
- }
- _vertScrollBar->InvalidateLayout();
- _vertScrollBar->Repaint();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets whether a vertical scrollbar is visible
-//-----------------------------------------------------------------------------
-void RichText::SetVerticalScrollbar(bool state)
-{
- if (_vertScrollBar->IsVisible() != state)
- {
- _vertScrollBar->SetVisible(state);
- InvalidateLineBreakStream();
- InvalidateLayout();
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Create cut/copy/paste dropdown menu
-//-----------------------------------------------------------------------------
-void RichText::CreateEditMenu()
-{
- // create a drop down cut/copy/paste menu appropriate for this object's states
- if (m_pEditMenu)
- delete m_pEditMenu;
- m_pEditMenu = new Menu(this, "EditMenu");
-
-
- // add cut/copy/paste drop down options if its editable, just copy if it is not
- m_pEditMenu->AddMenuItem("C&opy", new KeyValues("DoCopySelected"), this);
-
- m_pEditMenu->SetVisible(false);
- m_pEditMenu->SetParent(this);
- m_pEditMenu->AddActionSignalTarget(this);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: We want single line windows to scroll horizontally and select text
-// in response to clicking and holding outside window
-//-----------------------------------------------------------------------------
-void RichText::OnMouseFocusTicked()
-{
- // if a button is down move the scrollbar slider the appropriate direction
- if (_mouseDragSelection) // text is being selected via mouse clicking and dragging
- {
- OnCursorMoved(0,0); // we want the text to scroll as if we were dragging
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: If a cursor enters the window, we are not elegible for
-// MouseFocusTicked events
-//-----------------------------------------------------------------------------
-void RichText::OnCursorEntered()
-{
- _mouseDragSelection = false; // outside of window dont recieve drag scrolling ticks
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: When the cursor is outside the window, if we are holding the mouse
-// button down, then we want the window to scroll the text one char at a time using Ticks
-//-----------------------------------------------------------------------------
-void RichText::OnCursorExited()
-{
- // outside of window recieve drag scrolling ticks
- if (_mouseSelection)
- {
- _mouseDragSelection = true;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle selection of text by mouse
-//-----------------------------------------------------------------------------
-void RichText::OnCursorMoved(int x, int y)
-{
- if (_mouseSelection)
- {
- // update the cursor position
- int x, y;
- input()->GetCursorPos(x, y);
- ScreenToLocal(x, y);
- _cursorPos = PixelToCursorSpace(x, y);
-
- if (_cursorPos != _select[1])
- {
- _select[1] = _cursorPos;
- Repaint();
- }
- // Msg( "selecting range [%d..%d]\n", _select[0], _select[1] );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle mouse button down events.
-//-----------------------------------------------------------------------------
-void RichText::OnMousePressed(MouseCode code)
-{
- if (code == MOUSE_LEFT)
- {
- // clear current selection
- SelectNone();
-
- // move the cursor to where the mouse was pressed
- int x, y;
- input()->GetCursorPos(x, y);
- ScreenToLocal(x, y);
-
- _cursorPos = PixelToCursorSpace(x, y);
-
- if ( m_bInteractive )
- {
- // enter selection mode
- input()->SetMouseCapture(GetVPanel());
- _mouseSelection = true;
-
- if (_select[0] < 0)
- {
- // if no initial selection position, Start selection position at cursor
- _select[0] = _cursorPos;
- }
- _select[1] = _cursorPos;
- }
-
- RequestFocus();
- Repaint();
- }
- else if (code == MOUSE_RIGHT) // check for context menu open
- {
- if ( m_bInteractive )
- {
- CreateEditMenu();
- Assert(m_pEditMenu);
-
- OpenEditMenu();
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle mouse button up events
-//-----------------------------------------------------------------------------
-void RichText::OnMouseReleased(MouseCode code)
-{
- _mouseSelection = false;
- input()->SetMouseCapture(NULL);
-
- // make sure something has been selected
- int cx0, cx1;
- if (GetSelectedRange(cx0, cx1))
- {
- if (cx1 - cx0 == 0)
- {
- // nullify selection
- _select[0] = -1;
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Handle mouse double clicks
-//-----------------------------------------------------------------------------
-void RichText::OnMouseDoublePressed(MouseCode code)
-{
- if ( !m_bInteractive )
- return;
-
- // left double clicking on a word selects the word
- if (code == MOUSE_LEFT)
- {
- // move the cursor just as if you single clicked.
- OnMousePressed(code);
- // then find the start and end of the word we are in to highlight it.
- int selectSpot[2];
- GotoWordLeft();
- selectSpot[0] = _cursorPos;
- GotoWordRight();
- selectSpot[1] = _cursorPos;
-
- if ( _cursorPos > 0 && (_cursorPos-1) < m_TextStream.Count() )
- {
- if (iswspace(m_TextStream[_cursorPos-1]))
- {
- selectSpot[1]--;
- _cursorPos--;
- }
- }
-
- _select[0] = selectSpot[0];
- _select[1] = selectSpot[1];
- _mouseSelection = true;
- }
-
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Turn off text selection code when mouse button is not down
-//-----------------------------------------------------------------------------
-void RichText::OnMouseCaptureLost()
-{
- _mouseSelection = false;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Masks which keys get chained up
-// Maps keyboard input to text window functions.
-//-----------------------------------------------------------------------------
-void RichText::OnKeyCodeTyped(KeyCode code)
-{
- bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT));
- bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL));
- bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT));
- bool winkey = (input()->IsKeyDown(KEY_LWIN) || input()->IsKeyDown(KEY_RWIN));
- bool fallThrough = false;
-
- if ( ctrl || ( winkey && IsOSX() ) )
- {
- switch(code)
- {
- case KEY_INSERT:
- case KEY_C:
- case KEY_X:
- {
- CopySelected();
- break;
- }
- case KEY_PAGEUP:
- case KEY_HOME:
- {
- GotoTextStart();
- break;
- }
- case KEY_PAGEDOWN:
- case KEY_END:
- {
- GotoTextEnd();
- break;
- }
- default:
- {
- fallThrough = true;
- break;
- }
- }
- }
- else if (alt)
- {
- // do nothing with ALT-x keys
- fallThrough = true;
- }
- else
- {
- switch(code)
- {
- case KEY_TAB:
- case KEY_LSHIFT:
- case KEY_RSHIFT:
- case KEY_ESCAPE:
- case KEY_ENTER:
- {
- fallThrough = true;
- break;
- }
- case KEY_DELETE:
- {
- if (shift)
- {
- // shift-delete is cut
- CopySelected();
- }
- break;
- }
- case KEY_HOME:
- {
- GotoTextStart();
- break;
- }
- case KEY_END:
- {
- GotoTextEnd();
- break;
- }
- case KEY_PAGEUP:
- {
- // if there is a scroll bar scroll down one rangewindow
- if (_vertScrollBar->IsVisible())
- {
- int window = _vertScrollBar->GetRangeWindow();
- int newval = _vertScrollBar->GetValue();
- _vertScrollBar->SetValue(newval - window - 1);
- }
- break;
-
- }
- case KEY_PAGEDOWN:
- {
- // if there is a scroll bar scroll down one rangewindow
- if (_vertScrollBar->IsVisible())
- {
- int window = _vertScrollBar->GetRangeWindow();
- int newval = _vertScrollBar->GetValue();
- _vertScrollBar->SetValue(newval + window + 1);
- }
- break;
- }
- default:
- {
- // return if any other char is pressed.
- // as it will be a unicode char.
- // and we don't want select[1] changed unless a char was pressed that this fxn handles
- return;
- }
- }
- }
-
- // select[1] is the location in the line where the blinking cursor started
- _select[1] = _cursorPos;
-
- // chain back on some keys
- if (fallThrough)
- {
- BaseClass::OnKeyCodeTyped(code);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Scrolls the list according to the mouse wheel movement
-//-----------------------------------------------------------------------------
-void RichText::OnMouseWheeled(int delta)
-{
- MoveScrollBar(delta);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Scrolls the list
-// Input : delta - amount to move scrollbar up
-//-----------------------------------------------------------------------------
-void RichText::MoveScrollBar(int delta)
-{
- MoveScrollBarDirect( delta * 3 );
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Scrolls the list
-// Input : delta - amount to move scrollbar up
-//-----------------------------------------------------------------------------
-void RichText::MoveScrollBarDirect(int delta)
-{
- if (_vertScrollBar->IsVisible())
- {
- int val = _vertScrollBar->GetValue();
- val -= delta;
- _vertScrollBar->SetValue(val);
- _recalcSavedRenderState = true;
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: set the maximum number of chars in the text buffer
-//-----------------------------------------------------------------------------
-void RichText::SetMaximumCharCount(int maxChars)
-{
- _maxCharCount = maxChars;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Find out what line the cursor is on
-//-----------------------------------------------------------------------------
-int RichText::GetCursorLine()
-{
- // always returns the last place
- int pos = m_LineBreaks[m_LineBreaks.Count() - 1];
- Assert(pos == MAX_BUFFER_SIZE);
- return pos;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Move the cursor over to the Start of the next word to the right
-//-----------------------------------------------------------------------------
-void RichText::GotoWordRight()
-{
- // search right until we hit a whitespace character or a newline
- while (++_cursorPos < m_TextStream.Count())
- {
- if (iswspace(m_TextStream[_cursorPos]))
- break;
- }
-
- // search right until we hit an nonspace character
- while (++_cursorPos < m_TextStream.Count())
- {
- if (!iswspace(m_TextStream[_cursorPos]))
- break;
- }
-
- if (_cursorPos > m_TextStream.Count())
- {
- _cursorPos = m_TextStream.Count();
- }
-
- // now we are at the start of the next word
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Move the cursor over to the Start of the next word to the left
-//-----------------------------------------------------------------------------
-void RichText::GotoWordLeft()
-{
- if (_cursorPos < 1)
- return;
-
- // search left until we hit an nonspace character
- while (--_cursorPos >= 0)
- {
- if (!iswspace(m_TextStream[_cursorPos]))
- break;
- }
-
- // search left until we hit a whitespace character
- while (--_cursorPos >= 0)
- {
- if (iswspace(m_TextStream[_cursorPos]))
- {
- break;
- }
- }
-
- // we end one character off
- _cursorPos++;
-
- // now we are at the start of the previous word
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Move cursor to the Start of the text buffer
-//-----------------------------------------------------------------------------
-void RichText::GotoTextStart()
-{
- _cursorPos = 0; // set cursor to start
- _invalidateVerticalScrollbarSlider = true;
- // force scrollbar to the top
- _vertScrollBar->SetValue(0);
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Move cursor to the end of the text buffer
-//-----------------------------------------------------------------------------
-void RichText::GotoTextEnd()
-{
- _cursorPos = m_TextStream.Count(); // set cursor to end of buffer
- _invalidateVerticalScrollbarSlider = true;
-
- // force the scrollbar to the bottom
- int min, max;
- _vertScrollBar->GetRange(min, max);
- _vertScrollBar->SetValue(max);
-
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Culls the text stream down to a managable size
-//-----------------------------------------------------------------------------
-void RichText::TruncateTextStream()
-{
- if (_maxCharCount < 1)
- return;
-
- // choose a point to cull at
- int cullPos = _maxCharCount / 2;
-
- // kill half the buffer
- m_TextStream.RemoveMultiple(0, cullPos);
-
- // work out where in the format stream we can start
- int formatIndex = FindFormatStreamIndexForTextStreamPos(cullPos);
- if (formatIndex > 0)
- {
- // take a copy, make it first
- m_FormatStream[0] = m_FormatStream[formatIndex];
- m_FormatStream[0].textStreamIndex = 0;
- // kill the others
- m_FormatStream.RemoveMultiple(1, formatIndex);
- }
-
- // renormalize the remainder of the format stream
- for (int i = 1; i < m_FormatStream.Count(); i++)
- {
- Assert(m_FormatStream[i].textStreamIndex > cullPos);
- m_FormatStream[i].textStreamIndex -= cullPos;
- }
-
- // mark everything to be recalculated
- InvalidateLineBreakStream();
- InvalidateLayout();
- _invalidateVerticalScrollbarSlider = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Insert a character into the text buffer
-//-----------------------------------------------------------------------------
-void RichText::InsertChar(wchar_t wch)
-{
- // throw away redundant linefeed characters
- if ( wch == '\r' )
- return;
-
- if (_maxCharCount > 0 && m_TextStream.Count() > _maxCharCount)
- {
- TruncateTextStream();
- }
-
- // insert the new char at the end of the buffer
- m_TextStream.AddToTail(wch);
-
- // mark the linebreak steam as needing recalculating from that point
- _recalculateBreaksIndex = m_LineBreaks.Count() - 2;
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Insert a string into the text buffer, this is just a series
-// of char inserts because we have to check each char is ok to insert
-//-----------------------------------------------------------------------------
-void RichText::InsertString(const char *text)
-{
- if (text[0] == '#')
- {
- wchar_t unicode[ 1024 ];
- ResolveLocalizedTextAndVariables( text, unicode, sizeof( unicode ) );
- InsertString( unicode );
- return;
- }
-
- // upgrade the ansi text to unicode to display it
- int len = strlen(text);
- wchar_t *unicode = (wchar_t *)_alloca((len + 1) * sizeof(wchar_t));
- Q_UTF8ToUnicode(text, unicode, ((len + 1) * sizeof(wchar_t)));
- InsertString(unicode);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Insertsa a unicode string into the buffer
-//-----------------------------------------------------------------------------
-void RichText::InsertString(const wchar_t *wszText)
-{
- // insert the whole string
- for (const wchar_t *ch = wszText; *ch != 0; ++ch)
- {
- InsertChar(*ch);
- }
- InvalidateLayout();
- m_bRecalcLineBreaks = true;
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Declare a selection empty
-//-----------------------------------------------------------------------------
-void RichText::SelectNone()
-{
- // tag the selection as empty
- _select[0] = -1;
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Load in the selection range so cx0 is the Start and cx1 is the end
-// from smallest to highest (right to left)
-//-----------------------------------------------------------------------------
-bool RichText::GetSelectedRange(int &cx0, int &cx1)
-{
- // if there is nothing selected return false
- if (_select[0] == -1)
- return false;
-
- // sort the two position so cx0 is the smallest
- cx0 = _select[0];
- cx1 = _select[1];
- if (cx1 < cx0)
- {
- int temp = cx0;
- cx0 = cx1;
- cx1 = temp;
- }
-
- return true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Opens the cut/copy/paste dropdown menu
-//-----------------------------------------------------------------------------
-void RichText::OpenEditMenu()
-{
- // get cursor position, this is local to this text edit window
- // so we need to adjust it relative to the parent
- int cursorX, cursorY;
- input()->GetCursorPos(cursorX, cursorY);
-
- /* !! disabled since it recursively gets panel pointers, potentially across dll boundaries,
- and doesn't need to be necessary (it's just for handling windowed mode)
-
- // find the frame that has no parent (the one on the desktop)
- Panel *panel = this;
- while ( panel->GetParent() != NULL)
- {
- panel = panel->GetParent();
- }
- panel->ScreenToLocal(cursorX, cursorY);
- int x, y;
- // get base panel's postition
- panel->GetPos(x, y);
-
- // adjust our cursor position accordingly
- cursorX += x;
- cursorY += y;
- */
-
- int x0, x1;
- if (GetSelectedRange(x0, x1)) // there is something selected
- {
- m_pEditMenu->SetItemEnabled("&Cut", true);
- m_pEditMenu->SetItemEnabled("C&opy", true);
- }
- else // there is nothing selected, disable cut/copy options
- {
- m_pEditMenu->SetItemEnabled("&Cut", false);
- m_pEditMenu->SetItemEnabled("C&opy", false);
- }
- m_pEditMenu->SetVisible(true);
- m_pEditMenu->RequestFocus();
-
- // relayout the menu immediately so that we know it's size
- m_pEditMenu->InvalidateLayout(true);
- int menuWide, menuTall;
- m_pEditMenu->GetSize(menuWide, menuTall);
-
- // work out where the cursor is and therefore the best place to put the menu
- int wide, tall;
- surface()->GetScreenSize(wide, tall);
-
- if (wide - menuWide > cursorX)
- {
- // menu hanging right
- if (tall - menuTall > cursorY)
- {
- // menu hanging down
- m_pEditMenu->SetPos(cursorX, cursorY);
- }
- else
- {
- // menu hanging up
- m_pEditMenu->SetPos(cursorX, cursorY - menuTall);
- }
- }
- else
- {
- // menu hanging left
- if (tall - menuTall > cursorY)
- {
- // menu hanging down
- m_pEditMenu->SetPos(cursorX - menuWide, cursorY);
- }
- else
- {
- // menu hanging up
- m_pEditMenu->SetPos(cursorX - menuWide, cursorY - menuTall);
- }
- }
-
- m_pEditMenu->RequestFocus();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Cuts the selected chars from the buffer and
-// copies them into the clipboard
-//-----------------------------------------------------------------------------
-void RichText::CutSelected()
-{
- CopySelected();
- // have to request focus if we used the menu
- RequestFocus();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Copies the selected chars into the clipboard
-//-----------------------------------------------------------------------------
-void RichText::CopySelected()
-{
- int x0, x1;
- if (GetSelectedRange(x0, x1))
- {
- CUtlVector<wchar_t> buf;
- for (int i = x0; i <= x1; i++)
- {
- if ( m_TextStream.IsValidIndex(i) == false )
- continue;
-
- if (m_TextStream[i] == '\n')
- {
- buf.AddToTail( '\r' );
- }
- // remove any rich edit commands
- buf.AddToTail(m_TextStream[i]);
- }
- buf.AddToTail('\0');
- system()->SetClipboardText(buf.Base(), buf.Count() - 1);
- }
-
- // have to request focus if we used the menu
- RequestFocus();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns the index in the text buffer of the
-// character the drawing should Start at
-//-----------------------------------------------------------------------------
-int RichText::GetStartDrawIndex(int &lineBreakIndexIndex)
-{
- int startIndex = 0;
- int startLine = _vertScrollBar->GetValue();
-
- if ( startLine >= m_LineBreaks.Count() ) // incase the line breaks got reset and the scroll bar hasn't
- {
- startLine = m_LineBreaks.Count() - 1;
- }
-
- lineBreakIndexIndex = startLine;
- if (startLine && startLine < m_LineBreaks.Count())
- {
- startIndex = m_LineBreaks[startLine - 1];
- }
-
- return startIndex;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Get a string from text buffer
-// Input: offset - index to Start reading from
-// bufLen - length of string
-//-----------------------------------------------------------------------------
-void RichText::GetText(int offset, wchar_t *buf, int bufLenInBytes)
-{
- if (!buf)
- return;
-
- Assert( bufLenInBytes >= sizeof(buf[0]) );
- int bufLen = bufLenInBytes / sizeof(wchar_t);
- int i;
- for (i = offset; i < (offset + bufLen - 1); i++)
- {
- if (i >= m_TextStream.Count())
- break;
-
- buf[i-offset] = m_TextStream[i];
- }
- buf[(i-offset)] = 0;
- buf[bufLen-1] = 0;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: gets text from the buffer
-//-----------------------------------------------------------------------------
-void RichText::GetText(int offset, char *pch, int bufLenInBytes)
-{
- wchar_t rgwchT[4096];
- GetText(offset, rgwchT, sizeof(rgwchT));
- Q_UnicodeToUTF8(rgwchT, pch, bufLenInBytes);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Set the font of the buffer text
-//-----------------------------------------------------------------------------
-void RichText::SetFont(HFont font)
-{
- _font = font;
- InvalidateLayout();
- m_bRecalcLineBreaks = true;
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called when the scrollbar slider is moved
-//-----------------------------------------------------------------------------
-void RichText::OnSliderMoved()
-{
- _recalcSavedRenderState = true;
- Repaint();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-bool RichText::RequestInfo(KeyValues *outputData)
-{
- if (!stricmp(outputData->GetName(), "GetText"))
- {
- wchar_t wbuf[512];
- GetText(0, wbuf, sizeof(wbuf));
- outputData->SetWString("text", wbuf);
- return true;
- }
-
- return BaseClass::RequestInfo(outputData);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void RichText::OnSetText(const wchar_t *text)
-{
- SetText(text);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Called when a URL, etc has been clicked on
-//-----------------------------------------------------------------------------
-void RichText::OnClickPanel(int index)
-{
- wchar_t wBuf[512];
- int outIndex = 0;
-
- // parse out the clickable text, and send it to our listeners
- _currentTextClickable = true;
- TRenderState renderState;
- GenerateRenderStateForTextStreamIndex(index, renderState);
- for (int i = index; i < (sizeof(wBuf) - 1) && i < m_TextStream.Count(); i++)
- {
- // stop getting characters when text is no longer clickable
- UpdateRenderState(i, renderState);
- if (!renderState.textClickable)
- break;
-
- // copy out the character
- wBuf[outIndex++] = m_TextStream[i];
- }
-
- wBuf[outIndex] = 0;
-
- int iFormatSteam = FindFormatStreamIndexForTextStreamPos( index );
- if ( m_FormatStream[iFormatSteam].m_sClickableTextAction )
- {
- Q_UTF8ToUnicode( m_FormatStream[iFormatSteam].m_sClickableTextAction.String(), wBuf, sizeof( wBuf ) );
- }
-
- PostActionSignal(new KeyValues("TextClicked", "text", wBuf));
- OnTextClicked(wBuf);
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void RichText::ApplySettings(KeyValues *inResourceData)
-{
- BaseClass::ApplySettings(inResourceData);
- SetMaximumCharCount(inResourceData->GetInt("maxchars", -1));
- SetVerticalScrollbar(inResourceData->GetInt("scrollbar", 1));
-
- // get the starting text, if any
- const char *text = inResourceData->GetString("text", "");
- if (*text)
- {
- delete [] m_pszInitialText;
- int len = Q_strlen(text) + 1;
- m_pszInitialText = new char[ len ];
- Q_strncpy( m_pszInitialText, text, len );
- SetText(text);
- }
- else
- {
- const char *textfilename = inResourceData->GetString("textfile", NULL);
- if ( textfilename )
- {
- FileHandle_t f = g_pFullFileSystem->Open( textfilename, "rt" );
- if (!f)
- {
- Warning( "RichText: textfile parameter '%s' not found.\n", textfilename );
- return;
- }
-
- int len = g_pFullFileSystem->Size( f );
- delete [] m_pszInitialText;
- m_pszInitialText = new char[ len + 1 ];
- g_pFullFileSystem->Read( m_pszInitialText, len, f );
- m_pszInitialText[len - 1] = 0;
- SetText( m_pszInitialText );
-
- g_pFullFileSystem->Close( f );
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void RichText::GetSettings(KeyValues *outResourceData)
-{
- BaseClass::GetSettings(outResourceData);
- outResourceData->SetInt("maxchars", _maxCharCount);
- outResourceData->SetInt("scrollbar", _vertScrollBar->IsVisible() );
- if (m_pszInitialText)
- {
- outResourceData->SetString("text", m_pszInitialText);
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-const char *RichText::GetDescription()
-{
- static char buf[1024];
- Q_snprintf(buf, sizeof(buf), "%s, string text, bool scrollbar", BaseClass::GetDescription());
- return buf;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Get the number of lines in the window
-//-----------------------------------------------------------------------------
-int RichText::GetNumLines()
-{
- return m_LineBreaks.Count();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Sets the height of the text entry window so all text will fit inside
-//-----------------------------------------------------------------------------
-void RichText::SetToFullHeight()
-{
- PerformLayout();
- int wide, tall;
- GetSize(wide, tall);
-
- tall = GetNumLines() * (GetLineHeight() + _drawOffsetY) + _drawOffsetY + 2;
- SetSize (wide, tall);
- PerformLayout();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Select all the text.
-//-----------------------------------------------------------------------------
-void RichText::SelectAllText()
-{
- _cursorPos = 0;
- _select[0] = 0;
- _select[1] = m_TextStream.Count();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Select all the text.
-//-----------------------------------------------------------------------------
-void RichText::SelectNoText()
-{
- _select[0] = 0;
- _select[1] = 0;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void RichText::OnSetFocus()
-{
- BaseClass::OnSetFocus();
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Invalidates the current linebreak stream
-//-----------------------------------------------------------------------------
-void RichText::InvalidateLineBreakStream()
-{
- // clear the buffer
- m_LineBreaks.RemoveAll();
- m_LineBreaks.AddToTail(MAX_BUFFER_SIZE);
- _recalculateBreaksIndex = 0;
- m_bRecalcLineBreaks = true;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Inserts a text string while making URLs clickable/different color
-// Input : *text - string that may contain URLs to make clickable/color coded
-// URLTextColor - color for URL text
-// normalTextColor - color for normal text
-//-----------------------------------------------------------------------------
-void RichText::InsertPossibleURLString(const char* text, Color URLTextColor, Color normalTextColor)
-{
- InsertColorChange(normalTextColor);
-
- // parse out the string for URL's
- int len = Q_strlen(text), pos = 0;
- bool clickable = false;
- char *pchURLText = (char *)stackalloc( len + 1 );
- char *pchURL = (char *)stackalloc( len + 1 );
-
- while (pos < len)
- {
- pos = ParseTextStringForUrls( text, pos, pchURLText, len, pchURL, len, clickable );
-
- if ( clickable )
- {
- InsertClickableTextStart( pchURL );
- InsertColorChange( URLTextColor );
- }
-
- InsertString( pchURLText );
-
- if ( clickable )
- {
- InsertColorChange(normalTextColor);
- InsertClickableTextEnd();
- }
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: looks for URLs in the string and returns information about the URL
-//-----------------------------------------------------------------------------
-int RichText::ParseTextStringForUrls( const char *text, int startPos, char *pchURLText, int cchURLText, char *pchURL, int cchURL, bool &clickable )
-{
- // scan for text that looks like a URL
- int i = startPos;
- while (text[i] != 0)
- {
- bool bURLFound = false;
-
- if ( !Q_strnicmp(text + i, "<a href=", 8) )
- {
- if (i > startPos)
- break;
-
- // embedded link
- bURLFound = true;
- clickable = true;
- // get the url
- i += Q_strlen( "<a href=" );
- const char *pchURLEnd = Q_strstr( text + i, ">" );
- Q_strncpy( pchURL, text + i, min( pchURLEnd - text - i + 1, cchURL ) );
- i += ( pchURLEnd - text - i + 1 );
-
- // get the url text
- pchURLEnd = Q_strstr( text, "</a>" );
- Q_strncpy( pchURLText, text + i, min( pchURLEnd - text - i + 1, cchURLText ) );
- i += ( pchURLEnd - text - i );
- i += Q_strlen( "</a>" );
-
- // we're done
- return i;
- }
- else if (!Q_strnicmp(text + i, "www.", 4))
- {
- // scan ahead for another '.'
- bool bPeriodFound = false;
- for (const char *ch = text + i + 5; ch != 0; ch++)
- {
- if (*ch == '.')
- {
- bPeriodFound = true;
- break;
- }
- }
-
- // URL found
- if (bPeriodFound)
- {
- bURLFound = true;
- }
- }
- else if (!Q_strnicmp(text + i, "http://", 7))
- {
- bURLFound = true;
- }
- else if (!Q_strnicmp(text + i, "ftp://", 6))
- {
- bURLFound = true;
- }
- else if (!Q_strnicmp(text + i, "steam://", 8))
- {
- bURLFound = true;
- }
- else if (!Q_strnicmp(text + i, "steambeta://", 12))
- {
- bURLFound = true;
- }
- else if (!Q_strnicmp(text + i, "mailto:", 7))
- {
- bURLFound = true;
- }
- else if (!Q_strnicmp(text + i, "\\\\", 2))
- {
- bURLFound = true;
- }
-
- if (bURLFound)
- {
- if (i == startPos)
- {
- // we're at the Start of a URL, so parse that out
- clickable = true;
- int outIndex = 0;
- while (text[i] != 0 && !iswspace(text[i]))
- {
- pchURLText[outIndex++] = text[i++];
- }
- pchURLText[outIndex] = 0;
- Q_strncpy( pchURL, pchURLText, cchURL );
- return i;
- }
- else
- {
- // no url
- break;
- }
- }
-
- // increment and loop
- i++;
- }
-
- // nothing found;
- // parse out the text before the end
- clickable = false;
- int outIndex = 0;
- int fromIndex = startPos;
- while ( fromIndex < i && outIndex < cchURLText )
- {
- pchURLText[outIndex++] = text[fromIndex++];
- }
- pchURLText[outIndex] = 0;
- Q_strncpy( pchURL, pchURLText, cchURL );
-
- return i;
-}
-
-//-----------------------------------------------------------------------------
-// Purpose: Executes the text-clicked command, which opens a web browser by
-// default.
-//-----------------------------------------------------------------------------
-void RichText::OnTextClicked(const wchar_t *wszText)
-{
- // Strip leading/trailing quotes, which may be present on href tags or may not.
- const wchar_t *pwchURL = wszText;
- if ( pwchURL[0] == L'"' || pwchURL[0] == L'\'' )
- pwchURL = wszText + 1;
-
- char ansi[2048];
- Q_UnicodeToUTF8( pwchURL, ansi, sizeof(ansi) );
-
- size_t strLen = Q_strlen(ansi);
- if ( strLen && ( ansi[strLen-1] == '"' || ansi[strLen] == '\'' ) )
- {
- ansi[strLen-1] = 0;
- }
-
- if ( m_hPanelToHandleClickingURLs.Get() )
- {
- PostMessage( m_hPanelToHandleClickingURLs.Get(), new KeyValues( "URLClicked", "url", ansi ) );
- }
- else
- {
- system()->ShellExecute( "open", ansi );
- }
-}
-
-//-----------------------------------------------------------------------------
-// Purpose:
-//-----------------------------------------------------------------------------
-void RichText::SetURLClickedHandler( Panel *pPanelToHandleClickMsg )
-{
- m_hPanelToHandleClickingURLs = pPanelToHandleClickMsg;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: data accessor
-//-----------------------------------------------------------------------------
-bool RichText::IsScrollbarVisible()
-{
- return _vertScrollBar->IsVisible();
-}
-
-void RichText::SetUnderlineFont( HFont font )
-{
- m_hFontUnderline = font;
-}
-
-bool RichText::IsAllTextAlphaZero() const
-{
- return m_bAllTextAlphaIsZero;
-}
-
-bool RichText::HasText() const
-{
- int c = m_TextStream.Count();
- if ( c == 0 )
- {
- return false;
- }
- return true;
-}
-
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Returns the height of the base font
-//-----------------------------------------------------------------------------
-int RichText::GetLineHeight()
-{
- return surface()->GetFontTall( _font );
-}
-
-
-#ifdef DBGFLAG_VALIDATE
-//-----------------------------------------------------------------------------
-// Purpose: Run a global validation pass on all of our data structures and memory
-// allocations.
-// Input: validator - Our global validator object
-// pchName - Our name (typically a member var in our container)
-//-----------------------------------------------------------------------------
-void RichText::Validate( CValidator &validator, char *pchName )
-{
- validator.Push( "vgui::RichText", this, pchName );
-
- ValidateObj( m_TextStream );
- ValidateObj( m_FormatStream );
- ValidateObj( m_LineBreaks );
- ValidateObj( _clickableTextPanels );
- validator.ClaimMemory( m_pszInitialText );
-
- BaseClass::Validate( validator, "vgui::RichText" );
-
- validator.Pop();
-}
-#endif // DBGFLAG_VALIDATE
-
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "vgui_controls/pch_vgui_controls.h"
+#include "vgui/ILocalize.h"
+
+// memdbgon must be the last include file in a .cpp file
+#include "tier0/memdbgon.h"
+
+enum
+{
+ MAX_BUFFER_SIZE = 999999, // maximum size of text buffer
+ DRAW_OFFSET_X = 3,
+ DRAW_OFFSET_Y = 1,
+};
+
+using namespace vgui;
+
+#ifndef max
+#define max(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+
+namespace vgui
+{
+
+//#define DRAW_CLICK_PANELS
+
+//-----------------------------------------------------------------------------
+// Purpose: Panel used for clickable URL's
+//-----------------------------------------------------------------------------
+class ClickPanel : public Panel
+{
+ DECLARE_CLASS_SIMPLE( ClickPanel, Panel );
+
+public:
+ ClickPanel(Panel *parent)
+ {
+ _viewIndex = 0;
+ _textIndex = 0;
+ SetParent(parent);
+ AddActionSignalTarget(parent);
+
+ SetCursor(dc_hand);
+
+ SetPaintBackgroundEnabled(false);
+ SetPaintEnabled(false);
+// SetPaintAppearanceEnabled(false);
+
+#if defined( DRAW_CLICK_PANELS )
+ SetPaintEnabled(true);
+#endif
+ }
+
+ void SetTextIndex( int linkStartIndex, int viewStartIndex )
+ {
+ _textIndex = linkStartIndex;
+ _viewIndex = viewStartIndex;
+ }
+
+#if defined( DRAW_CLICK_PANELS )
+ virtual void Paint()
+ {
+ surface()->DrawSetColor( Color( 255, 0, 0, 255 ) );
+ surface()->DrawOutlinedRect( 0, 0, GetWide(), GetTall() );
+ }
+#endif
+
+ int GetTextIndex()
+ {
+ return _textIndex;
+ }
+
+ int GetViewTextIndex()
+ {
+ return _viewIndex;
+ }
+
+ void OnMousePressed(MouseCode code)
+ {
+ if (code == MOUSE_LEFT)
+ {
+ PostActionSignal(new KeyValues("ClickPanel", "index", _textIndex));
+ }
+ else
+ {
+ GetParent()->OnMousePressed( code );
+ }
+ }
+
+private:
+ int _textIndex;
+ int _viewIndex;
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Panel used only to draw the interior border region
+//-----------------------------------------------------------------------------
+class RichTextInterior : public Panel
+{
+ DECLARE_CLASS_SIMPLE( RichTextInterior, Panel );
+
+public:
+ RichTextInterior( RichText *pParent, const char *pchName ) : BaseClass( pParent, pchName )
+ {
+ SetKeyBoardInputEnabled( false );
+ SetMouseInputEnabled( false );
+ SetPaintBackgroundEnabled( false );
+ SetPaintEnabled( false );
+ m_pRichText = pParent;
+ }
+
+/* virtual IAppearance *GetAppearance()
+ {
+ if ( m_pRichText->IsScrollbarVisible() )
+ return m_pAppearanceScrollbar;
+
+ return BaseClass::GetAppearance();
+ }*/
+
+ virtual void ApplySchemeSettings( IScheme *pScheme )
+ {
+ BaseClass::ApplySchemeSettings( pScheme );
+// m_pAppearanceScrollbar = FindSchemeAppearance( pScheme, "scrollbar_visible" );
+ }
+
+private:
+ RichText *m_pRichText;
+// IAppearance *m_pAppearanceScrollbar;
+};
+
+}; // namespace vgui
+
+DECLARE_BUILD_FACTORY( RichText );
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+RichText::RichText(Panel *parent, const char *panelName) : BaseClass(parent, panelName)
+{
+ m_bAllTextAlphaIsZero = false;
+ _font = INVALID_FONT;
+ m_hFontUnderline = INVALID_FONT;
+
+ m_bRecalcLineBreaks = true;
+ m_pszInitialText = NULL;
+ _cursorPos = 0;
+ _mouseSelection = false;
+ _mouseDragSelection = false;
+ _vertScrollBar = new ScrollBar(this, "ScrollBar", true);
+ _vertScrollBar->AddActionSignalTarget(this);
+ _recalcSavedRenderState = true;
+ _maxCharCount = (64 * 1024);
+ AddActionSignalTarget(this);
+ m_pInterior = new RichTextInterior( this, NULL );
+
+ //a -1 for _select[0] means that the selection is empty
+ _select[0] = -1;
+ _select[1] = -1;
+ m_pEditMenu = NULL;
+
+ SetCursor(dc_ibeam);
+
+ //position the cursor so it is at the end of the text
+ GotoTextEnd();
+
+ // set default foreground color to black
+ _defaultTextColor = Color(0, 0, 0, 0);
+
+ // initialize the line break array
+ InvalidateLineBreakStream();
+
+ if ( IsProportional() )
+ {
+ int width, height;
+ int sw,sh;
+ surface()->GetProportionalBase( width, height );
+ surface()->GetScreenSize(sw, sh);
+
+ _drawOffsetX = static_cast<int>( static_cast<float>( DRAW_OFFSET_X )*( static_cast<float>( sw )/ static_cast<float>( width )));
+ _drawOffsetY = static_cast<int>( static_cast<float>( DRAW_OFFSET_Y )*( static_cast<float>( sw )/ static_cast<float>( width )));
+ }
+ else
+ {
+ _drawOffsetX = DRAW_OFFSET_X;
+ _drawOffsetY = DRAW_OFFSET_Y;
+ }
+
+ // add a basic format string
+ TFormatStream stream;
+ stream.color = _defaultTextColor;
+ stream.fade.flFadeStartTime = 0.0f;
+ stream.fade.flFadeLength = -1.0f;
+ stream.pixelsIndent = 0;
+ stream.textStreamIndex = 0;
+ stream.textClickable = false;
+ m_FormatStream.AddToTail(stream);
+
+ m_bResetFades = false;
+ m_bInteractive = true;
+ m_bUnusedScrollbarInvis = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor
+//-----------------------------------------------------------------------------
+RichText::~RichText()
+{
+ delete [] m_pszInitialText;
+ delete m_pEditMenu;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void RichText::SetDrawOffsets( int ofsx, int ofsy )
+{
+ _drawOffsetX = ofsx;
+ _drawOffsetY = ofsy;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: sets it as drawing text only - used for embedded RichText control into other text drawing situations
+//-----------------------------------------------------------------------------
+void RichText::SetDrawTextOnly()
+{
+ SetDrawOffsets( 0, 0 );
+ SetPaintBackgroundEnabled( false );
+// SetPaintAppearanceEnabled( false );
+ SetPostChildPaintEnabled( false );
+ m_pInterior->SetVisible( false );
+ SetVerticalScrollbar( false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: configures colors
+//-----------------------------------------------------------------------------
+void RichText::ApplySchemeSettings(IScheme *pScheme)
+{
+ BaseClass::ApplySchemeSettings(pScheme);
+
+ _font = pScheme->GetFont("Default", IsProportional() );
+ m_hFontUnderline = pScheme->GetFont("DefaultUnderline", IsProportional() );
+
+ SetFgColor(GetSchemeColor("RichText.TextColor", pScheme));
+ SetBgColor(GetSchemeColor("RichText.BgColor", pScheme));
+
+ _selectionTextColor = GetSchemeColor("RichText.SelectedTextColor", GetFgColor(), pScheme);
+ _selectionColor = GetSchemeColor("RichText.SelectedBgColor", pScheme);
+
+ if ( Q_strlen( pScheme->GetResourceString( "RichText.InsetX" ) ) )
+ {
+ SetDrawOffsets( atoi( pScheme->GetResourceString( "RichText.InsetX" ) ), atoi( pScheme->GetResourceString( "RichText.InsetY" ) ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: if the default format color isn't set then set it
+//-----------------------------------------------------------------------------
+void RichText::SetFgColor( Color color )
+{
+ // Replace default format color if
+ // the stream is empty and the color is the default ( or the previous FgColor )
+ if ( m_FormatStream.Size() == 1 &&
+ ( m_FormatStream[0].color == _defaultTextColor || m_FormatStream[0].color == GetFgColor() ) )
+ {
+ m_FormatStream[0].color = color;
+ }
+
+ BaseClass::SetFgColor( color );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sends a message if the data has changed
+// Turns off any selected text in the window if we are not using the edit menu
+//-----------------------------------------------------------------------------
+void RichText::OnKillFocus()
+{
+ // check if we clicked the right mouse button or if it is down
+ bool mouseRightClicked = input()->WasMousePressed(MOUSE_RIGHT);
+ bool mouseRightUp = input()->WasMouseReleased(MOUSE_RIGHT);
+ bool mouseRightDown = input()->IsMouseDown(MOUSE_RIGHT);
+
+ if (mouseRightClicked || mouseRightDown || mouseRightUp )
+ {
+ // get the start and ends of the selection area
+ int start, end;
+ if (GetSelectedRange(start, end)) // we have selected text
+ {
+ // see if we clicked in the selection area
+ int startX, startY;
+ CursorToPixelSpace(start, startX, startY);
+ int endX, endY;
+ CursorToPixelSpace(end, endX, endY);
+ int cursorX, cursorY;
+ input()->GetCursorPos(cursorX, cursorY);
+ ScreenToLocal(cursorX, cursorY);
+
+ // check the area vertically
+ // we need to handle the horizontal edge cases eventually
+ int fontTall = GetLineHeight();
+ endY = endY + fontTall;
+ if ((startY < cursorY) && (endY > cursorY))
+ {
+ // if we clicked in the selection area, leave the text highlighted
+ return;
+ }
+ }
+ }
+
+ // clear any selection
+ SelectNone();
+
+ // chain
+ BaseClass::OnKillFocus();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Wipe line breaks after the size of a panel has been changed
+//-----------------------------------------------------------------------------
+void RichText::OnSizeChanged( int wide, int tall )
+{
+ BaseClass::OnSizeChanged( wide, tall );
+
+ // blow away the line breaks list
+ _invalidateVerticalScrollbarSlider = true;
+ InvalidateLineBreakStream();
+ InvalidateLayout();
+
+ if ( _vertScrollBar->IsVisible() )
+ {
+ _vertScrollBar->MakeReadyForUse();
+ m_pInterior->SetBounds( 0, 0, wide - _vertScrollBar->GetWide(), tall );
+ }
+ else
+ {
+ m_pInterior->SetBounds( 0, 0, wide, tall );
+ }
+}
+
+
+const wchar_t *RichText::ResolveLocalizedTextAndVariables( char const *pchLookup, wchar_t *outbuf, size_t outbufsizeinbytes )
+{
+ if ( pchLookup[ 0 ] == '#' )
+ {
+ // try lookup in localization tables
+ StringIndex_t index = g_pVGuiLocalize->FindIndex( pchLookup + 1 );
+ if ( index == INVALID_LOCALIZE_STRING_INDEX )
+ {
+/* // if it's not found, maybe it's a special expanded variable - look for an expansion
+ char rgchT[MAX_PATH];
+
+ // get the variables
+ KeyValues *variables = GetDialogVariables_R();
+ if ( variables )
+ {
+ // see if any are any special vars to put in
+ for ( KeyValues *pkv = variables->GetFirstSubKey(); pkv != NULL; pkv = pkv->GetNextKey() )
+ {
+ if ( !Q_strncmp( pkv->GetName(), "$", 1 ) )
+ {
+ // make a new lookup, with this key appended
+ Q_snprintf( rgchT, sizeof( rgchT ), "%s%s=%s", pchLookup, pkv->GetName(), pkv->GetString() );
+ index = localize()->FindIndex( rgchT );
+ break;
+ }
+ }
+ }
+ */
+ }
+
+ // see if we have a valid string
+ if ( index != INVALID_LOCALIZE_STRING_INDEX )
+ {
+ wchar_t *format = g_pVGuiLocalize->GetValueByIndex( index );
+ Assert( format );
+ if ( format )
+ {
+ /*// Try and substitute variables if any
+ KeyValues *variables = GetDialogVariables_R();
+ if ( variables )
+ {
+ localize()->ConstructString( outbuf, outbufsizeinbytes, index, variables );
+ return outbuf;
+ }*/
+ }
+ V_wcsncpy( outbuf, format, outbufsizeinbytes );
+ return outbuf;
+ }
+ }
+
+ Q_UTF8ToUnicode( pchLookup, outbuf, outbufsizeinbytes );
+ return outbuf;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the text array
+// Using this function will cause all lineBreaks to be discarded.
+// This is because this fxn replaces the contents of the text buffer.
+// For modifying large buffers use insert functions.
+//-----------------------------------------------------------------------------
+void RichText::SetText(const char *text)
+{
+ if (!text)
+ {
+ text = "";
+ }
+
+ wchar_t unicode[1024];
+
+ if (text[0] == '#')
+ {
+ ResolveLocalizedTextAndVariables( text, unicode, sizeof( unicode ) );
+ SetText( unicode );
+ return;
+ }
+
+ // convert to unicode
+ Q_UTF8ToUnicode(text, unicode, sizeof(unicode));
+ SetText(unicode);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void RichText::SetText(const wchar_t *text)
+{
+ // reset the formatting stream
+ m_FormatStream.RemoveAll();
+ TFormatStream stream;
+ stream.color = GetFgColor();
+ stream.fade.flFadeLength = -1.0f;
+ stream.fade.flFadeStartTime = 0.0f;
+ stream.pixelsIndent = 0;
+ stream.textStreamIndex = 0;
+ stream.textClickable = false;
+ m_FormatStream.AddToTail(stream);
+
+ // set the new text stream
+ m_TextStream.RemoveAll();
+ if ( text && *text )
+ {
+ int textLen = wcslen(text) + 1;
+ m_TextStream.EnsureCapacity(textLen);
+ for(int i = 0; i < textLen; i++)
+ {
+ m_TextStream.AddToTail(text[i]);
+ }
+ }
+ GotoTextStart();
+ SelectNone();
+
+ // blow away the line breaks list
+ InvalidateLineBreakStream();
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given cursor's position in the text buffer, convert it to
+// the local window's x and y pixel coordinates
+// Input: cursorPos: cursor index
+// Output: cx, cy, the corresponding coords in the local window
+//-----------------------------------------------------------------------------
+void RichText::CursorToPixelSpace(int cursorPos, int &cx, int &cy)
+{
+ int yStart = _drawOffsetY;
+ int x = _drawOffsetX, y = yStart;
+ _pixelsIndent = 0;
+ int lineBreakIndexIndex = 0;
+
+ for (int i = GetStartDrawIndex(lineBreakIndexIndex); i < m_TextStream.Count(); i++)
+ {
+ wchar_t ch = m_TextStream[i];
+
+ // if we've found the position, break
+ if (cursorPos == i)
+ {
+ // if we've passed a line break go to that
+ if (m_LineBreaks[lineBreakIndexIndex] == i)
+ {
+ // add another line
+ AddAnotherLine(x, y);
+ lineBreakIndexIndex++;
+ }
+ break;
+ }
+
+ // if we've passed a line break go to that
+ if (m_LineBreaks[lineBreakIndexIndex] == i)
+ {
+ // add another line
+ AddAnotherLine(x, y);
+ lineBreakIndexIndex++;
+ }
+
+ // add to the current position
+ x += surface()->GetCharacterWidth(_font, ch);
+ }
+
+ cx = x;
+ cy = y;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Converts local pixel coordinates to an index in the text buffer
+//-----------------------------------------------------------------------------
+int RichText::PixelToCursorSpace(int cx, int cy)
+{
+ int fontTall = GetLineHeight();
+
+ // where to start reading
+ int yStart = _drawOffsetY;
+ int x = _drawOffsetX, y = yStart;
+ _pixelsIndent = 0;
+ int lineBreakIndexIndex = 0;
+
+ int startIndex = GetStartDrawIndex(lineBreakIndexIndex);
+ if (_recalcSavedRenderState)
+ {
+ RecalculateDefaultState(startIndex);
+ }
+
+ _pixelsIndent = m_CachedRenderState.pixelsIndent;
+ _currentTextClickable = m_CachedRenderState.textClickable;
+ TRenderState renderState = m_CachedRenderState;
+
+ bool onRightLine = false;
+ int i;
+ for (i = startIndex; i < m_TextStream.Count(); i++)
+ {
+ wchar_t ch = m_TextStream[i];
+
+ renderState.x = x;
+ if ( UpdateRenderState( i, renderState ) )
+ {
+ x = renderState.x;
+ }
+
+ // if we are on the right line but off the end of if put the cursor at the end of the line
+ if (m_LineBreaks[lineBreakIndexIndex] == i)
+ {
+ // add another line
+ AddAnotherLine(x, y);
+ lineBreakIndexIndex++;
+
+ if (onRightLine)
+ break;
+ }
+
+ // check to see if we're on the right line
+ if (cy < yStart)
+ {
+ // cursor is above panel
+ onRightLine = true;
+ }
+ else if (cy >= y && (cy < (y + fontTall + _drawOffsetY)))
+ {
+ onRightLine = true;
+ }
+
+ int wide = surface()->GetCharacterWidth(_font, ch);
+
+ // if we've found the position, break
+ if (onRightLine)
+ {
+ if (cx > GetWide()) // off right side of window
+ {
+ }
+ else if (cx < (_drawOffsetX + renderState.pixelsIndent) || cy < yStart) // off left side of window
+ {
+ // Msg( "PixelToCursorSpace() off left size, returning %d '%c'\n", i, m_TextStream[i] );
+ return i; // move cursor one to left
+ }
+
+ if (cx >= x && cx < (x + wide))
+ {
+ // check which side of the letter they're on
+ if (cx < (x + (wide * 0.5))) // left side
+ {
+ // Msg( "PixelToCursorSpace() on the left size, returning %d '%c'\n", i, m_TextStream[i] );
+ return i;
+ }
+ else // right side
+ {
+ // Msg( "PixelToCursorSpace() on the right size, returning %d '%c'\n", i + 1, m_TextStream[i + 1] );
+ return i + 1;
+ }
+ }
+ }
+ x += wide;
+ }
+
+ // Msg( "PixelToCursorSpace() never hit, returning %d\n", i );
+ return i;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draws a string of characters in the panel
+// Input: iFirst - Index of the first character to draw
+// iLast - Index of the last character to draw
+// renderState - Render state to use
+// font- font to use
+// Output: returns the width of the character drawn
+//-----------------------------------------------------------------------------
+int RichText::DrawString(int iFirst, int iLast, TRenderState &renderState, HFont font)
+{
+// VPROF( "RichText::DrawString" );
+
+ // Calculate the render size
+ int fontTall = surface()->GetFontTall(font);
+ // BUGBUG John: This won't exactly match the rendered size
+ int charWide = 0;
+ for ( int i = iFirst; i <= iLast; i++ )
+ {
+ wchar_t ch = m_TextStream[i];
+#if USE_GETKERNEDCHARWIDTH
+ wchar_t chBefore = 0;
+ wchar_t chAfter = 0;
+ if ( i > 0 )
+ chBefore = m_TextStream[i-1];
+ if ( i < iLast )
+ chAfter = m_TextStream[i+1];
+ float flWide = 0.0f, flabcA = 0.0f;
+ surface()->GetKernedCharWidth(font, ch, chBefore, chAfter, flWide, flabcA);
+ if ( ch == L' ' )
+ flWide = ceil( flWide );
+ charWide += floor( flWide + 0.6 );
+#else
+ charWide += surface()->GetCharacterWidth(font, ch);
+#endif
+ }
+
+ // draw selection, if any
+ int selection0 = -1, selection1 = -1;
+ GetSelectedRange(selection0, selection1);
+
+ if (iFirst >= selection0 && iFirst < selection1)
+ {
+ // draw background selection color
+ surface()->DrawSetColor(_selectionColor);
+ surface()->DrawFilledRect(renderState.x, renderState.y, renderState.x + charWide, renderState.y + 1 + fontTall);
+
+ // reset text color
+ surface()->DrawSetTextColor(_selectionTextColor);
+ m_bAllTextAlphaIsZero = false;
+ }
+ else
+ {
+ surface()->DrawSetTextColor(renderState.textColor);
+ }
+
+ if ( renderState.textColor.a() != 0 )
+ {
+ m_bAllTextAlphaIsZero = false;
+ surface()->DrawSetTextPos(renderState.x, renderState.y);
+ surface()->DrawPrintText(&m_TextStream[iFirst], iLast - iFirst + 1);
+ }
+
+ return charWide;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finish drawing url
+//-----------------------------------------------------------------------------
+void RichText::FinishingURL(int x, int y)
+{
+ // finishing URL
+ if ( _clickableTextPanels.IsValidIndex( _clickableTextIndex ) )
+ {
+ ClickPanel *clickPanel = _clickableTextPanels[ _clickableTextIndex ];
+ int px, py;
+ clickPanel->GetPos(px, py);
+ int fontTall = GetLineHeight();
+ clickPanel->SetSize( MAX( x - px, 6 ), y - py + fontTall );
+ clickPanel->SetVisible(true);
+
+ // if we haven't actually advanced any, step back and ignore this one
+ // this is probably a data input problem though, need to find root cause
+ if ( x - px <= 0 )
+ {
+ --_clickableTextIndex;
+ clickPanel->SetVisible(false);
+ }
+ }
+}
+
+void RichText::CalculateFade( TRenderState &renderState )
+{
+ if ( m_FormatStream.IsValidIndex( renderState.formatStreamIndex ) )
+ {
+ if ( m_bResetFades == false )
+ {
+ if ( m_FormatStream[renderState.formatStreamIndex].fade.flFadeLength != -1.0f )
+ {
+ float frac = ( m_FormatStream[renderState.formatStreamIndex].fade.flFadeStartTime - system()->GetCurrentTime() ) / m_FormatStream[renderState.formatStreamIndex].fade.flFadeLength;
+
+ int alpha = frac * m_FormatStream[renderState.formatStreamIndex].fade.iOriginalAlpha;
+ alpha = clamp( alpha, 0, m_FormatStream[renderState.formatStreamIndex].fade.iOriginalAlpha );
+
+ renderState.textColor.SetColor( renderState.textColor.r(), renderState.textColor.g(), renderState.textColor.b(), alpha );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Draws the text in the panel
+//-----------------------------------------------------------------------------
+void RichText::Paint()
+{
+ // Assume the worst
+ m_bAllTextAlphaIsZero = true;
+
+ HFont hFontCurrent = _font;
+
+ // hide all the clickable panels until we know where they are to reside
+ for (int j = 0; j < _clickableTextPanels.Count(); j++)
+ {
+ _clickableTextPanels[j]->SetVisible(false);
+ }
+
+ if ( !HasText() )
+ return;
+
+ int wide, tall;
+ GetSize( wide, tall );
+
+ int lineBreakIndexIndex = 0;
+ int startIndex = GetStartDrawIndex(lineBreakIndexIndex);
+ _currentTextClickable = false;
+
+ _clickableTextIndex = GetClickableTextIndexStart(startIndex);
+
+ // recalculate and cache the render state at the render start
+ if (_recalcSavedRenderState)
+ {
+ RecalculateDefaultState(startIndex);
+ }
+ // copy off the cached render state
+ TRenderState renderState = m_CachedRenderState;
+
+ _pixelsIndent = m_CachedRenderState.pixelsIndent;
+ _currentTextClickable = m_CachedRenderState.textClickable;
+
+ renderState.textClickable = _currentTextClickable;
+ renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color;
+ CalculateFade( renderState );
+
+ renderState.formatStreamIndex++;
+
+ if ( _currentTextClickable )
+ {
+ _clickableTextIndex = startIndex;
+ }
+
+ // where to start drawing
+ renderState.x = _drawOffsetX + _pixelsIndent;
+ renderState.y = _drawOffsetY;
+
+ // draw the text
+ int selection0 = -1, selection1 = -1;
+ GetSelectedRange(selection0, selection1);
+
+ surface()->DrawSetTextFont( hFontCurrent );
+
+ for (int i = startIndex; i < m_TextStream.Count() && renderState.y < tall; )
+ {
+ // 1.
+ // Update our current render state based on the formatting and color streams,
+ // this has to happen if it's our very first iteration, or if we are actually changing
+ // state.
+ int nXBeforeStateChange = renderState.x;
+ if ( UpdateRenderState(i, renderState) || i == startIndex )
+ {
+ // check for url state change
+ if (renderState.textClickable != _currentTextClickable)
+ {
+ if (renderState.textClickable)
+ {
+ // entering new URL
+ _clickableTextIndex++;
+ hFontCurrent = m_hFontUnderline;
+ surface()->DrawSetTextFont( hFontCurrent );
+
+ // set up the panel
+ ClickPanel *clickPanel = _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ? _clickableTextPanels[_clickableTextIndex] : NULL;
+
+ if (clickPanel)
+ {
+ clickPanel->SetPos(renderState.x, renderState.y);
+ }
+ }
+ else
+ {
+ FinishingURL(nXBeforeStateChange, renderState.y);
+ hFontCurrent = _font;
+ surface()->DrawSetTextFont( hFontCurrent );
+ }
+ _currentTextClickable = renderState.textClickable;
+ }
+ }
+
+ // 2.
+ // if we've passed a line break go to that
+ if ( m_LineBreaks.IsValidIndex( lineBreakIndexIndex ) && m_LineBreaks[lineBreakIndexIndex] <= i )
+ {
+ if (_currentTextClickable)
+ {
+ FinishingURL(renderState.x, renderState.y);
+ }
+
+ // add another line
+ AddAnotherLine(renderState.x, renderState.y);
+ lineBreakIndexIndex++;
+
+ // Skip white space unless the previous line ended from the hard carriage return
+ if ( i && ( m_TextStream[i-1] != '\n' ) && ( m_TextStream[i-1] != '\r') )
+ {
+ while ( m_TextStream[i] == L' ' )
+ {
+ if ( i+1 < m_TextStream.Count() )
+ ++i;
+ else
+ break;
+ }
+ }
+
+ if (renderState.textClickable)
+ {
+ // move to the next URL
+ _clickableTextIndex++;
+ ClickPanel *clickPanel = _clickableTextPanels.IsValidIndex( _clickableTextIndex ) ? _clickableTextPanels[_clickableTextIndex] : NULL;
+ if (clickPanel)
+ {
+ clickPanel->SetPos(renderState.x, renderState.y);
+ }
+ }
+ }
+
+ // 3.
+ // Calculate the range of text to draw all at once
+ int iLim = m_TextStream.Count();
+
+ // Stop at the next format change
+ if ( m_FormatStream.IsValidIndex(renderState.formatStreamIndex) &&
+ m_FormatStream[renderState.formatStreamIndex].textStreamIndex < iLim &&
+ m_FormatStream[renderState.formatStreamIndex].textStreamIndex >= i &&
+ m_FormatStream[renderState.formatStreamIndex].textStreamIndex )
+ {
+ iLim = m_FormatStream[renderState.formatStreamIndex].textStreamIndex;
+ }
+
+ // Stop at the next line break
+ if ( m_LineBreaks.IsValidIndex( lineBreakIndexIndex ) && m_LineBreaks[lineBreakIndexIndex] < iLim )
+ iLim = m_LineBreaks[lineBreakIndexIndex];
+
+ // Handle non-drawing characters specially
+ for ( int iT = i; iT < iLim; iT++ )
+ {
+ if ( iswcntrl(m_TextStream[iT]) )
+ {
+ iLim = iT;
+ break;
+ }
+ }
+
+ // 4.
+ // Draw the current text range
+ if ( iLim <= i )
+ {
+ if ( m_TextStream[i] == '\t' )
+ {
+ int dxTabWidth = 8 * surface()->GetCharacterWidth(hFontCurrent, ' ');
+ dxTabWidth = MAX( 1, dxTabWidth );
+
+ renderState.x = ( dxTabWidth * ( 1 + ( renderState.x / dxTabWidth ) ) );
+ }
+ i++;
+ }
+ else
+ {
+ renderState.x += DrawString(i, iLim - 1, renderState, hFontCurrent );
+ i = iLim;
+ }
+ }
+
+ if (renderState.textClickable)
+ {
+ FinishingURL(renderState.x, renderState.y);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int RichText::GetClickableTextIndexStart(int startIndex)
+{
+ // cycle to the right url panel for what is visible after the startIndex.
+ for (int i = 0; i < _clickableTextPanels.Count(); i++)
+ {
+ if (_clickableTextPanels[i]->GetViewTextIndex() >= startIndex)
+ {
+ return i - 1;
+ }
+ }
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Recalcultes the formatting state from the specified index
+//-----------------------------------------------------------------------------
+void RichText::RecalculateDefaultState(int startIndex)
+{
+ if (!HasText() )
+ return;
+
+ Assert(startIndex < m_TextStream.Count());
+
+ m_CachedRenderState.textColor = GetFgColor();
+ _pixelsIndent = 0;
+ _currentTextClickable = false;
+ _clickableTextIndex = GetClickableTextIndexStart(startIndex);
+
+ // find where in the formatting stream we need to be
+ GenerateRenderStateForTextStreamIndex(startIndex, m_CachedRenderState);
+ _recalcSavedRenderState = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: updates a render state based on the formatting and color streams
+// Output: true if we changed the render state
+//-----------------------------------------------------------------------------
+bool RichText::UpdateRenderState(int textStreamPos, TRenderState &renderState)
+{
+ // check the color stream
+ if (m_FormatStream.IsValidIndex(renderState.formatStreamIndex) &&
+ m_FormatStream[renderState.formatStreamIndex].textStreamIndex == textStreamPos)
+ {
+ // set the current formatting
+ renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color;
+ renderState.textClickable = m_FormatStream[renderState.formatStreamIndex].textClickable;
+
+ CalculateFade( renderState );
+
+ int indentChange = m_FormatStream[renderState.formatStreamIndex].pixelsIndent - renderState.pixelsIndent;
+ renderState.pixelsIndent = m_FormatStream[renderState.formatStreamIndex].pixelsIndent;
+
+ if (indentChange)
+ {
+ renderState.x = renderState.pixelsIndent + _drawOffsetX;
+ }
+
+ //!! for supporting old functionality, store off state in globals
+ _pixelsIndent = renderState.pixelsIndent;
+
+ // move to the next position in the color stream
+ renderState.formatStreamIndex++;
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the index in the format stream for the specified text stream index
+//-----------------------------------------------------------------------------
+int RichText::FindFormatStreamIndexForTextStreamPos(int textStreamIndex)
+{
+ int formatStreamIndex = 0;
+ for (; m_FormatStream.IsValidIndex(formatStreamIndex); formatStreamIndex++)
+ {
+ if (m_FormatStream[formatStreamIndex].textStreamIndex > textStreamIndex)
+ break;
+ }
+
+ // step back to the color change before the new line
+ formatStreamIndex--;
+ if (!m_FormatStream.IsValidIndex(formatStreamIndex))
+ {
+ formatStreamIndex = 0;
+ }
+ return formatStreamIndex;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Generates a base renderstate given a index into the text stream
+//-----------------------------------------------------------------------------
+void RichText::GenerateRenderStateForTextStreamIndex(int textStreamIndex, TRenderState &renderState)
+{
+ // find where in the format stream we need to be given the specified place in the text stream
+ renderState.formatStreamIndex = FindFormatStreamIndexForTextStreamPos(textStreamIndex);
+
+ // copy the state data
+ renderState.textColor = m_FormatStream[renderState.formatStreamIndex].color;
+ renderState.pixelsIndent = m_FormatStream[renderState.formatStreamIndex].pixelsIndent;
+ renderState.textClickable = m_FormatStream[renderState.formatStreamIndex].textClickable;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called pre render
+//-----------------------------------------------------------------------------
+void RichText::OnThink()
+{
+ if (m_bRecalcLineBreaks)
+ {
+ _recalcSavedRenderState = true;
+ RecalculateLineBreaks();
+
+ // recalculate scrollbar position
+ if (_invalidateVerticalScrollbarSlider)
+ {
+ LayoutVerticalScrollBarSlider();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when data changes or panel size changes
+//-----------------------------------------------------------------------------
+void RichText::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ // force a Repaint
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: inserts a color change into the formatting stream
+//-----------------------------------------------------------------------------
+void RichText::InsertColorChange(Color col)
+{
+ // see if color already exists in text stream
+ TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
+ if (prevItem.color == col)
+ {
+ // inserting same color into stream, just ignore
+ }
+ else if (prevItem.textStreamIndex == m_TextStream.Count())
+ {
+ // this item is in the same place; update values
+ prevItem.color = col;
+ }
+ else
+ {
+ // add to text stream, based off existing item
+ TFormatStream streamItem = prevItem;
+ streamItem.color = col;
+ streamItem.textStreamIndex = m_TextStream.Count();
+ m_FormatStream.AddToTail(streamItem);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: inserts a fade into the formatting stream
+//-----------------------------------------------------------------------------
+void RichText::InsertFade( float flSustain, float flLength )
+{
+ // see if color already exists in text stream
+ TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
+ if (prevItem.textStreamIndex == m_TextStream.Count())
+ {
+ // this item is in the same place; update values
+ prevItem.fade.flFadeStartTime = system()->GetCurrentTime() + flSustain;
+ prevItem.fade.flFadeSustain = flSustain;
+ prevItem.fade.flFadeLength = flLength;
+ prevItem.fade.iOriginalAlpha = prevItem.color.a();
+ }
+ else
+ {
+ // add to text stream, based off existing item
+ TFormatStream streamItem = prevItem;
+
+ prevItem.fade.flFadeStartTime = system()->GetCurrentTime() + flSustain;
+ prevItem.fade.flFadeLength = flLength;
+ prevItem.fade.flFadeSustain = flSustain;
+ prevItem.fade.iOriginalAlpha = prevItem.color.a();
+
+ streamItem.textStreamIndex = m_TextStream.Count();
+ m_FormatStream.AddToTail(streamItem);
+ }
+}
+
+void RichText::ResetAllFades( bool bHold, bool bOnlyExpired, float flNewSustain )
+{
+ m_bResetFades = bHold;
+
+ if ( m_bResetFades == false )
+ {
+ for (int i = 1; i < m_FormatStream.Count(); i++)
+ {
+ if ( bOnlyExpired == true )
+ {
+ if ( m_FormatStream[i].fade.flFadeStartTime >= system()->GetCurrentTime() )
+ continue;
+ }
+
+ if ( flNewSustain == -1.0f )
+ {
+ flNewSustain = m_FormatStream[i].fade.flFadeSustain;
+ }
+
+ m_FormatStream[i].fade.flFadeStartTime = system()->GetCurrentTime() + flNewSustain;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: inserts an indent change into the formatting stream
+//-----------------------------------------------------------------------------
+void RichText::InsertIndentChange(int pixelsIndent)
+{
+ if (pixelsIndent < 0)
+ {
+ pixelsIndent = 0;
+ }
+ else if (pixelsIndent > 255)
+ {
+ pixelsIndent = 255;
+ }
+
+ // see if indent change already exists in text stream
+ TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
+ if (prevItem.pixelsIndent == pixelsIndent)
+ {
+ // inserting same indent into stream, just ignore
+ }
+ else if (prevItem.textStreamIndex == m_TextStream.Count())
+ {
+ // this item is in the same place; update
+ prevItem.pixelsIndent = pixelsIndent;
+ }
+ else
+ {
+ // add to text stream, based off existing item
+ TFormatStream streamItem = prevItem;
+ streamItem.pixelsIndent = pixelsIndent;
+ streamItem.textStreamIndex = m_TextStream.Count();
+ m_FormatStream.AddToTail(streamItem);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inserts character Start for clickable text, eg. URLS
+//-----------------------------------------------------------------------------
+void RichText::InsertClickableTextStart( const char *pchClickAction )
+{
+ // see if indent change already exists in text stream
+ TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
+ TFormatStream *pFormatStream = &prevItem;
+ if (prevItem.textStreamIndex == m_TextStream.Count())
+ {
+ // this item is in the same place; update
+ prevItem.textClickable = true;
+ pFormatStream->m_sClickableTextAction = pchClickAction;
+ }
+ else
+ {
+ // add to text stream, based off existing item
+ TFormatStream formatStreamCopy = prevItem;
+ int iFormatStream = m_FormatStream.AddToTail( formatStreamCopy );
+
+ // set the new params
+ pFormatStream = &m_FormatStream[iFormatStream];
+ pFormatStream->textStreamIndex = m_TextStream.Count();
+ pFormatStream->textClickable = true;
+ pFormatStream->m_sClickableTextAction = pchClickAction;
+ }
+
+ // invalidate the layout to recalculate where the click panels should go
+ InvalidateLineBreakStream();
+ InvalidateLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inserts character end for clickable text, eg. URLS
+//-----------------------------------------------------------------------------
+void RichText::InsertClickableTextEnd()
+{
+ // see if indent change already exists in text stream
+ TFormatStream &prevItem = m_FormatStream[m_FormatStream.Count() - 1];
+ if (!prevItem.textClickable)
+ {
+ // inserting same indent into stream, just ignore
+ }
+ else if (prevItem.textStreamIndex == m_TextStream.Count())
+ {
+ // this item is in the same place; update
+ prevItem.textClickable = false;
+ }
+ else
+ {
+ // add to text stream, based off existing item
+ TFormatStream streamItem = prevItem;
+ streamItem.textClickable = false;
+ streamItem.textStreamIndex = m_TextStream.Count();
+ m_FormatStream.AddToTail(streamItem);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: moves x,y to the Start of the next line of text
+//-----------------------------------------------------------------------------
+void RichText::AddAnotherLine(int &cx, int &cy)
+{
+ cx = _drawOffsetX + _pixelsIndent;
+ cy += (GetLineHeight() + _drawOffsetY);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Recalculates line breaks
+//-----------------------------------------------------------------------------
+void RichText::RecalculateLineBreaks()
+{
+ if ( !m_bRecalcLineBreaks )
+ return;
+
+ int wide = GetWide();
+ if (!wide)
+ return;
+
+ wide -= _drawOffsetX;
+
+ m_bRecalcLineBreaks = false;
+ _recalcSavedRenderState = true;
+ if (!HasText())
+ return;
+
+ int selection0 = -1, selection1 = -1;
+
+ // subtract the scrollbar width
+ if (_vertScrollBar->IsVisible())
+ {
+ wide -= _vertScrollBar->GetWide();
+ }
+
+ int x = _drawOffsetX, y = _drawOffsetY;
+
+ HFont fontWordStart = INVALID_FONT;
+ int wordStartIndex = 0;
+ int lineStartIndex = 0;
+ bool hasWord = false;
+ bool justStartedNewLine = true;
+ bool wordStartedOnNewLine = true;
+
+ int startChar = 0;
+ if (_recalculateBreaksIndex <= 0)
+ {
+ m_LineBreaks.RemoveAll();
+ }
+ else
+ {
+ // remove the rest of the linebreaks list since its out of date.
+ for (int i = _recalculateBreaksIndex + 1; i < m_LineBreaks.Count(); ++i)
+ {
+ m_LineBreaks.Remove(i);
+ --i; // removing shrinks the list!
+ }
+ startChar = m_LineBreaks[_recalculateBreaksIndex];
+ lineStartIndex = m_LineBreaks[_recalculateBreaksIndex];
+ wordStartIndex = lineStartIndex;
+ }
+
+ // handle the case where this char is a new line, in that case
+ // we have already taken its break index into account above so skip it.
+ if (m_TextStream[startChar] == '\r' || m_TextStream[startChar] == '\n')
+ {
+ startChar++;
+ lineStartIndex = startChar;
+ }
+
+ // cycle to the right url panel for what is visible after the startIndex.
+ int clickableTextNum = GetClickableTextIndexStart(startChar);
+ clickableTextNum++;
+
+ // initialize the renderstate with the start
+ TRenderState renderState;
+ GenerateRenderStateForTextStreamIndex(startChar, renderState);
+ _currentTextClickable = false;
+
+ HFont font = _font;
+
+ bool bForceBreak = false;
+ float flLineWidthSoFar = 0;
+
+ // loop through all the characters
+ for (int i = startChar; i < m_TextStream.Count(); ++i)
+ {
+ wchar_t ch = m_TextStream[i];
+ renderState.x = x;
+ if (UpdateRenderState(i, renderState))
+ {
+ x = renderState.x;
+ int preI = i;
+
+ // check for clickable text
+ if (renderState.textClickable != _currentTextClickable)
+ {
+ if (renderState.textClickable)
+ {
+ // make a new clickable text panel
+ if (clickableTextNum >= _clickableTextPanels.Count())
+ {
+ _clickableTextPanels.AddToTail(new ClickPanel(this));
+ }
+
+ ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++];
+ clickPanel->SetTextIndex(preI, preI);
+ }
+
+ // url state change
+ _currentTextClickable = renderState.textClickable;
+ }
+ }
+
+ bool bIsWSpace = iswspace( ch ) ? true : false;
+
+ bool bPreviousWordStartedOnNewLine = wordStartedOnNewLine;
+ int iPreviousWordStartIndex = wordStartIndex;
+ if ( !bIsWSpace && ch != L'\t' && ch != L'\n' && ch != L'\r' )
+ {
+ if (!hasWord)
+ {
+ // Start a new word
+ wordStartIndex = i;
+ hasWord = true;
+ wordStartedOnNewLine = justStartedNewLine;
+ fontWordStart = font;
+ }
+ // else append to the current word
+ }
+ else
+ {
+ // whitespace/punctuation character
+ // end the word
+ hasWord = false;
+ }
+
+ float w = 0;
+ wchar_t wchBefore = 0;
+ wchar_t wchAfter = 0;
+
+ if ( i > 0 && i > lineStartIndex && i != selection0 && i-1 != selection1 )
+ wchBefore = m_TextStream[i-1];
+ if ( i < m_TextStream.Count() - 1 && i+1 != selection0 && i != selection1 )
+ wchAfter = m_TextStream[i+1];
+
+ float flabcA;
+ surface()->GetKernedCharWidth( font, ch, wchBefore, wchAfter, w, flabcA );
+ flLineWidthSoFar += w;
+
+ // See if we've exceeded the width we have available, with
+ if ( floor(flLineWidthSoFar + 0.6) + x > wide )
+ {
+ bForceBreak = true;
+ }
+
+ if (!iswcntrl(ch))
+ {
+ justStartedNewLine = false;
+ }
+
+ if ( bForceBreak || ch == '\r' || ch == '\n' )
+ {
+ bForceBreak = false;
+ // add another line
+ AddAnotherLine(x, y);
+
+ if ( ch == '\r' || ch == '\n' )
+ {
+ // skip the newline so it's not at the beginning of the new line
+ lineStartIndex = i + 1;
+ m_LineBreaks.AddToTail(i + 1);
+ }
+ else if ( bPreviousWordStartedOnNewLine || iPreviousWordStartIndex <= lineStartIndex )
+ {
+ lineStartIndex = i;
+ m_LineBreaks.AddToTail( i );
+
+ if (renderState.textClickable)
+ {
+ // need to split the url into two panels
+ int oldIndex = _clickableTextPanels[clickableTextNum - 1]->GetTextIndex();
+
+ // make a new clickable text panel
+ if (clickableTextNum >= _clickableTextPanels.Count())
+ {
+ _clickableTextPanels.AddToTail(new ClickPanel(this));
+ }
+
+ ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++];
+ clickPanel->SetTextIndex(oldIndex, i);
+ }
+ }
+ else
+ {
+ m_LineBreaks.AddToTail( iPreviousWordStartIndex );
+ lineStartIndex = iPreviousWordStartIndex;
+ i = iPreviousWordStartIndex;
+
+ TRenderState renderStateAtLastWord;
+ GenerateRenderStateForTextStreamIndex( i, renderStateAtLastWord );
+
+ // If the word is clickable, and that started prior to the beginning of the word, then we must split the click panel
+ if ( renderStateAtLastWord.textClickable && m_FormatStream[ renderStateAtLastWord.formatStreamIndex ].textStreamIndex < i )
+ {
+ // need to split the url into two panels
+ int oldIndex = _clickableTextPanels[clickableTextNum - 1]->GetTextIndex();
+
+ // make a new clickable text panel
+ if (clickableTextNum >= _clickableTextPanels.Count())
+ {
+ _clickableTextPanels.AddToTail(new ClickPanel(this));
+ }
+
+ ClickPanel *clickPanel = _clickableTextPanels[clickableTextNum++];
+ clickPanel->SetTextIndex(oldIndex, i);
+ }
+ }
+
+ flLineWidthSoFar = 0;
+ justStartedNewLine = true;
+ hasWord = false;
+ wordStartedOnNewLine = false;
+ _currentTextClickable = false;
+ continue;
+ }
+ }
+
+ // end the list
+ m_LineBreaks.AddToTail(MAX_BUFFER_SIZE);
+
+ // set up the scrollbar
+ _invalidateVerticalScrollbarSlider = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Recalculate where the vertical scroll bar slider should be
+// based on the current cursor line we are on.
+//-----------------------------------------------------------------------------
+void RichText::LayoutVerticalScrollBarSlider()
+{
+ _invalidateVerticalScrollbarSlider = false;
+
+ // set up the scrollbar
+ //if (!_vertScrollBar->IsVisible())
+ // return;
+
+
+ // see where the scrollbar currently is
+ int previousValue = _vertScrollBar->GetValue();
+ bool bCurrentlyAtEnd = false;
+ int rmin, rmax;
+ _vertScrollBar->GetRange(rmin, rmax);
+ if (rmax && (previousValue + rmin + _vertScrollBar->GetRangeWindow() == rmax))
+ {
+ bCurrentlyAtEnd = true;
+ }
+
+ // work out position to put scrollbar, factoring in insets
+ int wide, tall;
+ GetSize( wide, tall );
+
+ _vertScrollBar->SetPos( wide - _vertScrollBar->GetWide(), 0 );
+ // scrollbar is inside the borders.
+ _vertScrollBar->SetSize( _vertScrollBar->GetWide(), tall );
+
+ // calculate how many lines we can fully display
+ int displayLines = tall / (GetLineHeight() + _drawOffsetY);
+ int numLines = m_LineBreaks.Count();
+
+ if (numLines <= displayLines)
+ {
+ // disable the scrollbar
+ _vertScrollBar->SetEnabled(false);
+ _vertScrollBar->SetRange(0, numLines);
+ _vertScrollBar->SetRangeWindow(numLines);
+ _vertScrollBar->SetValue(0);
+
+ if ( m_bUnusedScrollbarInvis )
+ {
+ SetVerticalScrollbar( false );
+ }
+ }
+ else
+ {
+ if ( m_bUnusedScrollbarInvis )
+ {
+ SetVerticalScrollbar( true );
+ }
+
+ // set the scrollbars range
+ _vertScrollBar->SetRange(0, numLines);
+ _vertScrollBar->SetRangeWindow(displayLines);
+ _vertScrollBar->SetEnabled(true);
+
+ // this should make it scroll one line at a time
+ _vertScrollBar->SetButtonPressedScrollValue(1);
+ if (bCurrentlyAtEnd)
+ {
+ _vertScrollBar->SetValue(numLines - displayLines);
+ }
+ _vertScrollBar->InvalidateLayout();
+ _vertScrollBar->Repaint();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets whether a vertical scrollbar is visible
+//-----------------------------------------------------------------------------
+void RichText::SetVerticalScrollbar(bool state)
+{
+ if (_vertScrollBar->IsVisible() != state)
+ {
+ _vertScrollBar->SetVisible(state);
+ InvalidateLineBreakStream();
+ InvalidateLayout();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create cut/copy/paste dropdown menu
+//-----------------------------------------------------------------------------
+void RichText::CreateEditMenu()
+{
+ // create a drop down cut/copy/paste menu appropriate for this object's states
+ if (m_pEditMenu)
+ delete m_pEditMenu;
+ m_pEditMenu = new Menu(this, "EditMenu");
+
+
+ // add cut/copy/paste drop down options if its editable, just copy if it is not
+ m_pEditMenu->AddMenuItem("C&opy", new KeyValues("DoCopySelected"), this);
+
+ m_pEditMenu->SetVisible(false);
+ m_pEditMenu->SetParent(this);
+ m_pEditMenu->AddActionSignalTarget(this);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: We want single line windows to scroll horizontally and select text
+// in response to clicking and holding outside window
+//-----------------------------------------------------------------------------
+void RichText::OnMouseFocusTicked()
+{
+ // if a button is down move the scrollbar slider the appropriate direction
+ if (_mouseDragSelection) // text is being selected via mouse clicking and dragging
+ {
+ OnCursorMoved(0,0); // we want the text to scroll as if we were dragging
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: If a cursor enters the window, we are not elegible for
+// MouseFocusTicked events
+//-----------------------------------------------------------------------------
+void RichText::OnCursorEntered()
+{
+ _mouseDragSelection = false; // outside of window dont recieve drag scrolling ticks
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: When the cursor is outside the window, if we are holding the mouse
+// button down, then we want the window to scroll the text one char at a time using Ticks
+//-----------------------------------------------------------------------------
+void RichText::OnCursorExited()
+{
+ // outside of window recieve drag scrolling ticks
+ if (_mouseSelection)
+ {
+ _mouseDragSelection = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle selection of text by mouse
+//-----------------------------------------------------------------------------
+void RichText::OnCursorMoved(int x, int y)
+{
+ if (_mouseSelection)
+ {
+ // update the cursor position
+ int x, y;
+ input()->GetCursorPos(x, y);
+ ScreenToLocal(x, y);
+ _cursorPos = PixelToCursorSpace(x, y);
+
+ if (_cursorPos != _select[1])
+ {
+ _select[1] = _cursorPos;
+ Repaint();
+ }
+ // Msg( "selecting range [%d..%d]\n", _select[0], _select[1] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle mouse button down events.
+//-----------------------------------------------------------------------------
+void RichText::OnMousePressed(MouseCode code)
+{
+ if (code == MOUSE_LEFT)
+ {
+ // clear current selection
+ SelectNone();
+
+ // move the cursor to where the mouse was pressed
+ int x, y;
+ input()->GetCursorPos(x, y);
+ ScreenToLocal(x, y);
+
+ _cursorPos = PixelToCursorSpace(x, y);
+
+ if ( m_bInteractive )
+ {
+ // enter selection mode
+ input()->SetMouseCapture(GetVPanel());
+ _mouseSelection = true;
+
+ if (_select[0] < 0)
+ {
+ // if no initial selection position, Start selection position at cursor
+ _select[0] = _cursorPos;
+ }
+ _select[1] = _cursorPos;
+ }
+
+ RequestFocus();
+ Repaint();
+ }
+ else if (code == MOUSE_RIGHT) // check for context menu open
+ {
+ if ( m_bInteractive )
+ {
+ CreateEditMenu();
+ Assert(m_pEditMenu);
+
+ OpenEditMenu();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle mouse button up events
+//-----------------------------------------------------------------------------
+void RichText::OnMouseReleased(MouseCode code)
+{
+ _mouseSelection = false;
+ input()->SetMouseCapture(NULL);
+
+ // make sure something has been selected
+ int cx0, cx1;
+ if (GetSelectedRange(cx0, cx1))
+ {
+ if (cx1 - cx0 == 0)
+ {
+ // nullify selection
+ _select[0] = -1;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle mouse double clicks
+//-----------------------------------------------------------------------------
+void RichText::OnMouseDoublePressed(MouseCode code)
+{
+ if ( !m_bInteractive )
+ return;
+
+ // left double clicking on a word selects the word
+ if (code == MOUSE_LEFT)
+ {
+ // move the cursor just as if you single clicked.
+ OnMousePressed(code);
+ // then find the start and end of the word we are in to highlight it.
+ int selectSpot[2];
+ GotoWordLeft();
+ selectSpot[0] = _cursorPos;
+ GotoWordRight();
+ selectSpot[1] = _cursorPos;
+
+ if ( _cursorPos > 0 && (_cursorPos-1) < m_TextStream.Count() )
+ {
+ if (iswspace(m_TextStream[_cursorPos-1]))
+ {
+ selectSpot[1]--;
+ _cursorPos--;
+ }
+ }
+
+ _select[0] = selectSpot[0];
+ _select[1] = selectSpot[1];
+ _mouseSelection = true;
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Turn off text selection code when mouse button is not down
+//-----------------------------------------------------------------------------
+void RichText::OnMouseCaptureLost()
+{
+ _mouseSelection = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Masks which keys get chained up
+// Maps keyboard input to text window functions.
+//-----------------------------------------------------------------------------
+void RichText::OnKeyCodeTyped(KeyCode code)
+{
+ bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT));
+ bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL));
+ bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT));
+ bool winkey = (input()->IsKeyDown(KEY_LWIN) || input()->IsKeyDown(KEY_RWIN));
+ bool fallThrough = false;
+
+ if ( ctrl || ( winkey && IsOSX() ) )
+ {
+ switch(code)
+ {
+ case KEY_INSERT:
+ case KEY_C:
+ case KEY_X:
+ {
+ CopySelected();
+ break;
+ }
+ case KEY_PAGEUP:
+ case KEY_HOME:
+ {
+ GotoTextStart();
+ break;
+ }
+ case KEY_PAGEDOWN:
+ case KEY_END:
+ {
+ GotoTextEnd();
+ break;
+ }
+ default:
+ {
+ fallThrough = true;
+ break;
+ }
+ }
+ }
+ else if (alt)
+ {
+ // do nothing with ALT-x keys
+ fallThrough = true;
+ }
+ else
+ {
+ switch(code)
+ {
+ case KEY_TAB:
+ case KEY_LSHIFT:
+ case KEY_RSHIFT:
+ case KEY_ESCAPE:
+ case KEY_ENTER:
+ {
+ fallThrough = true;
+ break;
+ }
+ case KEY_DELETE:
+ {
+ if (shift)
+ {
+ // shift-delete is cut
+ CopySelected();
+ }
+ break;
+ }
+ case KEY_HOME:
+ {
+ GotoTextStart();
+ break;
+ }
+ case KEY_END:
+ {
+ GotoTextEnd();
+ break;
+ }
+ case KEY_PAGEUP:
+ {
+ // if there is a scroll bar scroll down one rangewindow
+ if (_vertScrollBar->IsVisible())
+ {
+ int window = _vertScrollBar->GetRangeWindow();
+ int newval = _vertScrollBar->GetValue();
+ _vertScrollBar->SetValue(newval - window - 1);
+ }
+ break;
+
+ }
+ case KEY_PAGEDOWN:
+ {
+ // if there is a scroll bar scroll down one rangewindow
+ if (_vertScrollBar->IsVisible())
+ {
+ int window = _vertScrollBar->GetRangeWindow();
+ int newval = _vertScrollBar->GetValue();
+ _vertScrollBar->SetValue(newval + window + 1);
+ }
+ break;
+ }
+ default:
+ {
+ // return if any other char is pressed.
+ // as it will be a unicode char.
+ // and we don't want select[1] changed unless a char was pressed that this fxn handles
+ return;
+ }
+ }
+ }
+
+ // select[1] is the location in the line where the blinking cursor started
+ _select[1] = _cursorPos;
+
+ // chain back on some keys
+ if (fallThrough)
+ {
+ BaseClass::OnKeyCodeTyped(code);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Scrolls the list according to the mouse wheel movement
+//-----------------------------------------------------------------------------
+void RichText::OnMouseWheeled(int delta)
+{
+ MoveScrollBar(delta);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Scrolls the list
+// Input : delta - amount to move scrollbar up
+//-----------------------------------------------------------------------------
+void RichText::MoveScrollBar(int delta)
+{
+ MoveScrollBarDirect( delta * 3 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Scrolls the list
+// Input : delta - amount to move scrollbar up
+//-----------------------------------------------------------------------------
+void RichText::MoveScrollBarDirect(int delta)
+{
+ if (_vertScrollBar->IsVisible())
+ {
+ int val = _vertScrollBar->GetValue();
+ val -= delta;
+ _vertScrollBar->SetValue(val);
+ _recalcSavedRenderState = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: set the maximum number of chars in the text buffer
+//-----------------------------------------------------------------------------
+void RichText::SetMaximumCharCount(int maxChars)
+{
+ _maxCharCount = maxChars;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find out what line the cursor is on
+//-----------------------------------------------------------------------------
+int RichText::GetCursorLine()
+{
+ // always returns the last place
+ int pos = m_LineBreaks[m_LineBreaks.Count() - 1];
+ Assert(pos == MAX_BUFFER_SIZE);
+ return pos;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move the cursor over to the Start of the next word to the right
+//-----------------------------------------------------------------------------
+void RichText::GotoWordRight()
+{
+ // search right until we hit a whitespace character or a newline
+ while (++_cursorPos < m_TextStream.Count())
+ {
+ if (iswspace(m_TextStream[_cursorPos]))
+ break;
+ }
+
+ // search right until we hit an nonspace character
+ while (++_cursorPos < m_TextStream.Count())
+ {
+ if (!iswspace(m_TextStream[_cursorPos]))
+ break;
+ }
+
+ if (_cursorPos > m_TextStream.Count())
+ {
+ _cursorPos = m_TextStream.Count();
+ }
+
+ // now we are at the start of the next word
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move the cursor over to the Start of the next word to the left
+//-----------------------------------------------------------------------------
+void RichText::GotoWordLeft()
+{
+ if (_cursorPos < 1)
+ return;
+
+ // search left until we hit an nonspace character
+ while (--_cursorPos >= 0)
+ {
+ if (!iswspace(m_TextStream[_cursorPos]))
+ break;
+ }
+
+ // search left until we hit a whitespace character
+ while (--_cursorPos >= 0)
+ {
+ if (iswspace(m_TextStream[_cursorPos]))
+ {
+ break;
+ }
+ }
+
+ // we end one character off
+ _cursorPos++;
+
+ // now we are at the start of the previous word
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move cursor to the Start of the text buffer
+//-----------------------------------------------------------------------------
+void RichText::GotoTextStart()
+{
+ _cursorPos = 0; // set cursor to start
+ _invalidateVerticalScrollbarSlider = true;
+ // force scrollbar to the top
+ _vertScrollBar->SetValue(0);
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Move cursor to the end of the text buffer
+//-----------------------------------------------------------------------------
+void RichText::GotoTextEnd()
+{
+ _cursorPos = m_TextStream.Count(); // set cursor to end of buffer
+ _invalidateVerticalScrollbarSlider = true;
+
+ // force the scrollbar to the bottom
+ int min, max;
+ _vertScrollBar->GetRange(min, max);
+ _vertScrollBar->SetValue(max);
+
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Culls the text stream down to a managable size
+//-----------------------------------------------------------------------------
+void RichText::TruncateTextStream()
+{
+ if (_maxCharCount < 1)
+ return;
+
+ // choose a point to cull at
+ int cullPos = _maxCharCount / 2;
+
+ // kill half the buffer
+ m_TextStream.RemoveMultiple(0, cullPos);
+
+ // work out where in the format stream we can start
+ int formatIndex = FindFormatStreamIndexForTextStreamPos(cullPos);
+ if (formatIndex > 0)
+ {
+ // take a copy, make it first
+ m_FormatStream[0] = m_FormatStream[formatIndex];
+ m_FormatStream[0].textStreamIndex = 0;
+ // kill the others
+ m_FormatStream.RemoveMultiple(1, formatIndex);
+ }
+
+ // renormalize the remainder of the format stream
+ for (int i = 1; i < m_FormatStream.Count(); i++)
+ {
+ Assert(m_FormatStream[i].textStreamIndex > cullPos);
+ m_FormatStream[i].textStreamIndex -= cullPos;
+ }
+
+ // mark everything to be recalculated
+ InvalidateLineBreakStream();
+ InvalidateLayout();
+ _invalidateVerticalScrollbarSlider = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Insert a character into the text buffer
+//-----------------------------------------------------------------------------
+void RichText::InsertChar(wchar_t wch)
+{
+ // throw away redundant linefeed characters
+ if ( wch == '\r' )
+ return;
+
+ if (_maxCharCount > 0 && m_TextStream.Count() > _maxCharCount)
+ {
+ TruncateTextStream();
+ }
+
+ // insert the new char at the end of the buffer
+ m_TextStream.AddToTail(wch);
+
+ // mark the linebreak steam as needing recalculating from that point
+ _recalculateBreaksIndex = m_LineBreaks.Count() - 2;
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Insert a string into the text buffer, this is just a series
+// of char inserts because we have to check each char is ok to insert
+//-----------------------------------------------------------------------------
+void RichText::InsertString(const char *text)
+{
+ if (text[0] == '#')
+ {
+ wchar_t unicode[ 1024 ];
+ ResolveLocalizedTextAndVariables( text, unicode, sizeof( unicode ) );
+ InsertString( unicode );
+ return;
+ }
+
+ // upgrade the ansi text to unicode to display it
+ int len = strlen(text);
+ wchar_t *unicode = (wchar_t *)_alloca((len + 1) * sizeof(wchar_t));
+ Q_UTF8ToUnicode(text, unicode, ((len + 1) * sizeof(wchar_t)));
+ InsertString(unicode);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Insertsa a unicode string into the buffer
+//-----------------------------------------------------------------------------
+void RichText::InsertString(const wchar_t *wszText)
+{
+ // insert the whole string
+ for (const wchar_t *ch = wszText; *ch != 0; ++ch)
+ {
+ InsertChar(*ch);
+ }
+ InvalidateLayout();
+ m_bRecalcLineBreaks = true;
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Declare a selection empty
+//-----------------------------------------------------------------------------
+void RichText::SelectNone()
+{
+ // tag the selection as empty
+ _select[0] = -1;
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Load in the selection range so cx0 is the Start and cx1 is the end
+// from smallest to highest (right to left)
+//-----------------------------------------------------------------------------
+bool RichText::GetSelectedRange(int &cx0, int &cx1)
+{
+ // if there is nothing selected return false
+ if (_select[0] == -1)
+ return false;
+
+ // sort the two position so cx0 is the smallest
+ cx0 = _select[0];
+ cx1 = _select[1];
+ if (cx1 < cx0)
+ {
+ int temp = cx0;
+ cx0 = cx1;
+ cx1 = temp;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Opens the cut/copy/paste dropdown menu
+//-----------------------------------------------------------------------------
+void RichText::OpenEditMenu()
+{
+ // get cursor position, this is local to this text edit window
+ // so we need to adjust it relative to the parent
+ int cursorX, cursorY;
+ input()->GetCursorPos(cursorX, cursorY);
+
+ /* !! disabled since it recursively gets panel pointers, potentially across dll boundaries,
+ and doesn't need to be necessary (it's just for handling windowed mode)
+
+ // find the frame that has no parent (the one on the desktop)
+ Panel *panel = this;
+ while ( panel->GetParent() != NULL)
+ {
+ panel = panel->GetParent();
+ }
+ panel->ScreenToLocal(cursorX, cursorY);
+ int x, y;
+ // get base panel's postition
+ panel->GetPos(x, y);
+
+ // adjust our cursor position accordingly
+ cursorX += x;
+ cursorY += y;
+ */
+
+ int x0, x1;
+ if (GetSelectedRange(x0, x1)) // there is something selected
+ {
+ m_pEditMenu->SetItemEnabled("&Cut", true);
+ m_pEditMenu->SetItemEnabled("C&opy", true);
+ }
+ else // there is nothing selected, disable cut/copy options
+ {
+ m_pEditMenu->SetItemEnabled("&Cut", false);
+ m_pEditMenu->SetItemEnabled("C&opy", false);
+ }
+ m_pEditMenu->SetVisible(true);
+ m_pEditMenu->RequestFocus();
+
+ // relayout the menu immediately so that we know it's size
+ m_pEditMenu->InvalidateLayout(true);
+ int menuWide, menuTall;
+ m_pEditMenu->GetSize(menuWide, menuTall);
+
+ // work out where the cursor is and therefore the best place to put the menu
+ int wide, tall;
+ surface()->GetScreenSize(wide, tall);
+
+ if (wide - menuWide > cursorX)
+ {
+ // menu hanging right
+ if (tall - menuTall > cursorY)
+ {
+ // menu hanging down
+ m_pEditMenu->SetPos(cursorX, cursorY);
+ }
+ else
+ {
+ // menu hanging up
+ m_pEditMenu->SetPos(cursorX, cursorY - menuTall);
+ }
+ }
+ else
+ {
+ // menu hanging left
+ if (tall - menuTall > cursorY)
+ {
+ // menu hanging down
+ m_pEditMenu->SetPos(cursorX - menuWide, cursorY);
+ }
+ else
+ {
+ // menu hanging up
+ m_pEditMenu->SetPos(cursorX - menuWide, cursorY - menuTall);
+ }
+ }
+
+ m_pEditMenu->RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Cuts the selected chars from the buffer and
+// copies them into the clipboard
+//-----------------------------------------------------------------------------
+void RichText::CutSelected()
+{
+ CopySelected();
+ // have to request focus if we used the menu
+ RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Copies the selected chars into the clipboard
+//-----------------------------------------------------------------------------
+void RichText::CopySelected()
+{
+ int x0, x1;
+ if (GetSelectedRange(x0, x1))
+ {
+ CUtlVector<wchar_t> buf;
+ for (int i = x0; i <= x1; i++)
+ {
+ if ( m_TextStream.IsValidIndex(i) == false )
+ continue;
+
+ if (m_TextStream[i] == '\n')
+ {
+ buf.AddToTail( '\r' );
+ }
+ // remove any rich edit commands
+ buf.AddToTail(m_TextStream[i]);
+ }
+ buf.AddToTail('\0');
+ system()->SetClipboardText(buf.Base(), buf.Count() - 1);
+ }
+
+ // have to request focus if we used the menu
+ RequestFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the index in the text buffer of the
+// character the drawing should Start at
+//-----------------------------------------------------------------------------
+int RichText::GetStartDrawIndex(int &lineBreakIndexIndex)
+{
+ int startIndex = 0;
+ int startLine = _vertScrollBar->GetValue();
+
+ if ( startLine >= m_LineBreaks.Count() ) // incase the line breaks got reset and the scroll bar hasn't
+ {
+ startLine = m_LineBreaks.Count() - 1;
+ }
+
+ lineBreakIndexIndex = startLine;
+ if (startLine && startLine < m_LineBreaks.Count())
+ {
+ startIndex = m_LineBreaks[startLine - 1];
+ }
+
+ return startIndex;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get a string from text buffer
+// Input: offset - index to Start reading from
+// bufLen - length of string
+//-----------------------------------------------------------------------------
+void RichText::GetText(int offset, wchar_t *buf, int bufLenInBytes)
+{
+ if (!buf)
+ return;
+
+ Assert( bufLenInBytes >= sizeof(buf[0]) );
+ int bufLen = bufLenInBytes / sizeof(wchar_t);
+ int i;
+ for (i = offset; i < (offset + bufLen - 1); i++)
+ {
+ if (i >= m_TextStream.Count())
+ break;
+
+ buf[i-offset] = m_TextStream[i];
+ }
+ buf[(i-offset)] = 0;
+ buf[bufLen-1] = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: gets text from the buffer
+//-----------------------------------------------------------------------------
+void RichText::GetText(int offset, char *pch, int bufLenInBytes)
+{
+ wchar_t rgwchT[4096];
+ GetText(offset, rgwchT, sizeof(rgwchT));
+ Q_UnicodeToUTF8(rgwchT, pch, bufLenInBytes);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the font of the buffer text
+//-----------------------------------------------------------------------------
+void RichText::SetFont(HFont font)
+{
+ _font = font;
+ InvalidateLayout();
+ m_bRecalcLineBreaks = true;
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when the scrollbar slider is moved
+//-----------------------------------------------------------------------------
+void RichText::OnSliderMoved()
+{
+ _recalcSavedRenderState = true;
+ Repaint();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool RichText::RequestInfo(KeyValues *outputData)
+{
+ if (!stricmp(outputData->GetName(), "GetText"))
+ {
+ wchar_t wbuf[512];
+ GetText(0, wbuf, sizeof(wbuf));
+ outputData->SetWString("text", wbuf);
+ return true;
+ }
+
+ return BaseClass::RequestInfo(outputData);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void RichText::OnSetText(const wchar_t *text)
+{
+ SetText(text);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when a URL, etc has been clicked on
+//-----------------------------------------------------------------------------
+void RichText::OnClickPanel(int index)
+{
+ wchar_t wBuf[512];
+ int outIndex = 0;
+
+ // parse out the clickable text, and send it to our listeners
+ _currentTextClickable = true;
+ TRenderState renderState;
+ GenerateRenderStateForTextStreamIndex(index, renderState);
+ for (int i = index; i < (sizeof(wBuf) - 1) && i < m_TextStream.Count(); i++)
+ {
+ // stop getting characters when text is no longer clickable
+ UpdateRenderState(i, renderState);
+ if (!renderState.textClickable)
+ break;
+
+ // copy out the character
+ wBuf[outIndex++] = m_TextStream[i];
+ }
+
+ wBuf[outIndex] = 0;
+
+ int iFormatSteam = FindFormatStreamIndexForTextStreamPos( index );
+ if ( m_FormatStream[iFormatSteam].m_sClickableTextAction )
+ {
+ Q_UTF8ToUnicode( m_FormatStream[iFormatSteam].m_sClickableTextAction.String(), wBuf, sizeof( wBuf ) );
+ }
+
+ PostActionSignal(new KeyValues("TextClicked", "text", wBuf));
+ OnTextClicked(wBuf);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void RichText::ApplySettings(KeyValues *inResourceData)
+{
+ BaseClass::ApplySettings(inResourceData);
+ SetMaximumCharCount(inResourceData->GetInt("maxchars", -1));
+ SetVerticalScrollbar(inResourceData->GetInt("scrollbar", 1));
+
+ // get the starting text, if any
+ const char *text = inResourceData->GetString("text", "");
+ if (*text)
+ {
+ delete [] m_pszInitialText;
+ int len = Q_strlen(text) + 1;
+ m_pszInitialText = new char[ len ];
+ Q_strncpy( m_pszInitialText, text, len );
+ SetText(text);
+ }
+ else
+ {
+ const char *textfilename = inResourceData->GetString("textfile", NULL);
+ if ( textfilename )
+ {
+ FileHandle_t f = g_pFullFileSystem->Open( textfilename, "rt" );
+ if (!f)
+ {
+ Warning( "RichText: textfile parameter '%s' not found.\n", textfilename );
+ return;
+ }
+
+ int len = g_pFullFileSystem->Size( f );
+ delete [] m_pszInitialText;
+ m_pszInitialText = new char[ len + 1 ];
+ g_pFullFileSystem->Read( m_pszInitialText, len, f );
+ m_pszInitialText[len - 1] = 0;
+ SetText( m_pszInitialText );
+
+ g_pFullFileSystem->Close( f );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void RichText::GetSettings(KeyValues *outResourceData)
+{
+ BaseClass::GetSettings(outResourceData);
+ outResourceData->SetInt("maxchars", _maxCharCount);
+ outResourceData->SetInt("scrollbar", _vertScrollBar->IsVisible() );
+ if (m_pszInitialText)
+ {
+ outResourceData->SetString("text", m_pszInitialText);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *RichText::GetDescription()
+{
+ static char buf[1024];
+ Q_snprintf(buf, sizeof(buf), "%s, string text, bool scrollbar", BaseClass::GetDescription());
+ return buf;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the number of lines in the window
+//-----------------------------------------------------------------------------
+int RichText::GetNumLines()
+{
+ return m_LineBreaks.Count();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the height of the text entry window so all text will fit inside
+//-----------------------------------------------------------------------------
+void RichText::SetToFullHeight()
+{
+ PerformLayout();
+ int wide, tall;
+ GetSize(wide, tall);
+
+ tall = GetNumLines() * (GetLineHeight() + _drawOffsetY) + _drawOffsetY + 2;
+ SetSize (wide, tall);
+ PerformLayout();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Select all the text.
+//-----------------------------------------------------------------------------
+void RichText::SelectAllText()
+{
+ _cursorPos = 0;
+ _select[0] = 0;
+ _select[1] = m_TextStream.Count();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Select all the text.
+//-----------------------------------------------------------------------------
+void RichText::SelectNoText()
+{
+ _select[0] = 0;
+ _select[1] = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void RichText::OnSetFocus()
+{
+ BaseClass::OnSetFocus();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Invalidates the current linebreak stream
+//-----------------------------------------------------------------------------
+void RichText::InvalidateLineBreakStream()
+{
+ // clear the buffer
+ m_LineBreaks.RemoveAll();
+ m_LineBreaks.AddToTail(MAX_BUFFER_SIZE);
+ _recalculateBreaksIndex = 0;
+ m_bRecalcLineBreaks = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Inserts a text string while making URLs clickable/different color
+// Input : *text - string that may contain URLs to make clickable/color coded
+// URLTextColor - color for URL text
+// normalTextColor - color for normal text
+//-----------------------------------------------------------------------------
+void RichText::InsertPossibleURLString(const char* text, Color URLTextColor, Color normalTextColor)
+{
+ InsertColorChange(normalTextColor);
+
+ // parse out the string for URL's
+ int len = Q_strlen(text), pos = 0;
+ bool clickable = false;
+ char *pchURLText = (char *)stackalloc( len + 1 );
+ char *pchURL = (char *)stackalloc( len + 1 );
+
+ while (pos < len)
+ {
+ pos = ParseTextStringForUrls( text, pos, pchURLText, len, pchURL, len, clickable );
+
+ if ( clickable )
+ {
+ InsertClickableTextStart( pchURL );
+ InsertColorChange( URLTextColor );
+ }
+
+ InsertString( pchURLText );
+
+ if ( clickable )
+ {
+ InsertColorChange(normalTextColor);
+ InsertClickableTextEnd();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: looks for URLs in the string and returns information about the URL
+//-----------------------------------------------------------------------------
+int RichText::ParseTextStringForUrls( const char *text, int startPos, char *pchURLText, int cchURLText, char *pchURL, int cchURL, bool &clickable )
+{
+ // scan for text that looks like a URL
+ int i = startPos;
+ while (text[i] != 0)
+ {
+ bool bURLFound = false;
+
+ if ( !Q_strnicmp(text + i, "<a href=", 8) )
+ {
+ if (i > startPos)
+ break;
+
+ // embedded link
+ bURLFound = true;
+ clickable = true;
+ // get the url
+ i += Q_strlen( "<a href=" );
+ const char *pchURLEnd = Q_strstr( text + i, ">" );
+ Q_strncpy( pchURL, text + i, min( pchURLEnd - text - i + 1, cchURL ) );
+ i += ( pchURLEnd - text - i + 1 );
+
+ // get the url text
+ pchURLEnd = Q_strstr( text, "</a>" );
+ Q_strncpy( pchURLText, text + i, min( pchURLEnd - text - i + 1, cchURLText ) );
+ i += ( pchURLEnd - text - i );
+ i += Q_strlen( "</a>" );
+
+ // we're done
+ return i;
+ }
+ else if (!Q_strnicmp(text + i, "www.", 4))
+ {
+ // scan ahead for another '.'
+ bool bPeriodFound = false;
+ for (const char *ch = text + i + 5; ch != 0; ch++)
+ {
+ if (*ch == '.')
+ {
+ bPeriodFound = true;
+ break;
+ }
+ }
+
+ // URL found
+ if (bPeriodFound)
+ {
+ bURLFound = true;
+ }
+ }
+ else if (!Q_strnicmp(text + i, "http://", 7))
+ {
+ bURLFound = true;
+ }
+ else if (!Q_strnicmp(text + i, "ftp://", 6))
+ {
+ bURLFound = true;
+ }
+ else if (!Q_strnicmp(text + i, "steam://", 8))
+ {
+ bURLFound = true;
+ }
+ else if (!Q_strnicmp(text + i, "steambeta://", 12))
+ {
+ bURLFound = true;
+ }
+ else if (!Q_strnicmp(text + i, "mailto:", 7))
+ {
+ bURLFound = true;
+ }
+ else if (!Q_strnicmp(text + i, "\\\\", 2))
+ {
+ bURLFound = true;
+ }
+
+ if (bURLFound)
+ {
+ if (i == startPos)
+ {
+ // we're at the Start of a URL, so parse that out
+ clickable = true;
+ int outIndex = 0;
+ while (text[i] != 0 && !iswspace(text[i]))
+ {
+ pchURLText[outIndex++] = text[i++];
+ }
+ pchURLText[outIndex] = 0;
+ Q_strncpy( pchURL, pchURLText, cchURL );
+ return i;
+ }
+ else
+ {
+ // no url
+ break;
+ }
+ }
+
+ // increment and loop
+ i++;
+ }
+
+ // nothing found;
+ // parse out the text before the end
+ clickable = false;
+ int outIndex = 0;
+ int fromIndex = startPos;
+ while ( fromIndex < i && outIndex < cchURLText )
+ {
+ pchURLText[outIndex++] = text[fromIndex++];
+ }
+ pchURLText[outIndex] = 0;
+ Q_strncpy( pchURL, pchURLText, cchURL );
+
+ return i;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Executes the text-clicked command, which opens a web browser by
+// default.
+//-----------------------------------------------------------------------------
+void RichText::OnTextClicked(const wchar_t *wszText)
+{
+ // Strip leading/trailing quotes, which may be present on href tags or may not.
+ const wchar_t *pwchURL = wszText;
+ if ( pwchURL[0] == L'"' || pwchURL[0] == L'\'' )
+ pwchURL = wszText + 1;
+
+ char ansi[2048];
+ Q_UnicodeToUTF8( pwchURL, ansi, sizeof(ansi) );
+
+ size_t strLen = Q_strlen(ansi);
+ if ( strLen && ( ansi[strLen-1] == '"' || ansi[strLen] == '\'' ) )
+ {
+ ansi[strLen-1] = 0;
+ }
+
+ if ( m_hPanelToHandleClickingURLs.Get() )
+ {
+ PostMessage( m_hPanelToHandleClickingURLs.Get(), new KeyValues( "URLClicked", "url", ansi ) );
+ }
+ else
+ {
+ system()->ShellExecute( "open", ansi );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void RichText::SetURLClickedHandler( Panel *pPanelToHandleClickMsg )
+{
+ m_hPanelToHandleClickingURLs = pPanelToHandleClickMsg;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: data accessor
+//-----------------------------------------------------------------------------
+bool RichText::IsScrollbarVisible()
+{
+ return _vertScrollBar->IsVisible();
+}
+
+void RichText::SetUnderlineFont( HFont font )
+{
+ m_hFontUnderline = font;
+}
+
+bool RichText::IsAllTextAlphaZero() const
+{
+ return m_bAllTextAlphaIsZero;
+}
+
+bool RichText::HasText() const
+{
+ int c = m_TextStream.Count();
+ if ( c == 0 )
+ {
+ return false;
+ }
+ return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the height of the base font
+//-----------------------------------------------------------------------------
+int RichText::GetLineHeight()
+{
+ return surface()->GetFontTall( _font );
+}
+
+
+#ifdef DBGFLAG_VALIDATE
+//-----------------------------------------------------------------------------
+// Purpose: Run a global validation pass on all of our data structures and memory
+// allocations.
+// Input: validator - Our global validator object
+// pchName - Our name (typically a member var in our container)
+//-----------------------------------------------------------------------------
+void RichText::Validate( CValidator &validator, char *pchName )
+{
+ validator.Push( "vgui::RichText", this, pchName );
+
+ ValidateObj( m_TextStream );
+ ValidateObj( m_FormatStream );
+ ValidateObj( m_LineBreaks );
+ ValidateObj( _clickableTextPanels );
+ validator.ClaimMemory( m_pszInitialText );
+
+ BaseClass::Validate( validator, "vgui::RichText" );
+
+ validator.Pop();
+}
+#endif // DBGFLAG_VALIDATE
+