diff options
Diffstat (limited to 'linden/indra/llui/lltexteditor.cpp')
-rw-r--r-- | linden/indra/llui/lltexteditor.cpp | 539 |
1 files changed, 465 insertions, 74 deletions
diff --git a/linden/indra/llui/lltexteditor.cpp b/linden/indra/llui/lltexteditor.cpp index b4d693b..ca9ea0a 100644 --- a/linden/indra/llui/lltexteditor.cpp +++ b/linden/indra/llui/lltexteditor.cpp | |||
@@ -12,12 +12,12 @@ | |||
12 | * ("GPL"), unless you have obtained a separate licensing agreement | 12 | * ("GPL"), unless you have obtained a separate licensing agreement |
13 | * ("Other License"), formally executed by you and Linden Lab. Terms of | 13 | * ("Other License"), formally executed by you and Linden Lab. Terms of |
14 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | 14 | * the GPL can be found in doc/GPL-license.txt in this distribution, or |
15 | * online at http://secondlife.com/developers/opensource/gplv2 | 15 | * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 |
16 | * | 16 | * |
17 | * There are special exceptions to the terms and conditions of the GPL as | 17 | * There are special exceptions to the terms and conditions of the GPL as |
18 | * it is applied to this Source Code. View the full text of the exception | 18 | * it is applied to this Source Code. View the full text of the exception |
19 | * in the file doc/FLOSS-exception.txt in this software distribution, or | 19 | * in the file doc/FLOSS-exception.txt in this software distribution, or |
20 | * online at http://secondlife.com/developers/opensource/flossexception | 20 | * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception |
21 | * | 21 | * |
22 | * By copying, modifying or distributing this software, you acknowledge | 22 | * By copying, modifying or distributing this software, you acknowledge |
23 | * that you have read and understood your obligations described above, | 23 | * that you have read and understood your obligations described above, |
@@ -79,6 +79,15 @@ const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds | |||
79 | const S32 CURSOR_THICKNESS = 2; | 79 | const S32 CURSOR_THICKNESS = 2; |
80 | const S32 SPACES_PER_TAB = 4; | 80 | const S32 SPACES_PER_TAB = 4; |
81 | 81 | ||
82 | const F32 PREEDIT_MARKER_BRIGHTNESS = 0.4f; | ||
83 | const S32 PREEDIT_MARKER_GAP = 1; | ||
84 | const S32 PREEDIT_MARKER_POSITION = 2; | ||
85 | const S32 PREEDIT_MARKER_THICKNESS = 1; | ||
86 | const F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f; | ||
87 | const S32 PREEDIT_STANDOUT_GAP = 1; | ||
88 | const S32 PREEDIT_STANDOUT_POSITION = 2; | ||
89 | const S32 PREEDIT_STANDOUT_THICKNESS = 2; | ||
90 | |||
82 | LLColor4 LLTextEditor::mLinkColor = LLColor4::blue; | 91 | LLColor4 LLTextEditor::mLinkColor = LLColor4::blue; |
83 | void (* LLTextEditor::mURLcallback)(const char*) = NULL; | 92 | void (* LLTextEditor::mURLcallback)(const char*) = NULL; |
84 | bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&) = NULL; | 93 | bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&) = NULL; |
@@ -274,14 +283,14 @@ private: | |||
274 | LLTextEditor::LLTextEditor( | 283 | LLTextEditor::LLTextEditor( |
275 | const LLString& name, | 284 | const LLString& name, |
276 | const LLRect& rect, | 285 | const LLRect& rect, |
277 | S32 max_length, | 286 | S32 max_length, // In bytes |
278 | const LLString &default_text, | 287 | const LLString &default_text, |
279 | const LLFontGL* font, | 288 | const LLFontGL* font, |
280 | BOOL allow_embedded_items) | 289 | BOOL allow_embedded_items) |
281 | : | 290 | : |
282 | LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ), | 291 | LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ), |
283 | mTextIsUpToDate(TRUE), | 292 | mTextIsUpToDate(TRUE), |
284 | mMaxTextLength( max_length ), | 293 | mMaxTextByteLength( max_length ), |
285 | mBaseDocIsPristine(TRUE), | 294 | mBaseDocIsPristine(TRUE), |
286 | mPristineCmd( NULL ), | 295 | mPristineCmd( NULL ), |
287 | mLastCmd( NULL ), | 296 | mLastCmd( NULL ), |
@@ -293,6 +302,7 @@ LLTextEditor::LLTextEditor( | |||
293 | mOnScrollEndData( NULL ), | 302 | mOnScrollEndData( NULL ), |
294 | mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ), | 303 | mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ), |
295 | mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ), | 304 | mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ), |
305 | mDefaultColor( LLUI::sColorsGroup->getColor( "TextDefaultColor" ) ), | ||
296 | mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ), | 306 | mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ), |
297 | mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ), | 307 | mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ), |
298 | mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ), | 308 | mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ), |
@@ -301,9 +311,9 @@ LLTextEditor::LLTextEditor( | |||
301 | mWordWrap( FALSE ), | 311 | mWordWrap( FALSE ), |
302 | mTabToNextField( TRUE ), | 312 | mTabToNextField( TRUE ), |
303 | mCommitOnFocusLost( FALSE ), | 313 | mCommitOnFocusLost( FALSE ), |
304 | mTakesFocus( TRUE ), | ||
305 | mHideScrollbarForShortDocs( FALSE ), | 314 | mHideScrollbarForShortDocs( FALSE ), |
306 | mTakesNonScrollClicks( TRUE ), | 315 | mTakesNonScrollClicks( TRUE ), |
316 | mTrackBottom( TRUE ), | ||
307 | mAllowEmbeddedItems( allow_embedded_items ), | 317 | mAllowEmbeddedItems( allow_embedded_items ), |
308 | mAcceptCallingCardNames(FALSE), | 318 | mAcceptCallingCardNames(FALSE), |
309 | mHandleEditKeysDirectly( FALSE ), | 319 | mHandleEditKeysDirectly( FALSE ), |
@@ -510,13 +520,27 @@ BOOL LLTextEditor::isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); } | |||
510 | 520 | ||
511 | 521 | ||
512 | 522 | ||
513 | void LLTextEditor::truncate() | 523 | BOOL LLTextEditor::truncate() |
514 | { | 524 | { |
515 | if (mWText.size() > (size_t)mMaxTextLength) | 525 | BOOL did_truncate = FALSE; |
516 | { | 526 | |
517 | LLWString::truncate(mWText, mMaxTextLength); | 527 | // First rough check - if we're less than 1/4th the size, we're OK |
518 | mTextIsUpToDate = FALSE; | 528 | if (mWText.size() >= (size_t) (mMaxTextByteLength / 4)) |
529 | { | ||
530 | // Have to check actual byte size | ||
531 | S32 utf8_byte_size = wstring_utf8_length( mWText ); | ||
532 | if ( utf8_byte_size > mMaxTextByteLength ) | ||
533 | { | ||
534 | // Truncate safely in UTF-8 | ||
535 | std::string temp_utf8_text = wstring_to_utf8str( mWText ); | ||
536 | temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength ); | ||
537 | mWText = utf8str_to_wstring( temp_utf8_text ); | ||
538 | mTextIsUpToDate = FALSE; | ||
539 | did_truncate = TRUE; | ||
540 | } | ||
519 | } | 541 | } |
542 | |||
543 | return did_truncate; | ||
520 | } | 544 | } |
521 | 545 | ||
522 | void LLTextEditor::setText(const LLStringExplicit &utf8str) | 546 | void LLTextEditor::setText(const LLStringExplicit &utf8str) |
@@ -750,12 +774,12 @@ S32 LLTextEditor::nextWordPos(S32 cursorPos) const | |||
750 | return cursorPos; | 774 | return cursorPos; |
751 | } | 775 | } |
752 | 776 | ||
753 | S32 LLTextEditor::getLineCount() | 777 | S32 LLTextEditor::getLineCount() const |
754 | { | 778 | { |
755 | return mLineStartList.size(); | 779 | return mLineStartList.size(); |
756 | } | 780 | } |
757 | 781 | ||
758 | S32 LLTextEditor::getLineStart( S32 line ) | 782 | S32 LLTextEditor::getLineStart( S32 line ) const |
759 | { | 783 | { |
760 | S32 num_lines = getLineCount(); | 784 | S32 num_lines = getLineCount(); |
761 | if (num_lines == 0) | 785 | if (num_lines == 0) |
@@ -1118,46 +1142,42 @@ void LLTextEditor::selectAll() | |||
1118 | 1142 | ||
1119 | BOOL LLTextEditor::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) | 1143 | BOOL LLTextEditor::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) |
1120 | { | 1144 | { |
1121 | if (pointInView(x, y) && getVisible()) | 1145 | for ( child_list_const_iter_t child_it = getChildList()->begin(); |
1146 | child_it != getChildList()->end(); ++child_it) | ||
1122 | { | 1147 | { |
1123 | for ( child_list_const_iter_t child_it = getChildList()->begin(); | 1148 | LLView* viewp = *child_it; |
1124 | child_it != getChildList()->end(); ++child_it) | 1149 | S32 local_x = x - viewp->getRect().mLeft; |
1125 | { | 1150 | S32 local_y = y - viewp->getRect().mBottom; |
1126 | LLView* viewp = *child_it; | 1151 | if( viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) |
1127 | S32 local_x = x - viewp->getRect().mLeft; | ||
1128 | S32 local_y = y - viewp->getRect().mBottom; | ||
1129 | if( viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) | ||
1130 | { | ||
1131 | return TRUE; | ||
1132 | } | ||
1133 | } | ||
1134 | |||
1135 | if( mSegments.empty() ) | ||
1136 | { | 1152 | { |
1137 | return TRUE; | 1153 | return TRUE; |
1138 | } | 1154 | } |
1155 | } | ||
1139 | 1156 | ||
1140 | LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); | 1157 | if( mSegments.empty() ) |
1141 | if( cur_segment ) | 1158 | { |
1142 | { | 1159 | return TRUE; |
1143 | BOOL has_tool_tip = FALSE; | 1160 | } |
1144 | has_tool_tip = cur_segment->getToolTip( msg ); | ||
1145 | 1161 | ||
1146 | if( has_tool_tip ) | 1162 | LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); |
1147 | { | 1163 | if( cur_segment ) |
1148 | // Just use a slop area around the cursor | 1164 | { |
1149 | // Convert rect local to screen coordinates | 1165 | BOOL has_tool_tip = FALSE; |
1150 | S32 SLOP = 8; | 1166 | has_tool_tip = cur_segment->getToolTip( msg ); |
1151 | localPointToScreen( | 1167 | |
1152 | x - SLOP, y - SLOP, | 1168 | if( has_tool_tip ) |
1153 | &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); | 1169 | { |
1154 | sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP; | 1170 | // Just use a slop area around the cursor |
1155 | sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP; | 1171 | // Convert rect local to screen coordinates |
1156 | } | 1172 | S32 SLOP = 8; |
1173 | localPointToScreen( | ||
1174 | x - SLOP, y - SLOP, | ||
1175 | &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); | ||
1176 | sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP; | ||
1177 | sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP; | ||
1157 | } | 1178 | } |
1158 | return TRUE; | ||
1159 | } | 1179 | } |
1160 | return FALSE; | 1180 | return TRUE; |
1161 | } | 1181 | } |
1162 | 1182 | ||
1163 | BOOL LLTextEditor::handleScrollWheel(S32 x, S32 y, S32 clicks) | 1183 | BOOL LLTextEditor::handleScrollWheel(S32 x, S32 y, S32 clicks) |
@@ -1240,7 +1260,7 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) | |||
1240 | handled = TRUE; | 1260 | handled = TRUE; |
1241 | } | 1261 | } |
1242 | 1262 | ||
1243 | if (mTakesFocus) | 1263 | if (hasTabStop()) |
1244 | { | 1264 | { |
1245 | setFocus( TRUE ); | 1265 | setFocus( TRUE ); |
1246 | handled = TRUE; | 1266 | handled = TRUE; |
@@ -1413,11 +1433,6 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) | |||
1413 | 1433 | ||
1414 | if( !handled && mTakesNonScrollClicks) | 1434 | if( !handled && mTakesNonScrollClicks) |
1415 | { | 1435 | { |
1416 | if (mTakesFocus) | ||
1417 | { | ||
1418 | setFocus( TRUE ); | ||
1419 | } | ||
1420 | |||
1421 | setCursorAtLocalPos( x, y, FALSE ); | 1436 | setCursorAtLocalPos( x, y, FALSE ); |
1422 | deselect(); | 1437 | deselect(); |
1423 | 1438 | ||
@@ -1604,7 +1619,7 @@ void LLTextEditor::removeChar() | |||
1604 | // Add a single character to the text | 1619 | // Add a single character to the text |
1605 | S32 LLTextEditor::addChar(S32 pos, llwchar wc) | 1620 | S32 LLTextEditor::addChar(S32 pos, llwchar wc) |
1606 | { | 1621 | { |
1607 | if ((S32)mWText.length() == mMaxTextLength) | 1622 | if ( (wstring_utf8_length( mWText ) + wchar_utf8_length( wc )) >= mMaxTextByteLength) |
1608 | { | 1623 | { |
1609 | make_ui_sound("UISndBadKeystroke"); | 1624 | make_ui_sound("UISndBadKeystroke"); |
1610 | return 0; | 1625 | return 0; |
@@ -2490,11 +2505,16 @@ void LLTextEditor::redo() | |||
2490 | } | 2505 | } |
2491 | } | 2506 | } |
2492 | 2507 | ||
2508 | void LLTextEditor::onFocusReceived() | ||
2509 | { | ||
2510 | LLUICtrl::onFocusReceived(); | ||
2511 | updateAllowingLanguageInput(); | ||
2512 | } | ||
2493 | 2513 | ||
2494 | // virtual, from LLView | 2514 | // virtual, from LLView |
2495 | void LLTextEditor::onFocusLost() | 2515 | void LLTextEditor::onFocusLost() |
2496 | { | 2516 | { |
2497 | getWindow()->allowLanguageTextInput(FALSE); | 2517 | updateAllowingLanguageInput(); |
2498 | 2518 | ||
2499 | // Route menu back to the default | 2519 | // Route menu back to the default |
2500 | if( gEditMenuHandler == this ) | 2520 | if( gEditMenuHandler == this ) |
@@ -2521,6 +2541,7 @@ void LLTextEditor::setEnabled(BOOL enabled) | |||
2521 | { | 2541 | { |
2522 | mReadOnly = read_only; | 2542 | mReadOnly = read_only; |
2523 | updateSegments(); | 2543 | updateSegments(); |
2544 | updateAllowingLanguageInput(); | ||
2524 | } | 2545 | } |
2525 | } | 2546 | } |
2526 | 2547 | ||
@@ -2825,6 +2846,100 @@ void LLTextEditor::drawCursor() | |||
2825 | } | 2846 | } |
2826 | } | 2847 | } |
2827 | 2848 | ||
2849 | void LLTextEditor::drawPreeditMarker() | ||
2850 | { | ||
2851 | if (!hasPreeditString()) | ||
2852 | { | ||
2853 | return; | ||
2854 | } | ||
2855 | |||
2856 | const llwchar *text = mWText.c_str(); | ||
2857 | const S32 text_len = getLength(); | ||
2858 | const S32 num_lines = getLineCount(); | ||
2859 | |||
2860 | S32 cur_line = mScrollbar->getDocPos(); | ||
2861 | if (cur_line >= num_lines) | ||
2862 | { | ||
2863 | return; | ||
2864 | } | ||
2865 | |||
2866 | const S32 line_height = llround( mGLFont->getLineHeight() ); | ||
2867 | |||
2868 | S32 line_start = getLineStart(cur_line); | ||
2869 | S32 line_y = mTextRect.mTop - line_height; | ||
2870 | while((mTextRect.mBottom <= line_y) && (num_lines > cur_line)) | ||
2871 | { | ||
2872 | S32 next_start = -1; | ||
2873 | S32 line_end = text_len; | ||
2874 | |||
2875 | if ((cur_line + 1) < num_lines) | ||
2876 | { | ||
2877 | next_start = getLineStart(cur_line + 1); | ||
2878 | line_end = next_start; | ||
2879 | } | ||
2880 | if ( text[line_end-1] == '\n' ) | ||
2881 | { | ||
2882 | --line_end; | ||
2883 | } | ||
2884 | |||
2885 | // Does this line contain preedits? | ||
2886 | if (line_start >= mPreeditPositions.back()) | ||
2887 | { | ||
2888 | // We have passed the preedits. | ||
2889 | break; | ||
2890 | } | ||
2891 | if (line_end > mPreeditPositions.front()) | ||
2892 | { | ||
2893 | for (U32 i = 0; i < mPreeditStandouts.size(); i++) | ||
2894 | { | ||
2895 | S32 left = mPreeditPositions[i]; | ||
2896 | S32 right = mPreeditPositions[i + 1]; | ||
2897 | if (right <= line_start || left >= line_end) | ||
2898 | { | ||
2899 | continue; | ||
2900 | } | ||
2901 | |||
2902 | S32 preedit_left = mTextRect.mLeft; | ||
2903 | if (left > line_start) | ||
2904 | { | ||
2905 | preedit_left += mGLFont->getWidth(text, line_start, left - line_start, mAllowEmbeddedItems); | ||
2906 | } | ||
2907 | S32 preedit_right = mTextRect.mLeft; | ||
2908 | if (right < line_end) | ||
2909 | { | ||
2910 | preedit_right += mGLFont->getWidth(text, line_start, right - line_start, mAllowEmbeddedItems); | ||
2911 | } | ||
2912 | else | ||
2913 | { | ||
2914 | preedit_right += mGLFont->getWidth(text, line_start, line_end - line_start, mAllowEmbeddedItems); | ||
2915 | } | ||
2916 | |||
2917 | if (mPreeditStandouts[i]) | ||
2918 | { | ||
2919 | gl_rect_2d(preedit_left + PREEDIT_STANDOUT_GAP, | ||
2920 | line_y + PREEDIT_STANDOUT_POSITION, | ||
2921 | preedit_right - PREEDIT_STANDOUT_GAP - 1, | ||
2922 | line_y + PREEDIT_STANDOUT_POSITION - PREEDIT_STANDOUT_THICKNESS, | ||
2923 | (mCursorColor * PREEDIT_STANDOUT_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.0f)); | ||
2924 | } | ||
2925 | else | ||
2926 | { | ||
2927 | gl_rect_2d(preedit_left + PREEDIT_MARKER_GAP, | ||
2928 | line_y + PREEDIT_MARKER_POSITION, | ||
2929 | preedit_right - PREEDIT_MARKER_GAP - 1, | ||
2930 | line_y + PREEDIT_MARKER_POSITION - PREEDIT_MARKER_THICKNESS, | ||
2931 | (mCursorColor * PREEDIT_MARKER_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.0f)); | ||
2932 | } | ||
2933 | } | ||
2934 | } | ||
2935 | |||
2936 | // move down one line | ||
2937 | line_y -= line_height; | ||
2938 | line_start = next_start; | ||
2939 | cur_line++; | ||
2940 | } | ||
2941 | } | ||
2942 | |||
2828 | 2943 | ||
2829 | void LLTextEditor::drawText() | 2944 | void LLTextEditor::drawText() |
2830 | { | 2945 | { |
@@ -3025,6 +3140,7 @@ void LLTextEditor::draw() | |||
3025 | 3140 | ||
3026 | drawBackground(); | 3141 | drawBackground(); |
3027 | drawSelectionBackground(); | 3142 | drawSelectionBackground(); |
3143 | drawPreeditMarker(); | ||
3028 | drawText(); | 3144 | drawText(); |
3029 | drawCursor(); | 3145 | drawCursor(); |
3030 | 3146 | ||
@@ -3036,6 +3152,9 @@ void LLTextEditor::draw() | |||
3036 | } | 3152 | } |
3037 | LLView::draw(); // Draw children (scrollbar and border) | 3153 | LLView::draw(); // Draw children (scrollbar and border) |
3038 | } | 3154 | } |
3155 | |||
3156 | // remember if we are supposed to be at the bottom of the buffer | ||
3157 | mScrolledToBottom = isScrolledToBottom(); | ||
3039 | } | 3158 | } |
3040 | 3159 | ||
3041 | void LLTextEditor::reportBadKeystroke() | 3160 | void LLTextEditor::reportBadKeystroke() |
@@ -3067,10 +3186,10 @@ void LLTextEditor::setFocus( BOOL new_state ) | |||
3067 | // Don't change anything if the focus state didn't change | 3186 | // Don't change anything if the focus state didn't change |
3068 | if (new_state == old_state) return; | 3187 | if (new_state == old_state) return; |
3069 | 3188 | ||
3070 | // Notify early if we are loosing focus. | 3189 | // Notify early if we are losing focus. |
3071 | if (!new_state) | 3190 | if (!new_state) |
3072 | { | 3191 | { |
3073 | getWindow()->allowLanguageTextInput(FALSE); | 3192 | getWindow()->allowLanguageTextInput(this, FALSE); |
3074 | } | 3193 | } |
3075 | 3194 | ||
3076 | LLUICtrl::setFocus( new_state ); | 3195 | LLUICtrl::setFocus( new_state ); |
@@ -3093,12 +3212,6 @@ void LLTextEditor::setFocus( BOOL new_state ) | |||
3093 | 3212 | ||
3094 | endSelection(); | 3213 | endSelection(); |
3095 | } | 3214 | } |
3096 | |||
3097 | // Notify late if we are gaining focus. | ||
3098 | if (new_state && !mReadOnly) | ||
3099 | { | ||
3100 | getWindow()->allowLanguageTextInput(TRUE); | ||
3101 | } | ||
3102 | } | 3215 | } |
3103 | 3216 | ||
3104 | BOOL LLTextEditor::acceptsTextInput() const | 3217 | BOOL LLTextEditor::acceptsTextInput() const |
@@ -3210,6 +3323,17 @@ void LLTextEditor::changeLine( S32 delta ) | |||
3210 | unbindEmbeddedChars( mGLFont ); | 3323 | unbindEmbeddedChars( mGLFont ); |
3211 | } | 3324 | } |
3212 | 3325 | ||
3326 | BOOL LLTextEditor::isScrolledToTop() | ||
3327 | { | ||
3328 | return mScrollbar->isAtBeginning(); | ||
3329 | } | ||
3330 | |||
3331 | BOOL LLTextEditor::isScrolledToBottom() | ||
3332 | { | ||
3333 | return mScrollbar->isAtEnd(); | ||
3334 | } | ||
3335 | |||
3336 | |||
3213 | void LLTextEditor::startOfLine() | 3337 | void LLTextEditor::startOfLine() |
3214 | { | 3338 | { |
3215 | S32 line, offset; | 3339 | S32 line, offset; |
@@ -3330,6 +3454,13 @@ void LLTextEditor::reshape(S32 width, S32 height, BOOL called_from_parent) | |||
3330 | { | 3454 | { |
3331 | LLView::reshape( width, height, called_from_parent ); | 3455 | LLView::reshape( width, height, called_from_parent ); |
3332 | 3456 | ||
3457 | // if scrolled to bottom, stay at bottom | ||
3458 | // unless user is editing text | ||
3459 | if (mScrolledToBottom && mTrackBottom && !hasFocus()) | ||
3460 | { | ||
3461 | endOfDoc(); | ||
3462 | } | ||
3463 | |||
3333 | updateTextRect(); | 3464 | updateTextRect(); |
3334 | 3465 | ||
3335 | S32 line_height = llround( mGLFont->getLineHeight() ); | 3466 | S32 line_height = llround( mGLFont->getLineHeight() ); |
@@ -3540,22 +3671,20 @@ void LLTextEditor::removeTextFromEnd(S32 num_chars) | |||
3540 | 3671 | ||
3541 | S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr) | 3672 | S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr) |
3542 | { | 3673 | { |
3543 | S32 len = mWText.length(); | 3674 | S32 old_len = mWText.length(); // length() returns character length |
3544 | S32 s_len = wstr.length(); | 3675 | S32 insert_len = wstr.length(); |
3545 | S32 new_len = len + s_len; | 3676 | |
3546 | if( new_len > mMaxTextLength ) | 3677 | mWText.insert(pos, wstr); |
3547 | { | 3678 | mTextIsUpToDate = FALSE; |
3548 | new_len = mMaxTextLength; | ||
3549 | 3679 | ||
3680 | if ( truncate() ) | ||
3681 | { | ||
3550 | // The user's not getting everything he's hoping for | 3682 | // The user's not getting everything he's hoping for |
3551 | make_ui_sound("UISndBadKeystroke"); | 3683 | make_ui_sound("UISndBadKeystroke"); |
3684 | insert_len = mWText.length() - old_len; | ||
3552 | } | 3685 | } |
3553 | 3686 | ||
3554 | mWText.insert(pos, wstr); | 3687 | return insert_len; |
3555 | mTextIsUpToDate = FALSE; | ||
3556 | truncate(); | ||
3557 | |||
3558 | return new_len - len; | ||
3559 | } | 3688 | } |
3560 | 3689 | ||
3561 | S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length) | 3690 | S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length) |
@@ -3671,7 +3800,7 @@ void LLTextEditor::loadKeywords(const LLString& filename, | |||
3671 | mKeywords.addToken(LLKeywordToken::WORD, name.c_str(), color, tooltips.get(i) ); | 3800 | mKeywords.addToken(LLKeywordToken::WORD, name.c_str(), color, tooltips.get(i) ); |
3672 | } | 3801 | } |
3673 | 3802 | ||
3674 | mKeywords.findSegments( &mSegments, mWText ); | 3803 | mKeywords.findSegments( &mSegments, mWText, mDefaultColor ); |
3675 | 3804 | ||
3676 | llassert( mSegments.front()->getStart() == 0 ); | 3805 | llassert( mSegments.front()->getStart() == 0 ); |
3677 | llassert( mSegments.back()->getEnd() == getLength() ); | 3806 | llassert( mSegments.back()->getEnd() == getLength() ); |
@@ -3683,7 +3812,7 @@ void LLTextEditor::updateSegments() | |||
3683 | if (mKeywords.isLoaded()) | 3812 | if (mKeywords.isLoaded()) |
3684 | { | 3813 | { |
3685 | // HACK: No non-ascii keywords for now | 3814 | // HACK: No non-ascii keywords for now |
3686 | mKeywords.findSegments(&mSegments, mWText); | 3815 | mKeywords.findSegments(&mSegments, mWText, mDefaultColor); |
3687 | } | 3816 | } |
3688 | else if (mAllowEmbeddedItems) | 3817 | else if (mAllowEmbeddedItems) |
3689 | { | 3818 | { |
@@ -3920,7 +4049,7 @@ BOOL LLTextEditor::importBuffer(const LLString& buffer ) | |||
3920 | return FALSE; | 4049 | return FALSE; |
3921 | } | 4050 | } |
3922 | 4051 | ||
3923 | if( text_len > mMaxTextLength ) | 4052 | if( text_len > mMaxTextByteLength ) |
3924 | { | 4053 | { |
3925 | llwarns << "Invalid Linden text length: " << text_len << llendl; | 4054 | llwarns << "Invalid Linden text length: " << text_len << llendl; |
3926 | return FALSE; | 4055 | return FALSE; |
@@ -4064,6 +4193,7 @@ LLXMLNodePtr LLTextEditor::getXML(bool save_children) const | |||
4064 | 4193 | ||
4065 | addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor"); | 4194 | addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor"); |
4066 | addColorXML(node, mFgColor, "text_color", "TextFgColor"); | 4195 | addColorXML(node, mFgColor, "text_color", "TextFgColor"); |
4196 | addColorXML(node, mDefaultColor, "text_default_color", "TextDefaultColor"); | ||
4067 | addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor"); | 4197 | addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor"); |
4068 | addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor"); | 4198 | addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor"); |
4069 | addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor"); | 4199 | addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor"); |
@@ -4118,6 +4248,8 @@ void LLTextEditor::setTextEditorParameters(LLXMLNodePtr node) | |||
4118 | node->getAttributeBOOL("word_wrap", word_wrap); | 4248 | node->getAttributeBOOL("word_wrap", word_wrap); |
4119 | setWordWrap(word_wrap); | 4249 | setWordWrap(word_wrap); |
4120 | 4250 | ||
4251 | node->getAttributeBOOL("track_bottom", mTrackBottom); | ||
4252 | |||
4121 | LLColor4 color; | 4253 | LLColor4 color; |
4122 | if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) | 4254 | if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) |
4123 | { | 4255 | { |
@@ -4281,3 +4413,262 @@ BOOL LLTextEditor::findHTML(const LLString &line, S32 *begin, S32 *end) | |||
4281 | } | 4413 | } |
4282 | return matched; | 4414 | return matched; |
4283 | } | 4415 | } |
4416 | |||
4417 | |||
4418 | |||
4419 | void LLTextEditor::updateAllowingLanguageInput() | ||
4420 | { | ||
4421 | if (hasFocus() && !mReadOnly) | ||
4422 | { | ||
4423 | getWindow()->allowLanguageTextInput(this, TRUE); | ||
4424 | } | ||
4425 | else | ||
4426 | { | ||
4427 | getWindow()->allowLanguageTextInput(this, FALSE); | ||
4428 | } | ||
4429 | } | ||
4430 | |||
4431 | // Preedit is managed off the undo/redo command stack. | ||
4432 | |||
4433 | BOOL LLTextEditor::hasPreeditString() const | ||
4434 | { | ||
4435 | return (mPreeditPositions.size() > 1); | ||
4436 | } | ||
4437 | |||
4438 | void LLTextEditor::resetPreedit() | ||
4439 | { | ||
4440 | if (hasPreeditString()) | ||
4441 | { | ||
4442 | mCursorPos = mPreeditPositions.front(); | ||
4443 | removeStringNoUndo(mCursorPos, mPreeditPositions.back() - mCursorPos); | ||
4444 | insertStringNoUndo(mCursorPos, mPreeditOverwrittenWString); | ||
4445 | |||
4446 | mPreeditWString.clear(); | ||
4447 | mPreeditOverwrittenWString.clear(); | ||
4448 | mPreeditPositions.clear(); | ||
4449 | |||
4450 | updateLineStartList(); | ||
4451 | setCursorPos(mCursorPos); | ||
4452 | // updateScrollFromCursor(); | ||
4453 | } | ||
4454 | } | ||
4455 | |||
4456 | void LLTextEditor::updatePreedit(const LLWString &preedit_string, | ||
4457 | const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) | ||
4458 | { | ||
4459 | // Just in case. | ||
4460 | if (mReadOnly) | ||
4461 | { | ||
4462 | return; | ||
4463 | } | ||
4464 | |||
4465 | if (hasSelection()) | ||
4466 | { | ||
4467 | if (hasPreeditString()) | ||
4468 | { | ||
4469 | llwarns << "Preedit and selection!" << llendl; | ||
4470 | deselect(); | ||
4471 | } | ||
4472 | else | ||
4473 | { | ||
4474 | deleteSelection(TRUE); | ||
4475 | } | ||
4476 | } | ||
4477 | |||
4478 | getWindow()->hideCursorUntilMouseMove(); | ||
4479 | |||
4480 | S32 insert_preedit_at = mCursorPos; | ||
4481 | if (hasPreeditString()) | ||
4482 | { | ||
4483 | insert_preedit_at = mPreeditPositions.front(); | ||
4484 | removeStringNoUndo(insert_preedit_at, mPreeditPositions.back() - insert_preedit_at); | ||
4485 | insertStringNoUndo(insert_preedit_at, mPreeditOverwrittenWString); | ||
4486 | } | ||
4487 | |||
4488 | mPreeditWString = preedit_string; | ||
4489 | mPreeditPositions.resize(preedit_segment_lengths.size() + 1); | ||
4490 | S32 position = insert_preedit_at; | ||
4491 | for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++) | ||
4492 | { | ||
4493 | mPreeditPositions[i] = position; | ||
4494 | position += preedit_segment_lengths[i]; | ||
4495 | } | ||
4496 | mPreeditPositions.back() = position; | ||
4497 | |||
4498 | if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) | ||
4499 | { | ||
4500 | mPreeditOverwrittenWString = getWSubString(insert_preedit_at, mPreeditWString.length()); | ||
4501 | removeStringNoUndo(insert_preedit_at, mPreeditWString.length()); | ||
4502 | } | ||
4503 | else | ||
4504 | { | ||
4505 | mPreeditOverwrittenWString.clear(); | ||
4506 | } | ||
4507 | insertStringNoUndo(insert_preedit_at, mPreeditWString); | ||
4508 | |||
4509 | mPreeditStandouts = preedit_standouts; | ||
4510 | |||
4511 | updateLineStartList(); | ||
4512 | setCursorPos(insert_preedit_at + caret_position); | ||
4513 | // updateScrollFromCursor(); | ||
4514 | |||
4515 | // Update of the preedit should be caused by some key strokes. | ||
4516 | mKeystrokeTimer.reset(); | ||
4517 | } | ||
4518 | |||
4519 | BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const | ||
4520 | { | ||
4521 | if (control) | ||
4522 | { | ||
4523 | LLRect control_rect_screen; | ||
4524 | localRectToScreen(mTextRect, &control_rect_screen); | ||
4525 | LLUI::screenRectToGL(control_rect_screen, control); | ||
4526 | } | ||
4527 | |||
4528 | S32 preedit_left_position, preedit_right_position; | ||
4529 | if (hasPreeditString()) | ||
4530 | { | ||
4531 | preedit_left_position = mPreeditPositions.front(); | ||
4532 | preedit_right_position = mPreeditPositions.back(); | ||
4533 | } | ||
4534 | else | ||
4535 | { | ||
4536 | preedit_left_position = preedit_right_position = mCursorPos; | ||
4537 | } | ||
4538 | |||
4539 | const S32 query = (query_offset >= 0 ? preedit_left_position + query_offset : mCursorPos); | ||
4540 | if (query < preedit_left_position || query > preedit_right_position) | ||
4541 | { | ||
4542 | return FALSE; | ||
4543 | } | ||
4544 | |||
4545 | const S32 first_visible_line = mScrollbar->getDocPos(); | ||
4546 | if (query < getLineStart(first_visible_line)) | ||
4547 | { | ||
4548 | return FALSE; | ||
4549 | } | ||
4550 | |||
4551 | S32 current_line = first_visible_line; | ||
4552 | S32 current_line_start, current_line_end; | ||
4553 | for (;;) | ||
4554 | { | ||
4555 | current_line_start = getLineStart(current_line); | ||
4556 | current_line_end = getLineStart(current_line + 1); | ||
4557 | if (query >= current_line_start && query < current_line_end) | ||
4558 | { | ||
4559 | break; | ||
4560 | } | ||
4561 | if (current_line_start == current_line_end) | ||
4562 | { | ||
4563 | // We have reached on the last line. The query position must be here. | ||
4564 | break; | ||
4565 | } | ||
4566 | current_line++; | ||
4567 | } | ||
4568 | |||
4569 | const llwchar * const text = mWText.c_str(); | ||
4570 | const S32 line_height = llround(mGLFont->getLineHeight()); | ||
4571 | |||
4572 | if (coord) | ||
4573 | { | ||
4574 | const S32 query_x = mTextRect.mLeft + mGLFont->getWidth(text, current_line_start, query - current_line_start, mAllowEmbeddedItems); | ||
4575 | const S32 query_y = mTextRect.mTop - (current_line - first_visible_line) * line_height - line_height / 2; | ||
4576 | S32 query_screen_x, query_screen_y; | ||
4577 | localPointToScreen(query_x, query_y, &query_screen_x, &query_screen_y); | ||
4578 | LLUI::screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); | ||
4579 | } | ||
4580 | |||
4581 | if (bounds) | ||
4582 | { | ||
4583 | S32 preedit_left = mTextRect.mLeft; | ||
4584 | if (preedit_left_position > current_line_start) | ||
4585 | { | ||
4586 | preedit_left += mGLFont->getWidth(text, current_line_start, preedit_left_position - current_line_start, mAllowEmbeddedItems); | ||
4587 | } | ||
4588 | |||
4589 | S32 preedit_right = mTextRect.mLeft; | ||
4590 | if (preedit_right_position < current_line_end) | ||
4591 | { | ||
4592 | preedit_right += mGLFont->getWidth(text, current_line_start, preedit_right_position - current_line_start, mAllowEmbeddedItems); | ||
4593 | } | ||
4594 | else | ||
4595 | { | ||
4596 | preedit_right += mGLFont->getWidth(text, current_line_start, current_line_end - current_line_start, mAllowEmbeddedItems); | ||
4597 | } | ||
4598 | |||
4599 | const S32 preedit_top = mTextRect.mTop - (current_line - first_visible_line) * line_height; | ||
4600 | const S32 preedit_bottom = preedit_top - line_height; | ||
4601 | |||
4602 | const LLRect preedit_rect_local(preedit_left, preedit_top, preedit_right, preedit_bottom); | ||
4603 | LLRect preedit_rect_screen; | ||
4604 | localRectToScreen(preedit_rect_local, &preedit_rect_screen); | ||
4605 | LLUI::screenRectToGL(preedit_rect_screen, bounds); | ||
4606 | } | ||
4607 | |||
4608 | return TRUE; | ||
4609 | } | ||
4610 | |||
4611 | void LLTextEditor::getSelectionRange(S32 *position, S32 *length) const | ||
4612 | { | ||
4613 | if (hasSelection()) | ||
4614 | { | ||
4615 | *position = llmin(mSelectionStart, mSelectionEnd); | ||
4616 | *length = llabs(mSelectionStart - mSelectionEnd); | ||
4617 | } | ||
4618 | else | ||
4619 | { | ||
4620 | *position = mCursorPos; | ||
4621 | *length = 0; | ||
4622 | } | ||
4623 | } | ||
4624 | |||
4625 | void LLTextEditor::getPreeditRange(S32 *position, S32 *length) const | ||
4626 | { | ||
4627 | if (hasPreeditString()) | ||
4628 | { | ||
4629 | *position = mPreeditPositions.front(); | ||
4630 | *length = mPreeditPositions.back() - mPreeditPositions.front(); | ||
4631 | } | ||
4632 | else | ||
4633 | { | ||
4634 | *position = mCursorPos; | ||
4635 | *length = 0; | ||
4636 | } | ||
4637 | } | ||
4638 | |||
4639 | void LLTextEditor::markAsPreedit(S32 position, S32 length) | ||
4640 | { | ||
4641 | deselect(); | ||
4642 | setCursorPos(position); | ||
4643 | if (hasPreeditString()) | ||
4644 | { | ||
4645 | llwarns << "markAsPreedit invoked when hasPreeditString is true." << llendl; | ||
4646 | } | ||
4647 | mPreeditWString = LLWString( mWText, position, length ); | ||
4648 | if (length > 0) | ||
4649 | { | ||
4650 | mPreeditPositions.resize(2); | ||
4651 | mPreeditPositions[0] = position; | ||
4652 | mPreeditPositions[1] = position + length; | ||
4653 | mPreeditStandouts.resize(1); | ||
4654 | mPreeditStandouts[0] = FALSE; | ||
4655 | } | ||
4656 | else | ||
4657 | { | ||
4658 | mPreeditPositions.clear(); | ||
4659 | mPreeditStandouts.clear(); | ||
4660 | } | ||
4661 | if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) | ||
4662 | { | ||
4663 | mPreeditOverwrittenWString = mPreeditWString; | ||
4664 | } | ||
4665 | else | ||
4666 | { | ||
4667 | mPreeditOverwrittenWString.clear(); | ||
4668 | } | ||
4669 | } | ||
4670 | |||
4671 | S32 LLTextEditor::getPreeditFontSize() const | ||
4672 | { | ||
4673 | return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); | ||
4674 | } | ||