mirror of
https://github.com/alliedmodders/hl2sdk.git
synced 2024-12-23 01:59:43 +08:00
4214 lines
109 KiB
C++
4214 lines
109 KiB
C++
//========= Copyright © 1996-2005, 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) : Panel(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 );
|
|
}
|
|
|
|
|
|
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"));
|
|
|
|
_font = pScheme->GetFont("Default", IsProportional() );
|
|
_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()
|
|
{
|
|
// draw background
|
|
Color col;
|
|
if (IsEnabled())
|
|
{
|
|
col = GetBgColor();
|
|
}
|
|
else
|
|
{
|
|
col = _disabledBgColor;
|
|
}
|
|
Color saveBgColor = col;
|
|
|
|
surface()->DrawSetColor(col);
|
|
int wide, tall;
|
|
GetSize( wide, tall );
|
|
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");
|
|
|
|
|
|
// 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" );
|
|
|
|
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" );
|
|
|
|
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" );
|
|
|
|
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 x, int y)
|
|
{
|
|
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: 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 fallThrough = false;
|
|
|
|
if (ctrl)
|
|
{
|
|
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(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(), x1 - x0);
|
|
}
|
|
|
|
// 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 (_hideText)
|
|
return;
|
|
|
|
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
|
|
// bufLen - length of string
|
|
//-----------------------------------------------------------------------------
|
|
void TextEntry::GetText(char *buf, int bufLen)
|
|
{
|
|
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, bufLen);
|
|
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(wchar_t *wbuf, int bufLenInBytes)
|
|
{
|
|
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"))
|
|
{
|
|
wchar_t wbuf[64];
|
|
GetText(wbuf, sizeof(wbuf));
|
|
outputData->SetInt("state", _wtoi(wbuf));
|
|
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()->GetFont(GetScheme(), "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));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|