diff options
Diffstat (limited to 'indra/newview/llappearancemgr.cpp')
-rw-r--r-- | indra/newview/llappearancemgr.cpp | 1521 |
1 files changed, 1140 insertions, 381 deletions
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 4e022aeb29..8c5352ded7 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -32,18 +32,64 @@ #include "llviewerprecompiledheaders.h" +#include "llagent.h" +#include "llagentwearables.h" #include "llappearancemgr.h" -#include "llinventorymodel.h" -#include "llnotifications.h" +#include "llcommandhandler.h" +#include "llfloatercustomize.h" #include "llgesturemgr.h" #include "llinventorybridge.h" -#include "llwearablelist.h" -#include "llagentwearables.h" -#include "llagent.h" +#include "llinventoryfunctions.h" +#include "llinventoryobserver.h" +#include "llnotificationsutil.h" +#include "llsidepanelappearance.h" +#include "llsidetray.h" #include "llvoavatar.h" #include "llvoavatarself.h" #include "llviewerregion.h" -#include "llfloatercustomize.h" +#include "llwearablelist.h" + +LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id,const std::string& name) +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + LLNameCategoryCollector has_name(name); + gInventory.collectDescendentsIf(parent_id, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + if (0 == cat_array.count()) + return LLUUID(); + else + { + LLViewerInventoryCategory *cat = cat_array.get(0); + if (cat) + return cat->getUUID(); + else + { + llwarns << "null cat" << llendl; + return LLUUID(); + } + } +} + +// support for secondlife:///app/appearance SLapps +class LLAppearanceHandler : public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLAppearanceHandler() : LLCommandHandler("appearance", UNTRUSTED_THROTTLE) {} + + bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web) + { + // support secondlife:///app/appearance/show, but for now we just + // make all secondlife:///app/appearance SLapps behave this way + LLSideTray::getInstance()->showPanel("sidepanel_appearance", LLSD()); + return true; + } +}; +LLAppearanceHandler gAppearanceHandler; class LLWearInventoryCategoryCallback : public LLInventoryCallback { @@ -68,11 +114,13 @@ public: protected: ~LLWearInventoryCategoryCallback() { + llinfos << "done all inventory callbacks" << llendl; + // 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); + LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(gInventory.getCategory(mCatID), mAppend); } else { @@ -94,7 +142,8 @@ public: mAppend(append) {} ~LLOutfitObserver() {} - virtual void done(); //public + virtual void done(); + void doWearCategory(); protected: LLUUID mCatID; @@ -104,6 +153,15 @@ protected: void LLOutfitObserver::done() { + llinfos << "done 2nd stage fetch" << llendl; + gInventory.removeObserver(this); + doOnIdle(boost::bind(&LLOutfitObserver::doWearCategory,this)); +} + +void LLOutfitObserver::doWearCategory() +{ + llinfos << "starting" << llendl; + // We now have an outfit ready to be copied to agent inventory. Do // it, and wear that outfit normally. if(mCopyItems) @@ -130,11 +188,11 @@ void LLOutfitObserver::done() { if(LLInventoryType::IT_GESTURE == item->getInventoryType()) { - pid = gInventory.findCategoryUUIDForType(LLAssetType::AT_GESTURE); + pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); } else { - pid = gInventory.findCategoryUUIDForType(LLAssetType::AT_CLOTHING); + pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); } break; } @@ -146,7 +204,7 @@ void LLOutfitObserver::done() LLUUID cat_id = gInventory.createNewCategory( pid, - LLAssetType::AT_NONE, + LLFolderType::FT_NONE, name); mCatID = cat_id; LLPointer<LLInventoryCallback> cb = new LLWearInventoryCategoryCallback(mCatID, mAppend); @@ -171,8 +229,9 @@ void LLOutfitObserver::done() else { // Wear the inventory category. - LLAppearanceManager::wearInventoryCategoryOnAvatar(gInventory.getCategory(mCatID), mAppend); + LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(gInventory.getCategory(mCatID), mAppend); } + delete this; } class LLOutfitFetch : public LLInventoryFetchDescendentsObserver @@ -191,6 +250,8 @@ 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. + llinfos << "done first stage fetch" << llendl; + LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; gInventory.collectDescendents(mCompleteFolders.front(), @@ -220,7 +281,6 @@ void LLOutfitFetch::done() // 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. @@ -239,424 +299,807 @@ void LLOutfitFetch::done() // will call done for us when everything is here. gInventory.addObserver(outfit_observer); } + delete this; } -class LLUpdateAppearanceOnDestroy: public LLInventoryCallback +LLUpdateAppearanceOnDestroy::LLUpdateAppearanceOnDestroy(): + mFireCount(0) { -public: - LLUpdateAppearanceOnDestroy(): - mFireCount(0) - { - } +} - virtual ~LLUpdateAppearanceOnDestroy() +LLUpdateAppearanceOnDestroy::~LLUpdateAppearanceOnDestroy() +{ + llinfos << "done update appearance on destroy" << llendl; + + if (!LLApp::isExiting()) { - LLAppearanceManager::updateAppearanceFromCOF(); + LLAppearanceMgr::instance().updateAppearanceFromCOF(); } +} - /* virtual */ void fire(const LLUUID& inv_item) - { - mFireCount++; - } -private: - U32 mFireCount; -}; +void LLUpdateAppearanceOnDestroy::fire(const LLUUID& inv_item) +{ + llinfos << "callback fired" << llendl; + mFireCount++; +} struct LLFoundData { + LLFoundData() : + mAssetType(LLAssetType::AT_NONE), + mWearableType(WT_INVALID), + mWearable(NULL) {} + LLFoundData(const LLUUID& item_id, const LLUUID& asset_id, const std::string& name, - LLAssetType::EType asset_type) : + const LLAssetType::EType& asset_type, + const EWearableType& wearable_type + ) : mItemID(item_id), mAssetID(asset_id), mName(name), mAssetType(asset_type), + mWearableType(wearable_type), mWearable( NULL ) {} LLUUID mItemID; LLUUID mAssetID; std::string mName; LLAssetType::EType mAssetType; + EWearableType mWearableType; LLWearable* mWearable; }; -struct LLWearableHoldingPattern +class LLWearableHoldingPattern { - LLWearableHoldingPattern() : mResolved(0) {} - ~LLWearableHoldingPattern() - { - for_each(mFoundList.begin(), mFoundList.end(), DeletePointer()); - mFoundList.clear(); - } - typedef std::list<LLFoundData*> found_list_t; +public: + LLWearableHoldingPattern(); + ~LLWearableHoldingPattern(); + + bool pollFetchCompletion(); + void onFetchCompletion(); + bool isFetchCompleted(); + bool isTimedOut(); + + void checkMissingWearables(); + bool pollMissingWearables(); + bool isMissingCompleted(); + void recoverMissingWearable(EWearableType type); + void clearCOFLinksForMissingWearables(); + + void onWearableAssetFetch(LLWearable *wearable); + void onAllComplete(); + + typedef std::list<LLFoundData> found_list_t; found_list_t mFoundList; + LLInventoryModel::item_array_t mObjItems; + LLInventoryModel::item_array_t mGestItems; + typedef std::set<S32> type_set_t; + type_set_t mTypesToRecover; + type_set_t mTypesToLink; S32 mResolved; - bool append; + LLTimer mWaitTime; + bool mFired; }; +LLWearableHoldingPattern::LLWearableHoldingPattern(): + mResolved(0), + mFired(false) +{ +} -void removeDuplicateItems(LLInventoryModel::item_array_t& dst, const LLInventoryModel::item_array_t& src) +LLWearableHoldingPattern::~LLWearableHoldingPattern() { - 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) +bool LLWearableHoldingPattern::isFetchCompleted() +{ + return (mResolved >= (S32)mFoundList.size()); // have everything we were waiting for? +} + +bool LLWearableHoldingPattern::isTimedOut() +{ + static F32 max_wait_time = 60.0; // give up if wearable fetches haven't completed in max_wait_time seconds. + return mWaitTime.getElapsedTimeF32() > max_wait_time; +} + +void LLWearableHoldingPattern::checkMissingWearables() +{ + std::vector<S32> found_by_type(WT_COUNT,0); + std::vector<S32> requested_by_type(WT_COUNT,0); + for (found_list_t::iterator it = mFoundList.begin(); it != mFoundList.end(); ++it) { - 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); + LLFoundData &data = *it; + if (data.mWearableType < WT_COUNT) + requested_by_type[data.mWearableType]++; + if (data.mWearable) + found_by_type[data.mWearableType]++; } - for (LLInventoryModel::item_array_t::const_iterator dst_pos = dst.begin(); - dst_pos != dst.end(); - ++dst_pos) + for (S32 type = 0; type < WT_COUNT; ++type) { - LLUUID dst_item_id = (*dst_pos)->getLinkedUUID(); - - if (mark_inventory.find(dst_item_id) == mark_inventory.end()) - { - } - else + llinfos << "type " << type << " requested " << requested_by_type[type] << " found " << found_by_type[type] << llendl; + if (found_by_type[type] > 0) + continue; + if ( + // Need to recover if at least one wearable of that type + // was requested but none was found (prevent missing + // pants) + (requested_by_type[type] > 0) || + // or if type is a body part and no wearables were found. + ((type == WT_SHAPE) || (type == WT_SKIN) || (type == WT_HAIR) || (type == WT_EYES))) { - inventory_dups++; + mTypesToRecover.insert(type); + mTypesToLink.insert(type); + recoverMissingWearable((EWearableType)type); + llwarns << "need to replace " << type << llendl; } + } - LLUUID dst_asset_id = (*dst_pos)->getAssetUUID(); + mWaitTime.reset(); + if (!pollMissingWearables()) + { + doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollMissingWearables,this)); + } +} - 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 +void LLWearableHoldingPattern::onAllComplete() +{ + // Activate all gestures in this folder + if (mGestItems.count() > 0) + { + llinfos << "Activating " << mGestItems.count() << " gestures" << llendl; + + LLGestureMgr::instance().activateGestures(mGestItems); + + // Update the inventory item labels to reflect the fact + // they are active. + LLViewerInventoryCategory* catp = + gInventory.getCategory(LLAppearanceMgr::instance().getCOF()); + + if (catp) { - asset_dups++; + gInventory.updateCategory(catp); + gInventory.notifyObservers(); } } - llinfos << "removeDups, original " << dst.count() << " final " << new_dst.count() - << " inventory dups " << inventory_dups << " asset_dups " << asset_dups << llendl; + + // Update wearables. + llinfos << "Updating agent wearables with " << mResolved << " wearable items " << llendl; + LLAppearanceMgr::instance().updateAgentWearables(this, false); - dst = new_dst; + // Update attachments to match those requested. + if (isAgentAvatarValid()) + { + llinfos << "Updating " << mObjItems.count() << " attachments" << llendl; + LLAgentWearables::userUpdateAttachments(mObjItems); + } + + if (isFetchCompleted() && isMissingCompleted()) + { + // Only safe to delete if all wearable callbacks and all missing wearables completed. + delete this; + } } +void LLWearableHoldingPattern::onFetchCompletion() +{ + checkMissingWearables(); +} -/* static */ -LLUUID LLAppearanceManager::getCOF() +// Runs as an idle callback until all wearables are fetched (or we time out). +bool LLWearableHoldingPattern::pollFetchCompletion() { - return gInventory.findCategoryUUIDForType(LLAssetType::AT_CURRENT_OUTFIT); + bool completed = isFetchCompleted(); + bool timed_out = isTimedOut(); + bool done = completed || timed_out; + + if (done) + { + llinfos << "polling, done status: " << completed << " timed out " << timed_out + << " elapsed " << mWaitTime.getElapsedTimeF32() << llendl; + + mFired = true; + + if (timed_out) + { + llwarns << "Exceeded max wait time for wearables, updating appearance based on what has arrived" << llendl; + } + + onFetchCompletion(); + } + return done; } -// Update appearance from outfit folder. -/* static */ -void LLAppearanceManager::changeOutfit(bool proceed, const LLUUID& category, bool append) +class RecoveredItemLinkCB: public LLInventoryCallback { - if (!proceed) - return; +public: + RecoveredItemLinkCB(EWearableType type, LLWearable *wearable, LLWearableHoldingPattern* holder): + mHolder(holder), + mWearable(wearable), + mType(type) + { + } + void fire(const LLUUID& item_id) + { + llinfos << "Recovered item link for type " << mType << llendl; + mHolder->mTypesToLink.erase(mType); + // Add wearable to FoundData for actual wearing + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; - if (append) + if (linked_item) + { + gInventory.addChangedMask(LLInventoryObserver::LABEL, linked_item->getUUID()); + + if (item) + { + LLFoundData found(linked_item->getUUID(), + linked_item->getAssetUUID(), + linked_item->getName(), + linked_item->getType(), + linked_item->isWearableType() ? linked_item->getWearableType() : WT_INVALID + ); + found.mWearable = mWearable; + mHolder->mFoundList.push_front(found); + } + else + { + llwarns << "inventory item not found for recovered wearable" << llendl; + } + } + else + { + llwarns << "inventory link not found for recovered wearable" << llendl; + } + } +private: + LLWearableHoldingPattern* mHolder; + LLWearable *mWearable; + EWearableType mType; +}; + +class RecoveredItemCB: public LLInventoryCallback +{ +public: + RecoveredItemCB(EWearableType type, LLWearable *wearable, LLWearableHoldingPattern* holder): + mHolder(holder), + mWearable(wearable), + mType(type) { - updateCOFFromCategory(category, append); // append is true - add non-duplicates to COF. } - else + void fire(const LLUUID& item_id) { - LLViewerInventoryCategory* catp = gInventory.getCategory(category); - if (catp->getPreferredType() == LLAssetType::AT_NONE || - LLAssetType::lookupIsEnsembleCategoryType(catp->getPreferredType())) + llinfos << "Recovered item for type " << mType << llendl; + LLViewerInventoryItem *itemp = gInventory.getItem(item_id); + mWearable->setItemID(item_id); + LLPointer<LLInventoryCallback> cb = new RecoveredItemLinkCB(mType,mWearable,mHolder); + mHolder->mTypesToRecover.erase(mType); + llassert(itemp); + if (itemp) { - updateCOFFromCategory(category, append); // append is false - rebuild COF. + link_inventory_item( gAgent.getID(), + item_id, + LLAppearanceMgr::instance().getCOF(), + itemp->getName(), + LLAssetType::AT_LINK, + cb); } - else if (catp->getPreferredType() == LLAssetType::AT_OUTFIT) + } +private: + LLWearableHoldingPattern* mHolder; + LLWearable *mWearable; + EWearableType mType; +}; + +void LLWearableHoldingPattern::recoverMissingWearable(EWearableType type) +{ + // Try to recover by replacing missing wearable with a new one. + LLNotificationsUtil::add("ReplacedMissingWearable"); + lldebugs << "Wearable " << LLWearableDictionary::getTypeLabel(type) + << " could not be downloaded. Replaced inventory item with default wearable." << llendl; + LLWearable* wearable = LLWearableList::instance().createNewWearable(type); + + // Add a new one in the lost and found folder. + const LLUUID lost_and_found_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + LLPointer<LLInventoryCallback> cb = new RecoveredItemCB(type,wearable,this); + + create_inventory_item(gAgent.getID(), + gAgent.getSessionID(), + lost_and_found_id, + wearable->getTransactionID(), + wearable->getName(), + wearable->getDescription(), + wearable->getAssetType(), + LLInventoryType::IT_WEARABLE, + wearable->getType(), + wearable->getPermissions().getMaskNextOwner(), + cb); +} + +bool LLWearableHoldingPattern::isMissingCompleted() +{ + return mTypesToLink.size()==0 && mTypesToRecover.size()==0; +} + +void LLWearableHoldingPattern::clearCOFLinksForMissingWearables() +{ + for (found_list_t::iterator it = mFoundList.begin(); it != mFoundList.end(); ++it) + { + LLFoundData &data = *it; + if ((data.mWearableType < WT_COUNT) && (!data.mWearable)) { - rebuildCOFFromOutfit(category); + // Wearable link that was never resolved; remove links to it from COF + llinfos << "removing link for unresolved item " << data.mItemID.asString() << llendl; + LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID,false); } } } -// Append to current COF contents by recursively traversing a folder. -/* static */ -void LLAppearanceManager::updateCOFFromCategory(const LLUUID& category, bool append) +bool LLWearableHoldingPattern::pollMissingWearables() { - // 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; - bool follow_folder_links = false; - getUserDescendents(category, wear_items, obj_items, gest_items, follow_folder_links); + bool timed_out = isTimedOut(); + bool missing_completed = isMissingCompleted(); + bool done = timed_out || missing_completed; + + llinfos << "polling missing wearables, waiting for items " << mTypesToRecover.size() + << " links " << mTypesToLink.size() + << " wearables, timed out " << timed_out + << " elapsed " << mWaitTime.getElapsedTimeF32() + << " done " << done << llendl; - // Find all the wearables that are in the category's subtree. - lldebugs << "appendCOFFromCategory()" << llendl; - if( !wear_items.count() && !obj_items.count() && !gest_items.count()) + if (done) { - LLNotifications::instance().add("CouldNotPutOnOutfit"); - return; + clearCOFLinksForMissingWearables(); + onAllComplete(); } - - 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); - // Remove duplicates - if (append) + return done; +} + +void LLWearableHoldingPattern::onWearableAssetFetch(LLWearable *wearable) +{ + mResolved += 1; // just counting callbacks, not successes. + llinfos << "onWearableAssetFetch, resolved count " << mResolved << " of requested " << mFoundList.size() << llendl; + if (wearable) { - removeDuplicateItems(wear_items, cof_items); - removeDuplicateItems(obj_items, cof_items); - removeDuplicateItems(gest_items, cof_items); + llinfos << "wearable found, type " << wearable->getType() << " asset " << wearable->getAssetID() << llendl; + } + else + { + llwarns << "no wearable found" << llendl; } - S32 total_links = gest_items.count() + wear_items.count() + obj_items.count(); + if (mFired) + { + llwarns << "called after holder fired" << llendl; + return; + } - if (!append && total_links > 0) + if (!wearable) { - purgeCOFBeforeRebuild(category); + return; } - LLPointer<LLUpdateAppearanceOnDestroy> link_waiter = new LLUpdateAppearanceOnDestroy; - - // Link all gestures in this folder - if (gest_items.count() > 0) + for (LLWearableHoldingPattern::found_list_t::iterator iter = mFoundList.begin(); + iter != mFoundList.end(); ++iter) { - llinfos << "Linking " << gest_items.count() << " gestures" << llendl; - for (S32 i = 0; i < gest_items.count(); ++i) + LLFoundData& data = *iter; + if(wearable->getAssetID() == data.mAssetID) { - 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); + data.mWearable = wearable; + // Failing this means inventory or asset server are corrupted in a way we don't handle. + llassert((data.mWearableType < WT_COUNT) && (wearable->getType() == data.mWearableType)); + break; } } +} - // Link all wearables - if(wear_items.count() > 0) +static void onWearableAssetFetch(LLWearable* wearable, void* data) +{ + LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data; + holder->onWearableAssetFetch(wearable); +} + + +static void removeDuplicateItems(LLInventoryModel::item_array_t& items) +{ + LLInventoryModel::item_array_t new_items; + std::set<LLUUID> items_seen; + std::deque<LLViewerInventoryItem*> tmp_list; + // Traverse from the front and keep the first of each item + // encountered, so we actually keep the *last* of each duplicate + // item. This is needed to give the right priority when adding + // duplicate items to an existing outfit. + for (S32 i=items.count()-1; i>=0; i--) { - llinfos << "Linking " << wear_items.count() << " wearables" << llendl; - for(S32 i = 0; i < wear_items.count(); ++i) + LLViewerInventoryItem *item = items.get(i); + LLUUID item_id = item->getLinkedUUID(); + if (items_seen.find(item_id)!=items_seen.end()) + continue; + items_seen.insert(item_id); + tmp_list.push_front(item); + } + for (std::deque<LLViewerInventoryItem*>::iterator it = tmp_list.begin(); + it != tmp_list.end(); + ++it) + { + new_items.put(*it); + } + items = new_items; +} + +const LLUUID LLAppearanceMgr::getCOF() const +{ + return gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); +} + + +const LLViewerInventoryItem* LLAppearanceMgr::getBaseOutfitLink() +{ + const LLUUID& current_outfit_cat = getCOF(); + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + // Can't search on FT_OUTFIT since links to categories return FT_CATEGORY for type since they don't + // return preferred type. + LLIsType is_category( LLAssetType::AT_CATEGORY ); + gInventory.collectDescendentsIf(current_outfit_cat, + cat_array, + item_array, + false, + is_category, + false); + for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin(); + iter != item_array.end(); + iter++) + { + const LLViewerInventoryItem *item = (*iter); + const LLViewerInventoryCategory *cat = item->getLinkedCategory(); + if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT) { - // 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); + return item; } } + return NULL; +} - // Link all attachments. - if( obj_items.count() > 0 ) +bool LLAppearanceMgr::getBaseOutfitName(std::string& name) +{ + const LLViewerInventoryItem* outfit_link = getBaseOutfitLink(); + if(outfit_link) { - llinfos << "Linking " << obj_items.count() << " attachments" << llendl; - LLVOAvatar* avatar = gAgent.getAvatarObject(); - if( avatar ) + const LLViewerInventoryCategory *cat = outfit_link->getLinkedCategory(); + if (cat) { - 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); - } + name = cat->getName(); + return true; } } + return false; } -/* static */ -void LLAppearanceManager::shallowCopyCategory(const LLUUID& src_id, const LLUUID& dst_id, +// Update appearance from outfit folder. +void LLAppearanceMgr::changeOutfit(bool proceed, const LLUUID& category, bool append) +{ + if (!proceed) + return; + LLAppearanceMgr::instance().updateCOF(category,append); +} + +// Create a copy of src_id + contents as a subfolder of dst_id. +void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& dst_id, LLPointer<LLInventoryCallback> cb) { - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(src_id, cats, items, - LLInventoryModel::EXCLUDE_TRASH); - for (S32 i = 0; i < items.count(); ++i) + LLInventoryCategory *src_cat = gInventory.getCategory(src_id); + if (!src_cat) { - const LLViewerInventoryItem* item = items.get(i).get(); - if (item->getActualType() == LLAssetType::AT_LINK) - { - link_inventory_item(gAgent.getID(), - item->getLinkedUUID(), - dst_id, - item->getName(), - LLAssetType::AT_LINK, cb); - } - else if (item->getActualType() == LLAssetType::AT_LINK_FOLDER) + llwarns << "folder not found for src " << src_id.asString() << llendl; + return; + } + LLUUID parent_id = dst_id; + if(parent_id.isNull()) + { + parent_id = gInventory.getRootFolderID(); + } + LLUUID subfolder_id = gInventory.createNewCategory( parent_id, + LLFolderType::FT_NONE, + src_cat->getName()); + shallowCopyCategoryContents(src_id, subfolder_id, cb); + + gInventory.notifyObservers(); +} + +// Copy contents of src_id to dst_id. +void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LLUUID& dst_id, + LLPointer<LLInventoryCallback> cb) +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(src_id, cats, items); + for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); + iter != items->end(); + ++iter) + { + const LLViewerInventoryItem* item = (*iter); + switch (item->getActualType()) { - LLViewerInventoryCategory *catp = item->getLinkedCategory(); - // Skip copying outfit links. - if (catp && catp->getPreferredType() != LLAssetType::AT_OUTFIT) + case LLAssetType::AT_LINK: { link_inventory_item(gAgent.getID(), item->getLinkedUUID(), dst_id, item->getName(), - LLAssetType::AT_LINK_FOLDER, cb); + LLAssetType::AT_LINK, cb); + break; } - } - else - { - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - dst_id, - item->getName(), - cb); + case LLAssetType::AT_LINK_FOLDER: + { + LLViewerInventoryCategory *catp = item->getLinkedCategory(); + // Skip copying outfit links. + if (catp && catp->getPreferredType() != LLFolderType::FT_OUTFIT) + { + link_inventory_item(gAgent.getID(), + item->getLinkedUUID(), + dst_id, + item->getName(), + LLAssetType::AT_LINK_FOLDER, cb); + } + break; + } + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_OBJECT: + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_GESTURE: + { + copy_inventory_item(gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + dst_id, + item->getName(), + cb); + break; + } + default: + // Ignore non-outfit asset types + break; } } } -/* static */ -bool LLAppearanceManager::isMandatoryWearableType(EWearableType type) +BOOL LLAppearanceMgr::getCanMakeFolderIntoOutfit(const LLUUID& folder_id) { - return (type==WT_SHAPE) || (type==WT_SKIN) || (type== WT_HAIR) || (type==WT_EYES); + // These are the wearable items that are required for considering this + // folder as containing a complete outfit. + U32 required_wearables = 0; + required_wearables |= 1LL << WT_SHAPE; + required_wearables |= 1LL << WT_SKIN; + required_wearables |= 1LL << WT_HAIR; + required_wearables |= 1LL << WT_EYES; + + // These are the wearables that the folder actually contains. + U32 folder_wearables = 0; + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(folder_id, cats, items); + for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); + iter != items->end(); + ++iter) + { + const LLViewerInventoryItem* item = (*iter); + if (item->isWearableType()) + { + const EWearableType wearable_type = item->getWearableType(); + folder_wearables |= 1LL << wearable_type; + } + } + + // If the folder contains the required wearables, return TRUE. + return ((required_wearables & folder_wearables) == required_wearables); } -// For mandatory body parts. -/* static */ -void LLAppearanceManager::checkMandatoryWearableTypes(const LLUUID& category, std::set<EWearableType>& types_found) + +void LLAppearanceMgr::purgeBaseOutfitLink(const LLUUID& category) { - LLInventoryModel::cat_array_t new_cats; - LLInventoryModel::item_array_t new_items; - gInventory.collectDescendents(category, new_cats, new_items, + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(category, cats, items, LLInventoryModel::EXCLUDE_TRASH); - std::set<EWearableType> wt_types_found; - for (S32 i = 0; i < new_items.count(); ++i) + for (S32 i = 0; i < items.count(); ++i) { - LLViewerInventoryItem *itemp = new_items.get(i); - if (itemp->isWearableType()) + LLViewerInventoryItem *item = items.get(i); + if (item->getActualType() != LLAssetType::AT_LINK_FOLDER) + continue; + if (item->getIsLinkType()) { - EWearableType type = itemp->getWearableType(); - if (isMandatoryWearableType(type)) + LLViewerInventoryCategory* catp = item->getLinkedCategory(); + if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) { - types_found.insert(type); + gInventory.purgeObject(item->getUUID()); } } } } -// Remove everything from the COF that we safely can before replacing -// with contents of new category. This means preserving any mandatory -// body parts that aren't present in the new category, and getting rid -// of everything else. -/* static */ -void LLAppearanceManager::purgeCOFBeforeRebuild(const LLUUID& category) +void LLAppearanceMgr::purgeCategory(const LLUUID& category, bool keep_outfit_links) { - // See which mandatory body types are present in the new category. - std::set<EWearableType> wt_types_found; - checkMandatoryWearableTypes(category,wt_types_found); - - LLInventoryModel::cat_array_t cof_cats; - LLInventoryModel::item_array_t cof_items; - gInventory.collectDescendents(getCOF(), cof_cats, cof_items, + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(category, cats, items, LLInventoryModel::EXCLUDE_TRASH); - for (S32 i = 0; i < cof_items.count(); ++i) + for (S32 i = 0; i < items.count(); ++i) { - LLViewerInventoryItem *itemp = cof_items.get(i); - if (itemp->isWearableType()) + LLViewerInventoryItem *item = items.get(i); + if (keep_outfit_links && (item->getActualType() == LLAssetType::AT_LINK_FOLDER)) + continue; + if (item->getIsLinkType()) { - EWearableType type = itemp->getWearableType(); - if (!isMandatoryWearableType(type) || (wt_types_found.find(type) != wt_types_found.end())) - { - // Not mandatory or supplied by the new category - OK to delete - gInventory.purgeObject(cof_items.get(i)->getUUID()); - } + gInventory.purgeObject(item->getUUID()); } - else + } +} + +// Keep the last N wearables of each type. For viewer 2.0, N is 1 for +// both body parts and clothing items. +void LLAppearanceMgr::filterWearableItems( + LLInventoryModel::item_array_t& items, S32 max_per_type) +{ + // Divvy items into arrays by wearable type. + std::vector<LLInventoryModel::item_array_t> items_by_type(WT_COUNT); + for (S32 i=0; i<items.count(); i++) + { + LLViewerInventoryItem *item = items.get(i); + // Ignore non-wearables. + if (!item->isWearableType()) + continue; + EWearableType type = item->getWearableType(); + if(type < 0 || type >= WT_COUNT) { - // Not a wearable - always purge - gInventory.purgeObject(cof_items.get(i)->getUUID()); + LL_WARNS("Appearance") << "Invalid wearable type. Inventory type does not match wearable flag bitfield." << LL_ENDL; + continue; + } + items_by_type[type].push_back(item); + } + + // rebuild items list, retaining the last max_per_type of each array + items.clear(); + for (S32 i=0; i<WT_COUNT; i++) + { + S32 size = items_by_type[i].size(); + if (size <= 0) + continue; + S32 start_index = llmax(0,size-max_per_type); + for (S32 j = start_index; j<size; j++) + { + items.push_back(items_by_type[i][j]); } } - gInventory.notifyObservers(); } -// Replace COF contents from a given outfit folder. -/* static */ -void LLAppearanceManager::rebuildCOFFromOutfit(const LLUUID& category) +// Create links to all listed items. +void LLAppearanceMgr::linkAll(const LLUUID& category, + LLInventoryModel::item_array_t& items, + LLPointer<LLInventoryCallback> cb) { - lldebugs << "rebuildCOFFromOutfit()" << llendl; + for (S32 i=0; i<items.count(); i++) + { + const LLInventoryItem* item = items.get(i).get(); + link_inventory_item(gAgent.getID(), + item->getLinkedUUID(), + category, + item->getName(), + LLAssetType::AT_LINK, + cb); + } +} - dumpCat(category,"start, source outfit"); - dumpCat(getCOF(),"start, COF"); +void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append) +{ + LLViewerInventoryCategory *pcat = gInventory.getCategory(category); + llinfos << "starting, cat " << (pcat ? pcat->getName() : "[UNKNOWN]") << llendl; - // Find all the wearables that are in the category's subtree. - LLInventoryModel::item_array_t items; - getCOFValidDescendents(category, items); + const LLUUID cof = getCOF(); - if( items.count() == 0) + // Deactivate currently active gestures in the COF, if replacing outfit + if (!append) { - LLNotifications::instance().add("CouldNotPutOnOutfit"); - return; + LLInventoryModel::item_array_t gest_items; + getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE, false); + for(S32 i = 0; i < gest_items.count(); ++i) + { + LLViewerInventoryItem *gest_item = gest_items.get(i); + if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) ) + { + LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() ); + } + } } - - // Processes that take time should show the busy cursor - //inc_busy_count(); - - //dumpCat(current_outfit_id,"COF before remove:"); + + // Collect and filter descendents to determine new COF contents. + + // - Body parts: always include COF contents as a fallback in case any + // required parts are missing. + LLInventoryModel::item_array_t body_items; + getDescendentsOfAssetType(cof, body_items, LLAssetType::AT_BODYPART, false); + getDescendentsOfAssetType(category, body_items, LLAssetType::AT_BODYPART, false); + // Reduce body items to max of one per type. + removeDuplicateItems(body_items); + filterWearableItems(body_items, 1); + + // - Wearables: include COF contents only if appending. + LLInventoryModel::item_array_t wear_items; + if (append) + getDescendentsOfAssetType(cof, wear_items, LLAssetType::AT_CLOTHING, false); + getDescendentsOfAssetType(category, wear_items, LLAssetType::AT_CLOTHING, false); + // Reduce wearables to max of one per type. + removeDuplicateItems(wear_items); + filterWearableItems(wear_items, 5); - //dumpCat(current_outfit_id,"COF after remove:"); + // - Attachments: include COF contents only if appending. + LLInventoryModel::item_array_t obj_items; + if (append) + getDescendentsOfAssetType(cof, obj_items, LLAssetType::AT_OBJECT, false); + getDescendentsOfAssetType(category, obj_items, LLAssetType::AT_OBJECT, false); + removeDuplicateItems(obj_items); - purgeCOFBeforeRebuild(category); + // - Gestures: include COF contents only if appending. + LLInventoryModel::item_array_t gest_items; + if (append) + getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE, false); + getDescendentsOfAssetType(category, gest_items, LLAssetType::AT_GESTURE, false); + removeDuplicateItems(gest_items); + // Remove current COF contents. + bool keep_outfit_links = append; + purgeCategory(cof, keep_outfit_links); + gInventory.notifyObservers(); + + // Create links to new COF contents. + llinfos << "creating LLUpdateAppearanceOnDestroy" << llendl; LLPointer<LLInventoryCallback> link_waiter = new LLUpdateAppearanceOnDestroy; - LLUUID current_outfit_id = getCOF(); - LLAppearanceManager::shallowCopyCategory(category, current_outfit_id, link_waiter); - //dumpCat(current_outfit_id,"COF after shallow copy:"); + linkAll(cof, body_items, link_waiter); + linkAll(cof, wear_items, link_waiter); + linkAll(cof, obj_items, link_waiter); + linkAll(cof, gest_items, link_waiter); - // Create a link to the outfit that we wore. - LLViewerInventoryCategory* catp = gInventory.getCategory(category); - if (catp && catp->getPreferredType() == LLAssetType::AT_OUTFIT) + // Add link to outfit if category is an outfit. + if (!append) { - link_inventory_item(gAgent.getID(), category, current_outfit_id, catp->getName(), - LLAssetType::AT_LINK_FOLDER, link_waiter); + createBaseOutfitLink(category, link_waiter); } + llinfos << "waiting for LLUpdateAppearanceOnDestroy" << llendl; } -/* static */ -void LLAppearanceManager::onWearableAssetFetch(LLWearable* wearable, void* data) +void LLAppearanceMgr::updatePanelOutfitName(const std::string& name) { - LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data; - bool append = holder->append; - - if(wearable) + LLSidepanelAppearance* panel_appearance = + dynamic_cast<LLSidepanelAppearance *>(LLSideTray::getInstance()->getPanel("sidepanel_appearance")); + if (panel_appearance) { - 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; - } - } + panel_appearance->refreshCurrentOutfitName(name); } - holder->mResolved += 1; - if(holder->mResolved >= (S32)holder->mFoundList.size()) +} + +void LLAppearanceMgr::createBaseOutfitLink(const LLUUID& category, LLPointer<LLInventoryCallback> link_waiter) +{ + const LLUUID cof = getCOF(); + LLViewerInventoryCategory* catp = gInventory.getCategory(category); + std::string new_outfit_name = ""; + + purgeBaseOutfitLink(cof); + + if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) { - LLAppearanceManager::updateAgentWearables(holder, append); + link_inventory_item(gAgent.getID(), category, cof, catp->getName(), + LLAssetType::AT_LINK_FOLDER, link_waiter); + new_outfit_name = catp->getName(); } + + updatePanelOutfitName(new_outfit_name); } -/* static */ -void LLAppearanceManager::updateAgentWearables(LLWearableHoldingPattern* holder, bool append) +void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder, bool append) { lldebugs << "updateAgentWearables()" << llendl; LLInventoryItem::item_array_t items; @@ -669,18 +1112,17 @@ void LLAppearanceManager::updateAgentWearables(LLWearableHoldingPattern* holder, for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->mFoundList.begin(); iter != holder->mFoundList.end(); ++iter) { - LLFoundData* data = *iter; - LLWearable* wearable = data->mWearable; + LLFoundData& data = *iter; + LLWearable* wearable = data.mWearable; if( wearable && ((S32)wearable->getType() == i) ) { LLViewerInventoryItem* item; - item = (LLViewerInventoryItem*)gInventory.getItem(data->mItemID); + item = (LLViewerInventoryItem*)gInventory.getItem(data.mItemID); if( item && (item->getAssetUUID() == wearable->getAssetID()) ) { items.put(item); wearables.put(wearable); } - break; } } } @@ -688,112 +1130,144 @@ void LLAppearanceManager::updateAgentWearables(LLWearableHoldingPattern* holder, if(wearables.count() > 0) { gAgentWearables.setWearableOutfit(items, wearables, !append); - gInventory.notifyObservers(); } - delete holder; - // dec_busy_count(); } -/* static */ -void LLAppearanceManager::updateAppearanceFromCOF() +static void remove_non_link_items(LLInventoryModel::item_array_t &items) +{ + LLInventoryModel::item_array_t pruned_items; + for (LLInventoryModel::item_array_t::const_iterator iter = items.begin(); + iter != items.end(); + ++iter) + { + const LLViewerInventoryItem *item = (*iter); + if (item && item->getIsLinkType()) + { + pruned_items.push_back((*iter)); + } + } + items = pruned_items; +} + +void LLAppearanceMgr::updateAppearanceFromCOF() { + // update dirty flag to see if the state of the COF matches + // the saved outfit stored as a folder link + llinfos << "starting" << llendl; + + updateIsDirty(); + dumpCat(getCOF(),"COF, start"); 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; + // Find all the wearables that are in the COF's subtree. + lldebugs << "LLAppearanceMgr::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()) + // Get rid of non-links in case somehow the COF was corrupted. + remove_non_link_items(wear_items); + remove_non_link_items(obj_items); + remove_non_link_items(gest_items); + + if(!wear_items.count()) { - LLNotifications::instance().add("CouldNotPutOnOutfit"); + LLNotificationsUtil::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); + LLWearableHoldingPattern* holder = new LLWearableHoldingPattern; - // 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(); - } - } + holder->mObjItems = obj_items; + holder->mGestItems = gest_items; + + // 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(). - if(wear_items.count() > 0) + for(S32 i = 0; i < wear_items.count(); ++i) { - // 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) + LLViewerInventoryItem *item = wear_items.get(i); + LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; + if (item && item->getIsLinkType() && linked_item) { - found = new LLFoundData(wear_items.get(i)->getLinkedUUID(), // Wear the base item, not the link - wear_items.get(i)->getAssetUUID(), - wear_items.get(i)->getName(), - wear_items.get(i)->getType()); + LLFoundData found(linked_item->getUUID(), + linked_item->getAssetUUID(), + linked_item->getName(), + linked_item->getType(), + linked_item->isWearableType() ? linked_item->getWearableType() : WT_INVALID + ); + +#if 0 + // Fault injection: uncomment this block to test asset + // fetch failures (should be replaced by new defaults in + // lost&found). + if (found.mWearableType == WT_SHAPE || found.mWearableType == WT_JACKET) + { + found.mAssetID.generate(); // Replace with new UUID, guaranteed not to exist in DB + + } +#endif + holder->mFoundList.push_front(found); - found_container.put(found); } - for(S32 i = 0; i < wear_items.count(); ++i) + else { - 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 (!item) + { + llwarns << "Attempt to wear a null item " << llendl; + } + else if (!linked_item) + { + llwarns << "Attempt to wear a broken link [ name:" << item->getName() << " ] " << llendl; + } } } - // Update attachments to match those requested. - LLVOAvatar* avatar = gAgent.getAvatarObject(); - if( avatar ) + for (LLWearableHoldingPattern::found_list_t::iterator it = holder->mFoundList.begin(); + it != holder->mFoundList.end(); ++it) { - LLAgentWearables::userUpdateAttachments(obj_items); + LLFoundData& found = *it; + + llinfos << "waiting for onWearableAssetFetch callback, asset " << found.mAssetID.asString() << llendl; + + // Fetch the wearables about to be worn. + LLWearableList::instance().getAsset(found.mAssetID, + found.mName, + found.mAssetType, + onWearableAssetFetch, + (void*)holder); + + } + + if (!holder->pollFetchCompletion()) + { + doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollFetchCompletion,holder)); } } -/* static */ -void LLAppearanceManager::getCOFValidDescendents(const LLUUID& category, - LLInventoryModel::item_array_t& items) +void LLAppearanceMgr::getDescendentsOfAssetType(const LLUUID& category, + LLInventoryModel::item_array_t& items, + LLAssetType::EType type, + bool follow_folder_links) { LLInventoryModel::cat_array_t cats; - LLFindCOFValidItems is_cof_valid; - bool follow_folder_links = false; + LLIsType is_of_type(type); gInventory.collectDescendentsIf(category, - cats, - items, + cats, + items, LLInventoryModel::EXCLUDE_TRASH, - is_cof_valid, + is_of_type, follow_folder_links); } -/* static */ -void LLAppearanceManager::getUserDescendents(const LLUUID& category, +void LLAppearanceMgr::getUserDescendents(const LLUUID& category, LLInventoryModel::item_array_t& wear_items, LLInventoryModel::item_array_t& obj_items, LLInventoryModel::item_array_t& gest_items, @@ -828,17 +1302,18 @@ void LLAppearanceManager::getUserDescendents(const LLUUID& category, follow_folder_links); } -void LLAppearanceManager::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append) +void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append) { if(!category) return; - lldebugs << "wearInventoryCategory( " << category->getName() + llinfos << "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; + uuid_vec_t folders; folders.push_back(category->getUUID()); outfit_fetcher->fetchDescendents(folders); //inc_busy_count(); @@ -856,28 +1331,29 @@ void LLAppearanceManager::wearInventoryCategory(LLInventoryCategory* category, b } // *NOTE: hack to get from avatar inventory to avatar -/* static */ -void LLAppearanceManager::wearInventoryCategoryOnAvatar( LLInventoryCategory* category, bool append ) +void LLAppearanceMgr::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() + + llinfos << "wearInventoryCategoryOnAvatar( " << category->getName() << " )" << llendl; if( gFloaterCustomize ) { - gFloaterCustomize->askToSaveIfDirty(boost::bind(LLAppearanceManager::changeOutfit, _1, category->getUUID(), append)); + gFloaterCustomize->askToSaveIfDirty(boost::bind(&LLAppearanceMgr::changeOutfit, + &LLAppearanceMgr::instance(), + _1, category->getUUID(), append)); } else { - LLAppearanceManager::changeOutfit(TRUE, category->getUUID(), append); + LLAppearanceMgr::changeOutfit(TRUE, category->getUUID(), append); } } -/* static */ -void LLAppearanceManager::wearOutfitByName(const std::string& name) +void LLAppearanceMgr::wearOutfitByName(const std::string& name) { llinfos << "Wearing category " << name << llendl; //inc_busy_count(); @@ -913,7 +1389,7 @@ void LLAppearanceManager::wearOutfitByName(const std::string& name) if(cat) { - LLAppearanceManager::wearInventoryCategory(cat, copy_items, false); + LLAppearanceMgr::wearInventoryCategory(cat, copy_items, false); } else { @@ -924,44 +1400,116 @@ void LLAppearanceManager::wearOutfitByName(const std::string& name) //dec_busy_count(); } -/* static */ -void LLAppearanceManager::wearItem( LLInventoryItem* item, bool do_update ) +bool areMatchingWearables(const LLViewerInventoryItem *a, const LLViewerInventoryItem *b) +{ + return (a->isWearableType() && b->isWearableType() && + (a->getWearableType() == b->getWearableType())); +} + +class LLDeferredCOFLinkObserver: public LLInventoryObserver { +public: + LLDeferredCOFLinkObserver(const LLUUID& item_id, bool do_update): + mItemID(item_id), + mDoUpdate(do_update) + { + } + + ~LLDeferredCOFLinkObserver() + { + } + + /* virtual */ void changed(U32 mask) + { + const LLInventoryItem *item = gInventory.getItem(mItemID); + if (item) + { + gInventory.removeObserver(this); + LLAppearanceMgr::instance().addCOFItemLink(item,mDoUpdate); + delete this; + } + } + +private: + const LLUUID mItemID; + bool mDoUpdate; +}; + + +void LLAppearanceMgr::addCOFItemLink(const LLUUID &item_id, bool do_update ) +{ + const LLInventoryItem *item = gInventory.getItem(item_id); + if (!item) + { + LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, do_update); + gInventory.addObserver(observer); + } + else + { + addCOFItemLink(item, do_update); + } +} + +void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update ) +{ + const LLViewerInventoryItem *vitem = dynamic_cast<const LLViewerInventoryItem*>(item); + if (!vitem) + { + llwarns << "not an llviewerinventoryitem, failed" << llendl; + return; + } + + gInventory.addChangedMask(LLInventoryObserver::LABEL, vitem->getLinkedUUID()); + LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(LLAppearanceManager::getCOF(), + gInventory.collectDescendents(LLAppearanceMgr::getCOF(), cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH); bool linked_already = false; for (S32 i=0; i<item_array.count(); i++) { - const LLInventoryItem* inv_item = item_array.get(i).get(); - if (inv_item->getLinkedUUID() == item->getLinkedUUID()) + // Are these links to the same object? + const LLViewerInventoryItem* inv_item = item_array.get(i).get(); + if (inv_item->getLinkedUUID() == vitem->getLinkedUUID()) { linked_already = true; - break; + } + // Are these links to different items of the same wearable + // type? If so, new item will replace old. + // MULTI-WEARABLES: revisit if more than one per type is allowed. + else if (areMatchingWearables(vitem,inv_item)) + { + if (inv_item->getIsLinkType()) + { + gInventory.purgeObject(inv_item->getUUID()); + } } } if (linked_already) { if (do_update) - LLAppearanceManager::updateAppearanceFromCOF(); + { + LLAppearanceMgr::updateAppearanceFromCOF(); + } + return; } else { LLPointer<LLInventoryCallback> cb = do_update ? new ModifiedCOFCallback : 0; link_inventory_item( gAgent.getID(), - item->getLinkedUUID(), + vitem->getLinkedUUID(), getCOF(), - item->getName(), + vitem->getName(), LLAssetType::AT_LINK, cb); } + return; } -/* static */ -void LLAppearanceManager::wearEnsemble( LLInventoryCategory* cat, bool do_update ) +// BAP remove ensemble code for 2.1? +void LLAppearanceMgr::addEnsembleLink( LLInventoryCategory* cat, bool do_update ) { #if SUPPORT_ENSEMBLES // BAP add check for already in COF. @@ -975,37 +1523,125 @@ void LLAppearanceManager::wearEnsemble( LLInventoryCategory* cat, bool do_update #endif } -/* static */ -void LLAppearanceManager::removeItemLinks(const LLUUID& item_id, bool do_update) +void LLAppearanceMgr::removeCOFItemLinks(const LLUUID& item_id, bool do_update) { + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(LLAppearanceManager::getCOF(), + gInventory.collectDescendents(LLAppearanceMgr::getCOF(), cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH); for (S32 i=0; i<item_array.count(); i++) { const LLInventoryItem* item = item_array.get(i).get(); - if (item->getLinkedUUID() == item_id) + if (item->getIsLinkType() && item->getLinkedUUID() == item_id) { - gInventory.purgeObject(item_array.get(i)->getUUID()); + gInventory.purgeObject(item->getUUID()); } } if (do_update) { - LLAppearanceManager::updateAppearanceFromCOF(); + LLAppearanceMgr::updateAppearanceFromCOF(); + } +} + +void LLAppearanceMgr::updateIsDirty() +{ + LLUUID cof = getCOF(); + LLUUID base_outfit; + + // find base outfit link + const LLViewerInventoryItem* base_outfit_item = getBaseOutfitLink(); + LLViewerInventoryCategory* catp = NULL; + if (base_outfit_item && base_outfit_item->getIsLinkType()) + { + catp = base_outfit_item->getLinkedCategory(); + } + if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + base_outfit = catp->getUUID(); + } + + if(base_outfit.isNull()) + { + // no outfit link found, display "unsaved outfit" + mOutfitIsDirty = true; + } + else + { + LLInventoryModel::cat_array_t cof_cats; + LLInventoryModel::item_array_t cof_items; + gInventory.collectDescendents(cof, cof_cats, cof_items, + LLInventoryModel::EXCLUDE_TRASH); + + LLInventoryModel::cat_array_t outfit_cats; + LLInventoryModel::item_array_t outfit_items; + gInventory.collectDescendents(base_outfit, outfit_cats, outfit_items, + LLInventoryModel::EXCLUDE_TRASH); + + if(outfit_items.count() != cof_items.count() -1) + { + // Current outfit folder should have one more item than the outfit folder. + // this one item is the link back to the outfit folder itself. + mOutfitIsDirty = true; + } + else + { + typedef std::set<LLUUID> item_set_t; + item_set_t cof_set; + item_set_t outfit_set; + + // sort COF items by UUID + for (S32 i = 0; i < cof_items.count(); ++i) + { + LLViewerInventoryItem *item = cof_items.get(i); + // don't add the base outfit link to the list of objects we're comparing + if(item != base_outfit_item) + { + cof_set.insert(item->getLinkedUUID()); + } + } + + // sort outfit folder by UUID + for (S32 i = 0; i < outfit_items.count(); ++i) + { + LLViewerInventoryItem *item = outfit_items.get(i); + outfit_set.insert(item->getLinkedUUID()); + } + + mOutfitIsDirty = (outfit_set != cof_set); + } + } +} + +void LLAppearanceMgr::onFirstFullyVisible() +{ + // If this is the very first time the user has logged into viewer2+ (from a legacy viewer, or new account) + // then auto-populate outfits from the library into the My Outfits folder. + + llinfos << "avatar fully visible" << llendl; + + static bool check_populate_my_outfits = true; + if (check_populate_my_outfits && + (LLInventoryModel::getIsFirstTimeInViewer2() + || gSavedSettings.getBOOL("MyOutfitsAutofill"))) + { + gAgentWearables.populateMyOutfitsFolder(); } + check_populate_my_outfits = false; } -/* static */ -void LLAppearanceManager::dumpCat(const LLUUID& cat_id, std::string str) +//#define DUMP_CAT_VERBOSE + +void LLAppearanceMgr::dumpCat(const LLUUID& cat_id, const std::string& msg) { LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; gInventory.collectDescendents(cat_id, cats, items, LLInventoryModel::EXCLUDE_TRASH); -#if 0 +#ifdef DUMP_CAT_VERBOSE llinfos << llendl; llinfos << str << llendl; S32 hitcount = 0; @@ -1017,6 +1653,129 @@ void LLAppearanceManager::dumpCat(const LLUUID& cat_id, std::string str) llinfos << i <<" "<< item->getName() <<llendl; } #endif - llinfos << str << " count " << items.count() << llendl; + llinfos << msg << " count " << items.count() << llendl; +} + +void LLAppearanceMgr::dumpItemArray(const LLInventoryModel::item_array_t& items, + const std::string& msg) +{ + llinfos << msg << llendl; + for (S32 i=0; i<items.count(); i++) + { + LLViewerInventoryItem *item = items.get(i); + llinfos << i <<" " << item->getName() << llendl; + } + llinfos << llendl; +} + +LLAppearanceMgr::LLAppearanceMgr(): + mAttachmentInvLinkEnabled(false), + mOutfitIsDirty(false) +{ +} + +LLAppearanceMgr::~LLAppearanceMgr() +{ +} + +void LLAppearanceMgr::setAttachmentInvLinkEnable(bool val) +{ + llinfos << "setAttachmentInvLinkEnable => " << (int) val << llendl; + mAttachmentInvLinkEnabled = val; +} + +void dumpAttachmentSet(const std::set<LLUUID>& atts, const std::string& msg) +{ + llinfos << msg << llendl; + for (std::set<LLUUID>::const_iterator it = atts.begin(); + it != atts.end(); + ++it) + { + LLUUID item_id = *it; + LLViewerInventoryItem *item = gInventory.getItem(item_id); + if (item) + llinfos << "atts " << item->getName() << llendl; + else + llinfos << "atts " << "UNKNOWN[" << item_id.asString() << "]" << llendl; + } + llinfos << llendl; +} + +void LLAppearanceMgr::registerAttachment(const LLUUID& item_id) +{ + mRegisteredAttachments.insert(item_id); + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + //dumpAttachmentSet(mRegisteredAttachments,"after register:"); + + if (mAttachmentInvLinkEnabled) + { + LLAppearanceMgr::addCOFItemLink(item_id, false); // Add COF link for item. + } + else + { + //llinfos << "no link changes, inv link not enabled" << llendl; + } +} + +void LLAppearanceMgr::unregisterAttachment(const LLUUID& item_id) +{ + mRegisteredAttachments.erase(item_id); + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + + //dumpAttachmentSet(mRegisteredAttachments,"after unregister:"); + + if (mAttachmentInvLinkEnabled) + { + //LLAppearanceMgr::dumpCat(LLAppearanceMgr::getCOF(),"Removing attachment link:"); + LLAppearanceMgr::removeCOFItemLinks(item_id, false); + } + else + { + //llinfos << "no link changes, inv link not enabled" << llendl; + } +} + +void LLAppearanceMgr::linkRegisteredAttachments() +{ + for (std::set<LLUUID>::iterator it = mRegisteredAttachments.begin(); + it != mRegisteredAttachments.end(); + ++it) + { + LLUUID item_id = *it; + addCOFItemLink(item_id, false); + } + mRegisteredAttachments.clear(); +} + +BOOL LLAppearanceMgr::getIsInCOF(const LLUUID& obj_id) const +{ + return gInventory.isObjectDescendentOf(obj_id, getCOF()); } +BOOL LLAppearanceMgr::getIsProtectedCOFItem(const LLUUID& obj_id) const +{ + if (!getIsInCOF(obj_id)) return FALSE; + + // If a non-link somehow ended up in COF, allow deletion. + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (obj && !obj->getIsLinkType()) + { + return FALSE; + } + + // For now, don't allow direct deletion from the COF. Instead, force users + // to choose "Detach" or "Take Off". + return TRUE; + /* + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (!obj) return FALSE; + + // Can't delete bodyparts, since this would be equivalent to removing the item. + if (obj->getType() == LLAssetType::AT_BODYPART) return TRUE; + + // Can't delete the folder link, since this is saved for bookkeeping. + if (obj->getActualType() == LLAssetType::AT_LINK_FOLDER) return TRUE; + + return FALSE; + */ +} |