From 667ca55bad0108c4bdf8f007b89e1a52fc766aad Mon Sep 17 00:00:00 2001
From: Kent Quirk <q@lindenlab.com>
Date: Mon, 5 Jan 2009 18:59:12 +0000
Subject: svn merge -r106715:HEAD
 svn+ssh://svn.lindenlab.com/svn/linden/branches/q/notifications-merge-r106715
 .  QAR-1149 -- Final merge of notifications to trunk.

---
 indra/llui/CMakeLists.txt        |    4 +
 indra/llui/llcombobox.cpp        |   32 +-
 indra/llui/llcombobox.h          |    1 +
 indra/llui/llfloater.cpp         |    5 +
 indra/llui/llfloater.h           |   24 +-
 indra/llui/llfunctorregistry.cpp |   37 +
 indra/llui/llfunctorregistry.h   |  145 ++++
 indra/llui/llnotifications.cpp   | 1471 ++++++++++++++++++++++++++++++++++++++
 indra/llui/llnotifications.h     |  892 +++++++++++++++++++++++
 indra/llui/llpanel.cpp           |  129 ++--
 indra/llui/llpanel.h             |   26 +-
 indra/llui/llscrollbar.cpp       |   76 +-
 indra/llui/llscrollbar.h         |    2 +-
 indra/llui/llscrolllistctrl.cpp  |  342 ++++++---
 indra/llui/llscrolllistctrl.h    |  115 +--
 indra/llui/llui.cpp              |   16 +-
 indra/llui/llui.h                |  237 ++++++
 indra/llui/lluictrl.cpp          |   11 +-
 indra/llui/lluictrl.h            |    2 +-
 indra/llui/lluictrlfactory.h     |    1 +
 indra/llui/lluistring.cpp        |   13 +
 indra/llui/lluistring.h          |    6 +-
 indra/llui/llview.cpp            |    5 +-
 indra/llui/llview.h              |    7 +-
 24 files changed, 3291 insertions(+), 308 deletions(-)
 create mode 100644 indra/llui/llfunctorregistry.cpp
 create mode 100644 indra/llui/llfunctorregistry.h
 create mode 100644 indra/llui/llnotifications.cpp
 create mode 100644 indra/llui/llnotifications.h

(limited to 'indra/llui')

diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 897cc4275d..1b4d4e7d54 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -36,6 +36,7 @@ set(llui_SOURCE_FILES
     lleditmenuhandler.cpp
     llfloater.cpp
     llfocusmgr.cpp
+    llfunctorregistry.cpp
     lliconctrl.cpp
     llkeywords.cpp
     lllineeditor.cpp
@@ -43,6 +44,7 @@ set(llui_SOURCE_FILES
     llmodaldialog.cpp
     llmultislider.cpp
     llmultisliderctrl.cpp
+    llnotifications.cpp
     llpanel.cpp
     llprogressbar.cpp
     llradiogroup.cpp
@@ -86,6 +88,7 @@ set(llui_HEADER_FILES
     lleditmenuhandler.h
     llfloater.h
     llfocusmgr.h
+    llfunctorregistry.h
     llhtmlhelp.h
     lliconctrl.h
     llkeywords.h
@@ -95,6 +98,7 @@ set(llui_HEADER_FILES
     llmodaldialog.h
     llmultisliderctrl.h
     llmultislider.h
+    llnotifications.h
     llpanel.h
     llprogressbar.h
     llradiogroup.h
diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp
index df5bcbe752..6edd5a7fe5 100644
--- a/indra/llui/llcombobox.cpp
+++ b/indra/llui/llcombobox.cpp
@@ -72,11 +72,12 @@ LLComboBox::LLComboBox(	const std::string& name, const LLRect &rect, const std::
 	mTextEntryTentative(TRUE),
 	mListPosition(BELOW),
 	mPrearrangeCallback( NULL ),
-	mTextEntryCallback( NULL )
+	mTextEntryCallback( NULL ),
+	mLabel(label)
 {
 	// Always use text box 
 	// Text label button
-	mButton = new LLButton(label,
+	mButton = new LLButton(mLabel,
 								LLRect(), 
 								LLStringUtil::null,
 								NULL, this);
@@ -197,7 +198,12 @@ LLView* LLComboBox::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *
 		}
 	}
 
-	combo_box->selectFirstItem();
+	// if providing user text entry or descriptive label
+	// don't select an item under the hood
+	if (!combo_box->acceptsTextInput() && combo_box->mLabel.empty())
+	{
+		combo_box->selectFirstItem();
+	}
 
 	return combo_box;
 }
@@ -259,7 +265,10 @@ LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, BOO
 {
 	LLScrollListItem* item = mList->addSimpleElement(name, pos);
 	item->setEnabled(enabled);
-	mList->selectFirstItem();
+	if (!mAllowTextEntry && mLabel.empty())
+	{
+		selectFirstItem();
+	}
 	return item;
 }
 
@@ -268,7 +277,10 @@ LLScrollListItem* LLComboBox::add(const std::string& name, const LLUUID& id, EAd
 {
 	LLScrollListItem* item = mList->addSimpleElement(name, pos, id);
 	item->setEnabled(enabled);
-	mList->selectFirstItem();
+	if (!mAllowTextEntry && mLabel.empty())
+	{
+		selectFirstItem();
+	}
 	return item;
 }
 
@@ -278,7 +290,10 @@ LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddP
 	LLScrollListItem* item = mList->addSimpleElement(name, pos);
 	item->setEnabled(enabled);
 	item->setUserdata( userdata );
-	mList->selectFirstItem();
+	if (!mAllowTextEntry && mLabel.empty())
+	{
+		selectFirstItem();
+	}
 	return item;
 }
 
@@ -287,7 +302,10 @@ LLScrollListItem* LLComboBox::add(const std::string& name, LLSD value, EAddPosit
 {
 	LLScrollListItem* item = mList->addSimpleElement(name, pos, value);
 	item->setEnabled(enabled);
-	mList->selectFirstItem();
+	if (!mAllowTextEntry && mLabel.empty())
+	{
+		selectFirstItem();
+	}
 	return item;
 }
 
diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h
index 7427a33129..0317ebdae9 100644
--- a/indra/llui/llcombobox.h
+++ b/indra/llui/llcombobox.h
@@ -188,6 +188,7 @@ protected:
 	LLScrollListCtrl*	mList;
 	EPreferredPosition	mListPosition;
 	LLPointer<LLUIImage>	mArrowImage;
+	std::string			mLabel;
 
 private:
 	S32					mButtonPadding;
diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp
index 22260b52cf..dd93684a69 100644
--- a/indra/llui/llfloater.cpp
+++ b/indra/llui/llfloater.cpp
@@ -147,6 +147,7 @@ LLFloater::LLFloater() :
 	}
 	mDragHandle = NULL;
 	mHandle.bind(this);
+	mNotificationContext = new LLFloaterNotificationContext(getHandle());
 }
 
 LLFloater::LLFloater(const std::string& name)
@@ -220,6 +221,7 @@ void LLFloater::initFloater(const std::string& title,
 					 BOOL drag_on_left, BOOL minimizable, BOOL close_btn)
 {
 	mHandle.bind(this);
+	mNotificationContext = new LLFloaterNotificationContext(getHandle());
 
 	// Init function can be called more than once, so clear out old data.
 	for (S32 i = 0; i < BUTTON_COUNT; i++)
@@ -429,6 +431,9 @@ void LLFloater::initFloater(const std::string& title,
 // virtual
 LLFloater::~LLFloater()
 {
+	delete mNotificationContext;
+	mNotificationContext = NULL;
+
 	control_map_t::iterator itor;
 	for (itor = mFloaterControls.begin(); itor != mFloaterControls.end(); ++itor)
 	{
diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h
index b66eba8810..ebf2676960 100644
--- a/indra/llui/llfloater.h
+++ b/indra/llui/llfloater.h
@@ -39,6 +39,7 @@
 #include "llpanel.h"
 #include "lluuid.h"
 #include "lltabcontainer.h"
+#include "llnotifications.h"
 #include <set>
 
 class LLDragHandle;
@@ -46,6 +47,7 @@ class LLResizeHandle;
 class LLResizeBar;
 class LLButton;
 class LLMultiFloater;
+class LLFloater;
 
 const S32 LLFLOATER_VPAD = 6;
 const S32 LLFLOATER_HPAD = 6;
@@ -70,6 +72,20 @@ const BOOL CLOSE_NO = FALSE;
 const BOOL ADJUST_VERTICAL_YES = TRUE;
 const BOOL ADJUST_VERTICAL_NO = FALSE;
 
+// associates a given notification instance with a particular floater
+class LLFloaterNotificationContext : 
+	public LLNotificationContext
+{
+public:
+	LLFloaterNotificationContext(LLHandle<LLFloater> floater_handle) :
+		mFloaterHandle(floater_handle)
+	{}
+
+	LLFloater* getFloater() { return mFloaterHandle.get(); }
+private:
+	LLHandle<LLFloater> mFloaterHandle;
+};
+
 
 class LLFloater : public LLPanel
 {
@@ -213,6 +229,11 @@ public:
 	// handle refocusing.
 	static void		closeFocusedFloater();
 
+	LLNotification::Params contextualNotification(const std::string& name) 
+	{ 
+	    return LLNotification::Params(name).context(mNotificationContext); 
+	}
+
 	static void		onClickClose(void *userdata);
 	static void		onClickMinimize(void *userdata);
 	static void		onClickTearOff(void *userdata);
@@ -299,7 +320,7 @@ private:
 	S32				mPreviousMinimizedBottom;
 	S32				mPreviousMinimizedLeft;
 	
-private:
+	LLFloaterNotificationContext* mNotificationContext;
 	LLRootHandle<LLFloater>		mHandle;	
 };
 
@@ -467,7 +488,6 @@ template <class T> class LLFloaterSingleton : public LLUISingleton<T, Visibility
 {
 };
 
-
 extern LLFloaterView* gFloaterView;
 
 #endif  // LL_FLOATER_H
diff --git a/indra/llui/llfunctorregistry.cpp b/indra/llui/llfunctorregistry.cpp
new file mode 100644
index 0000000000..a6ecc6aa18
--- /dev/null
+++ b/indra/llui/llfunctorregistry.cpp
@@ -0,0 +1,37 @@
+/**
+ * @file llfunctorregistry.cpp
+ * @author Kent Quirk
+ * @brief Maintains a registry of named callback functors taking a single LLSD parameter
+ *
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ * 
+ * Copyright (c) 2008, 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 "llfunctorregistry.h"
+
+// This is a default functor always resident in the system.
+// It's used whenever a functor isn't found in the registry, so that
+// we at least log the data relating to the user response.
diff --git a/indra/llui/llfunctorregistry.h b/indra/llui/llfunctorregistry.h
new file mode 100644
index 0000000000..02bf74a28a
--- /dev/null
+++ b/indra/llui/llfunctorregistry.h
@@ -0,0 +1,145 @@
+/**
+ * @file llfunctorregistry.h
+ * @author Kent Quirk
+ * @brief Maintains a registry of named callback functors taking a single LLSD parameter
+ *
+ * $LicenseInfo:firstyear=2008&license=viewergpl$
+ *
+ * Copyright (c) 2003-2007, 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://secondlife.com/developers/opensource/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://secondlife.com/developers/opensource/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_LLFUNCTORREGISTRY_H
+#define LL_LLFUNCTORREGISTRY_H
+
+#include <string>
+#include <map>
+
+#include <boost/function.hpp>
+
+#include "llsd.h"
+#include "llmemory.h"
+
+/**
+ * @class LLFunctorRegistry
+ * @brief Maintains a collection of named functors for remote binding
+ * (mainly for use in callbacks from notifications and other signals)
+ * @see LLNotifications
+ *
+ * This class maintains a collection of named functors in a singleton.
+ * We wanted to be able to persist notifications with their callbacks
+ * across restarts of the viewer; we couldn't store functors that way.
+ * Using this registry, systems that require a functor to be maintained
+ * long term can register it at system startup, and then pass in the
+ * functor by name. 
+ */
+
+template <typename FUNCTOR_TYPE>
+class LLFunctorRegistry : public LLSingleton<LLFunctorRegistry<FUNCTOR_TYPE> >
+{
+	friend class LLSingleton<LLFunctorRegistry>;
+	LOG_CLASS(LLFunctorRegistry);
+private:
+	LLFunctorRegistry() : LOGFUNCTOR("LogFunctor"), DONOTHING("DoNothing")
+	{
+		mMap[LOGFUNCTOR] = log_functor;
+		mMap[DONOTHING] = do_nothing;
+	}
+
+public:
+	typedef FUNCTOR_TYPE ResponseFunctor;
+	typedef typename std::map<std::string, FUNCTOR_TYPE> FunctorMap;
+	
+	bool registerFunctor(const std::string& name, ResponseFunctor f)
+	{
+		bool retval = true;
+		typename FunctorMap::iterator it = mMap.find(name);
+		if (mMap.count(name) == 0)
+		{
+			mMap[name] = f;
+		}
+		else
+		{
+			llerrs << "attempt to store duplicate name '" << name << "' in LLFunctorRegistry. NOT ADDED." << llendl;
+			retval = false;
+		}
+		
+		return retval;
+	}
+
+	bool unregisterFunctor(const std::string& name)
+	{
+		if (mMap.count(name) == 0)
+		{
+			llwarns << "trying to remove '" << name << "' from LLFunctorRegistry but it's not there." << llendl;
+			return false;
+		}
+		mMap.erase(name);
+		return true;
+	}
+
+	FUNCTOR_TYPE getFunctor(const std::string& name)
+	{
+		typename FunctorMap::iterator it = mMap.find(name);
+		if (mMap.count(name) != 0)
+		{
+			return mMap[name];
+		}
+		else
+		{
+			llwarns << "tried to find '" << name << "' in LLFunctorRegistry, but it wasn't there." << llendl;
+			return mMap[LOGFUNCTOR];
+		}
+	}
+
+	const std::string LOGFUNCTOR;
+	const std::string DONOTHING;
+	
+private:
+
+	static void log_functor(const LLSD& notification, const LLSD& payload)
+	{
+		llwarns << "log_functor called with payload: " << payload << llendl;
+	}
+
+	static void do_nothing(const LLSD& notification, const LLSD& payload)
+	{
+		// what the sign sez
+	}
+
+	FunctorMap mMap;
+};
+
+template <typename FUNCTOR_TYPE>
+class LLFunctorRegistration
+{
+public:
+	LLFunctorRegistration(const std::string& name, FUNCTOR_TYPE functor) 
+	{
+		LLFunctorRegistry<FUNCTOR_TYPE>::instance().registerFunctor(name, functor);
+	}
+};
+
+#endif//LL_LLFUNCTORREGISTRY_H
+
diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp
new file mode 100644
index 0000000000..c2da50b98b
--- /dev/null
+++ b/indra/llui/llnotifications.cpp
@@ -0,0 +1,1471 @@
+/**
+* @file llnotifications.cpp
+* @brief Non-UI queue manager for keeping a prioritized list of notifications
+*
+* $LicenseInfo:firstyear=2008&license=viewergpl$
+* 
+* Copyright (c) 2008, 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 "linden_common.h"
+#include "lluictrlfactory.h"
+#include "lldir.h"
+#include "llsdserialize.h"
+
+#include "llnotifications.h"
+
+#include <algorithm>
+#include <boost/regex.hpp>
+
+
+const std::string NOTIFICATION_PERSIST_VERSION = "0.93";
+
+// local channel for notification history
+class LLNotificationHistoryChannel : public LLNotificationChannel
+{
+	LOG_CLASS(LLNotificationHistoryChannel);
+public:
+	LLNotificationHistoryChannel(const std::string& filename) : 
+		LLNotificationChannel("History", "Visible", &historyFilter),
+		mFileName(filename)
+	{
+		connectChanged(boost::bind(&LLNotificationHistoryChannel::historyHandler, this, _1));
+		loadPersistentNotifications();
+	}
+
+private:
+	bool historyHandler(const LLSD& payload)
+	{
+		// we ignore "load" messages, but rewrite the persistence file on any other
+		std::string sigtype = payload["sigtype"];
+		if (sigtype != "load")
+		{
+			savePersistentNotifications();
+		}
+		return false;
+	}
+
+	// The history channel gets all notifications except those that have been cancelled
+	static bool historyFilter(LLNotificationPtr pNotification)
+	{
+		return !pNotification->isCancelled();
+	}
+
+	void savePersistentNotifications()
+	{
+		llinfos << "Saving open notifications to " << mFileName << llendl;
+
+		llofstream notify_file(mFileName.c_str());
+		if (!notify_file.is_open()) 
+		{
+			llwarns << "Failed to open " << mFileName << llendl;
+			return;
+		}
+
+		LLSD output;
+		output["version"] = NOTIFICATION_PERSIST_VERSION;
+		LLSD& data = output["data"];
+
+		for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
+		{
+			if (!LLNotifications::instance().templateExists((*it)->getName())) continue;
+
+			// only store notifications flagged as persisting
+			LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate((*it)->getName());
+			if (!templatep->mPersist) continue;
+
+			data.append((*it)->asLLSD());
+		}
+
+		LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter();
+		formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY);
+	}
+
+	void loadPersistentNotifications()
+	{
+		llinfos << "Loading open notifications from " << mFileName << llendl;
+
+		llifstream notify_file(mFileName.c_str());
+		if (!notify_file.is_open()) 
+		{
+			llwarns << "Failed to open " << mFileName << llendl;
+			return;
+		}
+
+		LLSD input;
+		LLPointer<LLSDParser> parser = new LLSDXMLParser();
+		if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0)
+		{
+			llwarns << "Failed to parse open notifications" << llendl;
+			return;
+		}
+
+		if (input.isUndefined()) return;
+		std::string version = input["version"];
+		if (version != NOTIFICATION_PERSIST_VERSION)
+		{
+			llwarns << "Bad open notifications version: " << version << llendl;
+			return;
+		}
+		LLSD& data = input["data"];
+		if (data.isUndefined()) return;
+
+		LLNotifications& instance = LLNotifications::instance();
+		for (LLSD::array_const_iterator notification_it = data.beginArray();
+			notification_it != data.endArray();
+			++notification_it)
+		{
+			instance.add(LLNotificationPtr(new LLNotification(*notification_it)));
+		}
+	}
+
+	//virtual
+	void onDelete(LLNotificationPtr pNotification)
+	{
+		// we want to keep deleted notifications in our log
+		mItems.insert(pNotification);
+		
+		return;
+	}
+	
+private:
+	std::string mFileName;
+};
+
+bool filterIgnoredNotifications(LLNotificationPtr notification)
+{
+	LLNotificationFormPtr form = notification->getForm();
+	// Check to see if the user wants to ignore this alert
+	if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO)
+	{
+		return LLUI::sConfigGroup->getWarning(notification->getName());
+	}
+
+	return true;
+}
+
+bool handleIgnoredNotification(const LLSD& payload)
+{
+	if (payload["sigtype"].asString() == "add")
+	{
+		LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());
+		if (!pNotif) return false;
+
+		LLNotificationFormPtr form = pNotif->getForm();
+		LLSD response;
+		switch(form->getIgnoreType())
+		{
+		case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE:
+			response = pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON);
+			break;
+		case LLNotificationForm::IGNORE_WITH_LAST_RESPONSE:
+			response = LLUI::sIgnoresGroup->getLLSD("Default" + pNotif->getName());
+			break;
+		case LLNotificationForm::IGNORE_SHOW_AGAIN:
+			break;
+		default:
+			return false;
+		}
+		pNotif->setIgnored(true);
+		pNotif->respond(response);
+		return true; 	// don't process this item any further
+	}
+	return false;
+}
+
+namespace LLNotificationFilters
+{
+	// a sample filter
+	bool includeEverything(LLNotificationPtr p)
+	{
+		return true;
+	}
+};
+
+LLNotificationForm::LLNotificationForm()
+:	mFormData(LLSD::emptyArray()),
+	mIgnore(IGNORE_NO)
+{
+}
+
+
+LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node) 
+:	mFormData(LLSD::emptyArray()),
+	mIgnore(IGNORE_NO)
+{
+	if (!xml_node->hasName("form"))
+	{
+		llwarns << "Bad xml node for form: " << xml_node->getName() << llendl;
+	}
+	LLXMLNodePtr child = xml_node->getFirstChild();
+	while(child)
+	{
+		child = LLNotifications::instance().checkForXMLTemplate(child);
+
+		LLSD item_entry;
+		std::string element_name = child->getName()->mString;
+
+		if (element_name == "ignore")
+		{
+			bool save_option = false;
+			child->getAttribute_bool("save_option", save_option);
+			if (!save_option)
+			{
+				mIgnore = IGNORE_WITH_DEFAULT_RESPONSE;
+			}
+			else
+			{
+				// remember last option chosen by user and automatically respond with that in the future
+				mIgnore = IGNORE_WITH_LAST_RESPONSE;
+				LLUI::sIgnoresGroup->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name));
+			}
+			child->getAttributeString("text", mIgnoreMsg);
+			LLUI::sIgnoresGroup->addWarning(name);
+		}
+		else
+		{
+			// flatten xml form entry into single LLSD map with type==name
+			item_entry["type"] = element_name;
+			const LLXMLAttribList::iterator attrib_end = child->mAttributes.end();
+			for(LLXMLAttribList::iterator attrib_it = child->mAttributes.begin();
+				attrib_it != attrib_end;
+				++attrib_it)
+			{
+				item_entry[std::string(attrib_it->second->getName()->mString)] = attrib_it->second->getValue();
+			}
+			item_entry["value"] = child->getTextContents();
+			mFormData.append(item_entry);
+		}
+
+		child = child->getNextSibling();
+	}
+}
+
+LLNotificationForm::LLNotificationForm(const LLSD& sd)
+{
+	if (sd.isArray())
+	{
+		mFormData = sd;
+	}
+	else
+	{
+		llwarns << "Invalid form data " << sd << llendl;
+		mFormData = LLSD::emptyArray();
+	}
+}
+
+LLSD LLNotificationForm::asLLSD() const
+{ 
+	return mFormData; 
+}
+
+LLSD LLNotificationForm::getElement(const std::string& element_name)
+{
+	for (LLSD::array_const_iterator it = mFormData.beginArray();
+		it != mFormData.endArray();
+		++it)
+	{
+		if ((*it)["name"].asString() == element_name) return (*it);
+	}
+	return LLSD();
+}
+
+
+bool LLNotificationForm::hasElement(const std::string& element_name)
+{
+	for (LLSD::array_const_iterator it = mFormData.beginArray();
+		it != mFormData.endArray();
+		++it)
+	{
+		if ((*it)["name"].asString() == element_name) return true;
+	}
+	return false;
+}
+
+void LLNotificationForm::addElement(const std::string& type, const std::string& name, const LLSD& value)
+{
+	LLSD element;
+	element["type"] = type;
+	element["name"] = name;
+	element["label"] = name;
+	element["value"] = value;
+	element["index"] = mFormData.size();
+	mFormData.append(element);
+}
+
+void LLNotificationForm::append(const LLSD& sub_form)
+{
+	if (sub_form.isArray())
+	{
+		for (LLSD::array_const_iterator it = sub_form.beginArray();
+			it != sub_form.endArray();
+			++it)
+		{
+			mFormData.append(*it);
+		}
+	}
+}
+
+void LLNotificationForm::formatElements(const LLSD& substitutions)
+{
+	for (LLSD::array_iterator it = mFormData.beginArray();
+		it != mFormData.endArray();
+		++it)
+	{
+		// format "text" component of each form element
+		if ((*it).has("text"))
+		{
+			std::string text = (*it)["text"].asString();
+			text = LLNotification::format(text, substitutions);
+			(*it)["text"] = text;
+		}
+		if ((*it)["type"].asString() == "text" && (*it).has("value"))
+		{
+			std::string value = (*it)["value"].asString();
+			value = LLNotification::format(value, substitutions);
+			(*it)["value"] = value;
+		}
+	}
+}
+
+std::string LLNotificationForm::getDefaultOption()
+{
+	for (LLSD::array_const_iterator it = mFormData.beginArray();
+		it != mFormData.endArray();
+		++it)
+	{
+		if ((*it)["default"]) return (*it)["name"].asString();
+	}
+	return "";
+}
+
+LLNotificationTemplate::LLNotificationTemplate() :
+	mExpireSeconds(0),
+	mExpireOption(-1),
+	mURLOption(-1),
+	mUnique(false),
+	mPriority(NOTIFICATION_PRIORITY_NORMAL)
+{
+	mForm = LLNotificationFormPtr(new LLNotificationForm()); 
+}
+
+LLNotification::LLNotification(const LLNotification::Params& p) : 
+	mTimestamp(p.timestamp), 
+	mSubstitutions(p.substitutions),
+	mPayload(p.payload),
+	mExpiresAt(0),
+	mResponseFunctorName(p.functor_name),
+	mTemporaryResponder(p.mTemporaryResponder),
+	mRespondedTo(false),
+	mPriority(p.priority),
+	mCancelled(false),
+	mIgnored(false)
+{
+	mId.generate();
+	init(p.name, p.form_elements);
+}
+
+
+LLNotification::LLNotification(const LLSD& sd) :
+	mTemporaryResponder(false),
+	mRespondedTo(false),
+	mCancelled(false),
+	mIgnored(false)
+{ 
+	mId.generate();
+	mSubstitutions = sd["substitutions"];
+	mPayload = sd["payload"]; 
+	mTimestamp = sd["time"]; 
+	mExpiresAt = sd["expiry"];
+	mPriority = (ENotificationPriority)sd["priority"].asInteger();
+	mResponseFunctorName = sd["responseFunctor"].asString();
+	std::string templatename = sd["name"].asString();
+	init(templatename, LLSD());
+	// replace form with serialized version
+	mForm = LLNotificationFormPtr(new LLNotificationForm(sd["form"]));
+}
+
+
+LLSD LLNotification::asLLSD()
+{
+	LLSD output;
+	output["name"] = mTemplatep->mName;
+	output["form"] = getForm()->asLLSD();
+	output["substitutions"] = mSubstitutions;
+	output["payload"] = mPayload;
+	output["time"] = mTimestamp;
+	output["expiry"] = mExpiresAt;
+	output["priority"] = (S32)mPriority;
+	output["responseFunctor"] = mResponseFunctorName;
+	return output;
+}
+
+void LLNotification::update()
+{
+	LLNotifications::instance().update(shared_from_this());
+}
+
+void LLNotification::updateFrom(LLNotificationPtr other)
+{
+	// can only update from the same notification type
+	if (mTemplatep != other->mTemplatep) return;
+
+	// NOTE: do NOT change the ID, since it is the key to
+	// this given instance, just update all the metadata
+	//mId = other->mId;
+
+	mPayload = other->mPayload;
+	mSubstitutions = other->mSubstitutions;
+	mTimestamp = other->mTimestamp;
+	mExpiresAt = other->mExpiresAt;
+	mCancelled = other->mCancelled;
+	mIgnored = other->mIgnored;
+	mPriority = other->mPriority;
+	mForm = other->mForm;
+	mResponseFunctorName = other->mResponseFunctorName;
+	mRespondedTo = other->mRespondedTo;
+	mTemporaryResponder = other->mTemporaryResponder;
+
+	update();
+}
+
+const LLNotificationFormPtr LLNotification::getForm()
+{
+	return mForm;
+}
+
+void LLNotification::cancel()
+{
+	mCancelled = true;
+}
+
+LLSD LLNotification::getResponseTemplate(EResponseTemplateType type)
+{
+	LLSD response = LLSD::emptyMap();
+	for (S32 element_idx = 0;
+		element_idx < mForm->getNumElements();
+		++element_idx)
+	{
+		LLSD element = mForm->getElement(element_idx);
+		if (element.has("name"))
+		{
+			response[element["name"].asString()] = element["value"];
+		}
+
+		if ((type == WITH_DEFAULT_BUTTON) 
+			&& element["default"].asBoolean())
+		{
+			response[element["name"].asString()] = true;
+		}
+	}
+	return response;
+}
+
+//static
+S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response)
+{
+	LLNotificationForm form(notification["form"]);
+
+	for (S32 element_idx = 0;
+		element_idx < form.getNumElements();
+		++element_idx)
+	{
+		LLSD element = form.getElement(element_idx);
+
+		// only look at buttons
+		if (element["type"].asString() == "button" 
+			&& response[element["name"].asString()].asBoolean())
+		{
+			return element["index"].asInteger();
+		}
+	}
+
+	return -1;
+}
+
+//static
+std::string LLNotification::getSelectedOptionName(const LLSD& response)
+{
+	for (LLSD::map_const_iterator response_it = response.beginMap();
+		response_it != response.endMap();
+		++response_it)
+	{
+		if (response_it->second.isBoolean() && response_it->second.asBoolean())
+		{
+			return response_it->first;
+		}
+	}
+	return "";
+}
+
+
+void LLNotification::respond(const LLSD& response)
+{
+	mRespondedTo = true;
+	LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName)(asLLSD(), response);
+	if (mTemporaryResponder)
+	{
+		LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
+		mResponseFunctorName = "";
+		mTemporaryResponder = false;
+	}
+
+	if (mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO)
+	{
+		LLUI::sIgnoresGroup->setWarning(getName(), !mIgnored);
+		if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE)
+		{
+			LLUI::sIgnoresGroup->setLLSD("Default" + getName(), response);
+		}
+	}
+
+	update();
+}
+
+void LLNotification::setIgnored(bool ignore)
+{
+	mIgnored = ignore;
+}
+
+void LLNotification::setResponseFunctor(std::string const &responseFunctorName)
+{
+	if (mTemporaryResponder)
+		// get rid of the old one
+		LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
+	mResponseFunctorName = responseFunctorName;
+	mTemporaryResponder = false;
+}
+
+bool LLNotification::payloadContainsAll(const std::vector<std::string>& required_fields) const
+{
+	for(std::vector<std::string>::const_iterator required_fields_it = required_fields.begin(); 
+		required_fields_it != required_fields.end();
+		required_fields_it++)
+	{
+		std::string required_field_name = *required_fields_it;
+		if( ! getPayload().has(required_field_name))
+		{
+			return false; // a required field was not found
+		}
+	}
+	return true; // all required fields were found
+}
+
+bool LLNotification::isEquivalentTo(LLNotificationPtr that) const
+{
+	if (this->mTemplatep->mName != that->mTemplatep->mName) 
+	{
+		return false; // must have the same template name or forget it
+	}
+	if (this->mTemplatep->mUnique)
+	{
+		// highlander bit sez there can only be one of these
+		return
+			this->payloadContainsAll(that->mTemplatep->mUniqueContext) &&
+			that->payloadContainsAll(this->mTemplatep->mUniqueContext);
+	}
+	return false; 
+}
+
+void LLNotification::init(const std::string& template_name, const LLSD& form_elements)
+{
+	mTemplatep = LLNotifications::instance().getTemplate(template_name);
+	if (!mTemplatep) return;
+
+	// add default substitutions
+	// TODO: change this to read from the translatable strings file!
+	mSubstitutions["SECOND_LIFE"] = "Second Life";
+	mSubstitutions["_URL"] = getURL();
+	mSubstitutions["_NAME"] = template_name;
+	// TODO: something like this so that a missing alert is sensible:
+	//mSubstitutions["_ARGS"] = get_all_arguments_as_text(mSubstitutions);
+
+	mForm = LLNotificationFormPtr(new LLNotificationForm(*mTemplatep->mForm));
+	mForm->append(form_elements);
+
+	// apply substitution to form labels
+	mForm->formatElements(mSubstitutions);
+
+	LLDate rightnow = LLDate::now();
+	if (mTemplatep->mExpireSeconds)
+	{
+		mExpiresAt = LLDate(rightnow.secondsSinceEpoch() + mTemplatep->mExpireSeconds);
+	}
+
+	if (mPriority == NOTIFICATION_PRIORITY_UNSPECIFIED)
+	{
+		mPriority = mTemplatep->mPriority;
+	}
+}
+
+std::string LLNotification::summarize() const
+{
+	std::string s = "Notification(";
+	s += getName();
+	s += ") : ";
+	s += mTemplatep ? mTemplatep->mMessage : "";
+	// should also include timestamp and expiration time (but probably not payload)
+	return s;
+}
+
+//static
+std::string LLNotification::format(const std::string& s, const LLSD& substitutions)
+{
+	if (!substitutions.isMap()) 
+	{
+		return s;
+	}
+
+	std::ostringstream output;
+	// match strings like [NAME]
+	const boost::regex key("\\[([0-9_A-Z]+)]");
+	
+	std::string::const_iterator start = s.begin();
+	std::string::const_iterator end = s.end();
+	boost::smatch match;
+	
+	while (boost::regex_search(start, end, match, key, boost::match_default))
+	{
+		bool found_replacement = false;
+		std::string replacement;
+		
+		// see if we have a replacement for the bracketed string (without the brackets)
+		// test first using has() because if we just look up with operator[] we get back an
+		// empty string even if the value is missing. We want to distinguish between 
+		// missing replacements and deliberately empty replacement strings.
+		if (substitutions.has(std::string(match[1].first, match[1].second)))
+		{
+			replacement = substitutions[std::string(match[1].first, match[1].second)].asString();
+			found_replacement = true;
+		}
+		// if not, see if there's one WITH brackets
+		else if (substitutions.has(std::string(match[0].first, match[0].second)))
+		{
+			replacement = substitutions[std::string(match[0].first, match[0].second)].asString();
+			found_replacement = true;
+		}
+		
+		if (found_replacement)
+		{
+			// found a replacement
+			// "hello world" is output
+			output << std::string(start, match[0].first) << replacement;
+		}
+		else
+		{
+			// we had no replacement, so leave the string we searched for so that it gets noticed by QA
+			// "hello [NAME_NOT_FOUND]" is output
+			output << std::string(start, match[0].second);
+		}
+		
+		// update search position 
+		start = match[0].second; 
+	}
+	// send the remainder of the string (with no further matches for bracketed names)
+	output << std::string(start, end);
+	return output.str();
+}
+
+std::string LLNotification::getMessage() const
+{
+	// all our callers cache this result, so it gives us more flexibility
+	// to do the substitution at call time rather than attempting to 
+	// cache it in the notification
+	if (!mTemplatep)
+		return std::string();
+	return format(mTemplatep->mMessage, mSubstitutions);
+}
+
+std::string LLNotification::getLabel() const
+{
+	return (mTemplatep ? format(mTemplatep->mLabel, mSubstitutions) : "");
+}
+
+
+
+// =========================================================
+// LLNotificationChannel implementation
+// ---
+void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type& slot)
+{
+	// when someone wants to connect to a channel, we first throw them
+	// all of the notifications that are already in the channel
+	// we use a special signal called "load" in case the channel wants to care
+	// only about new notifications
+	for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
+	{
+		slot.get_slot_function()(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));
+	}
+	// and then connect the signal so that all future notifications will also be
+	// forwarded.
+	mChanged.connect(slot);
+}
+
+void LLNotificationChannelBase::connectPassedFilter(const LLStandardSignal::slot_type& slot)
+{
+	// these two filters only fire for notifications added after the current one, because
+	// they don't participate in the hierarchy.
+	mPassedFilter.connect(slot);
+}
+
+void LLNotificationChannelBase::connectFailedFilter(const LLStandardSignal::slot_type& slot)
+{
+	mFailedFilter.connect(slot);
+}
+
+// external call, conforms to our standard signature
+bool LLNotificationChannelBase::updateItem(const LLSD& payload)
+{	
+	// first check to see if it's in the master list
+	LLNotificationPtr pNotification	 = LLNotifications::instance().find(payload["id"]);
+	if (!pNotification)
+		return false;	// not found
+	
+	return updateItem(payload, pNotification);
+}
+
+
+//FIX QUIT NOT WORKING
+
+
+// internal call, for use in avoiding lookup
+bool LLNotificationChannelBase::updateItem(const LLSD& payload, LLNotificationPtr pNotification)
+{	
+	std::string cmd = payload["sigtype"];
+	LLNotificationSet::iterator foundItem = mItems.find(pNotification);
+	bool wasFound = (foundItem != mItems.end());
+	bool passesFilter = mFilter(pNotification);
+	
+	// first, we offer the result of the filter test to the simple
+	// signals for pass/fail. One of these is guaranteed to be called.
+	// If either signal returns true, the change processing is NOT performed
+	// (so don't return true unless you know what you're doing!)
+	bool abortProcessing = false;
+	if (passesFilter)
+	{
+		abortProcessing = mPassedFilter(payload);
+	}
+	else
+	{
+		abortProcessing = mFailedFilter(payload);
+	}
+	
+	if (abortProcessing)
+	{
+		return true;
+	}
+	
+	if (cmd == "load")
+	{
+		// should be no reason we'd ever get a load if we already have it
+		// if passes filter send a load message, else do nothing
+		assert(!wasFound);
+		if (passesFilter)
+		{
+			// not in our list, add it and say so
+			mItems.insert(pNotification);
+			abortProcessing = mChanged(payload);
+			onLoad(pNotification);
+		}
+	}
+	else if (cmd == "change")
+	{
+		// if it passes filter now and was found, we just send a change message
+		// if it passes filter now and wasn't found, we have to add it
+		// if it doesn't pass filter and wasn't found, we do nothing
+		// if it doesn't pass filter and was found, we need to delete it
+		if (passesFilter)
+		{
+			if (wasFound)
+			{
+				// it already existed, so this is a change
+				// since it changed in place, all we have to do is resend the signal
+				abortProcessing = mChanged(payload);
+				onChange(pNotification);
+			}
+			else
+			{
+				// not in our list, add it and say so
+				mItems.insert(pNotification);
+				// our payload is const, so make a copy before changing it
+				LLSD newpayload = payload;
+				newpayload["sigtype"] = "add";
+				abortProcessing = mChanged(newpayload);
+				onChange(pNotification);
+			}
+		}
+		else
+		{
+			if (wasFound)
+			{
+				// it already existed, so this is a delete
+				mItems.erase(pNotification);
+				// our payload is const, so make a copy before changing it
+				LLSD newpayload = payload;
+				newpayload["sigtype"] = "delete";
+				abortProcessing = mChanged(newpayload);
+				onChange(pNotification);
+			}
+			// didn't pass, not on our list, do nothing
+		}
+	}
+	else if (cmd == "add")
+	{
+		// should be no reason we'd ever get an add if we already have it
+		// if passes filter send an add message, else do nothing
+		assert(!wasFound);
+		if (passesFilter)
+		{
+			// not in our list, add it and say so
+			mItems.insert(pNotification);
+			abortProcessing = mChanged(payload);
+			onAdd(pNotification);
+		}
+	}
+	else if (cmd == "delete")
+	{
+		// if we have it in our list, pass on the delete, then delete it, else do nothing
+		if (wasFound)
+		{
+			abortProcessing = mChanged(payload);
+			mItems.erase(pNotification);
+			onDelete(pNotification);
+		}
+	}
+	return abortProcessing;
+}
+
+LLNotificationChannel::LLNotificationChannel(const std::string& name, 
+											 const std::string& parent,
+											 LLNotificationFilter filter, 
+											 LLNotificationComparator comparator) : 
+LLNotificationChannelBase(filter, comparator),
+mName(name),
+mParent(parent)
+{
+	// store myself in the channel map
+	LLNotifications::instance().addChannel(LLNotificationChannelPtr(this));
+	// bind to notification broadcast
+	if (parent.empty())
+	{
+		LLNotifications::instance().connectChanged(
+			boost::bind(&LLNotificationChannelBase::updateItem, this, _1));
+	}
+	else
+	{
+		LLNotificationChannelPtr p = LLNotifications::instance().getChannel(parent);
+		LLStandardSignal::slot_type f = boost::bind(&LLNotificationChannelBase::updateItem, this, _1);
+		p->connectChanged(f);
+	}
+}
+
+
+void LLNotificationChannel::setComparator(LLNotificationComparator comparator) 
+{ 
+	mComparator = comparator; 
+	LLNotificationSet s2(mComparator);
+	s2.insert(mItems.begin(), mItems.end());
+	mItems.swap(s2);
+	
+	// notify clients that we've been resorted
+	mChanged(LLSD().insert("sigtype", "sort")); 
+}
+
+bool LLNotificationChannel::isEmpty() const
+{
+	return mItems.empty();
+}
+
+LLNotificationChannel::Iterator LLNotificationChannel::begin()
+{
+	return mItems.begin();
+}
+
+LLNotificationChannel::Iterator LLNotificationChannel::end()
+{
+	return mItems.end();
+}
+
+std::string LLNotificationChannel::summarize()
+{
+	std::string s("Channel '");
+	s += mName;
+	s += "'\n  ";
+	for (LLNotificationChannel::Iterator it = begin(); it != end(); ++it)
+	{
+		s += (*it)->summarize();
+		s += "\n  ";
+	}
+	return s;
+}
+
+
+// ---
+// END OF LLNotificationChannel implementation
+// =========================================================
+
+
+// =========================================================
+// LLNotifications implementation
+// ---
+LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything,
+															   LLNotificationComparators::orderByUUID())
+{
+}
+
+
+// The expiration channel gets all notifications that are cancelled
+bool LLNotifications::expirationFilter(LLNotificationPtr pNotification)
+{
+	return pNotification->isCancelled() || pNotification->isRespondedTo();
+}
+
+bool LLNotifications::expirationHandler(const LLSD& payload)
+{
+	if (payload["sigtype"].asString() != "delete")
+	{
+		// anything added to this channel actually should be deleted from the master
+		cancel(find(payload["id"]));
+		return true;	// don't process this item any further
+	}
+	return false;
+}
+
+bool LLNotifications::uniqueFilter(LLNotificationPtr pNotif)
+{
+	if (!pNotif->hasUniquenessConstraints())
+	{
+		return true;
+	}
+
+	// checks against existing unique notifications
+	for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName());
+		existing_it != mUniqueNotifications.end();
+		++existing_it)
+	{
+		LLNotificationPtr existing_notification = existing_it->second;
+		if (pNotif != existing_notification 
+			&& pNotif->isEquivalentTo(existing_notification))
+		{
+			return false;
+		}
+	}
+
+	return true;
+}
+
+bool LLNotifications::uniqueHandler(const LLSD& payload)
+{
+	LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());
+	if (pNotif && pNotif->hasUniquenessConstraints()) 
+	{
+		if (payload["sigtype"].asString() == "add")
+		{
+			// not a duplicate according to uniqueness criteria, so we keep it
+			// and store it for future uniqueness checks
+			mUniqueNotifications.insert(std::make_pair(pNotif->getName(), pNotif));
+		}
+		else if (payload["sigtype"].asString() == "delete")
+		{
+			mUniqueNotifications.erase(pNotif->getName());
+		}
+	}
+
+	return false;
+}
+
+bool LLNotifications::failedUniquenessTest(const LLSD& payload)
+{
+	LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());
+	
+	if (!pNotif || !pNotif->hasUniquenessConstraints())
+	{
+		return false;
+	}
+
+	// checks against existing unique notifications
+	for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName());
+		existing_it != mUniqueNotifications.end();
+		++existing_it)
+	{
+		LLNotificationPtr existing_notification = existing_it->second;
+		if (pNotif != existing_notification 
+			&& pNotif->isEquivalentTo(existing_notification))
+		{
+			// copy notification instance data over to oldest instance
+			// of this unique notification and update it
+			existing_notification->updateFrom(pNotif);
+			// then delete the new one
+			pNotif->cancel();
+		}
+	}
+
+	return false;
+}
+
+
+void LLNotifications::addChannel(LLNotificationChannelPtr pChan)
+{
+	mChannels[pChan->getName()] = pChan;
+}
+
+LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelName)
+{
+	ChannelMap::iterator p = mChannels.find(channelName);
+	if(p == mChannels.end())
+	{
+		llerrs << "Did not find channel named " << channelName << llendl;
+	}
+	return p->second;
+}
+
+
+// this function is called once at construction time, after the object is constructed.
+void LLNotifications::initSingleton()
+{
+	loadTemplates();
+	createDefaultChannels();
+}
+
+void LLNotifications::createDefaultChannels()
+{
+	// now construct the various channels AFTER loading the notifications,
+	// because the history channel is going to rewrite the stored notifications file
+	new LLNotificationChannel("Expiration", "",
+		boost::bind(&LLNotifications::expirationFilter, this, _1));
+	new LLNotificationChannel("Unexpired", "",
+		!boost::bind(&LLNotifications::expirationFilter, this, _1)); // use negated bind
+	new LLNotificationChannel("Unique", "Unexpired",
+		boost::bind(&LLNotifications::uniqueFilter, this, _1));
+	new LLNotificationChannel("Ignore", "Unique",
+		filterIgnoredNotifications);
+	new LLNotificationChannel("Visible", "Ignore",
+		&LLNotificationFilters::includeEverything);
+
+	// create special history channel
+	//std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_PER_SL_ACCOUNT, "open_notifications.xml" );
+	// use ^^^ when done debugging notifications serialization
+	std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_USER_SETTINGS, "open_notifications.xml" );
+	new LLNotificationHistoryChannel(notifications_log_file);
+
+	// connect action methods to these channels
+	LLNotifications::instance().getChannel("Expiration")->
+		connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1));
+	LLNotifications::instance().getChannel("Unique")->
+		connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));
+	LLNotifications::instance().getChannel("Unique")->
+		connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1));
+	LLNotifications::instance().getChannel("Ignore")->
+		connectFailedFilter(&handleIgnoredNotification);
+}
+
+static std::string sStringSkipNextTime("Skip this dialog next time");
+static std::string sStringAlwaysChoose("Always choose this option");
+
+bool LLNotifications::addTemplate(const std::string &name, 
+								  LLNotificationTemplatePtr theTemplate)
+{
+	if (mTemplates.count(name))
+	{
+		llwarns << "LLNotifications -- attempted to add template '" << name << "' twice." << llendl;
+		return false;
+	}
+	mTemplates[name] = theTemplate;
+	return true;
+}
+
+LLNotificationTemplatePtr LLNotifications::getTemplate(const std::string& name)
+{
+	if (mTemplates.count(name))
+	{
+		return mTemplates[name];
+	}
+	else
+	{
+		return mTemplates["MissingAlert"];
+	}
+}
+
+bool LLNotifications::templateExists(const std::string& name)
+{
+	return (mTemplates.count(name) != 0);
+}
+
+void LLNotifications::clearTemplates()
+{
+	mTemplates.clear();
+}
+
+void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option)
+{
+	LLNotificationPtr temp_notify(new LLNotification(params));
+	LLSD response = temp_notify->getResponseTemplate();
+	LLSD selected_item = temp_notify->getForm()->getElement(option);
+	
+	if (selected_item.isUndefined())
+	{
+		llwarns << "Invalid option" << option << " for notification " << (std::string)params.name << llendl;
+		return;
+	}
+	response[selected_item["name"].asString()] = true;
+
+	temp_notify->respond(response);
+}
+
+LLNotifications::TemplateNames LLNotifications::getTemplateNames() const
+{
+	TemplateNames names;
+	for (TemplateMap::const_iterator it = mTemplates.begin(); it != mTemplates.end(); ++it)
+	{
+		names.push_back(it->first);
+	}
+	return names;
+}
+
+typedef std::map<std::string, std::string> StringMap;
+void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements)
+{
+	//llwarns << "replaceSubstitutionStrings" << llendl;
+	// walk the list of attributes looking for replacements
+	for (LLXMLAttribList::iterator it=node->mAttributes.begin();
+		 it != node->mAttributes.end(); ++it)
+	{
+		std::string value = it->second->getValue();
+		if (value[0] == '$')
+		{
+			value.erase(0, 1);	// trim off the $
+			std::string replacement;
+			StringMap::const_iterator found = replacements.find(value);
+			if (found != replacements.end())
+			{
+				replacement = found->second;
+				//llwarns << "replaceSubstituionStrings: value: " << value << " repl: " << replacement << llendl;
+
+				it->second->setValue(replacement);
+			}
+			else
+			{
+				llwarns << "replaceSubstituionStrings FAILURE: value: " << value << " repl: " << replacement << llendl;
+			}
+		}
+	}
+	
+	// now walk the list of children and call this recursively.
+	for (LLXMLNodePtr child = node->getFirstChild(); 
+		 child.notNull(); child = child->getNextSibling())
+	{
+		replaceSubstitutionStrings(child, replacements);
+	}
+}
+
+// private to this file
+// returns true if the template request was invalid and there's nothing else we
+// can do with this node, false if you should keep processing (it may have
+// replaced the contents of the node referred to)
+LLXMLNodePtr LLNotifications::checkForXMLTemplate(LLXMLNodePtr item)
+{
+	if (item->hasName("usetemplate"))
+	{
+		std::string replacementName;
+		if (item->getAttributeString("name", replacementName))
+		{
+			StringMap replacements;
+			for (LLXMLAttribList::const_iterator it=item->mAttributes.begin(); 
+				 it != item->mAttributes.end(); ++it)
+			{
+				replacements[it->second->getName()->mString] = it->second->getValue();
+			}
+			if (mXmlTemplates.count(replacementName))
+			{
+				item=LLXMLNode::replaceNode(item, mXmlTemplates[replacementName]);
+				
+				// walk the nodes looking for $(substitution) here and replace
+				replaceSubstitutionStrings(item, replacements);
+			}
+			else
+			{
+				llwarns << "XML template lookup failure on '" << replacementName << "' " << llendl;
+			}
+		}
+	}
+	return item;
+}
+
+bool LLNotifications::loadTemplates()
+{
+	const std::string xml_filename = "notifications.xml";
+	LLXMLNodePtr root;
+	
+	BOOL success  = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root);
+	
+	if (!success || root.isNull() || !root->hasName( "notifications" ))
+	{
+		llerrs << "Problem reading UI Notifications file: " << xml_filename << llendl;
+		return false;
+	}
+	
+	clearTemplates();
+	
+	for (LLXMLNodePtr item = root->getFirstChild();
+		 item.notNull(); item = item->getNextSibling())
+	{
+		// we do this FIRST so that item can be changed if we 
+		// encounter a usetemplate -- we just replace the
+		// current xml node and keep processing
+		item = checkForXMLTemplate(item);
+		
+		if (item->hasName("global"))
+		{
+			std::string global_name;
+			if (item->getAttributeString("name", global_name))
+			{
+				mGlobalStrings[global_name] = item->getTextContents();
+			}
+			continue;
+		}
+		
+		if (item->hasName("template"))
+		{
+			// store an xml template; templates must have a single node (can contain
+			// other nodes)
+			std::string name;
+			item->getAttributeString("name", name);
+			LLXMLNodePtr ptr = item->getFirstChild();
+			mXmlTemplates[name] = ptr;
+			continue;
+		}
+		
+		if (!item->hasName("notification"))
+		{
+            llwarns << "Unexpected entity " << item->getName()->mString << 
+                       " found in " << xml_filename << llendl;
+			continue;
+		}
+		
+		// now we know we have a notification entry, so let's build it
+		LLNotificationTemplatePtr pTemplate(new LLNotificationTemplate());
+
+		if (!item->getAttributeString("name", pTemplate->mName))
+		{
+			llwarns << "Unable to parse notification with no name" << llendl;
+			continue;
+		}
+		
+		//llinfos << "Parsing " << pTemplate->mName << llendl;
+		
+		pTemplate->mMessage = item->getTextContents();
+		pTemplate->mDefaultFunctor = pTemplate->mName;
+		item->getAttributeString("type", pTemplate->mType);
+		item->getAttributeString("icon", pTemplate->mIcon);
+		item->getAttributeString("label", pTemplate->mLabel);
+		item->getAttributeU32("duration", pTemplate->mExpireSeconds);
+		item->getAttributeU32("expireOption", pTemplate->mExpireOption);
+
+		std::string priority;
+		item->getAttributeString("priority", priority);
+		pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL;
+		if (!priority.empty())
+		{
+			if (priority == "low")      pTemplate->mPriority = NOTIFICATION_PRIORITY_LOW;
+			if (priority == "normal")   pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL;
+			if (priority == "high")     pTemplate->mPriority = NOTIFICATION_PRIORITY_HIGH;
+			if (priority == "critical") pTemplate->mPriority = NOTIFICATION_PRIORITY_CRITICAL;
+		}
+		
+		item->getAttributeString("functor", pTemplate->mDefaultFunctor);
+
+		BOOL persist = false;
+		item->getAttributeBOOL("persist", persist);
+		pTemplate->mPersist = persist;
+		
+		std::string sound;
+		item->getAttributeString("sound", sound);
+		if (!sound.empty())
+		{
+			// TODO: test for bad sound effect name / missing effect
+			pTemplate->mSoundEffect = LLUUID(LLUI::sConfigGroup->getString(sound.c_str()));
+		}
+
+		for (LLXMLNodePtr child = item->getFirstChild();
+			 !child.isNull(); child = child->getNextSibling())
+		{
+			child = checkForXMLTemplate(child);
+			
+			// <url>
+			if (child->hasName("url"))
+			{
+				pTemplate->mURL = child->getTextContents();
+				child->getAttributeU32("option", pTemplate->mURLOption);
+			}
+			
+            if (child->hasName("unique"))
+            {
+                pTemplate->mUnique = true;
+                for (LLXMLNodePtr formitem = child->getFirstChild();
+                     !formitem.isNull(); formitem = formitem->getNextSibling())
+                {
+                    if (formitem->hasName("context"))
+                    {
+                        std::string key;
+                        formitem->getAttributeString("key", key);
+                        pTemplate->mUniqueContext.push_back(key);
+                        //llwarns << "adding " << key << " to unique context" << llendl;
+                    }
+                    else
+                    {
+                        llwarns << "'unique' has unrecognized subelement " 
+                        << formitem->getName()->mString << llendl;
+                    }
+                }
+            }
+            
+			// <form>
+			if (child->hasName("form"))
+			{
+                pTemplate->mForm = LLNotificationFormPtr(new LLNotificationForm(pTemplate->mName, child));
+			}
+		}
+		addTemplate(pTemplate->mName, pTemplate);
+	}
+	
+	//std::ostringstream ostream;
+	//root->writeToOstream(ostream, "\n  ");
+	//llwarns << ostream.str() << llendl;
+	
+	return true;
+}
+
+// we provide a couple of simple add notification functions so that it's reasonable to create notifications in one line
+LLNotificationPtr LLNotifications::add(const std::string& name, 
+										const LLSD& substitutions, 
+										const LLSD& payload)
+{
+	return add(LLNotification::Params(name).substitutions(substitutions).payload(payload));	
+}
+
+LLNotificationPtr LLNotifications::add(const std::string& name, 
+										const LLSD& substitutions, 
+										const LLSD& payload, 
+										const std::string& functor_name)
+{
+	return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor_name(functor_name));	
+}
+
+LLNotificationPtr LLNotifications::add(const std::string& name, 
+										const LLSD& substitutions, 
+										const LLSD& payload, 
+										LLNotificationFunctorRegistry::ResponseFunctor functor)
+{
+	return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor(functor));	
+}
+
+// generalized add function that takes a parameter block object for more complex instantiations
+LLNotificationPtr LLNotifications::add(const LLNotification::Params& p)
+{
+	LLNotificationPtr pNotif(new LLNotification(p));
+	add(pNotif);
+	return pNotif;
+}
+
+
+void LLNotifications::add(const LLNotificationPtr pNotif)
+{
+	// first see if we already have it -- if so, that's a problem
+	LLNotificationSet::iterator it=mItems.find(pNotif);
+	if (it != mItems.end())
+	{
+		llerrs << "Notification added a second time to the master notification channel." << llendl;
+	}
+
+	updateItem(LLSD().insert("sigtype", "add").insert("id", pNotif->id()), pNotif);
+}
+
+void LLNotifications::cancel(LLNotificationPtr pNotif)
+{
+	LLNotificationSet::iterator it=mItems.find(pNotif);
+	if (it == mItems.end())
+	{
+		llerrs << "Attempted to delete nonexistent notification " << pNotif->getName() << llendl;
+	}
+	updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif);
+	pNotif->cancel();
+}
+
+void LLNotifications::update(const LLNotificationPtr pNotif)
+{
+	LLNotificationSet::iterator it=mItems.find(pNotif);
+	if (it != mItems.end())
+	{
+		updateItem(LLSD().insert("sigtype", "change").insert("id", pNotif->id()), pNotif);
+	}
+}
+
+
+LLNotificationPtr LLNotifications::find(LLUUID uuid)
+{
+	LLNotificationPtr target = LLNotificationPtr(new LLNotification(uuid));
+	LLNotificationSet::iterator it=mItems.find(target);
+	if (it == mItems.end())
+	{
+		llwarns << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << llendl;
+		return LLNotificationPtr((LLNotification*)NULL);
+	}
+	else
+	{
+		return *it;
+	}
+}
+
+void LLNotifications::forEachNotification(NotificationProcess process)
+{
+	std::for_each(mItems.begin(), mItems.end(), process);
+}
+
+std::string LLNotifications::getGlobalString(const std::string& key) const
+{
+	GlobalStringMap::const_iterator it = mGlobalStrings.find(key);
+	if (it != mGlobalStrings.end())
+	{
+		return it->second;
+	}
+	else
+	{
+		// if we don't have the key as a global, return the key itself so that the error
+		// is self-diagnosing.
+		return key;
+	}
+}
+
+													
+// ---
+// END OF LLNotifications implementation
+// =========================================================
+
+std::ostream& operator<<(std::ostream& s, const LLNotification& notification)
+{
+	s << notification.summarize();
+	return s;
+}
+
diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h
new file mode 100644
index 0000000000..2a2003f499
--- /dev/null
+++ b/indra/llui/llnotifications.h
@@ -0,0 +1,892 @@
+/**
+* @file llnotifications.h
+* @brief Non-UI manager and support for keeping a prioritized list of notifications
+* @author Q (with assistance from Richard and Coco)
+*
+* $LicenseInfo:firstyear=2008&license=viewergpl$
+* 
+* Copyright (c) 2008, 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_LLNOTIFICATIONS_H
+#define LL_LLNOTIFICATIONS_H
+
+/**
+ * This system is intended to provide a singleton mechanism for adding
+ * notifications to one of an arbitrary set of event channels.
+ * 
+ * Controlling JIRA: DEV-9061
+ *
+ * Every notification has (see code for full list):
+ *  - a textual name, which is used to look up its template in the XML files
+ *  - a payload, which is a block of LLSD
+ *  - a channel, which is normally extracted from the XML files but
+ *	  can be overridden.
+ *  - a timestamp, used to order the notifications
+ *  - expiration time -- if nonzero, specifies a time after which the
+ *    notification will no longer be valid.
+ *  - a callback name and a couple of status bits related to callbacks (see below)
+ * 
+ * There is a management class called LLNotifications, which is an LLSingleton.
+ * The class maintains a collection of all of the notifications received
+ * or processed during this session, and also manages the persistence
+ * of those notifications that must be persisted.
+ * 
+ * We also have Channels. A channel is a view on a collection of notifications;
+ * The collection is defined by a filter function that controls which
+ * notifications are in the channel, and its ordering is controlled by 
+ * a comparator. 
+ *
+ * There is a hierarchy of channels; notifications flow down from
+ * the management class (LLNotifications, which itself inherits from
+ * The channel base class) to the individual channels.
+ * Any change to notifications (add, delete, modify) is 
+ * automatically propagated through the channel hierarchy.
+ * 
+ * We provide methods for adding a new notification, for removing
+ * one, and for managing channels. Channels are relatively cheap to construct
+ * and maintain, so in general, human interfaces should use channels to
+ * select and manage their lists of notifications.
+ * 
+ * We also maintain a collection of templates that are loaded from the 
+ * XML file of template translations. The system supports substitution
+ * of named variables from the payload into the XML file.
+ * 
+ * By default, only the "unknown message" template is built into the system.
+ * It is not an error to add a notification that's not found in the 
+ * template system, but it is logged.
+ *
+ */
+
+#include <string>
+#include <list>
+#include <vector>
+#include <map>
+#include <set>
+#include <iomanip>
+#include <sstream>
+
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/signal.hpp>
+#include <boost/type_traits.hpp>
+
+// we want to minimize external dependencies, but this one is important
+#include "llsd.h"
+
+// and we need this to manage the notification callbacks
+#include "llfunctorregistry.h"
+#include "llui.h"
+
+class LLNotification;
+typedef boost::shared_ptr<LLNotification> LLNotificationPtr;
+
+/*****************************************************************************
+*   Signal and handler declarations
+*   Using a single handler signature means that we can have a common handler
+*   type, rather than needing a distinct one for each different handler.
+*****************************************************************************/
+
+/**
+ * A boost::signals Combiner that stops the first time a handler returns true
+ * We need this because we want to have our handlers return bool, so that
+ * we have the option to cause a handler to stop further processing. The
+ * default handler fails when the signal returns a value but has no slots.
+ */
+struct LLStopWhenHandled
+{
+    typedef bool result_type;
+
+    template<typename InputIterator>
+    result_type operator()(InputIterator first, InputIterator last) const
+    {
+        for (InputIterator si = first; si != last; ++si)
+		{
+            if (*si)
+			{
+                return true;
+			}
+		}
+        return false;
+    }
+};
+
+	
+typedef enum e_notification_priority
+{
+	NOTIFICATION_PRIORITY_UNSPECIFIED,
+	NOTIFICATION_PRIORITY_LOW,
+	NOTIFICATION_PRIORITY_NORMAL,
+	NOTIFICATION_PRIORITY_HIGH,
+	NOTIFICATION_PRIORITY_CRITICAL
+} ENotificationPriority;
+
+/**
+ * We want to have a standard signature for all signals; this way,
+ * we can easily document a protocol for communicating across
+ * dlls and into scripting languages someday.
+ * we want to return a bool to indicate whether the signal has been
+ * handled and should NOT be passed on to other listeners.
+ * Return true to stop further handling of the signal, and false
+ * to continue.
+ * We take an LLSD because this way the contents of the signal
+ * are independent of the API used to communicate it.
+ * It is const ref because then there's low cost to pass it;
+ * if you only need to inspect it, it's very cheap.
+ */
+
+typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder;
+
+typedef LLFunctorRegistry<LLNotificationResponder> LLNotificationFunctorRegistry;
+typedef LLFunctorRegistration<LLNotificationResponder> LLNotificationFunctorRegistration;
+
+typedef boost::signal<bool(const LLSD&), LLStopWhenHandled>  LLStandardSignal;
+
+// context data that can be looked up via a notification's payload by the display logic
+// derive from this class to implement specific contexts
+class LLNotificationContext : public LLInstanceTracker<LLNotificationContext, LLUUID>
+{
+public:
+	LLNotificationContext() : LLInstanceTracker<LLNotificationContext, LLUUID>(LLUUID::generateNewID())
+	{
+	}
+
+	virtual ~LLNotificationContext() {}
+
+	LLSD asLLSD() const
+	{
+		return getKey();
+	}
+
+private:
+
+};
+
+// Contains notification form data, such as buttons and text fields along with
+// manipulator functions
+class LLNotificationForm
+{
+	LOG_CLASS(LLNotificationForm);
+
+public:
+	typedef enum e_ignore_type
+	{ 
+		IGNORE_NO,
+		IGNORE_WITH_DEFAULT_RESPONSE, 
+		IGNORE_WITH_LAST_RESPONSE, 
+		IGNORE_SHOW_AGAIN 
+	} EIgnoreType;
+
+	LLNotificationForm();
+	LLNotificationForm(const LLSD& sd);
+	LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node);
+
+	LLSD asLLSD() const;
+
+	S32 getNumElements() { return mFormData.size(); }
+	LLSD getElement(S32 index) { return mFormData.get(index); }
+	LLSD getElement(const std::string& element_name);
+	bool hasElement(const std::string& element_name);
+	void addElement(const std::string& type, const std::string& name, const LLSD& value = LLSD());
+	void formatElements(const LLSD& substitutions);
+	// appends form elements from another form serialized as LLSD
+	void append(const LLSD& sub_form);
+	std::string getDefaultOption();
+
+	EIgnoreType getIgnoreType() { return mIgnore; }
+	std::string getIgnoreMessage() { return mIgnoreMsg; }
+
+private:
+	LLSD	mFormData;
+	EIgnoreType mIgnore;
+	std::string mIgnoreMsg;
+};
+
+typedef boost::shared_ptr<LLNotificationForm> LLNotificationFormPtr;
+
+// This is the class of object read from the XML file (notifications.xml, 
+// from the appropriate local language directory).
+struct LLNotificationTemplate
+{
+	LLNotificationTemplate();
+    // the name of the notification -- the key used to identify it
+    // Ideally, the key should follow variable naming rules 
+    // (no spaces or punctuation).
+    std::string mName;
+    // The type of the notification
+    // used to control which queue it's stored in
+    std::string mType;
+    // The text used to display the notification. Replaceable parameters
+    // are enclosed in square brackets like this [].
+    std::string mMessage;
+	// The label for the notification; used for 
+	// certain classes of notification (those with a window and a window title). 
+	// Also used when a notification pops up underneath the current one.
+	// Replaceable parameters can be used in the label.
+	std::string mLabel;
+	// The name of the icon image. This should include an extension.
+	std::string mIcon;
+    // This is the Highlander bit -- "There Can Be Only One"
+    // An outstanding notification with this bit set
+    // is updated by an incoming notification with the same name,
+    // rather than creating a new entry in the queue.
+    // (used for things like progress indications, or repeating warnings
+    // like "the grid is going down in N minutes")
+    bool mUnique;
+    // if we want to be unique only if a certain part of the payload is constant
+    // specify the field names for the payload. The notification will only be
+    // combined if all of the fields named in the context are identical in the
+    // new and the old notification; otherwise, the notification will be
+    // duplicated. This is to support suppressing duplicate offers from the same
+    // sender but still differentiating different offers. Example: Invitation to
+    // conference chat.
+    std::vector<std::string> mUniqueContext;
+    // If this notification expires automatically, this value will be 
+    // nonzero, and indicates the number of seconds for which the notification
+    // will be valid (a teleport offer, for example, might be valid for 
+    // 300 seconds). 
+    U32 mExpireSeconds;
+    // if the offer expires, one of the options is chosen automatically
+    // based on its "value" parameter. This controls which one. 
+    // If expireSeconds is specified, expireOption should also be specified.
+    U32 mExpireOption;
+    // if the notification contains a url, it's stored here (and replaced 
+    // into the message where [_URL] is found)
+    std::string mURL;
+    // if there's a URL in the message, this controls which option visits
+    // that URL. Obsolete this and eliminate the buttons for affected
+    // messages when we allow clickable URLs in the UI
+    U32 mURLOption;
+	// does this notification persist across sessions? if so, it will be
+	// serialized to disk on first receipt and read on startup
+	bool mPersist;
+	// This is the name of the default functor, if present, to be
+	// used for the notification's callback. It is optional, and used only if 
+	// the notification is constructed without an identified functor.
+	std::string mDefaultFunctor;
+	// The form data associated with a given notification (buttons, text boxes, etc)
+    LLNotificationFormPtr mForm;
+	// default priority for notifications of this type
+	ENotificationPriority mPriority;
+	// UUID of the audio file to be played when this notification arrives
+	// this is loaded as a name, but looked up to get the UUID upon template load.
+	// If null, it wasn't specified.
+	LLUUID mSoundEffect;
+};
+
+// we want to keep a map of these by name, and it's best to manage them
+// with smart pointers
+typedef boost::shared_ptr<LLNotificationTemplate> LLNotificationTemplatePtr;
+
+/**
+ * @class LLNotification
+ * @brief The object that expresses the details of a notification
+ * 
+ * We make this noncopyable because
+ * we want to manage these through LLNotificationPtr, and only
+ * ever create one instance of any given notification.
+ * 
+ * The enable_shared_from_this flag ensures that if we construct
+ * a smart pointer from a notification, we'll always get the same
+ * shared pointer.
+ */
+class LLNotification  : 
+	boost::noncopyable,
+	public boost::enable_shared_from_this<LLNotification>
+{
+LOG_CLASS(LLNotification);
+friend class LLNotifications;
+
+public:
+	// parameter object used to instantiate a new notification
+	class Params : public LLParamBlock<Params>
+	{
+		friend class LLNotification;
+	public:
+		Params(const std::string& _name) 
+			:	name(_name),
+				mTemporaryResponder(false),
+				functor_name(_name),
+				priority(NOTIFICATION_PRIORITY_UNSPECIFIED),
+				timestamp(LLDate::now())
+		{
+		}
+
+		// pseudo-param
+		Params& functor(LLNotificationFunctorRegistry::ResponseFunctor f) 
+		{ 	
+			functor_name = LLUUID::generateNewID().asString();
+			LLNotificationFunctorRegistry::instance().registerFunctor(functor_name, f);
+
+			mTemporaryResponder = true;
+			return *this;
+		}
+
+		LLMandatoryParam<std::string>					name;
+
+		// optional
+		LLOptionalParam<LLSD>							substitutions;
+		LLOptionalParam<LLSD>							payload;
+		LLOptionalParam<ENotificationPriority>			priority;
+		LLOptionalParam<LLSD>							form_elements;
+		LLOptionalParam<LLDate>							timestamp;
+		LLOptionalParam<LLNotificationContext*>			context;
+		LLOptionalParam<std::string>					functor_name;
+
+	private:
+		bool					mTemporaryResponder;
+	};
+
+private:
+	
+	LLUUID mId;
+	LLSD mPayload;
+	LLSD mSubstitutions;
+	LLDate mTimestamp;
+	LLDate mExpiresAt;
+	bool mCancelled;
+	bool mRespondedTo; 	// once the notification has been responded to, this becomes true
+	bool mIgnored;
+	ENotificationPriority mPriority;
+	LLNotificationFormPtr mForm;
+	
+	// a reference to the template
+	LLNotificationTemplatePtr mTemplatep;
+	
+	/*
+	 We want to be able to store and reload notifications so that they can survive
+	 a shutdown/restart of the client. So we can't simply pass in callbacks;
+	 we have to specify a callback mechanism that can be used by name rather than 
+	 by some arbitrary pointer -- and then people have to initialize callbacks 
+	 in some useful location. So we use LLNotificationFunctorRegistry to manage them.
+	 */
+	 std::string mResponseFunctorName;
+	
+	/*
+	 In cases where we want to specify an explict, non-persisted callback, 
+	 we store that in the callback registry under a dynamically generated
+	 key, and store the key in the notification, so we can still look it up
+	 using the same mechanism.
+	 */
+	bool mTemporaryResponder;
+
+	void init(const std::string& template_name, const LLSD& form_elements);
+
+	LLNotification(const Params& p);
+
+	// this is just for making it easy to look things up in a set organized by UUID -- DON'T USE IT
+	// for anything real!
+	LLNotification(LLUUID uuid) : mId(uuid) {}
+
+	void cancel();
+
+	bool payloadContainsAll(const std::vector<std::string>& required_fields) const;
+
+public:
+
+	// constructor from a saved notification
+	LLNotification(const LLSD& sd);
+
+	// This is a string formatter for substituting into the message directly 
+	// from LLSD without going through the hopefully-to-be-obsoleted LLString
+	static std::string format(const std::string& text, const LLSD& substitutions);
+
+	void setResponseFunctor(std::string const &responseFunctorName);
+
+	typedef enum e_response_template_type
+	{
+		WITHOUT_DEFAULT_BUTTON,
+		WITH_DEFAULT_BUTTON
+	} EResponseTemplateType;
+
+	// return response LLSD filled in with default form contents and (optionally) the default button selected
+	LLSD getResponseTemplate(EResponseTemplateType type = WITHOUT_DEFAULT_BUTTON);
+
+	// returns index of first button with value==TRUE
+	// usually this the button the user clicked on
+	// returns -1 if no button clicked (e.g. form has not been displayed)
+	static S32 getSelectedOption(const LLSD& notification, const LLSD& response);
+	// returns name of first button with value==TRUE
+	static std::string getSelectedOptionName(const LLSD& notification);
+
+	// after someone responds to a notification (usually by clicking a button,
+	// but sometimes by filling out a little form and THEN clicking a button),
+    // the result of the response (the name and value of the button clicked,
+	// plus any other data) should be packaged up as LLSD, then passed as a
+	// parameter to the notification's respond() method here. This will look up
+	// and call the appropriate responder.
+	//
+	// response is notification serialized as LLSD:
+	// ["name"] = notification name
+	// ["form"] = LLSD tree that includes form description and any prefilled form data
+	// ["response"] = form data filled in by user
+	// (including, but not limited to which button they clicked on)
+	// ["payload"] = transaction specific data, such as ["source_id"] (originator of notification),  
+	//				["item_id"] (attached inventory item), etc.
+	// ["substitutions"] = string substitutions used to generate notification message
+    // from the template
+	// ["time"] = time at which notification was generated;
+	// ["expiry"] = time at which notification expires;
+	// ["responseFunctor"] = name of registered functor that handles responses to notification;
+	LLSD asLLSD();
+
+	void respond(const LLSD& sd);
+
+	void setIgnored(bool ignore);
+
+	bool isCancelled() const
+	{
+		return mCancelled;
+	}
+
+	bool isRespondedTo() const
+	{
+		return mRespondedTo;
+	}
+
+	bool isIgnored() const
+	{
+		return mIgnored;
+	}
+
+	const std::string& getName() const
+	{
+		return mTemplatep->mName;
+	}
+	
+	const LLUUID& id() const
+	{
+		return mId;
+	}
+	
+	const LLSD& getPayload() const
+	{
+		return mPayload;
+	}
+
+	const LLSD& getSubstitutions() const
+	{
+		return mSubstitutions;
+	}
+
+	const LLDate& getDate() const
+	{
+		return mTimestamp;
+	}
+
+	std::string getType() const
+	{
+		return (mTemplatep ? mTemplatep->mType : "");
+	}
+
+	std::string getMessage() const;
+	std::string getLabel() const;
+
+	std::string getURL() const
+	{
+		return (mTemplatep ? mTemplatep->mURL : "");
+	}
+
+	S32 getURLOption() const
+	{
+		return (mTemplatep ? mTemplatep->mURLOption : -1);
+	}
+
+	const LLNotificationFormPtr getForm();
+
+	const LLDate getExpiration() const
+	{
+		return mExpiresAt;
+	}
+
+	ENotificationPriority getPriority() const
+	{
+		return mPriority;
+	}
+
+	const LLUUID getID() const
+	{
+		return mId;
+	}
+	
+	// comparing two notifications normally means comparing them by UUID (so we can look them
+	// up quickly this way)
+	bool operator<(const LLNotification& rhs) const
+	{
+		return mId < rhs.mId;
+	}
+
+	bool operator==(const LLNotification& rhs) const
+	{
+		return mId == rhs.mId;
+	}
+
+	bool operator!=(const LLNotification& rhs) const
+	{
+		return !operator==(rhs);
+	}
+
+	bool isSameObjectAs(const LLNotification* rhs) const
+	{
+		return this == rhs;
+	}
+	
+	// this object has been updated, so tell all our clients
+	void update();
+
+	void updateFrom(LLNotificationPtr other);
+	
+	// A fuzzy equals comparator.
+	// true only if both notifications have the same template and 
+	//     1) flagged as unique (there can be only one of these) OR 
+	//     2) all required payload fields of each also exist in the other.
+	bool isEquivalentTo(LLNotificationPtr that) const;
+	
+	// if the current time is greater than the expiration, the notification is expired
+	bool isExpired() const
+	{
+		if (mExpiresAt.secondsSinceEpoch() == 0)
+		{
+			return false;
+		}
+		
+		LLDate rightnow = LLDate::now();
+		return rightnow > mExpiresAt;
+	}
+	
+	std::string summarize() const;
+
+	bool hasUniquenessConstraints() const { return (mTemplatep ? mTemplatep->mUnique : false);}
+
+	virtual ~LLNotification() {}
+};
+
+std::ostream& operator<<(std::ostream& s, const LLNotification& notification);
+
+namespace LLNotificationFilters
+{
+	// a sample filter
+	bool includeEverything(LLNotificationPtr p);
+
+	typedef enum e_comparison 
+	{ 
+		EQUAL, 
+		LESS, 
+		GREATER, 
+		LESS_EQUAL, 
+		GREATER_EQUAL 
+	} EComparison;
+
+	// generic filter functor that takes method or member variable reference
+	template<typename T>
+	struct filterBy
+	{
+		typedef boost::function<T (LLNotificationPtr)>	field_t;
+		typedef typename boost::remove_reference<T>::type		value_t;
+		
+		filterBy(field_t field, value_t value, EComparison comparison = EQUAL) 
+			:	mField(field), 
+				mFilterValue(value),
+				mComparison(comparison)
+		{
+		}		
+		
+		bool operator()(LLNotificationPtr p)
+		{
+			switch(mComparison)
+			{
+			case EQUAL:
+				return mField(p) == mFilterValue;
+			case LESS:
+				return mField(p) < mFilterValue;
+			case GREATER:
+				return mField(p) > mFilterValue;
+			case LESS_EQUAL:
+				return mField(p) <= mFilterValue;
+			case GREATER_EQUAL:
+				return mField(p) >= mFilterValue;
+			default:
+				return false;
+			}
+		}
+
+		field_t mField;
+		value_t	mFilterValue;
+		EComparison mComparison;
+	};
+};
+
+namespace LLNotificationComparators
+{
+	typedef enum e_direction { ORDER_DECREASING, ORDER_INCREASING } EDirection;
+
+	// generic order functor that takes method or member variable reference
+	template<typename T>
+	struct orderBy
+	{
+		typedef boost::function<T (LLNotificationPtr)> field_t;
+		orderBy(field_t field, EDirection = ORDER_INCREASING) : mField(field) {}
+		bool operator()(LLNotificationPtr lhs, LLNotificationPtr rhs)
+		{
+			if (mDirection == ORDER_DECREASING)
+			{
+				return mField(lhs) > mField(rhs);
+			}
+			else
+			{
+				return mField(lhs) < mField(rhs);
+			}
+		}
+
+		field_t mField;
+		EDirection mDirection;
+	};
+
+	struct orderByUUID : public orderBy<const LLUUID&>
+	{
+		orderByUUID(EDirection direction = ORDER_INCREASING) : orderBy<const LLUUID&>(&LLNotification::id, direction) {}
+	};
+
+	struct orderByDate : public orderBy<const LLDate&>
+	{
+		orderByDate(EDirection direction = ORDER_INCREASING) : orderBy<const LLDate&>(&LLNotification::getDate, direction) {}
+	};
+};
+
+typedef boost::function<bool (LLNotificationPtr)> LLNotificationFilter;
+typedef boost::function<bool (LLNotificationPtr, LLNotificationPtr)> LLNotificationComparator;
+typedef std::set<LLNotificationPtr, LLNotificationComparator> LLNotificationSet;
+typedef std::multimap<std::string, LLNotificationPtr> LLNotificationMap;
+
+// ========================================================
+// Abstract base class (interface) for a channel; also used for the master container.
+// This lets us arrange channels into a call hierarchy.
+
+// We maintain a heirarchy of notification channels; events are always started at the top
+// and propagated through the hierarchy only if they pass a filter.
+// Any channel can be created with a parent. A null parent (empty string) means it's
+// tied to the root of the tree (the LLNotifications class itself).
+// The default hierarchy looks like this:
+//
+// LLNotifications --+-- Expiration --+-- Mute --+-- Ignore --+-- Visible --+-- History
+//                                                                          +-- Alerts
+//                                                                          +-- Notifications
+//
+// In general, new channels that want to only see notifications that pass through 
+// all of the built-in tests should attach to the "Visible" channel
+//
+class LLNotificationChannelBase :
+	public boost::signals::trackable
+{
+	LOG_CLASS(LLNotificationChannelBase);
+public:
+	LLNotificationChannelBase(LLNotificationFilter filter, LLNotificationComparator comp) : 
+		mFilter(filter), mItems(comp) 
+	{}
+	virtual ~LLNotificationChannelBase() {}
+	// you can also connect to a Channel, so you can be notified of
+	// changes to this channel
+	virtual void connectChanged(const LLStandardSignal::slot_type& slot);
+	virtual void connectPassedFilter(const LLStandardSignal::slot_type& slot);
+	virtual void connectFailedFilter(const LLStandardSignal::slot_type& slot);
+
+	// use this when items change or to add a new one
+	bool updateItem(const LLSD& payload);
+	const LLNotificationFilter& getFilter() { return mFilter; }
+
+protected:
+	LLNotificationSet mItems;
+	LLStandardSignal mChanged;
+	LLStandardSignal mPassedFilter;
+	LLStandardSignal mFailedFilter;
+	
+	// these are action methods that subclasses can override to take action 
+	// on specific types of changes; the management of the mItems list is
+	// still handled by the generic handler.
+	virtual void onLoad(LLNotificationPtr p) {}
+	virtual void onAdd(LLNotificationPtr p) {}
+	virtual void onDelete(LLNotificationPtr p) {}
+	virtual void onChange(LLNotificationPtr p) {}
+
+	bool updateItem(const LLSD& payload, LLNotificationPtr pNotification);
+	LLNotificationFilter mFilter;
+};
+
+// manages a list of notifications
+// Note that if this is ever copied around, we might find ourselves with multiple copies
+// of a queue with notifications being added to different nonequivalent copies. So we 
+// make it inherit from boost::noncopyable, and then create a map of shared_ptr to manage it.
+// 
+// NOTE: LLNotificationChannel is self-registering. The *correct* way to create one is to 
+// do something like:
+//		new LLNotificationChannel("name", "parent"...);
+// You can then retrieve the channel by using the registry:
+//		LLNotifications::instance().getChannel("name")...
+//
+class LLNotificationChannel : 
+	boost::noncopyable, 
+	public LLNotificationChannelBase
+{
+	LOG_CLASS(LLNotificationChannel);
+
+public:  
+	virtual ~LLNotificationChannel() {}
+    // Notification Channels have a filter, which determines which notifications
+	// will be added to this channel. 
+	// Channel filters cannot change.
+	LLNotificationChannel(const std::string& name, const std::string& parent,
+						LLNotificationFilter filter=LLNotificationFilters::includeEverything, 
+						LLNotificationComparator comparator=LLNotificationComparators::orderByUUID());
+
+	typedef LLNotificationSet::iterator Iterator;
+    
+	std::string getName() const { return mName; }
+	std::string getParentChannelName() { return mParent; }
+    
+    bool isEmpty() const;
+    
+    Iterator begin();
+    Iterator end();
+
+    // Channels have a comparator to control sort order;
+	// the default sorts by arrival date
+    void setComparator(LLNotificationComparator comparator);
+	
+	std::string summarize();
+
+private:
+	std::string mName;
+	std::string mParent;
+	LLNotificationComparator mComparator;
+};
+
+
+
+// The type of the pointers that we're going to manage in the NotificationQueue system
+// Because LLNotifications is a singleton, we don't actually expect to ever 
+// destroy it, but if it becomes necessary to do so, the shared_ptr model
+// will ensure that we don't leak resources.
+typedef boost::shared_ptr<LLNotificationChannel> LLNotificationChannelPtr;
+
+class LLNotifications : 
+	public LLSingleton<LLNotifications>, 
+	public LLNotificationChannelBase
+{
+	LOG_CLASS(LLNotifications);
+
+	friend class LLSingleton<LLNotifications>;
+public:
+	// load notification descriptions from file; 
+	// OK to call more than once because it will reload
+	bool loadTemplates();  
+	LLXMLNodePtr checkForXMLTemplate(LLXMLNodePtr item);
+
+	// we provide a collection of simple add notification functions so that it's reasonable to create notifications in one line
+	LLNotificationPtr add(const std::string& name, 
+						const LLSD& substitutions = LLSD(), 
+						const LLSD& payload = LLSD());
+	LLNotificationPtr add(const std::string& name, 
+						const LLSD& substitutions, 
+						const LLSD& payload, 
+						const std::string& functor_name);
+	LLNotificationPtr add(const std::string& name, 
+						const LLSD& substitutions, 
+						const LLSD& payload, 
+						LLNotificationFunctorRegistry::ResponseFunctor functor);
+	LLNotificationPtr add(const LLNotification::Params& p);
+
+	void add(const LLNotificationPtr pNotif);
+	void cancel(LLNotificationPtr pNotif);
+	void update(const LLNotificationPtr pNotif);
+
+	LLNotificationPtr find(LLUUID uuid);
+	
+	typedef boost::function<void (LLNotificationPtr)> NotificationProcess;
+	
+	void forEachNotification(NotificationProcess process);
+
+	// This is all stuff for managing the templates
+	// take your template out
+	LLNotificationTemplatePtr getTemplate(const std::string& name);
+	
+	// get the whole collection
+	typedef std::vector<std::string> TemplateNames;
+	TemplateNames getTemplateNames() const;  // returns a list of notification names
+	
+	typedef std::map<std::string, LLNotificationTemplatePtr> TemplateMap;
+
+	TemplateMap::const_iterator templatesBegin() { return mTemplates.begin(); }
+	TemplateMap::const_iterator templatesEnd() { return mTemplates.end(); }
+
+	// test for existence
+	bool templateExists(const std::string& name);
+	// useful if you're reloading the file
+	void clearTemplates();   // erase all templates
+
+	void forceResponse(const LLNotification::Params& params, S32 option);
+
+	void createDefaultChannels();
+
+	typedef std::map<std::string, LLNotificationChannelPtr> ChannelMap;
+	ChannelMap mChannels;
+
+	void addChannel(LLNotificationChannelPtr pChan);
+	LLNotificationChannelPtr getChannel(const std::string& channelName);
+	
+	std::string getGlobalString(const std::string& key) const;
+
+private:
+	// we're a singleton, so we don't have a public constructor
+	LLNotifications();
+	/*virtual*/ void initSingleton();
+	
+	void loadPersistentNotifications();
+
+	bool expirationFilter(LLNotificationPtr pNotification);
+	bool expirationHandler(const LLSD& payload);
+	bool uniqueFilter(LLNotificationPtr pNotification);
+	bool uniqueHandler(const LLSD& payload);
+	bool failedUniquenessTest(const LLSD& payload);
+	LLNotificationChannelPtr pHistoryChannel;
+	LLNotificationChannelPtr pExpirationChannel;
+	
+	// put your template in
+	bool addTemplate(const std::string& name, LLNotificationTemplatePtr theTemplate);
+	TemplateMap mTemplates;
+
+	std::string mFileName;
+	
+	typedef std::map<std::string, LLXMLNodePtr> XMLTemplateMap;
+	XMLTemplateMap mXmlTemplates;
+
+	LLNotificationMap mUniqueNotifications;
+	
+	typedef std::map<std::string, std::string> GlobalStringMap;
+	GlobalStringMap mGlobalStrings;
+};
+
+
+#endif//LL_LLNOTIFICATIONS_H
+
diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp
index 22934450e7..2edbbe6abc 100644
--- a/indra/llui/llpanel.cpp
+++ b/indra/llui/llpanel.cpp
@@ -58,8 +58,6 @@
 #include "llresizebar.h"
 #include "llcriticaldamp.h"
 
-LLPanel::alert_queue_t LLPanel::sAlertQueue;
-
 const S32 RESIZE_BAR_OVERLAP = 1;
 const S32 RESIZE_BAR_HEIGHT = 3;
 
@@ -344,14 +342,14 @@ BOOL LLPanel::checkRequirements()
 {
 	if (!mRequirementsError.empty())
 	{
-		LLStringUtil::format_map_t args;
-		args["[COMPONENTS]"] = mRequirementsError;
-		args["[FLOATER]"] = getName();
+		LLSD args;
+		args["COMPONENTS"] = mRequirementsError;
+		args["FLOATER"] = getName();
 
 		llwarns << getName() << " failed requirements check on: \n"  
 				<< mRequirementsError << llendl;
-			
-		alertXml(std::string("FailedRequirementsCheck"), args);
+		
+		LLNotifications::instance().add(LLNotification::Params("FailedRequirementsCheck").payload(args));
 		mRequirementsError.clear();
 		return FALSE;
 	}
@@ -359,25 +357,6 @@ BOOL LLPanel::checkRequirements()
 	return TRUE;
 }
 
-//static
-void LLPanel::alertXml(const std::string& label, LLStringUtil::format_map_t args)
-{
-	sAlertQueue.push(LLAlertInfo(label,args));
-}
-
-//static
-BOOL LLPanel::nextAlert(LLAlertInfo &alert)
-{
-	if (!sAlertQueue.empty())
-	{
-		alert = sAlertQueue.front();
-		sAlertQueue.pop();
-		return TRUE;
-	}
-
-	return FALSE;
-}
-
 void LLPanel::setFocus(BOOL b)
 {
 	if( b )
@@ -1039,9 +1018,9 @@ void LLPanel::childDisplayNotFound()
 		mExpectedMembers.insert(*itor);
 	}
 	mNewExpectedMembers.clear();
-	LLStringUtil::format_map_t args;
-	args["[CONTROLS]"] = msg;
-	LLAlertDialog::showXml("FloaterNotFound", args);
+	LLSD args;
+	args["CONTROLS"] = msg;
+	LLNotifications::instance().add("FloaterNotFound", args);
 }
 
 void LLPanel::storeRectControl()
@@ -1065,6 +1044,8 @@ struct LLLayoutStack::LLEmbeddedPanel
 			mAutoResize(auto_resize),
 			mUserResize(user_resize),
 			mOrientation(orientation),
+			mCollapsed(FALSE),
+			mCollapseAmt(0.f),
 			mVisibleAmt(1.f) // default to fully visible
 	{
 		LLResizeBar::Side side = (orientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM;
@@ -1095,14 +1076,28 @@ struct LLLayoutStack::LLEmbeddedPanel
 		mResizeBar = NULL;
 	}
 
+	F32 getCollapseFactor()
+	{
+		if (mOrientation == HORIZONTAL)
+		{
+			return mVisibleAmt * clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinWidth / (F32)mPanel->getRect().getWidth());
+		}
+		else
+		{
+			return mVisibleAmt * clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinHeight / (F32)mPanel->getRect().getHeight());
+		}
+	}
+
 	LLPanel* mPanel;
 	S32 mMinWidth;
 	S32 mMinHeight;
 	BOOL mAutoResize;
 	BOOL mUserResize;
+	BOOL mCollapsed;
 	LLResizeBar* mResizeBar;
 	eLayoutOrientation mOrientation;
 	F32 mVisibleAmt;
+	F32 mCollapseAmt;
 };
 
 static LLRegisterWidget<LLLayoutStack> r2("layout_stack");
@@ -1123,28 +1118,27 @@ LLLayoutStack::~LLLayoutStack()
 void LLLayoutStack::draw()
 {
 	updateLayout();
+
+	e_panel_list_t::iterator panel_it;
+	for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it)
 	{
-		e_panel_list_t::iterator panel_it;
-		for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it)
+		// clip to layout rectangle, not bounding rectangle
+		LLRect clip_rect = (*panel_it)->mPanel->getRect();
+		// scale clipping rectangle by visible amount
+		if (mOrientation == HORIZONTAL)
 		{
-			// clip to layout rectangle, not bounding rectangle
-			LLRect clip_rect = (*panel_it)->mPanel->getRect();
-			// scale clipping rectangle by visible amount
-			if (mOrientation == HORIZONTAL)
-			{
-				clip_rect.mRight = clip_rect.mLeft + llround((F32)clip_rect.getWidth() * (*panel_it)->mVisibleAmt);
-			}
-			else
-			{
-				clip_rect.mBottom = clip_rect.mTop - llround((F32)clip_rect.getHeight() * (*panel_it)->mVisibleAmt);
-			}
+			clip_rect.mRight = clip_rect.mLeft + llround((F32)clip_rect.getWidth() * (*panel_it)->getCollapseFactor());
+		}
+		else
+		{
+			clip_rect.mBottom = clip_rect.mTop - llround((F32)clip_rect.getHeight() * (*panel_it)->getCollapseFactor());
+		}
 
-			LLPanel* panelp = (*panel_it)->mPanel;
+		LLPanel* panelp = (*panel_it)->mPanel;
 
-			LLLocalClipRect clip(clip_rect);
-			// only force drawing invisible children if visible amount is non-zero
-			drawChild(panelp, 0, 0, !clip_rect.isNull());
-		}
+		LLLocalClipRect clip(clip_rect);
+		// only force drawing invisible children if visible amount is non-zero
+		drawChild(panelp, 0, 0, !clip_rect.isNull());
 	}
 }
 
@@ -1276,8 +1270,13 @@ S32 LLLayoutStack::getDefaultWidth(S32 cur_width)
 	return cur_width;
 }
 
-void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, S32 index)
+void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, EAnimate animate, S32 index)
 {
+	// panel starts off invisible (collapsed)
+	if (animate == ANIMATE)
+	{
+		panel->setVisible(FALSE);
+	}
 	LLEmbeddedPanel* embedded_panel = new LLEmbeddedPanel(panel, mOrientation, min_width, min_height, auto_resize, user_resize);
 	
 	mPanels.insert(mPanels.begin() + llclamp(index, 0, (S32)mPanels.size()), embedded_panel);
@@ -1293,6 +1292,11 @@ void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL
 		sendChildToFront(resize_barp);
 	}
 
+	// start expanding panel animation
+	if (animate == ANIMATE)
+	{
+		panel->setVisible(TRUE);
+	}
 }
 
 void LLLayoutStack::removePanel(LLPanel* panel)
@@ -1300,6 +1304,14 @@ void LLLayoutStack::removePanel(LLPanel* panel)
 	removeChild(panel);
 }
 
+void LLLayoutStack::collapsePanel(LLPanel* panel, BOOL collapsed)
+{
+	LLEmbeddedPanel* panel_container = findEmbeddedPanel(panel);
+	if (!panel_container) return;
+
+	panel_container->mCollapsed = collapsed;
+}
+
 void LLLayoutStack::updateLayout(BOOL force_resize)
 {
 	calcMinExtents();
@@ -1332,6 +1344,15 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
 			}
 		}
 
+		if ((*panel_it)->mCollapsed)
+		{
+			(*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME));
+		}
+		else
+		{
+			(*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME));
+		}
+
 		if (mOrientation == HORIZONTAL)
 		{
 			// enforce minimize size constraint by default
@@ -1339,7 +1360,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
 			{
 				panelp->reshape((*panel_it)->mMinWidth, panelp->getRect().getHeight());
 			}
-        	total_width += llround(panelp->getRect().getWidth() * (*panel_it)->mVisibleAmt);
+        	total_width += llround(panelp->getRect().getWidth() * (*panel_it)->getCollapseFactor());
         	// want n-1 panel gaps for n panels
 			if (panel_it != mPanels.begin())
 			{
@@ -1353,7 +1374,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
 			{
 				panelp->reshape(panelp->getRect().getWidth(), (*panel_it)->mMinHeight);
 			}
-			total_height += llround(panelp->getRect().getHeight() * (*panel_it)->mVisibleAmt);
+			total_height += llround(panelp->getRect().getHeight() * (*panel_it)->getCollapseFactor());
 			if (panel_it != mPanels.begin())
 			{
 				total_height += mPanelSpacing;
@@ -1367,7 +1388,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
 	for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it)
 	{
 		// panels that are not fully visible do not count towards shrink headroom
-		if ((*panel_it)->mVisibleAmt < 1.f) 
+		if ((*panel_it)->getCollapseFactor() < 1.f) 
 		{
 			continue;
 		}
@@ -1431,7 +1452,7 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
 		S32 delta_size = 0;
 
 		// if panel can automatically resize (not animating, and resize flag set)...
-		if ((*panel_it)->mVisibleAmt == 1.f 
+		if ((*panel_it)->getCollapseFactor() == 1.f 
 			&& (force_resize || (*panel_it)->mAutoResize) 
 			&& !(*panel_it)->mResizeBar->hasMouseCapture()) 
 		{
@@ -1515,11 +1536,11 @@ void LLLayoutStack::updateLayout(BOOL force_resize)
 
 		if (mOrientation == HORIZONTAL)
 		{
-			cur_x += llround(new_width * (*panel_it)->mVisibleAmt) + mPanelSpacing;
+			cur_x += llround(new_width * (*panel_it)->getCollapseFactor()) + mPanelSpacing;
 		}
 		else //VERTICAL
 		{
-			cur_y -= llround(new_height * (*panel_it)->mVisibleAmt) + mPanelSpacing;
+			cur_y -= llround(new_height * (*panel_it)->getCollapseFactor()) + mPanelSpacing;
 		}
 	}
 
diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h
index c55eb6bba2..da876667e6 100644
--- a/indra/llui/llpanel.h
+++ b/indra/llui/llpanel.h
@@ -49,23 +49,13 @@ const BOOL BORDER_YES = TRUE;
 const BOOL BORDER_NO = FALSE;
 
 
-struct LLAlertInfo
-{
-	std::string mLabel;
-	LLStringUtil::format_map_t mArgs;
-
-	LLAlertInfo(std::string label, LLStringUtil::format_map_t args) : mLabel(label), mArgs(args) { }
-	LLAlertInfo(){}
-};
-
-
 /*
  * General purpose concrete view base class.
  * Transparent or opaque,
  * With or without border,
  * Can contain LLUICtrls.
  */
-class LLPanel : public LLUICtrl 
+class LLPanel : public LLUICtrl, public boost::signals::trackable
 {
 public:
 
@@ -227,8 +217,6 @@ public:
 	void childNotFound(const std::string& id) const;
 	void childDisplayNotFound();
 
-	static void		alertXml(const std::string& label, LLStringUtil::format_map_t args = LLStringUtil::format_map_t());
-	static BOOL		nextAlert(LLAlertInfo &alert);
 	static LLView*	fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory);
 	
 protected:
@@ -266,8 +254,6 @@ private:
 
 	std::string		mRequirementsError;
 
-	typedef std::queue<LLAlertInfo> alert_queue_t;
-	static alert_queue_t sAlertQueue;
 }; // end class LLPanel
 
 
@@ -292,8 +278,16 @@ public:
 	S32 getMinWidth() const { return mMinWidth; }
 	S32 getMinHeight() const { return mMinHeight; }
 	
-	void addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, S32 index = S32_MAX);
+	typedef enum e_animate
+	{
+		NO_ANIMATE,
+		ANIMATE
+	} EAnimate;
+
+	void addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, EAnimate animate = NO_ANIMATE, S32 index = S32_MAX);
 	void removePanel(LLPanel* panel);
+	void collapsePanel(LLPanel* panel, BOOL collapsed = TRUE);
+	S32 getNumPanels() { return mPanels.size(); }
 
 private:
 	struct LLEmbeddedPanel;
diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp
index a7163323bd..734b3ab381 100644
--- a/indra/llui/llscrollbar.cpp
+++ b/indra/llui/llscrollbar.cpp
@@ -127,6 +127,8 @@ LLScrollbar::LLScrollbar(
 	}
 	line_up_btn->setHeldDownCallback( &LLScrollbar::onLineUpBtnPressed );
 	line_up_btn->setTabStop(FALSE);
+	line_up_btn->setScaleImage(TRUE);
+
 	addChild(line_up_btn);
 
 	LLButton* line_down_btn = new LLButton(std::string("Line Down"), line_down_rect,
@@ -136,6 +138,7 @@ LLScrollbar::LLScrollbar(
 	line_down_btn->setFollowsBottom();
 	line_down_btn->setHeldDownCallback( &LLScrollbar::onLineDownBtnPressed );
 	line_down_btn->setTabStop(FALSE);
+	line_down_btn->setScaleImage(TRUE);
 	addChild(line_down_btn);
 }
 
@@ -148,20 +151,29 @@ LLScrollbar::~LLScrollbar()
 void LLScrollbar::setDocParams( S32 size, S32 pos )
 {
 	mDocSize = size;
-	mDocPos = llclamp( pos, 0, getDocPosMax() );
+	setDocPos(pos);
 	mDocChanged = TRUE;
 
 	updateThumbRect();
 }
 
-void LLScrollbar::setDocPos(S32 pos)
+void LLScrollbar::setDocPos(S32 pos, BOOL update_thumb)
 {
+	pos = llclamp(pos, 0, getDocPosMax());
 	if (pos != mDocPos)
 	{
-		mDocPos = llclamp( pos, 0, getDocPosMax() );
+		mDocPos = pos;
 		mDocChanged = TRUE;
 
-		updateThumbRect();
+		if( mChangeCallback )
+		{
+			mChangeCallback( mDocPos, this, mCallbackUserData );
+		}
+
+		if( update_thumb )
+		{
+			updateThumbRect();
+		}
 	}
 }
 
@@ -170,7 +182,7 @@ void LLScrollbar::setDocSize(S32 size)
 	if (size != mDocSize)
 	{
 		mDocSize = size;
-		mDocPos = llclamp( mDocPos, 0, getDocPosMax() );
+		setDocPos(mDocPos);
 		mDocChanged = TRUE;
 
 		updateThumbRect();
@@ -182,7 +194,7 @@ void LLScrollbar::setPageSize( S32 page_size )
 	if (page_size != mPageSize)
 	{
 		mPageSize = page_size;
-		mDocPos = llclamp( mDocPos, 0, getDocPosMax() );
+		setDocPos(mDocPos);
 		mDocChanged = TRUE;
 
 		updateThumbRect();
@@ -208,9 +220,9 @@ void LLScrollbar::updateThumbRect()
 	const S32 THUMB_MIN_LENGTH = 16;
 
 	S32 window_length = (mOrientation == LLScrollbar::HORIZONTAL) ? getRect().getWidth() : getRect().getHeight();
-	S32 thumb_bg_length = window_length - 2 * SCROLLBAR_SIZE;
+	S32 thumb_bg_length = llmax(0, window_length - 2 * SCROLLBAR_SIZE);
 	S32 visible_lines = llmin( mDocSize, mPageSize );
-	S32 thumb_length = mDocSize ? llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH ) : thumb_bg_length;
+	S32 thumb_length = mDocSize ? llmin(llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH), thumb_bg_length) : thumb_bg_length;
 
 	S32 variable_lines = mDocSize - visible_lines;
 
@@ -218,7 +230,7 @@ void LLScrollbar::updateThumbRect()
 	{ 
 		S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE;
 		S32 thumb_start_min = SCROLLBAR_SIZE + THUMB_MIN_LENGTH;
-		S32 thumb_start = variable_lines ? llclamp( thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min, thumb_start_max ) : thumb_start_max;
+		S32 thumb_start = variable_lines ? llmin( llmax(thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_max;
 
 		mThumbRect.mLeft =  0;
 		mThumbRect.mTop = thumb_start;
@@ -230,7 +242,7 @@ void LLScrollbar::updateThumbRect()
 		// Horizontal
 		S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE - thumb_length;
 		S32 thumb_start_min = SCROLLBAR_SIZE;
-		S32 thumb_start = variable_lines ? llclamp( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min, thumb_start_max ) : thumb_start_min;
+		S32 thumb_start = variable_lines ? llmin(llmax( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_min;
 	
 		mThumbRect.mLeft = thumb_start;
 		mThumbRect.mTop = SCROLLBAR_SIZE;
@@ -446,7 +458,7 @@ BOOL LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask)
 	}
 	else
 	{
-		// Opaque, so don't just check children
+		// Opaque, so don't just check children	
 		handled = LLView::handleMouseUp( x, y, mask );
 	}
 
@@ -455,13 +467,31 @@ BOOL LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask)
 
 void LLScrollbar::reshape(S32 width, S32 height, BOOL called_from_parent)
 {
+	if (width == getRect().getWidth() && height == getRect().getHeight()) return;
 	LLView::reshape( width, height, called_from_parent );
+	LLButton* up_button = getChild<LLButton>("Line Up");
+	LLButton* down_button = getChild<LLButton>("Line Down");
+
+	if (mOrientation == VERTICAL)
+	{
+		up_button->reshape(up_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, SCROLLBAR_SIZE));
+		down_button->reshape(down_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, SCROLLBAR_SIZE));
+		up_button->setOrigin(up_button->getRect().mLeft, getRect().getHeight() - up_button->getRect().getHeight());
+	}
+	else
+	{
+		up_button->reshape(llmin(getRect().getWidth() / 2, SCROLLBAR_SIZE), up_button->getRect().getHeight());
+		down_button->reshape(llmin(getRect().getWidth() / 2, SCROLLBAR_SIZE), down_button->getRect().getHeight());
+		down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), down_button->getRect().mBottom);
+	}
 	updateThumbRect();
 }
 
 
 void LLScrollbar::draw()
 {
+	if (!getRect().isValid()) return;
+
 	S32 local_mouse_x;
 	S32 local_mouse_y;
 	LLUI::getCursorPositionLocal(this, &local_mouse_x, &local_mouse_y);
@@ -531,21 +561,7 @@ void LLScrollbar::draw()
 
 void LLScrollbar::changeLine( S32 delta, BOOL update_thumb )
 {
-	S32 new_pos = llclamp( mDocPos + delta, 0, getDocPosMax() );
-	if( new_pos != mDocPos )
-	{
-		mDocPos = new_pos;
-	}
-
-	if( mChangeCallback )
-	{
-		mChangeCallback( mDocPos, this, mCallbackUserData );
-	}
-
-	if( update_thumb )
-	{
-		updateThumbRect();
-	}
+	setDocPos(mDocPos + delta, update_thumb);
 }
 
 void LLScrollbar::setValue(const LLSD& value) 
@@ -561,22 +577,22 @@ BOOL LLScrollbar::handleKeyHere(KEY key, MASK mask)
 	switch( key )
 	{
 	case KEY_HOME:
-		changeLine( -mDocPos, TRUE );
+		setDocPos( 0 );
 		handled = TRUE;
 		break;
 	
 	case KEY_END:
-		changeLine( getDocPosMax() - mDocPos, TRUE );
+		setDocPos( getDocPosMax() );
 		handled = TRUE;
 		break;
 	
 	case KEY_DOWN:
-		changeLine( mStepSize, TRUE );
+		setDocPos( getDocPos() + mStepSize );
 		handled = TRUE;
 		break;
 	
 	case KEY_UP:
-		changeLine( - mStepSize, TRUE );
+		setDocPos( getDocPos() - mStepSize );
 		handled = TRUE;
 		break;
 
diff --git a/indra/llui/llscrollbar.h b/indra/llui/llscrollbar.h
index d35dc10bd4..5fdacd43f8 100644
--- a/indra/llui/llscrollbar.h
+++ b/indra/llui/llscrollbar.h
@@ -81,7 +81,7 @@ public:
 
 	// How many "lines" the "document" has scrolled.
 	// 0 <= DocPos <= DocSize - DocVisibile
-	void				setDocPos( S32 pos );
+	void				setDocPos( S32 pos, BOOL update_thumb = TRUE );
 	S32					getDocPos() const		{ return mDocPos; }
 
 	BOOL				isAtBeginning();
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index c0f21ba9e5..ea0c6d4b2f 100644
--- a/indra/llui/llscrolllistctrl.cpp
+++ b/indra/llui/llscrolllistctrl.cpp
@@ -378,6 +378,22 @@ void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_col
 						TRUE);
 }
 
+LLScrollListDate::LLScrollListDate( const LLDate& date, const LLFontGL* font, S32 width, U8 font_style, LLFontGL::HAlign font_alignment, LLColor4& color, BOOL use_color, BOOL visible)
+:	LLScrollListText(date.asRFC1123(), font, width, font_style, font_alignment, color, use_color, visible),
+	mDate(date)
+{
+}
+
+void LLScrollListDate::setValue(const LLSD& value)
+{
+	mDate = value.asDate();
+	LLScrollListText::setValue(mDate.asRFC1123());
+}
+
+const LLSD LLScrollListDate::getValue() const
+{
+	return mDate;
+}
 
 LLScrollListItem::~LLScrollListItem()
 {
@@ -578,6 +594,7 @@ LLScrollListCtrl::LLScrollListCtrl(const std::string& name, const LLRect& rect,
 	mSearchColumn(0),
 	mNumDynamicWidthColumns(0),
 	mTotalStaticColumnWidth(0),
+	mTotalColumnPadding(0),
 	mSorted(TRUE),
 	mDirty(FALSE),
 	mOriginalSelection(-1),
@@ -627,6 +644,28 @@ LLScrollListCtrl::LLScrollListCtrl(const std::string& name, const LLRect& rect,
 	mLastSelected = NULL;
 }
 
+S32 LLScrollListCtrl::getSearchColumn()
+{
+	// search for proper search column
+	if (mSearchColumn < 0)
+	{
+		LLScrollListItem* itemp = getFirstData();
+		if (itemp)
+		{
+			for(S32 column = 0; column < getNumColumns(); column++)
+			{
+				LLScrollListCell* cell = itemp->getColumn(column);
+				if (cell && cell->isText())
+				{
+					mSearchColumn = column;
+					break;
+				}
+			}
+		}
+	}
+	return llclamp(mSearchColumn, 0, getNumColumns());
+}
+
 LLScrollListCtrl::~LLScrollListCtrl()
 {
 	std::for_each(mItemList.begin(), mItemList.end(), DeletePointer());
@@ -890,8 +929,8 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL r
 // *TODO: Use bookkeeping to make this an incramental cost with item additions
 void LLScrollListCtrl::calcColumnWidths()
 {
-	const S32 HEADING_TEXT_PADDING = 30;
-	const S32 COLUMN_TEXT_PADDING = 20;
+	const S32 HEADING_TEXT_PADDING = 25;
+	const S32 COLUMN_TEXT_PADDING = 10;
 
 	mMaxContentWidth = 0;
 
@@ -904,20 +943,17 @@ void LLScrollListCtrl::calcColumnWidths()
 		if (!column) continue;
 
 		// update column width
-		S32 new_width = column->mWidth;
+		S32 new_width = column->getWidth();
 		if (column->mRelWidth >= 0)
 		{
 			new_width = (S32)llround(column->mRelWidth*mItemListRect.getWidth());
 		}
 		else if (column->mDynamicWidth)
 		{
-			new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
+			new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns;
 		}
 
-		if (new_width != column->mWidth)
-		{
-			column->mWidth = new_width;
-		}
+		column->setWidth(new_width);
 
 		// update max content width for this column, by looking at all items
 		column->mMaxContentWidth = column->mHeader ? LLFontGL::sSansSerifSmall->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0;
@@ -971,28 +1007,13 @@ void LLScrollListCtrl::updateColumns()
 {
 	calcColumnWidths();
 
-	// propagate column widths to individual cells
-	item_list::iterator iter;
-	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
-	{
-		LLScrollListItem *itemp = *iter;
-		S32 num_cols = itemp->getNumColumns();
-		S32 i = 0;
-		for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
-		{
-			if (i >= (S32)mColumnsIndexed.size()) break;
-
-			cell->setWidth(mColumnsIndexed[i]->mWidth);
-		}
-	}
-
 	// update column headers
 	std::vector<LLScrollListColumn*>::iterator column_ordered_it;
 	S32 left = mItemListRect.mLeft;
 	LLColumnHeader* last_header = NULL;
 	for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it)
 	{
-		if ((*column_ordered_it)->mWidth < 0)
+		if ((*column_ordered_it)->getWidth() < 0)
 		{
 			// skip hidden columns
 			continue;
@@ -1001,9 +1022,11 @@ void LLScrollListCtrl::updateColumns()
 		
 		if (column->mHeader)
 		{
+			column->mHeader->updateResizeBars();
+
 			last_header = column->mHeader;
 			S32 top = mItemListRect.mTop;
-			S32 right = left + column->mWidth;
+			S32 right = left + column->getWidth();
 
 			if (column->mIndex != (S32)mColumnsIndexed.size()-1)
 			{
@@ -1021,14 +1044,30 @@ void LLScrollListCtrl::updateColumns()
 		}
 	}
 
-	//FIXME: stretch the entire last column if it is resizable (gestures windows shows truncated text in last column)
 	// expand last column header we encountered to full list width
-	if (last_header)
+	if (last_header && last_header->canResize())
 	{
 		S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft);
 		last_header->reshape(new_width, last_header->getRect().getHeight());
 		last_header->setVisible(mDisplayColumnHeaders && new_width > 0);
+		last_header->getColumn()->setWidth(new_width);
+	}
+
+	// propagate column widths to individual cells
+	item_list::iterator iter;
+	for (iter = mItemList.begin(); iter != mItemList.end(); iter++)
+	{
+		LLScrollListItem *itemp = *iter;
+		S32 num_cols = itemp->getNumColumns();
+		S32 i = 0;
+		for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i))
+		{
+			if (i >= (S32)mColumnsIndexed.size()) break;
+
+			cell->setWidth(mColumnsIndexed[i]->getWidth());
+		}
 	}
+
 }
 
 void LLScrollListCtrl::setDisplayHeading(BOOL display)
@@ -1490,7 +1529,7 @@ BOOL LLScrollListCtrl::selectItemByPrefix(const LLWString& target, BOOL case_sen
 		{
 			LLScrollListItem* item = *iter;
 			// Only select enabled items with matching names
-			LLScrollListCell* cellp = item->getColumn(mSearchColumn);
+			LLScrollListCell* cellp = item->getColumn(getSearchColumn());
 			BOOL select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : FALSE;
 			if (select)
 			{
@@ -1513,7 +1552,7 @@ BOOL LLScrollListCtrl::selectItemByPrefix(const LLWString& target, BOOL case_sen
 			LLScrollListItem* item = *iter;
 
 			// Only select enabled items with matching names
-			LLScrollListCell* cellp = item->getColumn(mSearchColumn);
+			LLScrollListCell* cellp = item->getColumn(getSearchColumn());
 			if (!cellp)
 			{
 				continue;
@@ -1743,6 +1782,8 @@ void LLScrollListCtrl::drawItems()
 
 void LLScrollListCtrl::draw()
 {
+	LLLocalClipRect clip(getLocalRect());
+
 	// if user specifies sort, make sure it is maintained
 	if (needsSorting() && !isSorted())
 	{
@@ -1816,7 +1857,7 @@ BOOL LLScrollListCtrl::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sti
 			S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft;
 			S32 rect_bottom = getRowOffsetFromIndex(getItemIndex(hit_item));
 			LLRect cell_rect;
-			cell_rect.setOriginAndSize(rect_left, rect_bottom, rect_left + columnp->mWidth, mLineHeight);
+			cell_rect.setOriginAndSize(rect_left, rect_bottom, rect_left + columnp->getWidth(), mLineHeight);
 			// Convert rect local to screen coordinates
 			localPointToScreen( 
 				cell_rect.mLeft, cell_rect.mBottom, 
@@ -2113,7 +2154,7 @@ S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x)
 	ordered_columns_t::const_iterator end = mColumnsIndexed.end();
 	for ( ; iter != end; ++iter)
 	{
-		width = (*iter)->mWidth + mColumnPadding;
+		width = (*iter)->getWidth() + mColumnPadding;
 		right += width;
 		if (left <= x && x < right )
 		{
@@ -2140,7 +2181,7 @@ S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index)
 		{
 			return column_offset;
 		}
-		column_offset += (*iter)->mWidth + mColumnPadding;
+		column_offset += (*iter)->getWidth() + mColumnPadding;
 	}
 
 	// when running off the end, return the rightmost pixel
@@ -2292,7 +2333,7 @@ BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask )
 				{
 					if (getFirstSelected())
 					{
-						LLScrollListCell* cellp = getFirstSelected()->getColumn(mSearchColumn);
+						LLScrollListCell* cellp = getFirstSelected()->getColumn(getSearchColumn());
 						if (cellp)
 						{
 							cellp->highlightText(0, 0);
@@ -2379,7 +2420,7 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char)
 		{
 			LLScrollListItem* item = *iter;
 
-			LLScrollListCell* cellp = item->getColumn(mSearchColumn);
+			LLScrollListCell* cellp = item->getColumn(getSearchColumn());
 			if (cellp)
 			{
 				// Only select enabled items with matching first characters
@@ -2446,7 +2487,7 @@ void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, BOOL select_single_it
 	{
 		if (mLastSelected)
 		{
-			LLScrollListCell* cellp = mLastSelected->getColumn(mSearchColumn);
+			LLScrollListCell* cellp = mLastSelected->getColumn(getSearchColumn());
 			if (cellp)
 			{
 				cellp->highlightText(0, 0);
@@ -2474,7 +2515,7 @@ void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp)
 		}
 
 		itemp->setSelected(FALSE);
-		LLScrollListCell* cellp = itemp->getColumn(mSearchColumn);
+		LLScrollListCell* cellp = itemp->getColumn(getSearchColumn());
 		if (cellp)
 		{
 			cellp->highlightText(0, 0);	
@@ -2501,9 +2542,14 @@ struct SameSortColumn
 	bool operator()(std::pair<S32, BOOL> sort_column) { return sort_column.first == mColumn; }
 };
 
-BOOL LLScrollListCtrl::setSort(S32 column, BOOL ascending)
+BOOL LLScrollListCtrl::setSort(S32 column_idx, BOOL ascending)
 {
-	sort_column_t new_sort_column(column, ascending);
+	LLScrollListColumn* sort_column = getColumn(column_idx);
+	if (!sort_column) return FALSE;
+
+	sort_column->mSortAscending = ascending;
+
+	sort_column_t new_sort_column(column_idx, ascending);
 
 	if (mSortColumns.empty())
 	{
@@ -2517,7 +2563,7 @@ BOOL LLScrollListCtrl::setSort(S32 column, BOOL ascending)
 		
 		// remove any existing sort criterion referencing this column
 		// and add the new one
-		mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column)), mSortColumns.end()); 
+		mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column_idx)), mSortColumns.end()); 
 		mSortColumns.push_back(new_sort_column);
 
 		// did the sort criteria change?
@@ -2643,6 +2689,12 @@ void LLScrollListCtrl::scrollToShowSelected()
 	}
 }
 
+void LLScrollListCtrl::updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width)
+{
+	mTotalStaticColumnWidth += llmax(0, new_width) - llmax(0, col->getWidth());
+}
+
+
 // virtual
 LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const
 {
@@ -2689,7 +2741,7 @@ LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const
 
 		child_node->createChild("name", TRUE)->setStringValue(column->mName);
 		child_node->createChild("label", TRUE)->setStringValue(column->mLabel);
-		child_node->createChild("width", TRUE)->setIntValue(column->mWidth);
+		child_node->createChild("width", TRUE)->setIntValue(column->getWidth());
 	}
 
 	return node;
@@ -2813,15 +2865,9 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
 
 	scroll_list->setSearchColumn(search_column);
 
-	if (sort_column >= 0)
-	{
-		scroll_list->sortByColumnIndex(sort_column, sort_ascending);
-	}
-
 	LLSD columns;
 	S32 index = 0;
 	LLXMLNodePtr child;
-	S32 total_static = 0;
 	for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
 	{
 		if (child->hasName("column"))
@@ -2850,8 +2896,6 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
 			std::string tooltip;
 			child->getAttributeString("tool_tip", tooltip);
 
-			if(!columndynamicwidth) total_static += llmax(0, columnwidth);
-
 			F32 columnrelwidth = 0.f;
 			child->getAttributeF32("relwidth", columnrelwidth);
 
@@ -2872,9 +2916,13 @@ LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFac
 			index++;
 		}
 	}
-	scroll_list->setTotalStaticColumnWidth(total_static);
 	scroll_list->setColumnHeadings(columns);
 
+	if (sort_column >= 0)
+	{
+		scroll_list->sortByColumnIndex(sort_column, sort_ascending);
+	}
+
 	for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling())
 	{
 		if (child->hasName("row"))
@@ -3019,22 +3067,26 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
 	if (mColumns.find(name) == mColumns.end())
 	{
 		// Add column
-		mColumns[name] = LLScrollListColumn(column);
+		mColumns[name] = LLScrollListColumn(column, this);
 		LLScrollListColumn* new_column = &mColumns[name];
 		new_column->mParentCtrl = this;
 		new_column->mIndex = mColumns.size()-1;
 
 		// Add button
-		if (new_column->mWidth > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
+		if (new_column->getWidth() > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth)
 		{
+			if (getNumColumns() > 0)
+			{
+				mTotalColumnPadding += mColumnPadding;
+			}
 			if (new_column->mRelWidth >= 0)
 			{
-				new_column->mWidth = (S32)llround(new_column->mRelWidth*mItemListRect.getWidth());
+				new_column->setWidth((S32)llround(new_column->mRelWidth*mItemListRect.getWidth()));
 			}
 			else if(new_column->mDynamicWidth)
 			{
 				mNumDynamicWidthColumns++;
-				new_column->mWidth = (mItemListRect.getWidth() - mTotalStaticColumnWidth) / mNumDynamicWidthColumns;
+				new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns);
 			}
 			S32 top = mItemListRect.mTop;
 			S32 left = mItemListRect.mLeft;
@@ -3043,14 +3095,14 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos)
 				for (itor = mColumns.begin(); itor != mColumns.end(); ++itor)
 				{
 					if (itor->second.mIndex < new_column->mIndex &&
-						itor->second.mWidth > 0)
+						itor->second.getWidth() > 0)
 					{
-						left += itor->second.mWidth + mColumnPadding;
+						left += itor->second.getWidth() + mColumnPadding;
 					}
 				}
 			}
 			std::string button_name = "btn_" + name;
-			S32 right = left+new_column->mWidth;
+			S32 right = left+new_column->getWidth();
 			if (new_column->mIndex != (S32)mColumns.size()-1)
 			{
 				right += mColumnPadding;
@@ -3145,6 +3197,8 @@ void LLScrollListCtrl::clearColumns()
 	}
 	mColumns.clear();
 	mSortColumns.clear();
+	mTotalStaticColumnWidth = 0;
+	mTotalColumnPadding = 0;
 }
 
 void LLScrollListCtrl::setColumnLabel(const std::string& column, const std::string& label)
@@ -3244,7 +3298,7 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
 		}
 
 		S32 index = columnp->mIndex;
-		S32 width = columnp->mWidth;
+		S32 width = columnp->getWidth();
 		LLFontGL::HAlign font_alignment = columnp->mFontAlignment;
 		LLColor4 fcolor = LLColor4::black;
 		
@@ -3301,6 +3355,19 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
 			}
 			new_item->setColumn(index, cell);
 		}
+		else if (type == "date")
+		{
+			LLScrollListDate* cell = new LLScrollListDate(value.asDate(), font, width, font_style, font_alignment);
+			if (has_color)
+			{
+				cell->setColor(color);
+			}
+			new_item->setColumn(index, cell);
+			if (columnp->mHeader && !value.asString().empty())
+			{
+				columnp->mHeader->setHasResizableElement(TRUE);
+			}
+		}
 		else
 		{
 			LLScrollListText* cell = new LLScrollListText(value.asString(), font, width, font_style, font_alignment, fcolor, TRUE);
@@ -3325,7 +3392,7 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p
 		if (new_item->getColumn(column_idx) == NULL)
 		{
 			LLScrollListColumn* column_ptr = &column_it->second;
-			new_item->setColumn(column_idx, new LLScrollListText(LLStringUtil::null, LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->mWidth, LLFontGL::NORMAL));
+			new_item->setColumn(column_idx, new LLScrollListText(LLStringUtil::null, LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->getWidth(), LLFontGL::NORMAL));
 		}
 	}
 
@@ -3469,6 +3536,7 @@ LLColumnHeader::LLColumnHeader(const std::string& label, const LLRect &rect, LLS
 	mButton->setMouseDownCallback(onMouseDown);
 
 	mButton->setCallbackUserData(this);
+	mButton->setToolTip(label);
 
 	mAscendingText = std::string("[LOW]...[HIGH](Ascending)"); // *TODO: Translate
 	mDescendingText = std::string("[HIGH]...[LOW](Descending)"); // *TODO: Translate
@@ -3556,7 +3624,7 @@ void LLColumnHeader::onClick(void* user_data)
 
 	LLScrollListCtrl::onClickColumn(column);
 
-	// propage new sort order to sort order list
+	// propagate new sort order to sort order list
 	headerp->mList->selectNthItem(column->mParentCtrl->getSortAscending() ? 0 : 1);
 }
 
@@ -3646,7 +3714,7 @@ void LLColumnHeader::showList()
 		text_width = llmax(text_width, LLFontGL::sSansSerifSmall->getWidth(descending_string)) + 10;
 		text_width = llmax(text_width, getRect().getWidth() - 30);
 
-		mList->getColumn(0)->mWidth = text_width;
+		mList->getColumn(0)->setWidth(text_width);
 		((LLScrollListText*)mList->getFirstData()->getColumn(0))->setText(ascending_string);
 		((LLScrollListText*)mList->getLastData()->getColumn(0))->setText(descending_string);
 
@@ -3688,7 +3756,7 @@ LLView*	LLColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_d
 	llassert(snap_edge == SNAP_RIGHT);
 
 	// use higher snap threshold for column headers
-	threshold = llmin(threshold, 15);
+	threshold = llmin(threshold, 10);
 
 	LLRect snap_rect = getSnapRect();
 
@@ -3727,47 +3795,48 @@ void LLColumnHeader::userSetShape(const LLRect& new_rect)
 
 	if (delta_width != 0)
 	{
-		S32 remaining_width = delta_width;
+		S32 remaining_width = -delta_width;
 		S32 col;
 		for (col = mColumn->mIndex + 1; col < mColumn->mParentCtrl->getNumColumns(); col++)
 		{
 			LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
-			if (!columnp) break;
+			if (!columnp) continue;
 
 			if (columnp->mHeader && columnp->mHeader->canResize())
 			{
 				// how many pixels in width can this column afford to give up?
-				S32 resize_buffer_amt = llmax(0, columnp->mWidth - MIN_COLUMN_WIDTH);
+				S32 resize_buffer_amt = llmax(0, columnp->getWidth() - MIN_COLUMN_WIDTH);
 				
 				// user shrinking column, need to add width to other columns
 				if (delta_width < 0)
 				{
-					if (!columnp->mDynamicWidth && columnp->mWidth > 0)
+					if (/*!columnp->mDynamicWidth && */columnp->getWidth() > 0)
 					{
 						// statically sized column, give all remaining width to this column
-						columnp->mWidth -= remaining_width;
+						columnp->setWidth(columnp->getWidth() + remaining_width);
 						if (columnp->mRelWidth > 0.f)
 						{
-							columnp->mRelWidth = (F32)columnp->mWidth / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
+							columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
 						}
+						// all padding went to this widget, we're done
+						break;
 					}
-					break;
 				}
 				else
 				{
 					// user growing column, need to take width from other columns
-					remaining_width -= resize_buffer_amt;
+					remaining_width += resize_buffer_amt;
 
-					if (!columnp->mDynamicWidth && columnp->mWidth > 0)
+					if (/*!columnp->mDynamicWidth && */columnp->getWidth() > 0)
 					{
-						columnp->mWidth -= llmin(columnp->mWidth - MIN_COLUMN_WIDTH, delta_width);
+						columnp->setWidth(columnp->getWidth() - llmin(columnp->getWidth() - MIN_COLUMN_WIDTH, delta_width));
 						if (columnp->mRelWidth > 0.f)
 						{
-							columnp->mRelWidth = (F32)columnp->mWidth / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
+							columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth();
 						}
 					}
 
-					if (remaining_width <= 0)
+					if (remaining_width >= 0)
 					{
 						// width sucked up from neighboring columns, done
 						break;
@@ -3779,14 +3848,14 @@ void LLColumnHeader::userSetShape(const LLRect& new_rect)
 		// clamp resize amount to maximum that can be absorbed by other columns
 		if (delta_width > 0)
 		{
-			delta_width -= llmax(remaining_width, 0);
+			delta_width += llmin(remaining_width, 0);
 		}
 
 		// propagate constrained delta_width to new width for this column
 		new_width = getRect().getWidth() + delta_width - mColumn->mParentCtrl->getColumnPadding();
 
 		// use requested width
-		mColumn->mWidth = new_width;
+		mColumn->setWidth(new_width);
 
 		// update proportional spacing
 		if (mColumn->mRelWidth > 0.f)
@@ -3804,36 +3873,40 @@ void LLColumnHeader::userSetShape(const LLRect& new_rect)
 void LLColumnHeader::setHasResizableElement(BOOL resizable)
 {
 	// for now, dynamically spaced columns can't be resized
-	if (mColumn->mDynamicWidth) return;
+//	if (mColumn->mDynamicWidth) return;
 
-	if (resizable != mHasResizableElement)
+	if (mHasResizableElement != resizable)
 	{
+		mColumn->mParentCtrl->dirtyColumns();
 		mHasResizableElement = resizable;
+	}
+}
 
-		S32 num_resizable_columns = 0;
-		S32 col;
-		for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+void LLColumnHeader::updateResizeBars()
+{
+	S32 num_resizable_columns = 0;
+	S32 col;
+	for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+	{
+		LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
+		if (columnp->mHeader && columnp->mHeader->canResize())
 		{
-			LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
-			if (columnp->mHeader && columnp->mHeader->canResize())
-			{
-				num_resizable_columns++;
-			}
+			num_resizable_columns++;
 		}
+	}
 
-		S32 num_resizers_enabled = 0;
+	S32 num_resizers_enabled = 0;
 
-		// now enable/disable resize handles on resizable columns if we have at least two
-		for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+	// now enable/disable resize handles on resizable columns if we have at least two
+	for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++)
+	{
+		LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
+		if (!columnp->mHeader) continue;
+		BOOL enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize();
+		columnp->mHeader->enableResizeBar(enable);
+		if (enable)
 		{
-			LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col);
-			if (!columnp->mHeader) continue;
-			BOOL enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize();
-			columnp->mHeader->enableResizeBar(enable);
-			if (enable)
-			{
-				num_resizers_enabled++;
-			}
+			num_resizers_enabled++;
 		}
 	}
 }
@@ -3841,7 +3914,7 @@ void LLColumnHeader::setHasResizableElement(BOOL resizable)
 void LLColumnHeader::enableResizeBar(BOOL enable)
 {
 	// for now, dynamically spaced columns can't be resized
-	if (!mColumn->mDynamicWidth)
+	//if (!mColumn->mDynamicWidth)
 	{
 		mResizeBar->setEnabled(enable);
 	}
@@ -3851,3 +3924,78 @@ BOOL LLColumnHeader::canResize()
 {
 	return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth);
 }
+
+void LLScrollListColumn::setWidth(S32 width) 
+{ 
+	if (!mDynamicWidth && mRelWidth <= 0.f) 
+	{
+		mParentCtrl->updateStaticColumnWidth(this, width);
+	}
+	mWidth = width;
+}
+
+// Default constructor
+LLScrollListColumn::LLScrollListColumn() : 
+	mName(), 
+	mSortingColumn(), 
+	mSortAscending(TRUE), 
+	mLabel(), 
+	mWidth(-1), 
+	mRelWidth(-1.0), 
+	mDynamicWidth(FALSE), 
+	mMaxContentWidth(0),
+	mIndex(-1), 
+	mParentCtrl(NULL), 
+	mHeader(NULL), 
+	mFontAlignment(LLFontGL::LEFT)
+{ }
+
+LLScrollListColumn::LLScrollListColumn(const LLSD &sd, LLScrollListCtrl* parent) :
+	mWidth(0),
+	mIndex (-1),
+	mParentCtrl(parent),
+	mHeader(NULL),
+	mMaxContentWidth(0),
+	mDynamicWidth(FALSE),
+	mRelWidth(-1.f)
+{
+	mName = sd.get("name").asString();
+	mSortingColumn = mName;
+	if (sd.has("sort"))
+	{
+		mSortingColumn = sd.get("sort").asString();
+	}
+	mSortAscending = TRUE;
+	if (sd.has("sort_ascending"))
+	{
+		mSortAscending = sd.get("sort_ascending").asBoolean();
+	}
+	mLabel = sd.get("label").asString();
+	if (sd.has("relwidth") && (F32)sd.get("relwidth").asReal() > 0)
+	{
+		mRelWidth = (F32)sd.get("relwidth").asReal();
+		if (mRelWidth < 0) mRelWidth = 0;
+		if (mRelWidth > 1) mRelWidth = 1;
+		mDynamicWidth = FALSE;
+	}
+	else if(sd.has("dynamicwidth") && (BOOL)sd.get("dynamicwidth").asBoolean() == TRUE)
+	{
+		mDynamicWidth = TRUE;
+		mRelWidth = -1;
+	}
+	else
+	{
+
+		setWidth(sd.get("width").asInteger());
+	}
+
+	if (sd.has("halign"))
+	{
+		mFontAlignment = (LLFontGL::HAlign)llclamp(sd.get("halign").asInteger(), (S32)LLFontGL::LEFT, (S32)LLFontGL::HCENTER);
+	}
+	else
+	{
+		mFontAlignment = LLFontGL::LEFT;
+	}
+
+}
diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h
index 0128c01eba..d8ef955042 100644
--- a/indra/llui/llscrolllistctrl.h
+++ b/indra/llui/llscrolllistctrl.h
@@ -47,6 +47,7 @@
 #include "llcombobox.h"
 #include "llscrollbar.h"
 #include "llresizebar.h"
+#include "lldate.h"
 
 /*
  * Represents a cell in a scrollable table.
@@ -133,6 +134,18 @@ private:
 	static U32 sCount;
 };
 
+
+class LLScrollListDate : public LLScrollListText
+{
+public:
+	LLScrollListDate( const LLDate& date, const LLFontGL* font, S32 width=0, U8 font_style = LLFontGL::NORMAL, LLFontGL::HAlign font_alignment = LLFontGL::LEFT, LLColor4& color = LLColor4::black, BOOL use_color = FALSE, BOOL visible = TRUE);
+	virtual void	setValue(const LLSD& value);
+	virtual const LLSD getValue() const;
+
+private:
+	LLDate		mDate;
+};
+
 /*
  * Cell displaying an image.
  */
@@ -185,88 +198,11 @@ private:
 class LLScrollListColumn
 {
 public:
-	// Default constructor
-	LLScrollListColumn() : 
-		mName(), 
-		mSortingColumn(), 
-		mSortAscending(TRUE), 
-		mLabel(), 
-		mWidth(-1), 
-		mRelWidth(-1.0), 
-		mDynamicWidth(FALSE), 
-		mMaxContentWidth(0),
-		mIndex(-1), 
-		mParentCtrl(NULL), 
-		mHeader(NULL), 
-		mFontAlignment(LLFontGL::LEFT)
-	{ }
-
-	LLScrollListColumn(std::string name, std::string label, S32 width, F32 relwidth) : 
-		mName(name), 
-		mSortingColumn(name), 
-        	mSortAscending(TRUE), 
-		mLabel(label), 
-		mWidth(width), 
-		mRelWidth(relwidth), 
-		mDynamicWidth(FALSE), 
-		mMaxContentWidth(0),
-		mIndex(-1), 
-		mParentCtrl(NULL), 
-		mHeader(NULL),
-		mFontAlignment(LLFontGL::LEFT)
-	{ }
-
-	LLScrollListColumn(const LLSD &sd)
-	{
-		mMaxContentWidth = 0;
-
-		mName = sd.get("name").asString();
-		mSortingColumn = mName;
-		if (sd.has("sort"))
-		{
-			mSortingColumn = sd.get("sort").asString();
-		}
-		mSortAscending = TRUE;
-		if (sd.has("sort_ascending"))
-		{
-			mSortAscending = sd.get("sort_ascending").asBoolean();
-		}
-		mLabel = sd.get("label").asString();
-		if (sd.has("relwidth") && (F32)sd.get("relwidth").asReal() > 0)
-		{
-			mRelWidth = (F32)sd.get("relwidth").asReal();
-			if (mRelWidth < 0) mRelWidth = 0;
-			if (mRelWidth > 1) mRelWidth = 1;
-			mDynamicWidth = FALSE;
-			mWidth = 0;
-		}
-		else if(sd.has("dynamicwidth") && (BOOL)sd.get("dynamicwidth").asBoolean() == TRUE)
-		{
-			mDynamicWidth = TRUE;
-			mRelWidth = -1;
-			mWidth = 0;
-		}
-		else
-		{
-			mWidth = sd.get("width").asInteger();
-			mDynamicWidth = FALSE;
-			mRelWidth = -1;
-		}
-
-		if (sd.has("halign"))
-		{
-			mFontAlignment = (LLFontGL::HAlign)llclamp(sd.get("halign").asInteger(), (S32)LLFontGL::LEFT, (S32)LLFontGL::HCENTER);
-		}
-		else
-		{
-			mFontAlignment = LLFontGL::LEFT;
-		}
-
-		mIndex = -1;
-		mParentCtrl = NULL;
-		mHeader = NULL;
-		mFontAlignment = LLFontGL::LEFT;
-	}
+	LLScrollListColumn();
+	LLScrollListColumn(const LLSD &sd, LLScrollListCtrl* parent);
+
+	void setWidth(S32 width);
+	S32 getWidth() const { return mWidth; }
 
 	// Public data is fine so long as this remains a simple struct-like data class.
 	// If it ever gets any smarter than that, these should all become private
@@ -275,7 +211,6 @@ public:
 	std::string			mSortingColumn;
 	BOOL				mSortAscending;
 	std::string			mLabel;
-	S32					mWidth;
 	F32					mRelWidth;
 	BOOL				mDynamicWidth;
 	S32					mMaxContentWidth;
@@ -283,6 +218,10 @@ public:
 	LLScrollListCtrl*	mParentCtrl;
 	class LLColumnHeader*		mHeader;
 	LLFontGL::HAlign	mFontAlignment;
+
+private:
+	S32					mWidth;
+
 };
 
 class LLColumnHeader : public LLComboBox
@@ -301,6 +240,7 @@ public:
 	void setImage(const std::string &image_name);
 	LLScrollListColumn* getColumn() { return mColumn; }
 	void setHasResizableElement(BOOL resizable);
+	void updateResizeBars();
 	BOOL canResize();
 	void enableResizeBar(BOOL enable);
 	std::string getLabel() { return mOrigLabel; }
@@ -551,8 +491,7 @@ public:
 
 	virtual S32		getScrollPos() const;
 	virtual void	setScrollPos( S32 pos );
-
-	S32				getSearchColumn() { return mSearchColumn; }
+	S32 getSearchColumn();
 	void			setSearchColumn(S32 column) { mSearchColumn = column; }
 	S32				getColumnIndexFromOffset(S32 x);
 	S32				getColumnOffsetFromIndex(S32 index);
@@ -613,8 +552,9 @@ public:
 	virtual void	deselect();
 	virtual BOOL	canDeselect() const;
 
-	void setNumDynamicColumns(int num) { mNumDynamicWidthColumns = num; }
-	void setTotalStaticColumnWidth(int width) { mTotalStaticColumnWidth = width; }
+	void setNumDynamicColumns(S32 num) { mNumDynamicWidthColumns = num; }
+	void updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width);
+	S32 getTotalStaticColumnWidth() { return mTotalStaticColumnWidth; }
 
 	std::string     getSortColumnName();
 	BOOL			getSortAscending() { return mSortColumns.empty() ? TRUE : mSortColumns.back().second; }
@@ -719,6 +659,7 @@ private:
 	S32				mSearchColumn;
 	S32				mNumDynamicWidthColumns;
 	S32				mTotalStaticColumnWidth;
+	S32				mTotalColumnPadding;
 
 	BOOL			mSorted;
 	
diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp
index a4b16dc2de..7b2c97af67 100644
--- a/indra/llui/llui.cpp
+++ b/indra/llui/llui.cpp
@@ -30,6 +30,7 @@
  */
 
 // Utilities functions the user interface needs
+
 #include "linden_common.h"
 
 #include <string>
@@ -65,6 +66,7 @@ std::map<std::string, std::string> gTranslation;
 std::list<std::string> gUntranslated;
 
 LLControlGroup* LLUI::sConfigGroup = NULL;
+LLControlGroup* LLUI::sIgnoresGroup = NULL;
 LLControlGroup* LLUI::sColorsGroup = NULL;
 LLImageProviderInterface* LLUI::sImageProvider = NULL;
 LLUIAudioCallback LLUI::sAudioCallback = NULL;
@@ -90,7 +92,7 @@ void make_ui_sound(const char* namep)
 		LLUUID uuid(LLUI::sConfigGroup->getString(name));		
 		if (uuid.isNull())
 		{
-			if ("00000000-0000-0000-0000-000000000000" == LLUI::sConfigGroup->getString(name))
+			if (LLUI::sConfigGroup->getString(name) == LLUUID::null.asString())
 			{
 				if (LLUI::sConfigGroup->getBOOL("UISndDebugSpamToggle"))
 				{
@@ -1552,6 +1554,7 @@ bool handleShowXUINamesChanged(const LLSD& newvalue)
 }
 
 void LLUI::initClass(LLControlGroup* config, 
+					 LLControlGroup* ignores, 
 					 LLControlGroup* colors, 
 					 LLImageProviderInterface* image_provider,
 					 LLUIAudioCallback audio_callback,
@@ -1559,7 +1562,16 @@ void LLUI::initClass(LLControlGroup* config,
 					 const std::string& language)
 {
 	sConfigGroup = config;
+	sIgnoresGroup = ignores;
 	sColorsGroup = colors;
+
+	if (sConfigGroup == NULL
+		|| sIgnoresGroup == NULL
+		|| sColorsGroup == NULL)
+	{
+		llerrs << "Failure to initialize configuration groups" << llendl;
+	}
+
 	sImageProvider = image_provider;
 	sAudioCallback = audio_callback;
 	sGLScaleFactor = (scale_factor == NULL) ? LLVector2(1.f, 1.f) : *scale_factor;
@@ -1567,7 +1579,7 @@ void LLUI::initClass(LLControlGroup* config,
 	LLFontGL::sShadowColor = colors->getColor("ColorDropShadow");
 
 	LLUI::sShowXUINames = LLUI::sConfigGroup->getBOOL("ShowXUINames");
-	LLUI::sConfigGroup->getControl("ShowXUINames")->getSignal()->connect(boost::bind(&handleShowXUINamesChanged, _1));
+	LLUI::sConfigGroup->getControl("ShowXUINames")->getSignal()->connect(&handleShowXUINamesChanged);
 }
 
 void LLUI::cleanupClass()
diff --git a/indra/llui/llui.h b/indra/llui/llui.h
index e2629ee2a4..4366c38740 100644
--- a/indra/llui/llui.h
+++ b/indra/llui/llui.h
@@ -42,6 +42,7 @@
 #include "llgl.h"			// *TODO: break this dependency
 #include <stack>
 //#include "llimagegl.h"
+#include <boost/signal.hpp>
 
 // LLUIFactory
 #include "llsd.h"
@@ -150,11 +151,13 @@ typedef	void (*LLUIAudioCallback)(const LLUUID& uuid);
 
 class LLUI
 {
+	LOG_CLASS(LLUI);
 public:
 	//
 	// Methods
 	//
 	static void initClass(LLControlGroup* config, 
+						  LLControlGroup* ignores,
 						  LLControlGroup* colors, 
 						  LLImageProviderInterface* image_provider,
 						  LLUIAudioCallback audio_callback = NULL,
@@ -190,6 +193,7 @@ public:
 	// Data
 	//
 	static LLControlGroup* sConfigGroup;
+	static LLControlGroup* sIgnoresGroup;
 	static LLControlGroup* sColorsGroup;
 	static LLImageProviderInterface* sImageProvider;
 	static LLUIAudioCallback sAudioCallback;
@@ -597,4 +601,237 @@ public:
 	virtual void cleanUp() = 0;
 };
 
+// This mix-in class adds support for tracking all instances of the specificed class parameter T
+// The (optional) key associates a value of type KEY with a given instance of T, for quick lookup
+// If KEY is not provided, then instances are stored in a simple list
+template<typename T, typename KEY = T*>
+class LLInstanceTracker : boost::noncopyable
+{
+public:
+	typedef typename std::map<KEY, T*>::iterator instance_iter;
+	typedef typename std::map<KEY, T*>::const_iterator instance_const_iter;
+
+	static T* getInstance(KEY k) { instance_iter found = sInstances.find(k); return (found == sInstances.end()) ? NULL : found->second; }
+
+	static instance_iter beginInstances() { return sInstances.begin(); }
+	static instance_iter endInstances() { return sInstances.end(); }
+	static S32 instanceCount() { return sInstances.size(); }
+protected:
+	LLInstanceTracker(KEY key) { add(key); }
+	virtual ~LLInstanceTracker() { remove(); }
+	virtual void setKey(KEY key) { remove(); add(key); }
+	virtual const KEY& getKey() const { return mKey; }
+
+private:
+	void add(KEY key) 
+	{ 
+		mKey = key; 
+		sInstances[key] = static_cast<T*>(this); 
+	}
+	void remove() { sInstances.erase(mKey); }
+
+private:
+
+	KEY mKey;
+	static std::map<KEY, T*> sInstances;
+};
+
+template<typename T>
+class LLInstanceTracker<T, T*> : boost::noncopyable
+{
+public:
+	typedef typename std::set<T*>::iterator instance_iter;
+	typedef typename std::set<T*>::const_iterator instance_const_iter;
+
+	static instance_iter instancesBegin() { return sInstances.begin(); }
+	static instance_iter instancesEnd() { return sInstances.end(); }
+	static S32 instanceCount() { return sInstances.size(); }
+
+protected:
+	LLInstanceTracker() { sInstances.insert(static_cast<T*>(this)); }
+	virtual ~LLInstanceTracker() { sInstances.erase(static_cast<T*>(this)); }
+
+	static std::set<T*> sInstances;
+};
+
+template <typename T, typename KEY> std::map<KEY, T*> LLInstanceTracker<T, KEY>::sInstances;
+template <typename T> std::set<T*> LLInstanceTracker<T, T*>::sInstances;
+
+class LLCallbackRegistry
+{
+public:
+	typedef boost::signal<void()> callback_signal_t;
+	
+	void registerCallback(const callback_signal_t::slot_type& slot)
+	{
+		mCallbacks.connect(slot);
+	}
+
+	void fireCallbacks()
+	{
+		mCallbacks();
+	}
+
+private:
+	callback_signal_t mCallbacks;
+};
+
+class LLInitClassList : 
+	public LLCallbackRegistry, 
+	public LLSingleton<LLInitClassList>
+{
+	friend class LLSingleton<LLInitClassList>;
+private:
+	LLInitClassList() {}
+};
+
+class LLDestroyClassList : 
+	public LLCallbackRegistry, 
+	public LLSingleton<LLDestroyClassList>
+{
+	friend class LLSingleton<LLDestroyClassList>;
+private:
+	LLDestroyClassList() {}
+};
+
+template<typename T>
+class LLRegisterWith
+{
+public:
+	LLRegisterWith(boost::function<void ()> func)
+	{
+		T::instance().registerCallback(func);
+	}
+
+	// this avoids a MSVC bug where non-referenced static members are "optimized" away
+	// even if their constructors have side effects
+	void reference()
+	{
+		S32 dummy;
+		dummy = 0;
+	}
+};
+
+template<typename T>
+class LLInitClass
+{
+public:
+	LLInitClass() { sRegister.reference(); }
+
+	static LLRegisterWith<LLInitClassList> sRegister;
+private:
+
+	static void initClass()
+	{
+		llerrs << "No static initClass() method defined for " << typeid(T).name() << llendl;
+	}
+};
+
+template<typename T>
+class LLDestroyClass
+{
+public:
+	LLDestroyClass() { sRegister.reference(); }
+
+	static LLRegisterWith<LLDestroyClassList> sRegister;
+private:
+
+	static void destroyClass()
+	{
+		llerrs << "No static destroyClass() method defined for " << typeid(T).name() << llendl;
+	}
+};
+
+template <typename T> LLRegisterWith<LLInitClassList> LLInitClass<T>::sRegister(&T::initClass);
+template <typename T> LLRegisterWith<LLDestroyClassList> LLDestroyClass<T>::sRegister(&T::destroyClass);
+
+
+template <typename DERIVED>
+class LLParamBlock
+{
+protected:
+	LLParamBlock() { sBlock = (DERIVED*)this; }
+
+	typedef typename boost::add_const<DERIVED>::type Tconst;
+
+	template <typename T>
+	class LLMandatoryParam
+	{
+	public:
+		typedef typename boost::add_const<T>::type T_const;
+
+		LLMandatoryParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {}
+		LLMandatoryParam(const LLMandatoryParam<T>& other) : mVal(other.mVal) {}
+
+		DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; }
+		operator T() const { return mVal; } 
+		T operator=(T_const set_value) { mVal = set_value; return mVal; }
+
+	private: 
+		T	mVal;
+		DERIVED* mBlock;
+	};
+
+	template <typename T>
+	class LLOptionalParam
+	{
+	public:
+		typedef typename boost::add_const<T>::type T_const;
+
+		LLOptionalParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {}
+		LLOptionalParam() : mBlock(sBlock) {}
+		LLOptionalParam(const LLOptionalParam<T>& other) : mVal(other.mVal) {}
+
+		DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; }
+		operator T() const { return mVal; } 
+		T operator=(T_const set_value) { mVal = set_value; return mVal; }
+
+	private:
+		T	mVal;
+		DERIVED* mBlock;
+	};
+
+	// specialization that requires initialization for reference types 
+	template <typename T>
+	class LLOptionalParam <T&>
+	{
+	public:
+		typedef typename boost::add_const<T&>::type T_const;
+
+		LLOptionalParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {}
+		LLOptionalParam(const LLOptionalParam<T&>& other) : mVal(other.mVal) {}
+
+		DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; }
+		operator T&() const { return mVal; } 
+		T& operator=(T_const set_value) { mVal = set_value; return mVal; }
+
+	private:
+		T&	mVal;
+		DERIVED* mBlock;
+	};
+
+	// specialization that initializes pointer params to NULL
+	template<typename T> 
+	class LLOptionalParam<T*>
+	{
+	public:
+		typedef typename boost::add_const<T*>::type T_const;
+
+		LLOptionalParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {}
+		LLOptionalParam() : mVal((T*)NULL), mBlock(sBlock)  {}
+		LLOptionalParam(const LLOptionalParam<T*>& other) : mVal(other.mVal) {}
+
+		DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; }
+		operator T*() const { return mVal; } 
+		T* operator=(T_const set_value) { mVal = set_value; return mVal; }
+	private:
+		T*	mVal;
+		DERIVED* mBlock;
+	};
+
+	static DERIVED* sBlock;
+};
+
+template <typename T> T* LLParamBlock<T>::sBlock = NULL;
+
 #endif
diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp
index f7419d615b..6e822f7c03 100644
--- a/indra/llui/lluictrl.cpp
+++ b/indra/llui/lluictrl.cpp
@@ -475,10 +475,10 @@ BOOL LLUICtrl::focusPrevItem(BOOL text_fields_only)
 	return focusPrev(result);
 }
 
-LLUICtrl* LLUICtrl::findRootMostFocusRoot() const
+LLUICtrl* LLUICtrl::findRootMostFocusRoot()
 {
-	const LLUICtrl* focus_root = NULL;
-	const LLUICtrl* next_view = this;
+	LLUICtrl* focus_root = NULL;
+	LLUICtrl* next_view = this;
 	while(next_view)
 	{
 		if (next_view->isFocusRoot())
@@ -487,9 +487,8 @@ LLUICtrl* LLUICtrl::findRootMostFocusRoot() const
 		}
 		next_view = next_view->getParentUICtrl();
 	}
-	// since focus_root could be this, need to cast away const to return
-	// a non-const result
-	return const_cast<LLUICtrl*>(focus_root);
+
+	return focus_root;
 }
 
 
diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h
index 4ad9042b9a..7d1f504f79 100644
--- a/indra/llui/lluictrl.h
+++ b/indra/llui/lluictrl.h
@@ -141,7 +141,7 @@ public:
 	
 	static LLView* fromXML(LLXMLNodePtr node, LLView* parent, class LLUICtrlFactory* factory);
 
-	LLUICtrl*		findRootMostFocusRoot() const;
+	LLUICtrl*		findRootMostFocusRoot();
 
 	class LLTextInputFilter : public LLQueryFilter, public LLSingleton<LLTextInputFilter>
 	{
diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h
index 3e22e196b0..dfcea7290e 100644
--- a/indra/llui/lluictrlfactory.h
+++ b/indra/llui/lluictrlfactory.h
@@ -78,6 +78,7 @@ public:
 	static bool getLayeredXMLNode(const std::string &filename, LLXMLNodePtr& root);
 
 private:
+	bool getLayeredXMLNodeImpl(const std::string &filename, LLXMLNodePtr& root);
 
 	typedef std::map<LLHandle<LLPanel>, std::string> built_panel_t;
 	built_panel_t mBuiltPanels;
diff --git a/indra/llui/lluistring.cpp b/indra/llui/lluistring.cpp
index 0dde20934c..51768ad885 100644
--- a/indra/llui/lluistring.cpp
+++ b/indra/llui/lluistring.cpp
@@ -31,6 +31,7 @@
 
 #include "linden_common.h"
 #include "lluistring.h"
+#include "llsd.h"
 
 const LLStringUtil::format_map_t LLUIString::sNullArgs;
 
@@ -54,6 +55,18 @@ void LLUIString::setArgList(const LLStringUtil::format_map_t& args)
 	format();
 }
 
+void LLUIString::setArgs(const LLSD& sd)
+{
+	if (!sd.isMap()) return;
+	for(LLSD::map_const_iterator sd_it = sd.beginMap();
+		sd_it != sd.endMap();
+		++sd_it)
+	{
+		setArg(sd_it->first, sd_it->second.asString());
+	}
+	format();
+}
+
 void LLUIString::setArg(const std::string& key, const std::string& replacement)
 {
 	mArgs[key] = replacement;
diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h
index 773bc09763..c99aeb50c6 100644
--- a/indra/llui/lluistring.h
+++ b/indra/llui/lluistring.h
@@ -50,9 +50,9 @@
 // llinfos << mMessage.getString() << llendl; // outputs "Welcome Steve to Second Life"
 // mMessage.setArg("[USERNAME]", "Joe");
 // llinfos << mMessage.getString() << llendl; // outputs "Welcome Joe to Second Life"
-// mMessage = "Recepci�n a la [SECONDLIFE] [USERNAME]"
+// mMessage = "Recepci￳n a la [SECONDLIFE] [USERNAME]"
 // mMessage.setArg("[SECONDLIFE]", "Segunda Vida");
-// llinfos << mMessage.getString() << llendl; // outputs "Recepci�n a la Segunda Vida Joe"
+// llinfos << mMessage.getString() << llendl; // outputs "Recepci￳n a la Segunda Vida Joe"
 
 // Implementation Notes:
 // Attempting to have operator[](const std::string& s) return mArgs[s] fails because we have
@@ -71,6 +71,8 @@ public:
 	LLUIString& operator=(const std::string& s) { assign(s); return *this; }
 
 	void setArgList(const LLStringUtil::format_map_t& args);
+	void setArgs(const LLStringUtil::format_map_t& args) { setArgList(args); }
+	void setArgs(const class LLSD& sd);
 	void setArg(const std::string& key, const std::string& replacement);
 
 	const std::string& getString() const { return mResult; }
diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp
index 3e7e59876c..65e9417b8b 100644
--- a/indra/llui/llview.cpp
+++ b/indra/llui/llview.cpp
@@ -1260,7 +1260,7 @@ void LLView::draw()
 	{
 		LLView *viewp = *child_iter;
 
-		if (viewp->getVisible() && viewp != focus_view)
+		if (viewp->getVisible() && viewp != focus_view && viewp->getRect().isValid())
 		{
 			// Only draw views that are within the root view
 			localRectToScreen(viewp->getRect(),&screenRect);
@@ -1357,7 +1357,8 @@ void LLView::drawChild(LLView* childp, S32 x_offset, S32 y_offset, BOOL force_dr
 	{
 		++sDepth;
 
-		if (childp->getVisible() || force_draw)
+		if ((childp->getVisible() && childp->getRect().isValid()) 
+			|| force_draw)
 		{
 			glMatrixMode(GL_MODELVIEW);
 			LLUI::pushMatrix();
diff --git a/indra/llui/llview.h b/indra/llui/llview.h
index ff7a1afb38..ae0df61599 100644
--- a/indra/llui/llview.h
+++ b/indra/llui/llview.h
@@ -485,7 +485,7 @@ public:
 			// did we find *something* with that name?
 			if (child)
 			{
-				llwarns << "Found child named " << name << " but of wrong type" << llendl;
+				llwarns << "Found child named " << name << " but of wrong type " << typeid(child).name() << ", expecting " << typeid(T).name() << llendl;
 			}
 			if (create_if_missing)
 			{
@@ -496,6 +496,11 @@ public:
 		return result;
 	}
 
+	template <class T> T& getChildRef(const std::string& name, BOOL recurse = TRUE) const
+	{
+		return *getChild<T>(name, recurse, TRUE);
+	}
+
 	virtual LLView* getChildView(const std::string& name, BOOL recurse = TRUE, BOOL create_if_missing = TRUE) const;
 
 	template <class T> T* createDummyWidget(const std::string& name) const
-- 
cgit v1.2.3