diff options
author | Don Kjer <don@lindenlab.com> | 2007-12-05 23:43:56 +0000 |
---|---|---|
committer | Don Kjer <don@lindenlab.com> | 2007-12-05 23:43:56 +0000 |
commit | facf67ae3226105910c983a8fa8760414bf703e9 (patch) | |
tree | b5f7cd6b79a79f769080a65b6fe2cb6b97c8b6fb /indra/llui | |
parent | 45057e8881c3166c7c0ef545c02bc177922af6fb (diff) |
EFFECTIVE MERGE: svn merge -r 71520:73420 svn+ssh://svn/svn/linden/branches/maintenance-3 into release
ACTUAL MERGE: svn merge -r 75074:75114 svn+ssh://svn/svn/linden/qa/maintenance-3-merge-75067 into release
Diffstat (limited to 'indra/llui')
-rw-r--r-- | indra/llui/lllineeditor.cpp | 347 | ||||
-rw-r--r-- | indra/llui/lllineeditor.h | 36 | ||||
-rw-r--r-- | indra/llui/llmenugl.cpp | 4 | ||||
-rw-r--r-- | indra/llui/lltextbox.cpp | 2 | ||||
-rw-r--r-- | indra/llui/lltexteditor.cpp | 437 | ||||
-rw-r--r-- | indra/llui/lltexteditor.h | 38 | ||||
-rw-r--r-- | indra/llui/llui.cpp | 28 | ||||
-rw-r--r-- | indra/llui/llui.h | 4 |
8 files changed, 815 insertions, 81 deletions
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 420970a38a..3c7cd17b92 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -72,6 +72,15 @@ const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing const F32 AUTO_SCROLL_TIME = 0.05f; const F32 LABEL_HPAD = 5.f; +const F32 PREEDIT_MARKER_BRIGHTNESS = 0.4f; +const S32 PREEDIT_MARKER_GAP = 1; +const S32 PREEDIT_MARKER_POSITION = 2; +const S32 PREEDIT_MARKER_THICKNESS = 1; +const F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f; +const S32 PREEDIT_STANDOUT_GAP = 1; +const S32 PREEDIT_STANDOUT_POSITION = 2; +const S32 PREEDIT_STANDOUT_THICKNESS = 2; + // This is a friend class of and is only used by LLLineEditor class LLLineEditorRollback { @@ -127,7 +136,6 @@ LLLineEditor::LLLineEditor(const LLString& name, const LLRect& rect, 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 ), @@ -223,12 +231,19 @@ LLString LLLineEditor::getWidgetTag() const return LL_LINE_EDITOR_TAG; } -void LLLineEditor::onFocusLost() +void LLLineEditor::onFocusReceived() { - // Need to notify early when loosing focus. - getWindow()->allowLanguageTextInput(FALSE); + LLUICtrl::onFocusReceived(); + updateAllowingLanguageInput(); +} - LLUICtrl::onFocusLost(); +void LLLineEditor::onFocusLost() +{ + // The call to updateAllowLanguageInput() + // when loosing the keyboard focus *may* + // indirectly invoke handleUnicodeCharHere(), + // so it must be called before onCommit. + updateAllowingLanguageInput(); if( mCommitOnFocusLost && mText.getString() != mPrevText) { @@ -241,6 +256,8 @@ void LLLineEditor::onFocusLost() } getWindow()->showCursorFromMouseMove(); + + LLUICtrl::onFocusLost(); } void LLLineEditor::onCommit() @@ -301,6 +318,7 @@ void LLLineEditor::setEnabled(BOOL enabled) { mReadOnly = !enabled; setTabStop(!mReadOnly); + updateAllowingLanguageInput(); } @@ -308,7 +326,6 @@ 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) @@ -337,13 +354,13 @@ void LLLineEditor::setText(const LLStringExplicit &new_text) BOOL allSelected = (len > 0) && (( mSelectionStart == 0 && mSelectionEnd == len ) || ( mSelectionStart == len && mSelectionEnd == 0 )); + // Do safe truncation so we don't split multi-byte characters LLString truncated_utf8 = new_text; if (truncated_utf8.size() > (U32)mMaxLengthBytes) - { - utf8str_truncate(truncated_utf8, mMaxLengthBytes); + { + truncated_utf8 = utf8str_truncate(new_text, mMaxLengthBytes); } mText.assign(truncated_utf8); - mText.truncate(mMaxLengthChars); if (allSelected) { @@ -735,17 +752,12 @@ void LLLineEditor::addChar(const llwchar uni_char) mText.erase(getCursor(), 1); } - S32 length_chars = mText.length(); - S32 cur_bytes = mText.getString().size();; + 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; - } + // Check byte length limit if ((new_bytes + cur_bytes) > mMaxLengthBytes) { allow_char = FALSE; @@ -794,6 +806,12 @@ void LLLineEditor::setSelection(S32 start, S32 end) setCursor(start); } +void LLLineEditor::setDrawAsterixes(BOOL b) +{ + mDrawAsterixes = b; + updateAllowingLanguageInput(); +} + S32 LLLineEditor::prevWordPos(S32 cursorPos) const { const LLWString& wtext = mText.getWString(); @@ -1022,13 +1040,11 @@ void LLLineEditor::paste() // Insert the string - //check to see that the size isn't going to be larger than the - //max number of characters or bytes + // Check to see that the size isn't going to be larger than the max number of 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) ) - { + { // Doesn't all fit llwchar current_symbol = clean_string[0]; U32 wchars_that_fit = 0; U32 total_bytes = wchar_utf8_length(current_symbol); @@ -1043,20 +1059,13 @@ void LLLineEditor::paste() current_symbol = clean_string[++wchars_that_fit]; total_bytes += wchar_utf8_length(current_symbol); } - + // Truncate the clean string at the limit of what will fit 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())); + setCursor( getCursor() + (S32)clean_string.length() ); deselect(); // Validate new string and rollback the if needed. @@ -1523,6 +1532,41 @@ void LLLineEditor::draw() } LLColor4 label_color = mTentativeFgColor; + if (hasPreeditString()) + { + // Draw preedit markers. This needs to be before drawing letters. + for (U32 i = 0; i < mPreeditStandouts.size(); i++) + { + const S32 preedit_left = mPreeditPositions[i]; + const S32 preedit_right = mPreeditPositions[i + 1]; + if (preedit_right > mScrollHPos) + { + S32 preedit_pixels_left = findPixelNearestPos(llmax(preedit_left, mScrollHPos) - getCursor()); + S32 preedit_pixels_right = llmin(findPixelNearestPos(preedit_right - getCursor()), background.mRight); + if (preedit_pixels_left >= background.mRight) + { + break; + } + if (mPreeditStandouts[i]) + { + gl_rect_2d(preedit_pixels_left + PREEDIT_STANDOUT_GAP, + background.mBottom + PREEDIT_STANDOUT_POSITION, + preedit_pixels_right - PREEDIT_STANDOUT_GAP - 1, + background.mBottom + PREEDIT_STANDOUT_POSITION - PREEDIT_STANDOUT_THICKNESS, + (text_color * PREEDIT_STANDOUT_BRIGHTNESS + bg_color * (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.0f)); + } + else + { + gl_rect_2d(preedit_pixels_left + PREEDIT_MARKER_GAP, + background.mBottom + PREEDIT_MARKER_POSITION, + preedit_pixels_right - PREEDIT_MARKER_GAP - 1, + background.mBottom + PREEDIT_MARKER_POSITION - PREEDIT_MARKER_THICKNESS, + (text_color * PREEDIT_MARKER_BRIGHTNESS + bg_color * (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.0f)); + } + } + } + } + S32 rendered_text = 0; F32 rendered_pixels_right = (F32)mMinHPixels; F32 text_bottom = (F32)background.mBottom + (F32)UI_LINEEDITOR_V_PAD; @@ -1677,7 +1721,7 @@ void LLLineEditor::draw() // Returns the local screen space X coordinate associated with the text cursor position. -S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) +S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const { S32 dpos = getCursor() - mScrollHPos + cursor_offset; S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mMinHPixels; @@ -1715,7 +1759,7 @@ void LLLineEditor::setFocus( BOOL new_state ) if (!new_state) { - getWindow()->allowLanguageTextInput(FALSE); + getWindow()->allowLanguageTextInput(this, FALSE); } @@ -1757,7 +1801,7 @@ void LLLineEditor::setFocus( BOOL new_state ) // fine on 1.15.0.2, since all prevalidate func reject any // non-ASCII characters. I'm not sure on future versions, // however. - getWindow()->allowLanguageTextInput(mPrevalidateFunc == NULL); + getWindow()->allowLanguageTextInput(this, mPrevalidateFunc == NULL); } } @@ -1776,6 +1820,12 @@ void LLLineEditor::setRect(const LLRect& rect) } } +void LLLineEditor::setPrevalidate(BOOL (*func)(const LLWString &)) +{ + mPrevalidateFunc = func; + updateAllowingLanguageInput(); +} + // 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. @@ -2336,6 +2386,239 @@ BOOL LLLineEditor::setLabelArg( const LLString& key, const LLStringExplicit& tex return TRUE; } + +void LLLineEditor::updateAllowingLanguageInput() +{ + // Allow Language Text Input only when this LineEditor has + // no prevalidate function attached (as long as other criteria + // common to LLTextEditor). This criterion works + // fine on 1.15.0.2, since all prevalidate func reject any + // non-ASCII characters. I'm not sure on future versions, + // however... + if (hasFocus() && !mReadOnly && !mDrawAsterixes && mPrevalidateFunc == NULL) + { + getWindow()->allowLanguageTextInput(this, TRUE); + } + else + { + getWindow()->allowLanguageTextInput(this, FALSE); + } +} + +BOOL LLLineEditor::hasPreeditString() const +{ + return (mPreeditPositions.size() > 1); +} + +void LLLineEditor::resetPreedit() +{ + if (hasPreeditString()) + { + const S32 preedit_pos = mPreeditPositions.front(); + mText.erase(preedit_pos, mPreeditPositions.back() - preedit_pos); + mText.insert(preedit_pos, mPreeditOverwrittenWString); + setCursor(preedit_pos); + + mPreeditWString.clear(); + mPreeditOverwrittenWString.clear(); + mPreeditPositions.clear(); + + mKeystrokeTimer.reset(); + if (mKeystrokeCallback) + { + mKeystrokeCallback(this, mCallbackUserData); + } + } +} + +void LLLineEditor::updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) +{ + // Just in case. + if (mReadOnly) + { + return; + } + + if (hasSelection()) + { + if (hasPreeditString()) + { + llwarns << "Preedit and selection!" << llendl; + deselect(); + } + else + { + deleteSelection(); + } + } + + S32 insert_preedit_at = getCursor(); + if (hasPreeditString()) + { + insert_preedit_at = mPreeditPositions.front(); + //mText.replace(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at, mPreeditOverwrittenWString); + mText.erase(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at); + mText.insert(insert_preedit_at, mPreeditOverwrittenWString); + } + + mPreeditWString = preedit_string; + mPreeditPositions.resize(preedit_segment_lengths.size() + 1); + S32 position = insert_preedit_at; + for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++) + { + mPreeditPositions[i] = position; + position += preedit_segment_lengths[i]; + } + mPreeditPositions.back() = position; + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString.assign( LLWString( mText, insert_preedit_at, mPreeditWString.length() ) ); + mText.erase(insert_preedit_at, mPreeditWString.length()); + } + else + { + mPreeditOverwrittenWString.clear(); + } + mText.insert(insert_preedit_at, mPreeditWString); + + mPreeditStandouts = preedit_standouts; + + setCursor(position); + setCursor(mPreeditPositions.front() + caret_position); + + // Update of the preedit should be caused by some key strokes. + mKeystrokeTimer.reset(); + if( mKeystrokeCallback ) + { + mKeystrokeCallback( this, mCallbackUserData ); + } +} + +BOOL LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const +{ + if (control) + { + LLRect control_rect_screen; + localRectToScreen(mRect, &control_rect_screen); + LLUI::screenRectToGL(control_rect_screen, control); + } + + S32 preedit_left_column, preedit_right_column; + if (hasPreeditString()) + { + preedit_left_column = mPreeditPositions.front(); + preedit_right_column = mPreeditPositions.back(); + } + else + { + preedit_left_column = preedit_right_column = getCursor(); + } + if (preedit_right_column < mScrollHPos) + { + // This should not occure... + return FALSE; + } + + const S32 query = (query_offset >= 0 ? preedit_left_column + query_offset : getCursor()); + if (query < mScrollHPos || query < preedit_left_column || query > preedit_right_column) + { + return FALSE; + } + + if (coord) + { + S32 query_local = findPixelNearestPos(query - getCursor()); + S32 query_screen_x, query_screen_y; + localPointToScreen(query_local, mRect.getHeight() / 2, &query_screen_x, &query_screen_y); + LLUI::screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); + } + + if (bounds) + { + S32 preedit_left_local = findPixelNearestPos(llmax(preedit_left_column, mScrollHPos) - getCursor()); + S32 preedit_right_local = llmin(findPixelNearestPos(preedit_right_column - getCursor()), mRect.getWidth() - mBorderThickness); + if (preedit_left_local > preedit_right_local) + { + // Is this condition possible? + preedit_right_local = preedit_left_local; + } + + LLRect preedit_rect_local(preedit_left_local, mRect.getHeight(), preedit_right_local, 0); + LLRect preedit_rect_screen; + localRectToScreen(preedit_rect_local, &preedit_rect_screen); + LLUI::screenRectToGL(preedit_rect_screen, bounds); + } + + return TRUE; +} + +void LLLineEditor::getPreeditRange(S32 *position, S32 *length) const +{ + if (hasPreeditString()) + { + *position = mPreeditPositions.front(); + *length = mPreeditPositions.back() - mPreeditPositions.front(); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLLineEditor::getSelectionRange(S32 *position, S32 *length) const +{ + if (hasSelection()) + { + *position = llmin(mSelectionStart, mSelectionEnd); + *length = llabs(mSelectionStart - mSelectionEnd); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLLineEditor::markAsPreedit(S32 position, S32 length) +{ + deselect(); + setCursor(position); + if (hasPreeditString()) + { + llwarns << "markAsPreedit invoked when hasPreeditString is true." << llendl; + } + mPreeditWString.assign( LLWString( mText.getWString(), position, length ) ); + if (length > 0) + { + mPreeditPositions.resize(2); + mPreeditPositions[0] = position; + mPreeditPositions[1] = position + length; + mPreeditStandouts.resize(1); + mPreeditStandouts[0] = FALSE; + } + else + { + mPreeditPositions.clear(); + mPreeditStandouts.clear(); + } + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString = mPreeditWString; + } + else + { + mPreeditOverwrittenWString.clear(); + } +} + +S32 LLLineEditor::getPreeditFontSize() const +{ + return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); +} + + LLSearchEditor::LLSearchEditor(const LLString& name, const LLRect& rect, S32 max_length_bytes, diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index f1b9fbe33e..a019353856 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -53,6 +53,8 @@ #include "lluistring.h" #include "llviewborder.h" +#include "llpreeditor.h" + class LLFontGL; class LLLineEditorRollback; class LLButton; @@ -63,7 +65,7 @@ typedef BOOL (*LLLinePrevalidateFunc)(const LLWString &wstr); // Classes // class LLLineEditor -: public LLUICtrl, public LLEditMenuHandler +: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor { friend class LLLineEditorRollback; @@ -120,6 +122,7 @@ public: // view overrides virtual void draw(); virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE); + virtual void onFocusReceived(); virtual void onFocusLost(); virtual void setEnabled(BOOL enabled); @@ -146,7 +149,7 @@ public: const LLWString& getWText() const { return mText.getWString(); } S32 getLength() const { return mText.length(); } - S32 getCursor() { return mCursorPos; } + S32 getCursor() const { return mCursorPos; } void setCursor( S32 pos ); void setCursorToEnd(); @@ -177,13 +180,13 @@ public: void setIgnoreTab(BOOL b) { mIgnoreTab = b; } void setPassDelete(BOOL b) { mPassDelete = b; } - void setDrawAsterixes(BOOL b) { mDrawAsterixes = b; } + void setDrawAsterixes(BOOL b); // get the cursor position of the beginning/end of the prev/next word in the text S32 prevWordPos(S32 cursorPos) const; S32 nextWordPos(S32 cursorPos) const; - BOOL hasSelection() { return (mSelectionStart != mSelectionEnd); } + BOOL hasSelection() const { return (mSelectionStart != mSelectionEnd); } void startSelection(); void endSelection(); void extendSelection(S32 new_cursor_pos); @@ -199,7 +202,7 @@ public: static BOOL isPartOfWord(llwchar c); // Prevalidation controls which keystrokes can affect the editor - void setPrevalidate( BOOL (*func)(const LLWString &) ) { mPrevalidateFunc = func; } + void setPrevalidate( BOOL (*func)(const LLWString &) ); static BOOL prevalidateFloat(const LLWString &str ); static BOOL prevalidateInt(const LLWString &str ); static BOOL prevalidatePositiveS32(const LLWString &str); @@ -221,7 +224,7 @@ protected: void addChar(const llwchar c); void setCursorAtLocalPos(S32 local_mouse_x); - S32 findPixelNearestPos(S32 cursor_offset = 0); + S32 findPixelNearestPos(S32 cursor_offset = 0) const; void reportBadKeystroke(); BOOL handleSpecialKey(KEY key, MASK mask); @@ -230,6 +233,19 @@ protected: S32 handleCommitKey(KEY key, MASK mask); protected: + void updateAllowingLanguageInput(); + BOOL hasPreeditString() const; + // Implementation (overrides) of LLPreeditor + virtual void resetPreedit(); + virtual void updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position); + virtual void markAsPreedit(S32 position, S32 length); + virtual void getPreeditRange(S32 *position, S32 *length) const; + virtual void getSelectionRange(S32 *position, S32 *length) const; + virtual BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const; + virtual S32 getPreeditFontSize() const; + +protected: LLUIString mText; // The string being edited. LLString mPrevText; // Saved string for 'ESC' revert LLUIString mLabel; // text label that is visible when no user text provided @@ -241,8 +257,7 @@ protected: LLViewBorder* mBorder; const LLFontGL* mGLFont; - S32 mMaxLengthChars; // Max number of characters - S32 mMaxLengthBytes; // Max length of the UTF8 string. + S32 mMaxLengthBytes; // Max length of the UTF8 string in bytes S32 mCursorPos; // I-beam is just after the mCursorPos-th character. S32 mScrollHPos; // Horizontal offset from the start of mText. Used for scrolling. LLFrameTimer mScrollTimer; @@ -288,6 +303,11 @@ protected: BOOL mPassDelete; BOOL mReadOnly; + + LLWString mPreeditWString; + LLWString mPreeditOverwrittenWString; + std::vector<S32> mPreeditPositions; + LLPreeditor::standouts_t mPreeditStandouts; }; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index d150f8954e..46f9f515d7 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -632,7 +632,7 @@ public: }; LLMenuItemSeparatorGL::LLMenuItemSeparatorGL( const LLString &name ) : - LLMenuItemGL( SEPARATOR_NAME, SEPARATOR_LABEL ) + LLMenuItemGL( name, SEPARATOR_LABEL ) { } @@ -2832,7 +2832,7 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa while(1) { // skip separators and disabled items - if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getName() != SEPARATOR_NAME) + if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getType() != SEPARATOR_NAME) { if (cur_item) { diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp index efd42455e5..8bd7b1509f 100644 --- a/indra/llui/lltextbox.cpp +++ b/indra/llui/lltextbox.cpp @@ -174,7 +174,7 @@ BOOL LLTextBox::handleHover(S32 x, S32 y, MASK mask) mHasHover = TRUE; // This should be set every frame during a hover. return TRUE; } - return FALSE; + return LLView::handleHover(x,y,mask); } void LLTextBox::setText(const LLStringExplicit& text) diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index d08997c3ed..5c8b7c7281 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -79,6 +79,15 @@ const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds const S32 CURSOR_THICKNESS = 2; const S32 SPACES_PER_TAB = 4; +const F32 PREEDIT_MARKER_BRIGHTNESS = 0.4f; +const S32 PREEDIT_MARKER_GAP = 1; +const S32 PREEDIT_MARKER_POSITION = 2; +const S32 PREEDIT_MARKER_THICKNESS = 1; +const F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f; +const S32 PREEDIT_STANDOUT_GAP = 1; +const S32 PREEDIT_STANDOUT_POSITION = 2; +const S32 PREEDIT_STANDOUT_THICKNESS = 2; + LLColor4 LLTextEditor::mLinkColor = LLColor4::blue; void (* LLTextEditor::mURLcallback)(const char*) = NULL; bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&) = NULL; @@ -274,14 +283,14 @@ private: LLTextEditor::LLTextEditor( const LLString& name, const LLRect& rect, - S32 max_length, + S32 max_length, // In bytes const LLString &default_text, const LLFontGL* font, BOOL allow_embedded_items) : LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ), mTextIsUpToDate(TRUE), - mMaxTextLength( max_length ), + mMaxTextByteLength( max_length ), mBaseDocIsPristine(TRUE), mPristineCmd( NULL ), mLastCmd( NULL ), @@ -510,13 +519,27 @@ BOOL LLTextEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); } -void LLTextEditor::truncate() +BOOL LLTextEditor::truncate() { - if (mWText.size() > (size_t)mMaxTextLength) - { - LLWString::truncate(mWText, mMaxTextLength); - mTextIsUpToDate = FALSE; + BOOL did_truncate = FALSE; + + // First rough check - if we're less than 1/4th the size, we're OK + if (mWText.size() >= (size_t) (mMaxTextByteLength / 4)) + { + // Have to check actual byte size + S32 utf8_byte_size = wstring_utf8_length( mWText ); + if ( utf8_byte_size > mMaxTextByteLength ) + { + // Truncate safely in UTF-8 + std::string temp_utf8_text = wstring_to_utf8str( mWText ); + temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength ); + mWText = utf8str_to_wstring( temp_utf8_text ); + mTextIsUpToDate = FALSE; + did_truncate = TRUE; + } } + + return did_truncate; } void LLTextEditor::setText(const LLStringExplicit &utf8str) @@ -750,12 +773,12 @@ S32 LLTextEditor::nextWordPos(S32 cursorPos) const return cursorPos; } -S32 LLTextEditor::getLineCount() +S32 LLTextEditor::getLineCount() const { return mLineStartList.size(); } -S32 LLTextEditor::getLineStart( S32 line ) +S32 LLTextEditor::getLineStart( S32 line ) const { S32 num_lines = getLineCount(); if (num_lines == 0) @@ -1604,7 +1627,7 @@ void LLTextEditor::removeChar() // Add a single character to the text S32 LLTextEditor::addChar(S32 pos, llwchar wc) { - if ((S32)mWText.length() == mMaxTextLength) + if ( (wstring_utf8_length( mWText ) + wchar_utf8_length( wc )) >= mMaxTextByteLength) { make_ui_sound("UISndBadKeystroke"); return 0; @@ -2490,11 +2513,16 @@ void LLTextEditor::redo() } } +void LLTextEditor::onFocusReceived() +{ + LLUICtrl::onFocusReceived(); + updateAllowingLanguageInput(); +} // virtual, from LLView void LLTextEditor::onFocusLost() { - getWindow()->allowLanguageTextInput(FALSE); + updateAllowingLanguageInput(); // Route menu back to the default if( gEditMenuHandler == this ) @@ -2521,6 +2549,7 @@ void LLTextEditor::setEnabled(BOOL enabled) { mReadOnly = read_only; updateSegments(); + updateAllowingLanguageInput(); } } @@ -2825,6 +2854,100 @@ void LLTextEditor::drawCursor() } } +void LLTextEditor::drawPreeditMarker() +{ + if (!hasPreeditString()) + { + return; + } + + const llwchar *text = mWText.c_str(); + const S32 text_len = getLength(); + const S32 num_lines = getLineCount(); + + S32 cur_line = mScrollbar->getDocPos(); + if (cur_line >= num_lines) + { + return; + } + + const S32 line_height = llround( mGLFont->getLineHeight() ); + + S32 line_start = getLineStart(cur_line); + S32 line_y = mTextRect.mTop - line_height; + while((mTextRect.mBottom <= line_y) && (num_lines > cur_line)) + { + S32 next_start = -1; + S32 line_end = text_len; + + if ((cur_line + 1) < num_lines) + { + next_start = getLineStart(cur_line + 1); + line_end = next_start; + } + if ( text[line_end-1] == '\n' ) + { + --line_end; + } + + // Does this line contain preedits? + if (line_start >= mPreeditPositions.back()) + { + // We have passed the preedits. + break; + } + if (line_end > mPreeditPositions.front()) + { + for (U32 i = 0; i < mPreeditStandouts.size(); i++) + { + S32 left = mPreeditPositions[i]; + S32 right = mPreeditPositions[i + 1]; + if (right <= line_start || left >= line_end) + { + continue; + } + + S32 preedit_left = mTextRect.mLeft; + if (left > line_start) + { + preedit_left += mGLFont->getWidth(text, line_start, left - line_start, mAllowEmbeddedItems); + } + S32 preedit_right = mTextRect.mLeft; + if (right < line_end) + { + preedit_right += mGLFont->getWidth(text, line_start, right - line_start, mAllowEmbeddedItems); + } + else + { + preedit_right += mGLFont->getWidth(text, line_start, line_end - line_start, mAllowEmbeddedItems); + } + + if (mPreeditStandouts[i]) + { + gl_rect_2d(preedit_left + PREEDIT_STANDOUT_GAP, + line_y + PREEDIT_STANDOUT_POSITION, + preedit_right - PREEDIT_STANDOUT_GAP - 1, + line_y + PREEDIT_STANDOUT_POSITION - PREEDIT_STANDOUT_THICKNESS, + (mCursorColor * PREEDIT_STANDOUT_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.0f)); + } + else + { + gl_rect_2d(preedit_left + PREEDIT_MARKER_GAP, + line_y + PREEDIT_MARKER_POSITION, + preedit_right - PREEDIT_MARKER_GAP - 1, + line_y + PREEDIT_MARKER_POSITION - PREEDIT_MARKER_THICKNESS, + (mCursorColor * PREEDIT_MARKER_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.0f)); + } + } + } + + // move down one line + line_y -= line_height; + line_start = next_start; + cur_line++; + } +} + void LLTextEditor::drawText() { @@ -3025,6 +3148,7 @@ void LLTextEditor::draw() drawBackground(); drawSelectionBackground(); + drawPreeditMarker(); drawText(); drawCursor(); @@ -3067,10 +3191,10 @@ void LLTextEditor::setFocus( BOOL new_state ) // Don't change anything if the focus state didn't change if (new_state == old_state) return; - // Notify early if we are loosing focus. + // Notify early if we are losing focus. if (!new_state) { - getWindow()->allowLanguageTextInput(FALSE); + getWindow()->allowLanguageTextInput(this, FALSE); } LLUICtrl::setFocus( new_state ); @@ -3093,12 +3217,6 @@ void LLTextEditor::setFocus( BOOL new_state ) endSelection(); } - - // Notify late if we are gaining focus. - if (new_state && !mReadOnly) - { - getWindow()->allowLanguageTextInput(TRUE); - } } BOOL LLTextEditor::acceptsTextInput() const @@ -3540,22 +3658,20 @@ void LLTextEditor::removeTextFromEnd(S32 num_chars) S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr) { - S32 len = mWText.length(); - S32 s_len = wstr.length(); - S32 new_len = len + s_len; - if( new_len > mMaxTextLength ) - { - new_len = mMaxTextLength; + S32 old_len = mWText.length(); // length() returns character length + S32 insert_len = wstr.length(); + mWText.insert(pos, wstr); + mTextIsUpToDate = FALSE; + + if ( truncate() ) + { // The user's not getting everything he's hoping for make_ui_sound("UISndBadKeystroke"); + insert_len = mWText.length() - old_len; } - mWText.insert(pos, wstr); - mTextIsUpToDate = FALSE; - truncate(); - - return new_len - len; + return insert_len; } S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length) @@ -3920,7 +4036,7 @@ BOOL LLTextEditor::importBuffer(const LLString& buffer ) return FALSE; } - if( text_len > mMaxTextLength ) + if( text_len > mMaxTextByteLength ) { llwarns << "Invalid Linden text length: " << text_len << llendl; return FALSE; @@ -4281,3 +4397,262 @@ BOOL LLTextEditor::findHTML(const LLString &line, S32 *begin, S32 *end) } return matched; } + + + +void LLTextEditor::updateAllowingLanguageInput() +{ + if (hasFocus() && !mReadOnly) + { + getWindow()->allowLanguageTextInput(this, TRUE); + } + else + { + getWindow()->allowLanguageTextInput(this, FALSE); + } +} + +// Preedit is managed off the undo/redo command stack. + +BOOL LLTextEditor::hasPreeditString() const +{ + return (mPreeditPositions.size() > 1); +} + +void LLTextEditor::resetPreedit() +{ + if (hasPreeditString()) + { + mCursorPos = mPreeditPositions.front(); + removeStringNoUndo(mCursorPos, mPreeditPositions.back() - mCursorPos); + insertStringNoUndo(mCursorPos, mPreeditOverwrittenWString); + + mPreeditWString.clear(); + mPreeditOverwrittenWString.clear(); + mPreeditPositions.clear(); + + updateLineStartList(); + setCursorPos(mCursorPos); + // updateScrollFromCursor(); + } +} + +void LLTextEditor::updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) +{ + // Just in case. + if (mReadOnly) + { + return; + } + + if (hasSelection()) + { + if (hasPreeditString()) + { + llwarns << "Preedit and selection!" << llendl; + deselect(); + } + else + { + deleteSelection(TRUE); + } + } + + getWindow()->hideCursorUntilMouseMove(); + + S32 insert_preedit_at = mCursorPos; + if (hasPreeditString()) + { + insert_preedit_at = mPreeditPositions.front(); + removeStringNoUndo(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at); + insertStringNoUndo(insert_preedit_at, mPreeditOverwrittenWString); + } + + mPreeditWString = preedit_string; + mPreeditPositions.resize(preedit_segment_lengths.size() + 1); + S32 position = insert_preedit_at; + for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++) + { + mPreeditPositions[i] = position; + position += preedit_segment_lengths[i]; + } + mPreeditPositions.back() = position; + + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString = getWSubString(insert_preedit_at, mPreeditWString.length()); + removeStringNoUndo(insert_preedit_at, mPreeditWString.length()); + } + else + { + mPreeditOverwrittenWString.clear(); + } + insertStringNoUndo(insert_preedit_at, mPreeditWString); + + mPreeditStandouts = preedit_standouts; + + updateLineStartList(); + setCursorPos(insert_preedit_at + caret_position); + // updateScrollFromCursor(); + + // Update of the preedit should be caused by some key strokes. + mKeystrokeTimer.reset(); +} + +BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const +{ + if (control) + { + LLRect control_rect_screen; + localRectToScreen(mTextRect, &control_rect_screen); + LLUI::screenRectToGL(control_rect_screen, control); + } + + S32 preedit_left_position, preedit_right_position; + if (hasPreeditString()) + { + preedit_left_position = mPreeditPositions.front(); + preedit_right_position = mPreeditPositions.back(); + } + else + { + preedit_left_position = preedit_right_position = mCursorPos; + } + + const S32 query = (query_offset >= 0 ? preedit_left_position + query_offset : mCursorPos); + if (query < preedit_left_position || query > preedit_right_position) + { + return FALSE; + } + + const S32 first_visible_line = mScrollbar->getDocPos(); + if (query < getLineStart(first_visible_line)) + { + return FALSE; + } + + S32 current_line = first_visible_line; + S32 current_line_start, current_line_end; + for (;;) + { + current_line_start = getLineStart(current_line); + current_line_end = getLineStart(current_line + 1); + if (query >= current_line_start && query < current_line_end) + { + break; + } + if (current_line_start == current_line_end) + { + // We have reached on the last line. The query position must be here. + break; + } + current_line++; + } + + const llwchar * const text = mWText.c_str(); + const S32 line_height = llround(mGLFont->getLineHeight()); + + if (coord) + { + const S32 query_x = mTextRect.mLeft + mGLFont->getWidth(text, current_line_start, query - current_line_start, mAllowEmbeddedItems); + const S32 query_y = mTextRect.mTop - (current_line - first_visible_line) * line_height - line_height / 2; + S32 query_screen_x, query_screen_y; + localPointToScreen(query_x, query_y, &query_screen_x, &query_screen_y); + LLUI::screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); + } + + if (bounds) + { + S32 preedit_left = mTextRect.mLeft; + if (preedit_left_position > current_line_start) + { + preedit_left += mGLFont->getWidth(text, current_line_start, preedit_left_position - current_line_start, mAllowEmbeddedItems); + } + + S32 preedit_right = mTextRect.mLeft; + if (preedit_right_position < current_line_end) + { + preedit_right += mGLFont->getWidth(text, current_line_start, preedit_right_position - current_line_start, mAllowEmbeddedItems); + } + else + { + preedit_right += mGLFont->getWidth(text, current_line_start, current_line_end - current_line_start, mAllowEmbeddedItems); + } + + const S32 preedit_top = mTextRect.mTop - (current_line - first_visible_line) * line_height; + const S32 preedit_bottom = preedit_top - line_height; + + const LLRect preedit_rect_local(preedit_left, preedit_top, preedit_right, preedit_bottom); + LLRect preedit_rect_screen; + localRectToScreen(preedit_rect_local, &preedit_rect_screen); + LLUI::screenRectToGL(preedit_rect_screen, bounds); + } + + return TRUE; +} + +void LLTextEditor::getSelectionRange(S32 *position, S32 *length) const +{ + if (hasSelection()) + { + *position = llmin(mSelectionStart, mSelectionEnd); + *length = llabs(mSelectionStart - mSelectionEnd); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLTextEditor::getPreeditRange(S32 *position, S32 *length) const +{ + if (hasPreeditString()) + { + *position = mPreeditPositions.front(); + *length = mPreeditPositions.back() - mPreeditPositions.front(); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLTextEditor::markAsPreedit(S32 position, S32 length) +{ + deselect(); + setCursorPos(position); + if (hasPreeditString()) + { + llwarns << "markAsPreedit invoked when hasPreeditString is true." << llendl; + } + mPreeditWString = LLWString( mWText, position, length ); + if (length > 0) + { + mPreeditPositions.resize(2); + mPreeditPositions[0] = position; + mPreeditPositions[1] = position + length; + mPreeditStandouts.resize(1); + mPreeditStandouts[0] = FALSE; + } + else + { + mPreeditPositions.clear(); + mPreeditStandouts.clear(); + } + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString = mPreeditWString; + } + else + { + mPreeditOverwrittenWString.clear(); + } +} + +S32 LLTextEditor::getPreeditFontSize() const +{ + return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); +} diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index d38accca8f..7049de7b89 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -43,6 +43,8 @@ #include "lleditmenuhandler.h" #include "lldarray.h" +#include "llpreeditor.h" + class LLFontGL; class LLScrollbar; class LLViewBorder; @@ -64,7 +66,7 @@ const S32 MAX_EMBEDDED_ITEMS = LAST_EMBEDDED_CHAR - FIRST_EMBEDDED_CHAR + 1; class LLTextSegment; class LLTextCmd; -class LLTextEditor : public LLUICtrl, LLEditMenuHandler +class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor { friend class LLTextCmd; public: @@ -104,6 +106,7 @@ public: // view overrides virtual void reshape(S32 width, S32 height, BOOL called_from_parent); virtual void draw(); + virtual void onFocusReceived(); virtual void onFocusLost(); virtual void setEnabled(BOOL enabled); @@ -234,7 +237,8 @@ public: void setText(const LLStringExplicit &utf8str); void setWText(const LLWString &wtext); - S32 getMaxLength() const { return mMaxTextLength; } + // Returns byte length limit + S32 getMaxLength() const { return mMaxTextByteLength; } // Change cursor void startOfLine(); @@ -259,6 +263,7 @@ protected: void drawCursor(); void drawText(); void drawClippedSegment(const LLWString &wtext, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyle& color, F32* right_x); + void drawPreeditMarker(); void updateLineStartList(S32 startpos = 0); void updateScrollFromCursor(); @@ -267,7 +272,7 @@ protected: void pruneSegments(); void assignEmbedded(const LLString &s); - void truncate(); + BOOL truncate(); // Returns true if truncation occurs static BOOL isPartOfWord(llwchar c); @@ -291,7 +296,7 @@ protected: BOOL handleControlKey(const KEY key, const MASK mask); BOOL handleEditKey(const KEY key, const MASK mask); - BOOL hasSelection() { return (mSelectionStart !=mSelectionEnd); } + BOOL hasSelection() const { return (mSelectionStart !=mSelectionEnd); } BOOL selectionContainsLineBreaks(); void startSelection(); void endSelection(); @@ -300,8 +305,8 @@ protected: S32 prevWordPos(S32 cursorPos) const; S32 nextWordPos(S32 cursorPos) const; - S32 getLineCount(); - S32 getLineStart( S32 line ); + S32 getLineCount() const; + S32 getLineStart( S32 line ) const; void getLineAndOffset(S32 pos, S32* linep, S32* offsetp); S32 getPos(S32 line, S32 offset); @@ -338,6 +343,20 @@ protected: S32 removeStringNoUndo(S32 pos, S32 length); S32 overwriteCharNoUndo(S32 pos, llwchar wc); +protected: + void updateAllowingLanguageInput(); + BOOL hasPreeditString() const; + + // Overrides LLPreeditor + virtual void resetPreedit(); + virtual void updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position); + virtual void markAsPreedit(S32 position, S32 length); + virtual void getPreeditRange(S32 *position, S32 *length) const; + virtual void getSelectionRange(S32 *position, S32 *length) const; + virtual BOOL getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const; + virtual S32 getPreeditFontSize() const; + public: LLKeywords mKeywords; static LLColor4 mLinkColor; @@ -349,7 +368,7 @@ protected: mutable LLString mUTF8Text; mutable BOOL mTextIsUpToDate; - S32 mMaxTextLength; // Maximum length mText is allowed to be + S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes const LLFontGL* mGLFont; @@ -439,6 +458,11 @@ protected: BOOL mParseHTML; LLString mHTML; + + LLWString mPreeditWString; + LLWString mPreeditOverwrittenWString; + std::vector<S32> mPreeditPositions; + std::vector<BOOL> mPreeditStandouts; }; class LLTextSegment diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index 7af0d726cb..00a230dff3 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -1696,6 +1696,34 @@ LLVector2 LLUI::getWindowSize() } //static +void LLUI::screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y) +{ + *gl_x = llround((F32)screen_x * sGLScaleFactor.mV[VX]); + *gl_y = llround((F32)screen_y * sGLScaleFactor.mV[VY]); +} + +//static +void LLUI::glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y) +{ + *screen_x = llround((F32)gl_x / sGLScaleFactor.mV[VX]); + *screen_y = llround((F32)gl_y / sGLScaleFactor.mV[VY]); +} + +//static +void LLUI::screenRectToGL(const LLRect& screen, LLRect *gl) +{ + screenPointToGL(screen.mLeft, screen.mTop, &gl->mLeft, &gl->mTop); + screenPointToGL(screen.mRight, screen.mBottom, &gl->mRight, &gl->mBottom); +} + +//static +void LLUI::glRectToScreen(const LLRect& gl, LLRect *screen) +{ + glPointToScreen(gl.mLeft, gl.mTop, &screen->mLeft, &screen->mTop); + glPointToScreen(gl.mRight, gl.mBottom, &screen->mRight, &screen->mBottom); +} + +//static LLUUID LLUI::findAssetUUIDByName(const LLString &asset_name) { if(asset_name == LLString::null) return LLUUID::null; diff --git a/indra/llui/llui.h b/indra/llui/llui.h index b78b046a8c..b98f4d5de2 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -174,6 +174,10 @@ public: static void setLineWidth(F32 width); static LLUUID findAssetUUIDByName(const LLString& name); static LLVector2 getWindowSize(); + static void screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y); + static void glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y); + static void screenRectToGL(const LLRect& screen, LLRect *gl); + static void glRectToScreen(const LLRect& gl, LLRect *screen); static void setHtmlHelp(LLHtmlHelp* html_help); private: |