From c5f6ff43da6a7cf5a20c61c16ba0c7aaaeb5687f Mon Sep 17 00:00:00 2001
From: Kelly Washington <kelly@lindenlab.com>
Date: Mon, 12 Nov 2012 15:38:58 -0800
Subject: MAINT-1897 Poor performance viewing large group member lists *
 Improve draw performance of scroll lists by only drawing what is visible *
 Reduce frequency of expensive column width calculations Fixes by Richard

---
 indra/llui/llcombobox.cpp         |   3 +-
 indra/llui/llscrolllistcolumn.cpp |   3 +
 indra/llui/llscrolllistctrl.cpp   | 161 ++++++++++++++++++++++----------------
 indra/llui/llscrolllistctrl.h     |  28 +++----
 indra/llui/llview.cpp             |  12 +--
 5 files changed, 118 insertions(+), 89 deletions(-)

(limited to 'indra/llui')

diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp
index 806d2ef3f6..41e5d74042 100644
--- a/indra/llui/llcombobox.cpp
+++ b/indra/llui/llcombobox.cpp
@@ -563,8 +563,7 @@ void LLComboBox::showList()
 	S32 min_width = getRect().getWidth();
 	S32 max_width = llmax(min_width, MAX_COMBO_WIDTH);
 	// make sure we have up to date content width metrics
-	mList->calcColumnWidths();
-	S32 list_width = llclamp(mList->getMaxContentWidth(), min_width, max_width);
+	S32 list_width = llclamp(mList->calcMaxContentWidth(), min_width, max_width);
 
 	if (mListPosition == BELOW)
 	{
diff --git a/indra/llui/llscrolllistcolumn.cpp b/indra/llui/llscrolllistcolumn.cpp
index 07a6dfaa10..af124d9826 100644
--- a/indra/llui/llscrolllistcolumn.cpp
+++ b/indra/llui/llscrolllistcolumn.cpp
@@ -98,6 +98,7 @@ BOOL LLScrollColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask)
 	if (canResize() && mResizeBar->getRect().pointInRect(x, y))
 	{
 		// reshape column to max content width
+		mColumn->mParentCtrl->calcMaxContentWidth();
 		LLRect column_rect = getRect();
 		column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth;
 		setShape(column_rect, true);
@@ -127,6 +128,8 @@ LLView*	LLScrollColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& m
 
 	LLRect snap_rect = getSnapRect();
 
+	mColumn->mParentCtrl->calcMaxContentWidth();
+
 	S32 snap_delta = mColumn->mMaxContentWidth - snap_rect.getWidth();
 
 	// x coord growing means column growing, so same signs mean we're going in right direction
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index da371622be..3e0653e9a4 100644
--- a/indra/llui/llscrolllistctrl.cpp
+++ b/indra/llui/llscrolllistctrl.cpp
@@ -159,15 +159,14 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
 	mMouseWheelOpaque(p.mouse_wheel_opaque),
 	mPageLines(p.page_lines),
 	mMaxSelectable(0),
-	mAllowKeyboardMovement(TRUE),
+	mAllowKeyboardMovement(true),
 	mCommitOnKeyboardMovement(p.commit_on_keyboard_movement),
-	mCommitOnSelectionChange(FALSE),
-	mSelectionChanged(FALSE),
-	mNeedsScroll(FALSE),
-	mCanSelect(TRUE),
-	mColumnsDirty(FALSE),
+	mCommitOnSelectionChange(false),
+	mSelectionChanged(false),
+	mNeedsScroll(false),
+	mCanSelect(true),
+	mColumnsDirty(false),
 	mMaxItemCount(INT_MAX), 
-	mMaxContentWidth(0),
 	mBorderThickness( 2 ),
 	mOnDoubleClickCallback( NULL ),
 	mOnMaximumSelectCallback( NULL ),
@@ -181,7 +180,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
 	mTotalStaticColumnWidth(0),
 	mTotalColumnPadding(0),
 	mSorted(false),
-	mDirty(FALSE),
+	mDirty(false),
 	mOriginalSelection(-1),
 	mLastSelected(NULL),
 	mHeadingHeight(p.heading_height),
@@ -357,7 +356,7 @@ void LLScrollListCtrl::clearRows()
 	mScrollLines = 0;
 	mLastSelected = NULL;
 	updateLayout();
-	mDirty = FALSE; 
+	mDirty = false; 
 }
 
 
@@ -592,15 +591,42 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL r
 // NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame
 //  while receiving a long list of names.
 // *TODO: Use bookkeeping to make this an incramental cost with item additions
-void LLScrollListCtrl::calcColumnWidths()
+S32 LLScrollListCtrl::calcMaxContentWidth()
 {
 	const S32 HEADING_TEXT_PADDING = 25;
 	const S32 COLUMN_TEXT_PADDING = 10;
 
-	mMaxContentWidth = 0;
-
 	S32 max_item_width = 0;
 
+	ordered_columns_t::iterator column_itor;
+	for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
+	{
+		LLScrollListColumn* column = *column_itor;
+		if (!column) continue;
+
+		if (mColumnWidthsDirty)
+		{
+			mColumnWidthsDirty = false;
+			// update max content width for this column, by looking at all items
+			column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
+			item_list::iterator iter;
+			for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+			{
+				LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
+				if (!cellp) continue;
+
+				column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
+			}
+		}
+		max_item_width += column->mMaxContentWidth;
+	}
+
+	return max_item_width;
+}
+
+bool LLScrollListCtrl::updateColumnWidths()
+{
+	bool width_changed = false;
 	ordered_columns_t::iterator column_itor;
 	for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor)
 	{
@@ -618,23 +644,13 @@ void LLScrollListCtrl::calcColumnWidths()
 			new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns;
 		}
 
-		column->setWidth(new_width);
-
-		// update max content width for this column, by looking at all items
-		column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
-		item_list::iterator iter;
-		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+		if (column->getWidth() != new_width)
 		{
-			LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex);
-			if (!cellp) continue;
-
-			column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth);
+			column->setWidth(new_width);
+			width_changed = true;
 		}
-
-		max_item_width += column->mMaxContentWidth;
 	}
-
-	mMaxContentWidth = max_item_width;
+	return width_changed;
 }
 
 const S32 SCROLL_LIST_ROW_PAD = 2;
@@ -670,7 +686,12 @@ void LLScrollListCtrl::updateLineHeightInsert(LLScrollListItem* itemp)
 
 void LLScrollListCtrl::updateColumns()
 {
-	calcColumnWidths();
+	if (!mColumnsDirty)
+		return;
+
+	mColumnsDirty = false;
+
+	bool columns_changed_width = updateColumnWidths();
 
 	// update column headers
 	std::vector<LLScrollListColumn*>::iterator column_ordered_it;
@@ -719,20 +740,22 @@ void LLScrollListCtrl::updateColumns()
 	}
 
 	// propagate column widths to individual cells
-	item_list::iterator iter;
-	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+	if (columns_changed_width)
 	{
-		LLScrollListItem *itemp = *iter;
-		S32 num_cols = itemp->getNumColumns();
-		S32 i = 0;
-		for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
+		item_list::iterator iter;
+		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
 		{
-			if (i >= (S32)mColumnsIndexed.size()) break;
+			LLScrollListItem *itemp = *iter;
+			S32 num_cols = itemp->getNumColumns();
+			S32 i = 0;
+			for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
+			{
+				if (i >= (S32)mColumnsIndexed.size()) break;
 
-			cell->setWidth(mColumnsIndexed[i]->getWidth());
+				cell->setWidth(mColumnsIndexed[i]->getWidth());
+			}
 		}
 	}
-
 }
 
 void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
@@ -773,7 +796,7 @@ BOOL LLScrollListCtrl::selectFirstItem()
 		{
 			deselectItem(itemp);
 		}
-		first_item = FALSE;
+		first_item = false;
 	}
 	if (mCommitOnSelectionChange)
 	{
@@ -1407,17 +1430,23 @@ void LLScrollListCtrl::drawItems()
 
 		S32 cur_y = y;
 		
-		S32 line = 0;
 		S32 max_columns = 0;
 
 		LLColor4 highlight_color = LLColor4::white;
 		static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0);
 		highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout, 0.4f, 0.f);
 
+		S32 first_line = mScrollLines;
+		S32 last_line = llmin((S32)mItemList.size() - 1, mScrollLines + getLinesPerPage());
+
+		if (first_line >= mItemList.size())
+		{
+			return;
+		}
 		item_list::iterator iter;
-		for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+		for (S32 line = first_line; line <= last_line; line++)
 		{
-			LLScrollListItem* item = *iter;
+			LLScrollListItem* item = mItemList[line];
 			
 			item_rect.setOriginAndSize( 
 				x, 
@@ -1481,7 +1510,6 @@ void LLScrollListCtrl::drawItems()
 
 				cur_y -= mLineHeight;
 			}
-			line++;
 		}
 	}
 }
@@ -1497,7 +1525,7 @@ void LLScrollListCtrl::draw()
 	if (mNeedsScroll)
 	{
 		scrollToShowSelected();
-		mNeedsScroll = FALSE;
+		mNeedsScroll = false;
 	}
 	LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0);
 	// Draw background
@@ -1508,11 +1536,7 @@ void LLScrollListCtrl::draw()
 		gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() % alpha : mBgReadOnlyColor.get() % alpha );
 	}
 
-	if (mColumnsDirty)
-	{
-		updateColumns();
-		mColumnsDirty = FALSE;
-	}
+	updateColumns();
 
 	getChildView("comment_text")->setVisible(mItemList.empty());
 
@@ -1720,7 +1744,7 @@ BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask)
 		setFocus(TRUE);
 
 		// clear selection changed flag because user is starting a selection operation
-		mSelectionChanged = FALSE;
+		mSelectionChanged = false;
 
 		handleClick(x, y, mask);
 	}
@@ -1738,15 +1762,15 @@ BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
 		if(mask == MASK_NONE)
 		{
 			selectItemAt(x, y, mask);
-			mNeedsScroll = TRUE;
+			mNeedsScroll = true;
 		}
 	}
 
 	// always commit when mouse operation is completed inside list
 	if (mItemListRect.pointInRect(x,y))
 	{
-		mDirty |= mSelectionChanged;
-		mSelectionChanged = FALSE;
+		mDirty = mDirty || mSelectionChanged;
+		mSelectionChanged = false;
 		onCommit();
 	}
 
@@ -1862,7 +1886,7 @@ BOOL LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask)
 		{
 			selectItemAt(x, y, mask);
 			gFocusMgr.setMouseCapture(this);
-			mNeedsScroll = TRUE;
+			mNeedsScroll = true;
 		}
 		
 		// propagate state of cell to rest of selected column
@@ -1891,7 +1915,7 @@ BOOL LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask)
 		// treat this as a normal single item selection
 		selectItemAt(x, y, mask);
 		gFocusMgr.setMouseCapture(this);
-		mNeedsScroll = TRUE;
+		mNeedsScroll = true;
 		// do not eat click (allow double click callback)
 		return FALSE;
 	}
@@ -1997,7 +2021,7 @@ BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask)
 		if(mask == MASK_NONE)
 		{
 			selectItemAt(x, y, mask);
-			mNeedsScroll = TRUE;
+			mNeedsScroll = true;
 		}
 	}
 	else 
@@ -2044,7 +2068,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
 				{
 					// commit implicit in call
 					selectPrevItem(FALSE);
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					handled = TRUE;
 				}
 				break;
@@ -2053,7 +2077,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
 				{
 					// commit implicit in call
 					selectNextItem(FALSE);
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					handled = TRUE;
 				}
 				break;
@@ -2061,7 +2085,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
 				if (mAllowKeyboardMovement || hasFocus())
 				{
 					selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1));
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					if (mCommitOnKeyboardMovement
 						&& !mCommitOnSelectionChange) 
 					{
@@ -2074,7 +2098,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
 				if (mAllowKeyboardMovement || hasFocus())
 				{
 					selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1));
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					if (mCommitOnKeyboardMovement
 						&& !mCommitOnSelectionChange) 
 					{
@@ -2087,7 +2111,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
 				if (mAllowKeyboardMovement || hasFocus())
 				{
 					selectFirstItem();
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					if (mCommitOnKeyboardMovement
 						&& !mCommitOnSelectionChange) 
 					{
@@ -2100,7 +2124,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
 				if (mAllowKeyboardMovement || hasFocus())
 				{
 					selectNthItem(getItemCount() - 1);
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					if (mCommitOnKeyboardMovement
 						&& !mCommitOnSelectionChange) 
 					{
@@ -2139,7 +2163,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
 				}
 				else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), FALSE))
 				{
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					// update search string only on successful match
 					mSearchTimer.reset();
 
@@ -2180,7 +2204,7 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char)
 	if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), FALSE))
 	{
 		// update search string only on successful match
-		mNeedsScroll = TRUE;
+		mNeedsScroll = true;
 		mSearchString += uni_char;
 		mSearchTimer.reset();
 
@@ -2226,7 +2250,7 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char)
 				if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char)
 				{
 					selectItem(item);
-					mNeedsScroll = TRUE;
+					mNeedsScroll = true;
 					cellp->highlightText(0, 1);
 					mSearchTimer.reset();
 
@@ -2297,7 +2321,7 @@ void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, BOOL select_single_it
 		}
 		itemp->setSelected(TRUE);
 		mLastSelected = itemp;
-		mSelectionChanged = TRUE;
+		mSelectionChanged = true;
 	}
 }
 
@@ -2318,7 +2342,7 @@ void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
 		{
 			cellp->highlightText(0, 0);	
 		}
-		mSelectionChanged = TRUE;
+		mSelectionChanged = true;
 	}
 }
 
@@ -2326,7 +2350,7 @@ void LLScrollListCtrl::commitIfChanged()
 {
 	if (mSelectionChanged)
 	{
-		mDirty = TRUE;
+		mDirty = true;
 		mSelectionChanged = FALSE;
 		onCommit();
 	}
@@ -2437,7 +2461,8 @@ void LLScrollListCtrl::sortOnce(S32 column, BOOL ascending)
 
 void LLScrollListCtrl::dirtyColumns() 
 { 
-	mColumnsDirty = TRUE; 
+	mColumnsDirty = true; 
+	mColumnWidthsDirty = true;
 
 	// need to keep mColumnsIndexed up to date
 	// just in case someone indexes into it immediately
@@ -3006,7 +3031,7 @@ void LLScrollListCtrl::resetDirty()
 void LLScrollListCtrl::onFocusReceived()
 {
 	// forget latent selection changes when getting focus
-	mSelectionChanged = FALSE;
+	mSelectionChanged = false;
 	LLUICtrl::onFocusReceived();
 }
 
diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h
index e83794e173..38450b6313 100644
--- a/indra/llui/llscrolllistctrl.h
+++ b/indra/llui/llscrolllistctrl.h
@@ -343,8 +343,8 @@ public:
 	static void onClickColumn(void *userdata);
 
 	virtual void updateColumns();
-	void calcColumnWidths();
-	S32 getMaxContentWidth() { return mMaxContentWidth; }
+	S32 calcMaxContentWidth();
+	bool updateColumnWidths();
 
 	void setHeadingHeight(S32 heading_height);
 	/**
@@ -440,16 +440,17 @@ private:
 	S32				mHeadingHeight;	// the height of the column header buttons, if visible
 	U32				mMaxSelectable; 
 	LLScrollbar*	mScrollbar;
-	BOOL 			mAllowMultipleSelection;
-	BOOL			mAllowKeyboardMovement;
-	BOOL			mCommitOnKeyboardMovement;
-	BOOL			mCommitOnSelectionChange;
-	BOOL			mSelectionChanged;
-	BOOL			mNeedsScroll;
-	BOOL			mMouseWheelOpaque;
-	BOOL			mCanSelect;
-	const BOOL		mDisplayColumnHeaders;
-	BOOL			mColumnsDirty;
+	bool 			mAllowMultipleSelection;
+	bool			mAllowKeyboardMovement;
+	bool			mCommitOnKeyboardMovement;
+	bool			mCommitOnSelectionChange;
+	bool			mSelectionChanged;
+	bool			mNeedsScroll;
+	bool			mMouseWheelOpaque;
+	bool			mCanSelect;
+	bool			mDisplayColumnHeaders;
+	bool			mColumnsDirty;
+	bool			mColumnWidthsDirty;
 
 	mutable item_list	mItemList;
 
@@ -458,7 +459,6 @@ private:
 	S32				mMaxItemCount; 
 
 	LLRect			mItemListRect;
-	S32				mMaxContentWidth;
 	S32             mColumnPadding;
 
 	BOOL			mBackgroundVisible;
@@ -498,7 +498,7 @@ private:
 	typedef std::map<std::string, LLScrollListColumn*> column_map_t;
 	column_map_t mColumns;
 
-	BOOL			mDirty;
+	bool			mDirty;
 	S32				mOriginalSelection;
 
 	ContextMenuType mContextMenuType;
diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp
index 54843227b7..ad9bec9f61 100644
--- a/indra/llui/llview.cpp
+++ b/indra/llui/llview.cpp
@@ -1549,16 +1549,18 @@ void LLView::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* l
 
 void LLView::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const
 {
-	*screen_x = local_x + getRect().mLeft;
-	*screen_y = local_y + getRect().mBottom;
+	*screen_x = local_x;
+	*screen_y = local_y;
 
 	const LLView* cur = this;
-	while( cur->mParentView )
+	do
 	{
+		LLRect cur_rect = cur->getRect();
+		*screen_x += cur_rect.mLeft;
+		*screen_y += cur_rect.mBottom;
 		cur = cur->mParentView;
-		*screen_x += cur->getRect().mLeft;
-		*screen_y += cur->getRect().mBottom;
 	}
+	while( cur );
 }
 
 void LLView::screenRectToLocal(const LLRect& screen, LLRect* local) const
-- 
cgit v1.2.3