//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//


#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <utlvector.h>

#include <vgui/Cursor.h>
#include <vgui/IInput.h>
#include <vgui/IScheme.h>
#include <vgui/ISystem.h>
#include <vgui/ISurface.h>
#include <vgui/ILocalize.h>
#include <vgui/IPanel.h>
#include <KeyValues.h>
#include <vgui/MouseCode.h>

#include <vgui_controls/Menu.h>
#include <vgui_controls/ScrollBar.h>
#include <vgui_controls/TextEntry.h>
#include <vgui_controls/Controls.h>
#include <vgui_controls/MenuItem.h>
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
#include <inputsystem/iinputsystem.h>

enum
{
	// maximum size of text buffer
	BUFFER_SIZE=999999,
};

using namespace vgui;

static const int DRAW_OFFSET_X = 3,DRAW_OFFSET_Y = 1; 

DECLARE_BUILD_FACTORY( TextEntry );

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
TextEntry::TextEntry(Panel *parent, const char *panelName) : BaseClass(parent, panelName)
{
	SetTriplePressAllowed( true );

	_font = INVALID_FONT;
	_smallfont = INVALID_FONT;

	m_szComposition[ 0 ] = L'\0';

	m_bAllowNumericInputOnly = false;
	m_bAllowNonAsciiCharacters = false;
	_hideText = false;
	_editable = false;
	_verticalScrollbar = false;
	_cursorPos = 0;
	_currentStartIndex = 0;
	_horizScrollingAllowed = true;
	_cursorIsAtEnd = false;
	_putCursorAtEnd = false;
	_multiline = false;
	_cursorBlinkRate = 400;
	_mouseSelection = false;
	_mouseDragSelection = false;
	_vertScrollBar=NULL;
	_catchEnterKey = false;
	_maxCharCount = -1;
	_charCount = 0;
	_wrap = false; // don't wrap by default
	_sendNewLines = false; // don't pass on a newline msg by default
	_drawWidth = 0;
	m_bAutoProgressOnHittingCharLimit = false;
	m_pIMECandidates = NULL;
	m_hPreviousIME = input()->GetEnglishIMEHandle();
	m_bDrawLanguageIDAtLeft = false;
	m_nLangInset = 0;
	m_bUseFallbackFont = false;
	m_hFallbackFont = INVALID_FONT;

	//a -1 for _select[0] means that the selection is empty
	_select[0] = -1;
	_select[1] = -1;
	m_pEditMenu = NULL;
	
	//this really just inits it when in here	
	ResetCursorBlink();
	
	SetCursor(dc_ibeam);
	
	SetEditable(true);
	
	_dataChanged = false;

	// initialize the line break array
	m_LineBreaks.AddToTail(BUFFER_SIZE);
	
	_recalculateBreaksIndex = 0;
	
	_selectAllOnFirstFocus = false;
	_selectAllOnFocusAlways = false;

	//position the cursor so it is at the end of the text
	GotoTextEnd();

	// If keyboard focus is in an edit control, don't chain keyboard mappings up to parents since it could mess with typing in text.
	SetAllowKeyBindingChainToParent( false );

	REGISTER_COLOR_AS_OVERRIDABLE( _disabledFgColor, "disabledFgColor_override" );
	REGISTER_COLOR_AS_OVERRIDABLE( _disabledBgColor, "disabledBgColor_override" );
	REGISTER_COLOR_AS_OVERRIDABLE( _selectionColor, "selectionColor_override" );
	REGISTER_COLOR_AS_OVERRIDABLE( _selectionTextColor, "selectionTextColor_override" );
	REGISTER_COLOR_AS_OVERRIDABLE( _defaultSelectionBG2Color, "defaultSelectionBG2Color_override" );
}


TextEntry::~TextEntry()
{
	delete m_pEditMenu;
	delete m_pIMECandidates;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TextEntry::ApplySchemeSettings(IScheme *pScheme)
{
	BaseClass::ApplySchemeSettings(pScheme);
	
	SetFgColor(GetSchemeColor("TextEntry.TextColor", pScheme));
	SetBgColor(GetSchemeColor("TextEntry.BgColor", pScheme));
	
	_cursorColor = GetSchemeColor("TextEntry.CursorColor", pScheme);
	_disabledFgColor = GetSchemeColor("TextEntry.DisabledTextColor", pScheme);
	_disabledBgColor = GetSchemeColor("TextEntry.DisabledBgColor", pScheme);
	
	_selectionTextColor = GetSchemeColor("TextEntry.SelectedTextColor", GetFgColor(), pScheme);
	_selectionColor = GetSchemeColor("TextEntry.SelectedBgColor", pScheme);
	_defaultSelectionBG2Color = GetSchemeColor("TextEntry.OutOfFocusSelectedBgColor", pScheme);
	_focusEdgeColor = GetSchemeColor("TextEntry.FocusEdgeColor", Color(0, 0, 0, 0), pScheme);

	SetBorder( pScheme->GetBorder("ButtonDepressedBorder"));

	if ( _font == INVALID_FONT ) _font = pScheme->GetFont("Default", IsProportional() );
	if ( _smallfont == INVALID_FONT ) _smallfont = pScheme->GetFont( "DefaultVerySmall", IsProportional() );

	SetFont( _font );
}

void TextEntry::SetSelectionTextColor( const Color& clr )
{
	_selectionTextColor = clr;
}

void TextEntry::SetSelectionBgColor( const Color& clr )
{
	_selectionColor = clr;
}

void TextEntry::SetSelectionUnfocusedBgColor( const Color& clr )
{
	_defaultSelectionBG2Color = clr;
}

//-----------------------------------------------------------------------------
// Purpose: sets the color of the background when the control is disabled
//-----------------------------------------------------------------------------
void TextEntry::SetDisabledBgColor(Color col)
{
	_disabledBgColor = col;
}


//-----------------------------------------------------------------------------
// 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 TextEntry::OnKillFocus()
{
	m_szComposition[ 0 ] = L'\0';
	HideIMECandidates();

	if (_dataChanged)
	{
		FireActionSignal();
		_dataChanged = false;
	}
	
	// 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 )
	{			
		int cursorX, cursorY;
		input()->GetCursorPos(cursorX, cursorY);

		// if we're right clicking within our window, we don't actually kill focus
		if (IsWithin(cursorX, cursorY))
			return;
	}
	
   	// clear any selection
    SelectNone();

	// move the cursor to the start
//	GotoTextStart();

	PostActionSignal( new KeyValues( "TextKillFocus" ) );

	// chain
	BaseClass::OnKillFocus();
}

//-----------------------------------------------------------------------------
// Purpose: Wipe line breaks after the size of a panel has been changed
//-----------------------------------------------------------------------------
void TextEntry::OnSizeChanged(int newWide, int newTall)
{
	BaseClass::OnSizeChanged(newWide, newTall);

   	// blow away the line breaks list 
	_recalculateBreaksIndex = 0;
	m_LineBreaks.RemoveAll();
	m_LineBreaks.AddToTail(BUFFER_SIZE);

    // if we're bigger, see if we can scroll left to put more text in the window
    if (newWide > _drawWidth)
    {
        ScrollLeftForResize();
    }

	_drawWidth = newWide;
	InvalidateLayout();
}


//-----------------------------------------------------------------------------
// Purpose: Set the text array - convert ANSI text to unicode and pass to unicode function
//-----------------------------------------------------------------------------
void TextEntry::SetText(const char *text)
{
	if (!text)
	{
		text = "";
	}

	if (text[0] == '#')
	{
		// check for localization
		wchar_t *wsz = g_pVGuiLocalize->Find(text);
		if (wsz)
		{
			SetText(wsz);
			return;
		}
	}

	size_t len = strlen( text );
	if ( len < 1023 )
	{
		wchar_t unicode[ 1024 ];
		g_pVGuiLocalize->ConvertANSIToUnicode( text, unicode, sizeof( unicode ) );
		SetText( unicode );
	}
	else
	{
		size_t lenUnicode = ( len * sizeof( wchar_t ) + 4 );
		wchar_t *unicode = ( wchar_t * ) malloc( lenUnicode );
			g_pVGuiLocalize->ConvertANSIToUnicode( text, unicode, lenUnicode );
			SetText( unicode );
		free( unicode );
	}
}

//-----------------------------------------------------------------------------
// 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 TextEntry::SetText(const wchar_t *wszText)
{
	if (!wszText)
	{
		wszText = L"";
	}
	int textLen = wcslen(wszText);
	m_TextStream.RemoveAll();
	m_TextStream.EnsureCapacity(textLen);

	int missed_count = 0;
	for (int i = 0; i < textLen; i++)
	{
		if(wszText[i]=='\r') // don't insert \r characters
		{
			missed_count++;
			continue;
		}
		m_TextStream.AddToTail(wszText[i]);
		SetCharAt(wszText[i], i-missed_count);
	}

	GotoTextStart();
	SelectNone();
	
	// reset the data changed flag
	_dataChanged = false;
	
	// blow away the line breaks list 
	_recalculateBreaksIndex = 0;
	m_LineBreaks.RemoveAll();
	m_LineBreaks.AddToTail(BUFFER_SIZE);
	
	InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose: Sets the value of char at index position.
//-----------------------------------------------------------------------------
void TextEntry::SetCharAt(wchar_t ch, int index)
{
	if ((ch == '\n') || (ch == '\0')) 
	{
		// if its not at the end of the buffer it matters.
		// redo the linebreaks
		//if (index != m_TextStream.Count())
		{
			_recalculateBreaksIndex = 0;
			m_LineBreaks.RemoveAll();
			m_LineBreaks.AddToTail(BUFFER_SIZE);
		}
	}
	
	if (index < 0)
		return;

	if (index >= m_TextStream.Count())
	{
		m_TextStream.AddMultipleToTail(index - m_TextStream.Count() + 1);
	}
	m_TextStream[index] = ch;
	_dataChanged = true;
}

//-----------------------------------------------------------------------------
// Purpose: Restarts the time of the next cursor blink
//-----------------------------------------------------------------------------
void TextEntry::ResetCursorBlink()
{
	_cursorBlink=false;
	_cursorNextBlinkTime=system()->GetTimeMillis()+_cursorBlinkRate;
}

//-----------------------------------------------------------------------------
// Purpose: Hides the text buffer so it will not be drawn
//-----------------------------------------------------------------------------
void TextEntry::SetTextHidden(bool bHideText)
{
	_hideText = bHideText;
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: return character width
//-----------------------------------------------------------------------------
int getCharWidth(HFont font, wchar_t ch)
{
	if (!iswcntrl(ch))
	{
		int a, b, c;
		surface()->GetCharABCwide(font, ch, a, b, c);
		return (a + b + c);
	}
	return 0;
}

//-----------------------------------------------------------------------------
// 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 TextEntry::CursorToPixelSpace(int cursorPos, int &cx, int &cy)
{
	int yStart = GetYStart();
	
	int x = DRAW_OFFSET_X, y = yStart;
	_pixelsIndent = 0;
	int lineBreakIndexIndex = 0;
	
	for (int i = GetStartDrawIndex(lineBreakIndexIndex); i < m_TextStream.Count(); i++)
	{
		wchar_t ch = m_TextStream[i];
		if (_hideText)
		{
			ch = '*';
		}
		
		// if we've found the position, break
		if (cursorPos == i)
		{
			// even if this is a line break entry for the cursor, the next insert
			// will be at this position, which will push the line break forward one
			// so don't push the cursor down a line here...
			/*if (!_putCursorAtEnd)
			{		
				// 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.Count() && 
			lineBreakIndexIndex < m_LineBreaks.Count() &&
			m_LineBreaks[lineBreakIndexIndex] == i)
		{
			// add another line
			AddAnotherLine(x,y);
			lineBreakIndexIndex++;
		}
		
		// add to the current position
		x += getCharWidth(_font, ch);
	}
	
	if ( m_bDrawLanguageIDAtLeft )
	{
		x += m_nLangInset;
	}

	cx = x;
	cy = y;
}

//-----------------------------------------------------------------------------
// Purpose: Converts local pixel coordinates to an index in the text buffer
//          This function appears to be used only in response to mouse clicking
// Input  : cx - 
//			cy - pixel location
//-----------------------------------------------------------------------------
int TextEntry::PixelToCursorSpace(int cx, int cy)
{
	
	int w, h;
	GetSize(w, h);
	cx = clamp(cx, 0, w+100);
	cy = clamp(cy, 0, h);

	_putCursorAtEnd = false; //	Start off assuming we clicked somewhere in the text
	
	int fontTall = surface()->GetFontTall(_font);
	
	// where to Start reading
	int yStart = GetYStart();
	int x = DRAW_OFFSET_X, y = yStart;
	_pixelsIndent = 0;
	int lineBreakIndexIndex = 0;
	
	int startIndex = GetStartDrawIndex(lineBreakIndexIndex);
	bool onRightLine = false;
	int i;
	for (i = startIndex; i < m_TextStream.Count(); i++)
	{
		wchar_t ch = m_TextStream[i];
		if (_hideText)
		{
			ch = '*';
		}
		
		// 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)
			{	
				_putCursorAtEnd = true;
				return i;
			}
		}
		
		// check to see if we're on the right line
		if (cy < yStart)
		{
			// cursor is above panel
			onRightLine = true;
			_putCursorAtEnd = true;	// this will make the text scroll up if needed
		}
		else if (cy >= y && (cy < (y + fontTall + DRAW_OFFSET_Y)))
		{
			onRightLine = true;
		}
		
		int wide = getCharWidth(_font, ch);
		
		// if we've found the position, break
		if (onRightLine)
		{
			if (cx > GetWide())	  // off right side of window
			{
			}
			else if (cx < (DRAW_OFFSET_X + _pixelsIndent) || cy < yStart)	 // off left side of window
			{
				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
				{
					return i;
				}
				else  // right side
				{						 
					return i + 1;
				}
			}
		}
		x += wide;
	}
	
	return i;
}

//-----------------------------------------------------------------------------
// Purpose: Draws a character in the panel
// Input:	ch - character to draw
//			font - font to use
//			x, y - pixel location to draw char at
// Output:	returns the width of the character drawn
//-----------------------------------------------------------------------------
int TextEntry::DrawChar(wchar_t ch, HFont font, int index, int x, int y)
{
	// add to the current position
	int charWide = getCharWidth(font, ch);
	int fontTall=surface()->GetFontTall(font);
	if (!iswcntrl(ch))
	{
		// draw selection, if any
		int selection0 = -1, selection1 = -1;
		GetSelectedRange(selection0, selection1);
		
		if (index >= selection0 && index < selection1)
		{
			// draw background selection color
            VPANEL focus = input()->GetFocus();
			Color bgColor;
			bool hasFocus = HasFocus();
			bool childOfFocus = focus && ipanel()->HasParent(focus, GetVPanel());

            // if one of the children of the SectionedListPanel has focus, then 'we have focus' if we're selected
            if ( hasFocus || childOfFocus )
			{
    			bgColor = _selectionColor;
			}
            else
			{
    			bgColor =_defaultSelectionBG2Color;
			}

			surface()->DrawSetColor(bgColor);

			surface()->DrawFilledRect(x, y, x + charWide, y + 1 + fontTall);
			
			// reset text color
			surface()->DrawSetTextColor(_selectionTextColor);
		}
		if (index == selection1)
		{
			// we've come out of selection, reset the color
			surface()->DrawSetTextColor(GetFgColor());
		}

		surface()->DrawSetTextPos(x, y);
		surface()->DrawUnicodeChar(ch);
		
		return charWide;
	}
	
	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: Draw the cursor, cursor is not drawn when it is blinked gone
// Input:  x,y where to draw cursor
// Output: returns true if cursor was drawn.
//-----------------------------------------------------------------------------
bool TextEntry::DrawCursor(int x, int y)
{
	if (!_cursorBlink)
	{
		int cx, cy;
		CursorToPixelSpace(_cursorPos, cx, cy);
		surface()->DrawSetColor(_cursorColor);
		int fontTall=surface()->GetFontTall(_font);
		surface()->DrawFilledRect(cx, cy, cx + 1, cy + fontTall);
		return true;
	}
	return false;
}

bool TextEntry::NeedsEllipses( HFont font, int *pIndex )
{
	Assert( pIndex );
	*pIndex = -1;
	int wide = DRAW_OFFSET_X; // buffer on left and right end of text.
	for ( int i = 0; i < m_TextStream.Count(); ++i )
	{	
		wide += getCharWidth( font , m_TextStream[i] );
		if (wide > _drawWidth)
		{
			*pIndex = i;
			return true;
		}
	}
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Draws the text in the panel
//-----------------------------------------------------------------------------
void TextEntry::PaintBackground()
{
	BaseClass::PaintBackground();

	// draw background
	Color col;
	if (IsEnabled())
	{
		col = GetBgColor();
	}
	else
	{
		col = _disabledBgColor;
	}
	Color saveBgColor = col;

	int wide, tall;
	GetSize( wide, tall );

//	surface()->DrawSetColor(col);
//	surface()->DrawFilledRect(0, 0, wide, tall);

	// where to Start drawing
	int x = DRAW_OFFSET_X + _pixelsIndent, y = GetYStart();

	m_nLangInset = 0;

	int langlen = 0;
	wchar_t shortcode[ 5 ];
	shortcode[ 0 ] = L'\0';

	if ( m_bAllowNonAsciiCharacters )
	{
		input()->GetIMELanguageShortCode( shortcode, sizeof( shortcode ) );

		if ( shortcode[ 0 ] != L'\0' &&
			 wcsicmp( shortcode, L"EN" ) )
		{
			m_nLangInset = 0;
			langlen = wcslen( shortcode );
			for ( int i = 0; i < langlen; ++i )
			{
				m_nLangInset += getCharWidth( _smallfont, shortcode[ i ] );
			}

			m_nLangInset += 4;

			if ( m_bDrawLanguageIDAtLeft )
			{
				x += m_nLangInset;
			}

			wide -= m_nLangInset;
		}
	}

	HFont useFont = _font;

	surface()->DrawSetTextFont(useFont);
	if (IsEnabled())
	{
		col = GetFgColor();
	}
	else
	{
		col = _disabledFgColor;
	}
	surface()->DrawSetTextColor(col);
	_pixelsIndent = 0;
	
	int lineBreakIndexIndex = 0;
	int startIndex = GetStartDrawIndex(lineBreakIndexIndex);
	int remembery = y;

	int oldEnd = m_TextStream.Count();
	int oldCursorPos = _cursorPos;
	int nCompStart = -1;
	int nCompEnd = -1;

	// FIXME: Should insert at cursor pos instead
	bool composing = m_bAllowNonAsciiCharacters && wcslen( m_szComposition ) > 0;
	bool invertcomposition = input()->GetShouldInvertCompositionString();

	if ( composing )
	{
		nCompStart = _cursorPos;

		wchar_t *s = m_szComposition;
		while ( *s != L'\0' )
		{
			m_TextStream.InsertBefore( _cursorPos, *s );
			++s;
			++_cursorPos;
		}

		nCompEnd = _cursorPos;
	}

	bool highlight_composition = ( nCompStart != -1 && nCompEnd != -1 ) ? true : false;

	// draw text with an elipsis
	if ( (!_multiline) && (!_horizScrollingAllowed) )
	{	
		int endIndex = m_TextStream.Count();
		// In editable windows only do the ellipsis if we don't have focus.
		// In non editable windows do it all the time.
		if ( (!HasFocus() && (IsEditable())) || (!IsEditable()) )
		{
			int i = -1;

			// loop through all the characters and sum their widths	
			bool addEllipses = NeedsEllipses( useFont, &i );
			if ( addEllipses && 
				!IsEditable() &&
				m_bUseFallbackFont && 
				INVALID_FONT != m_hFallbackFont )
			{
				// Switch to small font!!!
				useFont = m_hFallbackFont;
				surface()->DrawSetTextFont(useFont);
				addEllipses = NeedsEllipses( useFont, &i );
			}
			if (addEllipses)
			{
				int elipsisWidth = 3 * getCharWidth(useFont, '.');
				while (elipsisWidth > 0 && i >= 0)
				{
					elipsisWidth -= getCharWidth(useFont, m_TextStream[i]);
					i--;
				}
				endIndex = i + 1;	
			}

			// if we take off less than the last 3 chars we have to make sure
			// we take off the last 3 chars so selected text will look right.
			if (m_TextStream.Count() - endIndex < 3 && m_TextStream.Count() - endIndex > 0 )
			{
				endIndex = m_TextStream.Count() - 3;
			}
		}
		// draw the text
		int i;
		for (i = startIndex; i < endIndex; i++)
		{
			wchar_t ch = m_TextStream[i];
			if (_hideText)
			{
				ch = '*';
			}

			bool iscompositionchar = false;

			if ( highlight_composition )
			{
				iscompositionchar = ( i >= nCompStart && i < nCompEnd ) ? true : false;
				if ( iscompositionchar )
				{
					// Set the underline color to the text color
					surface()->DrawSetColor( col );

					int w = getCharWidth( useFont, ch );

					if ( invertcomposition )
					{
						// Invert color
						surface()->DrawSetTextColor( saveBgColor );
						surface()->DrawSetColor( col );
						
						surface()->DrawFilledRect(x, 0, x+w, tall);
						// Set the underline color to the text color
						surface()->DrawSetColor( saveBgColor );
					}

					surface()->DrawFilledRect( x, tall - 2, x + w, tall - 1 );
				}
			}


			// draw the character and update xposition  
			x += DrawChar(ch, useFont, i, x, y);

			// Restore color
			surface()->DrawSetTextColor(col);

		}
		if (endIndex < m_TextStream.Count()) // add an elipsis
		{
			x += DrawChar('.', useFont, i, x, y);
			i++;
			x += DrawChar('.', useFont, i, x, y);
			i++;
			x += DrawChar('.', useFont, i, x, y);
			i++;
		}
	}	
	else
	{
		// draw the text
		for ( int i = startIndex; i < m_TextStream.Count(); i++)
		{
			wchar_t ch = m_TextStream[i];
			if (_hideText)
			{
				ch = '*';
			}
			
			// if we've passed a line break go to that
			if ( _multiline && m_LineBreaks[lineBreakIndexIndex] == i)
			{
				// add another line
				AddAnotherLine(x, y);
				lineBreakIndexIndex++;
			}
			
			bool iscompositionchar = false;

			if ( highlight_composition )
			{
				iscompositionchar = ( i >= nCompStart && i < nCompEnd ) ? true : false;
				if ( iscompositionchar )
				{	
					// Set the underline color to the text color
					surface()->DrawSetColor( col );

					int w = getCharWidth( useFont, ch );

					if ( invertcomposition )
					{
						// Invert color
						surface()->DrawSetTextColor( saveBgColor );
						surface()->DrawFilledRect(x, 0, x+w, tall);
						// Set the underline color to the text color
						surface()->DrawSetColor( saveBgColor );
					}

					surface()->DrawFilledRect( x, tall - 2, x + w, tall - 1 );
				}
			}

			// draw the character and update xposition  
			x += DrawChar(ch, useFont, i, x, y);

			// Restore color
			surface()->DrawSetTextColor(col);
		}
	}

	// custom border
	//!! need to replace this with scheme stuff (TextEntryBorder/TextEntrySelectedBorder)
	surface()->DrawSetColor(50, 50, 50, 255);
	
	if (IsEnabled() && IsEditable() && HasFocus())
	{
		// set a more distinct border color
		surface()->DrawSetColor(0, 0, 0, 255);
		
		DrawCursor (x, y);

		if ( composing )
		{
			LocalToScreen( x, y );
			input()->SetCandidateWindowPos( x, y );
		}
	}

	int newEnd = m_TextStream.Count();
	int remove = newEnd - oldEnd;
	if ( remove > 0 )
	{
		m_TextStream.RemoveMultiple( oldCursorPos, remove );
	}
	_cursorPos = oldCursorPos;

	if ( HasFocus() && m_bAllowNonAsciiCharacters && langlen > 0 )
	{
		wide += m_nLangInset;

		if ( m_bDrawLanguageIDAtLeft )
		{
			x = 0;
		}
		else
		{
			// Draw language identififer
			x = wide - m_nLangInset;
		}

		surface()->DrawSetColor( col );

		surface()->DrawFilledRect( x, 2, x + m_nLangInset-2, tall - 2 );

		saveBgColor[ 3 ] = 255;
		surface()->DrawSetTextColor( saveBgColor );

		x += 1;

		surface()->DrawSetTextFont(_smallfont);
		for ( int i = 0; i < langlen; ++i )
		{
			x += DrawChar( shortcode[ i ], _smallfont, i, x, remembery );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called when data changes or panel size changes
//-----------------------------------------------------------------------------
void TextEntry::PerformLayout()
{
	BaseClass::PerformLayout();

	RecalculateLineBreaks();
	
	// recalculate scrollbar position
	if (_verticalScrollbar)
	{
		LayoutVerticalScrollBarSlider();
	}
	
	// force a Repaint
	Repaint();
}

// moves x,y to the Start of the next line of text
void TextEntry::AddAnotherLine(int &cx, int &cy)
{
	cx = DRAW_OFFSET_X + _pixelsIndent;
	cy += (surface()->GetFontTall(_font) + DRAW_OFFSET_Y);
}


//-----------------------------------------------------------------------------
// Purpose: Recalculates line breaks
//-----------------------------------------------------------------------------
void TextEntry::RecalculateLineBreaks()
{
	if (!_multiline || _hideText)
		return;

	if (m_TextStream.Count() < 1)
		return;
	
	HFont font = _font;
	
	// line break to our width -2 pixel to keep cursor blinking in window
	// (assumes borders are 1 pixel)
	int wide = GetWide()-2;
	
	// subtract the scrollbar width
	if (_vertScrollBar)
	{
		wide -= _vertScrollBar->GetWide();
	}
	
	int charWidth;
	int x = DRAW_OFFSET_X, y = DRAW_OFFSET_Y;
		
	int wordStartIndex = 0;
	int wordLength = 0;
	bool hasWord = false;
	bool justStartedNewLine = true;
	bool wordStartedOnNewLine = true;
	
	int startChar;
	if (_recalculateBreaksIndex <= 0)
	{
		m_LineBreaks.RemoveAll();
		startChar=0;
	}
	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((int)i);
			--i; // removing shrinks the list!
		}
		startChar = m_LineBreaks[_recalculateBreaksIndex];
	}
	
	// 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++;
	}
	
	// loop through all the characters	
	int i;
	for (i = startChar; i < m_TextStream.Count(); ++i)
	{
		wchar_t ch = m_TextStream[i];
		
		// line break only on whitespace characters
		if (!iswspace(ch))
		{
			if (hasWord)
			{
				// append to the current word
			}
			else
			{
				// Start a new word
				wordStartIndex = i;
				hasWord = true;
				wordStartedOnNewLine = justStartedNewLine;
				wordLength = 0;
			}
		}
		else
		{
			// whitespace/punctuation character
			// end the word
			hasWord = false;
		}
		
		// get the width
		charWidth = getCharWidth(font, ch);
		if (!iswcntrl(ch))
		{
			justStartedNewLine = false;
		}
				
		// check to see if the word is past the end of the line [wordStartIndex, i)
		if ((x + charWidth) >= wide || ch == '\r' || ch == '\n')
		{
			// add another line
			AddAnotherLine(x,y);
			
			justStartedNewLine = true;
			hasWord = false;
			
			if (ch == '\r' || ch == '\n')
			{
				// set the break at the current character
				m_LineBreaks.AddToTail(i);
			}
			else if (wordStartedOnNewLine)
			{
				// word is longer than a line, so set the break at the current cursor
				m_LineBreaks.AddToTail(i);
			}
			else
			{
				// set it at the last word Start
				m_LineBreaks.AddToTail(wordStartIndex);
				
				// just back to reparse the next line of text
				i = wordStartIndex;
			}
			
			// reset word length
			wordLength = 0;
		}
		
		// add to the size
		x += charWidth;
		wordLength += charWidth;
	}
	
	_charCount = i-1;
	
	// end the list
	m_LineBreaks.AddToTail(BUFFER_SIZE);
	
	// set up the scrollbar
	LayoutVerticalScrollBarSlider();
}

//-----------------------------------------------------------------------------
// Purpose: Recalculate where the vertical scroll bar slider should be 
//			based on the current cursor line we are on.
//-----------------------------------------------------------------------------
void TextEntry::LayoutVerticalScrollBarSlider()
{
	// set up the scrollbar
	if (_vertScrollBar)
	{
		int wide, tall;
		GetSize (wide, tall);
		
		// make sure we factor in insets
		int ileft, iright, itop, ibottom;
		GetInset(ileft, iright, itop, ibottom);
		
		// with a scroll bar we take off the inset
		wide -= iright;
		
		_vertScrollBar->SetPos(wide - _vertScrollBar->GetWide(), 0);
		// scrollbar is inside the borders.
		_vertScrollBar->SetSize(_vertScrollBar->GetWide(), tall - ibottom - itop);
		
		// calculate how many lines we can fully display
		int displayLines = tall / (surface()->GetFontTall(_font) + DRAW_OFFSET_Y);
		int numLines = m_LineBreaks.Count();
		
		if (numLines <= displayLines)
		{
			// disable the scrollbar
			_vertScrollBar->SetEnabled(false);
			_vertScrollBar->SetRange(0, numLines);
			_vertScrollBar->SetRangeWindow(numLines);
			_vertScrollBar->SetValue(0);
		}
		else
		{
			// 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);
			
			// set the value to view the last entries
			int val = _vertScrollBar->GetValue();
			int maxval = _vertScrollBar->GetValue() + displayLines;
			if (GetCursorLine() < val )
			{
				while (GetCursorLine() < val)
				{
					val--;
				}
			}
			else if (GetCursorLine() >= maxval)
			{
				while (GetCursorLine() >= maxval)
				{
					maxval++;
				}
				maxval -= displayLines;	
				val = maxval;
			}
			else 
			{
				//val = GetCursorLine();
			}
			
			_vertScrollBar->SetValue(val);
			_vertScrollBar->InvalidateLayout();
			_vertScrollBar->Repaint();
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Set boolean value of baseclass variables.
//-----------------------------------------------------------------------------
void TextEntry::SetEnabled(bool state)
{
	BaseClass::SetEnabled(state);
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Sets whether text wraps around multiple lines or not
// Input  : state - true or false
//-----------------------------------------------------------------------------
void TextEntry::SetMultiline(bool state)
{
	_multiline = state;
}

bool TextEntry::IsMultiline()
{
	return _multiline;
}

//-----------------------------------------------------------------------------
// Purpose: sets whether or not the edit catches and stores ENTER key presses
//-----------------------------------------------------------------------------
void TextEntry::SetCatchEnterKey(bool state)
{
	_catchEnterKey = state;
}

//-----------------------------------------------------------------------------
// Purpose: Sets whether a vertical scrollbar is visible
// Input  : state - true or false
//-----------------------------------------------------------------------------
void TextEntry::SetVerticalScrollbar(bool state)
{
	_verticalScrollbar = state;
	
	if (_verticalScrollbar)
	{
		if (!_vertScrollBar)
		{
			_vertScrollBar = new ScrollBar(this, "ScrollBar", true);
			_vertScrollBar->AddActionSignalTarget(this);
		}
		
		_vertScrollBar->SetVisible(true);
	}
	else if (_vertScrollBar)
	{
		_vertScrollBar->SetVisible(false);
	}
	
	InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose: sets _editable flag
// Input  : state - true or false
//-----------------------------------------------------------------------------
void TextEntry::SetEditable(bool state)
{
	if ( state )
	{
		SetDropEnabled( true, 1.0f );
	}
	else
	{
		SetDropEnabled( false );
	}
	_editable = state;
}

const wchar_t *UnlocalizeUnicode( wchar_t *unicode )
{
	if ( !unicode )
		return L"";

	if ( *unicode == L'#' )
	{
		char lookup[ 512 ];
		g_pVGuiLocalize->ConvertUnicodeToANSI( unicode + 1, lookup, sizeof( lookup ) );
		return g_pVGuiLocalize->Find( lookup );
	}
	return unicode;
}

Menu * TextEntry::GetEditMenu()
{
	return m_pEditMenu;
}

//-----------------------------------------------------------------------------
// Purpose: Create cut/copy/paste dropdown menu
//-----------------------------------------------------------------------------
void TextEntry::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");
	
	m_pEditMenu->SetFont( _font );
	
	// add cut/copy/paste drop down options if its editable, just copy if it is not
	if (_editable && !_hideText)
	{	
		m_pEditMenu->AddMenuItem("#TextEntry_Cut", new KeyValues("DoCutSelected"), this);
	}
	
	if ( !_hideText )
	{
		m_pEditMenu->AddMenuItem("#TextEntry_Copy", new KeyValues("DoCopySelected"), this);
	}
	
	if (_editable)
	{
		m_pEditMenu->AddMenuItem("#TextEntry_Paste", new KeyValues("DoPaste"), this);
	}
	

	if ( m_bAllowNonAsciiCharacters )
	{
		IInput::LanguageItem *langs = NULL;

		int count = input()->GetIMELanguageList( NULL, 0 );
		if ( count > 0 )
		{
			langs = new IInput::LanguageItem[ count ];
			input()->GetIMELanguageList( langs, count );

			// Create a submenu
			Menu *subMenu = new Menu( this, "LanguageMenu" );

			subMenu->SetFont( _font );

			for ( int i = 0; i < count; ++i )
			{
				int id = subMenu->AddCheckableMenuItem( "Language", UnlocalizeUnicode( langs[ i ].menuname ), new KeyValues( "DoLanguageChanged", "handle", langs[ i ].handleValue ), this );
				if ( langs[ i ].active )
				{
					subMenu->SetMenuItemChecked( id, true );
				}
			}

			m_pEditMenu->AddCascadingMenuItem( "Language", "#TextEntry_Language", "", this, subMenu );

			delete[] langs;
		}

		IInput::ConversionModeItem *modes = NULL;

		count = input()->GetIMEConversionModes( NULL, 0 );
		// if count == 0 then native mode is the only mode...
		if ( count > 0 )
		{
			modes = new IInput::ConversionModeItem[ count ];
			input()->GetIMEConversionModes( modes, count );

			// Create a submenu
			Menu *subMenu = new Menu( this, "ConversionModeMenu" );

			subMenu->SetFont( _font );

			for ( int i = 0; i < count; ++i )
			{
				int id = subMenu->AddCheckableMenuItem( "ConversionMode", UnlocalizeUnicode( modes[ i ].menuname ), new KeyValues( "DoConversionModeChanged", "handle", modes[ i ].handleValue ), this );
				if ( modes[ i ].active )
				{
					subMenu->SetMenuItemChecked( id, true );
				}
			}

			m_pEditMenu->AddCascadingMenuItem( "ConversionMode", "#TextEntry_ConversionMode", "", this, subMenu );

			delete[] modes;
		}

		IInput::SentenceModeItem *sentencemodes = NULL;

		count = input()->GetIMESentenceModes( NULL, 0 );
		// if count == 0 then native mode is the only mode...
		if ( count > 0 )
		{
			sentencemodes = new IInput::SentenceModeItem[ count ];
			input()->GetIMESentenceModes( sentencemodes, count );

			// Create a submenu
			Menu *subMenu = new Menu( this, "SentenceModeMenu" );

			subMenu->SetFont( _font );

			for ( int i = 0; i < count; ++i )
			{
				int id = subMenu->AddCheckableMenuItem( "SentenceMode", UnlocalizeUnicode( sentencemodes[ i ].menuname ), new KeyValues( "DoConversionModeChanged", "handle", modes[ i ].handleValue ), this );
				if ( modes[ i ].active )
				{
					subMenu->SetMenuItemChecked( id, true );
				}
			}

			m_pEditMenu->AddCascadingMenuItem( "SentenceMode", "#TextEntry_SentenceMode", "", this, subMenu );

			delete[] sentencemodes;
		}
	}
	

	m_pEditMenu->SetVisible(false);
	m_pEditMenu->SetParent(this);
	m_pEditMenu->AddActionSignalTarget(this);
}

//-----------------------------------------------------------------------------
// Purpsoe: Returns state of _editable flag
//-----------------------------------------------------------------------------
bool TextEntry::IsEditable()
{
	return _editable && IsEnabled();
}

//-----------------------------------------------------------------------------
// Purpose: We want single line windows to scroll horizontally and select text
//          in response to clicking and holding outside window
//-----------------------------------------------------------------------------
void TextEntry::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 TextEntry::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 TextEntry::OnCursorExited() // outside of window recieve drag scrolling ticks
{
	if (_mouseSelection)
		_mouseDragSelection = true;
}

//-----------------------------------------------------------------------------
// Purpose: Handle selection of text by mouse
//-----------------------------------------------------------------------------
void TextEntry::OnCursorMoved(int ignX, int ignY)
{
	if (_mouseSelection)
	{
		// update the cursor position
		int x, y;
		input()->GetCursorPos(x, y);
		ScreenToLocal(x, y);
		_cursorPos = PixelToCursorSpace(x, y);
		
		// if we are at Start of buffer don't put cursor at end, this will keep
		// window from scrolling up to a blank line
		if (_cursorPos == 0)
			_putCursorAtEnd = false;
		
		// scroll if we went off left side
		if (_cursorPos == _currentStartIndex)
		{
			if (_cursorPos > 0)
				_cursorPos--;

			ScrollLeft();
			_cursorPos = _currentStartIndex;
		}
		if ( _cursorPos != _select[1])
		{
			_select[1] = _cursorPos;
			Repaint();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handle Mouse button down events.
//-----------------------------------------------------------------------------
void TextEntry::OnMousePressed(MouseCode code)
{
	if (code == MOUSE_LEFT)
	{
		bool keepChecking = SelectCheck( true );
		if ( !keepChecking )
		{
			BaseClass::OnMousePressed( code );
			return;
		}

		if( IsEnabled() )
			g_pInputSystem->StartTextInput();

		// move the cursor to where the mouse was pressed
		int x, y;
		input()->GetCursorPos(x, y);
		ScreenToLocal(x, y);
		
		_cursorIsAtEnd = _putCursorAtEnd; // save this off before calling PixelToCursorSpace()
		_cursorPos = PixelToCursorSpace(x, y);
		// if we are at Start of buffer don't put cursor at end, this will keep
		// window from scrolling up to a blank line
		if (_cursorPos == 0)
			_putCursorAtEnd = false;
		
		// 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;
		
		ResetCursorBlink();
		RequestFocus();
		Repaint();
	}
	else if (code == MOUSE_RIGHT) // check for context menu open
	{	
		CreateEditMenu();
		Assert(m_pEditMenu);
		
		OpenEditMenu();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handle mouse button up events
//-----------------------------------------------------------------------------
void TextEntry::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: 
// Input  : code - 
//-----------------------------------------------------------------------------
void TextEntry::OnMouseTriplePressed( MouseCode code )
{
	BaseClass::OnMouseTriplePressed( code );

	// left triple clicking on a word selects all
	if (code == MOUSE_LEFT)
	{
		GotoTextEnd();

		SelectAllText( false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handle mouse double clicks
//-----------------------------------------------------------------------------
void TextEntry::OnMouseDoublePressed(MouseCode code)
{
	// 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)
		{
			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 TextEntry::OnMouseCaptureLost()
{
	_mouseSelection = false;
}

//-----------------------------------------------------------------------------
// Purpose: Only pass some keys upwards 
// everything else we don't relay to the parent
//-----------------------------------------------------------------------------
void TextEntry::OnKeyCodePressed(KeyCode code)
{
	// Pass enter on only if _catchEnterKey isn't set
	if ( code == KEY_ENTER )
	{
		if ( !_catchEnterKey )
		{
			Panel::OnKeyCodePressed( code );
			return;
		}
	}
	
	// Forward on just a few key codes, everything else can be handled by TextEntry itself
	switch ( code )
	{
		case KEY_F1:
		case KEY_F2:
		case KEY_F3:
		case KEY_F4:
		case KEY_F5:
		case KEY_F6:
		case KEY_F7:
		case KEY_F8:
		case KEY_F9:
		case KEY_F10:
		case KEY_F11:
		case KEY_F12:
		case KEY_ESCAPE:
		case KEY_APP:
			Panel::OnKeyCodePressed( code );
			return;
	}
	
	// Pass on the joystick and mouse codes
	if ( IsMouseCode(code) || IsNovintButtonCode(code) || IsJoystickCode(code) || IsJoystickButtonCode(code) ||
	     IsJoystickPOVCode(code) || IsJoystickAxisCode(code) )
	{
		Panel::OnKeyCodePressed( code );
		return;
	}
	    
}


//-----------------------------------------------------------------------------
// Purpose: Masks which keys get chained up
//			Maps keyboard input to text window functions.
//-----------------------------------------------------------------------------
void TextEntry::OnKeyCodeTyped(KeyCode code)
{
	_cursorIsAtEnd = _putCursorAtEnd;
	_putCursorAtEnd = false;
	
	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() ) ) && !alt)
	{
		switch(code)
		{
		case KEY_A:
			SelectAllText(false);
			// move the cursor to the end
			_cursorPos = _select[1];
			break;

		case KEY_INSERT:
		case KEY_C:
			{
				CopySelected();
				break;
			}
		case KEY_V:
			{
				DeleteSelected();
				Paste();
				break;
			}
		case KEY_X:
			{
				CopySelected();
				DeleteSelected();
				break;
			}
		case KEY_Z:
			{
				Undo();
				break;
			}
		case KEY_RIGHT:
			{
				GotoWordRight();
				break;
			}
		case KEY_LEFT:
			{
				GotoWordLeft();
				break;
			}
		case KEY_ENTER:
			{
				// insert a newline
				if (_multiline)
				{
					DeleteSelected();
					SaveUndoState();
					InsertChar('\n');
				}
				// fire newlines back to the main target if asked to
				if(_sendNewLines) 
				{
					PostActionSignal(new KeyValues("TextNewLine"));
				}
				break;
			}
		case KEY_HOME:
			{
				GotoTextStart();
				break;
			}
		case KEY_END:
			{
				GotoTextEnd();
				break;
			}
		case KEY_PAGEUP:
			{
				OnChangeIME( true );
			}
			break;
		case KEY_PAGEDOWN:
			{
				OnChangeIME( false );
			}
			break;
		case KEY_UP:
		case KEY_DOWN:
			if ( m_bAllowNonAsciiCharacters )
			{
				FlipToLastIME();
			}
			else
			{
				fallThrough = true;
			}
			break;
		default:
			{
				fallThrough = true;
				break;
			}
		}
	}
	else if (alt)
	{
		// do nothing with ALT-x keys
		if ( !m_bAllowNonAsciiCharacters || ( code != KEY_BACKQUOTE ) )
		{
			fallThrough = true;
		}
	}
	else
	{
		switch(code)
		{
		case KEY_TAB:
		case KEY_LSHIFT:
		case KEY_RSHIFT:
		case KEY_ESCAPE:
			{
				fallThrough = true;
				break;
			}
		case KEY_INSERT:
			{
				if (shift)
				{
					DeleteSelected();
					Paste();
				}
				else
				{
					fallThrough = true;
				}
				
				break;
			}
		case KEY_DELETE:
			{
				if (shift)
				{
					// shift-delete is cut
					CopySelected();
					DeleteSelected();
				}
				else
				{
					Delete();
				}
				break;
			}
		case KEY_LEFT:
			{
				GotoLeft();
				break;
			}
		case KEY_RIGHT:
			{
				GotoRight();
				break;
			}
		case KEY_UP:
			{
				if (_multiline)
				{
					GotoUp();
				}
				else
				{
					fallThrough = true;
				}
				break;
			}
		case KEY_DOWN:
			{
				if (_multiline)
				{
					GotoDown();
				}
				else
				{
					fallThrough = true;
				}
				break;
			}
		case KEY_HOME:
			{
				if (_multiline)
				{
					GotoFirstOfLine();
				}
				else
				{
					GotoTextStart();
				}
				break;
			}
		case KEY_END:
			{
				GotoEndOfLine();
				break;
			}
		case KEY_BACKSPACE:
			{
				int x0, x1;
				if (GetSelectedRange(x0, x1))
				{
					// act just like delete if there is a selection
					DeleteSelected();
				}
				else
				{
					Backspace();
				}
				break;
			}
		case KEY_ENTER:
			{
				// insert a newline
				if (_multiline && _catchEnterKey)
				{
					DeleteSelected();
					SaveUndoState();
					InsertChar('\n');
				}
				else
				{
					fallThrough = true;
				}
				// fire newlines back to the main target if asked to
				if(_sendNewLines) 
				{
					PostActionSignal(new KeyValues("TextNewLine"));
				}
				break;
			}
		case KEY_PAGEUP:
			{
				int val = 0;
				fallThrough = (!_multiline) && (!_vertScrollBar);
				if (_vertScrollBar)
				{
					val = _vertScrollBar->GetValue();
				}
				
				// if there is a scroll bar scroll down one rangewindow
				if (_multiline)
				{
					int displayLines = GetTall() / (surface()->GetFontTall(_font) + DRAW_OFFSET_Y);
					// move the cursor down
					for (int i=0; i < displayLines; i++)
					{
						GotoUp();
					}
				}
				
				// if there is a scroll bar scroll down one rangewindow
				if (_vertScrollBar)
				{
					int window = _vertScrollBar->GetRangeWindow();
					int newval = _vertScrollBar->GetValue();
					int linesToMove = window - (val - newval);
					_vertScrollBar->SetValue(val - linesToMove - 1);
				}
				break;
				
			}
		case KEY_PAGEDOWN:
			{
				int val = 0;
				fallThrough = (!_multiline) && (!_vertScrollBar);
				if (_vertScrollBar)
				{
					val = _vertScrollBar->GetValue();
				}
				
				if (_multiline)
				{
					int displayLines = GetTall() / (surface()->GetFontTall(_font) + DRAW_OFFSET_Y);
					// move the cursor down
					for (int i=0; i < displayLines; i++)
					{
						GotoDown();
					}
				}
				
				// if there is a scroll bar scroll down one rangewindow
				if (_vertScrollBar)
				{
					int window = _vertScrollBar->GetRangeWindow();
					int newval = _vertScrollBar->GetValue();
					int linesToMove = window - (newval - val);
					_vertScrollBar->SetValue(val + linesToMove + 1);
				}
				break;
			}

		case KEY_F1:
		case KEY_F2:
		case KEY_F3:
		case KEY_F4:
		case KEY_F5:
		case KEY_F6:
		case KEY_F7:
		case KEY_F8:
		case KEY_F9:
		case KEY_F10:
		case KEY_F11:
		case KEY_F12:
			{
				fallThrough = true;
				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;
	
	if (_dataChanged)
	{
		FireActionSignal();
	}
	
	// chain back on some keys
	if (fallThrough)
	{
		_putCursorAtEnd=_cursorIsAtEnd;	// keep state of cursor on fallthroughs
		BaseClass::OnKeyCodeTyped(code);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Masks which keys get chained up
//			Maps keyboard input to text window functions.
//-----------------------------------------------------------------------------
void TextEntry::OnKeyTyped(wchar_t unichar)
{
	_cursorIsAtEnd = _putCursorAtEnd;
	_putCursorAtEnd=false;
	
	bool fallThrough = false;
	
	// KeyCodes handle all non printable chars
	if (iswcntrl(unichar) || unichar == 9 ) // tab key (code 9) is printable but handled elsewhere
		return;
	
	// do readonly keys
	if (!IsEditable())
	{
		BaseClass::OnKeyTyped(unichar);
		return;
	}
	
	if (unichar != 0)
	{
		DeleteSelected();
		SaveUndoState();
		InsertChar(unichar);
	}
	
	// select[1] is the location in the line where the blinking cursor started
	_select[1] = _cursorPos;
	
	if (_dataChanged)
	{
		FireActionSignal();
	}
	
	// chain back on some keys
	if (fallThrough)
	{
		_putCursorAtEnd=_cursorIsAtEnd;	// keep state of cursor on fallthroughs
		BaseClass::OnKeyTyped(unichar);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Scrolls the list according to the mouse wheel movement
//-----------------------------------------------------------------------------
void TextEntry::OnMouseWheeled(int delta)
{
	if (_vertScrollBar)
	{
		MoveScrollBar(delta);
	}
	else
	{
		// if we don't use the input, chain back
		BaseClass::OnMouseWheeled(delta);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Scrolls the list 
// Input  : delta - amount to move scrollbar up
//-----------------------------------------------------------------------------
void TextEntry::MoveScrollBar(int delta)
{
	if (_vertScrollBar)
	{
		int val = _vertScrollBar->GetValue();
		val -= (delta * 3);
		_vertScrollBar->SetValue(val);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called every frame the entry has keyboard focus; 
//          blinks the text cursor
//-----------------------------------------------------------------------------
void TextEntry::OnKeyFocusTicked()
{
	int time=system()->GetTimeMillis();
	if(time>_cursorNextBlinkTime)
	{
		_cursorBlink=!_cursorBlink;
		_cursorNextBlinkTime=time+_cursorBlinkRate;
		Repaint();
	}
}

Panel *TextEntry::GetDragPanel()
{
	if ( input()->IsMouseDown( MOUSE_LEFT ) )
	{
		int x, y;
		input()->GetCursorPos(x, y);
		ScreenToLocal(x, y);
		int cursor = PixelToCursorSpace(x, y);
	
		int cx0, cx1;
		bool check = GetSelectedRange( cx0, cx1 );

		if ( check && cursor >= cx0 && cursor < cx1 )
		{
			// Don't deselect in this case!!!
			return BaseClass::GetDragPanel();
		}
		return NULL;
	}

	return BaseClass::GetDragPanel();
}

void TextEntry::OnCreateDragData( KeyValues *msg )
{
	BaseClass::OnCreateDragData( msg );

	char txt[ 256 ];
	GetText( txt, sizeof( txt ) );

	int r0, r1;
	if ( GetSelectedRange( r0, r1 ) && r0 != r1 )
	{
		int len = r1 - r0;
		if ( len > 0 && r0 < 1024 )
		{
			char selection[ 512 ];
			Q_strncpy( selection, &txt[ r0 ], len + 1 );
			selection[ len ] = 0;
			msg->SetString( "text", selection );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Check if we are selecting text (so we can highlight it)
//-----------------------------------------------------------------------------
bool TextEntry::SelectCheck( bool fromMouse /*=false*/ )
{
	bool bret = true;
	if (!HasFocus() || !(input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT)))
	{
		bool deselect = true;
		int cx0, cx1;
		if ( fromMouse && 
			GetDragPanel() != NULL )
		{
			// move the cursor to where the mouse was pressed
			int x, y;
			input()->GetCursorPos(x, y);
			ScreenToLocal(x, y);
			int cursor = PixelToCursorSpace(x, y);
		
			bool check = GetSelectedRange( cx0, cx1 );

			if ( check && cursor >= cx0 && cursor < cx1 )
			{
				// Don't deselect in this case!!!
				deselect = false;
				bret = false;
			}
		}

		if ( deselect )
		{
			_select[0] = -1;
		}
	}
	else if (_select[0] == -1)
	{
		_select[0] = _cursorPos;
	}
	return bret;
}

//-----------------------------------------------------------------------------
// Purpose: set the maximum number of chars in the text buffer
//-----------------------------------------------------------------------------
void TextEntry::SetMaximumCharCount(int maxChars)
{
	_maxCharCount = maxChars;
}

//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
int TextEntry::GetMaximumCharCount()
{
	return _maxCharCount;
}

//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
void TextEntry::SetAutoProgressOnHittingCharLimit(bool state)
{
	m_bAutoProgressOnHittingCharLimit = state;
}

//-----------------------------------------------------------------------------
// Purpose: set whether to wrap the text buffer
//-----------------------------------------------------------------------------
void TextEntry::SetWrap(bool wrap)
{
	_wrap = wrap;
}

//-----------------------------------------------------------------------------
// Purpose: set whether to pass newline msgs to parent
//-----------------------------------------------------------------------------
void TextEntry::SendNewLine(bool send)
{
	_sendNewLines = send;
}

//-----------------------------------------------------------------------------
// Purpose: Tell if an index is a linebreakindex
//-----------------------------------------------------------------------------
bool TextEntry::IsLineBreak(int index)
{
	for (int i=0; i<m_LineBreaks.Count(); ++i)
	{
		if (index ==  m_LineBreaks[i])
			return true;
	}
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Move the cursor one character to the left, scroll the text
//  horizontally if needed
//-----------------------------------------------------------------------------
void TextEntry::GotoLeft()
{
	SelectCheck();
	
	// if we are on a line break just move the cursor to the prev line
	if (IsLineBreak(_cursorPos))
	{
		// if we're already on the prev line at the end dont put it on the end
		if (!_cursorIsAtEnd)
			_putCursorAtEnd = true;
	}
	// if we are not at Start decrement cursor 
	if (!_putCursorAtEnd && _cursorPos > 0)
	{
		_cursorPos--;	
	}
	
    ScrollLeft();
	
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Move the cursor one character to the right, scroll the text
//			horizontally if needed
//-----------------------------------------------------------------------------
void TextEntry::GotoRight()
{
	SelectCheck();
	
	// if we are on a line break just move the cursor to the next line
	if (IsLineBreak(_cursorPos))
	{
		if (_cursorIsAtEnd)
		{
			_putCursorAtEnd = false;
		}
		else
		{
			// if we are not at end increment cursor 
			if (_cursorPos < m_TextStream.Count())
			{
				_cursorPos++;
			}	
		}
	}
	else
	{
		// if we are not at end increment cursor 
		if (_cursorPos < m_TextStream.Count())
		{
			_cursorPos++;
		}
		
		// if we are on a line break move the cursor to end of line 
		if (IsLineBreak(_cursorPos))
		{
			if (!_cursorIsAtEnd)
				_putCursorAtEnd = true;
		}
	}
	// scroll right if we need to
	ScrollRight();
	
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Find out what line the cursor is on
//-----------------------------------------------------------------------------
int TextEntry::GetCursorLine()
{
	// find which line the cursor is on
	int cursorLine;
	for (cursorLine = 0; cursorLine < m_LineBreaks.Count(); cursorLine++)
	{
		if (_cursorPos < m_LineBreaks[cursorLine])
			break;
	}
	
	if (_putCursorAtEnd)  // correct for when cursor is at end of line rather than Start of next
	{
		// we are not at end of buffer, in which case there is no next line to be at the Start of
		if (_cursorPos != m_TextStream.Count() ) 
			cursorLine--;
	}
	
	return cursorLine;
}

//-----------------------------------------------------------------------------
// Purpose: Move the cursor one line up 
//-----------------------------------------------------------------------------
void TextEntry::GotoUp()
{
	SelectCheck();
	
	if (_cursorIsAtEnd)
	{
		if ( (GetCursorLine() - 1 ) == 0) // we are on first line
		{
			// stay at end of line
			_putCursorAtEnd = true;
			return;	 // dont move the cursor
		}
		else
			_cursorPos--;  
	}
	
	int cx, cy;
	CursorToPixelSpace(_cursorPos, cx, cy);
	
	// move the cursor to the previous line
	MoveCursor(GetCursorLine() - 1, cx);
}


//-----------------------------------------------------------------------------
// Purpose: Move the cursor one line down
//-----------------------------------------------------------------------------
void TextEntry::GotoDown()
{
	SelectCheck();
	
	if (_cursorIsAtEnd)
	{
		_cursorPos--;
		if (_cursorPos < 0)
			_cursorPos = 0;
	}
	
	int cx, cy;
	CursorToPixelSpace(_cursorPos, cx, cy);
	
	// move the cursor to the next line
	MoveCursor(GetCursorLine() + 1, cx);
	if (!_putCursorAtEnd && _cursorIsAtEnd )
	{
		_cursorPos++;
		if (_cursorPos > m_TextStream.Count())
		{
			_cursorPos = m_TextStream.Count();
		}
	}
	LayoutVerticalScrollBarSlider();	
}

//-----------------------------------------------------------------------------
// Purpose: Set the starting ypixel positon for a walk through the window
//-----------------------------------------------------------------------------
int TextEntry::GetYStart()
{
	if (_multiline)
	{
		// just Start from the top
		return DRAW_OFFSET_Y;
	}

	int fontTall = surface()->GetFontTall(_font);
	return (GetTall() / 2) - (fontTall / 2);
}

//-----------------------------------------------------------------------------
// Purpose: Move the cursor to a line, need to know how many pixels are in a line
//-----------------------------------------------------------------------------
void TextEntry::MoveCursor(int line, int pixelsAcross)
{
	// clamp to a valid line
	if (line < 0)
		line = 0;
	if (line >= m_LineBreaks.Count())
		line = m_LineBreaks.Count() -1;
	
	// walk the whole text set looking for our place
	// work out where to Start checking
	
	int yStart = GetYStart();
	
	int x = DRAW_OFFSET_X, y = yStart;
	int lineBreakIndexIndex = 0;
	_pixelsIndent = 0;
	int i;
	for ( i = 0; i < m_TextStream.Count(); i++)
	{
		wchar_t ch = m_TextStream[i];
		
		if (_hideText)
		{
			ch = '*';
		}
		
		// if we've passed a line break go to that
		if (m_LineBreaks[lineBreakIndexIndex] == i)
		{
			if (lineBreakIndexIndex == line)
			{
				_putCursorAtEnd = true;
				_cursorPos = i;
				break;
			}
			
			// add another line
			AddAnotherLine(x,y);
			lineBreakIndexIndex++;
			
		}
		
		// add to the current position
		int charWidth = getCharWidth(_font, ch);		
		
		if (line == lineBreakIndexIndex)
		{
			// check to see if we're in range
			if ((x + (charWidth / 2)) > pixelsAcross)
			{
				// found position
				_cursorPos = i;
				break;
			}
		}
		
		x += charWidth;
	}
	
	// if we never find the cursor it must be past the end
	// of the text buffer, to let's just slap it on the end of the text buffer then.
	if (i ==  m_TextStream.Count())
	{
		GotoTextEnd();
	}
	
	LayoutVerticalScrollBarSlider();
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Turn horizontal scrolling on or off.
//			Horizontal scrolling is disabled in multline windows. 
//		    Toggling this will disable it in single line windows as well.
//-----------------------------------------------------------------------------
void TextEntry::SetHorizontalScrolling(bool status) 
{
	_horizScrollingAllowed = status;
}

//-----------------------------------------------------------------------------
// Purpose: Horizontal scrolling function, not used in multiline windows
//			Function will scroll the buffer to the left if the cursor is not in the window
//			scroll left if we need to 
//-----------------------------------------------------------------------------
void TextEntry::ScrollLeft()
{
	if (_multiline)	  // early out
	{
		return;
	}
	
	if (!_horizScrollingAllowed)  //early out
	{
		return;
	}
	
	if(_cursorPos < _currentStartIndex)	 // scroll left if we need to
	{
		if (_cursorPos < 0)// dont scroll past the Start of buffer
		{
			_cursorPos=0;			
		}
		_currentStartIndex = _cursorPos;
	}
	
	LayoutVerticalScrollBarSlider();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TextEntry::ScrollLeftForResize()
{
	if (_multiline)	  // early out
	{
		return;
	}
	
	if (!_horizScrollingAllowed)  //early out
	{
		return;
	}

    while (_currentStartIndex > 0)     // go until we hit leftmost
    {
        _currentStartIndex--;
		int nVal = _currentStartIndex;

        // check if the cursor is now off the screen
        if (IsCursorOffRightSideOfWindow(_cursorPos))
        {
            _currentStartIndex++;   // we've gone too far, return it
            break;
        }

        // IsCursorOffRightSideOfWindow actually fixes the _currentStartIndex, 
        // so if our value changed that menas we really are off the screen
		if (nVal != _currentStartIndex)
			break;
    }
	LayoutVerticalScrollBarSlider();
}

//-----------------------------------------------------------------------------
// Purpose: Horizontal scrolling function, not used in multiline windows
//			Scroll one char right until the cursor is visible in the window.
//			We do this one char at a time because char width isn't a constant.
//-----------------------------------------------------------------------------
void TextEntry::ScrollRight()
{
	if (!_horizScrollingAllowed)
		return;

	if (_multiline)	  
	{
	}
	// check if cursor is off the right side of window
	else if (IsCursorOffRightSideOfWindow(_cursorPos))
	{
		_currentStartIndex++; //scroll over
		ScrollRight(); // scroll again, check if cursor is in window yet 
	}
	
	LayoutVerticalScrollBarSlider();
}

//-----------------------------------------------------------------------------
// Purpose: Check and see if cursor position is off the right side of the window
//			just compare cursor's pixel coords with the window size coords.
// Input:	an integer cursor Position, if you pass _cursorPos fxn will tell you
//			if current cursor is outside window.
// Output:	true: cursor is outside right edge or window 
//			false: cursor is inside right edge
//-----------------------------------------------------------------------------
bool TextEntry::IsCursorOffRightSideOfWindow(int cursorPos)
{
	int cx, cy;
	CursorToPixelSpace(cursorPos, cx, cy);
	int wx=GetWide()-1;	//width of inside of window is GetWide()-1
	if ( wx <= 0 )
		return false;

	return (cx >= wx);
}

//-----------------------------------------------------------------------------
// Purpose: Check and see if cursor position is off the left side of the window
//			just compare cursor's pixel coords with the window size coords.
// Input:	an integer cursor Position, if you pass _cursorPos fxn will tell you
//			if current cursor is outside window.
// Output:	true - cursor is outside left edge or window 
//			false - cursor is inside left edge
//-----------------------------------------------------------------------------
bool TextEntry::IsCursorOffLeftSideOfWindow(int cursorPos)
{
	int cx, cy;
	CursorToPixelSpace(cursorPos, cx, cy);
	return (cx <= 0);
}

//-----------------------------------------------------------------------------
// Purpose: Move the cursor over to the Start of the next word to the right
//-----------------------------------------------------------------------------
void TextEntry::GotoWordRight()
{
	SelectCheck();
	
	// 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
		
	// scroll right if we need to
	ScrollRight();
	
	LayoutVerticalScrollBarSlider();
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Move the cursor over to the Start of the next word to the left
//-----------------------------------------------------------------------------
void TextEntry::GotoWordLeft()
{
	SelectCheck();
	
	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
	
	
	// scroll left if we need to
	ScrollLeft();
	
	LayoutVerticalScrollBarSlider();
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Move cursor to the Start of the text buffer
//-----------------------------------------------------------------------------
void TextEntry::GotoTextStart()
{
	SelectCheck();
	_cursorPos = 0;		   // set cursor to Start
	_putCursorAtEnd = false;
	_currentStartIndex=0;  // scroll over to Start
	
	LayoutVerticalScrollBarSlider();
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Move cursor to the end of the text buffer
//-----------------------------------------------------------------------------
void TextEntry::GotoTextEnd()
{
	SelectCheck();
	_cursorPos=m_TextStream.Count();	// set cursor to end of buffer
	_putCursorAtEnd = true; // move cursor Start of next line
	ScrollRight();				// scroll over until cursor is on screen
	
	LayoutVerticalScrollBarSlider();
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Move cursor to the Start of the current line
//-----------------------------------------------------------------------------
void TextEntry::GotoFirstOfLine()
{
	SelectCheck();
	// to get to the Start of the line you have to take into account line wrap
	// we have to figure out at which point the line wraps
	// given the current cursor position, select[1], find the index that is the
	// line Start to the left of the cursor
	//_cursorPos = 0; //TODO: this is wrong, should go to first non-whitespace first, then to zero
	_cursorPos = GetCurrentLineStart();
	_putCursorAtEnd = false;
	
	_currentStartIndex=_cursorPos;
	
	LayoutVerticalScrollBarSlider();
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Get the index of the first char on the current line
//-----------------------------------------------------------------------------
int TextEntry::GetCurrentLineStart()
{
	if (!_multiline)			// quick out for non multline buffers
		return _currentStartIndex;
	
	int i;
	if (IsLineBreak(_cursorPos))
	{
		for (i = 0; i < m_LineBreaks.Count(); ++i )
		{
			if (_cursorPos == m_LineBreaks[i])
				break;
		}
		if (_cursorIsAtEnd)
		{
			if (i > 0)
			{
				return m_LineBreaks[i-1];
			}
			return m_LineBreaks[0];
		}
		else
			return _cursorPos; // we are already at Start
	}
	
	for ( i = 0; i < m_LineBreaks.Count(); ++i )
	{
		if (_cursorPos < m_LineBreaks[i])
		{
			if (i == 0)
				return 0;
			else
				return m_LineBreaks[i-1];
		}
	}
	// if there were no line breaks, the first char in the line is the Start of the buffer
	return 0;
}

//-----------------------------------------------------------------------------
// Purpose: Move cursor to the end of the current line
//-----------------------------------------------------------------------------
void TextEntry::GotoEndOfLine()
{
	SelectCheck();
	// to get to the end of the line you have to take into account line wrap in the buffer
	// we have to figure out at which point the line wraps
	// given the current cursor position, select[1], find the index that is the
	// line end to the right of the cursor
	//_cursorPos=m_TextStream.Count(); //TODO: this is wrong, should go to last non-whitespace, then to true EOL
    _cursorPos = GetCurrentLineEnd();
	_putCursorAtEnd = true;
	
	ScrollRight();
	
	LayoutVerticalScrollBarSlider();
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Get the index of the last char on the current line
//-----------------------------------------------------------------------------
int TextEntry::GetCurrentLineEnd()
{
	int i;
	if (IsLineBreak(_cursorPos)	)
	{
		for ( i = 0; i < m_LineBreaks.Count()-1; ++i )
		{
			if (_cursorPos == m_LineBreaks[i])
				break;
		}
		if (!_cursorIsAtEnd)
		{
			if (i == m_LineBreaks.Count()-2 )
				m_TextStream.Count();		
			else
				return m_LineBreaks[i+1];
		}
		else
			return _cursorPos; // we are already at end
	}
	
	for ( i = 0; i < m_LineBreaks.Count()-1; i++ )
	{
		if ( _cursorPos < m_LineBreaks[i])
		{
			return m_LineBreaks[i];
		}
	}
	return m_TextStream.Count();
}

//-----------------------------------------------------------------------------
// Purpose: Insert a character into the text buffer
//-----------------------------------------------------------------------------
void TextEntry::InsertChar(wchar_t ch)
{
	// throw away redundant linefeed characters
	if (ch == '\r')
		return;
	
	// no newline characters in single-line dialogs
	if (!_multiline && ch == '\n')
		return;

	// no tab characters
	if (ch == '\t')
		return;

	if (m_bAllowNumericInputOnly)
	{
		if (!iswdigit(ch) && ((char)ch != '.'))
		{
			surface()->PlaySound("Resource\\warning.wav");
			return;
		}
	}
	
	// check against unicode characters
	if (!m_bAllowNonAsciiCharacters)
	{
		if (ch > 127)
			return;
	}

	// don't add characters if the max char count has been reached
	// ding at the user
	if (_maxCharCount > -1 && m_TextStream.Count() >= _maxCharCount)
	{
		if (_maxCharCount>0 && _multiline && _wrap)
		{
			// if we wrap lines rather than stopping
			while (m_TextStream.Count() > _maxCharCount)
			{
				if (_recalculateBreaksIndex==0) 
				{
					// we can get called before this has been run for the first time :)
					RecalculateLineBreaks();
				}
				if (m_LineBreaks[0]> m_TextStream.Count())
				{
					// if the line break is the past the end of the buffer recalc
					_recalculateBreaksIndex=-1;
					RecalculateLineBreaks();
				}
				
				if (m_LineBreaks[0]+1 < m_TextStream.Count())
				{
					// delete the line
					m_TextStream.RemoveMultiple(0, m_LineBreaks[0]);
					
					// in case we just deleted text from where the cursor is
					if (_cursorPos> m_TextStream.Count())
					{
						_cursorPos = m_TextStream.Count(); 
					}
					else
					{ // shift the cursor up. don't let it wander past zero
						_cursorPos-=m_LineBreaks[0]+1;
						if (_cursorPos<0)
						{
							_cursorPos=0;
						}
					}
					
					// move any selection area up
					if(_select[0]>-1)
					{
						_select[0] -=m_LineBreaks[0]+1;
						
						if(_select[0] <=0)
						{
							_select[0] =-1;
						}
						
						_select[1] -=m_LineBreaks[0]+1;
						if(_select[1] <=0)
						{
							_select[1] =-1;
						}
						
					}
					
					// now redraw the buffer
					for (int i = m_TextStream.Count() - 1; i >= 0; i--)
					{
						SetCharAt(m_TextStream[i], i+1);
					}
					
					// redo all the line breaks
					_recalculateBreaksIndex=-1;
					RecalculateLineBreaks();
					
				}
			}
			
		} 
		else
		{
			// make a sound
			// we've hit the max character limit
			surface()->PlaySound("Resource\\warning.wav");
			return;
		}
	}
	
	
	if (_wrap) 
	{
		// when wrapping you always insert the new char at the end of the buffer
		SetCharAt(ch, m_TextStream.Count());
		_cursorPos=m_TextStream.Count(); 
	}
	else 
	{
		// move chars right 1 starting from cursor, then replace cursorPos with char and increment cursor
		for (int i =  m_TextStream.Count()- 1; i >= _cursorPos; i--)
		{
			SetCharAt(m_TextStream[i], i+1);
		}
		
		SetCharAt(ch, _cursorPos);
		_cursorPos++;
	}
	
	// if its a newline char we can't do the slider until we recalc the line breaks
	if (ch == '\n')
	{
		RecalculateLineBreaks();
	}
	
	// see if we've hit the char limit
	if (m_bAutoProgressOnHittingCharLimit && m_TextStream.Count() == _maxCharCount)
	{
		// move the next panel (most likely another TextEntry)
		RequestFocusNext();
	}

	// scroll right if this pushed the cursor off screen
	ScrollRight();
	
	_dataChanged = true;
	
	CalcBreakIndex();
	LayoutVerticalScrollBarSlider();
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Get the lineBreakIndex index of the line before the cursor
//			note _recalculateBreaksIndex < 0 flags RecalculateLineBreaks 
//			to figure it all out from scratch
//-----------------------------------------------------------------------------
void TextEntry::CalcBreakIndex()
{
	// an optimization to handle when the cursor is at the end of the buffer.
	// pays off if the buffer is large, and the search loop would be long.
	if (_cursorPos == m_TextStream.Count())
	{
		// we know m_LineBreaks array always has at least one element in it (99999 sentinel)
		// when there is just one line this will make recalc = -1 which is ok.
		_recalculateBreaksIndex = m_LineBreaks.Count()-2;
		return;
	}
	
	_recalculateBreaksIndex=0;
	// find the line break just before the cursor position
	while (_cursorPos > m_LineBreaks[_recalculateBreaksIndex])
		++_recalculateBreaksIndex;
	
	// -1  is ok.
	--_recalculateBreaksIndex;	
}

//-----------------------------------------------------------------------------
// 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 TextEntry::InsertString(const wchar_t *wszText)
{
	SaveUndoState();

	for (const wchar_t *ch = wszText; *ch != 0; ++ch)
	{
		InsertChar(*ch);
	}
	
	if (_dataChanged)
	{
		FireActionSignal();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Converts an ansi string to unicode and inserts it into the text stream
//-----------------------------------------------------------------------------
void TextEntry::InsertString(const char *text)
{
	// check for to see if the string is in the localization tables
	if (text[0] == '#')
	{
		wchar_t *wsz = g_pVGuiLocalize->Find(text);
		if (wsz)
		{
			InsertString(wsz);
			return;
		}
	}

	// straight convert the ansi to unicode and insert
	wchar_t unicode[1024];
	g_pVGuiLocalize->ConvertANSIToUnicode(text, unicode, sizeof(unicode));
	InsertString(unicode);
}

//-----------------------------------------------------------------------------
// Purpose: Handle the effect of user hitting backspace key
//			we delete the char before the cursor and reformat the text so it
//			behaves like in windows.
//-----------------------------------------------------------------------------
void TextEntry::Backspace()
{
	if (!IsEditable())
		return;

	//if you are at the first position don't do anything
	if(_cursorPos==0)
	{
		return;
	}
	
	//if the line is empty, don't do anything
	if(m_TextStream.Count()==0)
	{
		return;
	}
	
	SaveUndoState();
	
	//shift chars left one, starting at the cursor position, then make the line one smaller
	for(int i=_cursorPos;i<m_TextStream.Count(); ++i)
	{
		SetCharAt(m_TextStream[i],i-1);
	}
	m_TextStream.Remove(m_TextStream.Count() - 1);
	
	// As we hit the Start of the window, expose more chars so we can see what we are deleting
	if (_cursorPos==_currentStartIndex)
	{
		// windows tabs over 6 chars
		if (_currentStartIndex-6 >= 0) // dont scroll if there are not enough chars to scroll
		{
			_currentStartIndex-=6; 
		}
		else
			_currentStartIndex=0;
	}
	
	//move the cursor left one
	_cursorPos--;
	
	_dataChanged = true;
	
	// recalculate linebreaks (the fast incremental linebreak function doesn't work in this case)
	_recalculateBreaksIndex = 0;
	m_LineBreaks.RemoveAll();
	m_LineBreaks.AddToTail(BUFFER_SIZE);

	LayoutVerticalScrollBarSlider();
	ResetCursorBlink();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Deletes the current selection, if any, moving the cursor to the Start
//			of the selection
//-----------------------------------------------------------------------------
void TextEntry::DeleteSelected()
{
	if (!IsEditable())
		return;

	// if the line is empty, don't do anything
	if (m_TextStream.Count() == 0)
		return;
	
	// get the range to delete
	int x0, x1;
	if (!GetSelectedRange(x0, x1))
	{
		// no selection, don't touch anything
		return;
	}
	
	SaveUndoState();
	
	// shift chars left one starting after cursor position, then make the line one smaller
	int dif = x1 - x0;
	for (int i = 0; i < dif; ++i)
	{
		m_TextStream.Remove(x0);
	}
	
	// clear any selection
	SelectNone();
	ResetCursorBlink();
	
	// move the cursor to just after the deleted section
	_cursorPos = x0;
	
	_dataChanged = true;
	
	_recalculateBreaksIndex = 0;
	m_LineBreaks.RemoveAll();
	m_LineBreaks.AddToTail(BUFFER_SIZE);

	CalcBreakIndex();
	
	LayoutVerticalScrollBarSlider();
}

//-----------------------------------------------------------------------------
// Purpose: Handle the effect of the user hitting the delete key
//			removes the char in front of the cursor
//-----------------------------------------------------------------------------
void TextEntry::Delete()
{
	if (!IsEditable())
		return;

	// if the line is empty, don't do anything
	if (m_TextStream.Count() == 0)
		return;
	
	// get the range to delete
	int x0, x1;
	if (!GetSelectedRange(x0, x1))
	{
		// no selection, so just delete the one character
		x0 = _cursorPos;
		x1 = x0 + 1;
		
		// if we're at the end of the line don't do anything
		if (_cursorPos >= m_TextStream.Count())
			return;
	}
	
	SaveUndoState();
	
	// shift chars left one starting after cursor position, then make the line one smaller
	int dif = x1 - x0;
	for (int i = 0; i < dif; i++)
	{
		m_TextStream.Remove((int)x0);
	}
	
	ResetCursorBlink();
	
	// clear any selection
	SelectNone();
	
	// move the cursor to just after the deleted section
	_cursorPos = x0;
	
	_dataChanged = true;
	
	_recalculateBreaksIndex = 0;
	m_LineBreaks.RemoveAll();
	m_LineBreaks.AddToTail(BUFFER_SIZE);

	CalcBreakIndex();
	
	LayoutVerticalScrollBarSlider();
}

//-----------------------------------------------------------------------------
// Purpose: Declare a selection empty
//-----------------------------------------------------------------------------
void TextEntry::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 TextEntry::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];
	int temp;
	if(cx1<cx0){temp=cx0;cx0=cx1;cx1=temp;}
	
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Opens the cut/copy/paste dropdown menu
//-----------------------------------------------------------------------------
void TextEntry::OpenEditMenu()
{
	// get cursor position, this is local to this text edit window
	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 TextEntry::CutSelected()
{
	CopySelected();
	DeleteSelected();
	// have to request focus if we used the menu
	RequestFocus();	

	if ( _dataChanged )
	{
		FireActionSignal();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Copies the selected chars into the clipboard
//-----------------------------------------------------------------------------
void TextEntry::CopySelected()
{
	if (_hideText)
		return;
	
	int x0, x1;
	if (GetSelectedRange(x0, x1))
	{
		CUtlVector<wchar_t> buf;
		for (int i = x0; i < x1; i++)
		{
			if ( m_TextStream[i]=='\n') 
			{
				buf.AddToTail( '\r' );
			}
			buf.AddToTail(m_TextStream[i]);
		}
		buf.AddToTail('\0');
		system()->SetClipboardText(buf.Base(), buf.Count());
	}
	
	// have to request focus if we used the menu
	RequestFocus();	
	
	if ( _dataChanged )
	{
		FireActionSignal();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Pastes the selected chars from the clipboard into the text buffer
//			truncates if text is longer than our _maxCharCount
//-----------------------------------------------------------------------------
void TextEntry::Paste()
{
	if (!IsEditable())
		return;

	CUtlVector<wchar_t> buf;
	int bufferSize = system()->GetClipboardTextCount();
	if (!m_bAutoProgressOnHittingCharLimit)
	{
		bufferSize = _maxCharCount > 0 ? _maxCharCount + 1 : system()->GetClipboardTextCount();  // +1 for terminator
	}

	buf.AddMultipleToTail(bufferSize);
	int len = system()->GetClipboardText(0, buf.Base(), bufferSize * sizeof(wchar_t));
	if (len < 1)
		return;
	
	SaveUndoState();
	bool bHaveMovedFocusAwayFromCurrentEntry = false;

	// insert all the characters
	for (int i = 0; i < len && buf[i] != 0; i++)
	{
		if (m_bAutoProgressOnHittingCharLimit)
		{
			// see if we're about to hit the char limit
			if (m_TextStream.Count() == _maxCharCount)
			{
				// move the next panel (most likely another TextEntry)
				RequestFocusNext();
				// copy the remainder into the clipboard
				wchar_t *remainingText = &buf[i];
				system()->SetClipboardText(remainingText, len - i - 1);
				// set the next entry to paste
				if (GetVParent() && ipanel()->GetCurrentKeyFocus(GetVParent()) != GetVPanel())
				{
					bHaveMovedFocusAwayFromCurrentEntry = true;
					ipanel()->SendMessage(ipanel()->GetCurrentKeyFocus(GetVParent()), new KeyValues("DoPaste"), GetVPanel());
				}
				break;
			}
		}

		// insert the character
		InsertChar(buf[i]);
	}

	// restore the original clipboard text if neccessary
	if (m_bAutoProgressOnHittingCharLimit)
	{
		system()->SetClipboardText(buf.Base(), bufferSize);
	}
	
	_dataChanged = true;
	FireActionSignal();

	if (!bHaveMovedFocusAwayFromCurrentEntry)
	{
		// have to request focus if we used the menu
		RequestFocus();	
	}
}

//-----------------------------------------------------------------------------
// Purpose: Reverts back to last saved changes
//-----------------------------------------------------------------------------
void TextEntry::Undo()
{
	_cursorPos = _undoCursorPos;
	m_TextStream.CopyArray(m_UndoTextStream.Base(), m_UndoTextStream.Count());
	
	InvalidateLayout();
	Repaint();
	SelectNone();
}

//-----------------------------------------------------------------------------
// Purpose: Saves the current state to the undo stack
//-----------------------------------------------------------------------------
void TextEntry::SaveUndoState()
{
	_undoCursorPos = _cursorPos;
	m_UndoTextStream.CopyArray(m_TextStream.Base(), m_TextStream.Count());
}

//-----------------------------------------------------------------------------
// Purpose: Returns the index in the text buffer of the
//          character the drawing should Start at
//-----------------------------------------------------------------------------
int TextEntry::GetStartDrawIndex(int &lineBreakIndexIndex)
{
	int startIndex = 0;
	
	int numLines = m_LineBreaks.Count();
	int startLine = 0;
	
	// determine the Start point from the scroll bar
	// do this only if we are not selecting text in the window with the mouse
	if (_vertScrollBar && !_mouseDragSelection)
	{	
		// skip to line indicated by scrollbar
		startLine = _vertScrollBar->GetValue();
	}
	else
	{
		// check to see if the cursor is off the screen-multiline case
		HFont font = _font;
		int displayLines = GetTall() / (surface()->GetFontTall(font) + DRAW_OFFSET_Y);
		if (displayLines < 1)
		{
			displayLines = 1;
		}
		if (numLines > displayLines)
		{
			int cursorLine = GetCursorLine();
			
			startLine = _currentStartLine;
			
			// see if that is visible
			if (cursorLine < _currentStartLine)
			{
				// cursor is above visible area; scroll back
				startLine = cursorLine;
				if (_vertScrollBar)
				{
					MoveScrollBar( 1 ); // should be calibrated for speed 
					// adjust startline incase we hit a limit
					startLine = _vertScrollBar->GetValue(); 
				}
			}
			else if (cursorLine > (_currentStartLine + displayLines - 1))
			{
				// cursor is down below visible area; scroll forward
				startLine = cursorLine - displayLines + 1;
				if (_vertScrollBar)
				{
					MoveScrollBar( -1 );
					startLine = _vertScrollBar->GetValue();
				}
			}
		}
		else if (!_multiline)
		{
			// check to see if cursor is off the right side of screen-single line case
			// get cursor's x coordinate in pixel space
			bool done = false;
			while ( !done )
			{
				done = true;
				int x = DRAW_OFFSET_X;
				for (int i = _currentStartIndex; i < m_TextStream.Count(); i++)
				{
					done = false;
					wchar_t ch = m_TextStream[i];			
					if (_hideText)
					{
						ch = '*';
					}
					
					// if we've found the position, break
					if (_cursorPos == i)
					{
						break;
					}
					
					// add to the current position		
					x += getCharWidth(font, ch);				
				}
				
				if ( x >= GetWide() )
				{
					_currentStartIndex++;
					// Keep searching...
					continue;
				}
				
				if ( x <= 0 )
				{
					// dont go past the Start of buffer
					if (_currentStartIndex > 0)
						_currentStartIndex--;
				}

				break;
			}
		}
	}
	
	if (startLine > 0)
	{
		lineBreakIndexIndex = startLine;
		if (startLine && startLine < m_LineBreaks.Count())
		{
			startIndex = m_LineBreaks[startLine - 1];
		}
	}
	
	if (!_horizScrollingAllowed)
		return 0;

	_currentStartLine = startLine;
	if (_multiline)
		return startIndex;
	else 
		return _currentStartIndex;

	
}

// helper accessors for common gets
float TextEntry::GetValueAsFloat()
{
	int nTextLength = GetTextLength() + 1;
	char* txt = ( char* )_alloca( nTextLength * sizeof( char ) );
	GetText( txt, nTextLength );

	return V_atof( txt );
}

int TextEntry::GetValueAsInt()
{
	int nTextLength = GetTextLength() + 1;
	char* txt = ( char* )_alloca( nTextLength * sizeof( char ) );
	GetText( txt, nTextLength );

	return V_atoi( txt );
}

//-----------------------------------------------------------------------------
// Purpose: Get a string from text buffer
// Input:	offset - index to Start reading from 
//			bufLenInBytes - length of string
//-----------------------------------------------------------------------------
void TextEntry::GetText(OUT_Z_BYTECAP(bufLenInBytes) char *buf, int bufLenInBytes)
{
	Assert(bufLenInBytes >= sizeof(buf[0]));
	if (m_TextStream.Count())
	{
		// temporarily null terminate the text stream so we can use the conversion function
		int nullTerminatorIndex = m_TextStream.AddToTail((wchar_t)0);
		g_pVGuiLocalize->ConvertUnicodeToANSI(m_TextStream.Base(), buf, bufLenInBytes);
		m_TextStream.FastRemove(nullTerminatorIndex);
	}
	else
	{
		// no characters in the stream
		buf[0] = 0;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Get a string from text buffer
// Input:	offset - index to Start reading from 
//			bufLen - length of string
//-----------------------------------------------------------------------------
void TextEntry::GetText(OUT_Z_BYTECAP(bufLenInBytes) wchar_t *wbuf, int bufLenInBytes)
{
	Assert(bufLenInBytes >= sizeof(wbuf[0]));
	int len = m_TextStream.Count();
	if (m_TextStream.Count())
	{
		int terminator = min(len, (bufLenInBytes / (int)sizeof(wchar_t)) - 1);
		wcsncpy(wbuf, m_TextStream.Base(), terminator);
		wbuf[terminator] = 0;
	}
	else
	{
		wbuf[0] = 0;
	}
}

void TextEntry::GetTextRange( wchar_t *buf, int from, int numchars )
{
	int len = m_TextStream.Count();
	int cpChars = max( 0, min( numchars, len - from ) );
	
	wcsncpy( buf, m_TextStream.Base() + max( 0, min( len, from ) ), cpChars );
	buf[ cpChars ] = 0;
}

void TextEntry::GetTextRange( char *buf, int from, int numchars )
{
	int len = m_TextStream.Count();
	int cpChars = max( 0, min( numchars, len - from ) );

	g_pVGuiLocalize->ConvertUnicodeToANSI( m_TextStream.Base() + max( 0, min( len, from ) ), buf, cpChars + 1 );
	buf[ cpChars ] = 0;
}

//-----------------------------------------------------------------------------
// Purpose: Sends a message that the text has changed
//-----------------------------------------------------------------------------
void TextEntry::FireActionSignal()
{
	PostActionSignal(new KeyValues("TextChanged"));
	_dataChanged = false;	// reset the data changed flag
	InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose: Set the font of the buffer text 
// Input:	font to change to
//-----------------------------------------------------------------------------
void TextEntry::SetFont(HFont font)
{
	_font = font;
	InvalidateLayout();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: Called when the scrollbar slider is moved
//-----------------------------------------------------------------------------
void TextEntry::OnSliderMoved()
{
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool TextEntry::RequestInfo(KeyValues *outputData)
{
	if (!stricmp(outputData->GetName(), "GetText"))
	{
		wchar_t wbuf[256];
		GetText(wbuf, 255);
		outputData->SetWString("text", wbuf);
		return true;
	}
	else if (!stricmp(outputData->GetName(), "GetState"))
	{
		char buf[64];
		GetText(buf, sizeof(buf));
		outputData->SetInt("state", atoi(buf));
		return true;
	}
	return BaseClass::RequestInfo(outputData);
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TextEntry::OnSetText(const wchar_t *text)
{
	SetText(text);
}

//-----------------------------------------------------------------------------
// Purpose: as above, but sets an integer
//-----------------------------------------------------------------------------
void TextEntry::OnSetState(int state)
{
	char buf[64];
	Q_snprintf(buf, sizeof(buf), "%d", state);
	SetText(buf);
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TextEntry::ApplySettings( KeyValues *inResourceData )
{
	BaseClass::ApplySettings( inResourceData );

	_font = scheme()->GetIScheme( GetScheme() )->GetFont( inResourceData->GetString( "font", "Default" ), IsProportional() );
	SetFont( _font );

	SetTextHidden((bool)inResourceData->GetInt("textHidden", 0));
	SetEditable((bool)inResourceData->GetInt("editable", 1));
	SetMaximumCharCount(inResourceData->GetInt("maxchars", -1));
	SetAllowNumericInputOnly(inResourceData->GetInt("NumericInputOnly", 0));
	SetAllowNonAsciiCharacters(inResourceData->GetInt("unicode", 0));
	SelectAllOnFirstFocus(inResourceData->GetInt("selectallonfirstfocus", 0));
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TextEntry::GetSettings( KeyValues *outResourceData )
{
	BaseClass::GetSettings( outResourceData );
	outResourceData->SetInt("textHidden", _hideText);
	outResourceData->SetInt("editable", IsEditable());
	outResourceData->SetInt("maxchars", GetMaximumCharCount());
	outResourceData->SetInt("NumericInputOnly", m_bAllowNumericInputOnly);
	outResourceData->SetInt("unicode", m_bAllowNonAsciiCharacters);
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const char *TextEntry::GetDescription()
{
	static char buf[1024];
	Q_snprintf(buf, sizeof(buf), "%s, bool textHidden, bool editable, bool unicode, bool NumericInputOnly, int maxchars", BaseClass::GetDescription());
	return buf;
}

//-----------------------------------------------------------------------------
// Purpose: Get the number of lines in the window
//-----------------------------------------------------------------------------
int TextEntry::GetNumLines()
{
	return m_LineBreaks.Count();
}

//-----------------------------------------------------------------------------
// Purpose: Sets the height of the text entry window so all text will fit inside
//-----------------------------------------------------------------------------
void TextEntry::SetToFullHeight()
{
	PerformLayout();
	int wide, tall;
	GetSize(wide, tall);
	
	tall = GetNumLines() * (surface()->GetFontTall(_font) + DRAW_OFFSET_Y) + DRAW_OFFSET_Y + 2;
	SetSize (wide, tall);
	PerformLayout();
	
}

//-----------------------------------------------------------------------------
// Purpose: Select all the text.
//-----------------------------------------------------------------------------
void TextEntry::SelectAllText( bool bResetCursorPos )
{
	// if there's no text at all, select none
	if ( m_TextStream.Count() == 0 )
	{
		_select[0] = -1;
	}
	else
	{
		_select[0] = 0;
	}

	_select[1] = m_TextStream.Count();

	if ( bResetCursorPos )
	{
		_cursorPos = _select[1];
	}
}

//-----------------------------------------------------------------------------
// Purpose: Select no text.
//-----------------------------------------------------------------------------
void TextEntry::SelectNoText()
{
	_select[0] = -1;
	_select[1] = 0;
}

//-----------------------------------------------------------------------------
// Purpose: Sets the width of the text entry window so all text will fit inside
//-----------------------------------------------------------------------------
void TextEntry::SetToFullWidth()
{
	// probably be problems if you try using this on multi line buffers
	// or buffers with clickable text in them.
	if (_multiline)
		return;
	
	PerformLayout();
	int wide = 2*DRAW_OFFSET_X; // buffer on left and right end of text.
	
	// loop through all the characters and sum their widths	
	for (int i = 0; i < m_TextStream.Count(); ++i)
	{
		wide += getCharWidth(_font, m_TextStream[i]);	
	}
	
	// height of one line of text
	int tall = (surface()->GetFontTall(_font) + DRAW_OFFSET_Y) + DRAW_OFFSET_Y + 2;
	
	SetSize (wide, tall);
	PerformLayout();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TextEntry::SelectAllOnFirstFocus( bool status )
{
	_selectAllOnFirstFocus = status;
}

void TextEntry::SelectAllOnFocusAlways( bool status )
{
	_selectAllOnFirstFocus = status;
	_selectAllOnFocusAlways = status;
}

//-----------------------------------------------------------------------------
// Purpose: called when the text entry receives focus
//-----------------------------------------------------------------------------
void TextEntry::OnSetFocus()
{ 
	// see if we should highlight all on selection
    if (_selectAllOnFirstFocus)
	{
		_select[1] = m_TextStream.Count();
		_select[0] = _select[1] > 0 ? 0 : -1;
		_cursorPos = _select[1]; // cursor at end of line
		if ( !_selectAllOnFocusAlways )
		{
			_selectAllOnFirstFocus = false;
		}
    }
	else if (input()->IsKeyDown(KEY_TAB) || input()->WasKeyReleased(KEY_TAB))
	{
		// if we've tabbed to this field then move to the end of the text
		GotoTextEnd();
		// clear any selection
		SelectNone();
	}
	
	BaseClass::OnSetFocus();
}

//-----------------------------------------------------------------------------
// Purpose: Set the width we have to draw text in.
//			Do not use in multiline windows.
//-----------------------------------------------------------------------------
void TextEntry::SetDrawWidth(int width)
{
	_drawWidth = width;
}

//-----------------------------------------------------------------------------
// Purpose: Get the width we have to draw text in.
//-----------------------------------------------------------------------------
int TextEntry::GetDrawWidth()
{
	return _drawWidth;
}

//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
void TextEntry::SetAllowNonAsciiCharacters(bool state)
{
	m_bAllowNonAsciiCharacters = state;
}

//-----------------------------------------------------------------------------
// Purpose: data accessor
//-----------------------------------------------------------------------------
void TextEntry::SetAllowNumericInputOnly(bool state)
{
	m_bAllowNumericInputOnly = state;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : forward - 
//-----------------------------------------------------------------------------
void TextEntry::OnChangeIME( bool forward )
{
	// Only change ime if Unicode aware
	if ( m_bAllowNonAsciiCharacters )
	{
		input()->OnChangeIME( forward );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : handleValue - 
//-----------------------------------------------------------------------------
void TextEntry::LanguageChanged( int handleValue )
{
	input()->OnChangeIMEByHandle( handleValue );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : handleValue - 
//-----------------------------------------------------------------------------
void TextEntry::ConversionModeChanged( int handleValue )
{
	input()->OnChangeIMEConversionModeByHandle( handleValue );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : handleValue - 
//-----------------------------------------------------------------------------
void TextEntry::SentenceModeChanged( int handleValue )
{
	input()->OnChangeIMESentenceModeByHandle( handleValue );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *compstr - 
//-----------------------------------------------------------------------------
void TextEntry::CompositionString( const wchar_t *compstr )
{
	wcsncpy( m_szComposition, compstr, sizeof( m_szComposition ) / sizeof( wchar_t ) - 1 );
	m_szComposition[  sizeof( m_szComposition ) / sizeof( wchar_t ) - 1 ] = L'\0';
}

void TextEntry::ShowIMECandidates()
{
	HideIMECandidates();

	int c = input()->GetCandidateListCount();
	if ( c == 0 )
	{
		return;
	}

	m_pIMECandidates = new Menu( this, "IMECandidatesMenu" );
	
	int pageStart = input()->GetCandidateListPageStart();
	int pageSize = input()->GetCandidateListPageSize();
	int selected = input()->GetCandidateListSelectedItem();

	int startAtOne = input()->CandidateListStartsAtOne() ? 1 : 0;

	if ( ( selected < pageStart ) || ( selected >= pageStart + pageSize ) )
	{
		pageStart = ( selected / pageSize ) * pageSize;
		input()->SetCandidateListPageStart( pageStart );
	}

	for ( int i = pageStart; i < pageStart + pageSize; ++i )
	{
		if ( i >= c )
			continue;

		bool isSelected = ( i == selected ) ? true : false;

		wchar_t unicode[ 32 ];
		input()->GetCandidate( i, unicode, sizeof( unicode ) );

		wchar_t label[ 64 ];
		_snwprintf( label, sizeof( label ) / sizeof( wchar_t ) - 1, L"%i %s", i - pageStart + startAtOne, unicode );
		label[ sizeof( label ) / sizeof( wchar_t ) - 1 ] = L'\0';

		int id = m_pIMECandidates->AddMenuItem( "Candidate", label, (KeyValues *)NULL, this );
		if ( isSelected )
		{
			m_pIMECandidates->SetCurrentlyHighlightedItem( id );
		}
	}
	
	m_pIMECandidates->SetVisible(true);
	m_pIMECandidates->SetParent(this);
	m_pIMECandidates->AddActionSignalTarget(this);
	m_pIMECandidates->SetKeyBoardInputEnabled( false );

	int cx, cy;
	CursorToPixelSpace(_cursorPos, cx, cy);
	cy = GetTall();

	LocalToScreen( cx, cy );

	//m_pIMECandidates->SetPos( cx, cy );

	// relayout the menu immediately so that we know it's size
	m_pIMECandidates->InvalidateLayout(true);
	int menuWide, menuTall;
	m_pIMECandidates->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 > cx)
	{
		// menu hanging right
		if (tall - menuTall > cy)
		{
			// menu hanging down
			m_pIMECandidates->SetPos(cx, cy);
		}
		else
		{
			// menu hanging up
			m_pIMECandidates->SetPos(cx, cy - menuTall - GetTall());
		}
	}
	else
	{
		// menu hanging left
		if (tall - menuTall > cy)
		{
			// menu hanging down
			m_pIMECandidates->SetPos(cx - menuWide, cy);
		}
		else
		{
			// menu hanging up
			m_pIMECandidates->SetPos(cx - menuWide, cy - menuTall-GetTall());
		}
	}
}

void TextEntry::HideIMECandidates()
{
	if ( m_pIMECandidates )
	{
		m_pIMECandidates->SetVisible( false );
	}
	delete m_pIMECandidates;
	m_pIMECandidates = NULL;
}

void TextEntry::UpdateIMECandidates()
{
	if ( !m_pIMECandidates )
		return;

	int c = input()->GetCandidateListCount();
	if ( c == 0 )
	{
		HideIMECandidates();
		return;
	}

	int oldCount = m_pIMECandidates->GetItemCount();
	int newCount = input()->GetCandidateListPageSize();

	if ( oldCount != newCount )
	{
		// Recreate the entire menu
		ShowIMECandidates();
		return;
	}

	int pageSize = input()->GetCandidateListPageSize();
	int selected = input()->GetCandidateListSelectedItem();
	int pageStart = input()->GetCandidateListPageStart();

	if ( ( selected < pageStart ) || selected >= pageStart + pageSize )
	{
		pageStart = ( selected / pageSize ) * pageSize;
		input()->SetCandidateListPageStart( pageStart );
	}

	int startAtOne = input()->CandidateListStartsAtOne() ? 1 : 0;

	for ( int i = pageStart; i < pageStart + pageSize; ++i )
	{
		int id = m_pIMECandidates->GetMenuID( i - pageStart );

		MenuItem *item = m_pIMECandidates->GetMenuItem( id );
		if ( !item )
			continue;

		if ( i >= c )
		{
			item->SetVisible( false );
			continue;
		}
		else
		{
			item->SetVisible( true );
		}

		bool isSelected = ( i == selected ) ? true : false;

		wchar_t unicode[ 32 ];
		input()->GetCandidate( i, unicode, sizeof( unicode ) );

		wchar_t label[ 64 ];
		_snwprintf( label, sizeof( label ) / sizeof( wchar_t ) - 1, L"%i %s", i - pageStart + startAtOne, unicode );
		label[ sizeof( label ) / sizeof( wchar_t ) - 1 ] = L'\0';
		item->SetText( label );
		if ( isSelected )
		{
			m_pIMECandidates->SetCurrentlyHighlightedItem( id );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void TextEntry::FlipToLastIME()
{
	int hCurrentIME = input()->GetCurrentIMEHandle();
	int hEnglishIME = input()->GetEnglishIMEHandle();

	bool isEnglish = ( hCurrentIME == hEnglishIME ) ? true : false;

	// If in english, flip back to previous
	if ( isEnglish )
	{
		input()->OnChangeIMEByHandle( m_hPreviousIME );
	}
	else
	{
		// If not, remember language and flip to english...
		m_hPreviousIME = hCurrentIME;
		input()->OnChangeIMEByHandle( hEnglishIME );
	}
}

void TextEntry::SetDrawLanguageIDAtLeft( bool state )
{
	m_bDrawLanguageIDAtLeft = state;
}

bool TextEntry::GetDropContextMenu( Menu *menu, CUtlVector< KeyValues * >& msglist )
{
	menu->AddMenuItem( "replace", "#TextEntry_ReplaceText", "replace", this );
	menu->AddMenuItem( "append", "#TextEntry_AppendText", "append", this );
	menu->AddMenuItem( "prepend", "#TextEntry_PrependText", "prepend", this );
	return true;
}

bool TextEntry::IsDroppable( CUtlVector< KeyValues * >& msglist )
{
	if ( msglist.Count() != 1 )
		return false;

	if ( !IsEnabled() )
		return false;

	KeyValues *msg = msglist[ 0 ];

	const wchar_t *txt = msg->GetWString( "text", L"" );
	if ( !txt || txt[ 0 ] == L'\0' )
		return false;

	return true;
}

void TextEntry::OnPanelDropped( CUtlVector< KeyValues * >& msglist )
{
	if ( msglist.Count() != 1 )
		return;

	KeyValues *data = msglist[ 0 ];

	const wchar_t *newText = data->GetWString( "text" );
	if ( !newText || newText[ 0 ] == L'\0' )
		return;

	char const *cmd = data->GetString( "command" );
	if ( !Q_stricmp( cmd, "replace" ) ||
		 !Q_stricmp( cmd, "default" ) )
	{
		SetText( newText );
		_dataChanged = true;
		FireActionSignal();
	}
	else if ( !Q_stricmp( cmd, "append" ) )
	{
		int newLen = wcslen( newText );
		int curLen = m_TextStream.Count();

		size_t outsize = sizeof( wchar_t ) * ( newLen + curLen + 1 );
		wchar_t *out = (wchar_t *)_alloca( outsize );
		Q_memset( out, 0, outsize );
		wcsncpy( out, m_TextStream.Base(), curLen );
		wcsncat( out, newText, wcslen( newText ) );
		out[ newLen + curLen ] = L'\0';
		SetText( out );
		_dataChanged = true;
		FireActionSignal();
	}
	else if ( !Q_stricmp( cmd, "prepend" ) )
	{
		int newLen = wcslen( newText );
		int curLen = m_TextStream.Count();

		size_t outsize = sizeof( wchar_t ) * ( newLen + curLen + 1 );
		wchar_t *out = (wchar_t *)_alloca( outsize );
		Q_memset( out, 0, outsize );
		wcsncpy( out, newText, wcslen( newText ) );
		wcsncat( out, m_TextStream.Base(), curLen );
		out[ newLen + curLen ] = L'\0';
		SetText( out );
		_dataChanged = true;
		FireActionSignal();
	}
}

int TextEntry::GetTextLength() const
{
	return m_TextStream.Count();
}

bool TextEntry::IsTextFullySelected() const 
{
	if ( _select[ 0 ] != 0 )
		return false;

	if ( _select[ 1 ] != GetTextLength() )
		return false;

	return true;
}

void TextEntry::SetUseFallbackFont( bool bState, HFont hFallback )
{
	m_bUseFallbackFont = bState;
	m_hFallbackFont = hFallback;
}
