From 7eca974a11cba4c4ed0bc89bccb937b044770c29 Mon Sep 17 00:00:00 2001
From: Bradley Payne <vir@lindenlab.com>
Date: Thu, 24 Sep 2009 18:05:38 +0000
Subject: [PATCH] Merging down avatar-pipeline/currently-worn-folder-8. 
 Includes fixes for EXT-1121: Attaching / detaching objects doesn't affect the
 Current Outfit folder, and EXT-1090: Attachments detach on login, as well as
 work in progress for ensemble support and look details.

---
 indra/newview/llagentwearables.cpp            |  99 +--------
 indra/newview/llagentwearables.h              |   2 +-
 indra/newview/llappearancemgr.cpp             | 205 ++++++++++++++----
 indra/newview/llappearancemgr.h               |  16 +-
 indra/newview/llfloaterinventory.cpp          |  14 +-
 indra/newview/llinventorybridge.cpp           | 123 +++++++++--
 indra/newview/llinventorymodel.cpp            |   9 +-
 indra/newview/llinventorymodel.h              |  15 ++
 indra/newview/llstartup.cpp                   |   4 -
 indra/newview/llviewermenu.cpp                |   2 +-
 indra/newview/llvoavatar.cpp                  |  13 ++
 indra/newview/llvoavatarself.cpp              |   6 +
 .../skins/default/xui/en/menu_inventory.xml   |   8 +
 13 files changed, 351 insertions(+), 165 deletions(-)

diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp
index c4e01808f77..6eb248ef74c 100644
--- a/indra/newview/llagentwearables.cpp
+++ b/indra/newview/llagentwearables.cpp
@@ -1172,99 +1172,24 @@ LLUUID LLAgentWearables::makeNewOutfitLinks(const std::string& new_folder_name,
 		return LLUUID::null;
 	}
 
-	// First, make a folder in the Clothes directory.
+	// First, make a folder in the My Outfits directory.
+	LLUUID parent_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_MY_OUTFITS);
 	LLUUID folder_id = gInventory.createNewCategory(
-		gInventory.findCategoryUUIDForType(LLAssetType::AT_MY_OUTFITS),
+		parent_id,
 		LLAssetType::AT_OUTFIT,
 		new_folder_name);
 
-//	bool found_first_item = false;
-
-	///////////////////
-	// Wearables
+	LLAppearanceManager::shallowCopyCategory(LLAppearanceManager::getCOF(),folder_id, NULL);
 
-	if (wearables_to_include.count())
+#if 0
+	LLViewerInventoryCategory *parent_category = gInventory.getCategory(parent_id);
+	if (parent_category)
 	{
-		// Then, iterate though each of the wearables and save links to them in the folder.
-		S32 i;
-		S32 count = wearables_to_include.count();
-		LLDynamicArray<LLUUID> delete_items;
-		LLPointer<LLRefCount> cbdone = NULL;
-		for (i = 0; i < count; ++i)
-		{
-			const S32 type = wearables_to_include[i];
-			for (U32 j=0; j<getWearableCount((EWearableType)i); j++)
-			{
-				LLWearable* old_wearable = getWearable((EWearableType)type,j);
-				if (old_wearable)
-				{
-					LLViewerInventoryItem* item = gInventory.getItem(getWearableItemID((EWearableType) type, j));
-					if (!item) continue;
-					LLPointer<LLInventoryCallback> cb = NULL;
-					link_inventory_item(gAgent.getID(),
-										item->getLinkedUUID(),
-										folder_id,
-										item->getName(),
-										LLAssetType::AT_LINK,
-										cb);
-				}
-			}
-		}
-		gInventory.notifyObservers();
+		parent_category->setSelectionByID(folder_id,TRUE);
+		parent_category->setNeedsAutoRename(TRUE);
 	}
+#endif
 
-
-	///////////////////
-	// Attachments
-
-	if (attachments_to_include.count())
-	{
-		for (S32 i = 0; i < attachments_to_include.count(); i++)
-		{
-			S32 attachment_pt = attachments_to_include[i];
-			LLViewerJointAttachment* attachment = get_if_there(mAvatarObject->mAttachmentPoints, attachment_pt, (LLViewerJointAttachment*)NULL);
-			if (!attachment) continue;
-			LLViewerObject* attached_object = attachment->getObject();
-			if (!attached_object) continue;
-			const LLUUID& item_id = attachment->getItemID();
-			if (item_id.isNull()) continue;
-			LLInventoryItem* item = gInventory.getItem(item_id);
-			if (!item) continue;
-
-			LLPointer<LLInventoryCallback> cb = NULL;
-			link_inventory_item(gAgent.getID(),
-								item->getLinkedUUID(),
-								folder_id,
-								item->getName(),
-								LLAssetType::AT_LINK,
-								cb);
-		}
-	} 
-
-	///////////////////
-	// Gestures
-
-	/* Disabling this for now, otherwise this adds all your default gestures and all previous
-	   active gestures.  Need to rethink the intended behavior.
-	for (LLGestureManager::item_map_t::iterator iter = LLGestureManager::instance().mActive.begin();
-		 iter != LLGestureManager::instance().mActive.end();
-		 ++iter)
-	{
-		const LLUUID &gesture_id = (*iter).first;
-		LLViewerInventoryItem* item = gInventory.getItem(gesture_id);
-		if (item)
-		{
-			LLPointer<LLInventoryCallback> cb = NULL;
-			link_inventory_item(gAgent.getID(),
-								item->getUUID(),
-								folder_id,
-								item->getName(),
-								LLAssetType::AT_LINK,
-								cb);
-		}
-	}
-	*/
-	
 	return folder_id;
 }
 
@@ -1759,7 +1684,7 @@ void LLAgentWearables::userRemoveAllClothesStep2(BOOL proceed)
 	}
 }
 
-void LLAgentWearables::userRemoveAllAttachments(void* userdata)
+void LLAgentWearables::userRemoveAllAttachments()
 {
 	LLVOAvatar* avatarp = gAgent.getAvatarObject();
 	if (!avatarp)
@@ -1819,7 +1744,7 @@ void LLAgentWearables::userAttachMultipleAttachments(LLInventoryModel::item_arra
 			msg->nextBlockFast(_PREHASH_HeaderData);
 			msg->addUUIDFast(_PREHASH_CompoundMsgID, compound_msg_id );
 			msg->addU8Fast(_PREHASH_TotalObjects, obj_count );
-			msg->addBOOLFast(_PREHASH_FirstDetachAll, true );
+			msg->addBOOLFast(_PREHASH_FirstDetachAll, true ); // BAP changing this doesn't seem to matter?
 		}
 
 		const LLInventoryItem* item = obj_item_array.get(i).get();
diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h
index cb4de555d56..f34b23e2204 100644
--- a/indra/newview/llagentwearables.h
+++ b/indra/newview/llagentwearables.h
@@ -181,7 +181,7 @@ class LLAgentWearables
 	// MULTI-WEARABLE: assuming one wearable per type.  Need upstream changes.
 	static void		userRemoveWearable(void* userdata);	// userdata is EWearableType
 	static void		userRemoveAllClothes(void* userdata);	// userdata is NULL
-	static void		userRemoveAllAttachments(void* userdata);	// userdata is NULL 
+	static void		userRemoveAllAttachments();
 	static void		userAttachMultipleAttachments(LLInventoryModel::item_array_t& obj_item_array);
 
 	BOOL			itemUpdatePending(const LLUUID& item_id) const;
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index 6c234f23fe8..74a8d8fe158 100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -239,33 +239,25 @@ void LLOutfitFetch::done()
 	}
 }
 
-class LLUpdateAppearanceOnCount: public LLInventoryCallback
+class LLUpdateAppearanceOnDestroy: public LLInventoryCallback
 {
 public:
-	LLUpdateAppearanceOnCount(S32 count):
-		mCount(count)
+	LLUpdateAppearanceOnDestroy():
+		mFireCount(0)
 	{
 	}
 
-	virtual ~LLUpdateAppearanceOnCount()
+	virtual ~LLUpdateAppearanceOnDestroy()
 	{
+		LLAppearanceManager::updateAppearanceFromCOF();
 	}
 
 	/* virtual */ void fire(const LLUUID& inv_item)
 	{
-		mCount--;
-		if (mCount==0)
-		{
-			done();
-		}
-	}
-
-	void done()
-	{
-		LLAppearanceManager::updateAppearanceFromCOF();
+		mFireCount++;
 	}
 private:
-	S32 mCount;
+	U32 mFireCount;
 };
 
 struct LLFoundData
@@ -362,25 +354,42 @@ void removeDuplicateItems(LLInventoryModel::item_array_t& dst, const LLInventory
 }
 
 // Update appearance from outfit folder.
-/* static */ void LLAppearanceManager::changeOutfit(bool proceed, const LLUUID& category, bool append, bool follow_folder_links)
+/* static */ void LLAppearanceManager::changeOutfit(bool proceed, const LLUUID& category, bool append)
 {
 	if (!proceed)
 		return;
-	
-	updateCOFFromOutfit(category, append, follow_folder_links);
+
+	if (append)
+	{
+		updateCOFFromCategory(category, append); // append is true - add non-duplicates to COF.
+	}
+	else
+	{
+		LLViewerInventoryCategory* catp = gInventory.getCategory(category);
+		if (catp->getPreferredType() == LLAssetType::AT_NONE ||
+			LLAssetType::lookupIsEnsembleCategoryType(catp->getPreferredType()))
+		{
+			updateCOFFromCategory(category, append);  // append is false - rebuild COF.
+		}
+		else if (catp->getPreferredType() == LLAssetType::AT_OUTFIT)
+		{
+			rebuildCOFFromOutfit(category);
+		}
+	}
 }
 
-// Update COF contents from outfit folder.
-/* static */ void LLAppearanceManager::updateCOFFromOutfit(const LLUUID& category, bool append, bool follow_folder_links)
+// Append to current COF contents by recursively traversing a folder.
+/* static */ void LLAppearanceManager::updateCOFFromCategory(const LLUUID& category, bool append)
 {
-	// BAP consolidate into one "get all 3 types of descendents" function, use both places.
+		// BAP consolidate into one "get all 3 types of descendents" function, use both places.
 	LLInventoryModel::item_array_t wear_items;
-	LLInventoryModel::item_array_t	obj_items;
-	LLInventoryModel::item_array_t	gest_items;
+	LLInventoryModel::item_array_t obj_items;
+	LLInventoryModel::item_array_t gest_items;
+	bool follow_folder_links = false;
 	getUserDescendents(category, wear_items, obj_items, gest_items, follow_folder_links);
 
 	// Find all the wearables that are in the category's subtree.	
-	lldebugs << "updateCOFFromOutfit()" << llendl;
+	lldebugs << "appendCOFFromCategory()" << llendl;
 	if( !wear_items.count() && !obj_items.count() && !gest_items.count())
 	{
 		LLNotifications::instance().add("CouldNotPutOnOutfit");
@@ -395,31 +404,26 @@ void removeDuplicateItems(LLInventoryModel::item_array_t& dst, const LLInventory
 	LLInventoryModel::item_array_t cof_items;
 	gInventory.collectDescendents(current_outfit_id, cof_cats, cof_items,
 								  LLInventoryModel::EXCLUDE_TRASH);
+	// Remove duplicates
 	if (append)
 	{
-		// Remove duplicates
 		removeDuplicateItems(wear_items, cof_items);
 		removeDuplicateItems(obj_items, cof_items);
 		removeDuplicateItems(gest_items, cof_items);
 	}
-			
 
-	if (wear_items.count() > 0 || obj_items.count() > 0)
+	S32 total_links = gest_items.count() + wear_items.count() + obj_items.count();
+
+	if (!append && total_links > 0)
 	{
-		if (!append) 
+		// Remove all current outfit folder links since we're now replacing the contents.
+		for (S32 i = 0; i < cof_items.count(); ++i)
 		{
-			// Remove all current outfit folder links if we're now replacing the contents.
-			for (S32 i = 0; i < cof_items.count(); ++i)
-			{
-				gInventory.purgeObject(cof_items.get(i)->getUUID());
-			}
+			gInventory.purgeObject(cof_items.get(i)->getUUID());
 		}
 	}
 
-	// BAP should we just link all contents, rather than restricting to these 3 types?
-
-	S32 total_links = gest_items.count() + wear_items.count() + obj_items.count();
-	LLPointer<LLUpdateAppearanceOnCount> link_waiter = new LLUpdateAppearanceOnCount(total_links);
+	LLPointer<LLUpdateAppearanceOnDestroy> link_waiter = new LLUpdateAppearanceOnDestroy;
 	
 	// Link all gestures in this folder
 	if (gest_items.count() > 0)
@@ -469,11 +473,86 @@ void removeDuplicateItems(LLInventoryModel::item_array_t& dst, const LLInventory
 			}
 		}
 	}
+}
+
+/* static */ void LLAppearanceManager::shallowCopyCategory(const LLUUID& src_id, const LLUUID& dst_id,
+														   LLPointer<LLInventoryCallback> cb)
+{
+	LLInventoryModel::cat_array_t cats;
+	LLInventoryModel::item_array_t items;
+	gInventory.collectDescendents(src_id, cats, items,
+								  LLInventoryModel::EXCLUDE_TRASH);
+	for (S32 i = 0; i < items.count(); ++i)
+	{
+		const LLViewerInventoryItem* item = items.get(i).get();
+		if (item->getActualType() == LLAssetType::AT_LINK)
+		{
+			link_inventory_item(gAgent.getID(),
+								item->getLinkedUUID(),
+								dst_id,
+								item->getName(),
+								LLAssetType::AT_LINK, cb);
+		}
+		else if (item->getActualType() == LLAssetType::AT_LINK_FOLDER)
+		{
+			link_inventory_item(gAgent.getID(),
+								item->getLinkedUUID(),
+								dst_id,
+								item->getName(),
+								LLAssetType::AT_LINK_FOLDER, cb);
+		}
+		else
+		{
+			copy_inventory_item(
+				gAgent.getID(),
+				item->getPermissions().getOwner(),
+				item->getUUID(),
+				dst_id,
+				item->getName(),
+				cb);
+		}
+	}
+}
+
+// Replace COF contents from a given outfit folder.
+/* static */ void LLAppearanceManager::rebuildCOFFromOutfit(const LLUUID& category)
+{
+	lldebugs << "rebuildCOFFromOutfit()" << llendl;
+
+	// Find all the wearables that are in the category's subtree.	
+	LLInventoryModel::item_array_t items;
+	getCOFValidDescendents(category, items);
+
+	if( items.count() == 0)
+	{
+		LLNotifications::instance().add("CouldNotPutOnOutfit");
+		return;
+	}
+		
+	const LLUUID &current_outfit_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_CURRENT_OUTFIT);
+	// Processes that take time should show the busy cursor
+	//inc_busy_count();
+		
+	LLInventoryModel::cat_array_t cof_cats;
+	LLInventoryModel::item_array_t cof_items;
+	gInventory.collectDescendents(current_outfit_id, cof_cats, cof_items,
+								  LLInventoryModel::EXCLUDE_TRASH);
+
+	if (items.count() > 0)
+	{
+		// Remove all current outfit folder links since we're now replacing the contents.
+		for (S32 i = 0; i < cof_items.count(); ++i)
+		{
+			gInventory.purgeObject(cof_items.get(i)->getUUID());
+		}
+	}
 
-	// In the particular case that we're switching to a different outfit,
-	// create a link to the folder that we wore.
+	LLPointer<LLInventoryCallback> link_waiter = new LLUpdateAppearanceOnDestroy;
+	LLAppearanceManager::shallowCopyCategory(category, current_outfit_id, link_waiter);
+
+	// Create a link to the outfit that we wore.
 	LLViewerInventoryCategory* catp = gInventory.getCategory(category);
-	if (!append && catp && catp->getPreferredType() == LLAssetType::AT_OUTFIT)
+	if (catp && catp->getPreferredType() == LLAssetType::AT_OUTFIT)
 	{
 		link_inventory_item(gAgent.getID(), category, current_outfit_id, catp->getName(),
 							LLAssetType::AT_LINK_FOLDER, LLPointer<LLInventoryCallback>(NULL));
@@ -621,7 +700,7 @@ void LLAppearanceManager::updateAgentWearables(LLWearableHoldingPattern* holder,
 	//If the folder doesn't contain only gestures, take off all attachments.
 	if (!(wear_items.count() == 0 && obj_items.count() == 0 && gest_items.count() > 0) )
 	{
-		LLAgentWearables::userRemoveAllAttachments(NULL);
+		LLAgentWearables::userRemoveAllAttachments();
 	}
 
 	if( obj_items.count() > 0 )
@@ -635,6 +714,21 @@ void LLAppearanceManager::updateAgentWearables(LLWearableHoldingPattern* holder,
 	}
 }
 
+/* static */ 
+void LLAppearanceManager::getCOFValidDescendents(const LLUUID& category,
+															  LLInventoryModel::item_array_t& items)
+{
+	LLInventoryModel::cat_array_t cats;
+	LLFindCOFValidItems is_cof_valid;
+	bool follow_folder_links = false;
+	gInventory.collectDescendentsIf(category,
+									cats, 
+									items, 
+									LLInventoryModel::EXCLUDE_TRASH,
+									is_cof_valid, 
+									follow_folder_links);
+}
+
 /* static */ void LLAppearanceManager::getUserDescendents(const LLUUID& category, 
 														  LLInventoryModel::item_array_t& wear_items,
 														  LLInventoryModel::item_array_t& obj_items,
@@ -708,14 +802,13 @@ void LLAppearanceManager::wearInventoryCategoryOnAvatar( LLInventoryCategory* ca
 	lldebugs << "wearInventoryCategoryOnAvatar( " << category->getName()
 			 << " )" << llendl;
 			 	
-	bool follow_folder_links = (category->getPreferredType() == LLAssetType::AT_CURRENT_OUTFIT || category->getPreferredType() == LLAssetType::AT_OUTFIT ); 
 	if( gFloaterCustomize )
 	{
-		gFloaterCustomize->askToSaveIfDirty(boost::bind(LLAppearanceManager::changeOutfit, _1, category->getUUID(), append, follow_folder_links));
+		gFloaterCustomize->askToSaveIfDirty(boost::bind(LLAppearanceManager::changeOutfit, _1, category->getUUID(), append));
 	}
 	else
 	{
-		LLAppearanceManager::changeOutfit(TRUE, category->getUUID(), append, follow_folder_links );
+		LLAppearanceManager::changeOutfit(TRUE, category->getUUID(), append);
 	}
 }
 
@@ -767,6 +860,7 @@ void LLAppearanceManager::wearOutfitByName(const std::string& name)
 	//dec_busy_count();
 }
 
+/* static */
 void LLAppearanceManager::wearItem( LLInventoryItem* item, bool do_update )
 {
 	// BAP add check for already in COF.
@@ -779,6 +873,7 @@ void LLAppearanceManager::wearItem( LLInventoryItem* item, bool do_update )
 						 cb);
 }
 
+/* static */
 void LLAppearanceManager::wearEnsemble( LLInventoryCategory* cat, bool do_update )
 {
 	// BAP add check for already in COF.
@@ -791,3 +886,25 @@ void LLAppearanceManager::wearEnsemble( LLInventoryCategory* cat, bool do_update
 						 cb);
 }
 
+/* static */
+void LLAppearanceManager::removeItemLinks(LLUUID& item_id, bool do_update)
+{
+	LLInventoryModel::cat_array_t cat_array;
+	LLInventoryModel::item_array_t item_array;
+	gInventory.collectDescendents(LLAppearanceManager::getCOF(),
+								  cat_array,
+								  item_array,
+								  LLInventoryModel::EXCLUDE_TRASH);
+	for (S32 i=0; i<item_array.count(); i++)
+	{
+		const LLInventoryItem* item = item_array.get(i).get();
+		if (item->getLinkedUUID() == item_id)
+		{
+			gInventory.purgeObject(item_array.get(i)->getUUID());
+		}
+	}
+	if (do_update)
+	{
+		LLAppearanceManager::updateAppearanceFromCOF();
+	}
+}
diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h
index 89b95833d7b..2aa10e0beac 100644
--- a/indra/newview/llappearancemgr.h
+++ b/indra/newview/llappearancemgr.h
@@ -35,6 +35,7 @@
 
 #include "llsingleton.h"
 #include "llinventorymodel.h"
+#include "llviewerinventory.h"
 
 class LLWearable;
 struct LLWearableHoldingPattern;
@@ -44,20 +45,29 @@ class LLAppearanceManager: public LLSingleton<LLAppearanceManager>
 public:
 	static void updateAppearanceFromCOF();
 	static bool needToSaveCOF();
-	static void changeOutfit(bool proceed, const LLUUID& category, bool append, bool follow_folder_links);
-	static void updateCOFFromOutfit(const LLUUID& category, bool append, bool follow_folder_links);
+	static void changeOutfit(bool proceed, const LLUUID& category, bool append);
+	static void updateCOFFromCategory(const LLUUID& category, bool append);
+	static void rebuildCOFFromOutfit(const LLUUID& category);
 	static void wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append);
 	static void wearInventoryCategoryOnAvatar(LLInventoryCategory* category, bool append);
 	static void wearOutfitByName(const std::string& name);
+	static void shallowCopyCategory(const LLUUID& src_id, const LLUUID& dst_id,
+									LLPointer<LLInventoryCallback> cb);
 
 	// Add COF link to individual item.
 	static void wearItem(LLInventoryItem* item, bool do_update = true);
 
 	// Add COF link to ensemble folder.
 	static void wearEnsemble(LLInventoryCategory* item, bool do_update = true);
+	static LLUUID getCOF();
+
+	// Remove COF entries
+	static void removeItemLinks(LLUUID& item_id, bool do_update = true);
 
 private:
-	static LLUUID getCOF();
+	static void getCOFValidDescendents(const LLUUID& category, 
+									   LLInventoryModel::item_array_t& items);
+									   
 	static void getUserDescendents(const LLUUID& category, 
 								   LLInventoryModel::item_array_t& wear_items,
 								   LLInventoryModel::item_array_t& obj_items,
diff --git a/indra/newview/llfloaterinventory.cpp b/indra/newview/llfloaterinventory.cpp
index 6a3a6c95140..27eb12b9ccc 100644
--- a/indra/newview/llfloaterinventory.cpp
+++ b/indra/newview/llfloaterinventory.cpp
@@ -1229,7 +1229,11 @@ BOOL LLInventoryPanel::postBuild()
 	// build everything.
 	mInventoryObserver = new LLInventoryPanelObserver(this);
 	mInventory->addObserver(mInventoryObserver);
-	rebuildViewsFor(LLUUID::null, LLInventoryObserver::ADD);
+	// build view of inventory if inventory ready, otherwise wait for modelChanged() callback
+	if (mInventory->isInventoryUsable())
+	{
+		rebuildViewsFor(LLUUID::null, LLInventoryObserver::ADD);
+	}
 
 	// bit of a hack to make sure the inventory is open.
 	mFolders->openFolder(std::string("My Inventory"));
@@ -1326,6 +1330,14 @@ void LLInventoryPanel::modelChanged(U32 mask)
 	LLFastTimer t2(FTM_REFRESH);
 
 	bool handled = false;
+
+	// inventory just initialized, do complete build
+	if ((mask & LLInventoryObserver::ADD) && gInventory.getChangedIDs().empty())
+	{
+		rebuildViewsFor(LLUUID::null, LLInventoryObserver::ADD);
+		return;
+	}
+
 	if(mask & LLInventoryObserver::LABEL)
 	{
 		handled = true;
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index eb070784022..40c5a243cc7 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -1481,8 +1481,8 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat,
 	LLVOAvatarSelf* avatar = gAgent.getAvatarObject();
 	if(!avatar) return FALSE;
 
-	// cannot drag categories into library or COF
-	if(!isAgentInventory() || isCOFFolder())
+	// cannot drag categories into library
+	if(!isAgentInventory())
 	{
 		return FALSE;
 	}
@@ -1729,6 +1729,34 @@ BOOL move_inv_category_world_to_agent(const LLUUID& object_id,
 	return accept;
 }
 
+bool LLFindCOFValidItems::operator()(LLInventoryCategory* cat,
+									 LLInventoryItem* item)
+{
+	// Valid COF items are:
+	// - links to wearables (body parts or clothing)
+	// - links to attachments
+	// - links to gestures
+	// - links to ensemble folders
+	LLViewerInventoryItem *linked_item = ((LLViewerInventoryItem*)item)->getLinkedItem(); // BAP - safe?
+	if (linked_item)
+	{
+		LLAssetType::EType type = linked_item->getType();
+		return (type == LLAssetType::AT_CLOTHING ||
+				type == LLAssetType::AT_BODYPART ||
+				type == LLAssetType::AT_GESTURE ||
+				type == LLAssetType::AT_OBJECT);
+	}
+	else
+	{
+		LLViewerInventoryCategory *linked_category = ((LLViewerInventoryItem*)item)->getLinkedCategory(); // BAP - safe?
+		// BAP remove AT_NONE support after ensembles are fully working?
+		return (linked_category && 
+				((linked_category->getPreferredType() == LLAssetType::AT_NONE) ||
+				 (LLAssetType::lookupIsEnsembleCategoryType(linked_category->getPreferredType()))));
+	}
+}
+
+
 bool LLFindWearables::operator()(LLInventoryCategory* cat,
 								 LLInventoryItem* item)
 {
@@ -1743,6 +1771,8 @@ bool LLFindWearables::operator()(LLInventoryCategory* cat,
 	return FALSE;
 }
 
+
+
 //Used by LLFolderBridge as callback for directory recursion.
 class LLRightClickInventoryFetchObserver : public LLInventoryFetchObserver
 {
@@ -1948,6 +1978,15 @@ void LLFolderBridge::performAction(LLFolderView* folder, LLInventoryModel* model
 		modifyOutfit(FALSE);
 		return;
 	}
+	else if ("wearasensemble" == action)
+	{
+		LLInventoryModel* model = getInventoryModel();
+		if(!model) return;
+		LLViewerInventoryCategory* cat = getCategory();
+		if(!cat) return;
+		LLAppearanceManager::wearEnsemble(cat,true);
+		return;
+	}
 	else if ("addtooutfit" == action)
 	{
 		modifyOutfit(TRUE);
@@ -2277,8 +2316,11 @@ void LLFolderBridge::folderOptionsMenu()
 	if(!model) return;
 
 	const LLInventoryCategory* category = model->getCategory(mUUID);
-	const bool is_default_folder = category &&
-		(LLAssetType::lookupIsProtectedCategoryType(category->getPreferredType()));
+	LLAssetType::EType type = category->getPreferredType();
+	const bool is_default_folder = category && LLAssetType::lookupIsProtectedCategoryType(type);
+	// BAP change once we're no longer treating regular categories as ensembles.
+	const bool is_ensemble = category && (type == LLAssetType::AT_NONE ||
+										  LLAssetType::lookupIsEnsembleCategoryType(type));
 	
 	// calling card related functionality for folders.
 
@@ -2313,6 +2355,10 @@ void LLFolderBridge::folderOptionsMenu()
 			mItems.push_back(std::string("Add To Outfit"));
 			mItems.push_back(std::string("Replace Outfit"));
 		}
+		if (is_ensemble)
+		{
+			mItems.push_back(std::string("Wear As Ensemble"));
+		}
 		mItems.push_back(std::string("Take Off Items"));
 	}
 	hideContextEntries(*mMenu, mItems, disabled_items);
@@ -2370,8 +2416,10 @@ void LLFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags)
 	{
 		LLViewerInventoryCategory *cat =  getCategory();
 
-		if (!isCOFFolder() && cat &&
-			LLAssetType::lookupIsProtectedCategoryType(cat->getPreferredType()))
+		// BAP removed protected check to re-enable standard ops in untyped folders.
+		// Not sure what the right thing is to do here.
+		if (!isCOFFolder() && cat /*&&
+			LLAssetType::lookupIsProtectedCategoryType(cat->getPreferredType())*/)
 		{
 			// Do not allow to create 2-level subfolder in the Calling Card/Friends folder. EXT-694.
 			if (!LLFriendCardsManager::instance().isCategoryInFriendFolder(cat))
@@ -3983,6 +4031,16 @@ void remove_inventory_category_from_avatar( LLInventoryCategory* category )
 	}
 }
 
+struct OnRemoveStruct
+{
+	LLUUID mUUID;
+	LLFolderView *mFolderToDeleteSelected;
+	OnRemoveStruct(const LLUUID& uuid, LLFolderView *fv = NULL):
+		mUUID(uuid),
+		mFolderToDeleteSelected(fv)
+	{
+	}
+};
 
 void remove_inventory_category_from_avatar_step2( BOOL proceed, LLUUID category_id)
 {
@@ -4030,10 +4088,10 @@ void remove_inventory_category_from_avatar_step2( BOOL proceed, LLUUID category_
 				if( gAgentWearables.isWearingItem (item_array.get(i)->getUUID()) )
 				{
 					LLWearableList::instance().getAsset(item_array.get(i)->getAssetUUID(),
-									item_array.get(i)->getName(),
-								   item_array.get(i)->getType(),
-								    LLWearableBridge::onRemoveFromAvatarArrived,
-								   new LLUUID(item_array.get(i)->getUUID()));
+														item_array.get(i)->getName(),
+														item_array.get(i)->getType(),
+														LLWearableBridge::onRemoveFromAvatarArrived,
+														new OnRemoveStruct(item_array.get(i)->getUUID()));
 
 				}
 			}
@@ -4134,12 +4192,25 @@ void LLWearableBridge::performAction(LLFolderView* folder, LLInventoryModel* mod
 		{
 			LLViewerInventoryItem* item = getItem();
 			if (item)
-			{
-				LLWearableList::instance().getAsset(item->getAssetUUID(),
-										item->getName(),
-									item->getType(),
-									LLWearableBridge::onRemoveFromAvatarArrived,
-									new LLUUID(mUUID));
+			{	
+				if (item->getIsLinkType() &&
+					model->isObjectDescendentOf(mUUID,LLAppearanceManager::getCOF()))
+				{
+					// Delete link after item has been taken off.
+					LLWearableList::instance().getAsset(item->getAssetUUID(),
+														item->getName(),
+														item->getType(),
+														LLWearableBridge::onRemoveFromAvatarArrived,
+														new OnRemoveStruct(mUUID, folder));
+				}
+				else
+				{
+					LLWearableList::instance().getAsset(item->getAssetUUID(),
+														item->getName(),
+														item->getType(),
+														LLWearableBridge::onRemoveFromAvatarArrived,
+														new OnRemoveStruct(mUUID));
+				}
 			}
 		}
 	}
@@ -4452,11 +4523,12 @@ void LLWearableBridge::onRemoveFromAvatar(void* user_data)
 		LLViewerInventoryItem* item = self->getItem();
 		if (item)
 		{
+			LLUUID parent_id = item->getParentUUID();
 			LLWearableList::instance().getAsset(item->getAssetUUID(),
-									item->getName(),
-								   item->getType(),
-								   onRemoveFromAvatarArrived,
-								   new LLUUID(self->mUUID));
+												item->getName(),
+												item->getType(),
+												onRemoveFromAvatarArrived,
+												new OnRemoveStruct(LLUUID(self->mUUID)));
 		}
 	}
 }
@@ -4465,10 +4537,11 @@ void LLWearableBridge::onRemoveFromAvatar(void* user_data)
 void LLWearableBridge::onRemoveFromAvatarArrived(LLWearable* wearable,
 												 void* userdata)
 {
-	LLUUID* item_id = (LLUUID*) userdata;
+	OnRemoveStruct *on_remove_struct = (OnRemoveStruct*) userdata;
+	LLUUID item_id = on_remove_struct->mUUID;
 	if(wearable)
 	{
-		if( gAgentWearables.isWearingItem( *item_id ) )
+		if( gAgentWearables.isWearingItem( item_id ) )
 		{
 			EWearableType type = wearable->getType();
 	
@@ -4481,7 +4554,11 @@ void LLWearableBridge::onRemoveFromAvatarArrived(LLWearable* wearable,
 			}
 		}
 	}
-	delete item_id;
+	if (on_remove_struct->mFolderToDeleteSelected)
+	{
+		on_remove_struct->mFolderToDeleteSelected->removeSelectedItems();
+	}
+	delete on_remove_struct;
 }
 
 LLInvFVBridgeAction* LLInvFVBridgeAction::createAction(LLAssetType::EType asset_type,
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index 4cbf3e169b5..2f3171a8680 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -474,7 +474,10 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id,
 			if (item->getActualType() == LLAssetType::AT_LINK_FOLDER)
 			{
 				LLViewerInventoryCategory *linked_cat = item->getLinkedCategory();
-				if (linked_cat)
+				if (linked_cat && linked_cat->getPreferredType() != LLAssetType::AT_OUTFIT)
+					// BAP - was 
+					// LLAssetType::lookupIsEnsembleCategoryType(linked_cat->getPreferredType()))
+					// Change back once ensemble typing is in place.
 				{
 					if(add(linked_cat,NULL))
 					{
@@ -2492,6 +2495,10 @@ void LLInventoryModel::buildParentChildMap()
 			// root of the agent's inv found.
 			// The inv tree is built.
 			mIsAgentInvUsable = true;
+
+			llinfos << "Inventory initialized, notifying observers" << llendl;
+			addChangedMask(LLInventoryObserver::ALL, LLUUID::null);
+			notifyObservers();
 		}
 	}
 }
diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h
index e3e4f6aca0f..91a1906fab9 100644
--- a/indra/newview/llinventorymodel.h
+++ b/indra/newview/llinventorymodel.h
@@ -730,6 +730,21 @@ class LLNameCategoryCollector : public LLInventoryCollectFunctor
 	std::string mName;
 };
 
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLFindCOFValidItems
+//
+// Collects items that can be legitimately linked to in the COF.
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+class LLFindCOFValidItems : public LLInventoryCollectFunctor
+{
+public:
+	LLFindCOFValidItems() {}
+	virtual ~LLFindCOFValidItems() {}
+	virtual bool operator()(LLInventoryCategory* cat,
+							LLInventoryItem* item);
+	
+};
+
 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 // Class LLFindWearables
 //
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index dbb3062323b..6a55b571aec 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -2158,10 +2158,6 @@ bool idle_startup()
 		// lets create "Friends" and "Friends/All" in the Inventory "Calling Cards" and fill it with buddies
 		LLFriendCardsManager::instance().syncFriendsFolder();
 
-		llinfos << "Setting Inventory changed mask and notifying observers" << llendl;
-		gInventory.addChangedMask(LLInventoryObserver::ALL, LLUUID::null);
-		gInventory.notifyObservers();
-
 		// set up callbacks
 		llinfos << "Registering Callbacks" << llendl;
 		LLMessageSystem* msg = gMessageSystem;
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 2ebf803e5ef..9d278e6a6c2 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -2836,7 +2836,7 @@ class LLSelfRemoveAllAttachments : public view_listener_t
 {
 	bool handleEvent(const LLSD& userdata)
 	{
-		LLAgentWearables::userRemoveAllAttachments(NULL);
+		LLAgentWearables::userRemoveAllAttachments();
 		return true;
 	}
 };
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 1d9ca12af82..92dbb29d8ed 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -80,6 +80,7 @@
 #include "llsky.h"
 #include "llanimstatelabels.h"
 #include "lltrans.h"
+#include "llappearancemgr.h"
 
 #include "llgesturemgr.h" //needed to trigger the voice gesticulations
 #include "llvoiceclient.h"
@@ -5492,6 +5493,18 @@ BOOL LLVOAvatar::detachObject(LLViewerObject *viewer_object)
 			if (isSelf())
 			{
 				// Then make sure the inventory is in sync with the avatar.
+
+				// Update COF contents, don't trigger appearance update.
+				if (gAgent.getAvatarObject() == NULL)
+				{
+					llinfos << "removeItemLinks skipped, avatar is under destruction" << llendl;
+				}
+				else
+				{
+					LLAppearanceManager::removeItemLinks(item_id, false);
+				}
+
+				// BAP - needs to change for label to track link.
 				gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
 				gInventory.notifyObservers();
 			}
diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp
index dfc82ea8d5a..20b8750fbf4 100644
--- a/indra/newview/llvoavatarself.cpp
+++ b/indra/newview/llvoavatarself.cpp
@@ -82,6 +82,7 @@
 #include "llgesturemgr.h" //needed to trigger the voice gesticulations
 #include "llvoiceclient.h"
 #include "llvoicevisualizer.h" // Ventrella
+#include "llappearancemgr.h"
 
 #include "boost/lexical_cast.hpp"
 
@@ -983,6 +984,11 @@ LLViewerJointAttachment *LLVOAvatarSelf::attachObject(LLViewerObject *viewer_obj
 	updateAttachmentVisibility(gAgent.getCameraMode());
 	
 	// Then make sure the inventory is in sync with the avatar.
+	LLViewerInventoryItem *item = gInventory.getItem(attachment->getItemID());
+	if (item)
+	{
+		LLAppearanceManager::wearItem(item,false);  // Add COF link for item.
+	}
 	gInventory.addChangedMask(LLInventoryObserver::LABEL, attachment->getItemID());
 	gInventory.notifyObservers();
 
diff --git a/indra/newview/skins/default/xui/en/menu_inventory.xml b/indra/newview/skins/default/xui/en/menu_inventory.xml
index 7762a7f6679..72cbd3bcd5a 100644
--- a/indra/newview/skins/default/xui/en/menu_inventory.xml
+++ b/indra/newview/skins/default/xui/en/menu_inventory.xml
@@ -457,6 +457,14 @@
          function="Inventory.DoToSelected"
          parameter="replaceoutfit" />
     </menu_item_call>
+    <menu_item_call
+     label="Wear As Ensemble"
+     layout="topleft"
+     name="Wear As Ensemble">
+        <menu_item_call.on_click
+         function="Inventory.DoToSelected"
+         parameter="wearasensemble" />
+    </menu_item_call>
     <menu_item_separator
      layout="topleft" />
     <menu_item_call
-- 
GitLab