Anonymous avatar Anonymous committed fb5ed31

completed EXT-6719 Utilize the "description" field of outfit wearable links to store order information

- COF items get checked for correct ordering when COF is loaded (LLAppearanceMgr::updateAppearanceFromCOF..., LLAppearanceMgr::updateClothingOrderingInfo() ), and also when COF is saved to a new outfit, base outfit.
- wearables are arranged in LLAgentWearables in a proper order

* updateClothingOrderingInfo() [struggling with naming] does integrity checking and updates "invalid" items (items with wrong, gapped descriptions)
* moved LLAgentWearables::makeNewOutfitLinks(...) and LLShowCreatedOutfit to a more natural place (llappearancemgr.cpp), because they use LLAppearanceMgr's methods a lot
* changed link_inventory_item(...), added the "new_description" parameter

Reviewed by Neal Orman at https://codereview.productengine.com/secondlife/r/268/

Comments (0)

Files changed (9)

indra/newview/llagentwearables.cpp

 	LLUUID mFolderID;
 };
 
-LLUUID LLAgentWearables::makeNewOutfitLinks(const std::string& new_folder_name)
-{
-	if (!isAgentAvatarValid()) return LLUUID::null;
-
-	// First, make a folder in the My Outfits directory.
-	const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
-	LLUUID folder_id = gInventory.createNewCategory(
-		parent_id,
-		LLFolderType::FT_OUTFIT,
-		new_folder_name);
-
-	LLPointer<LLInventoryCallback> cb = new LLShowCreatedOutfit(folder_id);
-	LLAppearanceMgr::instance().shallowCopyCategoryContents(LLAppearanceMgr::instance().getCOF(),folder_id, cb);
-	LLAppearanceMgr::instance().createBaseOutfitLink(folder_id, cb);
-
-	return folder_id;
-}
-
 void LLAgentWearables::makeNewOutfitDone(S32 type, U32 index)
 {
 	LLUUID first_item_id = getWearableItemID((EWearableType)type, index);

indra/newview/llagentwearables.h

 								  const LLDynamicArray<S32>& wearables_to_include,
 								  const LLDynamicArray<S32>& attachments_to_include,
 								  BOOL rename_clothing);
-	
-	LLUUID			makeNewOutfitLinks(const std::string& new_folder_name);
+
 	
 	// Should only be called if we *know* we've never done so before, since users may
 	// not want the Library outfits to stay in their quick outfit selector and can delete them.

indra/newview/llagentwearablesfetch.cpp

 								item->getLinkedUUID(),
 								LLAppearanceMgr::instance().getCOF(),
 								item->getName(),
+								item->getDescription(),
 								LLAssetType::AT_LINK,
 								link_waiter);
 		}
 								item->getLinkedUUID(),
 								new_outfit_folder_id,
 								item->getName(),
+								item->getDescription(),
 								LLAssetType::AT_LINK,
 								NULL);
 		}

indra/newview/llappearancemgr.cpp

 
 #include "llviewerprecompiledheaders.h"
 
+#include "llaccordionctrltab.h"
 #include "llagent.h"
 #include "llagentwearables.h"
 #include "llappearancemgr.h"
 #include "llinventoryfunctions.h"
 #include "llinventoryobserver.h"
 #include "llnotificationsutil.h"
+#include "llpaneloutfitsinventory.h"
 #include "llselectmgr.h"
 #include "llsidepanelappearance.h"
 #include "llsidetray.h"
 #include "llviewerregion.h"
 #include "llwearablelist.h"
 
+char ORDER_NUMBER_SEPARATOR('@');
+
 LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id, const std::string& name)
 {
 	LLInventoryModel::cat_array_t cat_array;
 					     item_id,
 					     LLAppearanceMgr::instance().getCOF(),
 					     itemp->getName(),
+						 itemp->getDescription(),
 					     LLAssetType::AT_LINK,
 					     cb);
 		}
 		{
 			case LLAssetType::AT_LINK:
 			{
+				//LLInventoryItem::getDescription() is used for a new description 
+				//to propagate ordering information saved in descriptions of links
 				link_inventory_item(gAgent.getID(),
 									item->getLinkedUUID(),
 									dst_id,
 									item->getName(),
+									item->LLInventoryItem::getDescription(),
 									LLAssetType::AT_LINK, cb);
 				break;
 			}
 										item->getLinkedUUID(),
 										dst_id,
 										item->getName(),
+										item->getDescription(),
 										LLAssetType::AT_LINK_FOLDER, cb);
 				}
 				break;
 {
 	// Divvy items into arrays by wearable type.
 	std::vector<LLInventoryModel::item_array_t> items_by_type(WT_COUNT);
-	for (S32 i=0; i<items.count(); i++)
-	{
-		LLViewerInventoryItem *item = items.get(i);
-		// Ignore non-wearables.
-		if (!item->isWearableType())
-			continue;
-		EWearableType type = item->getWearableType();
-		if(type < 0 || type >= WT_COUNT)
-		{
-			LL_WARNS("Appearance") << "Invalid wearable type. Inventory type does not match wearable flag bitfield." << LL_ENDL;
-			continue;
-		}
-		items_by_type[type].push_back(item);
-	}
+	divvyWearablesByType(items, items_by_type);
 
 	// rebuild items list, retaining the last max_per_type of each array
 	items.clear();
 							item->getLinkedUUID(),
 							category,
 							item->getName(),
+							item->LLInventoryItem::getDescription(),
 							LLAssetType::AT_LINK,
 							cb);
 	}
 
 	if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT)
 	{
-		link_inventory_item(gAgent.getID(), category, cof, catp->getName(),
+		link_inventory_item(gAgent.getID(), category, cof, catp->getName(), "",
 							LLAssetType::AT_LINK_FOLDER, link_waiter);
 		new_outfit_name = catp->getName();
 	}
 	items = pruned_items;
 }
 
+//a predicate for sorting inventory items by actual descriptions
+bool sort_by_description(const LLInventoryItem* item1, const LLInventoryItem* item2)
+{
+	if (!item1 || !item2) 
+	{
+		llwarning("either item1 or item2 is NULL", 0);
+		return true;
+	}
+
+	return item1->LLInventoryItem::getDescription() < item2->LLInventoryItem::getDescription();
+}
+
 void LLAppearanceMgr::updateAppearanceFromCOF()
 {
 	// update dirty flag to see if the state of the COF matches
 
 	dumpCat(getCOF(),"COF, start");
 
+	updateClothingOrderingInfo();
+
 	bool follow_folder_links = true;
 	LLUUID current_outfit_id = getCOF();
 
 		return;
 	}
 
+	//preparing the list of wearables in the correct order for LLAgentWearables
+	std::sort(wear_items.begin(), wear_items.end(), sort_by_description);
+
 	LLWearableHoldingPattern* holder = new LLWearableHoldingPattern;
 
 	holder->mObjItems = obj_items;
 				
 			}
 #endif
-
-			holder->mFoundList.push_front(found);
+			//pushing back, not front, to preserve order of wearables for LLAgentWearables
+			holder->mFoundList.push_back(found);
 		}
 		else
 		{
 		// Are these links to different items of the same wearable
 		// type? If so, new item will replace old.
 		// MULTI-WEARABLES: revisit if more than one per type is allowed.
-		else if (areMatchingWearables(vitem,inv_item))
+		else if (FALSE/*areMatchingWearables(vitem,inv_item)*/)
 		{
 			if (inv_item->getIsLinkType())
 			{
 							 vitem->getLinkedUUID(),
 							 getCOF(),
 							 vitem->getName(),
+							 vitem->getDescription(),
 							 LLAssetType::AT_LINK,
 							 cb);
 	}
 						 cat->getLinkedUUID(),
 						 getCOF(),
 						 cat->getName(),
+						 cat->getDescription(),
 						 LLAssetType::AT_LINK_FOLDER,
 						 cb);
 #endif
 	const LLUUID base_outfit_id = getBaseOutfitUUID();
 	if (base_outfit_id.isNull()) return false;
 
+	updateClothingOrderingInfo();
+
 	// in a Base Outfit we do not remove items, only links
 	purgeCategory(base_outfit_id, false);
 
 	return true;
 }
 
+void LLAppearanceMgr::divvyWearablesByType(const LLInventoryModel::item_array_t& items, wearables_by_type_t& items_by_type)
+{
+	items_by_type.reserve(WT_COUNT);
+	if (items.empty()) return;
+
+	for (S32 i=0; i<items.count(); i++)
+	{
+		LLViewerInventoryItem *item = items.get(i);
+		// Ignore non-wearables.
+		if (!item->isWearableType())
+			continue;
+		EWearableType type = item->getWearableType();
+		if(type < 0 || type >= WT_COUNT)
+		{
+			LL_WARNS("Appearance") << "Invalid wearable type. Inventory type does not match wearable flag bitfield." << LL_ENDL;
+			continue;
+		}
+		items_by_type[type].push_back(item);
+	}
+}
+
+std::string build_order_string(EWearableType type, U32 i)
+{
+		std::ostringstream order_num;
+		order_num << ORDER_NUMBER_SEPARATOR << type * 100 + i;
+		return order_num.str();
+}
+
+struct WearablesOrderComparator
+{
+	WearablesOrderComparator(const EWearableType type)
+	{
+		mControlSize = build_order_string(type, 0).size();
+	};
+
+	bool operator()(const LLInventoryItem* item1, const LLInventoryItem* item2)
+	{
+		if (!item1 || !item2)
+		{
+			llwarning("either item1 or item2 is NULL", 0);
+			return true;
+		}
+		
+		const std::string& desc1 = item1->LLInventoryItem::getDescription();
+		const std::string& desc2 = item2->LLInventoryItem::getDescription();
+		
+		bool item1_valid = (desc1.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc1[0]);
+		bool item2_valid = (desc2.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc2[0]);
+
+		if (item1_valid && item2_valid)
+			return desc1 < desc2;
+
+		//we need to sink down invalid items: items with empty descriptions, items with "Broken link" descriptions,
+		//items with ordering information but not for the associated wearables type
+		if (!item1_valid && item2_valid) 
+			return false;
+
+		return true;
+	}
+
+	U32 mControlSize;
+};
+
+void LLAppearanceMgr::updateClothingOrderingInfo()
+{
+	LLInventoryModel::item_array_t wear_items;
+	getDescendentsOfAssetType(getCOF(), wear_items, LLAssetType::AT_CLOTHING, false);
+
+	wearables_by_type_t items_by_type(WT_COUNT);
+	divvyWearablesByType(wear_items, items_by_type);
+
+	bool inventory_changed = false;
+	for (U32 type = WT_SHIRT; type < WT_COUNT; type++)
+	{
+		
+		U32 size = items_by_type[type].size();
+		if (!size) continue;
+
+		//sinking down invalid items which need reordering
+		std::sort(items_by_type[type].begin(), items_by_type[type].end(), WearablesOrderComparator((EWearableType) type));
+
+		//requesting updates only for those links which don't have "valid" descriptions
+		for (U32 i = 0; i < size; i++)
+		{
+			LLViewerInventoryItem* item = items_by_type[type][i];
+			if (!item) continue;
+
+			std::string new_order_str = build_order_string((EWearableType)type, i);
+			if (new_order_str == item->LLInventoryItem::getDescription()) continue;
+
+			item->setDescription(new_order_str);
+			item->setComplete(TRUE);
+ 			item->updateServer(FALSE);
+			gInventory.updateItem(item);
+			inventory_changed = true;
+		}
+	}
+
+	//*TODO do we really need to notify observers?
+	if (inventory_changed) gInventory.notifyObservers();
+}
+
+
+
+
+class LLShowCreatedOutfit: public LLInventoryCallback
+{
+public:
+	LLShowCreatedOutfit(LLUUID& folder_id): mFolderID(folder_id)
+	{}
+
+	virtual ~LLShowCreatedOutfit()
+	{
+		LLSD key;
+		LLSideTray::getInstance()->showPanel("panel_outfits_inventory", key);
+		LLPanelOutfitsInventory *outfit_panel =
+			dynamic_cast<LLPanelOutfitsInventory*>(LLSideTray::getInstance()->getPanel("panel_outfits_inventory"));
+		if (outfit_panel)
+		{
+			outfit_panel->getRootFolder()->clearSelection();
+			outfit_panel->getRootFolder()->setSelectionByID(mFolderID, TRUE);
+		}
+		
+		LLAccordionCtrlTab* tab_outfits = outfit_panel ? outfit_panel->findChild<LLAccordionCtrlTab>("tab_outfits") : 0;
+		if (tab_outfits && !tab_outfits->getDisplayChildren())
+		{
+			tab_outfits->changeOpenClose(tab_outfits->getDisplayChildren());
+		}
+
+		LLAppearanceMgr::getInstance()->updateIsDirty();
+		LLAppearanceMgr::getInstance()->updatePanelOutfitName("");
+	}
+
+	virtual void fire(const LLUUID&)
+	{}
+
+private:
+	LLUUID mFolderID;
+};
+
+LLUUID LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name)
+{
+	if (!isAgentAvatarValid()) return LLUUID::null;
+
+	// First, make a folder in the My Outfits directory.
+	const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
+	LLUUID folder_id = gInventory.createNewCategory(
+		parent_id,
+		LLFolderType::FT_OUTFIT,
+		new_folder_name);
+
+	updateClothingOrderingInfo();
+
+	LLPointer<LLInventoryCallback> cb = new LLShowCreatedOutfit(folder_id);
+	shallowCopyCategoryContents(getCOF(),folder_id, cb);
+	createBaseOutfitLink(folder_id, cb);
+
+	dumpCat(folder_id,"COF, new outfit");
+
+	return folder_id;
+}
+
 void LLAppearanceMgr::wearBaseOutfit()
 {
 	const LLUUID& base_outfit_id = getBaseOutfitUUID();

indra/newview/llappearancemgr.h

 	//Remove clothing or detach an object from the agent (a bodypart cannot be removed)
 	void removeItemFromAvatar(const LLUUID& item_id);
 
+
+	LLUUID makeNewOutfitLinks(const std::string& new_folder_name);
+
 protected:
 	LLAppearanceMgr();
 	~LLAppearanceMgr();
 
 private:
 
+	typedef std::vector<LLInventoryModel::item_array_t> wearables_by_type_t;
+
+	//Divvy items into arrays by wearable type
+	static void divvyWearablesByType(const LLInventoryModel::item_array_t& items, wearables_by_type_t& items_by_type);
+
+	//Check ordering information on wearables stored in links' descriptions and update if it is invalid
+	void updateClothingOrderingInfo();
+
 	void filterWearableItems(LLInventoryModel::item_array_t& items, S32 max_per_type);
 	
 	void getDescendentsOfAssetType(const LLUUID& category, 

indra/newview/llinventorybridge.cpp

 						item->getLinkedUUID(),
 						parent_id,
 						item->getName(),
+						item->getDescription(),
 						LLAssetType::AT_LINK,
 						LLPointer<LLInventoryCallback>(NULL));
 				}
 						inv_item->getLinkedUUID(),
 						mUUID,
 						inv_item->getName(),
+						inv_item->getDescription(),
 						LLAssetType::AT_LINK,
 						cb);
 				}

indra/newview/llpaneloutfitsinventory.cpp

 		LLStringUtil::trim(outfit_name);
 		if( !outfit_name.empty() )
 		{
-			LLUUID outfit_folder = gAgentWearables.makeNewOutfitLinks(outfit_name);
+			LLUUID outfit_folder = LLAppearanceMgr::getInstance()->makeNewOutfitLinks(outfit_name);
 
 			LLSidepanelAppearance* panel_appearance =
 				dynamic_cast<LLSidepanelAppearance *>(LLSideTray::getInstance()->getPanel("sidepanel_appearance"));

indra/newview/llviewerinventory.cpp

 	const LLUUID& item_id,
 	const LLUUID& parent_id,
 	const std::string& new_name,
+	const std::string& new_description,
 	const LLAssetType::EType asset_type,
 	LLPointer<LLInventoryCallback> cb)
 {
 	}
 	
 	LLUUID transaction_id;
-	std::string desc = "Broken link"; // This should only show if the object can't find its baseobj.
 	LLInventoryType::EType inv_type = LLInventoryType::IT_NONE;
 	if (dynamic_cast<const LLInventoryCategory *>(baseobj))
 	{
 		msg->addS8Fast(_PREHASH_Type, (S8)asset_type);
 		msg->addS8Fast(_PREHASH_InvType, (S8)inv_type);
 		msg->addStringFast(_PREHASH_Name, new_name);
-		msg->addStringFast(_PREHASH_Description, desc);
+		msg->addStringFast(_PREHASH_Description, new_description);
 	}
 	gAgent.sendReliableMessage();
 }

indra/newview/llviewerinventory.h

 	const LLUUID& item_id,
 	const LLUUID& parent_id,
 	const std::string& new_name,
+	const std::string& new_description,
 	const LLAssetType::EType asset_type,
 	LLPointer<LLInventoryCallback> cb);
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.