summaryrefslogtreecommitdiff
path: root/indra/newview/llappearancemgr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llappearancemgr.cpp')
-rw-r--r--indra/newview/llappearancemgr.cpp1035
1 files changed, 876 insertions, 159 deletions
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index a899926938..04a5bcc256 100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -2,31 +2,25 @@
* @file llappearancemgr.cpp
* @brief Manager for initiating appearance changes on the viewer
*
- * $LicenseInfo:firstyear=2004&license=viewergpl$
- *
- * Copyright (c) 2004-2009, Linden Research, Inc.
- *
+ * $LicenseInfo:firstyear=2004&license=viewerlgpl$
* 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
+ * Copyright (C) 2010, Linden Research, Inc.
*
- * 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
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
*
- * 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.
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
*
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -38,12 +32,14 @@
#include "llagentwearables.h"
#include "llappearancemgr.h"
#include "llcommandhandler.h"
+#include "lleventtimer.h"
#include "llgesturemgr.h"
#include "llinventorybridge.h"
#include "llinventoryfunctions.h"
#include "llinventoryobserver.h"
#include "llnotificationsutil.h"
-#include "llpaneloutfitsinventory.h"
+#include "lloutfitobserver.h"
+#include "lloutfitslist.h"
#include "llselectmgr.h"
#include "llsidepanelappearance.h"
#include "llsidetray.h"
@@ -53,8 +49,55 @@
#include "llviewerregion.h"
#include "llwearablelist.h"
+// RAII thingy to guarantee that a variable gets reset when the Setter
+// goes out of scope. More general utility would be handy - TODO:
+// check boost.
+class BoolSetter
+{
+public:
+ BoolSetter(bool& var):
+ mVar(var)
+ {
+ mVar = true;
+ }
+ ~BoolSetter()
+ {
+ mVar = false;
+ }
+private:
+ bool& mVar;
+};
+
char ORDER_NUMBER_SEPARATOR('@');
+class LLOutfitUnLockTimer: public LLEventTimer
+{
+public:
+ LLOutfitUnLockTimer(F32 period) : LLEventTimer(period)
+ {
+ // restart timer on BOF changed event
+ LLOutfitObserver::instance().addBOFChangedCallback(boost::bind(
+ &LLOutfitUnLockTimer::reset, this));
+ stop();
+ }
+
+ /*virtual*/
+ BOOL tick()
+ {
+ if(mEventTimer.hasExpired())
+ {
+ LLAppearanceMgr::instance().setOutfitLocked(false);
+ }
+ return FALSE;
+ }
+ void stop() { mEventTimer.stop(); }
+ void start() { mEventTimer.start(); }
+ void reset() { mEventTimer.reset(); }
+ BOOL getStarted() { return mEventTimer.getStarted(); }
+
+ LLTimer& getEventTimer() { return mEventTimer;}
+};
+
// support for secondlife:///app/appearance SLapps
class LLAppearanceHandler : public LLCommandHandler
{
@@ -147,13 +190,20 @@ class LLUpdateDirtyState: public LLInventoryCallback
{
public:
LLUpdateDirtyState() {}
- virtual ~LLUpdateDirtyState(){ LLAppearanceMgr::getInstance()->updateIsDirty(); }
+ virtual ~LLUpdateDirtyState()
+ {
+ if (LLAppearanceMgr::instanceExists())
+ {
+ LLAppearanceMgr::getInstance()->updateIsDirty();
+ }
+ }
virtual void fire(const LLUUID&) {}
};
-LLUpdateAppearanceOnDestroy::LLUpdateAppearanceOnDestroy():
- mFireCount(0)
+LLUpdateAppearanceOnDestroy::LLUpdateAppearanceOnDestroy(bool update_base_outfit_ordering):
+ mFireCount(0),
+ mUpdateBaseOrder(update_base_outfit_ordering)
{
}
@@ -163,7 +213,7 @@ LLUpdateAppearanceOnDestroy::~LLUpdateAppearanceOnDestroy()
if (!LLApp::isExiting())
{
- LLAppearanceMgr::instance().updateAppearanceFromCOF();
+ LLAppearanceMgr::instance().updateAppearanceFromCOF(mUpdateBaseOrder);
}
}
@@ -171,7 +221,9 @@ void LLUpdateAppearanceOnDestroy::fire(const LLUUID& inv_item)
{
LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(inv_item);
const std::string item_name = item ? item->getName() : "ITEM NOT FOUND";
+#ifndef LL_RELEASE_FOR_DOWNLOAD
llinfos << "callback fired [ name:" << item_name << " UUID:" << inv_item << " count:" << mFireCount << " ] " << llendl;
+#endif
mFireCount++;
}
@@ -186,13 +238,15 @@ struct LLFoundData
const LLUUID& asset_id,
const std::string& name,
const LLAssetType::EType& asset_type,
- const LLWearableType::EType& wearable_type
+ const LLWearableType::EType& wearable_type,
+ const bool is_replacement = false
) :
mItemID(item_id),
mAssetID(asset_id),
mName(name),
mAssetType(asset_type),
mWearableType(wearable_type),
+ mIsReplacement(is_replacement),
mWearable( NULL ) {}
LLUUID mItemID;
@@ -201,6 +255,7 @@ struct LLFoundData
LLAssetType::EType mAssetType;
LLWearableType::EType mWearableType;
LLWearable* mWearable;
+ bool mIsReplacement;
};
@@ -223,8 +278,18 @@ public:
void onWearableAssetFetch(LLWearable *wearable);
void onAllComplete();
-
+
typedef std::list<LLFoundData> found_list_t;
+ found_list_t& getFoundList();
+ void eraseTypeToLink(LLWearableType::EType type);
+ void eraseTypeToRecover(LLWearableType::EType type);
+ void setObjItems(const LLInventoryModel::item_array_t& items);
+ void setGestItems(const LLInventoryModel::item_array_t& items);
+ bool isMostRecent();
+ void handleLateArrivals();
+ void resetTime(F32 timeout);
+
+private:
found_list_t mFoundList;
LLInventoryModel::item_array_t mObjItems;
LLInventoryModel::item_array_t mGestItems;
@@ -234,34 +299,94 @@ public:
S32 mResolved;
LLTimer mWaitTime;
bool mFired;
+ typedef std::set<LLWearableHoldingPattern*> type_set_hp;
+ static type_set_hp sActiveHoldingPatterns;
+ bool mIsMostRecent;
+ std::set<LLWearable*> mLateArrivals;
+ bool mIsAllComplete;
};
+LLWearableHoldingPattern::type_set_hp LLWearableHoldingPattern::sActiveHoldingPatterns;
+
LLWearableHoldingPattern::LLWearableHoldingPattern():
mResolved(0),
- mFired(false)
+ mFired(false),
+ mIsMostRecent(true),
+ mIsAllComplete(false)
{
+ if (sActiveHoldingPatterns.size()>0)
+ {
+ llinfos << "Creating LLWearableHoldingPattern when "
+ << sActiveHoldingPatterns.size()
+ << " other attempts are active."
+ << " Flagging others as invalid."
+ << llendl;
+ for (type_set_hp::iterator it = sActiveHoldingPatterns.begin();
+ it != sActiveHoldingPatterns.end();
+ ++it)
+ {
+ (*it)->mIsMostRecent = false;
+ }
+
+ }
+ sActiveHoldingPatterns.insert(this);
}
LLWearableHoldingPattern::~LLWearableHoldingPattern()
{
+ sActiveHoldingPatterns.erase(this);
+}
+
+bool LLWearableHoldingPattern::isMostRecent()
+{
+ return mIsMostRecent;
+}
+
+LLWearableHoldingPattern::found_list_t& LLWearableHoldingPattern::getFoundList()
+{
+ return mFoundList;
+}
+
+void LLWearableHoldingPattern::eraseTypeToLink(LLWearableType::EType type)
+{
+ mTypesToLink.erase(type);
+}
+
+void LLWearableHoldingPattern::eraseTypeToRecover(LLWearableType::EType type)
+{
+ mTypesToRecover.erase(type);
+}
+
+void LLWearableHoldingPattern::setObjItems(const LLInventoryModel::item_array_t& items)
+{
+ mObjItems = items;
+}
+
+void LLWearableHoldingPattern::setGestItems(const LLInventoryModel::item_array_t& items)
+{
+ mGestItems = items;
}
bool LLWearableHoldingPattern::isFetchCompleted()
{
- return (mResolved >= (S32)mFoundList.size()); // have everything we were waiting for?
+ return (mResolved >= (S32)getFoundList().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;
+ return mWaitTime.hasExpired();
}
void LLWearableHoldingPattern::checkMissingWearables()
{
+ if (!isMostRecent())
+ {
+ llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
+ }
+
std::vector<S32> found_by_type(LLWearableType::WT_COUNT,0);
std::vector<S32> requested_by_type(LLWearableType::WT_COUNT,0);
- for (found_list_t::iterator it = mFoundList.begin(); it != mFoundList.end(); ++it)
+ for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it)
{
LLFoundData &data = *it;
if (data.mWearableType < LLWearableType::WT_COUNT)
@@ -272,7 +397,10 @@ void LLWearableHoldingPattern::checkMissingWearables()
for (S32 type = 0; type < LLWearableType::WT_COUNT; ++type)
{
- llinfos << "type " << type << " requested " << requested_by_type[type] << " found " << found_by_type[type] << llendl;
+ if (requested_by_type[type] > found_by_type[type])
+ {
+ llwarns << "got fewer wearables than requested, type " << type << ": requested " << requested_by_type[type] << ", found " << found_by_type[type] << llendl;
+ }
if (found_by_type[type] > 0)
continue;
if (
@@ -292,7 +420,7 @@ void LLWearableHoldingPattern::checkMissingWearables()
}
}
- mWaitTime.reset();
+ resetTime(60.0F);
if (!pollMissingWearables())
{
doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollMissingWearables,this));
@@ -301,6 +429,11 @@ void LLWearableHoldingPattern::checkMissingWearables()
void LLWearableHoldingPattern::onAllComplete()
{
+ if (!isMostRecent())
+ {
+ llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
+ }
+
// Activate all gestures in this folder
if (mGestItems.count() > 0)
{
@@ -336,16 +469,31 @@ void LLWearableHoldingPattern::onAllComplete()
// Only safe to delete if all wearable callbacks and all missing wearables completed.
delete this;
}
+ else
+ {
+ mIsAllComplete = true;
+ handleLateArrivals();
+ }
}
void LLWearableHoldingPattern::onFetchCompletion()
{
+ if (!isMostRecent())
+ {
+ llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
+ }
+
checkMissingWearables();
}
// Runs as an idle callback until all wearables are fetched (or we time out).
bool LLWearableHoldingPattern::pollFetchCompletion()
{
+ if (!isMostRecent())
+ {
+ llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
+ }
+
bool completed = isFetchCompleted();
bool timed_out = isTimedOut();
bool done = completed || timed_out;
@@ -378,8 +526,13 @@ public:
}
void fire(const LLUUID& item_id)
{
+ if (!mHolder->isMostRecent())
+ {
+ llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
+ }
+
llinfos << "Recovered item link for type " << mType << llendl;
- mHolder->mTypesToLink.erase(mType);
+ mHolder->eraseTypeToLink(mType);
// Add wearable to FoundData for actual wearing
LLViewerInventoryItem *item = gInventory.getItem(item_id);
LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL;
@@ -391,13 +544,14 @@ public:
if (item)
{
LLFoundData found(linked_item->getUUID(),
- linked_item->getAssetUUID(),
- linked_item->getName(),
- linked_item->getType(),
- linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID
- );
+ linked_item->getAssetUUID(),
+ linked_item->getName(),
+ linked_item->getType(),
+ linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID,
+ true // is replacement
+ );
found.mWearable = mWearable;
- mHolder->mFoundList.push_front(found);
+ mHolder->getFoundList().push_front(found);
}
else
{
@@ -426,11 +580,16 @@ public:
}
void fire(const LLUUID& item_id)
{
+ if (!mHolder->isMostRecent())
+ {
+ llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
+ }
+
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);
+ mHolder->eraseTypeToRecover(mType);
llassert(itemp);
if (itemp)
{
@@ -451,6 +610,11 @@ private:
void LLWearableHoldingPattern::recoverMissingWearable(LLWearableType::EType type)
{
+ if (!isMostRecent())
+ {
+ llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
+ }
+
// Try to recover by replacing missing wearable with a new one.
LLNotificationsUtil::add("ReplacedMissingWearable");
lldebugs << "Wearable " << LLWearableType::getTypeLabel(type)
@@ -481,7 +645,7 @@ bool LLWearableHoldingPattern::isMissingCompleted()
void LLWearableHoldingPattern::clearCOFLinksForMissingWearables()
{
- for (found_list_t::iterator it = mFoundList.begin(); it != mFoundList.end(); ++it)
+ for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it)
{
LLFoundData &data = *it;
if ((data.mWearableType < LLWearableType::WT_COUNT) && (!data.mWearable))
@@ -495,34 +659,144 @@ void LLWearableHoldingPattern::clearCOFLinksForMissingWearables()
bool LLWearableHoldingPattern::pollMissingWearables()
{
+ if (!isMostRecent())
+ {
+ llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
+ }
+
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;
+
+ if (!done)
+ {
+ llinfos << "polling missing wearables, waiting for items " << mTypesToRecover.size()
+ << " links " << mTypesToLink.size()
+ << " wearables, timed out " << timed_out
+ << " elapsed " << mWaitTime.getElapsedTimeF32()
+ << " done " << done << llendl;
+ }
if (done)
{
gAgentAvatarp->debugWearablesLoaded();
- clearCOFLinksForMissingWearables();
+
+ // BAP - if we don't call clearCOFLinksForMissingWearables()
+ // here, we won't have to add the link back in later if the
+ // wearable arrives late. This is to avoid corruption of
+ // wearable ordering info. Also has the effect of making
+ // unworn item links visible in the COF under some
+ // circumstances.
+
+ //clearCOFLinksForMissingWearables();
onAllComplete();
}
return done;
}
+// Handle wearables that arrived after the timeout period expired.
+void LLWearableHoldingPattern::handleLateArrivals()
+{
+ // Only safe to run if we have previously finished the missing
+ // wearables and other processing - otherwise we could be in some
+ // intermediate state - but have not been superceded by a later
+ // outfit change request.
+ if (mLateArrivals.size() == 0)
+ {
+ // Nothing to process.
+ return;
+ }
+ if (!isMostRecent())
+ {
+ llwarns << "Late arrivals not handled - outfit change no longer valid" << llendl;
+ }
+ if (!mIsAllComplete)
+ {
+ llwarns << "Late arrivals not handled - in middle of missing wearables processing" << llendl;
+ }
+
+ llinfos << "Need to handle " << mLateArrivals.size() << " late arriving wearables" << llendl;
+
+ // Update mFoundList using late-arriving wearables.
+ std::set<LLWearableType::EType> replaced_types;
+ for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin();
+ iter != getFoundList().end(); ++iter)
+ {
+ LLFoundData& data = *iter;
+ for (std::set<LLWearable*>::iterator wear_it = mLateArrivals.begin();
+ wear_it != mLateArrivals.end();
+ ++wear_it)
+ {
+ LLWearable *wearable = *wear_it;
+
+ if(wearable->getAssetID() == data.mAssetID)
+ {
+ data.mWearable = wearable;
+
+ replaced_types.insert(data.mWearableType);
+
+ // BAP - if we didn't call
+ // clearCOFLinksForMissingWearables() earlier, we
+ // don't need to restore the link here. Fixes
+ // wearable ordering problems.
+
+ // LLAppearanceMgr::instance().addCOFItemLink(data.mItemID,false);
+
+ // BAP failing this means inventory or asset server
+ // are corrupted in a way we don't handle.
+ llassert((data.mWearableType < LLWearableType::WT_COUNT) && (wearable->getType() == data.mWearableType));
+ break;
+ }
+ }
+ }
+
+ // Remove COF links for any default wearables previously used to replace the late arrivals.
+ // All this pussyfooting around with a while loop and explicit
+ // iterator incrementing is to allow removing items from the list
+ // without clobbering the iterator we're using to navigate.
+ LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin();
+ while (iter != getFoundList().end())
+ {
+ LLFoundData& data = *iter;
+
+ // If an item of this type has recently shown up, removed the corresponding replacement wearable from COF.
+ if (data.mWearable && data.mIsReplacement &&
+ replaced_types.find(data.mWearableType) != replaced_types.end())
+ {
+ LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID,false);
+ std::list<LLFoundData>::iterator clobber_ator = iter;
+ ++iter;
+ getFoundList().erase(clobber_ator);
+ }
+ else
+ {
+ ++iter;
+ }
+ }
+
+ // Clear contents of late arrivals.
+ mLateArrivals.clear();
+
+ // Update appearance based on mFoundList
+ LLAppearanceMgr::instance().updateAgentWearables(this, false);
+}
+
+void LLWearableHoldingPattern::resetTime(F32 timeout)
+{
+ mWaitTime.reset();
+ mWaitTime.setTimerExpirySec(timeout);
+}
+
void LLWearableHoldingPattern::onWearableAssetFetch(LLWearable *wearable)
{
- mResolved += 1; // just counting callbacks, not successes.
- llinfos << "onWearableAssetFetch, resolved count " << mResolved << " of requested " << mFoundList.size() << llendl;
- if (wearable)
+ if (!isMostRecent())
{
- llinfos << "wearable found, type " << wearable->getType() << " asset " << wearable->getAssetID() << llendl;
+ llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl;
}
- else
+
+ mResolved += 1; // just counting callbacks, not successes.
+ llinfos << "resolved " << mResolved << "/" << getFoundList().size() << llendl;
+ if (!wearable)
{
llwarns << "no wearable found" << llendl;
}
@@ -530,6 +804,14 @@ void LLWearableHoldingPattern::onWearableAssetFetch(LLWearable *wearable)
if (mFired)
{
llwarns << "called after holder fired" << llendl;
+ if (wearable)
+ {
+ mLateArrivals.insert(wearable);
+ if (mIsAllComplete)
+ {
+ handleLateArrivals();
+ }
+ }
return;
}
@@ -538,16 +820,20 @@ void LLWearableHoldingPattern::onWearableAssetFetch(LLWearable *wearable)
return;
}
- for (LLWearableHoldingPattern::found_list_t::iterator iter = mFoundList.begin();
- iter != mFoundList.end(); ++iter)
+ for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin();
+ iter != getFoundList().end(); ++iter)
{
LLFoundData& data = *iter;
if(wearable->getAssetID() == data.mAssetID)
{
- data.mWearable = wearable;
// Failing this means inventory or asset server are corrupted in a way we don't handle.
- llassert((data.mWearableType < LLWearableType::WT_COUNT) && (wearable->getType() == data.mWearableType));
- break;
+ if ((data.mWearableType >= LLWearableType::WT_COUNT) || (wearable->getType() != data.mWearableType))
+ {
+ llwarns << "recovered wearable but type invalid. inventory wearable type: " << data.mWearableType << " asset wearable type: " << wearable->getType() << llendl;
+ break;
+ }
+
+ data.mWearable = wearable;
}
}
}
@@ -614,6 +900,13 @@ const LLViewerInventoryItem* LLAppearanceMgr::getBaseOutfitLink()
const LLViewerInventoryCategory *cat = item->getLinkedCategory();
if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT)
{
+ const LLUUID parent_id = cat->getParentUUID();
+ LLViewerInventoryCategory* parent_cat = gInventory.getCategory(parent_id);
+ // if base outfit moved to trash it means that we don't have base outfit
+ if (parent_cat != NULL && parent_cat->getPreferredType() == LLFolderType::FT_TRASH)
+ {
+ return NULL;
+ }
return item;
}
}
@@ -652,10 +945,20 @@ const LLUUID LLAppearanceMgr::getBaseOutfitUUID()
return outfit_cat->getUUID();
}
-bool LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, bool do_update, bool replace)
+bool LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, bool do_update, bool replace, LLPointer<LLInventoryCallback> cb)
{
if (item_id_to_wear.isNull()) return false;
+ // *TODO: issue with multi-wearable should be fixed:
+ // in this case this method will be called N times - loading started for each item
+ // and than N times will be called - loading completed for each item.
+ // That means subscribers will be notified that loading is done after first item in a batch is worn.
+ // (loading indicator disappears for example before all selected items are worn)
+ // Have not fix this issue for 2.1 because of stability reason. EXT-7777.
+
+ // Disabled for now because it is *not* acceptable to call updateAppearanceFromCOF() multiple times
+// gAgentWearables.notifyLoadingStarted();
+
LLViewerInventoryItem* item_to_wear = gInventory.getItem(item_id_to_wear);
if (!item_to_wear) return false;
@@ -674,6 +977,10 @@ bool LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, bool do_up
LLNotificationsUtil::add("CannotWearTrash");
return false;
}
+ else if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), LLAppearanceMgr::instance().getCOF())) // EXT-84911
+ {
+ return false;
+ }
switch (item_to_wear->getType())
{
@@ -686,24 +993,17 @@ bool LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, bool do_up
{
removeCOFItemLinks(gAgentWearables.getWearableItemID(item_to_wear->getWearableType(), wearable_count-1), false);
}
+ addCOFItemLink(item_to_wear, do_update, cb);
}
+ break;
case LLAssetType::AT_BODYPART:
- // Don't wear anything until initial wearables are loaded, can
- // destroy clothing items.
- if (!gAgentWearables.areWearablesLoaded())
- {
- LLNotificationsUtil::add("CanNotChangeAppearanceUntilLoaded");
- return false;
- }
-
+ // TODO: investigate wearables may not be loaded at this point EXT-8231
+
// Remove the existing wearables of the same type.
// Remove existing body parts anyway because we must not be able to wear e.g. two skins.
- if (item_to_wear->getType() == LLAssetType::AT_BODYPART)
- {
- removeCOFLinksOfType(item_to_wear->getWearableType(), false);
- }
+ removeCOFLinksOfType(item_to_wear->getWearableType(), false);
- addCOFItemLink(item_to_wear, do_update);
+ addCOFItemLink(item_to_wear, do_update, cb);
break;
case LLAssetType::AT_OBJECT:
rez_attachment(item_to_wear, NULL);
@@ -762,6 +1062,27 @@ void LLAppearanceMgr::onOutfitRename(const LLSD& notification, const LLSD& respo
}
}
+void LLAppearanceMgr::setOutfitLocked(bool locked)
+{
+ if (mOutfitLocked == locked)
+ {
+ return;
+ }
+
+ mOutfitLocked = locked;
+ if (locked)
+ {
+ mUnlockOutfitTimer->reset();
+ mUnlockOutfitTimer->start();
+ }
+ else
+ {
+ mUnlockOutfitTimer->stop();
+ }
+
+ LLOutfitObserver::instance().notifyOutfitLockChanged();
+}
+
void LLAppearanceMgr::addCategoryToCurrentOutfit(const LLUUID& cat_id)
{
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
@@ -772,7 +1093,7 @@ void LLAppearanceMgr::takeOffOutfit(const LLUUID& cat_id)
{
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
- LLFindWorn collector;
+ LLFindWearablesEx collector(/*is_worn=*/ true, /*include_body_parts=*/ false);
gInventory.collectDescendentsIf(cat_id, cats, items, FALSE, collector);
@@ -795,6 +1116,7 @@ void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& ds
llwarns << "folder not found for src " << src_id.asString() << llendl;
return;
}
+ llinfos << "starting, src_id " << src_id << " name " << src_cat->getName() << " dst_id " << dst_id << llendl;
LLUUID parent_id = dst_id;
if(parent_id.isNull())
{
@@ -815,6 +1137,7 @@ void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LL
LLInventoryModel::cat_array_t* cats;
LLInventoryModel::item_array_t* items;
gInventory.getDirectDescendentsOf(src_id, cats, items);
+ llinfos << "copying " << items->count() << " items" << llendl;
for (LLInventoryModel::item_array_t::const_iterator iter = items->begin();
iter != items->end();
++iter)
@@ -854,6 +1177,7 @@ void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LL
case LLAssetType::AT_BODYPART:
case LLAssetType::AT_GESTURE:
{
+ llinfos << "copying inventory item " << item->getName() << llendl;
copy_inventory_item(gAgent.getID(),
item->getPermissions().getOwner(),
item->getUUID(),
@@ -914,18 +1238,10 @@ bool LLAppearanceMgr::getCanRemoveOutfit(const LLUUID& outfit_cat_id)
return false;
}
- // Check if the folder contains worn items.
- LLInventoryModel::cat_array_t cats;
- LLInventoryModel::item_array_t items;
- LLFindWorn filter_worn;
- gInventory.collectDescendentsIf(outfit_cat_id, cats, items, false, filter_worn);
- if (!items.empty())
- {
- return false;
- }
-
// Check for the folder's non-removable descendants.
LLFindNonRemovableObjects filter_non_removable;
+ LLInventoryModel::cat_array_t cats;
+ LLInventoryModel::item_array_t items;
LLInventoryModel::item_array_t::const_iterator it;
gInventory.collectDescendentsIf(outfit_cat_id, cats, items, false, filter_non_removable);
if (!cats.empty() || !items.empty())
@@ -936,6 +1252,65 @@ bool LLAppearanceMgr::getCanRemoveOutfit(const LLUUID& outfit_cat_id)
return true;
}
+// static
+bool LLAppearanceMgr::getCanRemoveFromCOF(const LLUUID& outfit_cat_id)
+{
+ LLInventoryModel::cat_array_t cats;
+ LLInventoryModel::item_array_t items;
+ LLFindWearablesEx is_worn(/*is_worn=*/ true, /*include_body_parts=*/ false);
+ gInventory.collectDescendentsIf(outfit_cat_id,
+ cats,
+ items,
+ LLInventoryModel::EXCLUDE_TRASH,
+ is_worn);
+ return items.size() > 0;
+}
+
+// static
+bool LLAppearanceMgr::getCanAddToCOF(const LLUUID& outfit_cat_id)
+{
+ if (gAgentWearables.isCOFChangeInProgress())
+ {
+ return false;
+ }
+
+ LLInventoryModel::cat_array_t cats;
+ LLInventoryModel::item_array_t items;
+ LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false);
+ gInventory.collectDescendentsIf(outfit_cat_id,
+ cats,
+ items,
+ LLInventoryModel::EXCLUDE_TRASH,
+ not_worn);
+ return items.size() > 0;
+}
+
+bool LLAppearanceMgr::getCanReplaceCOF(const LLUUID& outfit_cat_id)
+{
+ // Don't allow wearing anything while we're changing appearance.
+ if (gAgentWearables.isCOFChangeInProgress())
+ {
+ return false;
+ }
+
+ // Check whether it's the base outfit.
+ if (outfit_cat_id.isNull() || outfit_cat_id == getBaseOutfitUUID())
+ {
+ return false;
+ }
+
+ // Check whether the outfit contains any non-worn wearables.
+ LLInventoryModel::cat_array_t cats;
+ LLInventoryModel::item_array_t items;
+ LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ true);
+ gInventory.collectDescendentsIf(outfit_cat_id,
+ cats,
+ items,
+ LLInventoryModel::EXCLUDE_TRASH,
+ not_worn);
+ return items.size() > 0;
+}
+
void LLAppearanceMgr::purgeBaseOutfitLink(const LLUUID& category)
{
LLInventoryModel::cat_array_t cats;
@@ -1018,8 +1393,9 @@ void LLAppearanceMgr::linkAll(const LLUUID& cat_uuid,
const LLViewerInventoryCategory *cat = gInventory.getCategory(cat_uuid);
const std::string cat_name = cat ? cat->getName() : "CAT NOT FOUND";
-
- llinfos << "Linking Item [ name:" << item->getName() << " UUID:" << item->getUUID() << " ] to Category [ name:" << cat_name << " UUID:" << cat_uuid << " ] " << llendl; // Seraph remove for 2.1
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ llinfos << "Linking Item [ name:" << item->getName() << " UUID:" << item->getUUID() << " ] to Category [ name:" << cat_name << " UUID:" << cat_uuid << " ] " << llendl;
+#endif
}
}
@@ -1049,9 +1425,12 @@ void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append)
// - Body parts: always include COF contents as a fallback in case any
// required parts are missing.
+ // Preserve body parts from COF if appending.
LLInventoryModel::item_array_t body_items;
getDescendentsOfAssetType(cof, body_items, LLAssetType::AT_BODYPART, false);
getDescendentsOfAssetType(category, body_items, LLAssetType::AT_BODYPART, false);
+ if (append)
+ reverse(body_items.begin(), body_items.end());
// Reduce body items to max of one per type.
removeDuplicateItems(body_items);
filterWearableItems(body_items, 1);
@@ -1086,15 +1465,26 @@ void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append)
// Create links to new COF contents.
llinfos << "creating LLUpdateAppearanceOnDestroy" << llendl;
- LLPointer<LLInventoryCallback> link_waiter = new LLUpdateAppearanceOnDestroy;
+ LLPointer<LLInventoryCallback> link_waiter = new LLUpdateAppearanceOnDestroy(!append);
- llinfos << "Linking body items" << llendl; // Seraph remove for 2.1
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ llinfos << "Linking body items" << llendl;
+#endif
linkAll(cof, body_items, link_waiter);
- llinfos << "Linking wear items" << llendl; // Seraph remove for 2.1
+
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ llinfos << "Linking wear items" << llendl;
+#endif
linkAll(cof, wear_items, link_waiter);
- llinfos << "Linking obj items" << llendl; // Seraph remove for 2.1
+
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ llinfos << "Linking obj items" << llendl;
+#endif
linkAll(cof, obj_items, link_waiter);
- llinfos << "Linking gesture items" << llendl; // Seraph remove for 2.1
+
+#ifndef LL_RELEASE_FOR_DOWNLOAD
+ llinfos << "Linking gesture items" << llendl;
+#endif
linkAll(cof, gest_items, link_waiter);
// Add link to outfit if category is an outfit.
@@ -1139,12 +1529,11 @@ void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder, boo
LLInventoryItem::item_array_t items;
LLDynamicArray< LLWearable* > wearables;
- // For each wearable type, find the first instance in the category
- // that we recursed through.
+ // For each wearable type, find the wearables of that type.
for( S32 i = 0; i < LLWearableType::WT_COUNT; i++ )
{
- for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->mFoundList.begin();
- iter != holder->mFoundList.end(); ++iter)
+ for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->getFoundList().begin();
+ iter != holder->getFoundList().end(); ++iter)
{
LLFoundData& data = *iter;
LLWearable* wearable = data.mWearable;
@@ -1196,19 +1585,92 @@ bool sort_by_description(const LLInventoryItem* item1, const LLInventoryItem* it
return item1->LLInventoryItem::getDescription() < item2->LLInventoryItem::getDescription();
}
-void LLAppearanceMgr::updateAppearanceFromCOF()
+void item_array_diff(LLInventoryModel::item_array_t& full_list,
+ LLInventoryModel::item_array_t& keep_list,
+ LLInventoryModel::item_array_t& kill_list)
+
+{
+ for (LLInventoryModel::item_array_t::iterator it = full_list.begin();
+ it != full_list.end();
+ ++it)
+ {
+ LLViewerInventoryItem *item = *it;
+ if (keep_list.find(item) < 0) // Why on earth does LLDynamicArray need to redefine find()?
+ {
+ kill_list.push_back(item);
+ }
+ }
+}
+
+void LLAppearanceMgr::enforceItemCountLimits()
{
+ S32 purge_count = 0;
+
+ LLInventoryModel::item_array_t body_items;
+ getDescendentsOfAssetType(getCOF(), body_items, LLAssetType::AT_BODYPART, false);
+ LLInventoryModel::item_array_t curr_body_items = body_items;
+ removeDuplicateItems(body_items);
+ filterWearableItems(body_items, 1);
+ LLInventoryModel::item_array_t kill_body_items;
+ item_array_diff(curr_body_items,body_items,kill_body_items);
+ for (LLInventoryModel::item_array_t::iterator it = kill_body_items.begin();
+ it != kill_body_items.end();
+ ++it)
+ {
+ LLViewerInventoryItem *item = *it;
+ llinfos << "purging dup body part " << item->getName() << llendl;
+ gInventory.purgeObject(item->getUUID());
+ purge_count++;
+ }
+
+ LLInventoryModel::item_array_t wear_items;
+ getDescendentsOfAssetType(getCOF(), wear_items, LLAssetType::AT_CLOTHING, false);
+ LLInventoryModel::item_array_t curr_wear_items = wear_items;
+ removeDuplicateItems(wear_items);
+ filterWearableItems(wear_items, LLAgentWearables::MAX_CLOTHING_PER_TYPE);
+ LLInventoryModel::item_array_t kill_wear_items;
+ item_array_diff(curr_wear_items,wear_items,kill_wear_items);
+ for (LLInventoryModel::item_array_t::iterator it = kill_wear_items.begin();
+ it != kill_wear_items.end();
+ ++it)
+ {
+ LLViewerInventoryItem *item = *it;
+ llinfos << "purging excess clothing item " << item->getName() << llendl;
+ gInventory.purgeObject(item->getUUID());
+ purge_count++;
+ }
+
+ if (purge_count>0)
+ {
+ gInventory.notifyObservers();
+ }
+}
+
+void LLAppearanceMgr::updateAppearanceFromCOF(bool update_base_outfit_ordering)
+{
+ if (mIsInUpdateAppearanceFromCOF)
+ {
+ llwarns << "Called updateAppearanceFromCOF inside updateAppearanceFromCOF, skipping" << llendl;
+ return;
+ }
+
+ BoolSetter setIsInUpdateAppearanceFromCOF(mIsInUpdateAppearanceFromCOF);
+
+ llinfos << "starting" << llendl;
+
//checking integrity of the COF in terms of ordering of wearables,
//checking and updating links' descriptions of wearables in the COF (before analyzed for "dirty" state)
- updateClothingOrderingInfo();
+ updateClothingOrderingInfo(LLUUID::null, update_base_outfit_ordering);
+ // Remove duplicate or excess wearables. Should normally be enforced at the UI level, but
+ // this should catch anything that gets through.
+ enforceItemCountLimits();
+
// 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");
+ //dumpCat(getCOF(),"COF, start");
bool follow_folder_links = true;
LLUUID current_outfit_id = getCOF();
@@ -1224,6 +1686,9 @@ void LLAppearanceMgr::updateAppearanceFromCOF()
remove_non_link_items(obj_items);
remove_non_link_items(gest_items);
+ dumpItemArray(wear_items,"asset_dump: wear_item");
+ dumpItemArray(obj_items,"asset_dump: obj_item");
+
if(!wear_items.count())
{
LLNotificationsUtil::add("CouldNotPutOnOutfit");
@@ -1236,8 +1701,8 @@ void LLAppearanceMgr::updateAppearanceFromCOF()
LLWearableHoldingPattern* holder = new LLWearableHoldingPattern;
- holder->mObjItems = obj_items;
- holder->mGestItems = gest_items;
+ holder->setObjItems(obj_items);
+ holder->setGestItems(gest_items);
// Note: can't do normal iteration, because if all the
// wearables can be resolved immediately, then the
@@ -1248,6 +1713,12 @@ void LLAppearanceMgr::updateAppearanceFromCOF()
{
LLViewerInventoryItem *item = wear_items.get(i);
LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL;
+
+ // Fault injection: use debug setting to test asset
+ // fetch failures (should be replaced by new defaults in
+ // lost&found).
+ U32 skip_type = gSavedSettings.getU32("ForceAssetFail");
+
if (item && item->getIsLinkType() && linked_item)
{
LLFoundData found(linked_item->getUUID(),
@@ -1257,18 +1728,12 @@ void LLAppearanceMgr::updateAppearanceFromCOF()
linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::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 == LLWearableType::WT_SHAPE || found.mWearableType == LLWearableType::WT_JACKET)
+ if (skip_type != LLWearableType::WT_INVALID && skip_type == found.mWearableType)
{
found.mAssetID.generate(); // Replace with new UUID, guaranteed not to exist in DB
-
}
-#endif
//pushing back, not front, to preserve order of wearables for LLAgentWearables
- holder->mFoundList.push_back(found);
+ holder->getFoundList().push_back(found);
}
else
{
@@ -1283,12 +1748,12 @@ void LLAppearanceMgr::updateAppearanceFromCOF()
}
}
- for (LLWearableHoldingPattern::found_list_t::iterator it = holder->mFoundList.begin();
- it != holder->mFoundList.end(); ++it)
+ for (LLWearableHoldingPattern::found_list_t::iterator it = holder->getFoundList().begin();
+ it != holder->getFoundList().end(); ++it)
{
LLFoundData& found = *it;
- llinfos << "waiting for onWearableAssetFetch callback, asset " << found.mAssetID.asString() << llendl;
+ lldebugs << "waiting for onWearableAssetFetch callback, asset " << found.mAssetID.asString() << llendl;
// Fetch the wearables about to be worn.
LLWearableList::instance().getAsset(found.mAssetID,
@@ -1299,6 +1764,7 @@ void LLAppearanceMgr::updateAppearanceFromCOF()
}
+ holder->resetTime(gSavedSettings.getF32("MaxWearableWaitTime"));
if (!holder->pollFetchCompletion())
{
doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollFetchCompletion,holder));
@@ -1359,12 +1825,14 @@ void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool
{
if(!category) return;
+ gAgentWearables.notifyLoadingStarted();
+
llinfos << "wearInventoryCategory( " << category->getName()
<< " )" << llendl;
- callAfterCategoryFetch(category->getUUID(),boost::bind(&LLAppearanceMgr::wearCategoryFinal,
- &LLAppearanceMgr::instance(),
- category->getUUID(), copy, append));
+ callAfterCategoryFetch(category->getUUID(), boost::bind(&LLAppearanceMgr::wearCategoryFinal,
+ &LLAppearanceMgr::instance(),
+ category->getUUID(), copy, append));
}
void LLAppearanceMgr::wearCategoryFinal(LLUUID& cat_id, bool copy_items, bool append)
@@ -1520,9 +1988,10 @@ bool areMatchingWearables(const LLViewerInventoryItem *a, const LLViewerInventor
class LLDeferredCOFLinkObserver: public LLInventoryObserver
{
public:
- LLDeferredCOFLinkObserver(const LLUUID& item_id, bool do_update):
+ LLDeferredCOFLinkObserver(const LLUUID& item_id, bool do_update, LLPointer<LLInventoryCallback> cb = NULL):
mItemID(item_id),
- mDoUpdate(do_update)
+ mDoUpdate(do_update),
+ mCallback(cb)
{
}
@@ -1536,7 +2005,7 @@ public:
if (item)
{
gInventory.removeObserver(this);
- LLAppearanceMgr::instance().addCOFItemLink(item,mDoUpdate);
+ LLAppearanceMgr::instance().addCOFItemLink(item,mDoUpdate,mCallback);
delete this;
}
}
@@ -1544,26 +2013,27 @@ public:
private:
const LLUUID mItemID;
bool mDoUpdate;
+ LLPointer<LLInventoryCallback> mCallback;
};
// BAP - note that this runs asynchronously if the item is not already loaded from inventory.
// Dangerous if caller assumes link will exist after calling the function.
-void LLAppearanceMgr::addCOFItemLink(const LLUUID &item_id, bool do_update )
+void LLAppearanceMgr::addCOFItemLink(const LLUUID &item_id, bool do_update, LLPointer<LLInventoryCallback> cb)
{
const LLInventoryItem *item = gInventory.getItem(item_id);
if (!item)
{
- LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, do_update);
+ LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, do_update, cb);
gInventory.addObserver(observer);
}
else
{
- addCOFItemLink(item, do_update);
+ addCOFItemLink(item, do_update, cb);
}
}
-void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update )
+void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update, LLPointer<LLInventoryCallback> cb)
{
const LLViewerInventoryItem *vitem = dynamic_cast<const LLViewerInventoryItem*>(item);
if (!vitem)
@@ -1624,7 +2094,10 @@ void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update
}
else
{
- LLPointer<LLInventoryCallback> cb = do_update ? new ModifiedCOFCallback : 0;
+ if(do_update && cb.isNull())
+ {
+ cb = new ModifiedCOFCallback;
+ }
const std::string description = vitem->getIsLinkType() ? vitem->getDescription() : "";
link_inventory_item( gAgent.getID(),
vitem->getLinkedUUID(),
@@ -1735,17 +2208,19 @@ void LLAppearanceMgr::updateIsDirty()
}
else
{
+ LLIsOfAssetType collector = LLIsOfAssetType(LLAssetType::AT_LINK);
+
LLInventoryModel::cat_array_t cof_cats;
LLInventoryModel::item_array_t cof_items;
- gInventory.collectDescendents(cof, cof_cats, cof_items,
- LLInventoryModel::EXCLUDE_TRASH);
+ gInventory.collectDescendentsIf(cof, cof_cats, cof_items,
+ LLInventoryModel::EXCLUDE_TRASH, collector);
LLInventoryModel::cat_array_t outfit_cats;
LLInventoryModel::item_array_t outfit_items;
- gInventory.collectDescendents(base_outfit, outfit_cats, outfit_items,
- LLInventoryModel::EXCLUDE_TRASH);
+ gInventory.collectDescendentsIf(base_outfit, outfit_cats, outfit_items,
+ LLInventoryModel::EXCLUDE_TRASH, collector);
- if(outfit_items.count() != cof_items.count() -1)
+ if(outfit_items.count() != cof_items.count())
{
// Current outfit folder should have one more item than the outfit folder.
// this one item is the link back to the outfit folder itself.
@@ -1753,16 +2228,6 @@ void LLAppearanceMgr::updateIsDirty()
return;
}
- //getting rid of base outfit folder link to simplify comparison
- for (LLInventoryModel::item_array_t::iterator it = cof_items.begin(); it != cof_items.end(); ++it)
- {
- if (*it == base_outfit_item)
- {
- cof_items.erase(it);
- break;
- }
- }
-
//"dirty" - also means a difference in linked UUIDs and/or a difference in wearables order (links' descriptions)
std::sort(cof_items.begin(), cof_items.end(), sort_by_linked_uuid);
std::sort(outfit_items.begin(), outfit_items.end(), sort_by_linked_uuid);
@@ -1773,6 +2238,7 @@ void LLAppearanceMgr::updateIsDirty()
LLViewerInventoryItem *item2 = outfit_items.get(i);
if (item1->getLinkedUUID() != item2->getLinkedUUID() ||
+ item1->getName() != item2->getName() ||
item1->LLInventoryItem::getDescription() != item2->LLInventoryItem::getDescription())
{
mOutfitIsDirty = true;
@@ -1810,6 +2276,16 @@ void LLAppearanceMgr::onFirstFullyVisible()
bool LLAppearanceMgr::updateBaseOutfit()
{
+ if (isOutfitLocked())
+ {
+ // don't allow modify locked outfit
+ llassert(!isOutfitLocked());
+ return false;
+ }
+ setOutfitLocked(true);
+
+ gAgentWearables.notifyLoadingStarted();
+
const LLUUID base_outfit_id = getBaseOutfitUUID();
if (base_outfit_id.isNull()) return false;
@@ -1890,11 +2366,19 @@ struct WearablesOrderComparator
U32 mControlSize;
};
-void LLAppearanceMgr::updateClothingOrderingInfo(LLUUID cat_id)
+void LLAppearanceMgr::updateClothingOrderingInfo(LLUUID cat_id, bool update_base_outfit_ordering)
{
if (cat_id.isNull())
{
cat_id = getCOF();
+ if (update_base_outfit_ordering)
+ {
+ const LLUUID base_outfit_id = getBaseOutfitUUID();
+ if (base_outfit_id.notNull())
+ {
+ updateClothingOrderingInfo(base_outfit_id,false);
+ }
+ }
}
// COF is processed if cat_id is not specified
@@ -1927,6 +2411,7 @@ void LLAppearanceMgr::updateClothingOrderingInfo(LLUUID cat_id)
item->setComplete(TRUE);
item->updateServer(FALSE);
gInventory.updateItem(item);
+
inventory_changed = true;
}
}
@@ -1941,28 +2426,28 @@ void LLAppearanceMgr::updateClothingOrderingInfo(LLUUID cat_id)
class LLShowCreatedOutfit: public LLInventoryCallback
{
public:
- LLShowCreatedOutfit(LLUUID& folder_id): mFolderID(folder_id)
+ LLShowCreatedOutfit(LLUUID& folder_id, bool show_panel = true): mFolderID(folder_id), mShowPanel(show_panel)
{}
virtual ~LLShowCreatedOutfit()
{
LLSD key;
- LLSideTray::getInstance()->showPanel("panel_outfits_inventory", key);
- LLPanelOutfitsInventory *outfit_panel =
- dynamic_cast<LLPanelOutfitsInventory*>(LLSideTray::getInstance()->getPanel("panel_outfits_inventory"));
- if (outfit_panel)
+
+ //EXT-7727. For new accounts LLShowCreatedOutfit is created during login process
+ // add may be processed after login process is finished
+ if (mShowPanel)
{
- outfit_panel->getRootFolder()->clearSelection();
- outfit_panel->getRootFolder()->setSelectionByID(mFolderID, TRUE);
+ LLSideTray::getInstance()->showPanel("panel_outfits_inventory", key);
}
-
- LLAccordionCtrlTab* tab_outfits = outfit_panel ? outfit_panel->findChild<LLAccordionCtrlTab>("tab_outfits") : 0;
- if (tab_outfits && !tab_outfits->getDisplayChildren())
+ LLOutfitsList *outfits_list =
+ dynamic_cast<LLOutfitsList*>(LLSideTray::getInstance()->getPanel("outfitslist_tab"));
+ if (outfits_list)
{
- tab_outfits->changeOpenClose(tab_outfits->getDisplayChildren());
+ outfits_list->setSelectedOutfitByUUID(mFolderID);
}
LLAppearanceMgr::getInstance()->updateIsDirty();
+ gAgentWearables.notifyLoadingFinished(); // New outfit is saved.
LLAppearanceMgr::getInstance()->updatePanelOutfitName("");
}
@@ -1971,12 +2456,15 @@ public:
private:
LLUUID mFolderID;
+ bool mShowPanel;
};
-LLUUID LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name)
+LLUUID LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name, bool show_panel)
{
if (!isAgentAvatarValid()) return LLUUID::null;
+ gAgentWearables.notifyLoadingStarted();
+
// First, make a folder in the My Outfits directory.
const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
LLUUID folder_id = gInventory.createNewCategory(
@@ -1986,7 +2474,7 @@ LLUUID LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name)
updateClothingOrderingInfo();
- LLPointer<LLInventoryCallback> cb = new LLShowCreatedOutfit(folder_id);
+ LLPointer<LLInventoryCallback> cb = new LLShowCreatedOutfit(folder_id,show_panel);
shallowCopyCategoryContents(getCOF(),folder_id, cb);
createBaseOutfitLink(folder_id, cb);
@@ -2034,6 +2522,14 @@ void LLAppearanceMgr::removeItemFromAvatar(const LLUUID& id_to_remove)
}
default: break;
}
+
+ // *HACK: Force to remove garbage from COF.
+ // Unworn links or objects can't be processed by existed removing functionality
+ // since it is not designed for such cases. As example attachment object can't be removed
+ // since sever don't sends message _PREHASH_KillObject in that case.
+ // Also we can't check is link was successfully removed from COF since in case
+ // deleting attachment link removing performs asynchronously in process_kill_object callback.
+ removeCOFItemLinks(id_to_remove,false);
}
bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_body)
@@ -2080,7 +2576,7 @@ bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_b
bool result = false;
if (result = gAgentWearables.moveWearable(item, closer_to_body))
{
- gAgentAvatarp->wearableUpdated(item->getWearableType(), TRUE);
+ gAgentAvatarp->wearableUpdated(item->getWearableType(), FALSE);
}
setOutfitDirty(true);
@@ -2125,19 +2621,33 @@ void LLAppearanceMgr::dumpCat(const LLUUID& cat_id, const std::string& msg)
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;
+ LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL;
+ LLUUID asset_id;
+ if (linked_item)
+ {
+ asset_id = linked_item->getAssetUUID();
+ }
+ llinfos << msg << " " << i <<" " << (item ? item->getName() : "(nullitem)") << " " << asset_id.asString() << llendl;
}
llinfos << llendl;
}
LLAppearanceMgr::LLAppearanceMgr():
mAttachmentInvLinkEnabled(false),
- mOutfitIsDirty(false)
+ mOutfitIsDirty(false),
+ mIsInUpdateAppearanceFromCOF(false)
{
+ LLOutfitObserver& outfit_observer = LLOutfitObserver::instance();
+
+ // unlock outfit on save operation completed
+ outfit_observer.addCOFSavedCallback(boost::bind(
+ &LLAppearanceMgr::setOutfitLocked, this, false));
+
+ mUnlockOutfitTimer.reset(new LLOutfitUnLockTimer(gSavedSettings.getS32(
+ "OutfitOperationsTimeout")));
}
LLAppearanceMgr::~LLAppearanceMgr()
@@ -2175,6 +2685,9 @@ void LLAppearanceMgr::registerAttachment(const LLUUID& item_id)
if (mAttachmentInvLinkEnabled)
{
+ // we have to pass do_update = true to call LLAppearanceMgr::updateAppearanceFromCOF.
+ // it will trigger gAgentWariables.notifyLoadingFinished()
+ // But it is not acceptable solution. See EXT-7777
LLAppearanceMgr::addCOFItemLink(item_id, false); // Add COF link for item.
}
else
@@ -2215,6 +2728,21 @@ BOOL LLAppearanceMgr::getIsInCOF(const LLUUID& obj_id) const
return gInventory.isObjectDescendentOf(obj_id, getCOF());
}
+// static
+bool LLAppearanceMgr::isLinkInCOF(const LLUUID& obj_id)
+{
+ LLInventoryModel::cat_array_t cats;
+ LLInventoryModel::item_array_t items;
+ LLLinkedItemIDMatches find_links(gInventory.getLinkedItemID(obj_id));
+ gInventory.collectDescendentsIf(LLAppearanceMgr::instance().getCOF(),
+ cats,
+ items,
+ LLInventoryModel::EXCLUDE_TRASH,
+ find_links);
+
+ return !items.empty();
+}
+
BOOL LLAppearanceMgr::getIsProtectedCOFItem(const LLUUID& obj_id) const
{
if (!getIsInCOF(obj_id)) return FALSE;
@@ -2242,3 +2770,192 @@ BOOL LLAppearanceMgr::getIsProtectedCOFItem(const LLUUID& obj_id) const
return FALSE;
*/
}
+
+// Shim class to allow arbitrary boost::bind
+// expressions to be run as one-time idle callbacks.
+//
+// TODO: rework idle function spec to take a boost::function in the first place.
+class OnIdleCallbackOneTime
+{
+public:
+ OnIdleCallbackOneTime(nullary_func_t callable):
+ mCallable(callable)
+ {
+ }
+ static void onIdle(void *data)
+ {
+ gIdleCallbacks.deleteFunction(onIdle, data);
+ OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data);
+ self->call();
+ delete self;
+ }
+ void call()
+ {
+ mCallable();
+ }
+private:
+ nullary_func_t mCallable;
+};
+
+void doOnIdleOneTime(nullary_func_t callable)
+{
+ OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable);
+ gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor);
+}
+
+// Shim class to allow generic boost functions to be run as
+// recurring idle callbacks. Callable should return true when done,
+// false to continue getting called.
+//
+// TODO: rework idle function spec to take a boost::function in the first place.
+class OnIdleCallbackRepeating
+{
+public:
+ OnIdleCallbackRepeating(bool_func_t callable):
+ mCallable(callable)
+ {
+ }
+ // Will keep getting called until the callable returns true.
+ static void onIdle(void *data)
+ {
+ OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data);
+ bool done = self->call();
+ if (done)
+ {
+ gIdleCallbacks.deleteFunction(onIdle, data);
+ delete self;
+ }
+ }
+ bool call()
+ {
+ return mCallable();
+ }
+private:
+ bool_func_t mCallable;
+};
+
+void doOnIdleRepeating(bool_func_t callable)
+{
+ OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable);
+ gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor);
+}
+
+class CallAfterCategoryFetchStage2: public LLInventoryFetchItemsObserver
+{
+public:
+ CallAfterCategoryFetchStage2(const uuid_vec_t& ids,
+ nullary_func_t callable) :
+ LLInventoryFetchItemsObserver(ids),
+ mCallable(callable)
+ {
+ }
+ ~CallAfterCategoryFetchStage2()
+ {
+ }
+ virtual void done()
+ {
+ llinfos << this << " done with incomplete " << mIncomplete.size()
+ << " complete " << mComplete.size() << " calling callable" << llendl;
+
+ gInventory.removeObserver(this);
+ doOnIdleOneTime(mCallable);
+ delete this;
+ }
+protected:
+ nullary_func_t mCallable;
+};
+
+class CallAfterCategoryFetchStage1: public LLInventoryFetchDescendentsObserver
+{
+public:
+ CallAfterCategoryFetchStage1(const LLUUID& cat_id, nullary_func_t callable) :
+ LLInventoryFetchDescendentsObserver(cat_id),
+ mCallable(callable)
+ {
+ }
+ ~CallAfterCategoryFetchStage1()
+ {
+ }
+ virtual void 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(mComplete.front(),
+ cat_array,
+ item_array,
+ LLInventoryModel::EXCLUDE_TRASH);
+ S32 count = item_array.count();
+ if(!count)
+ {
+ llwarns << "Nothing fetched in category " << mComplete.front()
+ << llendl;
+ //dec_busy_count();
+ gInventory.removeObserver(this);
+
+ // lets notify observers that loading is finished.
+ gAgentWearables.notifyLoadingFinished();
+ delete this;
+ return;
+ }
+
+ llinfos << "stage1 got " << item_array.count() << " items, passing to stage2 " << llendl;
+ uuid_vec_t ids;
+ for(S32 i = 0; i < count; ++i)
+ {
+ ids.push_back(item_array.get(i)->getUUID());
+ }
+
+ gInventory.removeObserver(this);
+
+ // do the fetch
+ CallAfterCategoryFetchStage2 *stage2 = new CallAfterCategoryFetchStage2(ids, mCallable);
+ stage2->startFetch();
+ if(stage2->isFinished())
+ {
+ // everything is already here - call done.
+ stage2->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(stage2);
+ }
+ delete this;
+ }
+protected:
+ nullary_func_t mCallable;
+};
+
+void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb)
+{
+ CallAfterCategoryFetchStage1 *stage1 = new CallAfterCategoryFetchStage1(cat_id, cb);
+ stage1->startFetch();
+ if (stage1->isFinished())
+ {
+ stage1->done();
+ }
+ else
+ {
+ gInventory.addObserver(stage1);
+ }
+}
+
+void wear_multiple(const uuid_vec_t& ids, bool replace)
+{
+ LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy;
+
+ bool first = true;
+ uuid_vec_t::const_iterator it;
+ for (it = ids.begin(); it != ids.end(); ++it)
+ {
+ // if replace is requested, the first item worn will replace the current top
+ // item, and others will be added.
+ LLAppearanceMgr::instance().wearItemOnAvatar(*it,false,first && replace,cb);
+ first = false;
+ }
+}
+