summaryrefslogtreecommitdiff
path: root/indra/llui/lllineeditor.cpp
diff options
context:
space:
mode:
authorJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
committerJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
commit420b91db29485df39fd6e724e782c449158811cb (patch)
treeb471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llui/lllineeditor.cpp
Print done when done.
Diffstat (limited to 'indra/llui/lllineeditor.cpp')
-rw-r--r--indra/llui/lllineeditor.cpp2301
1 files changed, 2301 insertions, 0 deletions
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
new file mode 100644
index 0000000000..41049fdf1f
--- /dev/null
+++ b/indra/llui/lllineeditor.cpp
@@ -0,0 +1,2301 @@
+/**
+ * @file lllineeditor.cpp
+ * @brief LLLineEditor base class
+ *
+ * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
+ * $License$
+ */
+
+// Text editor widget to let users enter a single line.
+
+#include "linden_common.h"
+
+#include "lllineeditor.h"
+
+#include "audioengine.h"
+#include "llmath.h"
+#include "llfontgl.h"
+#include "llgl.h"
+#include "sound_ids.h"
+#include "lltimer.h"
+
+//#include "llclipboard.h"
+#include "llcontrol.h"
+#include "llbutton.h"
+#include "llfocusmgr.h"
+#include "llkeyboard.h"
+#include "llrect.h"
+#include "llresmgr.h"
+#include "llstring.h"
+#include "llwindow.h"
+#include "llui.h"
+#include "lluictrlfactory.h"
+#include "llclipboard.h"
+
+//
+// Imported globals
+//
+
+//
+// Constants
+//
+
+const S32 UI_LINEEDITOR_CURSOR_THICKNESS = 2;
+const S32 UI_LINEEDITOR_H_PAD = 2;
+const S32 UI_LINEEDITOR_V_PAD = 1;
+const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds
+const S32 SCROLL_INCREMENT_ADD = 0; // make space for typing
+const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing
+const F32 AUTO_SCROLL_TIME = 0.05f;
+const F32 LABEL_HPAD = 5.f;
+
+// This is a friend class of and is only used by LLLineEditor
+class LLLineEditorRollback
+{
+public:
+ LLLineEditorRollback( LLLineEditor* ed )
+ :
+ mCursorPos( ed->mCursorPos ),
+ mScrollHPos( ed->mScrollHPos ),
+ mIsSelecting( ed->mIsSelecting ),
+ mSelectionStart( ed->mSelectionStart ),
+ mSelectionEnd( ed->mSelectionEnd )
+ {
+ mText = ed->getText();
+ }
+
+ void doRollback( LLLineEditor* ed )
+ {
+ ed->mCursorPos = mCursorPos;
+ ed->mScrollHPos = mScrollHPos;
+ ed->mIsSelecting = mIsSelecting;
+ ed->mSelectionStart = mSelectionStart;
+ ed->mSelectionEnd = mSelectionEnd;
+ ed->mText = mText;
+ }
+
+ LLString getText() { return mText; }
+
+private:
+ LLString mText;
+ S32 mCursorPos;
+ S32 mScrollHPos;
+ BOOL mIsSelecting;
+ S32 mSelectionStart;
+ S32 mSelectionEnd;
+};
+
+
+//
+// Member functions
+//
+
+LLLineEditor::LLLineEditor(const LLString& name, const LLRect& rect,
+ const LLString& default_text, const LLFontGL* font,
+ S32 max_length_bytes,
+ void (*commit_callback)(LLUICtrl* caller, void* user_data ),
+ void (*keystroke_callback)(LLLineEditor* caller, void* user_data ),
+ void (*focus_lost_callback)(LLLineEditor* caller, void* user_data ),
+ void* userdata,
+ LLLinePrevalidateFunc prevalidate_func,
+ LLViewBorder::EBevel border_bevel,
+ LLViewBorder::EStyle border_style,
+ S32 border_thickness)
+ :
+ LLUICtrl( name, rect, TRUE, commit_callback, userdata, FOLLOWS_TOP | FOLLOWS_LEFT ),
+ mMaxLengthChars(max_length_bytes),
+ mMaxLengthBytes(max_length_bytes),
+ mCursorPos( 0 ),
+ mScrollHPos( 0 ),
+ mBorderLeft(0),
+ mBorderRight(0),
+ mCommitOnFocusLost( TRUE ),
+ mKeystrokeCallback( keystroke_callback ),
+ mFocusLostCallback( focus_lost_callback ),
+ mIsSelecting( FALSE ),
+ mSelectionStart( 0 ),
+ mSelectionEnd( 0 ),
+ mLastSelectionX(-1),
+ mLastSelectionY(-1),
+ mPrevalidateFunc( prevalidate_func ),
+ mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ),
+ mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ),
+ mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ),
+ mTentativeFgColor( LLUI::sColorsGroup->getColor( "TextFgTentativeColor" ) ),
+ mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ),
+ mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ),
+ mFocusBgColor( LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ),
+ mBorderThickness( border_thickness ),
+ mIgnoreArrowKeys( FALSE ),
+ mIgnoreTab( TRUE ),
+ mDrawAsterixes( FALSE ),
+ mHandleEditKeysDirectly( FALSE ),
+ mSelectAllonFocusReceived( FALSE ),
+ mPassDelete(FALSE),
+ mReadOnly(FALSE)
+{
+ llassert( max_length_bytes > 0 );
+
+ if (font)
+ {
+ mGLFont = font;
+ }
+ else
+ {
+ mGLFont = LLFontGL::sSansSerifSmall;
+ }
+
+ mMinHPixels = mBorderThickness + UI_LINEEDITOR_H_PAD + mBorderLeft;
+ mMaxHPixels = mRect.getWidth() - mMinHPixels - mBorderThickness - mBorderRight;
+
+ mScrollTimer.reset();
+
+ setText(default_text);
+
+ setCursor(mText.length());
+
+ // Scalable UI somehow made these rectangles off-by-one.
+ // I don't know why. JC
+ LLRect border_rect(0, mRect.getHeight()-1, mRect.getWidth()-1, 0);
+ mBorder = new LLViewBorder( "line ed border", border_rect, border_bevel, border_style, mBorderThickness );
+ addChild( mBorder );
+ mBorder->setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP|FOLLOWS_BOTTOM);
+}
+
+
+LLLineEditor::~LLLineEditor()
+{
+ mFocusLostCallback = NULL;
+ mCommitOnFocusLost = FALSE;
+
+ gFocusMgr.releaseFocusIfNeeded( this );
+
+ if( gEditMenuHandler == this )
+ {
+ gEditMenuHandler = NULL;
+ }
+}
+
+//virtual
+EWidgetType LLLineEditor::getWidgetType() const
+{
+ return WIDGET_TYPE_LINE_EDITOR;
+}
+
+//virtual
+LLString LLLineEditor::getWidgetTag() const
+{
+ return LL_LINE_EDITOR_TAG;
+}
+
+void LLLineEditor::onFocusLost()
+{
+ if( mFocusLostCallback )
+ {
+ mFocusLostCallback( this, mCallbackUserData );
+ }
+
+ if( mCommitOnFocusLost )
+ {
+ onCommit();
+ }
+
+ if( gEditMenuHandler == this )
+ {
+ gEditMenuHandler = NULL;
+ }
+
+ getWindow()->showCursorFromMouseMove();
+}
+
+void LLLineEditor::onCommit()
+{
+ LLUICtrl::onCommit();
+ selectAll();
+}
+
+void LLLineEditor::reshape(S32 width, S32 height, BOOL called_from_parent)
+{
+ LLUICtrl::reshape(width, height, called_from_parent );
+
+ mMaxHPixels = mRect.getWidth() - 2 * (mBorderThickness + UI_LINEEDITOR_H_PAD) + 1 - mBorderRight;
+}
+
+
+void LLLineEditor::setEnabled(BOOL enabled)
+{
+ mReadOnly = !enabled;
+ setTabStop(!mReadOnly);
+}
+
+
+void LLLineEditor::setMaxTextLength(S32 max_text_length)
+{
+ S32 max_len = llmax(0, max_text_length);
+ mMaxLengthBytes = max_len;
+ mMaxLengthChars = max_len;
+}
+
+void LLLineEditor::setBorderWidth(S32 left, S32 right)
+{
+ mBorderLeft = llclamp(left, 0, mRect.getWidth());
+ mBorderRight = llclamp(right, 0, mRect.getWidth());
+ mMinHPixels = mBorderThickness + UI_LINEEDITOR_H_PAD + mBorderLeft;
+ mMaxHPixels = mRect.getWidth() - mMinHPixels - mBorderThickness - mBorderRight;
+}
+
+void LLLineEditor::setLabel(const LLString &new_label)
+{
+ mLabel = new_label;
+}
+
+void LLLineEditor::setText(const LLString &new_text)
+{
+ // If new text is identical, don't copy and don't move insertion point
+ if (mText.getString() == new_text)
+ {
+ return;
+ }
+
+ // Check to see if entire field is selected.
+ S32 len = mText.length();
+ BOOL allSelected = (len > 0) && (( mSelectionStart == 0 && mSelectionEnd == len )
+ || ( mSelectionStart == len && mSelectionEnd == 0 ));
+
+ LLString truncated_utf8 = new_text;
+ if (truncated_utf8.size() > (U32)mMaxLengthBytes)
+ {
+ utf8str_truncate(truncated_utf8, mMaxLengthBytes);
+ }
+ mText.assign(truncated_utf8);
+ mText.truncate(mMaxLengthChars);
+
+ if (allSelected)
+ {
+ // ...keep whole thing selected
+ selectAll();
+ }
+ else
+ {
+ // try to preserve insertion point, but deselect text
+ deselect();
+ }
+ setCursor(llmin((S32)mText.length(), getCursor()));
+}
+
+
+// Picks a new cursor position based on the actual screen size of text being drawn.
+void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x )
+{
+ const llwchar* wtext = mText.getWString().c_str();
+ LLWString asterix_text;
+ if (mDrawAsterixes)
+ {
+ for (S32 i = 0; i < mText.length(); i++)
+ {
+ asterix_text += '*';
+ }
+ wtext = asterix_text.c_str();
+ }
+
+ S32 cursor_pos =
+ mScrollHPos +
+ mGLFont->charFromPixelOffset(
+ wtext, mScrollHPos,
+ (F32)(local_mouse_x - mMinHPixels),
+ (F32)(mMaxHPixels - mMinHPixels + 1)); // min-max range is inclusive
+ setCursor(cursor_pos);
+}
+
+void LLLineEditor::setCursor( S32 pos )
+{
+ S32 old_cursor_pos = getCursor();
+ mCursorPos = llclamp( pos, 0, mText.length());
+
+ S32 pixels_after_scroll = findPixelNearestPos();
+ if( pixels_after_scroll > mMaxHPixels )
+ {
+ S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos);
+ S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mMaxHPixels - mMinHPixels + width_chars_to_left)));
+ S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mMaxHPixels - mMinHPixels), mText.length(), getCursor());
+ if (old_cursor_pos == last_visible_char)
+ {
+ mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD));
+ }
+ else
+ {
+ mScrollHPos = min_scroll;
+ }
+ }
+ else if (getCursor() < mScrollHPos)
+ {
+ if (old_cursor_pos == mScrollHPos)
+ {
+ mScrollHPos = llmax(0, llmin(getCursor(), mScrollHPos - SCROLL_INCREMENT_DEL));
+ }
+ else
+ {
+ mScrollHPos = getCursor();
+ }
+ }
+}
+
+
+void LLLineEditor::setCursorToEnd()
+{
+ setCursor(mText.length());
+ deselect();
+}
+
+BOOL LLLineEditor::canDeselect()
+{
+ return hasSelection();
+}
+
+
+void LLLineEditor::deselect()
+{
+ mSelectionStart = 0;
+ mSelectionEnd = 0;
+ mIsSelecting = FALSE;
+}
+
+
+void LLLineEditor::startSelection()
+{
+ mIsSelecting = TRUE;
+ mSelectionStart = getCursor();
+ mSelectionEnd = getCursor();
+}
+
+void LLLineEditor::endSelection()
+{
+ if( mIsSelecting )
+ {
+ mIsSelecting = FALSE;
+ mSelectionEnd = getCursor();
+ }
+}
+
+BOOL LLLineEditor::canSelectAll()
+{
+ return TRUE;
+}
+
+void LLLineEditor::selectAll()
+{
+ mSelectionStart = mText.length();
+ mSelectionEnd = 0;
+ setCursor(mSelectionEnd);
+ //mScrollHPos = 0;
+ mIsSelecting = TRUE;
+}
+
+
+BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
+{
+ setFocus( TRUE );
+
+ if (mSelectionEnd == 0 && mSelectionStart == mText.length())
+ {
+ // if everything is selected, handle this as a normal click to change insertion point
+ handleMouseDown(x, y, mask);
+ }
+ else
+ {
+ // otherwise select everything
+ selectAll();
+ }
+
+ // We don't want handleMouseUp() to "finish" the selection (and thereby
+ // set mSelectionEnd to where the mouse is), so we finish the selection
+ // here.
+ mIsSelecting = FALSE;
+
+ // delay cursor flashing
+ mKeystrokeTimer.reset();
+
+ return TRUE;
+}
+
+BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask)
+{
+ if (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight))
+ {
+ return LLUICtrl::handleMouseDown(x, y, mask);
+ }
+ if (mSelectAllonFocusReceived
+ && gFocusMgr.getKeyboardFocus() != this)
+ {
+ setFocus( TRUE );
+ }
+ else
+ {
+ setFocus( TRUE );
+
+ if (mask & MASK_SHIFT)
+ {
+ // Handle selection extension
+ S32 old_cursor_pos = getCursor();
+ setCursorAtLocalPos(x);
+
+ if (hasSelection())
+ {
+ /* Mac-like behavior - extend selection towards the cursor
+ if (getCursor() < mSelectionStart
+ && getCursor() < mSelectionEnd)
+ {
+ // ...left of selection
+ mSelectionStart = llmax(mSelectionStart, mSelectionEnd);
+ mSelectionEnd = getCursor();
+ }
+ else if (getCursor() > mSelectionStart
+ && getCursor() > mSelectionEnd)
+ {
+ // ...right of selection
+ mSelectionStart = llmin(mSelectionStart, mSelectionEnd);
+ mSelectionEnd = getCursor();
+ }
+ else
+ {
+ mSelectionEnd = getCursor();
+ }
+ */
+ // Windows behavior
+ mSelectionEnd = getCursor();
+ }
+ else
+ {
+ mSelectionStart = old_cursor_pos;
+ mSelectionEnd = getCursor();
+ }
+ // assume we're starting a drag select
+ mIsSelecting = TRUE;
+ }
+ else
+ {
+ // Move cursor and deselect for regular click
+ setCursorAtLocalPos( x );
+ deselect();
+ startSelection();
+ }
+
+ gFocusMgr.setMouseCapture( this, &LLLineEditor::onMouseCaptureLost );
+ }
+
+ // delay cursor flashing
+ mKeystrokeTimer.reset();
+
+ return TRUE;
+}
+
+
+BOOL LLLineEditor::handleHover(S32 x, S32 y, MASK mask)
+{
+ BOOL handled = FALSE;
+ if (gFocusMgr.getMouseCapture() != this && (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight)))
+ {
+ return LLUICtrl::handleHover(x, y, mask);
+ }
+
+ if( getVisible() )
+ {
+ if( (gFocusMgr.getMouseCapture() == this) && mIsSelecting )
+ {
+ if (x != mLastSelectionX || y != mLastSelectionY)
+ {
+ mLastSelectionX = x;
+ mLastSelectionY = y;
+ }
+ // Scroll if mouse cursor outside of bounds
+ if (mScrollTimer.hasExpired())
+ {
+ S32 increment = llround(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME);
+ mScrollTimer.reset();
+ mScrollTimer.setTimerExpirySec(AUTO_SCROLL_TIME);
+ if( (x < mMinHPixels) && (mScrollHPos > 0 ) )
+ {
+ // Scroll to the left
+ mScrollHPos = llclamp(mScrollHPos - increment, 0, mText.length());
+ }
+ else
+ if( (x > mMaxHPixels) && (mCursorPos < (S32)mText.length()) )
+ {
+ // If scrolling one pixel would make a difference...
+ S32 pixels_after_scrolling_one_char = findPixelNearestPos(1);
+ if( pixels_after_scrolling_one_char >= mMaxHPixels )
+ {
+ // ...scroll to the right
+ mScrollHPos = llclamp(mScrollHPos + increment, 0, mText.length());
+ }
+ }
+ }
+
+ setCursorAtLocalPos( x );
+ mSelectionEnd = getCursor();
+
+ // delay cursor flashing
+ mKeystrokeTimer.reset();
+
+ getWindow()->setCursor(UI_CURSOR_IBEAM);
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl;
+ handled = TRUE;
+ }
+
+ if( !handled )
+ {
+ getWindow()->setCursor(UI_CURSOR_IBEAM);
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl;
+ handled = TRUE;
+ }
+ }
+
+ return handled;
+}
+
+
+BOOL LLLineEditor::handleMouseUp(S32 x, S32 y, MASK mask)
+{
+ BOOL handled = FALSE;
+
+ if( gFocusMgr.getMouseCapture() == this )
+ {
+ gFocusMgr.setMouseCapture( NULL, NULL );
+ handled = TRUE;
+ }
+
+ if (!handled && (x < mBorderLeft || x > (mRect.getWidth() - mBorderRight)))
+ {
+ return LLUICtrl::handleMouseUp(x, y, mask);
+ }
+
+ if( mIsSelecting )
+ {
+ setCursorAtLocalPos( x );
+ mSelectionEnd = getCursor();
+
+ handled = TRUE;
+ }
+
+ if( handled )
+ {
+ // delay cursor flashing
+ mKeystrokeTimer.reset();
+ }
+
+ return handled;
+}
+
+
+// Remove a single character from the text
+void LLLineEditor::removeChar()
+{
+ if( getCursor() > 0 )
+ {
+ mText.erase(getCursor() - 1, 1);
+
+ setCursor(getCursor() - 1);
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+}
+
+
+void LLLineEditor::addChar(const llwchar uni_char)
+{
+ llwchar new_c = uni_char;
+ if (hasSelection())
+ {
+ deleteSelection();
+ }
+ else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode())
+ {
+ mText.erase(getCursor(), 1);
+ }
+
+ S32 length_chars = mText.length();
+ S32 cur_bytes = mText.getString().size();;
+ S32 new_bytes = wchar_utf8_length(new_c);
+
+ BOOL allow_char = TRUE;
+
+ // Inserting character
+ if (length_chars == mMaxLengthChars)
+ {
+ allow_char = FALSE;
+ }
+ if ((new_bytes + cur_bytes) > mMaxLengthBytes)
+ {
+ allow_char = FALSE;
+ }
+
+ if (allow_char)
+ {
+ // Will we need to scroll?
+ LLWString w_buf;
+ w_buf.assign(1, new_c);
+
+ mText.insert(getCursor(), w_buf);
+ setCursor(getCursor() + 1);
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+
+ getWindow()->hideCursorUntilMouseMove();
+}
+
+// Extends the selection box to the new cursor position
+void LLLineEditor::extendSelection( S32 new_cursor_pos )
+{
+ if( !mIsSelecting )
+ {
+ startSelection();
+ }
+
+ setCursor(new_cursor_pos);
+ mSelectionEnd = getCursor();
+}
+
+
+void LLLineEditor::setSelection(S32 start, S32 end)
+{
+ S32 len = mText.length();
+
+ mIsSelecting = TRUE;
+
+ // JC, yes, this seems odd, but I think you have to presume a
+ // selection dragged from the end towards the start.
+ mSelectionStart = llclamp(end, 0, len);
+ mSelectionEnd = llclamp(start, 0, len);
+ setCursor(start);
+}
+
+S32 LLLineEditor::prevWordPos(S32 cursorPos) const
+{
+ const LLWString& wtext = mText.getWString();
+ while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') )
+ {
+ cursorPos--;
+ }
+ while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) )
+ {
+ cursorPos--;
+ }
+ return cursorPos;
+}
+
+S32 LLLineEditor::nextWordPos(S32 cursorPos) const
+{
+ const LLWString& wtext = mText.getWString();
+ while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos+1] ) )
+ {
+ cursorPos++;
+ }
+ while( (cursorPos < getLength()) && (wtext[cursorPos+1] == ' ') )
+ {
+ cursorPos++;
+ }
+ return cursorPos;
+}
+
+
+BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask)
+{
+ BOOL handled = FALSE;
+
+ if( mask & MASK_SHIFT )
+ {
+ handled = TRUE;
+
+ switch( key )
+ {
+ case KEY_LEFT:
+ if (mIgnoreArrowKeys)
+ {
+ handled = FALSE;
+ break;
+ }
+ if( 0 < getCursor() )
+ {
+ S32 cursorPos = getCursor() - 1;
+ if( mask & MASK_CONTROL )
+ {
+ cursorPos = prevWordPos(cursorPos);
+ }
+ extendSelection( cursorPos );
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ break;
+
+ case KEY_RIGHT:
+ if (mIgnoreArrowKeys)
+ {
+ handled = FALSE;
+ break;
+ }
+ if( getCursor() < mText.length())
+ {
+ S32 cursorPos = getCursor() + 1;
+ if( mask & MASK_CONTROL )
+ {
+ cursorPos = nextWordPos(cursorPos);
+ }
+ extendSelection( cursorPos );
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ break;
+
+ case KEY_PAGE_UP:
+ case KEY_HOME:
+ if (mIgnoreArrowKeys)
+ {
+ handled = FALSE;
+ break;
+ }
+ extendSelection( 0 );
+ break;
+
+ case KEY_PAGE_DOWN:
+ case KEY_END:
+ {
+ if (mIgnoreArrowKeys)
+ {
+ handled = FALSE;
+ break;
+ }
+ S32 len = mText.length();
+ if( len )
+ {
+ extendSelection( len );
+ }
+ break;
+ }
+
+ default:
+ handled = FALSE;
+ break;
+ }
+ }
+
+ if (!handled && mHandleEditKeysDirectly)
+ {
+ if( (MASK_CONTROL & mask) && ('A' == key) )
+ {
+ if( canSelectAll() )
+ {
+ selectAll();
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ handled = TRUE;
+ }
+ }
+
+
+ return handled;
+}
+
+void LLLineEditor::deleteSelection()
+{
+ if( !mReadOnly && hasSelection() )
+ {
+ S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
+ S32 selection_length = abs( mSelectionStart - mSelectionEnd );
+
+ mText.erase(left_pos, selection_length);
+ deselect();
+ setCursor(left_pos);
+ }
+}
+
+BOOL LLLineEditor::canCut()
+{
+ return !mReadOnly && !mDrawAsterixes && hasSelection();
+}
+
+// cut selection to clipboard
+void LLLineEditor::cut()
+{
+ if( canCut() )
+ {
+ // Prepare for possible rollback
+ LLLineEditorRollback rollback( this );
+
+
+ S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
+ S32 length = abs( mSelectionStart - mSelectionEnd );
+ gClipboard.copyFromSubstring( mText.getWString(), left_pos, length );
+ deleteSelection();
+
+ // Validate new string and rollback the if needed.
+ BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) );
+ if( need_to_rollback )
+ {
+ rollback.doRollback( this );
+ reportBadKeystroke();
+ }
+ else
+ if( mKeystrokeCallback )
+ {
+ mKeystrokeCallback( this, mCallbackUserData );
+ }
+ }
+}
+
+BOOL LLLineEditor::canCopy()
+{
+ return !mDrawAsterixes && hasSelection();
+}
+
+
+// copy selection to clipboard
+void LLLineEditor::copy()
+{
+ if( canCopy() )
+ {
+ S32 left_pos = llmin( mSelectionStart, mSelectionEnd );
+ S32 length = abs( mSelectionStart - mSelectionEnd );
+ gClipboard.copyFromSubstring( mText.getWString(), left_pos, length );
+ }
+}
+
+BOOL LLLineEditor::canPaste()
+{
+ return !mReadOnly && gClipboard.canPasteString();
+}
+
+
+// paste from clipboard
+void LLLineEditor::paste()
+{
+ if (canPaste())
+ {
+ LLWString paste = gClipboard.getPasteWString();
+ if (!paste.empty())
+ {
+ // Prepare for possible rollback
+ LLLineEditorRollback rollback(this);
+
+ // Delete any selected characters
+ if (hasSelection())
+ {
+ deleteSelection();
+ }
+
+ // Clean up string (replace tabs and returns and remove characters that our fonts don't support.)
+ LLWString clean_string(paste);
+ LLWString::replaceTabsWithSpaces(clean_string, 1);
+ //clean_string = wstring_detabify(paste, 1);
+ LLWString::replaceChar(clean_string, '\n', ' ');
+
+ // Insert the string
+
+ //check to see that the size isn't going to be larger than the
+ //max number of characters or bytes
+ U32 available_bytes = mMaxLengthBytes - wstring_utf8_length(mText);
+ size_t available_chars = mMaxLengthChars - mText.length();
+
+ if ( available_bytes < (U32) wstring_utf8_length(clean_string) )
+ {
+ llwchar current_symbol = clean_string[0];
+ U32 wchars_that_fit = 0;
+ U32 total_bytes = wchar_utf8_length(current_symbol);
+
+ //loop over the "wide" characters (symbols)
+ //and check to see how large (in bytes) each symbol is.
+ while ( total_bytes <= available_bytes )
+ {
+ //while we still have available bytes
+ //"accept" the current symbol and check the size
+ //of the next one
+ current_symbol = clean_string[++wchars_that_fit];
+ total_bytes += wchar_utf8_length(current_symbol);
+ }
+
+ clean_string = clean_string.substr(0, wchars_that_fit);
+ reportBadKeystroke();
+ }
+ else if (available_chars < clean_string.length())
+ {
+ // We can't insert all the characters. Insert as many as possible
+ // but make a noise to alert the user. JC
+ clean_string = clean_string.substr(0, available_chars);
+ reportBadKeystroke();
+ }
+
+ mText.insert(getCursor(), clean_string);
+ setCursor(llmin(mMaxLengthChars, getCursor() + (S32)clean_string.length()));
+ deselect();
+
+ // Validate new string and rollback the if needed.
+ BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) );
+ if( need_to_rollback )
+ {
+ rollback.doRollback( this );
+ reportBadKeystroke();
+ }
+ else
+ if( mKeystrokeCallback )
+ {
+ mKeystrokeCallback( this, mCallbackUserData );
+ }
+ }
+ }
+}
+
+
+BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask)
+{
+ BOOL handled = FALSE;
+
+ switch( key )
+ {
+ case KEY_INSERT:
+ if (mask == MASK_NONE)
+ {
+ gKeyboard->toggleInsertMode();
+ }
+
+ handled = TRUE;
+ break;
+
+ case KEY_BACKSPACE:
+ if (!mReadOnly)
+ {
+ //llinfos << "Handling backspace" << llendl;
+ if( hasSelection() )
+ {
+ deleteSelection();
+ }
+ else
+ if( 0 < getCursor() )
+ {
+ removeChar();
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_PAGE_UP:
+ case KEY_HOME:
+ if (!mIgnoreArrowKeys)
+ {
+ setCursor(0);
+ handled = TRUE;
+ }
+ break;
+
+ case KEY_PAGE_DOWN:
+ case KEY_END:
+ if (!mIgnoreArrowKeys)
+ {
+ S32 len = mText.length();
+ if( len )
+ {
+ setCursor(len);
+ }
+ handled = TRUE;
+ }
+ break;
+
+ case KEY_LEFT:
+ if (!mIgnoreArrowKeys)
+ {
+ if( hasSelection() )
+ {
+ setCursor(llmin( getCursor() - 1, mSelectionStart, mSelectionEnd ));
+ }
+ else
+ if( 0 < getCursor() )
+ {
+ S32 cursorPos = getCursor() - 1;
+ if( mask & MASK_CONTROL )
+ {
+ cursorPos = prevWordPos(cursorPos);
+ }
+ setCursor(cursorPos);
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ handled = TRUE;
+ }
+ break;
+
+ case KEY_RIGHT:
+ if (!mIgnoreArrowKeys)
+ {
+ if (hasSelection())
+ {
+ setCursor(llmax(getCursor() + 1, mSelectionStart, mSelectionEnd));
+ }
+ else
+ if (getCursor() < mText.length())
+ {
+ S32 cursorPos = getCursor() + 1;
+ if( mask & MASK_CONTROL )
+ {
+ cursorPos = nextWordPos(cursorPos);
+ }
+ setCursor(cursorPos);
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ handled = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if( !handled && mHandleEditKeysDirectly )
+ {
+ // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system.
+ if( KEY_DELETE == key )
+ {
+ if( canDoDelete() )
+ {
+ doDelete();
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ handled = TRUE;
+ }
+ else
+ if( MASK_CONTROL & mask )
+ {
+ if( 'C' == key )
+ {
+ if( canCopy() )
+ {
+ copy();
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ handled = TRUE;
+ }
+ else
+ if( 'V' == key )
+ {
+ if( canPaste() )
+ {
+ paste();
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ handled = TRUE;
+ }
+ else
+ if( 'X' == key )
+ {
+ if( canCut() )
+ {
+ cut();
+ }
+ else
+ {
+ reportBadKeystroke();
+ }
+ handled = TRUE;
+ }
+ }
+ }
+ return handled;
+}
+
+
+BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask, BOOL called_from_parent )
+{
+ BOOL handled = FALSE;
+ BOOL selection_modified = FALSE;
+
+ if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible())
+ {
+ LLLineEditorRollback rollback( this );
+
+ if( !handled )
+ {
+ handled = handleSelectionKey( key, mask );
+ selection_modified = handled;
+ }
+
+ // Handle most keys only if the text editor is writeable.
+ if ( !mReadOnly )
+ {
+ if( !handled )
+ {
+ handled = handleSpecialKey( key, mask );
+ }
+ }
+
+ if( handled )
+ {
+ mKeystrokeTimer.reset();
+
+ // Most keystrokes will make the selection box go away, but not all will.
+ if( !selection_modified &&
+ KEY_SHIFT != key &&
+ KEY_CONTROL != key &&
+ KEY_ALT != key &&
+ KEY_CAPSLOCK )
+ {
+ deselect();
+ }
+
+ BOOL need_to_rollback = FALSE;
+
+ // If read-only, don't allow changes
+ need_to_rollback |= (mReadOnly && (mText.getString() == rollback.getText()));
+
+ // Validate new string and rollback the keystroke if needed.
+ need_to_rollback |= (mPrevalidateFunc && !mPrevalidateFunc(mText.getWString()));
+
+ if (need_to_rollback)
+ {
+ rollback.doRollback(this);
+
+ reportBadKeystroke();
+ }
+
+ // Notify owner if requested
+ if (!need_to_rollback && handled)
+ {
+ if (mKeystrokeCallback)
+ {
+ mKeystrokeCallback(this, mCallbackUserData);
+ }
+ }
+ }
+ }
+
+ return handled;
+}
+
+
+BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char, BOOL called_from_parent)
+{
+ if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL
+ {
+ return FALSE;
+ }
+
+ BOOL handled = FALSE;
+
+ if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible() && !mReadOnly)
+ {
+ handled = TRUE;
+
+ LLLineEditorRollback rollback( this );
+
+ addChar(uni_char);
+
+ mKeystrokeTimer.reset();
+
+ deselect();
+
+ BOOL need_to_rollback = FALSE;
+
+ // Validate new string and rollback the keystroke if needed.
+ need_to_rollback |= ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) );
+
+ if( need_to_rollback )
+ {
+ rollback.doRollback( this );
+
+ reportBadKeystroke();
+ }
+
+ // Notify owner if requested
+ if( !need_to_rollback && handled )
+ {
+ if( mKeystrokeCallback )
+ {
+ // HACK! The only usage of this callback doesn't do anything with the character.
+ // We'll have to do something about this if something ever changes! - Doug
+ mKeystrokeCallback( this, mCallbackUserData );
+ }
+ }
+ }
+ return handled;
+}
+
+
+BOOL LLLineEditor::canDoDelete()
+{
+ return ( !mReadOnly && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) );
+}
+
+void LLLineEditor::doDelete()
+{
+ if (canDoDelete())
+ {
+ // Prepare for possible rollback
+ LLLineEditorRollback rollback( this );
+
+ if (hasSelection())
+ {
+ deleteSelection();
+ }
+ else if ( getCursor() < mText.length())
+ {
+ setCursor(getCursor() + 1);
+ removeChar();
+ }
+
+ // Validate new string and rollback the if needed.
+ BOOL need_to_rollback = ( mPrevalidateFunc && !mPrevalidateFunc( mText.getWString() ) );
+ if( need_to_rollback )
+ {
+ rollback.doRollback( this );
+ reportBadKeystroke();
+ }
+ else
+ {
+ if( mKeystrokeCallback )
+ {
+ mKeystrokeCallback( this, mCallbackUserData );
+ }
+ }
+ }
+}
+
+
+void LLLineEditor::draw()
+{
+ if( !getVisible() )
+ {
+ return;
+ }
+
+ S32 text_len = mText.length();
+
+ LLString saved_text;
+ if (mDrawAsterixes)
+ {
+ saved_text = mText.getString();
+ LLString text;
+ for (S32 i = 0; i < mText.length(); i++)
+ {
+ text += '*';
+ }
+ mText = text;
+ }
+
+ // draw rectangle for the background
+ LLRect background( 0, mRect.getHeight(), mRect.getWidth(), 0 );
+ background.stretch( -mBorderThickness );
+
+ LLColor4 bg_color = mReadOnlyBgColor;
+
+ // drawing solids requires texturing be disabled
+ {
+ LLGLSNoTexture no_texture;
+ // draw background for text
+ if( !mReadOnly )
+ {
+ if( gFocusMgr.getKeyboardFocus() == this )
+ {
+ bg_color = mFocusBgColor;
+ }
+ else
+ {
+ bg_color = mWriteableBgColor;
+ }
+ }
+ gl_rect_2d(background, bg_color);
+ }
+
+ // draw text
+
+ S32 cursor_bottom = background.mBottom + 1;
+ S32 cursor_top = background.mTop - 1;
+
+ LLColor4 text_color;
+ if (!mReadOnly)
+ {
+ if (!mTentative)
+ {
+ text_color = mFgColor;
+ }
+ else
+ {
+ text_color = mTentativeFgColor;
+ }
+ }
+ else
+ {
+ text_color = mReadOnlyFgColor;
+ }
+ LLColor4 label_color = mTentativeFgColor;
+
+ S32 rendered_text = 0;
+ F32 rendered_pixels_right = (F32)mMinHPixels;
+ F32 text_bottom = (F32)background.mBottom + (F32)UI_LINEEDITOR_V_PAD;
+
+ if( (gFocusMgr.getKeyboardFocus() == this) && hasSelection() )
+ {
+ S32 select_left;
+ S32 select_right;
+ if( mSelectionStart < getCursor() )
+ {
+ select_left = mSelectionStart;
+ select_right = getCursor();
+ }
+ else
+ {
+ select_left = getCursor();
+ select_right = mSelectionStart;
+ }
+
+ if( select_left > mScrollHPos )
+ {
+ // unselected, left side
+ rendered_text = mGLFont->render(
+ mText, mScrollHPos,
+ rendered_pixels_right, text_bottom,
+ text_color,
+ LLFontGL::LEFT, LLFontGL::BOTTOM,
+ LLFontGL::NORMAL,
+ select_left - mScrollHPos,
+ mMaxHPixels - llround(rendered_pixels_right),
+ &rendered_pixels_right);
+ }
+
+ if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) )
+ {
+ LLColor4 color(1.f - bg_color.mV[0], 1.f - bg_color.mV[1], 1.f - bg_color.mV[2], 1.f);
+ // selected middle
+ S32 width = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos + rendered_text, select_right - mScrollHPos - rendered_text);
+ width = llmin(width, mMaxHPixels - llround(rendered_pixels_right));
+ gl_rect_2d(llround(rendered_pixels_right), cursor_top, llround(rendered_pixels_right)+width, cursor_bottom, color);
+
+ rendered_text += mGLFont->render(
+ mText, mScrollHPos + rendered_text,
+ rendered_pixels_right, text_bottom,
+ LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ),
+ LLFontGL::LEFT, LLFontGL::BOTTOM,
+ LLFontGL::NORMAL,
+ select_right - mScrollHPos - rendered_text,
+ mMaxHPixels - llround(rendered_pixels_right),
+ &rendered_pixels_right);
+ }
+
+ if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) )
+ {
+ // unselected, right side
+ mGLFont->render(
+ mText, mScrollHPos + rendered_text,
+ rendered_pixels_right, text_bottom,
+ text_color,
+ LLFontGL::LEFT, LLFontGL::BOTTOM,
+ LLFontGL::NORMAL,
+ S32_MAX,
+ mMaxHPixels - llround(rendered_pixels_right),
+ &rendered_pixels_right);
+ }
+ }
+ else
+ {
+ mGLFont->render(
+ mText, mScrollHPos,
+ rendered_pixels_right, text_bottom,
+ text_color,
+ LLFontGL::LEFT, LLFontGL::BOTTOM,
+ LLFontGL::NORMAL,
+ S32_MAX,
+ mMaxHPixels - llround(rendered_pixels_right),
+ &rendered_pixels_right);
+ }
+
+ // If we're editing...
+ if( gFocusMgr.getKeyboardFocus() == this)
+ {
+ // (Flash the cursor every half second)
+ if (gShowTextEditCursor && !mReadOnly)
+ {
+ F32 elapsed = mKeystrokeTimer.getElapsedTimeF32();
+ if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) )
+ {
+ S32 cursor_left = findPixelNearestPos();
+ cursor_left -= UI_LINEEDITOR_CURSOR_THICKNESS / 2;
+ S32 cursor_right = cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS;
+ if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
+ {
+ const LLWString space(utf8str_to_wstring(LLString(" ")));
+ S32 wswidth = mGLFont->getWidth(space.c_str());
+ S32 width = mGLFont->getWidth(mText.getWString().c_str(), getCursor(), 1) + 1;
+ cursor_right = cursor_left + llmax(wswidth, width);
+ }
+ // Use same color as text for the Cursor
+ gl_rect_2d(cursor_left, cursor_top,
+ cursor_right, cursor_bottom, text_color);
+ if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection())
+ {
+ mGLFont->render(mText, getCursor(), (F32)(cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS / 2), text_bottom,
+ LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ),
+ LLFontGL::LEFT, LLFontGL::BOTTOM,
+ LLFontGL::NORMAL,
+ 1);
+ }
+ }
+ }
+
+ // Draw children (border)
+ //mBorder->setVisible(TRUE);
+ mBorder->setKeyboardFocusHighlight( TRUE );
+ LLView::draw();
+ mBorder->setKeyboardFocusHighlight( FALSE );
+ //mBorder->setVisible(FALSE);
+ }
+ else // does not have keyboard input
+ {
+ // draw label if no text provided
+ if (0 == mText.length())
+ {
+ mGLFont->render(mLabel.getWString(), 0,
+ LABEL_HPAD, (F32)text_bottom,
+ label_color,
+ LLFontGL::LEFT, LLFontGL::BOTTOM,
+ LLFontGL::NORMAL,
+ S32_MAX,
+ mMaxHPixels - llround(rendered_pixels_right),
+ &rendered_pixels_right, FALSE);
+ }
+ // Draw children (border)
+ LLView::draw();
+ }
+
+ if (mDrawAsterixes)
+ {
+ mText = saved_text;
+ }
+}
+
+
+// Returns the local screen space X coordinate associated with the text cursor position.
+S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset)
+{
+ S32 dpos = getCursor() - mScrollHPos + cursor_offset;
+ S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mMinHPixels;
+ return result;
+}
+
+void LLLineEditor::reportBadKeystroke()
+{
+ make_ui_sound("UISndBadKeystroke");
+}
+
+//virtual
+void LLLineEditor::clear()
+{
+ mText.clear();
+ setCursor(0);
+}
+
+//virtual
+void LLLineEditor::onTabInto()
+{
+ selectAll();
+}
+
+//virtual
+BOOL LLLineEditor::acceptsTextInput() const
+{
+ return TRUE;
+}
+
+// Start or stop the editor from accepting text-editing keystrokes
+void LLLineEditor::setFocus( BOOL new_state )
+{
+ BOOL old_state = hasFocus();
+
+ // getting focus when we didn't have it before, and we want to select all
+ if (!old_state && new_state && mSelectAllonFocusReceived)
+ {
+ selectAll();
+ // We don't want handleMouseUp() to "finish" the selection (and thereby
+ // set mSelectionEnd to where the mouse is), so we finish the selection
+ // here.
+ mIsSelecting = FALSE;
+ }
+
+ if( new_state )
+ {
+ gEditMenuHandler = this;
+
+ // Don't start the cursor flashing right away
+ mKeystrokeTimer.reset();
+ }
+ else
+ {
+ // Not really needed, since loss of keyboard focus should take care of this,
+ // but limited paranoia is ok.
+ if( gEditMenuHandler == this )
+ {
+ gEditMenuHandler = NULL;
+ }
+
+ endSelection();
+ }
+
+ LLUICtrl::setFocus( new_state );
+}
+
+//virtual
+void LLLineEditor::setRect(const LLRect& rect)
+{
+ LLUICtrl::setRect(rect);
+ if (mBorder)
+ {
+ LLRect border_rect = mBorder->getRect();
+ // Scalable UI somehow made these rectangles off-by-one.
+ // I don't know why. JC
+ border_rect.setOriginAndSize(border_rect.mLeft, border_rect.mBottom,
+ rect.getWidth()-1, rect.getHeight()-1);
+ mBorder->setRect(border_rect);
+ }
+}
+
+// Limits what characters can be used to [1234567890.-] with [-] only valid in the first position.
+// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for
+// the simple reasons that intermediate states may be invalid even if the final result is valid.
+//
+// static
+BOOL LLLineEditor::prevalidateFloat(const LLWString &str)
+{
+ LLLocale locale(LLLocale::USER_LOCALE);
+
+ BOOL success = TRUE;
+ LLWString trimmed = str;
+ LLWString::trim(trimmed);
+ S32 len = trimmed.length();
+ if( 0 < len )
+ {
+ // May be a comma or period, depending on the locale
+ char decimal_point = gResMgr->getDecimalPoint();
+
+ S32 i = 0;
+
+ // First character can be a negative sign
+ if( '-' == trimmed[0] )
+ {
+ i++;
+ }
+
+ for( ; i < len; i++ )
+ {
+ if( (decimal_point != trimmed[i] ) && !isdigit( trimmed[i] ) )
+ {
+ success = FALSE;
+ break;
+ }
+ }
+ }
+
+ return success;
+}
+
+//static
+BOOL LLLineEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); }
+
+// static
+BOOL LLLineEditor::postvalidateFloat(const LLString &str)
+{
+ LLLocale locale(LLLocale::USER_LOCALE);
+
+ BOOL success = TRUE;
+ BOOL has_decimal = FALSE;
+ BOOL has_digit = FALSE;
+
+ LLWString trimmed = utf8str_to_wstring(str);
+ LLWString::trim(trimmed);
+ S32 len = trimmed.length();
+ if( 0 < len )
+ {
+ S32 i = 0;
+
+ // First character can be a negative sign
+ if( '-' == trimmed[0] )
+ {
+ i++;
+ }
+
+ // May be a comma or period, depending on the locale
+ char decimal_point = gResMgr->getDecimalPoint();
+
+ for( ; i < len; i++ )
+ {
+ if( decimal_point == trimmed[i] )
+ {
+ if( has_decimal )
+ {
+ // can't have two
+ success = FALSE;
+ break;
+ }
+ else
+ {
+ has_decimal = TRUE;
+ }
+ }
+ else
+ if( isdigit( trimmed[i] ) )
+ {
+ has_digit = TRUE;
+ }
+ else
+ {
+ success = FALSE;
+ break;
+ }
+ }
+ }
+
+ // Gotta have at least one
+ success = has_digit;
+
+ return success;
+}
+
+// Limits what characters can be used to [1234567890-] with [-] only valid in the first position.
+// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for
+// the simple reasons that intermediate states may be invalid even if the final result is valid.
+//
+// static
+BOOL LLLineEditor::prevalidateInt(const LLWString &str)
+{
+ LLLocale locale(LLLocale::USER_LOCALE);
+
+ BOOL success = TRUE;
+ LLWString trimmed = str;
+ LLWString::trim(trimmed);
+ S32 len = trimmed.length();
+ if( 0 < len )
+ {
+ S32 i = 0;
+
+ // First character can be a negative sign
+ if( '-' == trimmed[0] )
+ {
+ i++;
+ }
+
+ for( ; i < len; i++ )
+ {
+ if( !isdigit( trimmed[i] ) )
+ {
+ success = FALSE;
+ break;
+ }
+ }
+ }
+
+ return success;
+}
+
+// static
+BOOL LLLineEditor::prevalidatePositiveS32(const LLWString &str)
+{
+ LLLocale locale(LLLocale::USER_LOCALE);
+
+ LLWString trimmed = str;
+ LLWString::trim(trimmed);
+ S32 len = trimmed.length();
+ BOOL success = TRUE;
+ if(0 < len)
+ {
+ if(('-' == trimmed[0]) || ('0' == trimmed[0]))
+ {
+ success = FALSE;
+ }
+ S32 i = 0;
+ while(success && (i < len))
+ {
+ if(!isdigit(trimmed[i++]))
+ {
+ success = FALSE;
+ }
+ }
+ }
+ if (success)
+ {
+ S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10);
+ if (val <= 0)
+ {
+ success = FALSE;
+ }
+ }
+ return success;
+}
+
+BOOL LLLineEditor::prevalidateNonNegativeS32(const LLWString &str)
+{
+ LLLocale locale(LLLocale::USER_LOCALE);
+
+ LLWString trimmed = str;
+ LLWString::trim(trimmed);
+ S32 len = trimmed.length();
+ BOOL success = TRUE;
+ if(0 < len)
+ {
+ if('-' == trimmed[0])
+ {
+ success = FALSE;
+ }
+ S32 i = 0;
+ while(success && (i < len))
+ {
+ if(!isdigit(trimmed[i++]))
+ {
+ success = FALSE;
+ }
+ }
+ }
+ if (success)
+ {
+ S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10);
+ if (val < 0)
+ {
+ success = FALSE;
+ }
+ }
+ return success;
+}
+
+BOOL LLLineEditor::prevalidateAlphaNum(const LLWString &str)
+{
+ LLLocale locale(LLLocale::USER_LOCALE);
+
+ BOOL rv = TRUE;
+ S32 len = str.length();
+ if(len == 0) return rv;
+ while(len--)
+ {
+ if( !isalnum(str[len]) )
+ {
+ rv = FALSE;
+ break;
+ }
+ }
+ return rv;
+}
+
+// static
+BOOL LLLineEditor::prevalidateAlphaNumSpace(const LLWString &str)
+{
+ LLLocale locale(LLLocale::USER_LOCALE);
+
+ BOOL rv = TRUE;
+ S32 len = str.length();
+ if(len == 0) return rv;
+ while(len--)
+ {
+ if(!(isalnum(str[len]) || (' ' == str[len])))
+ {
+ rv = FALSE;
+ break;
+ }
+ }
+ return rv;
+}
+
+// static
+BOOL LLLineEditor::prevalidatePrintableNotPipe(const LLWString &str)
+{
+ BOOL rv = TRUE;
+ S32 len = str.length();
+ if(len == 0) return rv;
+ while(len--)
+ {
+ if('|' == str[len])
+ {
+ rv = FALSE;
+ break;
+ }
+ if(!((' ' == str[len]) || isalnum(str[len]) || ispunct(str[len])))
+ {
+ rv = FALSE;
+ break;
+ }
+ }
+ return rv;
+}
+
+
+// static
+BOOL LLLineEditor::prevalidatePrintableNoSpace(const LLWString &str)
+{
+ BOOL rv = TRUE;
+ S32 len = str.length();
+ if(len == 0) return rv;
+ while(len--)
+ {
+ if(iswspace(str[len]))
+ {
+ rv = FALSE;
+ break;
+ }
+ if( !(isalnum(str[len]) || ispunct(str[len]) ) )
+ {
+ rv = FALSE;
+ break;
+ }
+ }
+ return rv;
+}
+
+// static
+BOOL LLLineEditor::prevalidateASCII(const LLWString &str)
+{
+ BOOL rv = TRUE;
+ S32 len = str.length();
+ while(len--)
+ {
+ if (str[len] < 0x20 || str[len] > 0x7f)
+ {
+ rv = FALSE;
+ break;
+ }
+ }
+ return rv;
+}
+
+//static
+void LLLineEditor::onMouseCaptureLost( LLMouseHandler* old_captor )
+{
+ LLLineEditor* self = (LLLineEditor*) old_captor;
+ self->endSelection();
+}
+
+
+void LLLineEditor::setSelectAllonFocusReceived(BOOL b)
+{
+ mSelectAllonFocusReceived = b;
+}
+
+
+void LLLineEditor::setKeystrokeCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data))
+{
+ mKeystrokeCallback = keystroke_callback;
+}
+
+void LLLineEditor::setFocusLostCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data))
+{
+ mFocusLostCallback = keystroke_callback;
+}
+
+// virtual
+LLXMLNodePtr LLLineEditor::getXML(bool save_children) const
+{
+ LLXMLNodePtr node = LLUICtrl::getXML();
+
+ node->createChild("max_length", TRUE)->setIntValue(mMaxLengthBytes);
+
+ node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont));
+
+ if (mBorder)
+ {
+ LLString bevel;
+ switch(mBorder->getBevel())
+ {
+ default:
+ case LLViewBorder::BEVEL_NONE: bevel = "none"; break;
+ case LLViewBorder::BEVEL_IN: bevel = "in"; break;
+ case LLViewBorder::BEVEL_OUT: bevel = "out"; break;
+ case LLViewBorder::BEVEL_BRIGHT:bevel = "bright"; break;
+ }
+ node->createChild("bevel_style", TRUE)->setStringValue(bevel);
+
+ LLString style;
+ switch(mBorder->getStyle())
+ {
+ default:
+ case LLViewBorder::STYLE_LINE: style = "line"; break;
+ case LLViewBorder::STYLE_TEXTURE: style = "texture"; break;
+ }
+ node->createChild("border_style", TRUE)->setStringValue(style);
+
+ node->createChild("border_thickness", TRUE)->setIntValue(mBorder->getBorderWidth());
+ }
+
+ if (!mLabel.empty())
+ {
+ node->createChild("label", TRUE)->setStringValue(mLabel.getString());
+ }
+
+ node->createChild("select_all_on_focus_received", TRUE)->setBoolValue(mSelectAllonFocusReceived);
+
+ node->createChild("handle_edit_keys_directly", TRUE)->setBoolValue(mHandleEditKeysDirectly );
+
+ addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor");
+ addColorXML(node, mFgColor, "text_color", "TextFgColor");
+ addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor");
+ addColorXML(node, mTentativeFgColor, "text_tentative_color", "TextFgTentativeColor");
+ addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor");
+ addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor");
+ addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor");
+
+ node->createChild("select_on_focus", TRUE)->setBoolValue(mSelectAllonFocusReceived );
+
+ return node;
+}
+
+// static
+LLView* LLLineEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
+{
+ LLString name("line_editor");
+ node->getAttributeString("name", name);
+
+ LLRect rect;
+ createRect(node, rect, parent, LLRect());
+
+ S32 max_text_length = 128;
+ node->getAttributeS32("max_length", max_text_length);
+
+ LLFontGL* font = LLView::selectFont(node);
+
+ LLString text = node->getTextContents().substr(0, max_text_length - 1);
+
+ LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_IN;
+ LLViewBorder::getBevelFromAttribute(node, bevel_style);
+
+ LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE;
+ LLString border_string;
+ node->getAttributeString("border_style", border_string);
+ LLString::toLower(border_string);
+
+ if (border_string == "texture")
+ {
+ border_style = LLViewBorder::STYLE_TEXTURE;
+ }
+
+ S32 border_thickness = 1;
+ node->getAttributeS32("border_thickness", border_thickness);
+
+ LLUICtrlCallback commit_callback = NULL;
+
+ LLLineEditor* line_editor = new LLLineEditor(name,
+ rect,
+ text,
+ font,
+ max_text_length,
+ commit_callback,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ bevel_style,
+ border_style,
+ border_thickness);
+
+ LLString label;
+ if(node->getAttributeString("label", label))
+ {
+ line_editor->setLabel(label);
+ }
+ BOOL select_all_on_focus_received = FALSE;
+ if (node->getAttributeBOOL("select_all_on_focus_received", select_all_on_focus_received))
+ {
+ line_editor->setSelectAllonFocusReceived(select_all_on_focus_received);
+ }
+ BOOL handle_edit_keys_directly = FALSE;
+ if (node->getAttributeBOOL("handle_edit_keys_directly", handle_edit_keys_directly))
+ {
+ line_editor->setHandleEditKeysDirectly(handle_edit_keys_directly);
+ }
+
+ line_editor->setColorParameters(node);
+
+ if(node->hasAttribute("select_on_focus"))
+ {
+ BOOL selectall = FALSE;
+ node->getAttributeBOOL("select_on_focus", selectall);
+ line_editor->setSelectAllonFocusReceived(selectall);
+ }
+
+ LLString prevalidate;
+ if(node->getAttributeString("prevalidate", prevalidate))
+ {
+ LLString::toLower(prevalidate);
+
+ if ("ascii" == prevalidate)
+ {
+ line_editor->setPrevalidate( LLLineEditor::prevalidateASCII );
+ }
+ else if ("float" == prevalidate)
+ {
+ line_editor->setPrevalidate( LLLineEditor::prevalidateFloat );
+ }
+ else if ("int" == prevalidate)
+ {
+ line_editor->setPrevalidate( LLLineEditor::prevalidateInt );
+ }
+ else if ("positive_s32" == prevalidate)
+ {
+ line_editor->setPrevalidate( LLLineEditor::prevalidatePositiveS32 );
+ }
+ else if ("non_negative_s32" == prevalidate)
+ {
+ line_editor->setPrevalidate( LLLineEditor::prevalidateNonNegativeS32 );
+ }
+ else if ("alpha_num" == prevalidate)
+ {
+ line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNum );
+ }
+ else if ("alpha_num_space" == prevalidate)
+ {
+ line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNumSpace );
+ }
+ else if ("printable_not_pipe" == prevalidate)
+ {
+ line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNotPipe );
+ }
+ else if ("printable_no_space" == prevalidate)
+ {
+ line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNoSpace );
+ }
+ }
+
+ line_editor->initFromXML(node, parent);
+
+ return line_editor;
+}
+
+void LLLineEditor::setColorParameters(LLXMLNodePtr node)
+{
+ LLColor4 color;
+ if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color))
+ {
+ setCursorColor(color);
+ }
+ if(node->hasAttribute("text_color"))
+ {
+ LLUICtrlFactory::getAttributeColor(node,"text_color", color);
+ setFgColor(color);
+ }
+ if(node->hasAttribute("text_readonly_color"))
+ {
+ LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color);
+ setReadOnlyFgColor(color);
+ }
+ if (LLUICtrlFactory::getAttributeColor(node,"text_tentative_color", color))
+ {
+ setTentativeFgColor(color);
+ }
+ if(node->hasAttribute("bg_readonly_color"))
+ {
+ LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color);
+ setReadOnlyBgColor(color);
+ }
+ if(node->hasAttribute("bg_writeable_color"))
+ {
+ LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color);
+ setWriteableBgColor(color);
+ }
+}
+
+void LLLineEditor::setValue(const LLSD& value )
+{
+ setText(value.asString());
+}
+
+LLSD LLLineEditor::getValue() const
+{
+ LLString str = getText();
+ LLSD ret(str);
+ return ret;
+}
+
+BOOL LLLineEditor::setTextArg( const LLString& key, const LLString& text )
+{
+ mText.setArg(key, text);
+ return TRUE;
+}
+
+BOOL LLLineEditor::setLabelArg( const LLString& key, const LLString& text )
+{
+ mLabel.setArg(key, text);
+ return TRUE;
+}
+
+LLSearchEditor::LLSearchEditor(const LLString& name,
+ const LLRect& rect,
+ S32 max_length_bytes,
+ void (*search_callback)(const LLString& search_string, void* user_data),
+ void* userdata)
+ :
+ LLUICtrl(name, rect, TRUE, NULL, userdata),
+ mSearchCallback(search_callback)
+{
+ LLRect search_edit_rect(0, mRect.getHeight(), mRect.getWidth(), 0);
+ mSearchEdit = new LLLineEditor("search edit",
+ search_edit_rect,
+ LLString::null,
+ NULL,
+ max_length_bytes,
+ NULL,
+ onSearchEdit,
+ NULL,
+ this);
+ // TODO: this should be translatable
+ mSearchEdit->setLabel("Type here to search");
+ mSearchEdit->setFollowsAll();
+ mSearchEdit->setSelectAllonFocusReceived(TRUE);
+
+ addChild(mSearchEdit);
+
+ S32 btn_width = rect.getHeight(); // button is square, and as tall as search editor
+ LLRect clear_btn_rect(rect.getWidth() - btn_width, rect.getHeight(), rect.getWidth(), 0);
+ mClearSearchButton = new LLButton("clear search",
+ clear_btn_rect,
+ "closebox.tga",
+ "UIImgBtnCloseInactiveUUID",
+ LLString::null,
+ onClearSearch,
+ this,
+ NULL,
+ LLString::null);
+ mClearSearchButton->setFollowsRight();
+ mClearSearchButton->setFollowsTop();
+ mClearSearchButton->setImageColor(LLUI::sColorsGroup->getColor("TextFgTentativeColor"));
+ mClearSearchButton->setTabStop(FALSE);
+ mSearchEdit->addChild(mClearSearchButton);
+
+ mSearchEdit->setBorderWidth(0, btn_width);
+}
+
+LLSearchEditor::~LLSearchEditor()
+{
+}
+
+//virtual
+EWidgetType LLSearchEditor::getWidgetType() const
+{
+ return WIDGET_TYPE_SEARCH_EDITOR;
+}
+
+//virtual
+LLString LLSearchEditor::getWidgetTag() const
+{
+ return LL_SEARCH_EDITOR_TAG;
+}
+
+//virtual
+void LLSearchEditor::setValue(const LLSD& value )
+{
+ mSearchEdit->setValue(value);
+}
+
+//virtual
+LLSD LLSearchEditor::getValue() const
+{
+ return mSearchEdit->getValue();
+}
+
+//virtual
+BOOL LLSearchEditor::setTextArg( const LLString& key, const LLString& text )
+{
+ return mSearchEdit->setTextArg(key, text);
+}
+
+//virtual
+BOOL LLSearchEditor::setLabelArg( const LLString& key, const LLString& text )
+{
+ return mSearchEdit->setLabelArg(key, text);
+}
+
+//virtual
+void LLSearchEditor::clear()
+{
+ if (mSearchEdit)
+ {
+ mSearchEdit->clear();
+ }
+}
+
+
+void LLSearchEditor::draw()
+{
+ mClearSearchButton->setVisible(!mSearchEdit->getWText().empty());
+
+ LLUICtrl::draw();
+}
+
+void LLSearchEditor::setText(const LLString &new_text)
+{
+ mSearchEdit->setText(new_text);
+}
+
+//static
+void LLSearchEditor::onSearchEdit(LLLineEditor* caller, void* user_data )
+{
+ LLSearchEditor* search_editor = (LLSearchEditor*)user_data;
+ if (search_editor->mSearchCallback)
+ {
+ search_editor->mSearchCallback(caller->getText(), search_editor->mCallbackUserData);
+ }
+}
+
+//static
+void LLSearchEditor::onClearSearch(void* user_data)
+{
+ LLSearchEditor* search_editor = (LLSearchEditor*)user_data;
+
+ search_editor->setText(LLString::null);
+ if (search_editor->mSearchCallback)
+ {
+ search_editor->mSearchCallback(LLString::null, search_editor->mCallbackUserData);
+ }
+}
+
+// static
+LLView* LLSearchEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
+{
+ LLString name("search_editor");
+ node->getAttributeString("name", name);
+
+ LLRect rect;
+ createRect(node, rect, parent, LLRect());
+
+ S32 max_text_length = 128;
+ node->getAttributeS32("max_length", max_text_length);
+
+ LLString text = node->getValue().substr(0, max_text_length - 1);
+
+ LLSearchEditor* search_editor = new LLSearchEditor(name,
+ rect,
+ max_text_length,
+ NULL, NULL);
+
+ search_editor->setText(text);
+
+ search_editor->initFromXML(node, parent);
+
+ return search_editor;
+}