diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7dbe650625ad07c81ee6d6f92ca6a38744ce0e3d..3b1c49edd36791b6cfac06d13839d879a55d46c0 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -306,6 +306,7 @@ set(viewer_SOURCE_FILES
     llnotificationstorage.cpp
     llnotificationtiphandler.cpp
     lloutfitslist.cpp
+    lloutfitobserver.cpp
     lloutputmonitorctrl.cpp
     llpanelavatar.cpp
     llpanelavatartag.cpp
@@ -822,6 +823,7 @@ set(viewer_HEADER_FILES
     llnotificationmanager.h
     llnotificationstorage.h
     lloutfitslist.h
+    lloutfitobserver.h
     lloutputmonitorctrl.h
     llpanelavatar.h
     llpanelavatartag.h
diff --git a/indra/newview/lloutfitobserver.cpp b/indra/newview/lloutfitobserver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..848b59561322b7c42e4d7a5ddea5fac3c076096f
--- /dev/null
+++ b/indra/newview/lloutfitobserver.cpp
@@ -0,0 +1,125 @@
+/**
+ * @file lloutfitobserver.cpp
+ * @brief Outfit observer facade.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2010, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llappearancemgr.h"
+#include "lloutfitobserver.h"
+#include "llinventorymodel.h"
+#include "llviewerinventory.h"
+
+LLOutfitObserver::LLOutfitObserver() :
+	mCOFLastVersion(LLViewerInventoryCategory::VERSION_UNKNOWN)
+{
+	gInventory.addObserver(this);
+}
+
+LLOutfitObserver::~LLOutfitObserver()
+{
+	if (gInventory.containsObserver(this))
+	{
+		gInventory.removeObserver(this);
+	}
+}
+
+void LLOutfitObserver::changed(U32 mask)
+{
+	if (!gInventory.isInventoryUsable())
+		return;
+
+	bool panel_updated = checkCOF();
+
+	if (!panel_updated)
+	{
+		checkBaseOutfit();
+	}
+}
+
+// static
+S32 LLOutfitObserver::getCategoryVersion(const LLUUID& cat_id)
+{
+	LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
+	if (!cat)
+		return LLViewerInventoryCategory::VERSION_UNKNOWN;
+
+	return cat->getVersion();
+}
+
+bool LLOutfitObserver::checkCOF()
+{
+	LLUUID cof = LLAppearanceMgr::getInstance()->getCOF();
+	if (cof.isNull())
+		return false;
+
+	S32 cof_version = getCategoryVersion(cof);
+
+	if (cof_version == mCOFLastVersion)
+		return false;
+
+	mCOFLastVersion = cof_version;
+
+	LLAppearanceMgr::getInstance()->updateIsDirty();
+	mCOFChanged();
+
+	return true;
+}
+
+void LLOutfitObserver::checkBaseOutfit()
+{
+	LLUUID baseoutfit_id =
+			LLAppearanceMgr::getInstance()->getBaseOutfitUUID();
+
+	if (baseoutfit_id == mBaseOutfitId)
+	{
+		if (baseoutfit_id.isNull())
+			return;
+
+		const S32 baseoutfit_ver = getCategoryVersion(baseoutfit_id);
+
+		if (baseoutfit_ver == mBaseOutfitLastVersion)
+			return;
+	}
+	else
+	{
+		mBaseOutfitId = baseoutfit_id;
+		mBOFReplaced();
+
+		if (baseoutfit_id.isNull())
+			return;
+
+		mBaseOutfitLastVersion = getCategoryVersion(mBaseOutfitId);
+	}
+
+	LLAppearanceMgr& app_mgr = LLAppearanceMgr::instance();
+	app_mgr.updateIsDirty();
+	mBOFChanged();
+}
diff --git a/indra/newview/lloutfitobserver.h b/indra/newview/lloutfitobserver.h
new file mode 100644
index 0000000000000000000000000000000000000000..4cb40ead15544a6ad771306ceb5428c94035494b
--- /dev/null
+++ b/indra/newview/lloutfitobserver.h
@@ -0,0 +1,82 @@
+/**
+ * @file lloutfitobserver.h
+ * @brief Outfit observer facade.
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2010, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab.  Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_OUTFITOBSERVER_H
+#define LL_OUTFITOBSERVER_H
+
+#include "llsingleton.h"
+
+/**
+ * Outfit observer facade that provides simple possibility to subscribe on
+ * BOF(base outfit) replaced, BOF changed, COF(current outfit) changed events.
+ */
+class LLOutfitObserver: public LLInventoryObserver, public LLSingleton<LLOutfitObserver>
+{
+public:
+	virtual ~LLOutfitObserver();
+
+	friend class LLSingleton<LLOutfitObserver>;
+
+	virtual void changed(U32 mask);
+
+	typedef boost::signals2::signal<void (void)> signal_t;
+
+	void addBOFReplacedCallback(const signal_t::slot_type& cb) { mBOFReplaced.connect(cb); }
+
+	void addBOFChangedCallback(const signal_t::slot_type& cb) { mBOFChanged.connect(cb); }
+
+	void addCOFChangedCallback(const signal_t::slot_type& cb) { mCOFChanged.connect(cb); }
+
+protected:
+	LLOutfitObserver();
+
+	/** Get a version of an inventory category specified by its UUID */
+	static S32 getCategoryVersion(const LLUUID& cat_id);
+
+	bool checkCOF();
+
+	void checkBaseOutfit();
+
+	//last version number of a COF category
+	S32 mCOFLastVersion;
+
+	LLUUID mBaseOutfitId;
+
+	S32 mBaseOutfitLastVersion;
+
+private:
+	signal_t mBOFReplaced;
+	signal_t mBOFChanged;
+	signal_t mCOFChanged;
+};
+
+#endif /* LL_OUTFITOBSERVER_H */
diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp
index 4982e98f8e0aa4eaff4727b93091464b935b2945..ea7410502d7e427acc670df007aac4ec438928d1 100644
--- a/indra/newview/llpaneloutfitedit.cpp
+++ b/indra/newview/llpaneloutfitedit.cpp
@@ -39,6 +39,7 @@
 #include "llagentcamera.h"
 #include "llagentwearables.h"
 #include "llappearancemgr.h"
+#include "lloutfitobserver.h"
 #include "llcofwearables.h"
 #include "llfilteredwearablelist.h"
 #include "llinventory.h"
@@ -143,100 +144,6 @@ class LLPanelOutfitEditGearMenu
 	}
 };
 
-class LLCOFObserver : public LLInventoryObserver
-{
-public:
-	LLCOFObserver(LLPanelOutfitEdit *panel) : mPanel(panel), 
-		mCOFLastVersion(LLViewerInventoryCategory::VERSION_UNKNOWN)
-	{
-		gInventory.addObserver(this);
-	}
-
-	virtual ~LLCOFObserver()
-	{
-		if (gInventory.containsObserver(this))
-		{
-			gInventory.removeObserver(this);
-		}
-	}
-	
-	virtual void changed(U32 mask)
-	{
-		if (!gInventory.isInventoryUsable()) return;
-	
-		bool panel_updated = checkCOF();
-
-		if (!panel_updated)
-		{
-			checkBaseOutfit();
-		}
-	}
-
-protected:
-
-	/** Get a version of an inventory category specified by its UUID */
-	static S32 getCategoryVersion(const LLUUID& cat_id)
-	{
-		LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
-		if (!cat) return LLViewerInventoryCategory::VERSION_UNKNOWN;
-
-		return cat->getVersion();
-	}
-
-	bool checkCOF()
-	{
-		LLUUID cof = LLAppearanceMgr::getInstance()->getCOF();
-		if (cof.isNull()) return false;
-
-		S32 cof_version = getCategoryVersion(cof);
-
-		if (cof_version == mCOFLastVersion) return false;
-		
-		mCOFLastVersion = cof_version;
-
-		mPanel->update();
-
-		return true;
-	}
-
-	void checkBaseOutfit()
-	{
-		LLUUID baseoutfit_id = LLAppearanceMgr::getInstance()->getBaseOutfitUUID();
-
-		if (baseoutfit_id == mBaseOutfitId)
-		{
-			if (baseoutfit_id.isNull()) return;
-
-			const S32 baseoutfit_ver = getCategoryVersion(baseoutfit_id);
-
-			if (baseoutfit_ver == mBaseOutfitLastVersion) return;
-		}
-		else
-		{
-			mBaseOutfitId = baseoutfit_id;
-			mPanel->updateCurrentOutfitName();
-
-			if (baseoutfit_id.isNull()) return;
-
-			mBaseOutfitLastVersion = getCategoryVersion(mBaseOutfitId);
-		}
-
-		mPanel->updateVerbs();
-	}
-	
-
-
-
-	LLPanelOutfitEdit *mPanel;
-
-	//last version number of a COF category
-	S32 mCOFLastVersion;
-
-	LLUUID  mBaseOutfitId;
-
-	S32 mBaseOutfitLastVersion;
-};
-
 class LLCOFDragAndDropObserver : public LLInventoryAddItemByAssetObserver
 {
 public:
@@ -277,7 +184,6 @@ LLPanelOutfitEdit::LLPanelOutfitEdit()
 	mSearchFilter(NULL),
 	mCOFWearables(NULL),
 	mInventoryItemsPanel(NULL),
-	mCOFObserver(NULL),
 	mGearMenu(NULL),
 	mCOFDragAndDropObserver(NULL),
 	mInitialized(false),
@@ -288,7 +194,11 @@ LLPanelOutfitEdit::LLPanelOutfitEdit()
 	mSavedFolderState = new LLSaveFolderState();
 	mSavedFolderState->setApply(FALSE);
 	
-	mCOFObserver = new LLCOFObserver(this);
+
+	LLOutfitObserver& observer = LLOutfitObserver::instance();
+	observer.addBOFReplacedCallback(boost::bind(&LLPanelOutfitEdit::updateCurrentOutfitName, this));
+	observer.addBOFChangedCallback(boost::bind(&LLPanelOutfitEdit::updateVerbs, this));
+	observer.addCOFChangedCallback(boost::bind(&LLPanelOutfitEdit::update, this));
 	
 	mLookItemTypes.reserve(NUM_LOOK_ITEM_TYPES);
 	for (U32 i = 0; i < NUM_LOOK_ITEM_TYPES; i++)
@@ -303,7 +213,6 @@ LLPanelOutfitEdit::~LLPanelOutfitEdit()
 {
 	delete mSavedFolderState;
 
-	delete mCOFObserver;
 	delete mCOFDragAndDropObserver;
 
 	delete mWearableListMaskCollector;
@@ -756,9 +665,6 @@ void LLPanelOutfitEdit::updateCurrentOutfitName()
 //private
 void LLPanelOutfitEdit::updateVerbs()
 {
-	//*TODO implement better handling of COF dirtiness
-	LLAppearanceMgr::getInstance()->updateIsDirty();
-
 	bool outfit_is_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty();
 	bool has_baseoutfit = LLAppearanceMgr::getInstance()->getBaseOutfitUUID().notNull();
 
diff --git a/indra/newview/llpaneloutfitedit.h b/indra/newview/llpaneloutfitedit.h
index 802386c57302ae8e8b16bd8d23b817655f9a7627..24ecf75c18a7b18a63cda6e928e900345cbae7d6 100644
--- a/indra/newview/llpaneloutfitedit.h
+++ b/indra/newview/llpaneloutfitedit.h
@@ -49,7 +49,7 @@ class LLButton;
 class LLCOFWearables;
 class LLTextBox;
 class LLInventoryCategory;
-class LLCOFObserver;
+class LLOutfitObserver;
 class LLCOFDragAndDropObserver;
 class LLInventoryPanel;
 class LLSaveFolderState;
@@ -153,7 +153,6 @@ class LLPanelOutfitEdit : public LLPanel
 	LLInventoryItemsList* 			mWearableItemsList;
 	LLPanel*						mWearableItemsPanel;
 
-	LLCOFObserver*	mCOFObserver;
 	LLCOFDragAndDropObserver* mCOFDragAndDropObserver;
 
 	std::vector<LLLookItemType> mLookItemTypes;
diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp
index 5f67f3d98969791f58053042a2cfd619e5322bf3..8836672f91e39d3e0f496ae682c7769d2e77568d 100644
--- a/indra/newview/llpaneloutfitsinventory.cpp
+++ b/indra/newview/llpaneloutfitsinventory.cpp
@@ -50,6 +50,7 @@
 #include "lllineeditor.h"
 #include "llmodaldialog.h"
 #include "llnotificationsutil.h"
+#include "lloutfitobserver.h"
 #include "lloutfitslist.h"
 #include "llsaveoutfitcombobtn.h"
 #include "llsidepanelappearance.h"
@@ -204,6 +205,10 @@ LLPanelOutfitsInventory::LLPanelOutfitsInventory() :
 	mSavedFolderState = new LLSaveFolderState();
 	mSavedFolderState->setApply(FALSE);
 	gAgentWearables.addLoadedCallback(boost::bind(&LLPanelOutfitsInventory::onWearablesLoaded, this));
+
+	LLOutfitObserver& observer = LLOutfitObserver::instance();
+	observer.addBOFChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this));
+	observer.addCOFChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this));
 }
 
 LLPanelOutfitsInventory::~LLPanelOutfitsInventory()
@@ -522,7 +527,7 @@ void LLPanelOutfitsInventory::updateListCommands()
 	mListCommands->childSetEnabled("trash_btn", trash_enabled);
 	mListCommands->childSetEnabled("wear_btn", wear_enabled);
 	mListCommands->childSetVisible("wear_btn", wear_enabled);
-	mSaveComboBtn->setSaveBtnEnabled(make_outfit_enabled);
+	mSaveComboBtn->setMenuItemEnabled("save_outfit", make_outfit_enabled);
 }
 
 void LLPanelOutfitsInventory::showGearMenu()
@@ -665,7 +670,7 @@ BOOL LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata)
 	}
 	if (command_name == "make_outfit")
 	{
-		return TRUE;
+		return LLAppearanceMgr::getInstance()->isOutfitDirty();
 	}
    
 	if (command_name == "edit" || 
@@ -789,6 +794,7 @@ void LLPanelOutfitsInventory::onWearablesLoaded()
 	setWearablesLoading(false);
 }
 
+// static
 LLSidepanelAppearance* LLPanelOutfitsInventory::getAppearanceSP()
 {
 	static LLSidepanelAppearance* panel_appearance =
diff --git a/indra/newview/llpaneloutfitsinventory.h b/indra/newview/llpaneloutfitsinventory.h
index aff7839bcc1bea2f51b804742a58f9bb558fb8df..d58ae554b04921d6a2c8a14b64a5cb71bba04924 100644
--- a/indra/newview/llpaneloutfitsinventory.h
+++ b/indra/newview/llpaneloutfitsinventory.h
@@ -75,7 +75,7 @@ class LLPanelOutfitsInventory : public LLPanel
 	void setParent(LLSidepanelAppearance *parent);
 
 	LLFolderView* getRootFolder();
-	LLSidepanelAppearance* getAppearanceSP();
+	static LLSidepanelAppearance* getAppearanceSP();
 
 	static LLPanelOutfitsInventory* findInstance();
 
diff --git a/indra/newview/llsaveoutfitcombobtn.cpp b/indra/newview/llsaveoutfitcombobtn.cpp
index b9b577084be9339458b7bbc96fcbe8a9d5a3ba0e..9518b0cbb32bd1134b599702cb656a5ca5cf325f 100644
--- a/indra/newview/llsaveoutfitcombobtn.cpp
+++ b/indra/newview/llsaveoutfitcombobtn.cpp
@@ -34,6 +34,7 @@
 
 #include "llappearancemgr.h"
 #include "llpaneloutfitsinventory.h"
+#include "llsidepanelappearance.h"
 #include "llsaveoutfitcombobtn.h"
 #include "llviewermenu.h"
 
diff --git a/indra/newview/llsidepanelappearance.cpp b/indra/newview/llsidepanelappearance.cpp
index b66789448fca5667a840fa0f82d9b9d93427b375..ef7286b7b447fa11a3b767f7ee0f1a2190121f01 100644
--- a/indra/newview/llsidepanelappearance.cpp
+++ b/indra/newview/llsidepanelappearance.cpp
@@ -42,6 +42,7 @@
 #include "llfloaterreg.h"
 #include "llfloaterworldmap.h"
 #include "llfoldervieweventlistener.h"
+#include "lloutfitobserver.h"
 #include "llpaneleditwearable.h"
 #include "llpaneloutfitsinventory.h"
 #include "llsidetray.h"
@@ -73,26 +74,6 @@ class LLCurrentlyWornFetchObserver : public LLInventoryFetchItemsObserver
 	LLSidepanelAppearance *mPanel;
 };
 
-class LLWatchForOutfitRenameObserver : public LLInventoryObserver
-{
-public:
-	LLWatchForOutfitRenameObserver(LLSidepanelAppearance *panel) :
-		mPanel(panel)
-	{}
-	virtual void changed(U32 mask);
-	
-private:
-	LLSidepanelAppearance *mPanel;
-};
-
-void LLWatchForOutfitRenameObserver::changed(U32 mask)
-{
-	if (mask & LABEL)
-	{
-		mPanel->refreshCurrentOutfitName();
-	}
-}
-
 LLSidepanelAppearance::LLSidepanelAppearance() :
 	LLPanel(),
 	mFilterSubString(LLStringUtil::null),
@@ -101,12 +82,13 @@ LLSidepanelAppearance::LLSidepanelAppearance() :
 	mCurrOutfitPanel(NULL),
 	mOpened(false)
 {
+	LLOutfitObserver& outfit_observer =  LLOutfitObserver::instance();
+	outfit_observer.addBOFChangedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, ""));
+	outfit_observer.addCOFChangedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, ""));
 }
 
 LLSidepanelAppearance::~LLSidepanelAppearance()
 {
-	gInventory.removeObserver(mOutfitRenameWatcher);
-	delete mOutfitRenameWatcher;
 }
 
 // virtual
@@ -160,8 +142,6 @@ BOOL LLSidepanelAppearance::postBuild()
 	
 	mCurrOutfitPanel = getChild<LLPanel>("panel_currentlook");
 
-	mOutfitRenameWatcher = new LLWatchForOutfitRenameObserver(this);
-	gInventory.addObserver(mOutfitRenameWatcher);
 
 	setVisibleCallback(boost::bind(&LLSidepanelAppearance::onVisibilityChange,this,_2));
 
diff --git a/indra/newview/llsidepanelappearance.h b/indra/newview/llsidepanelappearance.h
index 30022ae37535698a85af778b318bc147ffe4f2cb..812d6362efe4c1a462596665d06230960d67755a 100644
--- a/indra/newview/llsidepanelappearance.h
+++ b/indra/newview/llsidepanelappearance.h
@@ -40,7 +40,6 @@
 
 class LLFilterEditor;
 class LLCurrentlyWornFetchObserver;
-class LLWatchForOutfitRenameObserver;
 class LLPanelEditWearable;
 class LLWearable;
 class LLPanelOutfitsInventory;
@@ -97,9 +96,6 @@ class LLSidepanelAppearance : public LLPanel
 	// Used to make sure the user's inventory is in memory.
 	LLCurrentlyWornFetchObserver* mFetchWorn;
 
-	// Used to update title when currently worn outfit gets renamed.
-	LLWatchForOutfitRenameObserver* mOutfitRenameWatcher;
-
 	// Search string for filtering landmarks and teleport
 	// history locations
 	std::string					mFilterSubString;