From 50eb27a04d193ef145f8879e12d8a82089a66891 Mon Sep 17 00:00:00 2001
From: Alexei Arabadji <aarabadji@productengine.com>
Date: Tue, 8 Jun 2010 15:36:48 +0300
Subject: [PATCH] EXT-7644 FIXED Implemented functionality for locking outfit
 modification controls and used it for 'save outfit' action. 1 Added
 functionality for locking outfit in LLAppearanceMgr. Outfit should be locked
 when outfit related operation is started(now it is used for updateBaseOutfit)
 and unlocked when operation completed or timeout is exceeded. 2 Added outfit
 saved and outfit lock changed signals to LLOutfitObserver. 3 Updated
 LLPanelOutfitsInventory and LLPanelOutfitEdit with functionality of
 controlling 'save outfit' controls state('save outfit' controls should be
 enabled only if outfit isn't locked and outfit is dirty). 4 Renamed action
 label of method LLPanelOutfitsInventory::isActionEnabled "make_outfit" to
 "save_outfit".

--HG--
branch : product-engine
---
 indra/newview/app_settings/settings.xml   | 11 ++++
 indra/newview/llappearancemgr.cpp         | 67 +++++++++++++++++++++++
 indra/newview/llappearancemgr.h           | 14 +++++
 indra/newview/lloutfitobserver.cpp        | 15 ++++-
 indra/newview/lloutfitobserver.h          | 14 +++++
 indra/newview/llpaneloutfitedit.cpp       |  6 +-
 indra/newview/llpaneloutfitsinventory.cpp | 10 +++-
 7 files changed, 130 insertions(+), 7 deletions(-)

diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 864a79c38a0..52e987dac90 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -11417,5 +11417,16 @@
       <key>Value</key>
       <integer>1</integer>
     </map>
+    <key>OutfitOperationsTimeout</key>
+    <map>
+      <key>Comment</key>
+      <string>Timeout for outfit related operations.</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>S32</string>
+      <key>Value</key>
+      <integer>180</integer>
+    </map>
 </map>
 </llsd>
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index a899926938c..135629bb8df 100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -38,11 +38,13 @@
 #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 "lloutfitobserver.h"
 #include "llpaneloutfitsinventory.h"
 #include "llselectmgr.h"
 #include "llsidepanelappearance.h"
@@ -55,6 +57,34 @@
 
 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
 {
@@ -762,6 +792,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);
@@ -1810,6 +1861,14 @@ void LLAppearanceMgr::onFirstFullyVisible()
 
 bool LLAppearanceMgr::updateBaseOutfit()
 {
+	if (isOutfitLocked())
+	{
+		// don't allow modify locked outfit
+		llassert(!isOutfitLocked());
+		return false;
+	}
+	setOutfitLocked(true);
+
 	const LLUUID base_outfit_id = getBaseOutfitUUID();
 	if (base_outfit_id.isNull()) return false;
 
@@ -2138,6 +2197,14 @@ LLAppearanceMgr::LLAppearanceMgr():
 	mAttachmentInvLinkEnabled(false),
 	mOutfitIsDirty(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()
diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h
index f1beef5857f..2227a43cd88 100644
--- a/indra/newview/llappearancemgr.h
+++ b/indra/newview/llappearancemgr.h
@@ -42,10 +42,12 @@
 class LLWearable;
 class LLWearableHoldingPattern;
 class LLInventoryCallback;
+class LLOutfitUnLockTimer;
 
 class LLAppearanceMgr: public LLSingleton<LLAppearanceMgr>
 {
 	friend class LLSingleton<LLAppearanceMgr>;
+	friend class LLOutfitUnLockTimer;
 	
 public:
 	typedef std::vector<LLInventoryModel::item_array_t> wearables_by_type_t;
@@ -162,6 +164,8 @@ class LLAppearanceMgr: public LLSingleton<LLAppearanceMgr>
 	// COF is processed if cat_id is not specified
 	void updateClothingOrderingInfo(LLUUID cat_id = LLUUID::null);
 
+	bool isOutfitLocked() { return mOutfitLocked; }
+
 protected:
 	LLAppearanceMgr();
 	~LLAppearanceMgr();
@@ -186,10 +190,20 @@ class LLAppearanceMgr: public LLSingleton<LLAppearanceMgr>
 
 	static void onOutfitRename(const LLSD& notification, const LLSD& response);
 
+	void setOutfitLocked(bool locked);
+
 	std::set<LLUUID> mRegisteredAttachments;
 	bool mAttachmentInvLinkEnabled;
 	bool mOutfitIsDirty;
 
+	/**
+	 * Lock for blocking operations on outfit until server reply or timeout exceed
+	 * to avoid unsynchronized outfit state or performing duplicate operations.
+	 */
+	bool mOutfitLocked;
+
+	std::auto_ptr<LLOutfitUnLockTimer> mUnlockOutfitTimer;
+
 	//////////////////////////////////////////////////////////////////////////////////
 	// Item-specific convenience functions 
 public:
diff --git a/indra/newview/lloutfitobserver.cpp b/indra/newview/lloutfitobserver.cpp
index 848b5956132..5652a98981f 100644
--- a/indra/newview/lloutfitobserver.cpp
+++ b/indra/newview/lloutfitobserver.cpp
@@ -56,9 +56,9 @@ void LLOutfitObserver::changed(U32 mask)
 	if (!gInventory.isInventoryUsable())
 		return;
 
-	bool panel_updated = checkCOF();
+	bool COF_changed = checkCOF();
 
-	if (!panel_updated)
+	if (!COF_changed)
 	{
 		checkBaseOutfit();
 	}
@@ -87,6 +87,7 @@ bool LLOutfitObserver::checkCOF()
 
 	mCOFLastVersion = cof_version;
 
+	// dirtiness state should be updated before sending signal
 	LLAppearanceMgr::getInstance()->updateIsDirty();
 	mCOFChanged();
 
@@ -120,6 +121,16 @@ void LLOutfitObserver::checkBaseOutfit()
 	}
 
 	LLAppearanceMgr& app_mgr = LLAppearanceMgr::instance();
+	// dirtiness state should be updated before sending signal
 	app_mgr.updateIsDirty();
 	mBOFChanged();
+
+	if (mLastOutfitDirtiness != app_mgr.isOutfitDirty())
+	{
+		if(!app_mgr.isOutfitDirty())
+		{
+			mCOFSaved();
+		}
+		mLastOutfitDirtiness = app_mgr.isOutfitDirty();
+	}
 }
diff --git a/indra/newview/lloutfitobserver.h b/indra/newview/lloutfitobserver.h
index 4cb40ead155..a4b5fbe04ac 100644
--- a/indra/newview/lloutfitobserver.h
+++ b/indra/newview/lloutfitobserver.h
@@ -48,6 +48,8 @@ class LLOutfitObserver: public LLInventoryObserver, public LLSingleton<LLOutfitO
 
 	virtual void changed(U32 mask);
 
+	void notifyOutfitLockChanged() { mOutfitLockChanged();  }
+
 	typedef boost::signals2::signal<void (void)> signal_t;
 
 	void addBOFReplacedCallback(const signal_t::slot_type& cb) { mBOFReplaced.connect(cb); }
@@ -56,6 +58,10 @@ class LLOutfitObserver: public LLInventoryObserver, public LLSingleton<LLOutfitO
 
 	void addCOFChangedCallback(const signal_t::slot_type& cb) { mCOFChanged.connect(cb); }
 
+	void addCOFSavedCallback(const signal_t::slot_type& cb) { mCOFSaved.connect(cb); }
+
+	void addOutfitLockChangedCallback(const signal_t::slot_type& cb) { mOutfitLockChanged.connect(cb); }
+
 protected:
 	LLOutfitObserver();
 
@@ -73,10 +79,18 @@ class LLOutfitObserver: public LLInventoryObserver, public LLSingleton<LLOutfitO
 
 	S32 mBaseOutfitLastVersion;
 
+	bool mLastOutfitDirtiness;
+
 private:
 	signal_t mBOFReplaced;
 	signal_t mBOFChanged;
 	signal_t mCOFChanged;
+	signal_t mCOFSaved;
+
+	/**
+	 * Signal for changing state of outfit lock.
+	 */
+	signal_t mOutfitLockChanged;
 };
 
 #endif /* LL_OUTFITOBSERVER_H */
diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp
index ce382541c67..f3f67f2b031 100644
--- a/indra/newview/llpaneloutfitedit.cpp
+++ b/indra/newview/llpaneloutfitedit.cpp
@@ -198,6 +198,7 @@ LLPanelOutfitEdit::LLPanelOutfitEdit()
 	LLOutfitObserver& observer = LLOutfitObserver::instance();
 	observer.addBOFReplacedCallback(boost::bind(&LLPanelOutfitEdit::updateCurrentOutfitName, this));
 	observer.addBOFChangedCallback(boost::bind(&LLPanelOutfitEdit::updateVerbs, this));
+	observer.addOutfitLockChangedCallback(boost::bind(&LLPanelOutfitEdit::updateVerbs, this));
 	observer.addCOFChangedCallback(boost::bind(&LLPanelOutfitEdit::update, this));
 	
 	mLookItemTypes.reserve(NUM_LOOK_ITEM_TYPES);
@@ -666,12 +667,13 @@ void LLPanelOutfitEdit::updateCurrentOutfitName()
 void LLPanelOutfitEdit::updateVerbs()
 {
 	bool outfit_is_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty();
+	bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked();
 	bool has_baseoutfit = LLAppearanceMgr::getInstance()->getBaseOutfitUUID().notNull();
 
-	mSaveComboBtn->setSaveBtnEnabled(outfit_is_dirty);
+	mSaveComboBtn->setSaveBtnEnabled(!outfit_locked && outfit_is_dirty);
 	childSetEnabled(REVERT_BTN, outfit_is_dirty && has_baseoutfit);
 
-	mSaveComboBtn->setMenuItemEnabled("save_outfit", outfit_is_dirty);
+	mSaveComboBtn->setMenuItemEnabled("save_outfit", !outfit_locked && outfit_is_dirty);
 
 	mStatus->setText(outfit_is_dirty ? getString("unsaved_changes") : getString("now_editing"));
 
diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp
index 8836672f91e..8b451c156c3 100644
--- a/indra/newview/llpaneloutfitsinventory.cpp
+++ b/indra/newview/llpaneloutfitsinventory.cpp
@@ -209,6 +209,7 @@ LLPanelOutfitsInventory::LLPanelOutfitsInventory() :
 	LLOutfitObserver& observer = LLOutfitObserver::instance();
 	observer.addBOFChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this));
 	observer.addCOFChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this));
+	observer.addOutfitLockChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this));
 }
 
 LLPanelOutfitsInventory::~LLPanelOutfitsInventory()
@@ -522,7 +523,7 @@ void LLPanelOutfitsInventory::updateListCommands()
 {
 	bool trash_enabled = isActionEnabled("delete");
 	bool wear_enabled = isActionEnabled("wear");
-	bool make_outfit_enabled = isActionEnabled("make_outfit");
+	bool make_outfit_enabled = isActionEnabled("save_outfit");
 
 	mListCommands->childSetEnabled("trash_btn", trash_enabled);
 	mListCommands->childSetEnabled("wear_btn", wear_enabled);
@@ -668,9 +669,12 @@ BOOL LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata)
 			return FALSE;
 		}
 	}
-	if (command_name == "make_outfit")
+	if (command_name == "save_outfit")
 	{
-		return LLAppearanceMgr::getInstance()->isOutfitDirty();
+		bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked();
+		bool outfit_dirty =LLAppearanceMgr::getInstance()->isOutfitDirty();
+		// allow save only if outfit isn't locked and is dirty
+		return !outfit_locked && outfit_dirty;
 	}
    
 	if (command_name == "edit" || 
-- 
GitLab