diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 9ba44e787b329ceedc310164c8cffabfe0e4325a..b00104c4274006e142111edc77aa1dd662e5f6e9 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -466,6 +466,7 @@ set(viewer_SOURCE_FILES
     llviewchildren.cpp
     llviewerassetstorage.cpp
     llviewerassettype.cpp
+    llviewerattachmenu.cpp
     llvieweraudio.cpp
     llviewercamera.cpp
     llviewerchat.cpp
@@ -984,6 +985,7 @@ set(viewer_HEADER_FILES
     llviewchildren.h
     llviewerassetstorage.h
     llviewerassettype.h
+    llviewerattachmenu.h
     llvieweraudio.h
     llviewercamera.h
     llviewerchat.h
diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp
index e5796f8e639c9531ca27187d0b34645f1bc21f3f..d2a01aba16d549068d487a2472f64489e019580e 100644
--- a/indra/newview/llagentwearables.cpp
+++ b/indra/newview/llagentwearables.cpp
@@ -1973,6 +1973,32 @@ bool LLAgentWearables::moveWearable(const LLViewerInventoryItem* item, bool clos
 	return false;
 }
 
+// static
+void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, const LLUUID& parent_id)
+{
+	LLWearable* wearable = LLWearableList::instance().createNewWearable(type);
+	LLAssetType::EType asset_type = wearable->getAssetType();
+	LLInventoryType::EType inv_type = LLInventoryType::IT_WEARABLE;
+	LLPointer<LLInventoryCallback> cb = wear ? new WearOnAvatarCallback : NULL;
+	LLUUID folder_id;
+
+	if (parent_id.notNull())
+	{
+		folder_id = parent_id;
+	}
+	else
+	{
+		LLFolderType::EType folder_type = LLFolderType::assetTypeToFolderType(asset_type);
+		folder_id = gInventory.findCategoryUUIDForType(folder_type);
+	}
+
+	create_inventory_item(gAgent.getID(), gAgent.getSessionID(),
+						  folder_id, wearable->getTransactionID(), wearable->getName(),
+						  wearable->getDescription(), asset_type, inv_type, wearable->getType(),
+						  wearable->getPermissions().getMaskNextOwner(),
+						  cb);
+}
+
 // static
 void LLAgentWearables::editWearable(const LLUUID& item_id)
 {
diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h
index 5d5c5ae3718aa424830f9dd3423a5714ad8581af..6d379746baefe8fc73896cda065c6a7317dab06d 100644
--- a/indra/newview/llagentwearables.h
+++ b/indra/newview/llagentwearables.h
@@ -144,6 +144,7 @@ class LLAgentWearables
 	//--------------------------------------------------------------------
 
 public:
+	static void		createWearable(LLWearableType::EType type, bool wear = false, const LLUUID& parent_id = LLUUID::null);
 	static void		editWearable(const LLUUID& item_id);
 	bool			moveWearable(const LLViewerInventoryItem* item, bool closer_to_body);
 
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index f27e632180fb79c33f6fbf52a34c4753a7b055e5..cf45743a4860f658519fb50e3b22a7f0ffbbd8b9 100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -714,7 +714,7 @@ void LLAppearanceMgr::takeOffOutfit(const LLUUID& cat_id)
 {
 	LLInventoryModel::cat_array_t cats;
 	LLInventoryModel::item_array_t items;
-	LLFindWearables collector;
+	LLFindWorn collector;
 
 	gInventory.collectDescendentsIf(cat_id, cats, items, FALSE, collector);
 
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index 1f87b14dddf1f3f372656146ef33bed58d03d364..b7495f7dbe0207bb1c988f7cc9f1b70e36c2b9bf 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -164,38 +164,7 @@ time_t LLInvFVBridge::getCreationDate() const
 // Can be destroyed (or moved to trash)
 BOOL LLInvFVBridge::isItemRemovable() const
 {
-	const LLInventoryModel* model = getInventoryModel();
-	if(!model) 
-	{
-		return FALSE;
-	}
-
-	// Can't delete an item that's in the library.
-	if(!model->isObjectDescendentOf(mUUID, gInventory.getRootFolderID()))
-	{
-		return FALSE;
-	}
-
-	// Disable delete from COF folder; have users explicitly choose "detach/take off",
-	// unless the item is not worn but in the COF (i.e. is bugged).
-	if (LLAppearanceMgr::instance().getIsProtectedCOFItem(mUUID))
-	{
-		if (get_is_item_worn(mUUID))
-		{
-			return FALSE;
-		}
-	}
-
-	const LLInventoryObject *obj = model->getItem(mUUID);
-	if (obj && obj->getIsLinkType())
-	{
-		return TRUE;
-	}
-	if (get_is_item_worn(mUUID))
-	{
-		return FALSE;
-	}
-	return TRUE;
+	return get_is_item_removable(getInventoryModel(), mUUID);
 }
 
 // Can be moved to another folder
@@ -833,24 +802,7 @@ void LLInvFVBridge::changeCategoryParent(LLInventoryModel* model,
 										 const LLUUID& new_parent_id,
 										 BOOL restamp)
 {
-	// Can't move a folder into a child of itself.
-	if (model->isObjectDescendentOf(new_parent_id, cat->getUUID()))
-	{
-		return;
-	}
-
-	LLInventoryModel::update_list_t update;
-	LLInventoryModel::LLCategoryUpdate old_folder(cat->getParentUUID(), -1);
-	update.push_back(old_folder);
-	LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1);
-	update.push_back(new_folder);
-	model->accountForUpdate(update);
-	
-	LLPointer<LLViewerInventoryCategory> new_cat = new LLViewerInventoryCategory(cat);
-	new_cat->setParent(new_parent_id);
-	new_cat->updateParentOnServer(restamp);
-	model->updateCategory(new_cat);
-	model->notifyObservers();
+	change_category_parent(model, cat, new_parent_id, restamp);
 }
 
 LLInvFVBridge* LLInvFVBridge::createBridge(LLAssetType::EType asset_type,
@@ -1538,26 +1490,7 @@ class LLIsItemRemovable : public LLFolderViewFunctor
 // Can be destroyed (or moved to trash)
 BOOL LLFolderBridge::isItemRemovable() const
 {
-	LLInventoryModel* model = getInventoryModel();
-	if(!model)
-	{
-		return FALSE;
-	}
-
-	if(!model->isObjectDescendentOf(mUUID, gInventory.getRootFolderID()))
-	{
-		return FALSE;
-	}
-
-	if (!isAgentAvatarValid()) return FALSE;
-
-	LLInventoryCategory* category = model->getCategory(mUUID);
-	if(!category)
-	{
-		return FALSE;
-	}
-
-	if(LLFolderType::lookupIsProtectedType(category->getPreferredType()))
+	if (!get_is_category_removable(getInventoryModel(), mUUID))
 	{
 		return FALSE;
 	}
@@ -1573,6 +1506,7 @@ BOOL LLFolderBridge::isItemRemovable() const
 			return FALSE;
 		}
 	}
+
 	return TRUE;
 }
 
@@ -2379,21 +2313,8 @@ LLUIImagePtr LLFolderBridge::getOpenIcon() const
 
 BOOL LLFolderBridge::renameItem(const std::string& new_name)
 {
-	if(!isItemRenameable())
-		return FALSE;
-	LLInventoryModel* model = getInventoryModel();
-	if(!model)
-		return FALSE;
-	LLViewerInventoryCategory* cat = getCategory();
-	if(cat && (cat->getName() != new_name))
-	{
-		LLPointer<LLViewerInventoryCategory> new_cat = new LLViewerInventoryCategory(cat);
-		new_cat->rename(new_name);
-		new_cat->updateServer(FALSE);
-		model->updateCategory(new_cat);
+	rename_category(getInventoryModel(), mUUID, new_name);
 
-		model->notifyObservers();
-	}
 	// return FALSE because we either notified observers (& therefore
 	// rebuilt) or we didn't update.
 	return FALSE;
@@ -2447,36 +2368,7 @@ bool LLFolderBridge::removeItemResponse(const LLSD& notification, const LLSD& re
 	{
 		// move it to the trash
 		LLPreview::hide(mUUID);
-		LLInventoryModel* model = getInventoryModel();
-		if(!model) return FALSE;
-		
-		const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH);
-		
-		// Look for any gestures and deactivate them
-		LLInventoryModel::cat_array_t	descendent_categories;
-		LLInventoryModel::item_array_t	descendent_items;
-		gInventory.collectDescendents( mUUID, descendent_categories, descendent_items, FALSE );
-		
-		for (LLInventoryModel::item_array_t::const_iterator iter = descendent_items.begin();
-			 iter != descendent_items.end();
-			 ++iter)
-		{
-			const LLViewerInventoryItem* item = (*iter);
-			const LLUUID& item_id = item->getUUID();
-			if (item->getType() == LLAssetType::AT_GESTURE
-				&& LLGestureMgr::instance().isGestureActive(item_id))
-			{
-				LLGestureMgr::instance().deactivateGesture(item_id);
-			}
-		}
-		
-		// go ahead and do the normal remove if no 'last calling
-		// cards' are being removed.
-		LLViewerInventoryCategory* cat = getCategory();
-		if(cat)
-		{
-			LLInvFVBridge::changeCategoryParent(model, cat, trash_id, TRUE);
-		}
+		remove_category(getInventoryModel(), mUUID);
 		return TRUE;
 	}
 	return FALSE;
@@ -2672,22 +2564,6 @@ BOOL LLFolderBridge::checkFolderForContentsOfType(LLInventoryModel* model, LLInv
 	return ((item_array.count() > 0) ? TRUE : FALSE );
 }
 
-class LLFindWorn : public LLInventoryCollectFunctor
-{
-public:
-	LLFindWorn() {}
-	virtual ~LLFindWorn() {}
-	virtual bool operator()(LLInventoryCategory* cat,
-							LLInventoryItem* item)
-	{
-		if (item && get_is_item_worn(item->getUUID()))
-		{
-			return TRUE;
-		}
-		return FALSE;
-	}
-};
-
 BOOL LLFolderBridge::areAnyContentsWorn(LLInventoryModel* model) const
 {
 	LLInventoryModel::cat_array_t cat_array;
@@ -3006,22 +2882,7 @@ void LLFolderBridge::createWearable(LLFolderBridge* bridge, LLWearableType::ETyp
 {
 	if(!bridge) return;
 	LLUUID parent_id = bridge->getUUID();
-	createWearable(parent_id, type);
-}
-
-// Separate function so can be called by global menu as well as right-click
-// menu.
-// static
-void LLFolderBridge::createWearable(const LLUUID &parent_id, LLWearableType::EType type)
-{
-	LLWearable* wearable = LLWearableList::instance().createNewWearable(type);
-	LLAssetType::EType asset_type = wearable->getAssetType();
-	LLInventoryType::EType inv_type = LLInventoryType::IT_WEARABLE;
-	create_inventory_item(gAgent.getID(), gAgent.getSessionID(),
-						  parent_id, wearable->getTransactionID(), wearable->getName(),
-						  wearable->getDescription(), asset_type, inv_type, wearable->getType(),
-						  wearable->getPermissions().getMaskNextOwner(),
-						  LLPointer<LLInventoryCallback>(NULL));
+	LLAgentWearables::createWearable(type, false, parent_id);
 }
 
 void LLFolderBridge::modifyOutfit(BOOL append)
@@ -4898,13 +4759,7 @@ void LLWearableBridge::onEditOnAvatar(void* user_data)
 
 void LLWearableBridge::editOnAvatar()
 {
-	LLWearable* wearable = gAgentWearables.getWearableFromItemID(mUUID);
-	if( wearable )
-	{
-		LLPanel * panel = LLSideTray::getInstance()->getPanel("sidepanel_appearance");
-
-		LLSidepanelAppearance::editWearable(wearable, panel);
-	}
+	LLAgentWearables::editWearable(mUUID);
 }
 
 // static
diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h
index c5efefac7e6537ea7bcb35c8eef596b1f76b8495..59c1f3d6fb52c3b8b999071036042791a54f472e 100644
--- a/indra/newview/llinventorybridge.h
+++ b/indra/newview/llinventorybridge.h
@@ -270,7 +270,6 @@ class LLFolderBridge : public LLInvFVBridge
 	virtual BOOL copyToClipboard() const;
 	
 	static void createWearable(LLFolderBridge* bridge, LLWearableType::EType type);
-	static void createWearable(const LLUUID &parent_folder_id, LLWearableType::EType type);
 
 	LLViewerInventoryCategory* getCategory() const;
 
diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp
index 0cc4b0e389594919d0795092ff7c17d48d37e1fb..9fe9d2de8ebfd8d8abef68f79d3ecb2008aea158 100644
--- a/indra/newview/llinventoryfunctions.cpp
+++ b/indra/newview/llinventoryfunctions.cpp
@@ -130,6 +130,90 @@ void change_item_parent(LLInventoryModel* model,
 	}
 }
 
+void change_category_parent(LLInventoryModel* model,
+	LLViewerInventoryCategory* cat,
+	const LLUUID& new_parent_id,
+	BOOL restamp)
+{
+	if (!model || !cat)
+	{
+		return;
+	}
+
+	// Can't move a folder into a child of itself.
+	if (model->isObjectDescendentOf(new_parent_id, cat->getUUID()))
+	{
+		return;
+	}
+
+	LLInventoryModel::update_list_t update;
+	LLInventoryModel::LLCategoryUpdate old_folder(cat->getParentUUID(), -1);
+	update.push_back(old_folder);
+	LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1);
+	update.push_back(new_folder);
+	model->accountForUpdate(update);
+
+	LLPointer<LLViewerInventoryCategory> new_cat = new LLViewerInventoryCategory(cat);
+	new_cat->setParent(new_parent_id);
+	new_cat->updateParentOnServer(restamp);
+	model->updateCategory(new_cat);
+	model->notifyObservers();
+}
+
+void remove_category(LLInventoryModel* model, const LLUUID& cat_id)
+{
+	if (!model || !get_is_category_removable(model, cat_id))
+	{
+		return;
+	}
+
+	// Look for any gestures and deactivate them
+	LLInventoryModel::cat_array_t	descendent_categories;
+	LLInventoryModel::item_array_t	descendent_items;
+	gInventory.collectDescendents(cat_id, descendent_categories, descendent_items, FALSE);
+
+	for (LLInventoryModel::item_array_t::const_iterator iter = descendent_items.begin();
+		 iter != descendent_items.end();
+		 ++iter)
+	{
+		const LLViewerInventoryItem* item = (*iter);
+		const LLUUID& item_id = item->getUUID();
+		if (item->getType() == LLAssetType::AT_GESTURE
+			&& LLGestureMgr::instance().isGestureActive(item_id))
+		{
+			LLGestureMgr::instance().deactivateGesture(item_id);
+		}
+	}
+
+	// go ahead and do the normal remove if no 'last calling
+	// cards' are being removed.
+	LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
+	if (cat)
+	{
+		const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH);
+		change_category_parent(model, cat, trash_id, TRUE);
+	}
+}
+
+void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name)
+{
+	LLViewerInventoryCategory* cat;
+
+	if (!model ||
+		!get_is_category_renameable(model, cat_id) ||
+		(cat = model->getCategory(cat_id)) == NULL ||
+		cat->getName() == new_name)
+	{
+		return;
+	}
+
+	LLPointer<LLViewerInventoryCategory> new_cat = new LLViewerInventoryCategory(cat);
+	new_cat->rename(new_name);
+	new_cat->updateServer(FALSE);
+	model->updateCategory(new_cat);
+
+	model->notifyObservers();
+}
 
 BOOL get_is_item_worn(const LLUUID& id)
 {
@@ -160,6 +244,83 @@ BOOL get_is_item_worn(const LLUUID& id)
 	return FALSE;
 }
 
+BOOL get_is_item_removable(const LLInventoryModel* model, const LLUUID& id)
+{
+	if (!model)
+	{
+		return FALSE;
+	}
+
+	// Can't delete an item that's in the library.
+	if (!model->isObjectDescendentOf(id, gInventory.getRootFolderID()))
+	{
+		return FALSE;
+	}
+
+	// Disable delete from COF folder; have users explicitly choose "detach/take off",
+	// unless the item is not worn but in the COF (i.e. is bugged).
+	if (LLAppearanceMgr::instance().getIsProtectedCOFItem(id))
+	{
+		if (get_is_item_worn(id))
+		{
+			return FALSE;
+		}
+	}
+
+	const LLInventoryObject *obj = model->getItem(id);
+	if (obj && obj->getIsLinkType())
+	{
+		return TRUE;
+	}
+	if (get_is_item_worn(id))
+	{
+		return FALSE;
+	}
+	return TRUE;
+}
+
+BOOL get_is_category_removable(const LLInventoryModel* model, const LLUUID& id)
+{
+	// This function doesn't check the folder's children.
+
+	if (!model)
+	{
+		return FALSE;
+	}
+
+	if (!model->isObjectDescendentOf(id, gInventory.getRootFolderID()))
+	{
+		return FALSE;
+	}
+
+	if (!isAgentAvatarValid()) return FALSE;
+
+	LLInventoryCategory* category = model->getCategory(id);
+	if (!category)
+	{
+		return FALSE;
+	}
+
+	if (LLFolderType::lookupIsProtectedType(category->getPreferredType()))
+	{
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+BOOL get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id)
+{
+	LLViewerInventoryCategory* cat = model->getCategory(id);
+
+	if (cat && !LLFolderType::lookupIsProtectedType(cat->getPreferredType()) &&
+		cat->getOwnerID() == gAgent.getID())
+	{
+		return TRUE;
+	}
+	return FALSE;
+}
+
 void show_item_profile(const LLUUID& item_uuid)
 {
 	LLUUID linked_uuid = gInventory.getLinkedItemID(item_uuid);
@@ -376,6 +537,11 @@ void LLFindWearablesOfType::setType(LLWearableType::EType type)
 	mWearableType = type;
 }
 
+bool LLFindWorn::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
+{
+	return item && get_is_item_worn(item->getUUID());
+}
+
 ///----------------------------------------------------------------------------
 /// LLAssetIDMatches 
 ///----------------------------------------------------------------------------
diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h
index bb365573d7756d266384f249b2a58466324118fa..5c07a3190f655e60b3ad59e1d8d47a2aba423805 100644
--- a/indra/newview/llinventoryfunctions.h
+++ b/indra/newview/llinventoryfunctions.h
@@ -46,6 +46,12 @@
 // Is this item or its baseitem is worn, attached, etc...
 BOOL get_is_item_worn(const LLUUID& id);
 
+BOOL get_is_item_removable(const LLInventoryModel* model, const LLUUID& id);
+
+BOOL get_is_category_removable(const LLInventoryModel* model, const LLUUID& id);
+
+BOOL get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id);
+
 void show_item_profile(const LLUUID& item_uuid);
 
 void show_item_original(const LLUUID& item_uuid);
@@ -55,6 +61,15 @@ void change_item_parent(LLInventoryModel* model,
 									 const LLUUID& new_parent_id,
 									 BOOL restamp);
 
+void change_category_parent(LLInventoryModel* model,
+	LLViewerInventoryCategory* cat,
+	const LLUUID& new_parent_id,
+	BOOL restamp);
+
+void remove_category(LLInventoryModel* model, const LLUUID& cat_id);
+
+void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name);
+
 // Generates a string containing the path to the item specified by item_id.
 void append_path(const LLUUID& id, std::string& path);
 
@@ -309,6 +324,16 @@ class LLFindWearablesOfType : public LLInventoryCollectFunctor
 	LLWearableType::EType mWearableType;
 };
 
+// Find worn items.
+class LLFindWorn : public LLInventoryCollectFunctor
+{
+public:
+	LLFindWorn() {}
+	virtual ~LLFindWorn() {}
+	virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item);
+};
+
+
 /**                    Inventory Collector Functions
  **                                                                            **
  *******************************************************************************/
diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp
index 4766c1c22781b72fdadd9bb61e806d8e506c1c22..bb3f34dde2158cf7d3c03e0dff388919b4ff77fc 100644
--- a/indra/newview/llinventorypanel.cpp
+++ b/indra/newview/llinventorypanel.cpp
@@ -49,6 +49,7 @@
 #include "llsidepanelinventory.h"
 #include "llsidetray.h"
 #include "llscrollcontainer.h"
+#include "llviewerattachmenu.h"
 #include "llviewerfoldertype.h"
 #include "llvoavatarself.h"
 
@@ -877,48 +878,19 @@ bool LLInventoryPanel::beginIMSession()
 
 bool LLInventoryPanel::attachObject(const LLSD& userdata)
 {
+	// Copy selected item UUIDs to a vector.
 	std::set<LLUUID> selected_items = mFolderRoot->getSelectionList();
-
-	std::string joint_name = userdata.asString();
-	LLViewerJointAttachment* attachmentp = NULL;
-	for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); 
-		 iter != gAgentAvatarp->mAttachmentPoints.end(); )
-	{
-		LLVOAvatar::attachment_map_t::iterator curiter = iter++;
-		LLViewerJointAttachment* attachment = curiter->second;
-		if (attachment->getName() == joint_name)
-		{
-			attachmentp = attachment;
-			break;
-		}
-	}
-	if (attachmentp == NULL)
-	{
-		return true;
-	}
-
+	uuid_vec_t items;
 	for (std::set<LLUUID>::const_iterator set_iter = selected_items.begin(); 
 		 set_iter != selected_items.end(); 
 		 ++set_iter)
 	{
-		const LLUUID &id = *set_iter;
-		LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(id);
-		if(item && gInventory.isObjectDescendentOf(id, gInventory.getRootFolderID()))
-		{
-			rez_attachment(item, attachmentp);
-		}
-		else if(item && item->isFinished())
-		{
-			// must be in library. copy it to our inventory and put it on.
-			LLPointer<LLInventoryCallback> cb = new RezAttachmentCallback(attachmentp);
-			copy_inventory_item(gAgent.getID(),
-								item->getPermissions().getOwner(),
-								item->getUUID(),
-								LLUUID::null,
-								std::string(),
-								cb);
-		}
+		items.push_back(*set_iter);
 	}
+
+	// Attach selected items.
+	LLViewerAttachMenu::attachObjects(items, userdata.asString());
+
 	gFocusMgr.setKeyboardFocus(NULL);
 
 	return true;
diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp
index 98ec272e9656250cccd69802ac37ec125c3e37c8..5c8b3d1894e30480a6a819cf221e7a78c6330ce0 100644
--- a/indra/newview/lloutfitslist.cpp
+++ b/indra/newview/lloutfitslist.cpp
@@ -43,6 +43,8 @@
 #include "llinventoryfunctions.h"
 #include "llinventorymodel.h"
 #include "lllistcontextmenu.h"
+#include "llnotificationsutil.h"
+#include "llsidetray.h"
 #include "lltransutil.h"
 #include "llviewermenu.h"
 #include "llvoavatar.h"
@@ -53,6 +55,28 @@ static bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y);
 
 //////////////////////////////////////////////////////////////////////////
 
+// Collect non-removable folders and items.
+class LLFindNonRemovableObjects : public LLInventoryCollectFunctor
+{
+public:
+	virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item)
+	{
+		if (item)
+		{
+			return !get_is_item_removable(&gInventory, item->getUUID());
+		}
+		if (cat)
+		{
+			return !get_is_category_removable(&gInventory, cat->getUUID());
+		}
+
+		llwarns << "Not a category and not an item?" << llendl;
+		return false;
+	}
+};
+
+//////////////////////////////////////////////////////////////////////////
+
 class OutfitContextMenu : public LLListContextMenu
 {
 protected:
@@ -68,12 +92,124 @@ class OutfitContextMenu : public LLListContextMenu
 			boost::bind(&LLAppearanceMgr::addCategoryToCurrentOutfit, &LLAppearanceMgr::instance(), selected_id));
 		registrar.add("Outfit.TakeOff",
 				boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id));
-		// *TODO: implement this
-		// registrar.add("Outfit.Rename", boost::bind());
-		// registrar.add("Outfit.Delete", boost::bind());
+		registrar.add("Outfit.Edit", boost::bind(editOutfit));
+		registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id));
+		registrar.add("Outfit.Delete", boost::bind(deleteOutfit, selected_id));
+
+		enable_registrar.add("Outfit.OnEnable", boost::bind(&OutfitContextMenu::onEnable, this, _2));
 
 		return createFromFile("menu_outfit_tab.xml");
 	}
+
+	bool onEnable(const LLSD& data)
+	{
+		std::string param = data.asString();
+		LLUUID outfit_cat_id = mUUIDs.back();
+		bool is_worn = LLAppearanceMgr::instance().getBaseOutfitUUID() == outfit_cat_id;
+
+		if ("wear_replace" == param)
+		{
+			return !is_worn;
+		}
+		else if ("wear_add" == param)
+		{
+			return !is_worn;
+		}
+		else if ("take_off" == param)
+		{
+			return is_worn;
+		}
+		else if ("edit" == param)
+		{
+			return is_worn;
+		}
+		else if ("rename" == param)
+		{
+			return get_is_category_renameable(&gInventory, outfit_cat_id);
+		}
+		else if ("delete" == param)
+		{
+			return canDeleteOutfit(outfit_cat_id);
+		}
+
+		return true;
+	}
+
+	static void editOutfit()
+	{
+		LLSideTray::getInstance()->showPanel("sidepanel_appearance", LLSD().with("type", "edit_outfit"));
+	}
+
+	static void renameOutfit(const LLUUID& outfit_cat_id)
+	{
+		LLViewerInventoryCategory* outfit_cat = gInventory.getCategory(outfit_cat_id);
+		llassert(outfit_cat);
+		if (!outfit_cat) return;
+
+		LLSD args;
+		args["NAME"] = outfit_cat->getName();
+
+		LLSD payload;
+		payload["cat_id"] = outfit_cat_id;
+
+		LLNotificationsUtil::add("RenameOutfit", args, payload, boost::bind(onRename, _1, _2));
+	}
+
+	// User typed new outfit name.
+	static void onRename(const LLSD& notification, const LLSD& response)
+	{
+		S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+		if (option != 0) return; // canceled
+
+		std::string outfit_name = response["new_name"].asString();
+		LLStringUtil::trim(outfit_name);
+		if (!outfit_name.empty())
+		{
+			LLUUID cat_id = notification["payload"]["cat_id"].asUUID();
+			rename_category(&gInventory, cat_id, outfit_name);
+		}
+	}
+
+	static bool canDeleteOutfit(const LLUUID& outfit_cat_id)
+	{
+		// Disallow removing the base outfit.
+		if (outfit_cat_id == LLAppearanceMgr::instance().getBaseOutfitUUID())
+		{
+			return false;
+		}
+
+		// Check if the outfit folder itself is removable.
+		if (!get_is_category_removable(&gInventory, outfit_cat_id))
+		{
+			return false;
+		}
+
+		// Check if the folder contains worn items.
+		LLInventoryModel::cat_array_t cats;
+		LLInventoryModel::item_array_t items;
+		LLFindWorn filter_worn;
+		gInventory.collectDescendentsIf(outfit_cat_id, cats, items, false, filter_worn);
+		if (!items.empty())
+		{
+			return false;
+		}
+
+		// Check for the folder's non-removable descendants.
+		LLFindNonRemovableObjects filter_non_removable;
+		LLInventoryModel::item_array_t::const_iterator it;
+		gInventory.collectDescendentsIf(outfit_cat_id, cats, items, false, filter_non_removable);
+		if (!cats.empty() || !items.empty())
+		{
+			return false;
+		}
+
+		return true;
+	}
+
+	static void deleteOutfit(const LLUUID& outfit_cat_id)
+	{
+		remove_category(&gInventory, outfit_cat_id);
+	}
 };
 
 //////////////////////////////////////////////////////////////////////////
diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp
index 21f69d3470e80390832bcbc8a34b48f642166128..6fb016cdfd56c32ea23e2ff7ca8b998c07c9cce5 100644
--- a/indra/newview/llpaneloutfitsinventory.cpp
+++ b/indra/newview/llpaneloutfitsinventory.cpp
@@ -42,6 +42,7 @@
 #include "llfloaterworldmap.h"
 #include "llfloaterinventory.h"
 #include "llfoldervieweventlistener.h"
+#include "llinventorybridge.h"
 #include "llinventoryfunctions.h"
 #include "llinventorymodelbackgroundfetch.h"
 #include "llinventorypanel.h"
@@ -70,6 +71,89 @@ static const std::string COF_TAB_NAME = "cof_tab";
 
 static LLRegisterPanelClassWrapper<LLPanelOutfitsInventory> t_inventory("panel_outfits_inventory");
 
+// Context-dependent menu actions are not implemented
+// because accordions don't properly support selection yet.
+class LLOutfitListGearMenu
+{
+public:
+	static LLMenuGL* createMenu()
+	{
+		LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+		LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
+
+		registrar.add("Gear.Wear", boost::bind(onWear));
+		registrar.add("Gear.TakeOff", boost::bind(onTakeOff));
+		registrar.add("Gear.Rename", boost::bind(onRename));
+		registrar.add("Gear.Delete", boost::bind(onDelete));
+		registrar.add("Gear.Create", boost::bind(onCreate, _2));
+
+		enable_registrar.add("Gear.OnEnable", boost::bind(onEnable, _2));
+
+		return LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>(
+			"menu_outfit_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
+	}
+
+private:
+	static void onWear()
+	{
+		// *TODO: not implemented
+	}
+
+	static void onTakeOff()
+	{
+		// *TODO: not implemented
+	}
+
+	static void onRename()
+	{
+		// *TODO: not implemented
+	}
+
+	static void onDelete()
+	{
+		// *TODO: not implemented
+	}
+
+	static void onCreate(const LLSD& data)
+	{
+		LLWearableType::EType type = LLWearableType::typeNameToType(data.asString());
+		if (type == LLWearableType::WT_NONE)
+		{
+			llwarns << "Invalid wearable type" << llendl;
+			return;
+		}
+
+		LLAgentWearables::createWearable(type, true);
+	}
+
+	static bool onEnable(const LLSD& data)
+	{
+		std::string param = data.asString();
+
+		if ("wear" == param)
+		{
+			// *TODO: not implemented
+			return false;
+		}
+		else if ("take_off" == param)
+		{
+			// *TODO: not implemented
+			return false;
+		}
+		else if ("rename" == param)
+		{
+			// *TODO: not implemented
+			return false;
+		}
+		else if ("delete" == param)
+		{
+			// *TODO: not implemented
+			return false;
+		}
+
+		return true;
+	}
+};
 
 LLPanelOutfitsInventory::LLPanelOutfitsInventory() :
 	mMyOutfitsPanel(NULL),
@@ -385,8 +469,7 @@ void LLPanelOutfitsInventory::initListCommandsHandlers()
 				   ,       _7 // EAcceptance* accept
 				   ));
 
-	mMenuGearDefault = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_outfit_gear.xml",
-		gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
+	mGearMenu = LLOutfitListGearMenu::createMenu();
 }
 
 void LLPanelOutfitsInventory::updateListCommands()
@@ -403,7 +486,7 @@ void LLPanelOutfitsInventory::updateListCommands()
 
 void LLPanelOutfitsInventory::onGearButtonClick()
 {
-	showActionMenu(mMenuGearDefault,"options_gear_btn");
+	showActionMenu(mGearMenu, "options_gear_btn");
 }
 
 void LLPanelOutfitsInventory::showActionMenu(LLMenuGL* menu, std::string spawning_view_name)
diff --git a/indra/newview/llpaneloutfitsinventory.h b/indra/newview/llpaneloutfitsinventory.h
index 7bdd37c16c2e2da078ef2d1b05ccd773c5e5367b..8e76688de562b51a73bd37469f4faa792d3b6ad4 100644
--- a/indra/newview/llpaneloutfitsinventory.h
+++ b/indra/newview/llpaneloutfitsinventory.h
@@ -129,7 +129,7 @@ class LLPanelOutfitsInventory : public LLPanel
 	void onWearablesLoaded();
 private:
 	LLPanel*					mListCommands;
-	LLMenuGL*					mMenuGearDefault;
+	LLMenuGL*					mGearMenu;
 	LLMenuGL*					mMenuAdd;
 	// List Commands                                                              //
 	////////////////////////////////////////////////////////////////////////////////
diff --git a/indra/newview/llviewerattachmenu.cpp b/indra/newview/llviewerattachmenu.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f7f5ec72fd5155c4e436d6bb20349ab119652c7c
--- /dev/null
+++ b/indra/newview/llviewerattachmenu.cpp
@@ -0,0 +1,139 @@
+/** 
+ * @file llviewerattachmenu.cpp
+ * @brief "Attach to" / "Attach to HUD" submenus.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ * 
+ * Copyright (c) 2010, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llviewerattachmenu.h"
+
+// project includes
+#include "llagent.h"
+#include "llinventorybridge.h" // for rez_attachment()
+#include "llinventorymodel.h"
+#include "llviewerinventory.h"
+#include "llviewermenu.h" // for gMenuHolder
+#include "llvoavatarself.h"
+
+// linden libraries
+#include "llmenugl.h"
+#include "lltrans.h"
+
+// static
+void LLViewerAttachMenu::populateMenus(const std::string& attach_to_menu_name, const std::string& attach_to_hud_menu_name)
+{
+	// *TODO: share this code with other similar menus
+	// (inventory panel context menu, in-world object menu).
+
+	if (attach_to_menu_name.empty() || attach_to_hud_menu_name.empty() || !isAgentAvatarValid()) return;
+
+	LLContextMenu* attach_menu = gMenuHolder->getChild<LLContextMenu>(attach_to_menu_name);
+	LLContextMenu* attach_hud_menu = gMenuHolder->getChild<LLContextMenu>(attach_to_hud_menu_name);
+
+	if (!attach_menu || attach_menu->getChildCount() != 0 ||
+		!attach_hud_menu || attach_hud_menu->getChildCount() != 0)
+	{
+		return;
+	}
+
+	// Populate "Attach to..." / "Attach to HUD..." submenus.
+	for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin();
+		 iter != gAgentAvatarp->mAttachmentPoints.end(); )
+	{
+		LLVOAvatar::attachment_map_t::iterator curiter = iter++;
+		LLViewerJointAttachment* attachment = curiter->second;
+		LLMenuItemCallGL::Params p;
+		std::string submenu_name = attachment->getName();
+		std::string translated_submenu_name;
+
+		if (LLTrans::findString(translated_submenu_name, submenu_name))
+		{
+			p.name = (" ") + translated_submenu_name + " ";
+		}
+		else
+		{
+			p.name = submenu_name;
+		}
+
+		LLSD cbparams;
+		cbparams["index"] = curiter->first;
+		cbparams["label"] = attachment->getName();
+		p.on_click.function_name = "Object.Attach";
+		p.on_click.parameter = LLSD(attachment->getName());
+		p.on_enable.function_name = "Attachment.Label";
+		p.on_enable.parameter = cbparams;
+
+		LLMenuItemCallGL* item = LLUICtrlFactory::create<LLMenuItemCallGL>(p);
+		LLView* parent_menu = attachment->getIsHUDAttachment() ? attach_hud_menu : attach_menu;
+		parent_menu->addChild(item);
+	}
+}
+
+// static
+void LLViewerAttachMenu::attachObjects(const uuid_vec_t& items, const std::string& joint_name)
+{
+	LLViewerJointAttachment* attachmentp = NULL;
+	for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); 
+		 iter != gAgentAvatarp->mAttachmentPoints.end(); )
+	{
+		LLVOAvatar::attachment_map_t::iterator curiter = iter++;
+		LLViewerJointAttachment* attachment = curiter->second;
+		if (attachment->getName() == joint_name)
+		{
+			attachmentp = attachment;
+			break;
+		}
+	}
+	if (attachmentp == NULL)
+	{
+		return;
+	}
+
+	for (uuid_vec_t::const_iterator it = items.begin(); it != items.end(); ++it)
+	{
+		const LLUUID &id = *it;
+		LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getLinkedItem(id);
+		if(item && gInventory.isObjectDescendentOf(id, gInventory.getRootFolderID()))
+		{
+			rez_attachment(item, attachmentp);
+		}
+		else if(item && item->isFinished())
+		{
+			// must be in library. copy it to our inventory and put it on.
+			LLPointer<LLInventoryCallback> cb = new RezAttachmentCallback(attachmentp);
+			copy_inventory_item(gAgent.getID(),
+								item->getPermissions().getOwner(),
+								item->getUUID(),
+								LLUUID::null,
+								std::string(),
+								cb);
+		}
+	}
+}
diff --git a/indra/newview/llviewerattachmenu.h b/indra/newview/llviewerattachmenu.h
new file mode 100644
index 0000000000000000000000000000000000000000..d1db9914f3cffe06f3e731e82d6f28fe812ee3a2
--- /dev/null
+++ b/indra/newview/llviewerattachmenu.h
@@ -0,0 +1,43 @@
+/** 
+ * @file llviewerattachmenu.h
+ * @brief "Attach to" / "Attach to HUD" submenus.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ * 
+ * Copyright (c) 2010, Linden Research, Inc.
+ * 
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ * 
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ * 
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ * 
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLVIEWERATTACHMENU_H
+#define LL_LLVIEWERATTACHMENU_H
+
+class LLViewerAttachMenu
+{
+public:
+	static void populateMenus(const std::string& attach_to_menu_name, const std::string& attach_to_hud_menu_name);
+	static void attachObjects(const uuid_vec_t& items, const std::string& joint_name);
+};
+
+#endif // LL_LLVIEWERATTACHMENU_H
diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp
index 77b0d9f8d310f346fdf56e81c715ba1b169b9315..a788d3c45784c6047ce8cc108e1da70d74a564fd 100644
--- a/indra/newview/llviewerinventory.cpp
+++ b/indra/newview/llviewerinventory.cpp
@@ -39,6 +39,7 @@
 
 #include "llagent.h"
 #include "llagentcamera.h"
+#include "llagentwearables.h"
 #include "llviewerfoldertype.h"
 #include "llfolderview.h"
 #include "llviewercontrol.h"
@@ -880,6 +881,14 @@ void WearOnAvatarCallback::fire(const LLUUID& inv_item)
 void ModifiedCOFCallback::fire(const LLUUID& inv_item)
 {
 	LLAppearanceMgr::instance().updateAppearanceFromCOF();
+
+	if (LLSideTray::getInstance()->isPanelActive("sidepanel_appearance"))
+	{
+		// *HACK: Edit the wearable that has just been worn
+		//        only if the Appearance SP is currently opened.
+		LLAgentWearables::editWearable(inv_item);
+	}
+
 	// TODO: camera mode may not be changed if a debug setting is tweaked
 	if( gAgentCamera.cameraCustomizeAvatar() )
 	{
@@ -1240,10 +1249,8 @@ void menu_create_inventory_item(LLFolderView* root, LLFolderBridge *bridge, cons
 		LLWearableType::EType wearable_type = LLWearableType::typeNameToType(type_name);
 		if (wearable_type >= LLWearableType::WT_SHAPE && wearable_type < LLWearableType::WT_COUNT)
 		{
-			LLAssetType::EType asset_type = LLWearableType::getAssetType(wearable_type);
-			LLFolderType::EType folder_type = LLFolderType::assetTypeToFolderType(asset_type);
-			const LLUUID parent_id = bridge ? bridge->getUUID() : gInventory.findCategoryUUIDForType(folder_type);
-			LLFolderBridge::createWearable(parent_id, wearable_type);
+			const LLUUID parent_id = bridge ? bridge->getUUID() : LLUUID::null;
+			LLAgentWearables::createWearable(wearable_type, false, parent_id);
 		}
 		else
 		{
diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp
index cfb48a22bb89ed6382ad6ebe353fe1d60d095e65..78c784e4df0fd92744c6f82678982c30098fca31 100644
--- a/indra/newview/llwearableitemslist.cpp
+++ b/indra/newview/llwearableitemslist.cpp
@@ -41,6 +41,7 @@
 #include "llinventorymodel.h"
 #include "llmenugl.h" // for LLContextMenu
 #include "lltransutil.h"
+#include "llviewerattachmenu.h"
 
 class LLFindOutfitItems : public LLInventoryCollectFunctor
 {
@@ -511,7 +512,9 @@ LLContextMenu* LLWearableItemsList::ContextMenu::createMenu()
 	// Register handlers common for all wearable types.
 	registrar.add("Wearable.Wear", boost::bind(handleMultiple, wear, ids));
 	registrar.add("Wearable.Edit", boost::bind(handleMultiple, LLAgentWearables::editWearable, ids));
+	registrar.add("Wearable.CreateNew", boost::bind(createNewWearable, selected_id));
 	registrar.add("Wearable.ShowOriginal", boost::bind(show_item_original, selected_id));
+	registrar.add("Wearable.TakeOffDetach", boost::bind(handleMultiple, take_off, ids));
 
 	// Register handlers for clothing.
 	registrar.add("Clothing.TakeOff", boost::bind(handleMultiple, take_off, ids));
@@ -521,12 +524,16 @@ LLContextMenu* LLWearableItemsList::ContextMenu::createMenu()
 	// Register handlers for attachments.
 	registrar.add("Attachment.Detach", boost::bind(handleMultiple, take_off, ids));
 	registrar.add("Attachment.Profile", boost::bind(show_item_profile, selected_id));
+	registrar.add("Object.Attach", boost::bind(LLViewerAttachMenu::attachObjects, ids, _2));
 
 	// Create the menu.
 	LLContextMenu* menu = createFromFile("menu_wearable_list_item.xml");
 
 	// Determine which items should be visible/enabled.
 	updateItemsVisibility(menu);
+
+	// Update labels for the items requiring that.
+	updateItemsLabels(menu);
 	return menu;
 }
 
@@ -540,10 +547,10 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu
 
 	const uuid_vec_t& ids = mUUIDs;	// selected items IDs
 	U32 mask = 0;					// mask of selected items' types
-	U32 nitems = ids.size();		// number of selected items
-	U32 nworn = 0;					// number of worn items among the selected ones
-	U32 nwornlinks = 0;				// number of worn links among the selected items
-	U32 neditable = 0;				// number of editable items among the selected ones
+	U32 n_items = ids.size();		// number of selected items
+	U32 n_worn = 0;					// number of worn items among the selected ones
+	U32 n_links = 0;				// number of links among the selected items
+	U32 n_editable = 0;				// number of editable items among the selected ones
 
 	for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it)
 	{
@@ -565,38 +572,82 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu
 
 		if (is_worn)
 		{
-			++nworn;
-
-			if (is_link)
-			{
-				++nwornlinks;
-			}
+			++n_worn;
 		}
 		if (is_editable)
 		{
-			++neditable;
+			++n_editable;
+		}
+		if (is_link)
+		{
+			++n_links;
 		}
 	} // for
 
 	// *TODO: eliminate multiple traversals over the menu items
-	// *TODO: try disabling items rather than hiding them
-	// *FIX:  we may hide *all* items and thus get an ugly empty menu
-	setMenuItemVisible(menu, "wear",			nworn == 0);
-	setMenuItemVisible(menu, "edit",			mask & (MASK_CLOTHING|MASK_BODYPART) && nitems == 1 && neditable == 1);
-	setMenuItemVisible(menu, "show_original",	nitems == 1 && nwornlinks == nitems);
-	setMenuItemVisible(menu, "take_off",		mask == MASK_CLOTHING && nworn == nitems); // selected only worn clothes
-	setMenuItemVisible(menu, "detach",			mask == MASK_ATTACHMENT && nworn == nitems);
-	setMenuItemVisible(menu, "object_profile",	mask == MASK_ATTACHMENT && nitems == 1);
+	setMenuItemVisible(menu, "wear",				n_worn == 0);
+	setMenuItemVisible(menu, "edit",				mask & (MASK_CLOTHING|MASK_BODYPART) && n_items == 1);
+	setMenuItemEnabled(menu, "edit",				n_editable == 1 && n_worn == 1);
+	setMenuItemVisible(menu, "create_new",			mask & (MASK_CLOTHING|MASK_BODYPART) && n_items == 1);
+	setMenuItemEnabled(menu, "show_original",		n_items == 1 && n_links == n_items);
+	setMenuItemVisible(menu, "take_off",			mask == MASK_CLOTHING && n_worn == n_items);
+	setMenuItemVisible(menu, "detach",				mask == MASK_ATTACHMENT && n_worn == n_items);
+	setMenuItemVisible(menu, "take_off_or_detach",	mask == (MASK_ATTACHMENT|MASK_CLOTHING));
+	setMenuItemEnabled(menu, "take_off_or_detach",	n_worn == n_items);
+	setMenuItemVisible(menu, "object_profile",		mask & (MASK_ATTACHMENT|MASK_CLOTHING));
+	setMenuItemEnabled(menu, "object_profile",		n_items == 1);
+
+	// Populate or hide the "Attach to..." / "Attach to HUD..." submenus.
+	if (mask == MASK_ATTACHMENT && n_worn == 0)
+	{
+		LLViewerAttachMenu::populateMenus("wearable_attach_to", "wearable_attach_to_hud");
+	}
+	else
+	{
+		setMenuItemVisible(menu, "wearable_attach_to",			false);
+		setMenuItemVisible(menu, "wearable_attach_to_hud",		false);
+	}
+
+	if (mask & MASK_UNKNOWN)
+	{
+		llwarns << "Non-wearable items passed." << llendl;
+	}
+}
+
+void LLWearableItemsList::ContextMenu::updateItemsLabels(LLContextMenu* menu)
+{
+	llassert(menu);
+	if (!menu) return;
+
+	// Set proper label for the "Create new <WEARABLE_TYPE>" menu item.
+	LLViewerInventoryItem* item = gInventory.getLinkedItem(mUUIDs.back());
+	if (!item || !item->isWearableType()) return;
+
+	LLStringUtil::format_map_t args;
+	LLWearableType::EType w_type = item->getWearableType();
+	args["[WEARABLE_TYPE]"] = LLWearableType::getTypeDefaultNewName(w_type);
+	std::string new_label = LLTrans::getString("CreateNewWearable", args);
+
+	LLMenuItemGL* menu_item = menu->getChild<LLMenuItemGL>("create_new");
+	menu_item->setLabel(new_label);
 }
 
 // We need this method to convert non-zero BOOL values to exactly 1 (TRUE).
 // Otherwise code relying on a BOOL value being TRUE may fail
 // (I experienced a weird assert in LLView::drawChildren() because of that.
+// static
 void LLWearableItemsList::ContextMenu::setMenuItemVisible(LLContextMenu* menu, const std::string& name, bool val)
 {
 	menu->setItemVisible(name, val);
 }
 
+// static
+void LLWearableItemsList::ContextMenu::setMenuItemEnabled(LLContextMenu* menu, const std::string& name, bool val)
+{
+	menu->setItemEnabled(name, val);
+}
+
+// static
 void LLWearableItemsList::ContextMenu::updateMask(U32& mask, LLAssetType::EType at)
 {
 	if (at == LLAssetType::AT_CLOTHING)
@@ -613,8 +664,18 @@ void LLWearableItemsList::ContextMenu::updateMask(U32& mask, LLAssetType::EType
 	}
 	else
 	{
-		llwarns << "Unsupported asset type: " << at << llendl;
+		mask |= MASK_UNKNOWN;
 	}
 }
 
+// static
+void LLWearableItemsList::ContextMenu::createNewWearable(const LLUUID& item_id)
+{
+	// *TODO: proper implementation of creating new wearables.
+	LLViewerInventoryItem* item = gInventory.getLinkedItem(item_id);
+	if (!item || !item->isWearableType()) return;
+
+	LLAgentWearables::createWearable(item->getWearableType(), true);
+}
+
 // EOF
diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h
index 995a8976f39dfd83c404ee21d48522c0d7f4e90a..0ed480a92a29a4961603cffc42a29a40cde486a2 100644
--- a/indra/newview/llwearableitemslist.h
+++ b/indra/newview/llwearableitemslist.h
@@ -315,12 +315,16 @@ class LLWearableItemsList : public LLInventoryItemsList
 			MASK_CLOTHING		= 0x01,
 			MASK_BODYPART		= 0x02,
 			MASK_ATTACHMENT		= 0x04,
+			MASK_UNKNOWN		= 0x08,
 		};
 
 		/* virtual */ LLContextMenu* createMenu();
 		void updateItemsVisibility(LLContextMenu* menu);
-		void setMenuItemVisible(LLContextMenu* menu, const std::string& name, bool val);
-		void updateMask(U32& mask, LLAssetType::EType at);
+		void updateItemsLabels(LLContextMenu* menu);
+		static void setMenuItemVisible(LLContextMenu* menu, const std::string& name, bool val);
+		static void setMenuItemEnabled(LLContextMenu* menu, const std::string& name, bool val);
+		static void updateMask(U32& mask, LLAssetType::EType at);
+		static void createNewWearable(const LLUUID& item_id);
 	};
 
 	struct Params : public LLInitParam::Block<Params, LLInventoryItemsList::Params>
diff --git a/indra/newview/skins/default/xui/en/menu_cof_attachment.xml b/indra/newview/skins/default/xui/en/menu_cof_attachment.xml
index b422d87938bf4f14f6950c2d1e2fe7ea4289bf28..c402100fb1e713cadc8a6b49dc35278aaeb4dbed 100644
--- a/indra/newview/skins/default/xui/en/menu_cof_attachment.xml
+++ b/indra/newview/skins/default/xui/en/menu_cof_attachment.xml
@@ -10,12 +10,4 @@
          function="Attachment.Detach"
          parameter="detach"/>
     </menu_item_call>
-    <context_menu
-     label="Attach to"
-     layout="topleft"
-     name="attach_to" />
-    <context_menu
-     label="Attach to HUD"
-     layout="topleft"
-     name="attach_to_hud" />
 </context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_outfit_gear.xml b/indra/newview/skins/default/xui/en/menu_outfit_gear.xml
index dfc72b557cc75b425159b4cf5fc078db3bb8993e..3cae12e30cdb66747f33b4d20f6bbfb770bb7314 100644
--- a/indra/newview/skins/default/xui/en/menu_outfit_gear.xml
+++ b/indra/newview/skins/default/xui/en/menu_outfit_gear.xml
@@ -7,8 +7,7 @@
      layout="topleft"
      name="wear">
         <on_click
-         function="Gear.OnClick"
-         parameter="wear"/>
+         function="Gear.Wear" />
         <on_enable
          function="Gear.OnEnable"
          parameter="wear" />
@@ -18,20 +17,164 @@
      layout="topleft"
      name="take_off">
         <on_click
-         function="Gear.OnClick"
-         parameter="take_off"/>
+         function="Gear.TakeOff" />
         <on_enable
          function="Gear.OnEnable"
          parameter="take_off" />
     </menu_item_call>
+
+            <menu_item_separator />
+            <!-- copied (with minor modifications) from menu_inventory_add.xml -->
+            <!--  *TODO: generate dynamically? -->
+            <menu
+             height="175"
+             label="New Clothes"
+             layout="topleft"
+             left_delta="0"
+             mouse_opaque="false"
+             name="New Clothes"
+             top_pad="514"
+             width="125">
+                <menu_item_call
+                 label="New Shirt"
+                 layout="topleft"
+                 name="New Shirt">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="shirt" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Pants"
+                 layout="topleft"
+                 name="New Pants">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="pants" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Shoes"
+                 layout="topleft"
+                 name="New Shoes">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="shoes" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Socks"
+                 layout="topleft"
+                 name="New Socks">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="socks" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Jacket"
+                 layout="topleft"
+                 name="New Jacket">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="jacket" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Skirt"
+                 layout="topleft"
+                 name="New Skirt">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="skirt" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Gloves"
+                 layout="topleft"
+                 name="New Gloves">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="gloves" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Undershirt"
+                 layout="topleft"
+                 name="New Undershirt">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="undershirt" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Underpants"
+                 layout="topleft"
+                 name="New Underpants">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="underpants" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Alpha"
+                 layout="topleft"
+                 name="New Alpha">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="alpha" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Tattoo"
+                 layout="topleft"
+                 name="New Tattoo">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="tattoo" />
+                </menu_item_call>
+            </menu>
+            <menu
+             height="85"
+             label="New Body Parts"
+             layout="topleft"
+             left_delta="0"
+             mouse_opaque="false"
+             name="New Body Parts"
+             top_pad="514"
+             width="118">
+                <menu_item_call
+                 label="New Shape"
+                 layout="topleft"
+                 name="New Shape">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="shape" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Skin"
+                 layout="topleft"
+                 name="New Skin">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="skin" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Hair"
+                 layout="topleft"
+                 name="New Hair">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="hair" />
+                </menu_item_call>
+                <menu_item_call
+                 label="New Eyes"
+                 layout="topleft"
+                 name="New Eyes">
+                    <menu_item_call.on_click
+                     function="Gear.Create"
+                     parameter="eyes" />
+                </menu_item_call>
+            </menu>
+            <!-- copied from menu_inventory_add.xml -->
+
     <menu_item_separator />
     <menu_item_call
      label="Rename"
      layout="topleft"
      name="rename">
         <on_click
-         function="Gear.OnClick"
-         parameter="rename"/>
+         function="Gear.Rename" />
         <on_enable
          function="Gear.OnEnable"
          parameter="rename" />
@@ -41,10 +184,9 @@
      layout="topleft"
      name="delete_outfit">
         <on_click
-         function="Gear.OnClick"
-         parameter="delete_outfit"/>
+         function="Gear.Delete" />
         <on_enable
          function="Gear.OnEnable"
-         parameter="delete_outfit" />
+         parameter="delete" />
     </menu_item_call>
 </menu>
diff --git a/indra/newview/skins/default/xui/en/menu_outfit_tab.xml b/indra/newview/skins/default/xui/en/menu_outfit_tab.xml
index 8f3e62157a9c2a19ef8bf207c498ca8722526490..67559638d94e9b081f4261e215b4a1a33b3abf7c 100644
--- a/indra/newview/skins/default/xui/en/menu_outfit_tab.xml
+++ b/indra/newview/skins/default/xui/en/menu_outfit_tab.xml
@@ -8,6 +8,9 @@
      name="wear_replace">
         <on_click
          function="Outfit.WearReplace" />
+        <on_enable
+         function="Outfit.OnEnable"
+         parameter="wear_replace" />
     </menu_item_call>
     <menu_item_call
      label="Wear - Add to Current Outfit"
@@ -15,13 +18,29 @@
      name="wear_add">
         <on_click
          function="Outfit.WearAdd" />
+        <on_enable
+         function="Outfit.OnEnable"
+         parameter="wear_add" />
     </menu_item_call>
     <menu_item_call
-     label="Take Off - Remove Current Outfit"
+     label="Take Off - Remove from Current Outfit"
      layout="topleft"
      name="take_off">
         <on_click
          function="Outfit.TakeOff" />
+        <on_enable
+         function="Outfit.OnEnable"
+         parameter="take_off" />
+    </menu_item_call>
+    <menu_item_call
+     label="Edit Outfit"
+     layout="topleft"
+     name="edit">
+        <on_click
+         function="Outfit.Edit" />
+        <on_enable
+         function="Outfit.OnEnable"
+         parameter="edit" />
     </menu_item_call>
     <menu_item_separator />
     <menu_item_call
@@ -30,6 +49,9 @@
      name="rename">
         <on_click
          function="Outfit.Rename" />
+        <on_enable
+         function="Outfit.OnEnable"
+         parameter="rename" />
     </menu_item_call>
     <menu_item_call
      label="Delete Outfit"
@@ -37,5 +59,8 @@
      name="delete">
         <on_click
          function="Outfit.Delete" />
+        <on_enable
+         function="Outfit.OnEnable"
+         parameter="delete" />
     </menu_item_call>
 </context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_wearable_list_item.xml b/indra/newview/skins/default/xui/en/menu_wearable_list_item.xml
index 7ea7eaade5ace7b4e1d55f00528d64b9d2298c06..46aca54eee922bedd1843ca3308cc9a932f9e6b7 100644
--- a/indra/newview/skins/default/xui/en/menu_wearable_list_item.xml
+++ b/indra/newview/skins/default/xui/en/menu_wearable_list_item.xml
@@ -8,6 +8,13 @@
         <on_click
          function="Wearable.Wear" />
     </menu_item_call>
+    <menu_item_call
+     label="Take Off / Detach"
+     layout="topleft"
+     name="take_off_or_detach">
+        <on_click
+         function="Wearable.TakeOffDetach" />
+    </menu_item_call>
     <menu_item_call
      label="Detach"
      layout="topleft"
@@ -15,16 +22,14 @@
         <on_click
          function="Attachment.Detach" />
     </menu_item_call>
-<!-- *TODO: implement the submenus
-    <menu
-     label="Attach to"
+    <context_menu
+     label="Attach to  â–¶"
      layout="topleft"
-     name="attach_to" />
-    <menu
-     label="Attach to HUD"
+     name="wearable_attach_to" />
+    <context_menu
+     label="Attach to HUD  â–¶"
      layout="topleft"
-     name="attach_to_hud" />
--->
+     name="wearable_attach_to_hud" />
     <menu_item_call
      label="Object Profile"
      layout="topleft"
@@ -37,16 +42,14 @@
      layout="topleft"
      name="take_off">
         <on_click
-         function="Clothing.TakeOff"
-         parameter="take_off"/>
+         function="Clothing.TakeOff" />
     </menu_item_call>
     <menu_item_call
      label="Edit"
      layout="topleft"
      name="edit">
         <on_click
-         function="Wearable.Edit"
-         parameter="edit"/>
+         function="Wearable.Edit" />
     </menu_item_call>
     <menu_item_call
      label="Show Original"
@@ -55,4 +58,11 @@
         <on_click
          function="Wearable.ShowOriginal" />
     </menu_item_call>
+    <menu_item_call
+     label="Create New"
+     layout="topleft"
+     name="create_new">
+        <on_click
+         function="Wearable.CreateNew" />
+    </menu_item_call>
 </context_menu>
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 2590924b58b0d2d1aaea483be7119772135a0515..5ead756d208cd7684db81976c5922cc7db22dcd2 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -2068,6 +2068,28 @@ Would you be my friend?
     </form>
   </notification>
 
+  <notification
+   icon="alertmodal.tga"
+   label="Rename Outfit"
+   name="RenameOutfit"
+   type="alertmodal">
+    New outfit name:
+    <form name="form">
+      <input name="new_name" type="text">
+        [NAME]
+      </input>
+      <button
+       default="true"
+       index="0"
+       name="Offer"
+       text="OK"/>
+      <button
+       index="1"
+       name="Cancel"
+       text="Cancel"/>
+    </form>
+  </notification>
+
   <notification
    icon="alertmodal.tga"
    name="RemoveFromFriends"
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 2d424e7ab2caa564e2cf0ee7ccaef5593b29ee01..9d7079a4955be6151fde33fe1054c99b15f78574 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -1830,6 +1830,7 @@ Clears (deletes) the media and all params from the given face.
 
   <!-- Wearable List-->
   <string name="NewWearable">New [WEARABLE_ITEM]</string>
+  <string name="CreateNewWearable">Create [WEARABLE_TYPE]</string>
   
 	<!-- LLGroupNotify -->
 	<!-- used in the construction of a Group Notice blue dialog box, buttons, tooltip etc. Seems to be no longer utilized by code in Viewer 2.0 -->