From 2b22831dd0fffde8345a7b933ace46be818dec9a Mon Sep 17 00:00:00 2001
From: richard <none@none>
Date: Fri, 18 Dec 2009 15:18:16 -0800
Subject: [PATCH] ext-3307 - nearby chat stays scrolled at bottom

---
 indra/llui/llfloater.cpp                      |   2 +-
 indra/llui/lllayoutstack.cpp                  |   2 +-
 indra/llui/llpanel.cpp                        |   4 +-
 indra/llui/llradiogroup.cpp                   |   4 +
 indra/llui/llscrolllistcell.cpp               |   2 +-
 indra/llui/llspinctrl.cpp                     |  20 +--
 indra/llui/lltabcontainer.cpp                 |   8 -
 indra/llui/lltextbase.cpp                     |  23 ++-
 indra/llui/lltextbase.h                       |   2 +
 indra/llui/llui.cpp                           |   1 +
 indra/llui/lluictrlfactory.h                  |   3 +-
 indra/llui/lluiimage.cpp                      |   1 +
 indra/llui/llview.cpp                         |  24 +--
 indra/llui/llview.h                           |   8 +-
 indra/newview/llchathistory.cpp               | 140 +++++++++++++++---
 indra/newview/llchathistory.h                 |  22 ++-
 .../skins/default/xui/en/floater_aaa.xml      |  10 +-
 .../newview/skins/default/xui/en/strings.xml  |   6 +
 .../default/xui/en/widgets/chat_history.xml   |   8 +-
 19 files changed, 208 insertions(+), 82 deletions(-)

diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index f7fd2dbdfee..d7a692ec9ba 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -2717,7 +2717,7 @@ bool LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr o
 	}
 
 	params.from_xui = true;
-	setupParams(params, parent);
+	applyXUILayout(params, parent);
  	initFromParams(params);
 	
 	initFloater(params);
diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp
index 5e15fa3919a..7d8c1027502 100644
--- a/indra/llui/lllayoutstack.cpp
+++ b/indra/llui/lllayoutstack.cpp
@@ -247,7 +247,7 @@ LLView* LLLayoutStack::fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr o
 	}
 
 	p.from_xui = true;
-	setupParams(p, parent);
+	applyXUILayout(p, parent);
 	LLLayoutStack* layout_stackp = LLUICtrlFactory::create<LLLayoutStack>(p);
 
 	if (parent && layout_stackp)
diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp
index 738a96f7305..db328824382 100644
--- a/indra/llui/llpanel.cpp
+++ b/indra/llui/llpanel.cpp
@@ -452,7 +452,7 @@ void LLPanel::initFromParams(const LLPanel::Params& p)
 	parseFollowsFlags(p);
 
 	setToolTip(p.tool_tip());
-	setSaveToXML(p.from_xui);
+	setFromXUI(p.from_xui);
 	
 	mHoverCursor = getCursorFromString(p.hover_cursor);
 	
@@ -542,7 +542,7 @@ BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu
 		}
 		
 		params.from_xui = true;
-		setupParams(params, parent);
+		applyXUILayout(params, parent);
 		{
 			LLFastTimer timer(FTM_PANEL_CONSTRUCTION);
 			initFromParams(params);
diff --git a/indra/llui/llradiogroup.cpp b/indra/llui/llradiogroup.cpp
index 997b9c13f85..4087b484aaa 100644
--- a/indra/llui/llradiogroup.cpp
+++ b/indra/llui/llradiogroup.cpp
@@ -116,6 +116,10 @@ void LLRadioGroup::initFromParams(const Params& p)
 		item_params.font.setIfNotProvided(mFont); // apply radio group font by default
 		item_params.commit_callback.function = boost::bind(&LLRadioGroup::onClickButton, this, _1);
 		item_params.from_xui = p.from_xui;
+		if (p.from_xui)
+		{
+			applyXUILayout(item_params, this);
+		}
 
 		LLRadioCtrl* item = LLUICtrlFactory::create<LLRadioCtrl>(item_params, this);
 		mRadioButtons.push_back(item);
diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp
index 544352176ab..7238d903a30 100644
--- a/indra/llui/llscrolllistcell.cpp
+++ b/indra/llui/llscrolllistcell.cpp
@@ -353,7 +353,7 @@ LLScrollListCheck::LLScrollListCheck(const LLScrollListCell::Params& p)
 {
 	LLCheckBoxCtrl::Params checkbox_p;
 	checkbox_p.name("checkbox");
-	checkbox_p.rect.left(0).bottom(0).width(p.width).height(p.width);
+	checkbox_p.rect = LLRect(0, p.width, p.width, 0);
 	checkbox_p.enabled(p.enabled);
 	checkbox_p.initial_value(p.value());
 
diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp
index d6d46654d57..20a1ab7af3a 100644
--- a/indra/llui/llspinctrl.cpp
+++ b/indra/llui/llspinctrl.cpp
@@ -75,10 +75,8 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p)
 	static LLUICachedControl<S32> spinctrl_spacing ("UISpinctrlSpacing", 0);
 	static LLUICachedControl<S32> spinctrl_btn_width ("UISpinctrlBtnWidth", 0);
 	static LLUICachedControl<S32> spinctrl_btn_height ("UISpinctrlBtnHeight", 0);
-	S32 top = getRect().getHeight();
-	S32 bottom = top - 2 * spinctrl_btn_height;
-	S32 centered_top = top;
-	S32 centered_bottom = bottom;
+	S32 centered_top = getRect().getHeight();
+	S32 centered_bottom = getRect().getHeight() - 2 * spinctrl_btn_height;
 	S32 btn_left = 0;
 	// reserve space for spinner
 	S32 label_width = llclamp(p.label_width(), 0, llmax(0, getRect().getWidth() - 40));
@@ -105,25 +103,15 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p)
 	
 	// Spin buttons
 	LLButton::Params up_button_params(p.up_button);
-	up_button_params.rect
-					.left(btn_left)
-					.top(top)
-					.right(btn_right)
-					.height(spinctrl_btn_height);
+	up_button_params.rect = LLRect(btn_left, getRect().getHeight(), btn_right, getRect().getHeight() - spinctrl_btn_height);
 	up_button_params.click_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2));
 	up_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2));
 
 	mUpBtn = LLUICtrlFactory::create<LLButton>(up_button_params);
 	addChild(mUpBtn);
 
-	LLRect down_rect( btn_left, top - spinctrl_btn_height, btn_right, bottom );
-
 	LLButton::Params down_button_params(p.down_button);
-	down_button_params.rect
-					.left(btn_left)
-					.right(btn_right)
-					.bottom(bottom)
-					.height(spinctrl_btn_height);
+	down_button_params.rect = LLRect(btn_left, getRect().getHeight() - spinctrl_btn_height, btn_right, getRect().getHeight() - 2 * spinctrl_btn_height);
 	down_button_params.click_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2));
 	down_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2));
 	mDownBtn = LLUICtrlFactory::create<LLButton>(down_button_params);
diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp
index 83e2e3db50d..327dd01612b 100644
--- a/indra/llui/lltabcontainer.cpp
+++ b/indra/llui/lltabcontainer.cpp
@@ -266,8 +266,6 @@ bool LLTabContainer::addChild(LLView* view, S32 tab_group)
 
 	if (panelp)
 	{
-		panelp->setSaveToXML(TRUE);
-
 		addTabPanel(TabPanelParams().panel(panelp).label(panelp->getLabel()).is_placeholder(dynamic_cast<LLPlaceHolderPanel*>(view) != NULL));
 		return true;
 	}
@@ -1019,12 +1017,10 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel)
 	{
 		if (textbox)
 		{
-			textbox->setSaveToXML(false);
 			addChild( textbox, 0 );
 		}
 		if (btn)
 		{
-			btn->setSaveToXML(false);
 			addChild( btn, 0 );
 		}
 	}
@@ -1747,24 +1743,20 @@ void LLTabContainer::initButtons()
 		}
 	}
 
-	mPrevArrowBtn->setSaveToXML(false);
 	mPrevArrowBtn->setTabStop(FALSE);
 	addChild(mPrevArrowBtn);
 
-	mNextArrowBtn->setSaveToXML(false);
 	mNextArrowBtn->setTabStop(FALSE);
 	addChild(mNextArrowBtn);
 
 	if (mJumpPrevArrowBtn)
 	{
-		mJumpPrevArrowBtn->setSaveToXML(false);
 		mJumpPrevArrowBtn->setTabStop(FALSE);
 		addChild(mJumpPrevArrowBtn);
 	}
 
 	if (mJumpNextArrowBtn)
 	{
-		mJumpNextArrowBtn->setSaveToXML(false);
 		mJumpNextArrowBtn->setTabStop(FALSE);
 		addChild(mJumpNextArrowBtn);
 	}
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index cb0907a771b..e54032ac5e5 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -962,7 +962,10 @@ void LLTextBase::draw()
 	reflow();
 
 	// then update scroll position, as cursor may have moved
-	updateScrollFromCursor();
+	if (!mReadOnly)
+	{
+		updateScrollFromCursor();
+	}
 
 	LLRect doc_rect;
 	if (mScroller)
@@ -1932,11 +1935,19 @@ void LLTextBase::endOfLine()
 void LLTextBase::startOfDoc()
 {
 	setCursorPos(0);
+	if (mScroller)
+	{
+		mScroller->goToTop();
+	}
 }
 
 void LLTextBase::endOfDoc()
 {
 	setCursorPos(getLength());
+	if (mScroller)
+	{
+		mScroller->goToBottom();
+	}
 }
 
 void LLTextBase::changePage( S32 delta )
@@ -2001,6 +2012,16 @@ void LLTextBase::changeLine( S32 delta )
 	setCursorPos(new_cursor_pos, true);
 }
 
+bool LLTextBase::scrolledToStart()
+{
+	return mScroller->isAtTop();
+}
+
+bool LLTextBase::scrolledToEnd()
+{
+	return mScroller->isAtBottom();
+}
+
 
 bool LLTextBase::setCursor(S32 row, S32 column)
 {
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index a1f8ba39ae7..c91578b6377 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -180,6 +180,8 @@ class LLTextBase
 	void					changePage( S32 delta );
 	void					changeLine( S32 delta );
 
+	bool					scrolledToStart();
+	bool					scrolledToEnd();
 
 	const LLFontGL*			getDefaultFont() const					{ return mDefaultFont; }
 
diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp
index 1ea6b66a93c..728ed4e7aa1 100644
--- a/indra/llui/llui.cpp
+++ b/indra/llui/llui.cpp
@@ -1915,6 +1915,7 @@ namespace LLInitParam
 		green = color.mV[VGREEN];
 		blue = color.mV[VBLUE];
 		alpha = color.mV[VALPHA];
+		control.set("", false);
 	}
 
 	void TypeValues<LLUIColor>::declareValues()
diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h
index 6788f29ba93..b1fa6add67b 100644
--- a/indra/llui/lluictrlfactory.h
+++ b/indra/llui/lluictrlfactory.h
@@ -184,8 +184,6 @@ class LLUICtrlFactory : public LLSingleton<LLUICtrlFactory>
 	{
 		T* widget = NULL;
 
-		T::setupParams(params, parent);
-
 		if (!params.validateBlock())
 		{
 			llwarns << getInstance()->getCurFileName() << ": Invalid parameter block for " << typeid(T).name() << llendl;
@@ -310,6 +308,7 @@ class LLUICtrlFactory : public LLSingleton<LLUICtrlFactory>
 
 		// Apply layout transformations, usually munging rect
 		params.from_xui = true;
+		T::applyXUILayout(params, parent);
 		T* widget = createWidget<T>(params, parent);
 
 		typedef typename T::child_registry_t registry_t;
diff --git a/indra/llui/lluiimage.cpp b/indra/llui/lluiimage.cpp
index 1dfc281d93e..966d919dc76 100644
--- a/indra/llui/lluiimage.cpp
+++ b/indra/llui/lluiimage.cpp
@@ -168,6 +168,7 @@ namespace LLInitParam
 		if (name() == "none")
 		{
 			mData.mValue = NULL;
+			return;
 		}
 
 		LLUIImage* imagep =  LLUI::getUIImage(name());
diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp
index a8d8626e49e..f1b08c380b5 100644
--- a/indra/llui/llview.cpp
+++ b/indra/llui/llview.cpp
@@ -126,7 +126,7 @@ LLView::LLView(const LLView::Params& p)
 :	mName(p.name),
 	mParentView(NULL),
 	mReshapeFlags(FOLLOWS_NONE),
-	mSaveToXML(p.from_xui),
+	mFromXUI(p.from_xui),
 	mIsFocusRoot(FALSE),
 	mLastVisible(FALSE),
 	mNextInsertionOrdinal(0),
@@ -2479,7 +2479,7 @@ static bool get_last_child_rect(LLView* parent, LLRect *rect)
 	for (;itor != parent->getChildList()->end(); ++itor)
 	{
 		LLView *last_view = (*itor);
-		if (last_view->getSaveToXML())
+		if (last_view->getFromXUI())
 		{
 			*rect = last_view->getRect();
 			return true;
@@ -2489,7 +2489,7 @@ static bool get_last_child_rect(LLView* parent, LLRect *rect)
 }
 
 //static
-void LLView::setupParams(LLView::Params& p, LLView* parent)
+void LLView::applyXUILayout(LLView::Params& p, LLView* parent)
 {
 	const S32 VPAD = 4;
 	const S32 MIN_WIDGET_HEIGHT = 10;
@@ -2509,7 +2509,7 @@ void LLView::setupParams(LLView::Params& p, LLView* parent)
 		LLRect last_rect = parent->getLocalRect();
 
 		bool layout_topleft = (p.layout() == "topleft");
-		if (layout_topleft && p.from_xui)
+		if (layout_topleft)
 		{
 			//invert top to bottom
 			if (p.rect.top.isProvided()) p.rect.top = parent_rect.getHeight() - p.rect.top;
@@ -2535,10 +2535,10 @@ void LLView::setupParams(LLView::Params& p, LLView* parent)
 				p.rect.right.setProvided(false); // recalculate the right
 			}
 		}
-		else if (p.from_xui) // only do negative coordinate magic for XUI
+		else
 		{
-			if (p.rect.left < 0) p.rect.left = p.rect.left + parent_rect.getWidth();
-			if (p.rect.right < 0) p.rect.right = p.rect.right + parent_rect.getWidth();
+			if (p.rect.left.isProvided() && p.rect.left < 0) p.rect.left = p.rect.left + parent_rect.getWidth();
+			if (p.rect.right.isProvided() && p.rect.right < 0) p.rect.right = p.rect.right + parent_rect.getWidth();
 		}
 		if (p.center_vert)
 		{
@@ -2556,15 +2556,15 @@ void LLView::setupParams(LLView::Params& p, LLView* parent)
 				p.rect.top.setProvided(false); // recalculate the top
 			}
 		}
-		else if (p.from_xui)
+		else
 		{
-			if (p.rect.bottom < 0) p.rect.bottom = p.rect.bottom + parent_rect.getHeight();
-			if (p.rect.top < 0) p.rect.top = p.rect.top + parent_rect.getHeight();
+			if (p.rect.bottom.isProvided() && p.rect.bottom < 0) p.rect.bottom = p.rect.bottom + parent_rect.getHeight();
+			if (p.rect.top.isProvided() && p.rect.top < 0) p.rect.top = p.rect.top + parent_rect.getHeight();
 		}
 
 
 		// DEPRECATE: automatically fall back to height of MIN_WIDGET_HEIGHT pixels
-		if (!p.rect.height.isProvided() && !p.rect.top.isProvided())
+		if (!p.rect.height.isProvided() && !p.rect.top.isProvided() && p.rect.height == 0)
 		{
 			p.rect.height = MIN_WIDGET_HEIGHT;
 		}
@@ -2663,7 +2663,7 @@ static void convert_to_relative_layout(LLView::Params& p, LLView* parent)
 	// Use setupParams to get the final widget rectangle
 	// according to our wacky layout rules.
 	LLView::Params final = p;
-	LLView::setupParams(final, parent);
+	LLView::applyXUILayout(final, parent);
 	// Must actually extract the rectangle to get consistent
 	// right = left+width, top = bottom+height
 	LLRect final_rect = final.rect;
diff --git a/indra/llui/llview.h b/indra/llui/llview.h
index f8460f53616..c4d73137431 100644
--- a/indra/llui/llview.h
+++ b/indra/llui/llview.h
@@ -395,8 +395,8 @@ class LLView : public LLMouseHandler, public LLMortician, public LLFocusableElem
 	void parseFollowsFlags(const LLView::Params& params);
 
 	// Some widgets, like close box buttons, don't need to be saved
-	BOOL getSaveToXML() const { return mSaveToXML; }
-	void setSaveToXML(BOOL b) { mSaveToXML = b; }
+	BOOL getFromXUI() const { return mFromXUI; }
+	void setFromXUI(BOOL b) { mFromXUI = b; }
 
 	typedef enum e_hit_test_type
 	{
@@ -498,7 +498,7 @@ class LLView : public LLMouseHandler, public LLMortician, public LLFocusableElem
 
 	// Set up params after XML load before calling new(),
 	// usually to adjust layout.
-	static void setupParams(Params& p, LLView* parent);
+	static void applyXUILayout(Params& p, LLView* parent);
 
 	// For re-export of floaters and panels, convert the coordinate system
 	// to be top-left based.
@@ -573,7 +573,7 @@ class LLView : public LLMouseHandler, public LLMortician, public LLFocusableElem
 	LLUIString	mToolTipMsg;	// isNull() is true if none.
 
 	U8          mSoundFlags;
-	BOOL		mSaveToXML;
+	BOOL		mFromXUI;
 
 	BOOL		mIsFocusRoot;
 	BOOL		mUseBoundingRect; // hit test against bounding rectangle that includes all child elements
diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp
index ee60df1b4be..6f7c60ba954 100644
--- a/indra/newview/llchathistory.cpp
+++ b/indra/newview/llchathistory.cpp
@@ -44,6 +44,7 @@
 #include "llfloaterreg.h"
 #include "llmutelist.h"
 #include "llstylemap.h"
+#include "lllayoutstack.h"
 
 #include "llsidetray.h"//for blocked objects panel
 
@@ -311,18 +312,23 @@ class LLChatHistoryHeader: public LLPanel
 
 
 LLChatHistory::LLChatHistory(const LLChatHistory::Params& p)
-: LLTextEditor(p),
-mMessageHeaderFilename(p.message_header),
-mMessageSeparatorFilename(p.message_separator),
-mLeftTextPad(p.left_text_pad),
-mRightTextPad(p.right_text_pad),
-mLeftWidgetPad(p.left_widget_pad),
-mRightWidgetPad(p.right_widget_pad),
-mTopSeparatorPad(p.top_separator_pad),
-mBottomSeparatorPad(p.bottom_separator_pad),
-mTopHeaderPad(p.top_header_pad),
-mBottomHeaderPad(p.bottom_header_pad)
+:	LLUICtrl(p),
+	mMessageHeaderFilename(p.message_header),
+	mMessageSeparatorFilename(p.message_separator),
+	mLeftTextPad(p.left_text_pad),
+	mRightTextPad(p.right_text_pad),
+	mLeftWidgetPad(p.left_widget_pad),
+	mRightWidgetPad(p.right_widget_pad),
+	mTopSeparatorPad(p.top_separator_pad),
+	mBottomSeparatorPad(p.bottom_separator_pad),
+	mTopHeaderPad(p.top_header_pad),
+	mBottomHeaderPad(p.bottom_header_pad)
 {
+	LLTextEditor::Params editor_params(p);
+	editor_params.rect = getLocalRect();
+	editor_params.follows.flags = FOLLOWS_ALL;
+	editor_params.enabled = false; // read only
+	mEditor = LLUICtrlFactory::create<LLTextEditor>(editor_params, this);
 }
 
 LLChatHistory::~LLChatHistory()
@@ -330,6 +336,49 @@ LLChatHistory::~LLChatHistory()
 	this->clear();
 }
 
+void LLChatHistory::initFromParams(const LLChatHistory::Params& p)
+{
+	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
+
+	LLRect stack_rect = getLocalRect();
+	stack_rect.mRight -= scrollbar_size;
+	LLLayoutStack::Params layout_p;
+	layout_p.rect = stack_rect;
+	layout_p.follows.flags = FOLLOWS_ALL;
+	layout_p.orientation = "vertical";
+	layout_p.mouse_opaque = false;
+	
+	LLLayoutStack* stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p, this);
+	
+	const S32 NEW_TEXT_NOTICE_HEIGHT = 20;
+	
+	LLPanel::Params panel_p;
+	panel_p.name = "spacer";
+	panel_p.background_visible = false;
+	panel_p.has_border = false;
+	panel_p.mouse_opaque = false;
+	stackp->addPanel(LLUICtrlFactory::create<LLPanel>(panel_p), 0, 30, true, false, LLLayoutStack::ANIMATE);
+
+	panel_p.name = "new_text_notice_holder";
+	LLRect new_text_notice_rect = getLocalRect();
+	new_text_notice_rect.mTop = new_text_notice_rect.mBottom + NEW_TEXT_NOTICE_HEIGHT;
+	panel_p.rect = new_text_notice_rect;
+	panel_p.background_opaque = true;
+	panel_p.background_visible = true;
+	panel_p.visible = false;
+	mMoreChatPanel = LLUICtrlFactory::create<LLPanel>(panel_p);
+	
+	LLTextBox::Params text_p(p.more_chat_text);
+	text_p.rect = mMoreChatPanel->getLocalRect();
+	text_p.follows.flags = FOLLOWS_ALL;
+	text_p.name = "more_chat_text";
+	mMoreChatText = LLUICtrlFactory::create<LLTextBox>(text_p, mMoreChatPanel);
+	mMoreChatText->setClickedCallback(boost::bind(&LLChatHistory::onClickMoreText, this));
+
+	stackp->addPanel(mMoreChatPanel, 0, 0, false, false, LLLayoutStack::ANIMATE);
+}
+
+
 /*void LLChatHistory::updateTextRect()
 {
 	static LLUICachedControl<S32> texteditor_border ("UITextEditorBorder", 0);
@@ -358,15 +407,49 @@ LLView* LLChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style
 	return header;
 }
 
+void LLChatHistory::onClickMoreText()
+{
+	mEditor->endOfDoc();
+}
+
 void LLChatHistory::clear()
 {
 	mLastFromName.clear();
-	LLTextEditor::clear();
+	mEditor->clear();
 	mLastFromID = LLUUID::null;
 }
 
 void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_chat_history, const LLStyle::Params& input_append_params)
 {
+	if (!mEditor->scrolledToEnd() && chat.mFromID != gAgent.getID())
+	{
+		mUnreadChatSources.insert(chat.mFromName);
+		mMoreChatPanel->setVisible(TRUE);
+		std::string chatters;
+		for (unread_chat_source_t::iterator it = mUnreadChatSources.begin();
+			it != mUnreadChatSources.end();)
+		{
+			chatters += *it;
+			if (++it != mUnreadChatSources.end())
+			{
+				chatters += ",";
+			}
+		}
+		LLStringUtil::format_map_t args;
+		args["SOURCES"] = chatters;
+
+		if (mUnreadChatSources.size() == 1)
+		{
+			mMoreChatText->setValue(LLTrans::getString("unread_chat_single", args));
+		}
+		else
+		{
+			mMoreChatText->setValue(LLTrans::getString("unread_chat_multiple", args));
+		}
+		S32 height = mMoreChatText->getTextPixelHeight() + 5;
+		mMoreChatPanel->reshape(mMoreChatPanel->getRect().getWidth(), height);
+	}
+
 	LLColor4 txt_color = LLUIColorTable::instance().getColor("White");
 	LLViewerChat::getChatColor(chat,txt_color);
 	LLFontGL* fontp = LLViewerChat::getChatFont();	
@@ -381,7 +464,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_
 	
 	if (use_plain_text_chat_history)
 	{
-		appendText("[" + chat.mTimeStr + "] ", getText().size() != 0, style_params);
+		mEditor->appendText("[" + chat.mTimeStr + "] ", mEditor->getText().size() != 0, style_params);
 
 		if (utf8str_trim(chat.mFromName).size() != 0)
 		{
@@ -391,11 +474,11 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_
 				LLStyle::Params link_params(style_params);
 				link_params.fillFrom(LLStyleMap::instance().lookupAgent(chat.mFromID));
 				// Convert the name to a hotlink and add to message.
-				appendText(chat.mFromName + ": ", false, link_params);
+				mEditor->appendText(chat.mFromName + ": ", false, link_params);
 			}
 			else
 			{
-				appendText(chat.mFromName + ": ", false, style_params);
+				mEditor->appendText(chat.mFromName + ": ", false, style_params);
 			}
 		}
 	}
@@ -422,7 +505,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_
 		else
 		{
 			view = getHeader(chat, style_params);
-			if (getText().size() == 0)
+			if (mEditor->getText().size() == 0)
 				p.top_pad = 0;
 			else
 				p.top_pad = mTopHeaderPad;
@@ -432,9 +515,9 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_
 		p.view = view;
 
 		//Prepare the rect for the view
-		LLRect target_rect = getDocumentView()->getRect();
+		LLRect target_rect = mEditor->getDocumentView()->getRect();
 		// squeeze down the widget by subtracting padding off left and right
-		target_rect.mLeft += mLeftWidgetPad + mHPad;
+		target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad();
 		target_rect.mRight -= mRightWidgetPad;
 		view->reshape(target_rect.getWidth(), view->getRect().getHeight());
 		view->setOrigin(target_rect.mLeft, view->getRect().mBottom);
@@ -443,7 +526,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_
 		if (utf8str_trim(chat.mFromName).size() != 0 && chat.mFromName != SYSTEM_FROM)
 			header_text += chat.mFromName + ": ";
 
-		appendWidget(p, header_text, false);
+		mEditor->appendWidget(p, header_text, false);
 		mLastFromName = chat.mFromName;
 		mLastFromID = chat.mFromID;
 		mLastMessageTime = new_message_time;
@@ -455,10 +538,10 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_
 		style_params.font.style = "ITALIC";
 
 		if (chat.mFromName.size() > 0)
-			appendText(chat.mFromName + " ", TRUE, style_params);
+			mEditor->appendText(chat.mFromName + " ", TRUE, style_params);
 		// Ensure that message ends with NewLine, to avoid losing of new lines
 		// while copy/paste from text chat. See EXT-3263.
-		appendText(chat.mText.substr(4) + NEW_LINE, FALSE, style_params);
+		mEditor->appendText(chat.mText.substr(4) + NEW_LINE, FALSE, style_params);
 	}
 	else
 	{
@@ -469,8 +552,19 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_
 			// while copy/paste from text chat. See EXT-3263.
 			message += NEW_LINE;
 		}
-		appendText(message, FALSE, style_params);
+		mEditor->appendText(message, FALSE, style_params);
 	}
-	blockUndo();
+	mEditor->blockUndo();
+}
+
+void LLChatHistory::draw()
+{
+	if (mEditor->scrolledToEnd())
+	{
+		mUnreadChatSources.clear();
+		mMoreChatPanel->setVisible(FALSE);
+	}
+
+	LLUICtrl::draw();
 }
 
diff --git a/indra/newview/llchathistory.h b/indra/newview/llchathistory.h
index 8ca7dd1d589..260015e2dc8 100644
--- a/indra/newview/llchathistory.h
+++ b/indra/newview/llchathistory.h
@@ -34,10 +34,11 @@
 #define LLCHATHISTORY_H_
 
 #include "lltexteditor.h"
+#include "lltextbox.h"
 #include "llviewerchat.h"
 
 //Chat log widget allowing addition of a message as a widget 
-class LLChatHistory : public LLTextEditor
+class LLChatHistory : public LLUICtrl
 {
 	public:
 		struct Params : public LLInitParam::Block<Params, LLTextEditor::Params>
@@ -63,6 +64,8 @@ class LLChatHistory : public LLTextEditor
 			//Header bottom padding
 			Optional<S32>			bottom_header_pad;
 
+			Optional<LLTextBox::Params>	more_chat_text;
+
 			Params()
 			:	message_header("message_header"),
 				message_separator("message_separator"),
@@ -73,15 +76,16 @@ class LLChatHistory : public LLTextEditor
 				top_separator_pad("top_separator_pad"),
 				bottom_separator_pad("bottom_separator_pad"),
 				top_header_pad("top_header_pad"),
-				bottom_header_pad("bottom_header_pad")
-				{
-				}
+				bottom_header_pad("bottom_header_pad"),
+				more_chat_text("more_chat_text")
+			{}
 
 		};
 	protected:
 		LLChatHistory(const Params&);
 		friend class LLUICtrlFactory;
 
+		/*virtual*/ void draw();
 		/**
 		 * Redefinition of LLTextEditor::updateTextRect() to considerate text
 		 * left/right padding params.
@@ -98,9 +102,13 @@ class LLChatHistory : public LLTextEditor
 		 */
 		LLView* getHeader(const LLChat& chat,const LLStyle::Params& style_params);
 
+		void onClickMoreText();
+
 	public:
 		~LLChatHistory();
 
+		void initFromParams(const Params&);
+
 		/**
 		 * Appends a widget message.
 		 * If last user appended message, concurs with current user,
@@ -129,5 +137,11 @@ class LLChatHistory : public LLTextEditor
 		S32 mBottomSeparatorPad;
 		S32 mTopHeaderPad;
 		S32 mBottomHeaderPad;
+
+		LLPanel*		mMoreChatPanel;
+		LLTextBox*		mMoreChatText;
+		LLTextEditor*	mEditor;
+		typedef std::set<std::string> unread_chat_source_t;
+		unread_chat_source_t mUnreadChatSources;
 };
 #endif /* LLCHATHISTORY_H_ */
diff --git a/indra/newview/skins/default/xui/en/floater_aaa.xml b/indra/newview/skins/default/xui/en/floater_aaa.xml
index 6956b733714..cb4cbd229a8 100644
--- a/indra/newview/skins/default/xui/en/floater_aaa.xml
+++ b/indra/newview/skins/default/xui/en/floater_aaa.xml
@@ -15,21 +15,21 @@
  title="TEST FLOATER"
  save_dock_state="true"
  save_visibility="true"
- single_instance="true"
+ single_instance="true" 
  width="320">
   <string name="nudge_parabuild">Nudge 1</string>
   <string name="test_the_vlt">This string is extracted.</string>
   <chat_history
    allow_html="true"
    bg_readonly_color="ChatHistoryBgColor"
-   bg_writeable_color="ChatHistoryBgColor"
-   border_visible="false" 
+   bg_writeable_color="ChatHistoryBgColor" 
+   border_visible="false"
    follows="all"
    font="SansSerif" 
-	    left="1"
+	 left="1"
    top="20"
    layout="topleft"
-	    height="260"
+	 height="260"
    name="chat_history"
    parse_highlights="true"
    text_color="ChatHistoryTextColor"
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index ec4723bd55d..1ab25072321 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -2929,4 +2929,10 @@ If you continue to receive this message, contact the [SUPPORT_SITE].
   <string name="close_on_no_ability">
     You no longer have the ability to be in the chat session.
   </string>
+  <string name="unread_chat_single">
+    [SOURCES] has said something new
+  </string>"
+  <string name="unread_chat_multiple">
+    [SOURCES] have said something new
+  </string>"
 </strings>
diff --git a/indra/newview/skins/default/xui/en/widgets/chat_history.xml b/indra/newview/skins/default/xui/en/widgets/chat_history.xml
index 2be37d222a5..8785dff2ae5 100644
--- a/indra/newview/skins/default/xui/en/widgets/chat_history.xml
+++ b/indra/newview/skins/default/xui/en/widgets/chat_history.xml
@@ -11,9 +11,13 @@
 	top_header_pad="17"
 	bottom_header_pad="10"
 	max_length="2147483647"
-	enabled="false"
 	track_bottom="true"
 	name="chat_history"
 	type="string"
 	word_wrap="true"
-  font="SansSerif"/>
+  font="SansSerif">
+  <more_chat_text
+    mouse_opaque="true" 
+    word_wrap="true"
+    />
+</chat_history>
-- 
GitLab