diff options
Diffstat (limited to 'indra/newview/llappearancemgr.cpp')
-rw-r--r-- | indra/newview/llappearancemgr.cpp | 793 |
1 files changed, 793 insertions, 0 deletions
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp new file mode 100644 index 0000000000..6c234f23fe --- /dev/null +++ b/indra/newview/llappearancemgr.cpp @@ -0,0 +1,793 @@ +/** + * @file llappearancemgr.cpp + * @brief Manager for initiating appearance changes on the viewer + * + * $LicenseInfo:firstyear=2004&license=viewergpl$ + * + * Copyright (c) 2004-2009, 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 "llappearancemgr.h" +#include "llinventorymodel.h" +#include "llnotifications.h" +#include "llgesturemgr.h" +#include "llinventorybridge.h" +#include "llwearablelist.h" +#include "llagentwearables.h" +#include "llagent.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "llviewerregion.h" +#include "llfloatercustomize.h" + +class LLWearInventoryCategoryCallback : public LLInventoryCallback +{ +public: + LLWearInventoryCategoryCallback(const LLUUID& cat_id, bool append) + { + mCatID = cat_id; + mAppend = append; + } + void fire(const LLUUID& item_id) + { + /* + * Do nothing. We only care about the destructor + * + * The reason for this is that this callback is used in a hack where the + * same callback is given to dozens of items, and the destructor is called + * after the last item has fired the event and dereferenced it -- if all + * the events actually fire! + */ + } + +protected: + ~LLWearInventoryCategoryCallback() + { + // Is the destructor called by ordinary dereference, or because the app's shutting down? + // If the inventory callback manager goes away, we're shutting down, no longer want the callback. + if( LLInventoryCallbackManager::is_instantiated() ) + { + LLAppearanceManager::wearInventoryCategoryOnAvatar(gInventory.getCategory(mCatID), mAppend); + } + else + { + llwarns << "Dropping unhandled LLWearInventoryCategoryCallback" << llendl; + } + } + +private: + LLUUID mCatID; + bool mAppend; +}; + +class LLOutfitObserver : public LLInventoryFetchObserver +{ +public: + LLOutfitObserver(const LLUUID& cat_id, bool copy_items, bool append) : + mCatID(cat_id), + mCopyItems(copy_items), + mAppend(append) + {} + ~LLOutfitObserver() {} + virtual void done(); //public + +protected: + LLUUID mCatID; + bool mCopyItems; + bool mAppend; +}; + +void LLOutfitObserver::done() +{ + // We now have an outfit ready to be copied to agent inventory. Do + // it, and wear that outfit normally. + if(mCopyItems) + { + LLInventoryCategory* cat = gInventory.getCategory(mCatID); + std::string name; + if(!cat) + { + // should never happen. + name = "New Outfit"; + } + else + { + name = cat->getName(); + } + LLViewerInventoryItem* item = NULL; + item_ref_t::iterator it = mComplete.begin(); + item_ref_t::iterator end = mComplete.end(); + LLUUID pid; + for(; it < end; ++it) + { + item = (LLViewerInventoryItem*)gInventory.getItem(*it); + if(item) + { + if(LLInventoryType::IT_GESTURE == item->getInventoryType()) + { + pid = gInventory.findCategoryUUIDForType(LLAssetType::AT_GESTURE); + } + else + { + pid = gInventory.findCategoryUUIDForType(LLAssetType::AT_CLOTHING); + } + break; + } + } + if(pid.isNull()) + { + pid = gInventory.getRootFolderID(); + } + + LLUUID cat_id = gInventory.createNewCategory( + pid, + LLAssetType::AT_NONE, + name); + mCatID = cat_id; + LLPointer<LLInventoryCallback> cb = new LLWearInventoryCategoryCallback(mCatID, mAppend); + it = mComplete.begin(); + for(; it < end; ++it) + { + item = (LLViewerInventoryItem*)gInventory.getItem(*it); + if(item) + { + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + cat_id, + std::string(), + cb); + } + } + } + else + { + // Wear the inventory category. + LLAppearanceManager::wearInventoryCategoryOnAvatar(gInventory.getCategory(mCatID), mAppend); + } +} + +class LLOutfitFetch : public LLInventoryFetchDescendentsObserver +{ +public: + LLOutfitFetch(bool copy_items, bool append) : mCopyItems(copy_items), mAppend(append) {} + ~LLOutfitFetch() {} + virtual void done(); +protected: + bool mCopyItems; + bool mAppend; +}; + +void LLOutfitFetch::done() +{ + // What we do here is get the complete information on the items in + // the library, and set up an observer that will wait for that to + // happen. + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(mCompleteFolders.front(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + S32 count = item_array.count(); + if(!count) + { + llwarns << "Nothing fetched in category " << mCompleteFolders.front() + << llendl; + //dec_busy_count(); + gInventory.removeObserver(this); + delete this; + return; + } + + LLOutfitObserver* outfit_observer = new LLOutfitObserver(mCompleteFolders.front(), mCopyItems, mAppend); + LLInventoryFetchObserver::item_ref_t ids; + for(S32 i = 0; i < count; ++i) + { + ids.push_back(item_array.get(i)->getUUID()); + } + + // clean up, and remove this as an observer since the call to the + // outfit could notify observers and throw us into an infinite + // loop. + //dec_busy_count(); + gInventory.removeObserver(this); + delete this; + + // increment busy count and either tell the inventory to check & + // call done, or add this object to the inventory for observation. + //inc_busy_count(); + + // do the fetch + outfit_observer->fetchItems(ids); + if(outfit_observer->isEverythingComplete()) + { + // everything is already here - call done. + outfit_observer->done(); + } + else + { + // it's all on it's way - add an observer, and the inventory + // will call done for us when everything is here. + gInventory.addObserver(outfit_observer); + } +} + +class LLUpdateAppearanceOnCount: public LLInventoryCallback +{ +public: + LLUpdateAppearanceOnCount(S32 count): + mCount(count) + { + } + + virtual ~LLUpdateAppearanceOnCount() + { + } + + /* virtual */ void fire(const LLUUID& inv_item) + { + mCount--; + if (mCount==0) + { + done(); + } + } + + void done() + { + LLAppearanceManager::updateAppearanceFromCOF(); + } +private: + S32 mCount; +}; + +struct LLFoundData +{ + LLFoundData(const LLUUID& item_id, + const LLUUID& asset_id, + const std::string& name, + LLAssetType::EType asset_type) : + mItemID(item_id), + mAssetID(asset_id), + mName(name), + mAssetType(asset_type), + mWearable( NULL ) {} + + LLUUID mItemID; + LLUUID mAssetID; + std::string mName; + LLAssetType::EType mAssetType; + LLWearable* mWearable; +}; + + +struct LLWearableHoldingPattern +{ + LLWearableHoldingPattern() : mResolved(0) {} + ~LLWearableHoldingPattern() + { + for_each(mFoundList.begin(), mFoundList.end(), DeletePointer()); + mFoundList.clear(); + } + typedef std::list<LLFoundData*> found_list_t; + found_list_t mFoundList; + S32 mResolved; + bool append; +}; + + +void removeDuplicateItems(LLInventoryModel::item_array_t& dst, const LLInventoryModel::item_array_t& src) +{ + LLInventoryModel::item_array_t new_dst; + std::set<LLUUID> mark_inventory; + std::set<LLUUID> mark_asset; + + S32 inventory_dups = 0; + S32 asset_dups = 0; + + for (LLInventoryModel::item_array_t::const_iterator src_pos = src.begin(); + src_pos != src.end(); + ++src_pos) + { + LLUUID src_item_id = (*src_pos)->getLinkedUUID(); + mark_inventory.insert(src_item_id); + LLUUID src_asset_id = (*src_pos)->getAssetUUID(); + mark_asset.insert(src_asset_id); + } + + for (LLInventoryModel::item_array_t::const_iterator dst_pos = dst.begin(); + dst_pos != dst.end(); + ++dst_pos) + { + LLUUID dst_item_id = (*dst_pos)->getLinkedUUID(); + + if (mark_inventory.find(dst_item_id) == mark_inventory.end()) + { + } + else + { + inventory_dups++; + } + + LLUUID dst_asset_id = (*dst_pos)->getAssetUUID(); + + if (mark_asset.find(dst_asset_id) == mark_asset.end()) + { + // Item is not already present in COF. + new_dst.put(*dst_pos); + mark_asset.insert(dst_item_id); + } + else + { + asset_dups++; + } + } + llinfos << "removeDups, original " << dst.count() << " final " << new_dst.count() + << " inventory dups " << inventory_dups << " asset_dups " << asset_dups << llendl; + + dst = new_dst; +} + + +/* static */ LLUUID LLAppearanceManager::getCOF() +{ + return gInventory.findCategoryUUIDForType(LLAssetType::AT_CURRENT_OUTFIT); +} + +// Update appearance from outfit folder. +/* static */ void LLAppearanceManager::changeOutfit(bool proceed, const LLUUID& category, bool append, bool follow_folder_links) +{ + if (!proceed) + return; + + updateCOFFromOutfit(category, append, follow_folder_links); +} + +// Update COF contents from outfit folder. +/* static */ void LLAppearanceManager::updateCOFFromOutfit(const LLUUID& category, bool append, bool follow_folder_links) +{ + // 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; + 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; + if( !wear_items.count() && !obj_items.count() && !gest_items.count()) + { + LLNotifications::instance().add("CouldNotPutOnOutfit"); + return; + } + + const LLUUID ¤t_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 (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) + { + if (!append) + { + // 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()); + } + } + } + + // 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); + + // Link all gestures in this folder + if (gest_items.count() > 0) + { + llinfos << "Linking " << gest_items.count() << " gestures" << llendl; + for (S32 i = 0; i < gest_items.count(); ++i) + { + const LLInventoryItem* gest_item = gest_items.get(i).get(); + link_inventory_item(gAgent.getID(), gest_item->getLinkedUUID(), current_outfit_id, + gest_item->getName(), + LLAssetType::AT_LINK, link_waiter); + } + } + + // Link all wearables + if(wear_items.count() > 0) + { + llinfos << "Linking " << wear_items.count() << " wearables" << llendl; + for(S32 i = 0; i < wear_items.count(); ++i) + { + // Populate the current outfit folder with links to the newly added wearables + const LLInventoryItem* wear_item = wear_items.get(i).get(); + link_inventory_item(gAgent.getID(), + wear_item->getLinkedUUID(), // If this item is a link, then we'll use the linked item's UUID. + current_outfit_id, + wear_item->getName(), + LLAssetType::AT_LINK, + link_waiter); + } + } + + // Link all attachments. + if( obj_items.count() > 0 ) + { + llinfos << "Linking " << obj_items.count() << " attachments" << llendl; + LLVOAvatar* avatar = gAgent.getAvatarObject(); + if( avatar ) + { + for(S32 i = 0; i < obj_items.count(); ++i) + { + const LLInventoryItem* obj_item = obj_items.get(i).get(); + link_inventory_item(gAgent.getID(), + obj_item->getLinkedUUID(), // If this item is a link, then we'll use the linked item's UUID. + current_outfit_id, + obj_item->getName(), + LLAssetType::AT_LINK, link_waiter); + } + } + } + + // In the particular case that we're switching to a different outfit, + // create a link to the folder that we wore. + LLViewerInventoryCategory* catp = gInventory.getCategory(category); + if (!append && catp && catp->getPreferredType() == LLAssetType::AT_OUTFIT) + { + link_inventory_item(gAgent.getID(), category, current_outfit_id, catp->getName(), + LLAssetType::AT_LINK_FOLDER, LLPointer<LLInventoryCallback>(NULL)); + } +} + +/* static */ +void LLAppearanceManager::onWearableAssetFetch(LLWearable* wearable, void* data) +{ + LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data; + bool append = holder->append; + + if(wearable) + { + for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->mFoundList.begin(); + iter != holder->mFoundList.end(); ++iter) + { + LLFoundData* data = *iter; + if(wearable->getAssetID() == data->mAssetID) + { + data->mWearable = wearable; + break; + } + } + } + holder->mResolved += 1; + if(holder->mResolved >= (S32)holder->mFoundList.size()) + { + LLAppearanceManager::updateAgentWearables(holder, append); + } +} + +/* static */ +void LLAppearanceManager::updateAgentWearables(LLWearableHoldingPattern* holder, bool append) +{ + lldebugs << "updateAgentWearables()" << llendl; + LLInventoryItem::item_array_t items; + LLDynamicArray< LLWearable* > wearables; + + // For each wearable type, find the first instance in the category + // that we recursed through. + for( S32 i = 0; i < WT_COUNT; i++ ) + { + for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->mFoundList.begin(); + iter != holder->mFoundList.end(); ++iter) + { + LLFoundData* data = *iter; + LLWearable* wearable = data->mWearable; + if( wearable && ((S32)wearable->getType() == i) ) + { + LLViewerInventoryItem* item; + item = (LLViewerInventoryItem*)gInventory.getItem(data->mItemID); + if( item && (item->getAssetUUID() == wearable->getAssetID()) ) + { + items.put(item); + wearables.put(wearable); + } + break; + } + } + } + + if(wearables.count() > 0) + { + gAgentWearables.setWearableOutfit(items, wearables, !append); + gInventory.notifyObservers(); + } + + delete holder; + +// dec_busy_count(); +} + +/* static */ void LLAppearanceManager::updateAppearanceFromCOF() +{ + bool follow_folder_links = true; + LLUUID current_outfit_id = getCOF(); + + // Find all the wearables that are in the COF's subtree. + lldebugs << "LLAppearanceManager::updateFromCOF()" << llendl; + LLInventoryModel::item_array_t wear_items; + LLInventoryModel::item_array_t obj_items; + LLInventoryModel::item_array_t gest_items; + getUserDescendents(current_outfit_id, wear_items, obj_items, gest_items, follow_folder_links); + + if( !wear_items.count() && !obj_items.count() && !gest_items.count()) + { + LLNotifications::instance().add("CouldNotPutOnOutfit"); + return; + } + + // Processes that take time should show the busy cursor + //inc_busy_count(); // BAP this is currently a no-op in llinventorybridge.cpp - do we need it? + + // Activate all gestures in this folder + if (gest_items.count() > 0) + { + llinfos << "Activating " << gest_items.count() << " gestures" << llendl; + + LLGestureManager::instance().activateGestures(gest_items); + + // Update the inventory item labels to reflect the fact + // they are active. + LLViewerInventoryCategory* catp = gInventory.getCategory(current_outfit_id); + if (catp) + { + gInventory.updateCategory(catp); + gInventory.notifyObservers(); + } + } + + if(wear_items.count() > 0) + { + // Note: can't do normal iteration, because if all the + // wearables can be resolved immediately, then the + // callback will be called (and this object deleted) + // before the final getNextData(). + LLWearableHoldingPattern* holder = new LLWearableHoldingPattern; + LLFoundData* found; + LLDynamicArray<LLFoundData*> found_container; + for(S32 i = 0; i < wear_items.count(); ++i) + { + found = new LLFoundData(wear_items.get(i)->getUUID(), + wear_items.get(i)->getAssetUUID(), + wear_items.get(i)->getName(), + wear_items.get(i)->getType()); + holder->mFoundList.push_front(found); + found_container.put(found); + } + for(S32 i = 0; i < wear_items.count(); ++i) + { + holder->append = false; + found = found_container.get(i); + + // Fetch the wearables about to be worn. + LLWearableList::instance().getAsset(found->mAssetID, + found->mName, + found->mAssetType, + LLAppearanceManager::onWearableAssetFetch, + (void*)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); + } + + if( obj_items.count() > 0 ) + { + // We've found some attachments. Add these. + LLVOAvatar* avatar = gAgent.getAvatarObject(); + if( avatar ) + { + LLAgentWearables::userAttachMultipleAttachments(obj_items); + } + } +} + +/* static */ void LLAppearanceManager::getUserDescendents(const LLUUID& category, + LLInventoryModel::item_array_t& wear_items, + LLInventoryModel::item_array_t& obj_items, + LLInventoryModel::item_array_t& gest_items, + bool follow_folder_links) +{ + LLInventoryModel::cat_array_t wear_cats; + LLFindWearables is_wearable; + gInventory.collectDescendentsIf(category, + wear_cats, + wear_items, + LLInventoryModel::EXCLUDE_TRASH, + is_wearable, + follow_folder_links); + + LLInventoryModel::cat_array_t obj_cats; + LLIsType is_object( LLAssetType::AT_OBJECT ); + gInventory.collectDescendentsIf(category, + obj_cats, + obj_items, + LLInventoryModel::EXCLUDE_TRASH, + is_object, + follow_folder_links); + + // Find all gestures in this folder + LLInventoryModel::cat_array_t gest_cats; + LLIsType is_gesture( LLAssetType::AT_GESTURE ); + gInventory.collectDescendentsIf(category, + gest_cats, + gest_items, + LLInventoryModel::EXCLUDE_TRASH, + is_gesture, + follow_folder_links); +} + +void LLAppearanceManager::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append) +{ + if(!category) return; + + lldebugs << "wearInventoryCategory( " << category->getName() + << " )" << llendl; + // What we do here is get the complete information on the items in + // the inventory, and set up an observer that will wait for that to + // happen. + LLOutfitFetch* outfit_fetcher = new LLOutfitFetch(copy, append); + LLInventoryFetchDescendentsObserver::folder_ref_t folders; + folders.push_back(category->getUUID()); + outfit_fetcher->fetchDescendents(folders); + //inc_busy_count(); + if(outfit_fetcher->isEverythingComplete()) + { + // everything is already here - call done. + outfit_fetcher->done(); + } + else + { + // it's all on it's way - add an observer, and the inventory + // will call done for us when everything is here. + gInventory.addObserver(outfit_fetcher); + } +} + +// *NOTE: hack to get from avatar inventory to avatar +/* static */ +void LLAppearanceManager::wearInventoryCategoryOnAvatar( LLInventoryCategory* category, bool append ) +{ + // Avoid unintentionally overwriting old wearables. We have to do + // this up front to avoid having to deal with the case of multiple + // wearables being dirty. + if(!category) return; + 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)); + } + else + { + LLAppearanceManager::changeOutfit(TRUE, category->getUUID(), append, follow_folder_links ); + } +} + +/* static */ +void LLAppearanceManager::wearOutfitByName(const std::string& name) +{ + llinfos << "Wearing category " << name << llendl; + //inc_busy_count(); + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + LLNameCategoryCollector has_name(name); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + bool copy_items = false; + LLInventoryCategory* cat = NULL; + if (cat_array.count() > 0) + { + // Just wear the first one that matches + cat = cat_array.get(0); + } + else + { + gInventory.collectDescendentsIf(LLUUID::null, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + if(cat_array.count() > 0) + { + cat = cat_array.get(0); + copy_items = true; + } + } + + if(cat) + { + LLAppearanceManager::wearInventoryCategory(cat, copy_items, false); + } + else + { + llwarns << "Couldn't find outfit " <<name<< " in wearOutfitByName()" + << llendl; + } + + //dec_busy_count(); +} + +void LLAppearanceManager::wearItem( LLInventoryItem* item, bool do_update ) +{ + // BAP add check for already in COF. + LLPointer<LLInventoryCallback> cb = do_update ? new ModifiedCOFCallback : 0; + link_inventory_item( gAgent.getID(), + item->getLinkedUUID(), + getCOF(), + item->getName(), + LLAssetType::AT_LINK, + cb); +} + +void LLAppearanceManager::wearEnsemble( LLInventoryCategory* cat, bool do_update ) +{ + // BAP add check for already in COF. + LLPointer<LLInventoryCallback> cb = do_update ? new ModifiedCOFCallback : 0; + link_inventory_item( gAgent.getID(), + cat->getLinkedUUID(), + getCOF(), + cat->getName(), + LLAssetType::AT_LINK_FOLDER, + cb); +} + |