/** * @file llwearableitemslist.cpp * @brief A flat list of wearable items. * * $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 "llwearableitemslist.h" #include "lliconctrl.h" #include "llmenugl.h" // for LLContextMenu #include "llagentwearables.h" #include "llappearancemgr.h" #include "llinventoryfunctions.h" #include "lltransutil.h" #include "llviewerattachmenu.h" #include "llvoavatarself.h" class LLFindOutfitItems : public LLInventoryCollectFunctor { public: LLFindOutfitItems() {} virtual ~LLFindOutfitItems() {} virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); }; bool LLFindOutfitItems::operator()(LLInventoryCategory* cat, LLInventoryItem* item) { if(item) { if((item->getType() == LLAssetType::AT_CLOTHING) || (item->getType() == LLAssetType::AT_BODYPART) || (item->getType() == LLAssetType::AT_OBJECT)) { return TRUE; } } return FALSE; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// void LLPanelWearableListItem::onMouseEnter(S32 x, S32 y, MASK mask) { LLPanelInventoryListItemBase::onMouseEnter(x, y, mask); setWidgetsVisible(true); reshapeWidgets(); } void LLPanelWearableListItem::onMouseLeave(S32 x, S32 y, MASK mask) { LLPanelInventoryListItemBase::onMouseLeave(x, y, mask); setWidgetsVisible(false); reshapeWidgets(); } LLPanelWearableListItem::LLPanelWearableListItem(LLViewerInventoryItem* item, const LLPanelWearableListItem::Params& params) : LLPanelInventoryListItemBase(item, params) { } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // static LLPanelWearableOutfitItem* LLPanelWearableOutfitItem::create(LLViewerInventoryItem* item, bool worn_indication_enabled) { LLPanelWearableOutfitItem* list_item = NULL; if (item) { const LLPanelInventoryListItemBase::Params& params = LLUICtrlFactory::getDefaultParams(); list_item = new LLPanelWearableOutfitItem(item, worn_indication_enabled, params); list_item->initFromParams(params); list_item->postBuild(); } return list_item; } LLPanelWearableOutfitItem::LLPanelWearableOutfitItem(LLViewerInventoryItem* item, bool worn_indication_enabled, const LLPanelWearableOutfitItem::Params& params) : LLPanelInventoryListItemBase(item, params) , mWornIndicationEnabled(worn_indication_enabled) { } // virtual void LLPanelWearableOutfitItem::updateItem(const std::string& name, EItemState item_state) { std::string search_label = name; if (mWornIndicationEnabled && get_is_item_worn(mInventoryItemUUID)) { search_label += LLTrans::getString("worn"); item_state = IS_WORN; } LLPanelInventoryListItemBase::updateItem(search_label, item_state); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelClothingListItem(&typeid(LLPanelClothingListItem::Params), "clothing_list_item"); LLPanelClothingListItem::Params::Params() : up_btn("up_btn"), down_btn("down_btn"), edit_btn("edit_btn"), lock_panel("lock_panel"), edit_panel("edit_panel"), lock_icon("lock_icon") {} // static LLPanelClothingListItem* LLPanelClothingListItem::create(LLViewerInventoryItem* item) { LLPanelClothingListItem* list_item = NULL; if(item) { const LLPanelClothingListItem::Params& params = LLUICtrlFactory::getDefaultParams(); list_item = new LLPanelClothingListItem(item, params); list_item->initFromParams(params); list_item->postBuild(); } return list_item; } LLPanelClothingListItem::LLPanelClothingListItem(LLViewerInventoryItem* item, const LLPanelClothingListItem::Params& params) : LLPanelDeletableWearableListItem(item, params) { LLButton::Params button_params = params.up_btn; applyXUILayout(button_params, this); addChild(LLUICtrlFactory::create(button_params)); button_params = params.down_btn; applyXUILayout(button_params, this); addChild(LLUICtrlFactory::create(button_params)); LLPanel::Params panel_params = params.lock_panel; applyXUILayout(panel_params, this); LLPanel* lock_panelp = LLUICtrlFactory::create(panel_params); addChild(lock_panelp); panel_params = params.edit_panel; applyXUILayout(panel_params, this); LLPanel* edit_panelp = LLUICtrlFactory::create(panel_params); addChild(edit_panelp); if (lock_panelp) { LLIconCtrl::Params icon_params = params.lock_icon; applyXUILayout(icon_params, this); lock_panelp->addChild(LLUICtrlFactory::create(icon_params)); } if (edit_panelp) { button_params = params.edit_btn; applyXUILayout(button_params, this); edit_panelp->addChild(LLUICtrlFactory::create(button_params)); } setSeparatorVisible(false); } LLPanelClothingListItem::~LLPanelClothingListItem() { } BOOL LLPanelClothingListItem::postBuild() { LLPanelDeletableWearableListItem::postBuild(); addWidgetToRightSide("btn_move_up"); addWidgetToRightSide("btn_move_down"); addWidgetToRightSide("btn_lock"); addWidgetToRightSide("btn_edit_panel"); setWidgetsVisible(false); reshapeWidgets(); return TRUE; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelBodyPartsListItem(&typeid(LLPanelBodyPartsListItem::Params), "bodyparts_list_item"); LLPanelBodyPartsListItem::Params::Params() : edit_btn("edit_btn"), edit_panel("edit_panel"), lock_panel("lock_panel"), lock_icon("lock_icon") {} // static LLPanelBodyPartsListItem* LLPanelBodyPartsListItem::create(LLViewerInventoryItem* item) { LLPanelBodyPartsListItem* list_item = NULL; if(item) { const Params& params = LLUICtrlFactory::getDefaultParams(); list_item = new LLPanelBodyPartsListItem(item, params); list_item->initFromParams(params); list_item->postBuild(); } return list_item; } LLPanelBodyPartsListItem::LLPanelBodyPartsListItem(LLViewerInventoryItem* item, const LLPanelBodyPartsListItem::Params& params) : LLPanelWearableListItem(item, params) { LLPanel::Params panel_params = params.edit_panel; applyXUILayout(panel_params, this); LLPanel* edit_panelp = LLUICtrlFactory::create(panel_params); addChild(edit_panelp); panel_params = params.lock_panel; applyXUILayout(panel_params, this); LLPanel* lock_panelp = LLUICtrlFactory::create(panel_params); addChild(lock_panelp); if (edit_panelp) { LLButton::Params btn_params = params.edit_btn; applyXUILayout(btn_params, this); edit_panelp->addChild(LLUICtrlFactory::create(btn_params)); } if (lock_panelp) { LLIconCtrl::Params icon_params = params.lock_icon; applyXUILayout(icon_params, this); lock_panelp->addChild(LLUICtrlFactory::create(icon_params)); } setSeparatorVisible(true); } LLPanelBodyPartsListItem::~LLPanelBodyPartsListItem() { } BOOL LLPanelBodyPartsListItem::postBuild() { LLPanelInventoryListItemBase::postBuild(); addWidgetToRightSide("btn_lock"); addWidgetToRightSide("btn_edit_panel"); return TRUE; } static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelDeletableWearableListItem(&typeid(LLPanelDeletableWearableListItem::Params), "deletable_wearable_list_item"); LLPanelDeletableWearableListItem::Params::Params() : delete_btn("delete_btn") {} // static LLPanelDeletableWearableListItem* LLPanelDeletableWearableListItem::create(LLViewerInventoryItem* item) { LLPanelDeletableWearableListItem* list_item = NULL; if(item) { const Params& params = LLUICtrlFactory::getDefaultParams(); list_item = new LLPanelDeletableWearableListItem(item, params); list_item->initFromParams(params); list_item->postBuild(); } return list_item; } LLPanelDeletableWearableListItem::LLPanelDeletableWearableListItem(LLViewerInventoryItem* item, const LLPanelDeletableWearableListItem::Params& params) : LLPanelWearableListItem(item, params) { LLButton::Params button_params = params.delete_btn; applyXUILayout(button_params, this); addChild(LLUICtrlFactory::create(button_params)); setSeparatorVisible(true); } BOOL LLPanelDeletableWearableListItem::postBuild() { LLPanelWearableListItem::postBuild(); addWidgetToLeftSide("btn_delete"); LLButton* delete_btn = getChild("btn_delete"); // Reserve space for 'delete' button event if it is invisible. setLeftWidgetsWidth(delete_btn->getRect().mRight); setWidgetsVisible(false); reshapeWidgets(); return TRUE; } // static LLPanelAttachmentListItem* LLPanelAttachmentListItem::create(LLViewerInventoryItem* item) { LLPanelAttachmentListItem* list_item = NULL; if(item) { const Params& params = LLUICtrlFactory::getDefaultParams(); list_item = new LLPanelAttachmentListItem(item, params); list_item->initFromParams(params); list_item->postBuild(); } return list_item; } void LLPanelAttachmentListItem::updateItem(const std::string& name, EItemState item_state) { std::string title_joint = name; LLViewerInventoryItem* inv_item = getItem(); if (inv_item && isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(inv_item->getLinkedUUID())) { std::string joint = LLTrans::getString(gAgentAvatarp->getAttachedPointName(inv_item->getLinkedUUID())); title_joint = title_joint + " (" + joint + ")"; } LLPanelInventoryListItemBase::updateItem(title_joint, item_state); } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelDummyClothingListItem(&typeid(LLPanelDummyClothingListItem::Params), "dummy_clothing_list_item"); LLPanelDummyClothingListItem::Params::Params() : add_panel("add_panel"), add_btn("add_btn") {} LLPanelDummyClothingListItem* LLPanelDummyClothingListItem::create(LLWearableType::EType w_type) { const Params& params = LLUICtrlFactory::getDefaultParams(); LLPanelDummyClothingListItem* list_item = new LLPanelDummyClothingListItem(w_type, params); list_item->initFromParams(params); list_item->postBuild(); return list_item; } BOOL LLPanelDummyClothingListItem::postBuild() { addWidgetToRightSide("btn_add_panel"); setIconImage(LLInventoryIcon::getIcon(LLAssetType::AT_CLOTHING, LLInventoryType::IT_NONE, mWearableType, FALSE)); updateItem(wearableTypeToString(mWearableType)); // Make it look loke clothing item - reserve space for 'delete' button setLeftWidgetsWidth(getChildView("item_icon")->getRect().mLeft); setWidgetsVisible(false); reshapeWidgets(); return TRUE; } LLWearableType::EType LLPanelDummyClothingListItem::getWearableType() const { return mWearableType; } LLPanelDummyClothingListItem::LLPanelDummyClothingListItem(LLWearableType::EType w_type, const LLPanelDummyClothingListItem::Params& params) : LLPanelWearableListItem(NULL, params), mWearableType(w_type) { LLPanel::Params panel_params(params.add_panel); applyXUILayout(panel_params, this); LLPanel* add_panelp = LLUICtrlFactory::create(panel_params); addChild(add_panelp); if (add_panelp) { LLButton::Params button_params(params.add_btn); applyXUILayout(button_params, this); add_panelp->addChild(LLUICtrlFactory::create(button_params)); } setSeparatorVisible(true); } typedef std::map clothing_to_string_map_t; clothing_to_string_map_t init_clothing_string_map() { clothing_to_string_map_t w_map; w_map.insert(std::make_pair(LLWearableType::WT_SHIRT, "shirt_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_PANTS, "pants_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_SHOES, "shoes_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_SOCKS, "socks_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_JACKET, "jacket_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_GLOVES, "gloves_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_UNDERSHIRT, "undershirt_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_UNDERPANTS, "underpants_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_SKIRT, "skirt_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_ALPHA, "alpha_not_worn")); w_map.insert(std::make_pair(LLWearableType::WT_TATTOO, "tattoo_not_worn")); return w_map; } std::string LLPanelDummyClothingListItem::wearableTypeToString(LLWearableType::EType w_type) { static const clothing_to_string_map_t w_map = init_clothing_string_map(); static const std::string invalid_str = LLTrans::getString("invalid_not_worn"); std::string type_str = invalid_str; clothing_to_string_map_t::const_iterator it = w_map.find(w_type); if(w_map.end() != it) { type_str = LLTrans::getString(it->second); } return type_str; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// LLWearableItemTypeNameComparator::LLWearableTypeOrder::LLWearableTypeOrder(LLWearableItemTypeNameComparator::ETypeListOrder order_priority, bool sort_asset_by_name, bool sort_wearable_by_name): mOrderPriority(order_priority), mSortAssetTypeByName(sort_asset_by_name), mSortWearableTypeByName(sort_wearable_by_name) { } LLWearableItemTypeNameComparator::LLWearableItemTypeNameComparator() { // By default the sort order conforms the order by spec of MY OUTFITS items list: // 1. CLOTHING - sorted by name // 2. OBJECT - sorted by type // 3. BODYPART - sorted by name mWearableOrder[LLAssetType::AT_CLOTHING] = LLWearableTypeOrder(ORDER_RANK_1, false, false); mWearableOrder[LLAssetType::AT_OBJECT] = LLWearableTypeOrder(ORDER_RANK_2, true, true); mWearableOrder[LLAssetType::AT_BODYPART] = LLWearableTypeOrder(ORDER_RANK_3, false, true); } void LLWearableItemTypeNameComparator::setOrder(LLAssetType::EType items_of_type, LLWearableItemTypeNameComparator::ETypeListOrder order_priority, bool sort_asset_items_by_name, bool sort_wearable_items_by_name) { mWearableOrder[items_of_type] = LLWearableTypeOrder(order_priority, sort_asset_items_by_name, sort_wearable_items_by_name); } /*virtual*/ bool LLWearableItemNameComparator::doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const { std::string name1 = wearable_item1->getItemName(); std::string name2 = wearable_item2->getItemName(); LLStringUtil::toUpper(name1); LLStringUtil::toUpper(name2); return name1 < name2; } /*virtual*/ bool LLWearableItemTypeNameComparator::doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const { const LLAssetType::EType item_type1 = wearable_item1->getType(); const LLAssetType::EType item_type2 = wearable_item2->getType(); LLWearableItemTypeNameComparator::ETypeListOrder item_type_order1 = getTypeListOrder(item_type1); LLWearableItemTypeNameComparator::ETypeListOrder item_type_order2 = getTypeListOrder(item_type2); if (item_type_order1 != item_type_order2) { // If items are of different asset types we can compare them // by types order in the list. return item_type_order1 < item_type_order2; } if (sortAssetTypeByName(item_type1)) { // If both items are of the same asset type except AT_CLOTHING and AT_BODYPART // we can compare them by name. return LLWearableItemNameComparator::doCompare(wearable_item1, wearable_item2); } const LLWearableType::EType item_wearable_type1 = wearable_item1->getWearableType(); const LLWearableType::EType item_wearable_type2 = wearable_item2->getWearableType(); if (item_wearable_type1 != item_wearable_type2) // If items are of different LLWearableType::EType types they are compared // by LLWearableType::EType. types order determined in LLWearableType::EType. { // If items are of different LLWearableType::EType types they are compared // by LLWearableType::EType. types order determined in LLWearableType::EType. return item_wearable_type1 < item_wearable_type2; } else { // If both items are of the same clothing type they are compared // by description and place in reverse order (i.e. outer layer item // on top) OR by name if(sortWearableTypeByName(item_type1)) { return LLWearableItemNameComparator::doCompare(wearable_item1, wearable_item2); } return wearable_item1->getDescription() > wearable_item2->getDescription(); } } LLWearableItemTypeNameComparator::ETypeListOrder LLWearableItemTypeNameComparator::getTypeListOrder(LLAssetType::EType item_type) const { wearable_type_order_map_t::const_iterator const_it = mWearableOrder.find(item_type); if(const_it == mWearableOrder.end()) { llwarns<<"Absent information about order rang of items of "<second.mOrderPriority; } bool LLWearableItemTypeNameComparator::sortAssetTypeByName(LLAssetType::EType item_type) const { wearable_type_order_map_t::const_iterator const_it = mWearableOrder.find(item_type); if(const_it == mWearableOrder.end()) { llwarns<<"Absent information about sorting items of "<second.mSortAssetTypeByName; } bool LLWearableItemTypeNameComparator::sortWearableTypeByName(LLAssetType::EType item_type) const { wearable_type_order_map_t::const_iterator const_it = mWearableOrder.find(item_type); if(const_it == mWearableOrder.end()) { llwarns<<"Absent information about sorting items of "<second.mSortWearableTypeByName; } /*virtual*/ bool LLWearableItemCreationDateComparator::doCompare(const LLPanelInventoryListItemBase* item1, const LLPanelInventoryListItemBase* item2) const { time_t date1 = item1->getCreationDate(); time_t date2 = item2->getCreationDate(); if (date1 == date2) { return LLWearableItemNameComparator::doCompare(item1, item2); } return date1 > date2; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// static LLWearableItemTypeNameComparator WEARABLE_TYPE_NAME_COMPARATOR; static const LLWearableItemTypeNameComparator WEARABLE_TYPE_LAYER_COMPARATOR; static const LLWearableItemNameComparator WEARABLE_NAME_COMPARATOR; static const LLWearableItemCreationDateComparator WEARABLE_CREATION_DATE_COMPARATOR; static const LLDefaultChildRegistry::Register r("wearable_items_list"); LLWearableItemsList::Params::Params() : standalone("standalone", true) , worn_indication_enabled("worn_indication_enabled", true) {} LLWearableItemsList::LLWearableItemsList(const LLWearableItemsList::Params& p) : LLInventoryItemsList(p) { setSortOrder(E_SORT_BY_TYPE_LAYER, false); mIsStandalone = p.standalone; if (mIsStandalone) { // Use built-in context menu. setRightMouseDownCallback(boost::bind(&LLWearableItemsList::onRightClick, this, _2, _3)); } mWornIndicationEnabled = p.worn_indication_enabled; } // virtual LLWearableItemsList::~LLWearableItemsList() {} // virtual void LLWearableItemsList::addNewItem(LLViewerInventoryItem* item, bool rearrange /*= true*/) { if (!item) { llwarns << "No inventory item. Couldn't create flat list item." << llendl; llassert(item != NULL); } LLPanelWearableOutfitItem *list_item = LLPanelWearableOutfitItem::create(item, mWornIndicationEnabled); if (!list_item) return; bool is_item_added = addItem(list_item, item->getUUID(), ADD_BOTTOM, rearrange); if (!is_item_added) { llwarns << "Couldn't add flat list item." << llendl; llassert(is_item_added); } } void LLWearableItemsList::updateList(const LLUUID& category_id) { LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; LLFindOutfitItems collector = LLFindOutfitItems(); // collectDescendentsIf takes non-const reference: gInventory.collectDescendentsIf( category_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector); refreshList(item_array); } void LLWearableItemsList::updateChangedItems(const LLInventoryModel::changed_items_t& changed_items_uuids) { // nothing to update if (changed_items_uuids.empty()) return; typedef std::vector item_panel_list_t; item_panel_list_t items; getItems(items); for (item_panel_list_t::iterator items_iter = items.begin(); items_iter != items.end(); ++items_iter) { LLPanelInventoryListItemBase* item = dynamic_cast(*items_iter); if (!item) continue; LLViewerInventoryItem* inv_item = item->getItem(); if (!inv_item) continue; LLUUID linked_uuid = inv_item->getLinkedUUID(); for (LLInventoryModel::changed_items_t::const_iterator iter = changed_items_uuids.begin(); iter != changed_items_uuids.end(); ++iter) { if (linked_uuid == *iter) { item->setNeedsRefresh(true); break; } } } } void LLWearableItemsList::onRightClick(S32 x, S32 y) { uuid_vec_t selected_uuids; getSelectedUUIDs(selected_uuids); if (selected_uuids.empty()) { return; } ContextMenu::instance().show(this, selected_uuids, x, y); } void LLWearableItemsList::setSortOrder(ESortOrder sort_order, bool sort_now) { switch (sort_order) { case E_SORT_BY_MOST_RECENT: setComparator(&WEARABLE_CREATION_DATE_COMPARATOR); break; case E_SORT_BY_NAME: setComparator(&WEARABLE_NAME_COMPARATOR); break; case E_SORT_BY_TYPE_LAYER: setComparator(&WEARABLE_TYPE_LAYER_COMPARATOR); break; case E_SORT_BY_TYPE_NAME: { WEARABLE_TYPE_NAME_COMPARATOR.setOrder(LLAssetType::AT_CLOTHING, LLWearableItemTypeNameComparator::ORDER_RANK_1, false, true); setComparator(&WEARABLE_TYPE_NAME_COMPARATOR); break; } // No "default:" to raise compiler warning // if we're not handling something } mSortOrder = sort_order; if (sort_now) { sort(); } } ////////////////////////////////////////////////////////////////////////// /// ContextMenu ////////////////////////////////////////////////////////////////////////// LLWearableItemsList::ContextMenu::ContextMenu() : mParent(NULL) { } void LLWearableItemsList::ContextMenu::show(LLView* spawning_view, const uuid_vec_t& uuids, S32 x, S32 y) { mParent = dynamic_cast(spawning_view); LLListContextMenu::show(spawning_view, uuids, x, y); mParent = NULL; // to avoid dereferencing an invalid pointer } // virtual LLContextMenu* LLWearableItemsList::ContextMenu::createMenu() { LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; const uuid_vec_t& ids = mUUIDs; // selected items IDs LLUUID selected_id = ids.front(); // ID of the first selected item functor_t take_off = boost::bind(&LLAppearanceMgr::removeItemFromAvatar, LLAppearanceMgr::getInstance(), _1); // Register handlers common for all wearable types. registrar.add("Wearable.Wear", boost::bind(wear_multiple, ids, true)); registrar.add("Wearable.Add", boost::bind(wear_multiple, ids, false)); 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)); // Register handlers for body parts. // 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; } void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu) { if (!menu) { llwarns << "Invalid menu" << llendl; return; } const uuid_vec_t& ids = mUUIDs; // selected items IDs U32 mask = 0; // mask of selected items' types U32 n_items = ids.size(); // number of selected items U32 n_worn = 0; // number of worn items among the selected ones U32 n_already_worn = 0; // number of items worn of same type as selected items U32 n_links = 0; // number of links among the selected items U32 n_editable = 0; // number of editable items among the selected ones bool can_be_worn = true; for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) { LLUUID id = *it; LLViewerInventoryItem* item = gInventory.getItem(id); if (!item) { llwarns << "Invalid item" << llendl; // *NOTE: the logic below may not work in this case continue; } updateMask(mask, item->getType()); const LLWearableType::EType wearable_type = item->getWearableType(); const bool is_link = item->getIsLinkType(); const bool is_worn = get_is_item_worn(id); const bool is_editable = gAgentWearables.isWearableModifiable(id); const bool is_already_worn = gAgentWearables.selfHasWearable(wearable_type); if (is_worn) { ++n_worn; } if (is_editable) { ++n_editable; } if (is_link) { ++n_links; } if (is_already_worn) { ++n_already_worn; } if (can_be_worn) { can_be_worn = get_can_item_be_worn(item->getLinkedUUID()); } } // for bool standalone = mParent ? mParent->isStandalone() : false; bool wear_add_visible = mask & (MASK_CLOTHING|MASK_ATTACHMENT) && n_worn == 0 && can_be_worn && (n_already_worn != 0 || mask & MASK_ATTACHMENT); // *TODO: eliminate multiple traversals over the menu items setMenuItemVisible(menu, "wear_wear", n_already_worn == 0 && n_worn == 0 && can_be_worn); setMenuItemEnabled(menu, "wear_wear", n_already_worn == 0 && n_worn == 0); setMenuItemVisible(menu, "wear_add", wear_add_visible); setMenuItemEnabled(menu, "wear_add", canAddWearables(ids)); setMenuItemVisible(menu, "wear_replace", n_worn == 0 && n_already_worn != 0 && can_be_worn); //visible only when one item selected and this item is worn setMenuItemVisible(menu, "edit", !standalone && mask & (MASK_CLOTHING|MASK_BODYPART) && n_worn == n_items && n_worn == 1); setMenuItemEnabled(menu, "edit", n_editable == 1 && n_worn == 1 && n_items == 1); setMenuItemVisible(menu, "create_new", mask & (MASK_CLOTHING|MASK_BODYPART) && n_items == 1); setMenuItemVisible(menu, "show_original", !standalone); 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", !standalone); setMenuItemEnabled(menu, "object_profile", n_items == 1); setMenuItemVisible(menu, "--no options--", FALSE); setMenuItemEnabled(menu, "--no options--", FALSE); // 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; } U32 num_visible_items = 0; for (U32 menu_item_index = 0; menu_item_index < menu->getItemCount(); ++menu_item_index) { const LLMenuItemGL* menu_item = menu->getItem(menu_item_index); if (menu_item && menu_item->getVisible()) { num_visible_items++; } } if (num_visible_items == 0) { setMenuItemVisible(menu, "--no options--", TRUE); } } void LLWearableItemsList::ContextMenu::updateItemsLabels(LLContextMenu* menu) { llassert(menu); if (!menu) return; // Set proper label for the "Create new " menu item. LLViewerInventoryItem* item = gInventory.getLinkedItem(mUUIDs.back()); if (!item || !item->isWearableType()) return; LLWearableType::EType w_type = item->getWearableType(); std::string new_label = LLTrans::getString("create_new_" + LLWearableType::getTypeName(w_type)); LLMenuItemGL* menu_item = menu->getChild("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) { mask |= MASK_CLOTHING; } else if (at == LLAssetType::AT_BODYPART) { mask |= MASK_BODYPART; } else if (at == LLAssetType::AT_OBJECT) { mask |= MASK_ATTACHMENT; } else { mask |= MASK_UNKNOWN; } } // static void LLWearableItemsList::ContextMenu::createNewWearable(const LLUUID& item_id) { LLViewerInventoryItem* item = gInventory.getLinkedItem(item_id); if (!item || !item->isWearableType()) return; LLAgentWearables::createWearable(item->getWearableType(), true); } // Returns true if all the given objects and clothes can be added. // static bool LLWearableItemsList::ContextMenu::canAddWearables(const uuid_vec_t& item_ids) { // TODO: investigate wearables may not be loaded at this point EXT-8231 U32 n_objects = 0; boost::unordered_map clothes_by_type; // Count given clothes (by wearable type) and objects. for (uuid_vec_t::const_iterator it = item_ids.begin(); it != item_ids.end(); ++it) { LLViewerInventoryItem* item = gInventory.getItem(*it); if (!item) { return false; } if (item->getType() == LLAssetType::AT_OBJECT) { ++n_objects; } else if (item->getType() == LLAssetType::AT_CLOTHING) { ++clothes_by_type[item->getWearableType()]; } else { llwarns << "Unexpected wearable type" << llendl; return false; } } // Check whether we can add all the objects. if (!isAgentAvatarValid() || !gAgentAvatarp->canAttachMoreObjects(n_objects)) { return false; } // Check whether we can add all the clothes. boost::unordered_map::const_iterator m_it; for (m_it = clothes_by_type.begin(); m_it != clothes_by_type.end(); ++m_it) { LLWearableType::EType w_type = m_it->first; U32 n_clothes = m_it->second; U32 wearable_count = gAgentWearables.getWearableCount(w_type); if ((wearable_count + n_clothes) > LLAgentWearables::MAX_CLOTHING_PER_TYPE) { return false; } } return true; } // EOF