You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4279 lines
107 KiB
4279 lines
107 KiB
//========= 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> |
|
|
|
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); |
|
|
|
// 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; |
|
} |
|
|
|
// 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; |
|
}
|
|
|