From 05f5b3c04a6273303bce3abec6f79f27a8a93faf Mon Sep 17 00:00:00 2001 From: McCabe Maxsted Date: Fri, 23 Jan 2009 18:59:42 -0700 Subject: [PATCH] Backported fix for VWR-5529: group chat scrolls up when logging enabled --- ChangeLog.txt | 15 ++ linden/indra/llcommon/llstring.h | 30 ++-- linden/indra/llui/lltexteditor.cpp | 128 ++++++++------- linden/indra/llui/lltexteditor.h | 21 ++- .../xui/en-us/floater_chat_history.xml | 4 +- .../xui/en-us/floater_instant_message.xml | 1 + .../en-us/floater_instant_message_ad_hoc.xml | 147 +++++++++--------- .../en-us/floater_instant_message_group.xml | 1 + 8 files changed, 191 insertions(+), 156 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index da216019f..6f6cd910b 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -7,6 +7,21 @@ * linden/indra/llui/lltexteditor.cpp: Backported Qarl's fix for VWR-8773: Closing parenthesis breaks urls. + * linden/indra/llcommon/llstring.h: + Backported fix for VWR-5529: group chat scrolls up when logging enabled. + * linden/indra/llui/lltexteditor.cpp: + Ditto. + * linden/indra/llui/lltexteditor.h: + Ditto. + * linden/indra/newview/skins/default/xui/en-us/floater_chat_history.xml: + Ditto. + * linden/indra/newview/skins/default/xui/en-us/floater_instant_message.xml: + Ditto. + * linden/indra/newview/skins/default/xui/en-us/floater_instant_message_ad_hoc.xml: + Ditto. + * linden/indra/newview/skins/default/xui/en-us/floater_instant_message_group.xml: + Ditto. + 2009-01-22 McCabe Maxsted diff --git a/linden/indra/llcommon/llstring.h b/linden/indra/llcommon/llstring.h index 7b08fd3e3..2d76eca42 100644 --- a/linden/indra/llcommon/llstring.h +++ b/linden/indra/llcommon/llstring.h @@ -133,26 +133,32 @@ struct char_traits class LLStringOps { public: - static char toUpper(char elem) { return toupper(elem); } + static char toUpper(char elem) { return toupper((unsigned char)elem); } static llwchar toUpper(llwchar elem) { return towupper(elem); } - static char toLower(char elem) { return tolower(elem); } + static char toLower(char elem) { return tolower((unsigned char)elem); } static llwchar toLower(llwchar elem) { return towlower(elem); } - static BOOL isSpace(char elem) { return isspace(elem) != 0; } - static BOOL isSpace(llwchar elem) { return iswspace(elem) != 0; } + static bool isSpace(char elem) { return isspace((unsigned char)elem) != 0; } + static bool isSpace(llwchar elem) { return iswspace(elem) != 0; } - static BOOL isUpper(char elem) { return isupper(elem) != 0; } - static BOOL isUpper(llwchar elem) { return iswupper(elem) != 0; } + static bool isUpper(char elem) { return isupper((unsigned char)elem) != 0; } + static bool isUpper(llwchar elem) { return iswupper(elem) != 0; } - static BOOL isLower(char elem) { return islower(elem) != 0; } - static BOOL isLower(llwchar elem) { return iswlower(elem) != 0; } + static bool isLower(char elem) { return islower((unsigned char)elem) != 0; } + static bool isLower(llwchar elem) { return iswlower(elem) != 0; } + + static bool isDigit(char a) { return isdigit((unsigned char)a) != 0; } + static bool isDigit(llwchar a) { return iswdigit(a) != 0; } + + static bool isPunct(char a) { return ispunct((unsigned char)a) != 0; } + static bool isPunct(llwchar a) { return iswpunct(a) != 0; } + + static bool isAlnum(char a) { return isalnum((unsigned char)a) != 0; } + static bool isAlnum(llwchar a) { return iswalnum(a) != 0; } static S32 collate(const char* a, const char* b) { return strcoll(a, b); } static S32 collate(const llwchar* a, const llwchar* b); - - static BOOL isDigit(char a) { return isdigit(a) != 0; } - static BOOL isDigit(llwchar a) { return iswdigit(a) != 0; } }; /** @@ -194,7 +200,7 @@ class LLStringUtilBase typedef std::map format_map_t; static S32 format(std::basic_string& s, const format_map_t& fmt_map); - static BOOL isValidIndex(const std::basic_string& string, size_type i) + static bool isValidIndex(const std::basic_string& string, size_type i) { return !string.empty() && (0 <= i) && (i <= string.size()); } diff --git a/linden/indra/llui/lltexteditor.cpp b/linden/indra/llui/lltexteditor.cpp index 90199f1b3..72d8f179e 100644 --- a/linden/indra/llui/lltexteditor.cpp +++ b/linden/indra/llui/lltexteditor.cpp @@ -260,7 +260,7 @@ LLTextEditor::LLTextEditor( mIsSelecting( FALSE ), mSelectionStart( 0 ), mSelectionEnd( 0 ), - mScrolledToBottom( FALSE ), + mScrolledToBottom( TRUE ), mOnScrollEndCallback( NULL ), mOnScrollEndData( NULL ), mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ), @@ -277,14 +277,16 @@ LLTextEditor::LLTextEditor( mCommitOnFocusLost( FALSE ), mHideScrollbarForShortDocs( FALSE ), mTakesNonScrollClicks( TRUE ), - mTrackBottom( TRUE ), + mTrackBottom( FALSE ), mAllowEmbeddedItems( allow_embedded_items ), mAcceptCallingCardNames(FALSE), mHandleEditKeysDirectly( FALSE ), mMouseDownX(0), mMouseDownY(0), mLastSelectionX(-1), - mLastSelectionY(-1) + mLastSelectionY(-1), + mReflowNeeded(FALSE), + mScrollNeeded(FALSE) { mSourceID.generate(); @@ -468,6 +470,13 @@ void LLTextEditor::updateLineStartList(S32 startpos) mScrollbar->setVisible(!short_doc); } + // if scrolled to bottom, stay at bottom + // unless user is editing text + // do this after updating page size + if (mScrolledToBottom && mTrackBottom && !hasFocus()) + { + endOfDoc(); + } } //////////////////////////////////////////////////////////// @@ -511,8 +520,7 @@ void LLTextEditor::setText(const LLStringExplicit &utf8str) setCursorPos(0); deselect(); - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); resetDirty(); } @@ -529,8 +537,7 @@ void LLTextEditor::setWText(const LLWString &wtext) setCursorPos(0); deselect(); - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); resetDirty(); } @@ -568,8 +575,7 @@ void LLTextEditor::setWordWrap(BOOL b) setCursorPos(0); deselect(); - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); } @@ -734,6 +740,7 @@ S32 LLTextEditor::getLineStart( S32 line ) const { return 0; } + line = llclamp(line, 0, num_lines-1); S32 segidx = mLineStartList[line].mSegment; S32 segoffset = mLineStartList[line].mOffset; @@ -781,14 +788,14 @@ void LLTextEditor::getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp *offsetp = startpos - (*seg_iter)->getStart(); } -const LLTextSegment* LLTextEditor::getPreviousSegment() +const LLTextSegment* LLTextEditor::getPreviousSegment() const { // find segment index at character to left of cursor (or rightmost edge of selection) S32 idx = llmax(0, getSegmentIdxAtOffset(mCursorPos) - 1); return idx >= 0 ? mSegments[idx] : NULL; } -void LLTextEditor::getSelectedSegments(std::vector& segments) +void LLTextEditor::getSelectedSegments(std::vector& segments) const { S32 left = hasSelection() ? llmin(mSelectionStart, mSelectionEnd) : mCursorPos; S32 right = hasSelection() ? llmax(mSelectionStart, mSelectionEnd) : mCursorPos; @@ -875,13 +882,12 @@ void LLTextEditor::setCursor(S32 row, S32 column) } doc += column; setCursorPos(doc - mWText.c_str()); - updateScrollFromCursor(); } void LLTextEditor::setCursorPos(S32 offset) { mCursorPos = llclamp(offset, 0, (S32)getLength()); - updateScrollFromCursor(); + needsScroll(); // reset desired x cursor position mDesiredXPixel = -1; } @@ -925,7 +931,7 @@ BOOL LLTextEditor::selectionContainsLineBreaks() if (hasSelection()) { S32 left = llmin(mSelectionStart, mSelectionEnd); - S32 right = left + abs(mSelectionStart - mSelectionEnd); + S32 right = left + llabs(mSelectionStart - mSelectionEnd); const LLWString &wtext = mWText; for( S32 i = left; i < right; i++ ) @@ -981,7 +987,7 @@ void LLTextEditor::indentSelectedLines( S32 spaces ) { const LLWString &text = mWText; S32 left = llmin( mSelectionStart, mSelectionEnd ); - S32 right = left + abs( mSelectionStart - mSelectionEnd ); + S32 right = left + llabs( mSelectionStart - mSelectionEnd ); BOOL cursor_on_right = (mSelectionEnd > mSelectionStart); S32 cur = left; @@ -1222,8 +1228,6 @@ BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask) setCursorAtLocalPos( x, y, TRUE ); mSelectionEnd = mCursorPos; - - updateScrollFromCursor(); } lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; @@ -1812,7 +1816,7 @@ void LLTextEditor::deleteSelection(BOOL group_with_next_op ) if( getEnabled() && hasSelection() ) { S32 pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = abs( mSelectionStart - mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); remove( pos, length, group_with_next_op ); @@ -1835,12 +1839,11 @@ void LLTextEditor::cut() return; } S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = abs( mSelectionStart - mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); gClipboard.copyFromSubstring( mWText, left_pos, length, mSourceID ); deleteSelection( FALSE ); - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); } BOOL LLTextEditor::canCopy() const @@ -1856,7 +1859,7 @@ void LLTextEditor::copy() return; } S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = abs( mSelectionStart - mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); gClipboard.copyFromSubstring(mWText, left_pos, length, mSourceID); } @@ -1910,8 +1913,7 @@ void LLTextEditor::paste() setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE)); deselect(); - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); } @@ -2235,9 +2237,9 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) if(text_may_have_changed) { - updateLineStartList(); + needsReflow(); } - updateScrollFromCursor(); + needsScroll(); } } @@ -2280,8 +2282,7 @@ BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char) // Most keystrokes will make the selection box go away, but not all will. deselect(); - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); } } @@ -2339,8 +2340,7 @@ void LLTextEditor::doDelete() } } - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); } //---------------------------------------------------------------------------- @@ -2383,8 +2383,7 @@ void LLTextEditor::undo() setCursorPos(pos); - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); } BOOL LLTextEditor::canRedo() const @@ -2426,8 +2425,7 @@ void LLTextEditor::redo() setCursorPos(pos); - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); } void LLTextEditor::onFocusReceived() @@ -3100,6 +3098,20 @@ void LLTextEditor::drawClippedSegment(const LLWString &text, S32 seg_start, S32 void LLTextEditor::draw() { + // do on-demand reflow + if (mReflowNeeded) + { + updateLineStartList(); + mReflowNeeded = FALSE; + } + + // then update scroll position, as cursor may have moved + if (mScrollNeeded) + { + updateScrollFromCursor(); + mScrollNeeded = FALSE; + } + { LLLocalClipRect clip(LLRect(0, getRect().getHeight(), getRect().getWidth() - (mScrollbar->getVisible() ? SCROLLBAR_SIZE : 0), 0)); @@ -3118,10 +3130,10 @@ void LLTextEditor::draw() mBorder->setKeyboardFocusHighlight( gFocusMgr.getKeyboardFocus() == this);// && !mReadOnly); } + LLView::draw(); // Draw children (scrollbar and border) + // remember if we are supposed to be at the bottom of the buffer mScrolledToBottom = isScrolledToBottom(); - - LLView::draw(); // Draw children (scrollbar and border) } @@ -3311,7 +3323,7 @@ void LLTextEditor::setCursorAndScrollToEnd() { deselect(); endOfDoc(); - updateScrollFromCursor(); + needsScroll(); } void LLTextEditor::getLineAndColumnForPosition( S32 position, S32* line, S32* col, BOOL include_wordwrap ) @@ -3374,7 +3386,9 @@ void LLTextEditor::endOfLine() void LLTextEditor::endOfDoc() { - mScrollbar->setDocPos( mScrollbar->getDocPosMax() ); + mScrollbar->setDocPos(mScrollbar->getDocPosMax()); + mScrolledToBottom = true; + S32 len = getLength(); if( len ) { @@ -3438,7 +3452,7 @@ void LLTextEditor::reshape(S32 width, S32 height, BOOL called_from_parent) // up-to-date mTextRect updateTextRect(); - updateLineStartList(); + needsReflow(); // propagate shape information to scrollbar mScrollbar->setDocSize( getLineCount() ); @@ -3446,14 +3460,6 @@ void LLTextEditor::reshape(S32 width, S32 height, BOOL called_from_parent) S32 line_height = llround( mGLFont->getLineHeight() ); S32 page_lines = mTextRect.getHeight() / line_height; mScrollbar->setPageSize( page_lines ); - - // if scrolled to bottom, stay at bottom - // unless user is editing text - // do this after updating page size - if (mScrolledToBottom && mTrackBottom && !hasFocus()) - { - endOfDoc(); - } } void LLTextEditor::autoIndent() @@ -3500,8 +3506,7 @@ void LLTextEditor::insertText(const std::string &new_text) setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE )); - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); setEnabled( enabled ); } @@ -3600,7 +3605,7 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool mSegments.push_back(segment); } - updateLineStartList(old_length); + needsReflow(); // Set the cursor and scroll position // Maintain the scroll position unless the scroll was at the end of the doc (in which @@ -3639,14 +3644,6 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool { blockUndo(); } - - // if scrolled to bottom, stay at bottom - // unless user is editing text - // do this after updating page size - if (mScrolledToBottom && mTrackBottom && !hasFocus()) - { - endOfDoc(); - } } void LLTextEditor::removeTextFromEnd(S32 num_chars) @@ -3661,7 +3658,10 @@ void LLTextEditor::removeTextFromEnd(S32 num_chars) mSelectionEnd = llclamp(mSelectionEnd, 0, len); pruneSegments(); + + // pruneSegments will invalidate mLineStartList. updateLineStartList(); + needsScroll(); } /////////////////////////////////////////////////////////////////// @@ -3759,8 +3759,7 @@ BOOL LLTextEditor::tryToRevertToPristineState() } } - updateLineStartList(); - updateScrollFromCursor(); + needsReflow(); } return isPristine(); // TRUE => success @@ -3808,6 +3807,7 @@ void LLTextEditor::updateSegments() { findEmbeddedItemSegments(); } + // Make sure we have at least one segment if (mSegments.size() == 1 && mSegments[0]->getIsDefault()) { @@ -3824,6 +3824,7 @@ void LLTextEditor::updateSegments() } // Only effective if text was removed from the end of the editor +// *NOTE: Using this will invalidate references to mSegments from mLineStartList. void LLTextEditor::pruneSegments() { S32 len = mWText.length(); @@ -4066,9 +4067,7 @@ BOOL LLTextEditor::importBuffer(const char* buffer, S32 length ) setCursorPos(0); deselect(); - updateLineStartList(); - updateScrollFromCursor(); - + needsReflow(); return success; } @@ -4496,9 +4495,8 @@ void LLTextEditor::updatePreedit(const LLWString &preedit_string, mPreeditStandouts = preedit_standouts; - updateLineStartList(); + needsReflow(); setCursorPos(insert_preedit_at + caret_position); - // updateScrollFromCursor(); // Update of the preedit should be caused by some key strokes. mKeystrokeTimer.reset(); diff --git a/linden/indra/llui/lltexteditor.h b/linden/indra/llui/lltexteditor.h index 3cff91dda..c76395c42 100644 --- a/linden/indra/llui/lltexteditor.h +++ b/linden/indra/llui/lltexteditor.h @@ -110,6 +110,7 @@ class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor virtual BOOL canUndo() const; virtual void redo(); virtual BOOL canRedo() const; + virtual void cut(); virtual BOOL canCut() const; virtual void copy(); @@ -245,9 +246,11 @@ class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor llwchar getWChar(S32 pos) const { return mWText[pos]; } LLWString getWSubString(S32 pos, S32 len) const { return mWText.substr(pos, len); } - const LLTextSegment* getCurrentSegment() { return getSegmentAtOffset(mCursorPos); } - const LLTextSegment* getPreviousSegment(); - void getSelectedSegments(std::vector& segments); + const LLTextSegment* getCurrentSegment() const { return getSegmentAtOffset(mCursorPos); } + const LLTextSegment* getPreviousSegment() const; + void getSelectedSegments(std::vector& segments) const; + + static bool isPartOfWord(llwchar c) { return (c == '_') || LLStringOps::isAlnum((char)c); } protected: // @@ -266,8 +269,6 @@ class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor void assignEmbedded(const std::string &s); BOOL truncate(); // Returns true if truncation occurs - static BOOL isPartOfWord(llwchar c) { return (c == '_') || isalnum(c); } - void removeCharOrTab(); void setCursorAtLocalPos(S32 x, S32 y, BOOL round); S32 getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const; @@ -433,6 +434,14 @@ class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor void drawText(); void drawClippedSegment(const LLWString &wtext, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyleSP& color, F32* right_x); + void needsReflow() + { + mReflowNeeded = TRUE; + // cursor might have moved, need to scroll + mScrollNeeded = TRUE; + } + void needsScroll() { mScrollNeeded = TRUE; } + // // Data // @@ -489,6 +498,8 @@ class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor }; typedef std::vector line_list_t; line_list_t mLineStartList; + BOOL mReflowNeeded; + BOOL mScrollNeeded; LLFrameTimer mKeystrokeTimer; diff --git a/linden/indra/newview/skins/default/xui/en-us/floater_chat_history.xml b/linden/indra/newview/skins/default/xui/en-us/floater_chat_history.xml index 6bd442423..a12cb2bfa 100644 --- a/linden/indra/newview/skins/default/xui/en-us/floater_chat_history.xml +++ b/linden/indra/newview/skins/default/xui/en-us/floater_chat_history.xml @@ -46,13 +46,15 @@ bottom="28" embedded_items="false" enabled="false" follows="left|top|right|bottom" font="SansSerif" height="74" left="5" max_length="2147483647" mouse_opaque="true" name="Chat History Editor" + track_bottom="true" text_color="ChatHistoryTextColor" text_readonly_color="ChatHistoryTextColor" width="299" word_wrap="true" /> - diff --git a/linden/indra/newview/skins/default/xui/en-us/floater_instant_message.xml b/linden/indra/newview/skins/default/xui/en-us/floater_instant_message.xml index eb8e672bb..3b1c927f7 100644 --- a/linden/indra/newview/skins/default/xui/en-us/floater_instant_message.xml +++ b/linden/indra/newview/skins/default/xui/en-us/floater_instant_message.xml @@ -64,6 +64,7 @@ follows="left|top|right|bottom" font="SansSerif" height="221" left="5" max_length="2147483647" mouse_opaque="true" name="im_history" text_color="ChatHistoryTextColor" + track_bottom="true" text_readonly_color="ChatHistoryTextColor" width="487" word_wrap="true" /> - - - Joining Voice Chat... - - - Connected, click End Call to hang up - - - Left Voice Chat - - - icn_voice-groupfocus.tga - - - Instant Message with [NAME] - - - [NAME] is typing... - - - Starting session with [NAME] please wait. - - - Click here to instant message. - - - -