Commits

Seth ProductEngine committed de24520

(EXT-6722) Create modified inventory view for "my outfits" tab in top-level appearance sidebar (tier 2)
llui:
- Added accordion tab title setter.
- Added setters for accordion tab focus changes callbacks.

newview:
- Fixed observer used for outfit items collecting. Added checking number of fetched items.
- Added outfit selection and enabled "replace outfit" and "add to outfit" commands for selected outfit.

Reviewed by Mike Antipov https://codereview.productengine.com/secondlife/r/332/

  • Participants
  • Parent commits e592b64
  • Branches product-engine

Comments (0)

Files changed (8)

File indra/llui/llaccordionctrltab.cpp

 	addChild(panel,0);
 }
 
+void LLAccordionCtrlTab::setTitle(const std::string& title)
+{
+	LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME);
+	if (header)
+	{
+		header->setTitle(title);
+	}
+}
+
+boost::signals2::connection LLAccordionCtrlTab::setFocusReceivedCallback(const focus_signal_t::slot_type& cb)
+{
+	LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME);
+	if (header)
+	{
+		return header->setFocusReceivedCallback(cb);
+	}
+	return boost::signals2::connection();
+}
+
+boost::signals2::connection LLAccordionCtrlTab::setFocusLostCallback(const focus_signal_t::slot_type& cb)
+{
+	LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME);
+	if (header)
+	{
+		return header->setFocusLostCallback(cb);
+	}
+	return boost::signals2::connection();
+}
 
 LLView*	LLAccordionCtrlTab::findContainerView()
 {

File indra/llui/llaccordionctrltab.h

 	void		setAccordionView(LLView* panel);
 	LLView*		getAccordionView() { return mContainerPanel; };
 
+	// Set text in LLAccordionCtrlTabHeader
+	void setTitle(const std::string& title);
+
+	boost::signals2::connection setFocusReceivedCallback(const focus_signal_t::slot_type& cb);
+	boost::signals2::connection setFocusLostCallback(const focus_signal_t::slot_type& cb);
+
 	bool getCollapsible() {return mCollapsible;};
 
 	void setCollapsible(bool collapsible) {mCollapsible = collapsible;};

File indra/newview/llinventoryitemslist.cpp

 	if (!item)
 	{
 		llwarns << "No inventory item. Couldn't create flat list item." << llendl;
-		llassert(!"No inventory item. Couldn't create flat list item.");
+		llassert(item != NULL);
 	}
 
 	LLPanelInventoryListItemBase *list_item = LLPanelInventoryListItemBase::create(item);
 	if (!list_item)
 		return;
 
-	if (!addItem(list_item, item->getUUID()))
+	bool is_item_added = addItem(list_item, item->getUUID());
+	if (!is_item_added)
 	{
 		llwarns << "Couldn't add flat list item." << llendl;
-		llassert(!"Couldn't add flat list item.");
+		llassert(is_item_added);
 	}
 }
 

File indra/newview/llinventoryobserver.cpp

 		if (!category)
 			continue;
 
-		S32 version = category->getVersion();
-		if (version != (*iter).second.mVersion)
+		const S32 version = category->getVersion();
+		const S32 expected_num_descendents = category->getDescendentCount();
+		if ((version == LLViewerInventoryCategory::VERSION_UNKNOWN) ||
+			(expected_num_descendents == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN))
 		{
-			// Update category version in map.
-			(*iter).second.mVersion = version;
-			(*iter).second.mCallback();
+			continue;
+		}
+
+		// Check number of known descendents to find out whether it has changed.
+		LLInventoryModel::cat_array_t* cats;
+		LLInventoryModel::item_array_t* items;
+		gInventory.getDirectDescendentsOf((*iter).first, cats, items);
+		if (!cats || !items)
+		{
+			llwarns << "Category '" << category->getName() << "' descendents corrupted, fetch failed." << llendl;
+			// NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean
+			// that the cat just doesn't have any items or subfolders).
+			// Unrecoverable, so just skip this category.
+
+			llassert(cats != NULL && items != NULL);
+		}
+		const S32 current_num_known_descendents = cats->count() + items->count();
+
+		LLCategoryData cat_data = (*iter).second;
+
+		// If category version or descendents count has changed
+		// update category data in mCategoryMap and fire a callback.
+		if (version != cat_data.mVersion || current_num_known_descendents != cat_data.mDescendentsCount)
+		{
+			cat_data.mVersion = version;
+			cat_data.mDescendentsCount = current_num_known_descendents;
+
+			cat_data.mCallback();
 		}
 	}
 }
 
-void LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t cb)
+bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t cb)
 {
 	S32 version;
+	S32 current_num_known_descendents;
+	bool can_be_added = true;
+
 	LLViewerInventoryCategory* category = gInventory.getCategory(cat_id);
 	if (category)
 	{
 		// Inventory category version is used to find out if some changes
 		// to a category have been made.
 		version = category->getVersion();
+
+		LLInventoryModel::cat_array_t* cats;
+		LLInventoryModel::item_array_t* items;
+		gInventory.getDirectDescendentsOf(cat_id, cats, items);
+		if (!cats || !items)
+		{
+			llwarns << "Category '" << category->getName() << "' descendents corrupted, fetch failed." << llendl;
+			// NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean
+			// that the cat just doesn't have any items or subfolders).
+			// Unrecoverable, so just return "false" meaning that the category can't be observed.
+			can_be_added = false;
+
+			llassert(cats != NULL && items != NULL);
+		}
+		current_num_known_descendents = cats->count() + items->count();
 	}
 	else
 	{
 		// If category could not be retrieved it might mean that
 		// inventory is unusable at the moment so the category is
-		// stored with VERSION_UNKNOWN and it may be updated later.
+		// stored with VERSION_UNKNOWN and DESCENDENT_COUNT_UNKNOWN,
+		// it may be updated later.
 		version = LLViewerInventoryCategory::VERSION_UNKNOWN;
+		current_num_known_descendents = LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN;
 	}
 
-	version = category->getVersion();
-	mCategoryMap.insert(category_map_value_t(cat_id, LLCategoryData(cb, version)));
+	if (can_be_added)
+	{
+		mCategoryMap.insert(category_map_value_t(cat_id, LLCategoryData(cb, version, current_num_known_descendents)));
+	}
+
+	return can_be_added;
 }
 
 void LLInventoryCategoriesObserver::removeCategory(const LLUUID& cat_id)

File indra/newview/llinventoryobserver.h

 	LLInventoryCategoriesObserver() {};
 	virtual void changed(U32 mask);
 
-	void addCategory(const LLUUID& cat_id, callback_t cb);
+	/**
+	 * Add cat_id to the list of observed categories with a
+	 * callback fired on category being changed.
+	 *
+	 * @return "true" if category was added, "false" if it could
+	 * not be found.
+	 */
+	bool addCategory(const LLUUID& cat_id, callback_t cb);
 	void removeCategory(const LLUUID& cat_id);
 
 protected:
 	struct LLCategoryData
 	{
-		LLCategoryData(callback_t cb, S32 version)
+		LLCategoryData(callback_t cb, S32 version, S32 num_descendents)
 		: mCallback(cb)
 		, mVersion(version)
+		, mDescendentsCount(num_descendents)
 		{}
 
 		callback_t	mCallback;
 		S32			mVersion;
+		S32			mDescendentsCount;
 	};
 
 	typedef	std::map<LLUUID, LLCategoryData>	category_map_t;

File indra/newview/lloutfitslist.cpp

 
 #include "llaccordionctrl.h"
 #include "llaccordionctrltab.h"
+#include "llappearancemgr.h"
 #include "llinventoryfunctions.h"
 #include "llinventorymodel.h"
 #include "llwearableitemslist.h"
 	:	LLPanel()
 	,	mAccordion(NULL)
 	,	mListCommands(NULL)
+	,	mSelectedList(NULL)
 {
 	mCategoriesObserver = new LLInventoryCategoriesObserver();
 	gInventory.addObserver(mCategoriesObserver);
 	{
 		const LLUUID cat_id = (*iter);
 		LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
-		if (!cat)
-			continue;
+		if (!cat) continue;
 
 		std::string name = cat->getName();
 
 		static LLXMLNodePtr accordionXmlNode = getAccordionTabXMLNode();
+		LLAccordionCtrlTab* tab = LLUICtrlFactory::defaultBuilder<LLAccordionCtrlTab>(accordionXmlNode, NULL, NULL);
 
-		accordionXmlNode->setAttributeString("name", name);
-		accordionXmlNode->setAttributeString("title", name);
-		LLAccordionCtrlTab* tab = LLUICtrlFactory::defaultBuilder<LLAccordionCtrlTab>(accordionXmlNode, NULL, NULL);
+		tab->setName(name);
+		tab->setTitle(name);
 
 		// *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated.
 		tab->setDisplayChildren(false);
 		mAccordion->addCollapsibleCtrl(tab);
 
+		// Start observing the new outfit category.
+		LLWearableItemsList* list  = tab->getChild<LLWearableItemsList>("wearable_items_list");
+		if (!mCategoriesObserver->addCategory(cat_id, boost::bind(&LLWearableItemsList::updateList, list, cat_id)))
+		{
+			// Remove accordion tab if category could not be added to observer.
+			mAccordion->removeCollapsibleCtrl(tab);
+			continue;
+		}
+
 		// Map the new tab with outfit category UUID.
 		mOutfitsMap.insert(LLOutfitsList::outfits_map_value_t(cat_id, tab));
 
-		// Start observing the new outfit category.
-		LLWearableItemsList* list  = tab->getChild<LLWearableItemsList>("wearable_items_list");
-		mCategoriesObserver->addCategory(cat_id, boost::bind(&LLWearableItemsList::updateList, list, cat_id));
+		// Setting tab focus callback to monitor currently selected outfit.
+		tab->setFocusReceivedCallback(boost::bind(&LLOutfitsList::changeOutfitSelection, this, list, cat_id));
 
-		// Setting drop down callback to monitor currently selected outfit.
-		tab->setDropDownStateChangedCallback(boost::bind(&LLOutfitsList::onTabExpandedCollapsed, this, list));
+		// Setting list commit callback to monitor currently selected wearable item.
+		list->setCommitCallback(boost::bind(&LLOutfitsList::onSelectionChange, this, _1));
 
 		// Fetch the new outfit contents.
 		cat->fetch();
 			// 1. Remove outfit accordion tab from accordion.
 			mAccordion->removeCollapsibleCtrl(outfits_iter->second);
 
+			const LLUUID& outfit_id = outfits_iter->first;
+
 			// 2. Remove outfit category from observer to stop monitoring its changes.
-			mCategoriesObserver->removeCategory(outfits_iter->first);
+			mCategoriesObserver->removeCategory(outfit_id);
 
-			// 3. Remove category UUID to accordion tab mapping.
+			// 3. Reset selection if selected outfit is being removed.
+			if (mSelectedOutfitUUID == outfit_id)
+			{
+				changeOutfitSelection(NULL, LLUUID());
+			}
+
+			// 4. Remove category UUID to accordion tab mapping.
 			mOutfitsMap.erase(outfits_iter);
 		}
 	}
 	mAccordion->arrange();
 }
 
-void LLOutfitsList::updateOutfitTab(const LLUUID& category_id)
+void LLOutfitsList::onSelectionChange(LLUICtrl* ctrl)
 {
-	outfits_map_t::iterator outfits_iter = mOutfitsMap.find(category_id);
-	if (outfits_iter != mOutfitsMap.end())
-	{
-		LLViewerInventoryCategory *cat = gInventory.getCategory(category_id);
-		if (!cat)
-			return;
+	LLWearableItemsList* list = dynamic_cast<LLWearableItemsList*>(ctrl);
+	if (!list) return;
 
-		std::string name = cat->getName();
+	LLViewerInventoryItem *item = gInventory.getItem(list->getSelectedUUID());
+	if (!item) return;
 
-		// Update tab name with the new category name.
-		LLAccordionCtrlTab* tab = outfits_iter->second;
-		if (tab)
-		{
-			tab->setName(name);
-		}
-
-		// Update tab title with the new category name using textbox
-		// in accordion tab header.
-		LLTextBox* tab_title = tab->findChild<LLTextBox>("dd_textbox");
-		if (tab_title)
-		{
-			tab_title->setText(name);
-		}
-	}
+	changeOutfitSelection(list, item->getParentUUID());
 }
 
-void LLOutfitsList::onTabExpandedCollapsed(LLWearableItemsList* list)
+void LLOutfitsList::performAction(std::string action)
 {
-	if (!list)
-		return;
+	LLViewerInventoryCategory* cat = gInventory.getCategory(mSelectedOutfitUUID);
+	if (!cat) return;
 
-	// TODO: Add outfit selection handling.
+	if ("replaceoutfit" == action)
+	{
+		LLAppearanceMgr::instance().wearInventoryCategory( cat, FALSE, FALSE );
+	}
+	else if ("addtooutfit" == action)
+	{
+		LLAppearanceMgr::instance().wearInventoryCategory( cat, FALSE, TRUE );
+	}
 }
 
 void LLOutfitsList::setFilterSubString(const std::string& string)
 	mFilterSubString = string;
 }
 
-
 //////////////////////////////////////////////////////////////////////////
 // Private methods
 //////////////////////////////////////////////////////////////////////////
 	LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved);
 }
 
+void LLOutfitsList::updateOutfitTab(const LLUUID& category_id)
+{
+	outfits_map_t::iterator outfits_iter = mOutfitsMap.find(category_id);
+	if (outfits_iter != mOutfitsMap.end())
+	{
+		LLViewerInventoryCategory *cat = gInventory.getCategory(category_id);
+		if (!cat) return;
+
+		std::string name = cat->getName();
+
+		// Update tab name with the new category name.
+		LLAccordionCtrlTab* tab = outfits_iter->second;
+		if (tab)
+		{
+			tab->setName(name);
+			tab->setTitle(name);
+		}
+	}
+}
+
+void LLOutfitsList::changeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id)
+{
+	// Reset selection in previously selected tab
+	// if a new one is selected.
+	if (list && mSelectedList && mSelectedList != list)
+	{
+		mSelectedList->resetSelection();
+	}
+
+	mSelectedList = list;
+	mSelectedOutfitUUID = category_id;
+}
+
 // EOF

File indra/newview/lloutfitslist.h

 
 	void refreshList(const LLUUID& category_id);
 
-	// Update tab displaying outfit identified by category_id.
-	void updateOutfitTab(const LLUUID& category_id);
+	void onSelectionChange(LLUICtrl* ctrl);
 
-	void onTabExpandedCollapsed(LLWearableItemsList* list);
+	void performAction(std::string action);
 
 	void setFilterSubString(const std::string& string);
 
 	 */
 	void computeDifference(const LLInventoryModel::cat_array_t& vcats, uuid_vec_t& vadded, uuid_vec_t& vremoved);
 
+	/**
+	 * Updates tab displaying outfit identified by category_id.
+	 */
+	void updateOutfitTab(const LLUUID& category_id);
+
+	/**
+	 * Resets previous selection and stores newly selected list and outfit id.
+	 */
+	void changeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id);
 
 	LLInventoryCategoriesObserver* 	mCategoriesObserver;
 
 	LLAccordionCtrl*				mAccordion;
 	LLPanel*						mListCommands;
 
+	LLWearableItemsList*			mSelectedList;
+	LLUUID							mSelectedOutfitUUID;
+
 	std::string 					mFilterSubString;
 
 	typedef	std::map<LLUUID, LLAccordionCtrlTab*>		outfits_map_t;

File indra/newview/llpaneloutfitsinventory.cpp

 
 void LLPanelOutfitsInventory::onWearButtonClick()
 {
-	LLFolderViewEventListener* listenerp = getCorrectListenerForAction();
-	if (listenerp)
+	// TODO: Remove if/else, add common interface
+	// for "My Outfits" and "Wearing" tabs.
+	if (!isCOFPanelActive())
 	{
-		listenerp->performAction(NULL, "replaceoutfit");
+		mMyOutfitsPanel->performAction("replaceoutfit");
+	}
+	else
+	{
+		LLFolderViewEventListener* listenerp = getCorrectListenerForAction();
+		if (listenerp)
+		{
+			listenerp->performAction(NULL, "replaceoutfit");
+		}
 	}
 }
 
 void LLPanelOutfitsInventory::onAdd()
 {
-	LLFolderViewEventListener* listenerp = getCorrectListenerForAction();
-	if (listenerp)
+	// TODO: Remove if/else, add common interface
+	// for "My Outfits" and "Wearing" tabs.
+	if (!isCOFPanelActive())
 	{
-		listenerp->performAction(NULL, "addtooutfit");
+		mMyOutfitsPanel->performAction("addtooutfit");
+	}
+	else
+	{
+		LLFolderViewEventListener* listenerp = getCorrectListenerForAction();
+		if (listenerp)
+		{
+			listenerp->performAction(NULL, "addtooutfit");
+		}
 	}
 }