From d6edc4cd12c015003cd665028b69a7b34b3aa54f Mon Sep 17 00:00:00 2001
From: Eugene Mutavchi <emutavchi@productengine.com>
Date: Fri, 30 Apr 2010 18:27:24 +0300
Subject: [PATCH] Implemented normal EXT-7002 (Inventory flat list needs
 optimization.):  - changed the LLInventoryItemsList::addNewItem() to add item
 to the list without immediately rearranging  - implemented
 LLFlatListViewEx::setFilterSubString(), sets up new filter string and filters
 the list.  - implemented LLFlatListViewEx::filterItems(), filters the list,
 rearranges and notifies parent about shape changes. The list items are
 filtered using the notify() functionality, without need in adding/removing
 them on each filter call. It sends 'match_filter' request to items and
 interprets the returned zero as sign of matched filter string, i.e. we don't
 hide items that don't support 'match_filter' action(separators etc).  -
 filtring of LLOutfitsList.  Reviewed by Mike Antipov at
 https://codereview.productengine.com/secondlife/r/342/

--HG--
branch : product-engine
---
 indra/llui/llflatlistview.cpp          | 160 +++++++++++++++++++------
 indra/llui/llflatlistview.h            |  17 ++-
 indra/newview/llinventoryitemslist.cpp |  58 ++++++++-
 indra/newview/llinventoryitemslist.h   |  26 +++-
 indra/newview/lloutfitslist.cpp        |  28 +++++
 5 files changed, 240 insertions(+), 49 deletions(-)

diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp
index ec247b25c32..bea2572ff83 100644
--- a/indra/llui/llflatlistview.cpp
+++ b/indra/llui/llflatlistview.cpp
@@ -297,6 +297,27 @@ void LLFlatListView::setNoItemsCommentText(const std::string& comment_text)
 	mNoItemsCommentTextbox->setValue(comment_text);
 }
 
+U32 LLFlatListView::size(const bool only_visible_items) const
+{
+	if (only_visible_items)
+	{
+		U32 size = 0;
+		for (pairs_const_iterator_t
+				 iter = mItemPairs.begin(),
+				 iter_end = mItemPairs.end();
+			 iter != iter_end; ++iter)
+		{
+			if ((*iter)->first->getVisible())
+				++size;
+		}
+		return size;
+	}
+	else
+	{
+		return mItemPairs.size();
+	}
+}
+
 void LLFlatListView::clear()
 {
 	// do not use LLView::deleteAllChildren to avoid removing nonvisible items. drag-n-drop for ex.
@@ -426,7 +447,7 @@ void LLFlatListView::rearrangeItems()
 {
 	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
 
-	setNoItemsCommentVisible(mItemPairs.empty());
+	setNoItemsCommentVisible(0==size());
 
 	if (mItemPairs.empty()) return;
 
@@ -745,19 +766,43 @@ LLRect LLFlatListView::getLastSelectedItemRect()
 void LLFlatListView::selectFirstItem	()
 {
 	// No items - no actions!
-	if (mItemPairs.empty()) return;
+	if (0 == size()) return;
 
-	selectItemPair(mItemPairs.front(), true);
-	ensureSelectedVisible();
+	// Select first visible item
+	for (pairs_iterator_t
+			 iter = mItemPairs.begin(),
+			 iter_end = mItemPairs.end();
+		 iter != iter_end; ++iter)
+	{
+		// skip invisible items
+		if ( (*iter)->first->getVisible() )
+		{
+			selectItemPair(*iter, true);
+			ensureSelectedVisible();
+			break;
+		}
+	}
 }
 
 void LLFlatListView::selectLastItem		()
 {
 	// No items - no actions!
-	if (mItemPairs.empty()) return;
+	if (0 == size()) return;
 
-	selectItemPair(mItemPairs.back(), true);
-	ensureSelectedVisible();
+	// Select last visible item
+	for (pairs_list_t::reverse_iterator
+			 r_iter = mItemPairs.rbegin(),
+			 r_iter_end = mItemPairs.rend();
+		 r_iter != r_iter_end; ++r_iter)
+	{
+		// skip invisible items
+		if ( (*r_iter)->first->getVisible() )
+		{
+			selectItemPair(*r_iter, true);
+			ensureSelectedVisible();
+			break;
+		}
+	}
 }
 
 void LLFlatListView::ensureSelectedVisible()
@@ -775,14 +820,14 @@ void LLFlatListView::ensureSelectedVisible()
 bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selection)
 {
 	// No items - no actions!
-	if ( !mItemPairs.size() )
+	if ( 0 == size() )
 		return false;
 
-	
-	item_pair_t* to_sel_pair = NULL;
-	item_pair_t* cur_sel_pair = NULL;
 	if ( mSelectedItemPairs.size() )
 	{
+		item_pair_t* to_sel_pair = NULL;
+		item_pair_t* cur_sel_pair = NULL;
+
 		// Take the last selected pair
 		cur_sel_pair = mSelectedItemPairs.back();
 		// Bases on given direction choose next item to select
@@ -816,42 +861,42 @@ bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selecti
 				}
 			}
 		}
+
+		if ( to_sel_pair )
+		{
+			bool select = true;
+			if ( reset_selection )
+			{
+				// Reset current selection if we were asked about it
+				resetSelection();
+			}
+			else
+			{
+				// If item already selected and no reset request than we should deselect last selected item.
+				select = (mSelectedItemPairs.end() == std::find(mSelectedItemPairs.begin(), mSelectedItemPairs.end(), to_sel_pair));
+			}
+			// Select/Deselect next item
+			selectItemPair(select ? to_sel_pair : cur_sel_pair, select);
+			return true;
+		}
 	}
 	else
 	{
 		// If there weren't selected items then choose the first one bases on given direction
-		cur_sel_pair = (is_up_direction) ? mItemPairs.back() : mItemPairs.front();
 		// Force selection to first item
-		to_sel_pair = cur_sel_pair;
-	}
-
-
-	if ( to_sel_pair )
-	{
-		bool select = true;
-
-		if ( reset_selection )
-		{
-			// Reset current selection if we were asked about it
-			resetSelection();
-		}
+		if (is_up_direction)
+			selectLastItem();
 		else
-		{
-			// If item already selected and no reset request than we should deselect last selected item.
-			select = (mSelectedItemPairs.end() == std::find(mSelectedItemPairs.begin(), mSelectedItemPairs.end(), to_sel_pair));
-		}
-
-		// Select/Deselect next item
-		selectItemPair(select ? to_sel_pair : cur_sel_pair, select);
-
+			selectFirstItem();
 		return true;
 	}
+
 	return false;
 }
 
 BOOL LLFlatListView::canSelectAll() const
 {
-	return !mItemPairs.empty() && mAllowSelection && mMultipleSelection;
+	return 0 != size() && mAllowSelection && mMultipleSelection;
 }
 
 void LLFlatListView::selectAll()
@@ -1167,4 +1212,51 @@ void LLFlatListViewEx::updateNoItemsMessage(const std::string& filter_string)
 
 }
 
+void LLFlatListViewEx::setFilterSubString(const std::string& filter_str)
+{
+	if (0 != LLStringUtil::compareInsensitive(filter_str, mFilterSubString))
+	{
+		mFilterSubString = filter_str;
+		updateNoItemsMessage(mFilterSubString);
+		filterItems();
+	}
+}
+
+void LLFlatListViewEx::filterItems()
+{
+	typedef std::vector <LLPanel*> item_panel_list_t;
+
+	std::string cur_filter = mFilterSubString;
+	LLStringUtil::toUpper(cur_filter);
+
+	LLSD action;
+	action.with("match_filter", cur_filter);
+
+	item_panel_list_t items;
+	getItems(items);
+
+	for (item_panel_list_t::iterator
+			 iter = items.begin(),
+			 iter_end = items.end();
+		 iter != iter_end; ++iter)
+	{
+		LLPanel* pItem = (*iter);
+		// 0 signifies that filter is matched,
+		// i.e. we don't hide items that don't support 'match_filter' action, separators etc.
+		if (0 == pItem->notify(action))
+		{
+			pItem->setVisible(true);
+		}
+		else
+		{
+			// TODO: implement (re)storing of current selection.
+			selectItem(pItem, false);
+			pItem->setVisible(false);
+		}
+	}
+
+	rearrangeItems();
+	notifyParentItemsRectChanged();
+}
+
 //EOF
diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h
index 4f718ab0dca..6395805aab3 100644
--- a/indra/llui/llflatlistview.h
+++ b/indra/llui/llflatlistview.h
@@ -264,9 +264,8 @@ class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler
 	/** Get number of selected items in the list */
 	U32 numSelected() const {return mSelectedItemPairs.size(); }
 
-	/** Get number of items in the list */
-	U32 size() const { return mItemPairs.size(); }
-
+	/** Get number of (visible) items in the list */
+	U32 size(const bool only_visible_items = true) const;
 
 	/** Removes all items from the list */
 	virtual void clear();
@@ -464,6 +463,17 @@ class LLFlatListViewEx : public LLFlatListView
 	void setNoItemsMsg(const std::string& msg) { mNoItemsMsg = msg; }
 	void setNoFilteredItemsMsg(const std::string& msg) { mNoFilteredItemsMsg = msg; }
 
+	/**
+	 * Sets up new filter string and filters the list.
+	 */
+	void setFilterSubString(const std::string& filter_str);
+	
+	/**
+	 * Filters the list, rearranges and notifies parent about shape changes.
+	 * Derived classes may want to overload rearrangeItems() to exclude repeated separators after filtration.
+	 */
+	void filterItems();
+
 protected:
 	LLFlatListViewEx(const Params& p);
 
@@ -478,6 +488,7 @@ class LLFlatListViewEx : public LLFlatListView
 private:
 	std::string mNoFilteredItemsMsg;
 	std::string mNoItemsMsg;
+	std::string	mFilterSubString;
 };
 
 #endif
diff --git a/indra/newview/llinventoryitemslist.cpp b/indra/newview/llinventoryitemslist.cpp
index 8dfdb0788a2..9719de46506 100644
--- a/indra/newview/llinventoryitemslist.cpp
+++ b/indra/newview/llinventoryitemslist.cpp
@@ -45,6 +45,7 @@
 #include "llinventoryfunctions.h"
 #include "llinventorymodel.h"
 #include "lltextutil.h"
+#include "lltrans.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
@@ -63,6 +64,16 @@ LLPanelInventoryListItemBase* LLPanelInventoryListItemBase::create(LLViewerInven
 	return list_item;
 }
 
+void LLPanelInventoryListItemBase::draw()
+{
+	if (getNeedsRefresh())
+	{
+		updateItem();
+		setNeedsRefresh(false);
+	}
+	LLPanel::draw();
+}
+
 void LLPanelInventoryListItemBase::updateItem()
 {
 	setIconImage(mIconImage);
@@ -121,7 +132,7 @@ BOOL LLPanelInventoryListItemBase::postBuild()
 
 	mIconImage = get_item_icon(mItem->getType(), mItem->getInventoryType(), mItem->getFlags(), FALSE);
 
-	updateItem();
+	setNeedsRefresh(true);
 
 	setWidgetsVisible(false);
 	reshapeWidgets();
@@ -148,6 +159,34 @@ void LLPanelInventoryListItemBase::onMouseLeave(S32 x, S32 y, MASK mask)
 	LLPanel::onMouseLeave(x, y, mask);
 }
 
+S32 LLPanelInventoryListItemBase::notify(const LLSD& info)
+{
+	S32 rv = 0;
+	if(info.has("match_filter"))
+	{
+		mHighlightedText = info["match_filter"].asString();
+
+		std::string test(mItem->getName());
+		LLStringUtil::toUpper(test);
+
+		if(mHighlightedText.empty() || std::string::npos != test.find(mHighlightedText))
+		{
+			rv = 0; // substring is found
+		}
+		else
+		{
+			rv = -1;
+		}
+
+		setNeedsRefresh(true);
+	}
+	else
+	{
+		rv = LLPanel::notify(info);
+	}
+	return rv;
+}
+
 LLPanelInventoryListItemBase::LLPanelInventoryListItemBase(LLViewerInventoryItem* item)
 : LLPanel()
 , mItem(item)
@@ -156,6 +195,7 @@ LLPanelInventoryListItemBase::LLPanelInventoryListItemBase(LLViewerInventoryItem
 , mWidgetSpacing(WIDGET_SPACING)
 , mLeftWidgetsWidth(0)
 , mRightWidgetsWidth(0)
+, mNeedsRefresh(false)
 {
 }
 
@@ -278,13 +318,15 @@ LLInventoryItemsList::Params::Params()
 {}
 
 LLInventoryItemsList::LLInventoryItemsList(const LLInventoryItemsList::Params& p)
-:	LLFlatListView(p)
+:	LLFlatListViewEx(p)
 ,	mNeedsRefresh(false)
 {
 	// TODO: mCommitOnSelectionChange is set to "false" in LLFlatListView
 	// but reset to true in all derived classes. This settings might need to
 	// be added to LLFlatListView::Params() and/or set to "true" by default.
 	setCommitOnSelectionChange(true);
+
+	setNoFilteredItemsMsg(LLTrans::getString("InventoryNoMatchingItems"));
 }
 
 // virtual
@@ -304,7 +346,7 @@ void LLInventoryItemsList::refreshList(const LLInventoryModel::item_array_t item
 
 void LLInventoryItemsList::draw()
 {
-	LLFlatListView::draw();
+	LLFlatListViewEx::draw();
 	if(mNeedsRefresh)
 	{
 		refresh();
@@ -332,7 +374,8 @@ void LLInventoryItemsList::refresh()
 			break;
 		}
 		LLViewerInventoryItem* item = gInventory.getItem(*it);
-		addNewItem(item);
+		// Do not rearrange items on each adding, let's do that on filter call
+		addNewItem(item, false);
 		++nadded;
 	}
 
@@ -342,6 +385,9 @@ void LLInventoryItemsList::refresh()
 		removeItemByUUID(*it);
 	}
 
+	// Filter, rearrange and notify parent about shape changes
+	filterItems();
+
 	bool needs_refresh = add_limit_exceeded;
 	setNeedsRefresh(needs_refresh);
 }
@@ -363,7 +409,7 @@ void LLInventoryItemsList::computeDifference(
 	LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved);
 }
 
-void LLInventoryItemsList::addNewItem(LLViewerInventoryItem* item)
+void LLInventoryItemsList::addNewItem(LLViewerInventoryItem* item, bool rearrange /*= true*/)
 {
 	if (!item)
 	{
@@ -375,7 +421,7 @@ void LLInventoryItemsList::addNewItem(LLViewerInventoryItem* item)
 	if (!list_item)
 		return;
 
-	bool is_item_added = addItem(list_item, item->getUUID());
+	bool is_item_added = addItem(list_item, item->getUUID(), ADD_BOTTOM, rearrange);
 	if (!is_item_added)
 	{
 		llwarns << "Couldn't add flat list item." << llendl;
diff --git a/indra/newview/llinventoryitemslist.h b/indra/newview/llinventoryitemslist.h
index 152aafbd7e0..bc04eb6f5b3 100644
--- a/indra/newview/llinventoryitemslist.h
+++ b/indra/newview/llinventoryitemslist.h
@@ -64,13 +64,16 @@ class LLViewerInventoryItem;
 class LLPanelInventoryListItemBase : public LLPanel
 {
 public:
-
 	static LLPanelInventoryListItemBase* create(LLViewerInventoryItem* item);
 
+	virtual void draw();
+
 	/**
-	 * Called after inventory item was updated, update panel widgets to reflect inventory changes.
+	 * Let item know it need to be refreshed in next draw()
 	 */
-	virtual void updateItem();
+	void setNeedsRefresh(bool needs_refresh){ mNeedsRefresh = needs_refresh; }
+
+	bool getNeedsRefresh(){ return mNeedsRefresh; }
 
 	/**
 	 * Add widget to left side
@@ -107,6 +110,11 @@ class LLPanelInventoryListItemBase : public LLPanel
 	 */
 	/*virtual*/ void setValue(const LLSD& value);
 
+	/**
+	 * Handles filter request
+	 */
+	/*virtual*/ S32  notify(const LLSD& info);
+
 	 /* Highlights item */
 	/*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask);
 	/* Removes item highlight */
@@ -125,6 +133,11 @@ class LLPanelInventoryListItemBase : public LLPanel
 	 */
 	virtual void init();
 
+	/**
+	 * Called after inventory item was updated, update panel widgets to reflect inventory changes.
+	 */
+	virtual void updateItem();
+
 	/** setter for mIconCtrl */
 	void setIconCtrl(LLIconCtrl* icon) { mIconCtrl = icon; }
 	/** setter for MTitleCtrl */
@@ -178,14 +191,15 @@ class LLPanelInventoryListItemBase : public LLPanel
 
 	S32				mLeftWidgetsWidth;
 	S32				mRightWidgetsWidth;
+	bool			mNeedsRefresh;
 };
 
 //////////////////////////////////////////////////////////////////////////
 
-class LLInventoryItemsList : public LLFlatListView
+class LLInventoryItemsList : public LLFlatListViewEx
 {
 public:
-	struct Params : public LLInitParam::Block<Params, LLFlatListView::Params>
+	struct Params : public LLInitParam::Block<Params, LLFlatListViewEx::Params>
 	{
 		Params();
 	};
@@ -225,7 +239,7 @@ class LLInventoryItemsList : public LLFlatListView
 	/**
 	 * Add an item to the list
 	 */
-	virtual void addNewItem(LLViewerInventoryItem* item);
+	virtual void addNewItem(LLViewerInventoryItem* item, bool rearrange = true);
 
 private:
 	uuid_vec_t mIDs; // IDs of items that were added in refreshList().
diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp
index b103ec45d0d..18bd610dd9d 100644
--- a/indra/newview/lloutfitslist.cpp
+++ b/indra/newview/lloutfitslist.cpp
@@ -245,6 +245,34 @@ void LLOutfitsList::performAction(std::string action)
 void LLOutfitsList::setFilterSubString(const std::string& string)
 {
 	mFilterSubString = string;
+
+	for (outfits_map_t::iterator
+			 iter = mOutfitsMap.begin(),
+			 iter_end = mOutfitsMap.end();
+		 iter != iter_end; ++iter)
+	{
+		LLAccordionCtrlTab* tab = iter->second;
+		if (tab)
+		{
+			LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*> (tab->getAccordionView());
+			if (list)
+			{
+				list->setFilterSubString(mFilterSubString);
+			}
+
+			if(!mFilterSubString.empty())
+			{
+				//store accordion tab state when filter is not empty
+				tab->notifyChildren(LLSD().with("action","store_state"));
+				tab->setDisplayChildren(true);
+			}
+			else
+			{
+				//restore accordion state after all those accodrion tab manipulations
+				tab->notifyChildren(LLSD().with("action","restore_state"));
+			}
+		}
+	}
 }
 
 //////////////////////////////////////////////////////////////////////////
-- 
GitLab