//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include #include #ifdef _PS3 #include //!!BUG!! "wcsicmp is unsupported on PS3" #ifdef wcsicmp #undef wcsicmp #endif #define wcsicmp wcscmp #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // memdbgon must be the last include file in a .cpp file!!! #include enum { // maximum size of text buffer BUFFER_SIZE=999999, }; using namespace vgui; static const int DRAW_OFFSET_X = 3,DRAW_OFFSET_Y = 1; DECLARE_BUILD_FACTORY( TextEntry ); //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- TextEntry::TextEntry(Panel *parent, const char *panelName) : BaseClass(parent, panelName) { SetTriplePressAllowed( true ); _font = INVALID_FONT; _smallfont = INVALID_FONT; m_szComposition[ 0 ] = L'\0'; m_bAllowNumericInputOnly = false; m_bAllowNonAsciiCharacters = false; _hideText = false; _editable = false; _verticalScrollbar = false; _cursorPos = 0; _currentStartIndex = 0; _horizScrollingAllowed = true; _cursorIsAtEnd = false; _putCursorAtEnd = false; _multiline = false; _cursorBlinkRate = 400; _mouseSelection = false; _mouseDragSelection = false; _vertScrollBar=NULL; _catchEnterKey = false; _catchTabKey = 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; _tabSpaces = 4; m_bAutoProgressOnHittingCharLimit = false; m_pIMECandidates = NULL; m_hPreviousIME = input()->GetEnglishIMEHandle(); m_bDrawLanguageIDAtLeft = false; m_nLangInset = 0; m_bUseFallbackFont = false; m_hFallbackFont = INVALID_FONT; m_bAutoLocalize = true; //a -1 for _select[0] means that the selection is empty _select[0] = -1; _select[1] = -1; m_pEditMenu = NULL; //this really just inits it when in here ResetCursorBlink(); SetCursor(dc_ibeam); SetEditable(true); // initialize the line break array m_LineBreaks.AddToTail(BUFFER_SIZE); _recalculateBreaksIndex = 0; _selectAllOnFirstFocus = false; _selectAllOnFocusAlways = false; //position the cursor so it is at the end of the text GotoTextEnd(); // If keyboard focus is in an edit control, don't chain keyboard mappings up to parents since it could mess with typing in text. SetAllowKeyBindingChainToParent( false ); REGISTER_COLOR_AS_OVERRIDABLE( _disabledFgColor, "disabledFgColor_override" ); REGISTER_COLOR_AS_OVERRIDABLE( _disabledBgColor, "disabledBgColor_override" ); REGISTER_COLOR_AS_OVERRIDABLE( _selectionColor, "selectionColor_override" ); REGISTER_COLOR_AS_OVERRIDABLE( _selectionTextColor, "selectionTextColor_override" ); REGISTER_COLOR_AS_OVERRIDABLE( _defaultSelectionBG2Color, "defaultSelectionBG2Color_override" ); } TextEntry::~TextEntry() { delete m_pEditMenu; delete m_pIMECandidates; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TextEntry::ApplySchemeSettings(IScheme *pScheme) { BaseClass::ApplySchemeSettings(pScheme); SetFgColor(GetSchemeColor("TextEntry.TextColor", pScheme)); SetBgColor(GetSchemeColor("TextEntry.BgColor", pScheme)); _cursorColor = GetSchemeColor("TextEntry.CursorColor", pScheme); _disabledFgColor = GetSchemeColor("TextEntry.DisabledTextColor", pScheme); _disabledBgColor = GetSchemeColor("TextEntry.DisabledBgColor", pScheme); _selectionTextColor = GetSchemeColor("TextEntry.SelectedTextColor", GetFgColor(), pScheme); _selectionColor = GetSchemeColor("TextEntry.SelectedBgColor", pScheme); _defaultSelectionBG2Color = GetSchemeColor("TextEntry.OutOfFocusSelectedBgColor", pScheme); _focusEdgeColor = GetSchemeColor("TextEntry.FocusEdgeColor", Color(0, 0, 0, 0), pScheme); SetBorder( pScheme->GetBorder("ButtonDepressedBorder")); if ( _font == INVALID_FONT ) _font = pScheme->GetFont("Default", IsProportional() ); if ( _smallfont == INVALID_FONT ) _smallfont = pScheme->GetFont( "DefaultVerySmall", IsProportional() ); SetFont( _font ); } void TextEntry::SetSelectionTextColor( const Color& clr ) { _selectionTextColor = clr; } void TextEntry::SetSelectionBgColor( const Color& clr ) { _selectionColor = clr; } void TextEntry::SetSelectionUnfocusedBgColor( const Color& clr ) { _defaultSelectionBG2Color = clr; } //----------------------------------------------------------------------------- // Purpose: sets the color of the background when the control is disabled //----------------------------------------------------------------------------- void TextEntry::SetDisabledBgColor(Color col) { _disabledBgColor = col; } //----------------------------------------------------------------------------- // Purpose: Sends a message if the data has changed // Turns off any selected text in the window if we are not using the edit menu //----------------------------------------------------------------------------- void TextEntry::OnKillFocus() { m_szComposition[ 0 ] = L'\0'; HideIMECandidates(); if (_dataChanged) { FireActionSignal(); _dataChanged = false; } // check if we clicked the right mouse button or if it is down bool mouseRightClicked = input()->WasMousePressed(MOUSE_RIGHT); bool mouseRightUp = input()->WasMouseReleased(MOUSE_RIGHT); bool mouseRightDown = input()->IsMouseDown(MOUSE_RIGHT); if (mouseRightClicked || mouseRightDown || mouseRightUp ) { int cursorX, cursorY; input()->GetCursorPos(cursorX, cursorY); // if we're right clicking within our window, we don't actually kill focus if (IsWithin(cursorX, cursorY)) return; } // clear any selection SelectNone(); // move the cursor to the start // GotoTextStart(); PostActionSignal( new KeyValues( "TextKillFocus" ) ); // chain BaseClass::OnKillFocus(); } //----------------------------------------------------------------------------- // Purpose: Wipe line breaks after the size of a panel has been changed //----------------------------------------------------------------------------- void TextEntry::OnSizeChanged(int newWide, int newTall) { BaseClass::OnSizeChanged(newWide, newTall); // blow away the line breaks list _recalculateBreaksIndex = 0; m_LineBreaks.RemoveAll(); m_LineBreaks.AddToTail(BUFFER_SIZE); // if we're bigger, see if we can scroll left to put more text in the window if (newWide > _drawWidth) { ScrollLeftForResize(); } _drawWidth = newWide; InvalidateLayout(); } //----------------------------------------------------------------------------- // Purpose: Set the text array - convert ANSI text to unicode and pass to unicode function //----------------------------------------------------------------------------- void TextEntry::SetText(const char *text) { if (!text) { text = ""; } if (text[0] == '#' && m_bAutoLocalize) { // 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, except if it is the // first character because in that case the break has not been accounted for. if ( (m_TextStream[startChar] == '\r' || m_TextStream[startChar] == '\n') && ( startChar > 0 ) ) { 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: set the number of spaces inserted for a tab key press when catch // tab is enabled. //----------------------------------------------------------------------------- void TextEntry::SetTabSpaces(int count) { _tabSpaces = count; } //----------------------------------------------------------------------------- // Purpose: sets whether or not the edit catches and stores TAB key presses //----------------------------------------------------------------------------- void TextEntry::SetCatchTabKey(bool state) { _catchTabKey = state; } //----------------------------------------------------------------------------- // Purpose: Sets whether a vertical scrollbar is visible // Input : state - true or false //----------------------------------------------------------------------------- void TextEntry::SetVerticalScrollbar(bool state) { _verticalScrollbar = state; if (_verticalScrollbar) { if (!_vertScrollBar) { _vertScrollBar = new ScrollBar(this, "ScrollBar", true); _vertScrollBar->AddActionSignalTarget(this); } _vertScrollBar->SetVisible(true); } else if (_vertScrollBar) { _vertScrollBar->SetVisible(false); } InvalidateLayout(); } //----------------------------------------------------------------------------- // Purpose: sets _editable flag // Input : state - true or false //----------------------------------------------------------------------------- void TextEntry::SetEditable(bool state) { if ( state ) { SetDropEnabled( true, 1.0f ); } else { SetDropEnabled( false ); } _editable = state; } const wchar_t *UnlocalizeUnicode( wchar_t *unicode ) { if ( !unicode ) return L""; if ( *unicode == L'#' ) { char lookup[ 512 ]; g_pVGuiLocalize->ConvertUnicodeToANSI( unicode + 1, lookup, sizeof( lookup ) ); return g_pVGuiLocalize->Find( lookup ); } return unicode; } Menu * TextEntry::GetEditMenu() { return m_pEditMenu; } //----------------------------------------------------------------------------- // Purpose: Create cut/copy/paste dropdown menu //----------------------------------------------------------------------------- void TextEntry::CreateEditMenu() { // create a drop down cut/copy/paste menu appropriate for this object's states if (m_pEditMenu) delete m_pEditMenu; m_pEditMenu = new Menu(this, "EditMenu"); m_pEditMenu->SetFont( _font ); // add cut/copy/paste drop down options if its editable, just copy if it is not if (_editable && !_hideText) { m_pEditMenu->AddMenuItem("#TextEntry_Cut", new KeyValues("DoCutSelected"), this); } if ( !_hideText ) { m_pEditMenu->AddMenuItem("#TextEntry_Copy", new KeyValues("DoCopySelected"), this); } if (_editable) { m_pEditMenu->AddMenuItem("#TextEntry_Paste", new KeyValues("DoPaste"), this); } if ( m_bAllowNonAsciiCharacters ) { IInput::LanguageItem *langs = NULL; int count = input()->GetIMELanguageList( NULL, 0 ); if ( count > 0 ) { langs = new IInput::LanguageItem[ count ]; input()->GetIMELanguageList( langs, count ); // Create a submenu Menu *subMenu = new Menu( this, "LanguageMenu" ); subMenu->SetFont( _font ); for ( int i = 0; i < count; ++i ) { int id = subMenu->AddCheckableMenuItem( "Language", UnlocalizeUnicode( langs[ i ].menuname ), new KeyValues( "DoLanguageChanged", "handle", langs[ i ].handleValue ), this ); if ( langs[ i ].active ) { subMenu->SetMenuItemChecked( id, true ); } } m_pEditMenu->AddCascadingMenuItem( "Language", "#TextEntry_Language", "", this, subMenu ); delete[] langs; } IInput::ConversionModeItem *modes = NULL; count = input()->GetIMEConversionModes( NULL, 0 ); // if count == 0 then native mode is the only mode... if ( count > 0 ) { modes = new IInput::ConversionModeItem[ count ]; input()->GetIMEConversionModes( modes, count ); // Create a submenu Menu *subMenu = new Menu( this, "ConversionModeMenu" ); subMenu->SetFont( _font ); for ( int i = 0; i < count; ++i ) { int id = subMenu->AddCheckableMenuItem( "ConversionMode", UnlocalizeUnicode( modes[ i ].menuname ), new KeyValues( "DoConversionModeChanged", "handle", modes[ i ].handleValue ), this ); if ( modes[ i ].active ) { subMenu->SetMenuItemChecked( id, true ); } } m_pEditMenu->AddCascadingMenuItem( "ConversionMode", "#TextEntry_ConversionMode", "", this, subMenu ); delete[] modes; } IInput::SentenceModeItem *sentencemodes = NULL; count = input()->GetIMESentenceModes( NULL, 0 ); // if count == 0 then native mode is the only mode... if ( count > 0 ) { sentencemodes = new IInput::SentenceModeItem[ count ]; input()->GetIMESentenceModes( sentencemodes, count ); // Create a submenu Menu *subMenu = new Menu( this, "SentenceModeMenu" ); subMenu->SetFont( _font ); for ( int i = 0; i < count; ++i ) { int id = subMenu->AddCheckableMenuItem( "SentenceMode", UnlocalizeUnicode( sentencemodes[ i ].menuname ), new KeyValues( "DoConversionModeChanged", "handle", modes[ i ].handleValue ), this ); if ( modes[ i ].active ) { subMenu->SetMenuItemChecked( id, true ); } } m_pEditMenu->AddCascadingMenuItem( "SentenceMode", "#TextEntry_SentenceMode", "", this, subMenu ); delete[] sentencemodes; } } m_pEditMenu->SetVisible(false); m_pEditMenu->SetParent(this); m_pEditMenu->AddActionSignalTarget(this); } //----------------------------------------------------------------------------- // Purpsoe: Returns state of _editable flag //----------------------------------------------------------------------------- bool TextEntry::IsEditable() { return _editable && IsEnabled(); } //----------------------------------------------------------------------------- // Purpose: We want single line windows to scroll horizontally and select text // in response to clicking and holding outside window //----------------------------------------------------------------------------- void TextEntry::OnMouseFocusTicked() { // if a button is down move the scrollbar slider the appropriate direction if (_mouseDragSelection) // text is being selected via mouse clicking and dragging { OnCursorMoved(0,0); // we want the text to scroll as if we were dragging } } //----------------------------------------------------------------------------- // Purpose: If a cursor enters the window, we are not elegible for // MouseFocusTicked events //----------------------------------------------------------------------------- void TextEntry::OnCursorEntered() { _mouseDragSelection = false; // outside of window dont recieve drag scrolling ticks } //----------------------------------------------------------------------------- // Purpose: When the cursor is outside the window, if we are holding the mouse // button down, then we want the window to scroll the text one char at a time // using Ticks //----------------------------------------------------------------------------- void TextEntry::OnCursorExited() // outside of window recieve drag scrolling ticks { if (_mouseSelection) _mouseDragSelection = true; } //----------------------------------------------------------------------------- // Purpose: Handle selection of text by mouse //----------------------------------------------------------------------------- void TextEntry::OnCursorMoved(int 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 winkey = (input()->IsKeyDown(KEY_LWIN) || input()->IsKeyDown(KEY_RWIN)); bool fallThrough = false; REFERENCE( winkey ); if ( ( ctrl || ( IsOSX() && winkey ) ) && !alt) { switch(code) { case KEY_A: SelectAllText(false); // move the cursor to the end _cursorPos = _select[1]; break; case KEY_INSERT: case KEY_C: { CopySelected(); break; } case KEY_V: { DeleteSelected(); Paste(); break; } case KEY_X: { CopySelected(); DeleteSelected(); break; } case KEY_Z: { Undo(); break; } case KEY_RIGHT: { GotoWordRight(); break; } case KEY_LEFT: { GotoWordLeft(); break; } case KEY_ENTER: { // insert a newline if (_multiline) { DeleteSelected(); SaveUndoState(); InsertChar('\n'); } // fire newlines back to the main target if asked to if(_sendNewLines) { PostActionSignal(new KeyValues("TextNewLine")); } break; } case KEY_HOME: { GotoTextStart(); break; } case KEY_END: { GotoTextEnd(); break; } case KEY_PAGEUP: { OnChangeIME( true ); } break; case KEY_PAGEDOWN: { OnChangeIME( false ); } break; case KEY_UP: case KEY_DOWN: if ( m_bAllowNonAsciiCharacters ) { FlipToLastIME(); } else { fallThrough = true; } break; default: { fallThrough = true; break; } } } else if (alt) { // do nothing with ALT-x keys if ( !m_bAllowNonAsciiCharacters || ( code != KEY_BACKQUOTE ) ) { fallThrough = true; } } else { switch(code) { case KEY_TAB: if ( _catchTabKey ) { DeleteSelected(); SaveUndoState(); for ( int i = 0; i < _tabSpaces; ++i ) { InsertChar(' '); } } else { fallThrough = true; } break; 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 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; for ( int i = 0; i < m_LineBreaks.Count(); ++i ) { if (_cursorPos <= m_LineBreaks[i]) { if (i == 0) return 0; else { return m_LineBreaks[i-1] + 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() { for ( int 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) { // Allow digits, decimal places, and a single leading minus sign // A minus sign is allowed if the cursor is at the 0th position AND there is not already a minus sign there. bool bMinusSignAllowed = ( _cursorPos == 0 ) && ( ( m_TextStream.Count() > 0 && m_TextStream[0] != '-' ) || m_TextStream.Count() == 0 ); if (!iswdigit(ch) && ((char)ch != '.') && !( bMinusSignAllowed && 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] == '#' && m_bAutoLocalize) { 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= 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(cx1GetCursorPos(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 buf; for (int i = x0; i < x1; i++) { if ( m_TextStream[i]=='\n') { if ( buf.Count() == 0 ) { // Don't put an end line at the beginning // It makes it really difficult to copy paste from the console into // single line dialogs continue; } 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 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* )stackalloc( nTextLength * sizeof( char ) ); GetText( txt, nTextLength ); return V_atof( txt ); } int TextEntry::GetValueAsInt() { int nTextLength = GetTextLength() + 1; char* txt = ( char* )stackalloc( 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")) { char buf[64]; GetText(buf, sizeof(buf)); outputData->SetInt("state", atoi(buf)); return true; } return BaseClass::RequestInfo(outputData); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TextEntry::OnSetText(const wchar_t *text) { SetText(text); } //----------------------------------------------------------------------------- // Purpose: as above, but sets an integer //----------------------------------------------------------------------------- void TextEntry::OnSetState(int state) { char buf[64]; Q_snprintf(buf, sizeof(buf), "%d", state); SetText(buf); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TextEntry::ApplySettings( KeyValues *inResourceData ) { BaseClass::ApplySettings( inResourceData ); // _font = scheme()->GetFont(GetScheme(), "Default", IsProportional() ); // SetFont( _font ); SetTextHidden(inResourceData->GetBool("textHidden", false)); SetEditable(inResourceData->GetBool("editable", true)); SetMaximumCharCount(inResourceData->GetInt("maxchars", -1)); SetAllowNumericInputOnly(inResourceData->GetBool("NumericInputOnly", false)); SetAllowNonAsciiCharacters(inResourceData->GetBool("unicode", false)); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void TextEntry::GetSettings( KeyValues *outResourceData ) { BaseClass::GetSettings( outResourceData ); outResourceData->SetBool("textHidden", _hideText); outResourceData->SetBool("editable", IsEditable()); outResourceData->SetInt("maxchars", GetMaximumCharCount()); outResourceData->SetBool("NumericInputOnly", m_bAllowNumericInputOnly); outResourceData->SetBool("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: Get the current starting line //----------------------------------------------------------------------------- int TextEntry::GetCurrentStartLine() const { if ( _multiline ) { return _currentStartLine; } else { return 0; } } //----------------------------------------------------------------------------- // 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 ]; #if defined( PLATFORM_WINDOWS) || defined( _GAMECONSOLE ) V_snwprintf( label, sizeof( label ) / sizeof( wchar_t ) - 1, L"%i %s", i - pageStart + startAtOne, unicode ); #else V_snwprintf( label, sizeof( label ) / sizeof( wchar_t ) - 1, L"%i %S", i - pageStart + startAtOne, unicode ); #endif 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 ]; #if defined( PLATFORM_WINDOWS ) || defined( _GAMECONSOLE ) V_snwprintf( label, sizeof( label ) / sizeof( wchar_t ) - 1, L"%i %s", i - pageStart + startAtOne, unicode ); #else V_snwprintf( label, sizeof( label ) / sizeof( wchar_t ) - 1, L"%i %S", i - pageStart + startAtOne, unicode ); #endif 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 *)stackalloc( 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 *)stackalloc( 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; } void TextEntry::GetSizerMinimumSize(int &wide, int &tall) { tall = 5; wide = 5; }