diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index ad3323426247ec9212653c63f011ba34b289a37c..1451eace3944b3aa91a6f8974ca9287add61c209 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -234,6 +234,10 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)
 	mParseHTML(p.parse_urls),
 	mForceUrlsExternal(p.force_urls_external),
 	mParseHighlights(p.parse_highlights),
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+	mHighlightsMask(LLHighlightEntry::CAT_GENERAL),
+	mHighlightsSignal(NULL),
+// [/SL:KB]
 	mBGVisible(p.bg_visible),
 	mScroller(NULL),
 	mStyleDirty(true)
@@ -291,6 +295,9 @@ LLTextBase::~LLTextBase()
 	delete mURLClickSignal;
 	delete mIsFriendSignal;
 	delete mIsObjectBlockedSignal;
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+	delete mHighlightsSignal;
+// [/SL:KB]
 }
 
 void LLTextBase::initFromParams(const LLTextBase::Params& p)
@@ -503,11 +510,8 @@ void LLTextBase::drawHighlightsBackground(const highlight_list_t& highlights, co
 		alpha *= getDrawContext().mAlpha;
 		LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha);
 
-		for (std::vector<LLRect>::iterator rect_it = selection_rects.begin();
-			rect_it != selection_rects.end();
-			++rect_it)
+		for (LLRect selection_rect : selection_rects)
 		{
-			LLRect selection_rect = *rect_it;
             if (mScroller)
             {
                 // If scroller is On content_display_rect has correct rect and safe to use as is
@@ -2230,6 +2234,19 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
 	LLStyle::Params style_params(input_params);
 	style_params.fillFrom(getStyleParams());
 
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+	const LLHighlightEntry* pEntry = NULL;
+	if ( (mParseHighlights) && (LLTextParser::instance().parseFullLineHighlights(new_text, mHighlightsMask, &pEntry)) )
+	{
+		if (mHighlightsSignal)
+			(*mHighlightsSignal)(new_text, pEntry);
+
+		style_params.color = pEntry->mColor;
+		if (pEntry->mColorReadOnly)
+			style_params.readonly_color = pEntry->mColor;
+	}
+// [/SL:KB]
+
 	S32 part = (S32)LLTextParser::WHOLE;
 	if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).
 	{
@@ -2340,6 +2357,15 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c
 	appendTextImpl(new_text,input_params);
 }
 
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+boost::signals2::connection LLTextBase::setHighlightsCallback(const highlights_signal_t::slot_type& cb)
+{
+	if (!mHighlightsSignal)
+		mHighlightsSignal = new highlights_signal_t();
+	return mHighlightsSignal->connect(cb);
+}
+// [/SL:KB]
+
 void LLTextBase::setLabel(const LLStringExplicit& label)
 {
 	mLabel = label;
@@ -2451,16 +2477,38 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
 	{
 		LLStyle::Params highlight_params(style_params);
 
-		LLSD pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part);
-		for (S32 i = 0; i < pieces.size(); i++)
+//		LLSD pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part);
+//		for (S32 i = 0; i < pieces.size(); i++)
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+		LLTextParser::partial_results_t results = LLTextParser::instance().parsePartialLineHighlights(new_text, mHighlightsMask, (LLTextParser::EHighlightPosition)highlight_part);
+		for (LLTextParser::partial_results_t::const_iterator itResult = results.begin(); itResult != results.end(); ++itResult)
+// [/SL:KB]
 		{
-			LLSD color_llsd = pieces[i]["color"];
-			LLColor4 lcolor;
-			lcolor.setValue(color_llsd);
-			highlight_params.color = lcolor;
+//			LLSD color_llsd = pieces[i]["color"];
+//			LLColor4 lcolor;
+//			lcolor.setValue(color_llsd);
+//			highlight_params.color = lcolor;
+//
+//			LLWString wide_text;
+//			wide_text = utf8str_to_wstring(pieces[i]["text"].asString());
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+			highlight_params.color = style_params.color();
+			highlight_params.readonly_color = style_params.readonly_color();
+
+			const LLHighlightEntry* pEntry = itResult->second;
+			if (pEntry)
+			{
+				if (mHighlightsSignal)
+					(*mHighlightsSignal)(new_text, pEntry);
+
+				highlight_params.color = pEntry->mColor;
+				if (pEntry->mColorReadOnly)
+					highlight_params.readonly_color = pEntry->mColor;
+			}
 
 			LLWString wide_text;
-			wide_text = utf8str_to_wstring(pieces[i]["text"].asString());
+			wide_text = utf8str_to_wstring(itResult->first);
+// [/SL:KB]
 
 			S32 cur_length = getLength();
 			LLStyleConstSP sp(new LLStyle(highlight_params));
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index a0bee6fdfd461db89aeb98bd66e0697496c34604..e701796cb689220fdb35e06f16b4b8102ea8b941 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -44,6 +44,9 @@
 class LLScrollContainer;
 class LLContextMenu;
 class LLUrlMatch;
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+class LLHighlightEntry;
+// [/SL:KB]
 
 ///
 /// A text segment is used to specify a subsection of a text string
@@ -501,6 +504,14 @@ class LLTextBase
 	void					setWordWrap(bool wrap);
 	LLScrollContainer*		getScrollContainer() const { return mScroller; }
 
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+	void setParseHighlights(bool parse)       { mParseHighlights = parse; }
+	S32  getHighlightsMask() const            { return mHighlightsMask; }
+	void setHighlightsMask(S32 category_mask) { mHighlightsMask = category_mask; }
+
+	typedef boost::signals2::signal<void(const std::string&, const LLHighlightEntry*)> highlights_signal_t;
+	boost::signals2::connection	setHighlightsCallback(const highlights_signal_t::slot_type& cb);
+// [/SL:KB]
 protected:
 	// protected member variables
 	// List of offsets and segment index of the start of each line.  Always has at least one node (0).
@@ -731,6 +742,10 @@ class LLTextBase
 	bool                		mParseHTML;			// make URLs interactive
 	bool						mForceUrlsExternal; // URLs from this textbox will be opened in external browser
 	bool						mParseHighlights;	// highlight user-defined keywords
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+	S32							mHighlightsMask;	// category mask for matching highlights
+	highlights_signal_t* 		mHighlightsSignal;	// signal fires whenever a highlighted segment is appended
+// [/SL:KB]
 	bool                		mWordWrap;
 	bool						mUseEllipses;
 	bool						mTrackEnd;			// if true, keeps scroll position at end of document during resize
diff --git a/indra/llui/lltextparser.cpp b/indra/llui/lltextparser.cpp
index 5b66fd1af7c72369db72a1c4bf5d6abd148f4158..fc095641d558a708ce380a866ab52adc7c529dbe 100644
--- a/indra/llui/lltextparser.cpp
+++ b/indra/llui/lltextparser.cpp
@@ -3,6 +3,7 @@
  *
  * $LicenseInfo:firstyear=2001&license=viewerlgpl$
  * Second Life Viewer Source Code
+ * Copyright (C) 2012, Kitty Barnett
  * Copyright (C) 2010, Linden Research, Inc.
  * 
  * This library is free software; you can redistribute it and/or
@@ -37,6 +38,78 @@
 #include "v4color.h"
 #include "lldir.h"
 
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+#include <boost/bind.hpp>
+#include <boost/algorithm/string.hpp>
+// [/SL:KB]
+
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+LLHighlightEntry::LLHighlightEntry()
+	: mId(LLUUID::generateNewID())
+	, mCategoryMask(CAT_NONE)
+	, mCondition(CONTAINS)
+	, mCaseSensitive(false)
+	, mColor(LLColor4::white)
+	, mColorReadOnly(true)
+	, mHighlightType(PART)
+	, mFlashWindow(false)
+{
+}
+
+LLHighlightEntry::LLHighlightEntry(const LLSD& sdEntry)
+	: mId(LLUUID::generateNewID())
+	, mCategoryMask(CAT_NONE)
+	, mCondition(CONTAINS)
+	, mCaseSensitive(false)
+	, mColor(LLColor4::white)
+	, mColorReadOnly(true)
+	, mHighlightType(PART)
+	, mFlashWindow(false)
+{
+	if (sdEntry.has("id"))
+		mId = sdEntry["id"].asUUID();
+	if (sdEntry.has("category_mask"))
+		mCategoryMask = sdEntry["category_mask"].asInteger();
+	if (sdEntry.has("condition"))
+		mCondition = (EConditionType)sdEntry["condition"].asInteger();
+	if (sdEntry.has("pattern"))
+		mPattern = sdEntry["pattern"].asString();
+	if (sdEntry.has("case_sensitive"))
+		mCaseSensitive = sdEntry["case_sensitive"].asBoolean();
+	if (sdEntry.has("color"))
+		mColor.setValue(sdEntry["color"]);
+	if (sdEntry.has("color_readonly"))
+		mColorReadOnly = sdEntry["color_readonly"].asBoolean();
+	if (sdEntry.has("highlight"))
+		mHighlightType = (EHighlightType)sdEntry["highlight"].asInteger();
+	if (sdEntry.has("sound_asset"))
+		mSoundAsset = sdEntry["sound_asset"].asUUID();
+	if (sdEntry.has("sound_item"))
+		mSoundItem = sdEntry["sound_item"].asUUID();
+	if (sdEntry.has("flash_window"))
+		mFlashWindow = sdEntry["flash_window"].asBoolean();
+}
+
+LLSD LLHighlightEntry::toLLSD() const
+{
+	LLSD sdEntry;
+	sdEntry["id"] = mId;
+	sdEntry["category_mask"] = mCategoryMask;
+	sdEntry["condition"] = (S32)mCondition;
+	sdEntry["pattern"] = mPattern;
+	sdEntry["case_sensitive"] = mCaseSensitive;
+	sdEntry["color"] = mColor.getValue();
+	sdEntry["color_readonly"] = mColorReadOnly;
+	sdEntry["highlight"] = (S32)mHighlightType;
+	if (mSoundAsset.notNull())
+		sdEntry["sound_asset"] = mSoundAsset;
+	if (mSoundItem.notNull())
+		sdEntry["sound_item"] = mSoundItem;
+	sdEntry["flash_window"] = mFlashWindow;
+	return sdEntry;
+}
+// [/SL:KB]
+
 //
 // Member Functions
 //
@@ -45,153 +118,261 @@ LLTextParser::LLTextParser()
 :	mLoaded(false)
 {}
 
-
-S32 LLTextParser::findPattern(const std::string &text, LLSD highlight)
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+S32 LLHighlightEntry::findPattern(const std::string& text, S32 cat_mask) const
 {
-	if (!highlight.has("pattern")) return -1;
-	
-	std::string pattern=std::string(highlight["pattern"]);
-	std::string ltext=text;
-	
-	if (!(bool)highlight["case_sensitive"])
-	{
-		ltext   = utf8str_tolower(text);
-		pattern= utf8str_tolower(pattern);
-	}
-
-	size_t found=std::string::npos;
+	if ( (mPattern.empty()) || ((mCategoryMask & cat_mask) == 0) )
+		return -1;
 	
-	switch ((S32)highlight["condition"])
+	size_t idxFound = std::string::npos;
+	switch (mCondition)
 	{
 		case CONTAINS:
-			found = ltext.find(pattern); 
+			{
+				boost::iterator_range<std::string::const_iterator> itRange = (mCaseSensitive) ? boost::find_first(text, mPattern) : boost::ifind_first(text, mPattern);
+				if (!itRange.empty())
+					idxFound = itRange.begin() - text.begin();
+			}
 			break;
 		case MATCHES:
-		    found = (! ltext.compare(pattern) ? 0 : std::string::npos);
+			{
+				if ( ((mCaseSensitive) && (boost::equals(text, mPattern))) || (boost::iequals(text, mPattern)) )
+					idxFound = 0;
+			}
 			break;
 		case STARTS_WITH:
-			found = (! ltext.find(pattern) ? 0 : std::string::npos);
+			{
+				if ( ((mCaseSensitive) && (boost::starts_with(text, mPattern))) || (boost::istarts_with(text, mPattern)) )
+					idxFound = 0;
+			}
 			break;
 		case ENDS_WITH:
-			size_t pos = ltext.rfind(pattern); 
-			if (pos != std::string::npos && pos >= 0 && (ltext.length()-pattern.length()==pos)) found = pos;
+			{
+				if ( ((mCaseSensitive) && (boost::ends_with(text, mPattern))) || (boost::iends_with(text, mPattern)) )
+					idxFound = text.length() - mPattern.length();
+			}
 			break;
 	}
-	return found;
+	return idxFound;
 }
+// [/SL:KB]
+//S32 LLTextParser::findPattern(const std::string &text, LLSD highlight)
+//{
+//	if (!highlight.has("pattern")) return -1;
+//	
+//	std::string pattern=std::string(highlight["pattern"]);
+//	std::string ltext=text;
+//	
+//	if (!(bool)highlight["case_sensitive"])
+//	{
+//		ltext   = utf8str_tolower(text);
+//		pattern= utf8str_tolower(pattern);
+//	}
+//
+//	size_t found=std::string::npos;
+//	
+//	switch ((S32)highlight["condition"])
+//	{
+//		case CONTAINS:
+//			found = ltext.find(pattern); 
+//			break;
+//		case MATCHES:
+//		    found = (! ltext.compare(pattern) ? 0 : std::string::npos);
+//			break;
+//		case STARTS_WITH:
+//			found = (! ltext.find(pattern) ? 0 : std::string::npos);
+//			break;
+//		case ENDS_WITH:
+//			S32 pos = ltext.rfind(pattern); 
+//			if (pos >= 0 && (ltext.length()-pattern.length()==pos)) found = pos;
+//			break;
+//	}
+//	return found;
+//}
 
-LLSD LLTextParser::parsePartialLineHighlights(const std::string &text, const LLColor4 &color, EHighlightPosition part, S32 index)
+//LLSD LLTextParser::parsePartialLineHighlights(const std::string &text, const LLColor4 &color, EHighlightPosition part, S32 index)
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+LLTextParser::partial_results_t LLTextParser::parsePartialLineHighlights(const std::string &text, S32 cat_mask, EHighlightPosition part, S32 index)
+// [/SL:KB]
 {
-	loadKeywords();
-
-	//evil recursive string atomizer.
-	LLSD ret_llsd, start_llsd, middle_llsd, end_llsd;
+//	loadKeywords();
+//
+//	//evil recursive string atomizer.
+//	LLSD ret_llsd, start_llsd, middle_llsd, end_llsd;
+//
+//	for (S32 i=index;i<mHighlights.size();i++)
+//	{
+//		S32 condition = mHighlights[i]["condition"];
+//		if ((S32)mHighlights[i]["highlight"]==PART && condition!=MATCHES)
+//		{
+//			if ( (condition==STARTS_WITH && part==START) ||
+//			     (condition==ENDS_WITH   && part==END)   ||
+//				  condition==CONTAINS    || part==WHOLE )
+//			{
+//				S32 start = findPattern(text,mHighlights[i]);
+//				if (start >= 0 )
+//				{
+//					S32 end =  std::string(mHighlights[i]["pattern"]).length();
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+	partial_results_t resStart, resMiddle, resEnd;
 
-	for (S32 i=index;i<mHighlights.size();i++)
+	for (S32 i = index; i < mHighlightEntries.size(); i++)
 	{
-		S32 condition = mHighlights[i]["condition"];
-		if ((S32)mHighlights[i]["highlight"]==PART && condition!=MATCHES)
+		const LLHighlightEntry& entry = mHighlightEntries[i];
+		if ( (entry.mHighlightType == LLHighlightEntry::PART) && (entry.mCondition != LLHighlightEntry::MATCHES) )
 		{
-			if ( (condition==STARTS_WITH && part==START) ||
-			     (condition==ENDS_WITH   && part==END)   ||
-				  condition==CONTAINS    || part==WHOLE )
+			if ( (entry.mCondition == LLHighlightEntry::STARTS_WITH && part == START) || 
+			     (entry.mCondition == LLHighlightEntry::ENDS_WITH   && part == END)   ||
+				  entry.mCondition == LLHighlightEntry::CONTAINS    || part == WHOLE )
 			{
-				S32 start = findPattern(text,mHighlights[i]);
+				S32 start = entry.findPattern(text, cat_mask);
 				if (start >= 0 )
 				{
-					S32 end =  std::string(mHighlights[i]["pattern"]).length();
+					S32 end = entry.mPattern.length();
+// [/SL:KB]
 					S32 len = text.length();
 					EHighlightPosition newpart;
 					if (start==0)
 					{
-						start_llsd[0]["text"] =text.substr(0,end);
-						start_llsd[0]["color"]=mHighlights[i]["color"];
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+						resStart.push_back(partial_result_t(text.substr(0, end), &entry));
+// [/SL:KB]
+//						start_llsd[0]["text"] =text.substr(0,end);
+//						start_llsd[0]["color"]=mHighlights[i]["color"];
 						
 						if (end < len)
 						{
 							if (part==END   || part==WHOLE) newpart=END; else newpart=MIDDLE;
-							end_llsd=parsePartialLineHighlights(text.substr( end ),color,newpart,i);
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+							resEnd = parsePartialLineHighlights(text.substr(end), cat_mask, newpart, i);
+// [/SL:KB]
+//							end_llsd=parsePartialLineHighlights(text.substr( end ),color,newpart,i);
 						}
 					}
 					else
 					{
 						if (part==START || part==WHOLE) newpart=START; else newpart=MIDDLE;
 
-						start_llsd=parsePartialLineHighlights(text.substr(0,start),color,newpart,i+1);
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+						resStart = parsePartialLineHighlights(text.substr(0,start), cat_mask, newpart, i+1);
+// [/SL:KB]
+//						start_llsd=parsePartialLineHighlights(text.substr(0,start),color,newpart,i+1);
 						
 						if (end < len)
 						{
-							middle_llsd[0]["text"] =text.substr(start,end);
-							middle_llsd[0]["color"]=mHighlights[i]["color"];
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+							resMiddle.push_back(partial_result_t(text.substr(start,end), &entry));
+// [/SL:KB]
+//							middle_llsd[0]["text"] =;
+//							middle_llsd[0]["color"]=mHighlights[i]["color"];
 						
 							if (part==END   || part==WHOLE) newpart=END; else newpart=MIDDLE;
 
-							end_llsd=parsePartialLineHighlights(text.substr( (start+end) ),color,newpart,i);
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+							resEnd = parsePartialLineHighlights(text.substr(start + end), cat_mask, newpart, i);
+// [/SL:KB]
+//							end_llsd=parsePartialLineHighlights(text.substr( (start+end) ),color,newpart,i);
 						}
 						else
 						{
-							end_llsd[0]["text"] =text.substr(start,end);
-							end_llsd[0]["color"]=mHighlights[i]["color"];
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+							resEnd.push_back(partial_result_t(text.substr(start,end), &entry));
+// [/SL:KB]
+//							end_llsd[0]["text"] =text.substr(start,end);
+//							end_llsd[0]["color"]=mHighlights[i]["color"];
 						}
 					}
 						
-					S32 retcount=0;
-					
-					//FIXME These loops should be wrapped into a subroutine.
-					for (LLSD::array_iterator iter = start_llsd.beginArray();
-						 iter != start_llsd.endArray();++iter)
-					{
-						LLSD highlight = *iter;
-						ret_llsd[retcount++]=highlight;
-					}
-						   
-					for (LLSD::array_iterator iter = middle_llsd.beginArray();
-						 iter != middle_llsd.endArray();++iter)
-					{
-						LLSD highlight = *iter;
-						ret_llsd[retcount++]=highlight;
-					}
-						   
-					for (LLSD::array_iterator iter = end_llsd.beginArray();
-						 iter != end_llsd.endArray();++iter)
-					{
-						LLSD highlight = *iter;
-						ret_llsd[retcount++]=highlight;
-					}
-						   
-					return ret_llsd;
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+					partial_results_t resFinal(resStart.size() + resMiddle.size() + resEnd.size());
+					resFinal.splice(resFinal.end(), resStart);
+					resFinal.splice(resFinal.end(), resMiddle);
+					resFinal.splice(resFinal.end(), resEnd);
+					return resFinal;
+// [/SL:KB]
+//					S32 retcount=0;
+//					
+//					//FIXME These loops should be wrapped into a subroutine.
+//					for (LLSD::array_iterator iter = start_llsd.beginArray();
+//						 iter != start_llsd.endArray();++iter)
+//					{
+//						LLSD highlight = *iter;
+//						ret_llsd[retcount++]=highlight;
+//					}
+//						   
+//					for (LLSD::array_iterator iter = middle_llsd.beginArray();
+//						 iter != middle_llsd.endArray();++iter)
+//					{
+//						LLSD highlight = *iter;
+//						ret_llsd[retcount++]=highlight;
+//					}
+//						   
+//					for (LLSD::array_iterator iter = end_llsd.beginArray();
+//						 iter != end_llsd.endArray();++iter)
+//					{
+//						LLSD highlight = *iter;
+//						ret_llsd[retcount++]=highlight;
+//					}
+//						   
+//					return ret_llsd;
 				}
 			}
 		}
 	}
 	
 	//No patterns found.  Just send back what was passed in.
-	ret_llsd[0]["text"] =text;
-	LLSD color_sd = color.getValue();
-	ret_llsd[0]["color"]=color_sd;
-	return ret_llsd;
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+	partial_results_t resFinal;
+	resFinal.push_back(partial_result_t(text, (const LLHighlightEntry*)NULL));
+	return resFinal;
+// [/SL:KB]
+//	ret_llsd[0]["text"] =text;
+//	LLSD color_sd = color.getValue();
+//	ret_llsd[0]["color"]=color_sd;
+//	return ret_llsd;
 }
 
-bool LLTextParser::parseFullLineHighlights(const std::string &text, LLColor4 *color)
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+bool LLTextParser::parseFullLineHighlights(const std::string& text, S32 cat_mask, const LLHighlightEntry** ppEntry) const
 {
-	loadKeywords();
-
-	for (S32 i=0;i<mHighlights.size();i++)
+	for (const LLHighlightEntry& entry : mHighlightEntries)
 	{
-		if ((S32)mHighlights[i]["highlight"]==ALL || (S32)mHighlights[i]["condition"]==MATCHES)
+		if ( (entry.mHighlightType == LLHighlightEntry::ALL) || (entry.mCondition == LLHighlightEntry::MATCHES) )
 		{
-			if (findPattern(text,mHighlights[i]) >= 0 )
+			if (std::string::npos != entry.findPattern(text, cat_mask))
 			{
-				LLSD color_llsd = mHighlights[i]["color"];
-				color->setValue(color_llsd);
+				if (ppEntry)
+					*ppEntry = &entry;
 				return TRUE;
 			}
 		}
 	}
 	return FALSE;	//No matches found.
 }
+// [/SL:KB]
+//bool LLTextParser::parseFullLineHighlights(const std::string &text, LLColor4 *color)
+//{
+//	loadKeywords();
+//
+//	for (S32 i=0;i<mHighlights.size();i++)
+//	{
+//		if ((S32)mHighlights[i]["highlight"]==ALL || (S32)mHighlights[i]["condition"]==MATCHES)
+//		{
+//			if (findPattern(text,mHighlights[i]) >= 0 )
+//			{
+//				LLSD color_llsd = mHighlights[i]["color"];
+//				color->setValue(color_llsd);
+//				return TRUE;
+//			}
+//		}
+//	}
+//	return FALSE;	//No matches found.
+//}
 
-std::string LLTextParser::getFileName()
+//std::string LLTextParser::getFileName()
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+std::string LLTextParser::getFileName() const
+// [/SL:KB]
 {
 	std::string path=gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "");
 	
@@ -202,38 +383,120 @@ std::string LLTextParser::getFileName()
 	return path;  
 }
 
-void LLTextParser::loadKeywords()
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+bool LLTextParser::loadKeywords()
 {
-	if (mLoaded)
-	{// keywords already loaded
-		return;
+	llifstream fileHighlights(getFileName());
+	if (!fileHighlights.is_open())
+	{
+		LL_WARNS() << "Can't open highlights file for reading" << LL_ENDL;
+		return false;
 	}
-	std::string filename=getFileName();
-	if (!filename.empty())
+
+	mHighlightEntries.clear();
+
+	LLSD sdIn;
+	if (LLSDSerialize::fromXML(sdIn, fileHighlights) == LLSDParser::PARSE_FAILURE)
 	{
-		llifstream file;
-		file.open(filename.c_str());
-		if (file.is_open())
-		{
-			LLSDSerialize::fromXML(mHighlights, file);
-		}
-		file.close();
-		mLoaded = true;
+		LL_WARNS() << "Failed to parse highlights file" << LL_ENDL;
+		return false;
 	}
+
+	for (const LLSD& sdEntry : sdIn.array())
+	{
+		mHighlightEntries.push_back(LLHighlightEntry(sdEntry));
+	}
+
+	return true;
 }
+// [/SL:KB]
+//void LLTextParser::loadKeywords()
+//{
+//	if (mLoaded)
+//	{// keywords already loaded
+//		return;
+//	}
+//	std::string filename=getFileName();
+//	if (!filename.empty())
+//	{
+//		llifstream file;
+//		file.open(filename.c_str());
+//		if (file.is_open())
+//		{
+//			LLSDSerialize::fromXML(mHighlights, file);
+//		}
+//		file.close();
+//		mLoaded = true;
+//	}
+//}
 
-bool LLTextParser::saveToDisk(LLSD highlights)
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+void LLTextParser::saveToDisk() const
 {
-	mHighlights=highlights;
-	std::string filename=getFileName();
-	if (filename.empty())
+	llofstream fileHighlights(getFileName());
+	if (!fileHighlights.is_open())
+	{
+		LL_WARNS() << "Can't open highlights file for writing" << LL_ENDL;
+		return;
+	}
+
+	LLSD out = LLSD::emptyArray();
+	for (const auto& entry : mHighlightEntries)
 	{
-		LL_WARNS() << "LLTextParser::saveToDisk() no valid user directory." << LL_ENDL; 
-		return FALSE;
-	}	
-	llofstream file;
-	file.open(filename.c_str());
-	LLSDSerialize::toPrettyXML(mHighlights, file);
-	file.close();
-	return TRUE;
+		out.append(entry.toLLSD());
+	}
+	LLSDSerialize::toPrettyXML(out, fileHighlights);
+}
+// [/SL:KB]
+//bool LLTextParser::saveToDisk(LLSD highlights)
+//{
+//	mHighlights=highlights;
+//	std::string filename=getFileName();
+//	if (filename.empty())
+//	{
+//		LL_WARNS() << "LLTextParser::saveToDisk() no valid user directory." << LL_ENDL; 
+//		return FALSE;
+//	}	
+//	llofstream file;
+//	file.open(filename.c_str());
+//	LLSDSerialize::toPrettyXML(mHighlights, file);
+//	file.close();
+//	return TRUE;
+//}
+
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-17 (Catznip-3.3)
+void LLTextParser::addHighlight(const LLHighlightEntry& entry)
+{
+	// Make sure we don't already have it added
+	if (NULL != getHighlightById(entry.getId()))
+		return;
+	mHighlightEntries.push_back(entry);
+}
+
+LLHighlightEntry* LLTextParser::getHighlightById(const LLUUID& idEntry)
+{
+	highlight_list_t::iterator itEntry =
+		std::find_if(mHighlightEntries.begin(), mHighlightEntries.end(), boost::bind(&LLHighlightEntry::getId, _1) == idEntry);
+	return (mHighlightEntries.end() != itEntry) ? &(*itEntry) : NULL;
+}
+
+const LLHighlightEntry* LLTextParser::getHighlightById(const LLUUID& idEntry) const
+{
+	highlight_list_t::const_iterator itEntry =
+		std::find_if(mHighlightEntries.begin(), mHighlightEntries.end(), boost::bind(&LLHighlightEntry::getId, _1) == idEntry);
+	return (mHighlightEntries.end() != itEntry) ? &(*itEntry) : NULL;
+}
+
+const LLTextParser::highlight_list_t& LLTextParser::getHighlights() const
+{
+	return mHighlightEntries;
+}
+
+void LLTextParser::removeHighlight(const LLUUID& idEntry)
+{
+	highlight_list_t::iterator itEntry =
+		std::find_if(mHighlightEntries.begin(), mHighlightEntries.end(), boost::bind(&LLHighlightEntry::getId, _1) == idEntry);
+	if (mHighlightEntries.end() != itEntry)
+		mHighlightEntries.erase(itEntry);
 }
+// [/SL:KB]
diff --git a/indra/llui/lltextparser.h b/indra/llui/lltextparser.h
index e76b21cfac9a4fe5839f047c31c76750c8988027..3c71a87fd7f254c656b91913d8fcc25ad3243e6b 100644
--- a/indra/llui/lltextparser.h
+++ b/indra/llui/lltextparser.h
@@ -4,6 +4,7 @@
  *
  * $LicenseInfo:firstyear=2002&license=viewerlgpl$
  * Second Life Viewer Source Code
+ * Copyright (C) 2012, Kitty Barnett
  * Copyright (C) 2010, Linden Research, Inc.
  * 
  * This library is free software; you can redistribute it and/or
@@ -30,33 +31,98 @@
 
 #include "llsd.h"
 #include "llsingleton.h"
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+#include "v4color.h"
+
+#include <list>
+// [/SL:KB]
 
 class LLUUID;
 class LLVector3d;
-class LLColor4;
+//class LLColor4;
 
-class LLTextParser final : public LLSingleton<LLTextParser>
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+class LLHighlightEntry
 {
-	LLSINGLETON(LLTextParser);
-
 public:
-	typedef enum e_condition_type { CONTAINS, MATCHES, STARTS_WITH, ENDS_WITH } EConditionType;
-	typedef enum e_highlight_type { PART, ALL } EHighlightType;
-	typedef enum e_highlight_position { WHOLE, START, MIDDLE, END } EHighlightPosition;
-	typedef enum e_dialog_action { ACTION_NONE, ACTION_CLOSE, ACTION_ADD, ACTION_COPY, ACTION_UPDATE } EDialogAction;
+	LLHighlightEntry();
+	LLHighlightEntry(const LLSD& sdEntry);
 
-	LLSD parsePartialLineHighlights(const std::string &text,const LLColor4 &color, EHighlightPosition part=WHOLE, S32 index=0);
-	bool parseFullLineHighlights(const std::string &text, LLColor4 *color);
+	S32           findPattern(const std::string& text, S32 cat_mask) const;
+	const LLUUID& getId() const { return mId; }
+	LLSD          toLLSD() const;
 
-private:
-	S32  findPattern(const std::string &text, LLSD highlight);
-	std::string getFileName();
-	void loadKeywords();
-	bool saveToDisk(LLSD highlights);
+public:
+	enum ECategory      { CAT_NONE = 0x00, CAT_GENERAL = 0x01, CAT_NEARBYCHAT = 0x02, CAT_IM = 0x04, CAT_GROUP = 0x08, CAT_ALL = 0xFF };
+	enum EConditionType { CONTAINS, MATCHES, STARTS_WITH, ENDS_WITH };
+	enum EHighlightType { PART, ALL };
+	S32            mCategoryMask;
+	EConditionType mCondition;
+	std::string    mPattern;
+	bool           mCaseSensitive;
+	LLColor4       mColor;
+	bool           mColorReadOnly;     // If TRUE, the highlight color will also be set as the read-only text color
+	EHighlightType mHighlightType;
+	// Other actions
+	LLUUID         mSoundAsset;        // Asset UUID of the sound
+	LLUUID         mSoundItem;         // Item UUID of the sound
+	bool           mFlashWindow;
+protected:
+	LLUUID         mId;
+};
+// [/SL:KB]
 
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-10 (Catznip-3.3)
+class LLTextParser : public LLSingleton<LLTextParser>
+{
+	LLSINGLETON(LLTextParser);
 public:
-	LLSD	mHighlights;
+	typedef std::vector<LLHighlightEntry> highlight_list_t;
+	void                    addHighlight(const LLHighlightEntry& entry);
+	LLHighlightEntry*       getHighlightById(const LLUUID& idEntry);
+	const LLHighlightEntry* getHighlightById(const LLUUID& idEntry) const;
+	S32                     getHighlightCount() const { return mHighlightEntries.size(); }
+	const highlight_list_t& getHighlights() const;
+	void                    removeHighlight(const LLUUID& idEntry);
+
+	typedef std::pair<std::string, const LLHighlightEntry*> partial_result_t;
+	typedef std::list<partial_result_t> partial_results_t;
+	typedef enum e_highlight_position { WHOLE, START, MIDDLE, END } EHighlightPosition;
+	partial_results_t parsePartialLineHighlights(const std::string &text, S32 cat_mask, EHighlightPosition part, S32 index = 0);
+	bool parseFullLineHighlights(const std::string& text, S32 cat_mask, const LLHighlightEntry** ppEntry = NULL) const;
+
+	bool loadKeywords();
+	void saveToDisk() const;
+protected:
+	std::string getFileName() const;
+
+protected:
 	bool	mLoaded;
+	highlight_list_t mHighlightEntries;
 };
+// [/SL:KB]
+//class LLTextParser : public LLSingleton<LLTextParser>
+//{
+//	LLSINGLETON(LLTextParser);
+//
+//public:
+//	typedef enum e_condition_type { CONTAINS, MATCHES, STARTS_WITH, ENDS_WITH } EConditionType;
+//	typedef enum e_highlight_type { PART, ALL } EHighlightType;
+//	typedef enum e_highlight_position { WHOLE, START, MIDDLE, END } EHighlightPosition;
+//	typedef enum e_dialog_action { ACTION_NONE, ACTION_CLOSE, ACTION_ADD, ACTION_COPY, ACTION_UPDATE } EDialogAction;
+//
+//	LLSD parsePartialLineHighlights(const std::string &text,const LLColor4 &color, EHighlightPosition part=WHOLE, S32 index=0);
+//	bool parseFullLineHighlights(const std::string &text, LLColor4 *color);
+//
+//private:
+//	S32  findPattern(const std::string &text, LLSD highlight);
+//	std::string getFileName();
+//	void loadKeywords();
+//	bool saveToDisk(LLSD highlights);
+//
+//public:
+//	LLSD	mHighlights;
+//	bool	mLoaded;
+//};
 
 #endif
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 3a41831e5eca3fad792946c46999d05b62104ecc..4a1de70396bde61a2c8cd7fcdc13aa071db6bf61 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -249,6 +249,7 @@ set(viewer_SOURCE_FILES
     llfloaterbuyland.cpp
     llfloatercamera.cpp
     llfloatercamerapresets.cpp
+    llfloaterchatalerts.cpp
     llfloaterchatvoicevolume.cpp
     llfloatercolorpicker.cpp
     llfloaterconversationlog.cpp
@@ -910,6 +911,7 @@ set(viewer_HEADER_FILES
     llfloaterbuyland.h
     llfloatercamerapresets.h
     llfloatercamera.h
+    llfloaterchatalerts.h
     llfloaterchatvoicevolume.h
     llfloatercolorpicker.h
     llfloaterconversationlog.h
diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml
index 40bccbff5b7b2800c60ba8799763f9a39a6eac01..76db5db97bf107aae2744895c24bbeeaa4c53aea 100644
--- a/indra/newview/app_settings/settings_alchemy.xml
+++ b/indra/newview/app_settings/settings_alchemy.xml
@@ -497,5 +497,16 @@
       <key>Value</key>
       <integer>1</integer>
     </map>
+    <key>ChatAlerts</key>
+    <map>
+      <key>Comment</key>
+      <string>Enable chat keyword alerts for nearby chat and instant messages</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <boolean>0</boolean>
+    </map>
   </map>
 </llsd>
diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp
index d5c1663df6a447305af1b07463565ca62a11f6da..b16358cd7fa2ac8f9b23bb2aa58b8cdc347307ad 100644
--- a/indra/newview/llchathistory.cpp
+++ b/indra/newview/llchathistory.cpp
@@ -64,6 +64,12 @@
 #include "lluiconstants.h"
 #include "llstring.h"
 #include "llurlaction.h"
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-07-10 (Catznip-3.3)
+#include "llaudioengine.h"
+#include "lltextparser.h"
+#include "llviewerwindow.h"
+#include "llwindow.h"
+// [/SL:KB]
 #include "llviewercontrol.h"
 #include "llviewermenu.h"
 #include "llviewerobjectlist.h"
@@ -1099,6 +1105,9 @@ class LLChatHistoryHeader: public LLPanel
 
 LLChatHistory::LLChatHistory(const LLChatHistory::Params& p)
 :	LLUICtrl(p),
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-27 (Catznip-3.3)
+	mParseHighlightTypeMask(PARSE_ALL),
+// [/SL:KB]
 	mMessageHeaderFilename(p.message_header),
 	mMessageSeparatorFilename(p.message_separator),
 	mLeftTextPad(p.left_text_pad),
@@ -1121,7 +1130,9 @@ LLChatHistory::LLChatHistory(const LLChatHistory::Params& p)
 	mEditor = LLUICtrlFactory::create<LLTextEditor>(editor_params, this);
 	mEditor->setIsFriendCallback(LLAvatarActions::isFriend);
 	mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0));
-
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-07-10 (Catznip-3.3)
+	mEditor->setHighlightsCallback(boost::bind(&LLChatHistory::onTextHighlight, this, _1, _2));
+// [/SL:KB]
 }
 
 LLSD LLChatHistory::getValue() const
@@ -1565,7 +1576,40 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
 			message += "]";
 	}
 
-		mEditor->appendText(message, prependNewLineState, body_message_params);
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-07-10 (Catznip-3.3)
+		static LLCachedControl<bool> sEnableChatAlerts(gSavedSettings, "ChatAlerts", false);
+		if (sEnableChatAlerts)
+		{
+			S32 nHighlightMask = mEditor->getHighlightsMask();
+			if ( (CHAT_STYLE_HISTORY != chat.mChatStyle) && (gAgentID != chat.mFromID) )
+			{
+				const LLIMModel::LLIMSession* pSession = NULL;
+				if (chat.mSessionID.isNull())
+				{
+					mEditor->setHighlightsMask(nHighlightMask| LLHighlightEntry::CAT_NEARBYCHAT);
+				}
+				else if (pSession = LLIMModel::getInstance()->findIMSession(chat.mSessionID))
+				{
+					if (pSession->isP2PSessionType())
+						mEditor->setHighlightsMask(nHighlightMask | LLHighlightEntry::CAT_IM);
+					else if ( (pSession->isGroupSessionType()) || (pSession->isAdHocSessionType()) )
+						mEditor->setHighlightsMask(nHighlightMask | LLHighlightEntry::CAT_GROUP);
+				}
+			}
+			else
+			{
+				mEditor->setHighlightsMask(LLHighlightEntry::CAT_GENERAL);
+			}
+
+			mEditor->appendText(message, prependNewLineState, body_message_params);
+			mEditor->setHighlightsMask(nHighlightMask & ~(LLHighlightEntry::CAT_NEARBYCHAT | LLHighlightEntry::CAT_IM | LLHighlightEntry::CAT_GROUP));
+		}
+		else
+		{
+			mEditor->appendText(message, prependNewLineState, body_message_params);
+		}
+// [/SL:KB]
+//		mEditor->appendText(message, prependNewLineState, body_message_params);
 		prependNewLineState = false;
 	}
 
@@ -1588,3 +1632,24 @@ void LLChatHistory::draw()
 
 	LLUICtrl::draw();
 }
+
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-27 (Catznip-3.3)
+void LLChatHistory::onTextHighlight(const std::string& strText, const LLHighlightEntry* pEntry)
+{
+	if (!pEntry)
+	{
+		return;
+	}
+
+	if ( (mParseHighlightTypeMask && PARSE_SOUND) && (pEntry->mSoundAsset.notNull()) && (gAudiop) )
+	{
+		gAudiop->triggerSound(pEntry->mSoundAsset, gAgent.getID(), 1.f, LLAudioEngine::AUDIO_TYPE_UI, gAgent.getPositionGlobal());
+	}
+	if ( (mParseHighlightTypeMask && PARSE_FLASH) && (pEntry->mFlashWindow) )
+	{
+		LLWindow* pWindow = gViewerWindow->getWindow();
+		if ( (pWindow) && (pWindow->getMinimized()) )
+			pWindow->flashIcon(5.f);
+	}
+}
+// [/SL:KB]
diff --git a/indra/newview/llchathistory.h b/indra/newview/llchathistory.h
index 44736a04895d7cd81257265c166a51e0c02b6ebe..fe1fe55cefb5079bdef1dbc095588b96f90d1061 100644
--- a/indra/newview/llchathistory.h
+++ b/indra/newview/llchathistory.h
@@ -31,6 +31,10 @@
 #include "lltextbox.h"
 #include "llviewerchat.h"
 
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-07-10 (Catznip-3.3)
+class LLHighlightEntry;
+// [/SL:KB]
+
 //Chat log widget allowing addition of a message as a widget 
 class LLChatHistory : public LLUICtrl
 {
@@ -121,7 +125,26 @@ class LLChatHistory : public LLUICtrl
 		void appendMessage(const LLChat& chat, const LLSD &args = LLSD(), const LLStyle::Params& input_append_params = LLStyle::Params());
 		/*virtual*/ void clear();
 
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-27 (Catznip-3.3)
+	public:
+		enum EParseHighlightType
+		{ 
+			PARSE_NONE  = 0x00, 
+			PARSE_FLASH = 0x01, 
+			PARSE_SOUND = 0x02, 
+			PARSE_ALL   = 0xFF
+		};
+
+		U32  getParseHighlightTypeMask() const        { return mParseHighlightTypeMask; }
+		void setParseHighlightTypeMask(U32 type_mask) { mParseHighlightTypeMask = type_mask; }
+	protected:
+		void onTextHighlight(const std::string& strText, const LLHighlightEntry* pEntry);
+// [/SL:KB]
+
 	private:
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-27 (Catznip-3.3)
+		U32 mParseHighlightTypeMask;
+// [/SL:KB]
 		std::string mLastFromName;
 		LLUUID mLastFromID;
 		LLDate mLastMessageTime;
diff --git a/indra/newview/llchatitemscontainerctrl.cpp b/indra/newview/llchatitemscontainerctrl.cpp
index 80525c04fa892978acf6cb928d6200a81a5aacbc..95042ec8ba58e99cd33fb6ac1689d111ef3650c8 100644
--- a/indra/newview/llchatitemscontainerctrl.cpp
+++ b/indra/newview/llchatitemscontainerctrl.cpp
@@ -37,6 +37,9 @@
 #include "lllocalcliprect.h"
 #include "lltrans.h"
 #include "llfloaterimnearbychat.h"
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-29 (Catznip-3.3)
+#include "lltextparser.h"
+// [/SL:KB]
 
 #include "llviewercontrol.h"
 #include "llagentdata.h"
@@ -66,12 +69,12 @@ class LLObjectHandler : public LLCommandHandler
 		if (params.size() < 2) return false;
 
 		LLUUID object_id;
-		if (!object_id.set(params[0].asStringRef(), FALSE))
+		if (!object_id.set(params[0].asString(), FALSE))
 		{
 			return false;
 		}
 
-		const std::string& verb = params[1].asStringRef();
+		const std::string verb = params[1].asString();
 
 		if (verb == "inspect")
 		{
@@ -131,7 +134,10 @@ BOOL LLFloaterIMNearbyChatToastPanel::postBuild()
 	return LLPanel::postBuild();
 }
 
-void LLFloaterIMNearbyChatToastPanel::addMessage(LLSD& notification)
+//void LLFloaterIMNearbyChatToastPanel::addMessage(LLSD& notification)
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-29 (Catznip-3.3)
+void LLFloaterIMNearbyChatToastPanel::addMessage(const LLSD& notification, bool prepend_newline)
+// [/SL:KB]
 {
 	std::string		messageText = notification["message"].asString();		// UTF-8 line of text
 
@@ -152,6 +158,36 @@ void LLFloaterIMNearbyChatToastPanel::addMessage(LLSD& notification)
 		case 2:	messageFont = LLFontGL::getFontSansSerifBig();	break;
 	}
 
+// [SL:KB] - Patch: Chat-Alerts | Checked: Catznip-5.3
+	// Copied from LLFloaterIMNearbyChatToastPanel::init(LLSD& notification)
+	S32 chars_in_line = mMsgText->getRect().getWidth() / messageFont->getWidth("c");
+	S32 max_lines = notification["available_height"].asInteger() / (mMsgText->getTextPixelHeight() + 4);
+	int lines = 0;
+	int chars = 0;
+
+	//Remove excessive chars if message does not fit in available height. MAINT-6891
+	std::string::iterator it;
+	for (it = messageText.begin(); it < messageText.end() && lines < max_lines; it++)
+	{
+		if (*it == '\n')
+			++lines;
+		else
+			++chars;
+
+		if (chars >= chars_in_line)
+		{
+			chars = 0;
+			++lines;
+		}
+	}
+
+	if (it < messageText.end())
+	{
+		messageText.erase(it, messageText.end());
+		messageText += " ...";
+	}
+// [/SL:KB]
+
 	//append text
 	{
 		LLStyle::Params style_params;
@@ -175,7 +211,22 @@ void LLFloaterIMNearbyChatToastPanel::addMessage(LLSD& notification)
 		{
 			style_params.font.style = "ITALIC";
 		}
-		mMsgText->appendText(messageText, TRUE, style_params);
+
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-29 (Catznip-3.3)
+		static LLCachedControl<bool> sEnableChatAlerts(gSavedSettings, "ChatAlerts", false);
+		if ( (sEnableChatAlerts) && (gAgentID != mFromID) )
+		{
+			S32 nHighlightMask = mMsgText->getHighlightsMask();
+			mMsgText->setHighlightsMask(nHighlightMask | LLHighlightEntry::CAT_NEARBYCHAT);
+			mMsgText->appendText(messageText, prepend_newline, style_params);
+			mMsgText->setHighlightsMask(nHighlightMask & ~LLHighlightEntry::CAT_NEARBYCHAT);
+		}
+		else
+		{
+			mMsgText->appendText(messageText, TRUE, style_params);
+		}
+// [/SL:KB]
+//		mMsgText->appendText(messageText, TRUE, style_params);
 	}
 
 	snapToMessageHeight();
@@ -184,7 +235,7 @@ void LLFloaterIMNearbyChatToastPanel::addMessage(LLSD& notification)
 
 void LLFloaterIMNearbyChatToastPanel::init(LLSD& notification)
 {
-	std::string		messageText = notification["message"].asString();		// UTF-8 line of text
+//	std::string		messageText = notification["message"].asString();		// UTF-8 line of text
 	std::string		fromName = notification["from"].asString();	// agent or object name
 	mFromID = notification["from_id"].asUUID();		// agent id or object id
 	mFromName = fromName;
@@ -196,10 +247,10 @@ void LLFloaterIMNearbyChatToastPanel::init(LLSD& notification)
 	int sType = notification["source"].asInteger();
     mSourceType = (EChatSourceType)sType;
 	
-	std::string color_name = notification["text_color"].asString();
-	
-	LLColor4 textColor = LLUIColorTable::instance().getColor(color_name);
-	textColor.mV[VALPHA] =notification["color_alpha"].asReal();
+//	std::string color_name = notification["text_color"].asString();
+//	
+//	LLColor4 textColor = LLUIColorTable::instance().getColor(color_name);
+//	textColor.mV[VALPHA] =notification["color_alpha"].asReal();
 	
 	S32 font_size = notification["font_size"].asInteger();
 
@@ -258,61 +309,64 @@ void LLFloaterIMNearbyChatToastPanel::init(LLSD& notification)
 		}
 	}
 
-	S32 chars_in_line = mMsgText->getRect().getWidth() / messageFont->getWidth("c");
-	S32 max_lines = notification["available_height"].asInteger() / (mMsgText->getTextPixelHeight() + 4);
-	int lines = 0;
-	int chars = 0;
-
-	//Remove excessive chars if message does not fit in available height. MAINT-6891
-	std::string::iterator it;
-	for (it = messageText.begin(); it < messageText.end() && lines < max_lines; it++)
-	{
-		if (*it == '\n')
-			++lines;
-		else
-			++chars;
-
-		if (chars >= chars_in_line)
-		{
-			chars = 0;
-			++lines;
-		}
-	}
-
-	if (it < messageText.end())
-	{
-		messageText.erase(it, messageText.end());
-		messageText += " ...";
-	}
-
-	//append text
-	{
-		LLStyle::Params style_params;
-		style_params.color(textColor);
-		std::string font_name = LLFontGL::nameFromFont(messageFont);
-		std::string font_style_size = LLFontGL::sizeFromFont(messageFont);
-		style_params.font.name(font_name);
-		style_params.font.size(font_style_size);
-
-		int chat_type = notification["chat_type"].asInteger();
-
-		if(notification["chat_style"].asInteger()== CHAT_STYLE_IRC)
-		{
-			style_params.font.style = "ITALIC";
-		}
-		else if( chat_type == CHAT_TYPE_SHOUT)
-		{
-			style_params.font.style = "BOLD";
-		}
-		else if( chat_type == CHAT_TYPE_WHISPER)
-		{
-			style_params.font.style = "ITALIC";
-		}
-		mMsgText->appendText(messageText, FALSE, style_params);
-	}
-
-
-	snapToMessageHeight();
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-29 (Catznip-3.3)
+	addMessage(notification, false);
+// [/SL:KB]
+//	S32 chars_in_line = mMsgText->getRect().getWidth() / messageFont->getWidth("c");
+//	S32 max_lines = notification["available_height"].asInteger() / (mMsgText->getTextPixelHeight() + 4);
+//	int lines = 0;
+//	int chars = 0;
+//
+//	//Remove excessive chars if message does not fit in available height. MAINT-6891
+//	std::string::iterator it;
+//	for (it = messageText.begin(); it < messageText.end() && lines < max_lines; it++)
+//	{
+//		if (*it == '\n')
+//			++lines;
+//		else
+//			++chars;
+//
+//		if (chars >= chars_in_line)
+//		{
+//			chars = 0;
+//			++lines;
+//		}
+//	}
+//
+//	if (it < messageText.end())
+//	{
+//		messageText.erase(it, messageText.end());
+//		messageText += " ...";
+//	}
+//
+//	//append text
+//	{
+//		LLStyle::Params style_params;
+//		style_params.color(textColor);
+//		std::string font_name = LLFontGL::nameFromFont(messageFont);
+//		std::string font_style_size = LLFontGL::sizeFromFont(messageFont);
+//		style_params.font.name(font_name);
+//		style_params.font.size(font_style_size);
+//
+//		int chat_type = notification["chat_type"].asInteger();
+//
+//		if(notification["chat_style"].asInteger()== CHAT_STYLE_IRC)
+//		{
+//			style_params.font.style = "ITALIC";
+//		}
+//		else if( chat_type == CHAT_TYPE_SHOUT)
+//		{
+//			style_params.font.style = "BOLD";
+//		}
+//		else if( chat_type == CHAT_TYPE_WHISPER)
+//		{
+//			style_params.font.style = "ITALIC";
+//		}
+//		mMsgText->appendText(messageText, FALSE, style_params);
+//	}
+//
+//
+//	snapToMessageHeight();
 
 	mIsDirty = true;//will set Avatar Icon in draw
 }
diff --git a/indra/newview/llchatitemscontainerctrl.h b/indra/newview/llchatitemscontainerctrl.h
index d50b9f4955ac8f05612a04472cf7ac6deb60ebb5..35caaa81eec740931b8d4fc27b24703adfcf63ca 100644
--- a/indra/newview/llchatitemscontainerctrl.h
+++ b/indra/newview/llchatitemscontainerctrl.h
@@ -80,7 +80,10 @@ class LLFloaterIMNearbyChatToastPanel : public LLPanel
 	BOOL	handleRightMouseDown(S32 x, S32 y, MASK mask);
 
 	virtual void init(LLSD& data);
-	virtual void addMessage(LLSD& data);
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-29 (Catznip-3.3)
+	virtual void addMessage(const LLSD& notification, bool prepend_newline = true);
+// [/SL:KB]
+//	virtual void addMessage(LLSD& data);
 
 	virtual void draw();
 
diff --git a/indra/newview/llfloaterchatalerts.cpp b/indra/newview/llfloaterchatalerts.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..320c92dfb174a6ed178047edecabfc7288d5e7b6
--- /dev/null
+++ b/indra/newview/llfloaterchatalerts.cpp
@@ -0,0 +1,508 @@
+/** 
+ *
+ * Copyright (c) 2012, Kitty Barnett
+ * 
+ * The source code in this file is provided to you under the terms of the 
+ * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
+ * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt 
+ * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
+ * 
+ * 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.
+ * 
+ */
+#include "llviewerprecompiledheaders.h"
+
+#include "llappviewer.h"
+#include "llaudioengine.h"
+#include "llassetstorage.h"
+#include "llbutton.h"
+#include "llcallbacklist.h"
+#include "llcheckboxctrl.h"
+#include "llcolorswatch.h"
+#include "llfloaterchatalerts.h"
+#include "lliconctrl.h"
+#include "llinventoryicon.h"
+#include "llinventorymodel.h"
+#include "lllineeditor.h"
+#include "llnotificationsutil.h"
+#include "llscrolllistctrl.h"
+#include "lltextparser.h"
+#include "llviewercontrol.h"
+#include "llviewerinventory.h"
+
+#include <boost/algorithm/string.hpp>
+
+// ============================================================================
+// LLSoundDropTarget helper class
+//
+
+static LLDefaultChildRegistry::Register<LLSoundDropTarget> r("sound_drop_target");
+
+LLSoundDropTarget::LLSoundDropTarget(const LLSoundDropTarget::Params& p) 
+	: LLView(p)
+	, m_pDropSignal(NULL)
+{
+}
+
+LLSoundDropTarget::~LLSoundDropTarget()
+{
+	delete m_pDropSignal;
+}
+
+BOOL LLSoundDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg)
+{
+	BOOL fHandled = FALSE;
+	if (getParent())
+	{
+		switch (cargo_type)
+		{
+			case DAD_SOUND:
+				{
+					const LLViewerInventoryItem* pItem = (LLViewerInventoryItem*)cargo_data;
+					if (gInventory.getItem(pItem->getUUID()))
+					{
+						*accept = ACCEPT_YES_COPY_SINGLE;
+
+						if (drop)
+						{
+							if ( (m_pDropSignal) && (!m_pDropSignal->empty()) )
+								(*m_pDropSignal)(pItem->getUUID());
+							else
+								getParent()->notifyParent(LLSD().with("item_id", pItem->getUUID()));;
+						}
+					}
+					else
+					{
+						// It's not in the user's inventory
+						*accept = ACCEPT_NO;
+					}
+				}
+				break;
+			default:
+				*accept = ACCEPT_NO;
+				break;
+		}
+
+		fHandled = TRUE;
+	}
+	return fHandled;
+}
+
+boost::signals2::connection LLSoundDropTarget::setDropCallback(const drop_signal_t::slot_type& cb) 
+{ 
+	if (!m_pDropSignal)
+		m_pDropSignal = new drop_signal_t();
+	return m_pDropSignal->connect(cb); 
+}
+
+// ============================================================================
+// LLFloaterChatAlerts
+//
+
+LLFloaterChatAlerts::LLFloaterChatAlerts(const LLSD& sdKey)
+	: LLFloater(sdKey)
+	, m_pAlertList(NULL)
+	, m_fNewEntry(false)
+	, m_pKeywordEditor(NULL)
+	, m_pKeywordCase(NULL)
+	, m_pColorCtrl(NULL)
+	, m_fSoundChanged(false)
+	, m_pSoundIconCtrl(NULL)
+	, m_pSoundEditor(NULL)
+	, m_pSoundClearBtn(NULL)
+	, m_pTriggerChat(NULL)
+	, m_pTriggerIM(NULL)
+	, m_pTriggerGroup(NULL)
+	, m_fChatAlertsEnabled(false)
+	, m_fPendingSave(false)
+{
+}
+
+LLFloaterChatAlerts::~LLFloaterChatAlerts()
+{
+	mChatAlertsConnection.disconnect();
+}
+
+BOOL LLFloaterChatAlerts::canClose()
+{
+	if (isEntryDirty())
+	{
+		m_fPendingSave = true;
+		onEntrySaveChanges(LLUUID::null, true);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+void LLFloaterChatAlerts::onOpen(const LLSD& sdKey)
+{
+	refresh();
+}
+
+void LLFloaterChatAlerts::onClose(bool app_quitting)
+{
+	LLTextParser::instance().saveToDisk();
+}
+
+BOOL LLFloaterChatAlerts::postBuild(void)
+{
+	m_pAlertList = findChild<LLScrollListCtrl>("alerts_list");
+	m_pAlertList->setCommitOnKeyboardMovement(true);
+	m_pAlertList->setCommitOnSelectionChange(true);
+	m_pAlertList->setCommitCallback(boost::bind(&LLFloaterChatAlerts::onEntrySelect, this));
+
+	findChild<LLButton>("alerts_new_btn")->setCommitCallback(boost::bind(&LLFloaterChatAlerts::onEntryNew, this));
+	findChild<LLButton>("alerts_delete_btn")->setCommitCallback(boost::bind(&LLFloaterChatAlerts::onEntryDelete, this));
+	findChild<LLButton>("alerts_save_btn")->setCommitCallback(boost::bind(&LLFloaterChatAlerts::onEntrySave, this));
+	findChild<LLButton>("alerts_revert_btn")->setCommitCallback(boost::bind(&LLFloaterChatAlerts::onEntryRevert, this));
+
+	m_pKeywordEditor = findChild<LLLineEditor>("alerts_keyword");
+	m_pKeywordCase = findChild<LLCheckBoxCtrl>("alerts_keyword_case");
+	m_pColorCtrl = findChild<LLColorSwatchCtrl>("alerts_highlight_color");
+
+	m_pSoundIconCtrl = findChild<LLIconCtrl>("alerts_sound_icon");
+	m_pSoundEditor = findChild<LLLineEditor>("alerts_sound_name");
+	m_pSoundClearBtn = findChild<LLButton>("alerts_sound_clear_btn");
+	m_pSoundClearBtn->setCommitCallback(boost::bind(&LLFloaterChatAlerts::onSoundClearItem, this));
+
+	m_pTriggerChat = findChild<LLCheckBoxCtrl>("alerts_trigger_chat");
+	m_pTriggerChat->setCommitCallback(boost::bind(&LLFloaterChatAlerts::onToggleTriggerType, this));
+	m_pTriggerIM = findChild<LLCheckBoxCtrl>("alerts_trigger_im");
+	m_pTriggerIM->setCommitCallback(boost::bind(&LLFloaterChatAlerts::onToggleTriggerType, this));
+	m_pTriggerGroup = findChild<LLCheckBoxCtrl>("alerts_trigger_group");
+	m_pTriggerGroup->setCommitCallback(boost::bind(&LLFloaterChatAlerts::onToggleTriggerType, this));
+
+	m_fChatAlertsEnabled = gSavedSettings.getBOOL("ChatAlerts");
+	mChatAlertsConnection = gSavedSettings.getControl("ChatAlerts")->getSignal()->connect(
+			boost::bind(&LLFloaterChatAlerts::onToggleChatAlerts, this, _2));
+
+	return TRUE;
+}
+
+S32 LLFloaterChatAlerts::notifyParent(const LLSD& sdInfo)
+{
+	if (sdInfo.has("item_id"))
+	{
+		m_fSoundChanged = true;
+		m_idSoundItem = sdInfo["item_id"].asUUID();
+		refreshSound();
+		return 1;
+	}
+	return LLFloater::notifyParent(sdInfo);
+}
+
+bool LLFloaterChatAlerts::isEntryDirty() const
+{
+	return
+		(m_pKeywordEditor->isDirty()) || (m_pKeywordCase->isDirty()) || (m_pColorCtrl->isDirty()) || (m_fSoundChanged) || 
+		(m_pTriggerChat->isDirty()) || (m_pTriggerIM->isDirty()) || (m_pTriggerGroup->isDirty());
+}
+
+void LLFloaterChatAlerts::onEntryNew()
+{
+	if (isEntryDirty())
+	{
+		m_fPendingSave = true;
+		onEntrySaveChanges(LLUUID::null, false);
+	}
+	else
+	{
+		m_pAlertList->deselectAllItems(true);
+		refreshEntry(true);
+		m_pKeywordEditor->setFocus(true);
+	}
+}
+
+void LLFloaterChatAlerts::onEntryDelete()
+{
+	const LLUUID idEntry = m_pAlertList->getFirstSelected()->getUUID();
+	if (idEntry.notNull())
+	{
+		LLTextParser::instance().removeHighlight(idEntry);
+	}
+	refreshList();
+}
+
+void LLFloaterChatAlerts::onEntrySave()
+{
+	LLHighlightEntry* pEntry = (m_idCurEntry.notNull()) ? LLTextParser::instance().getHighlightById(m_idCurEntry) 
+	                                                    : (m_fNewEntry) ? new LLHighlightEntry() : NULL;
+	if ( (pEntry) && (m_pKeywordEditor->getLength() > 0) )
+	{
+		if ( (m_pKeywordEditor->isDirty()) || (m_fNewEntry) )
+		{
+			pEntry->mPattern = m_pKeywordEditor->getText();
+			boost::trim(pEntry->mPattern);
+		}
+		if ( (m_pKeywordCase->isDirty()) || (m_fNewEntry) )
+		{
+			pEntry->mCaseSensitive = m_pKeywordCase->get();
+		}
+		if ( (m_pColorCtrl->isDirty()) || (m_fNewEntry) )
+		{
+			pEntry->mColor.set(m_pColorCtrl->get());
+		}
+
+		if (m_fSoundChanged)
+		{
+			// Store both the item and asset UUID for now
+			const LLViewerInventoryItem* pItem = gInventory.getItem(m_idSoundItem);
+			pEntry->mSoundAsset = (pItem) ? pItem->getAssetUUID() : LLUUID::null;
+			pEntry->mSoundItem = m_idSoundItem;
+		}
+
+		if ( (m_pTriggerChat->isDirty()) || (m_fNewEntry) )
+		{
+			if (m_pTriggerChat->get())
+				pEntry->mCategoryMask |= LLHighlightEntry::CAT_NEARBYCHAT;
+			else
+				pEntry->mCategoryMask &= ~LLHighlightEntry::CAT_NEARBYCHAT;
+		}
+		if ( (m_pTriggerIM->isDirty()) || (m_fNewEntry) )
+		{
+			if (m_pTriggerIM->get())
+				pEntry->mCategoryMask |= LLHighlightEntry::CAT_IM;
+			else
+				pEntry->mCategoryMask &= ~LLHighlightEntry::CAT_IM;
+		}
+		if ( (m_pTriggerGroup->isDirty()) || (m_fNewEntry) )
+		{
+			if (m_pTriggerGroup->get())
+				pEntry->mCategoryMask |= LLHighlightEntry::CAT_GROUP;
+			else
+				pEntry->mCategoryMask &= ~LLHighlightEntry::CAT_GROUP;
+		}
+
+		if (m_fNewEntry)
+		{
+			LLTextParser::instance().addHighlight(*pEntry);
+
+			refreshList();
+			m_pAlertList->setSelectedByValue(pEntry->getId(), true);
+
+			delete pEntry;
+		}
+		else
+		{
+			refreshEntry(false);
+		}
+	}
+}
+
+void LLFloaterChatAlerts::onEntrySaveChanges(const LLUUID& idNewEntry, bool fCloseFloater)
+{
+	// HACK: the scroll list control seems to trigger the commit callback multiple times over different mouse events
+	//       so we try and get rid of duplicate notifcations by delaying showing them slightly
+	if (m_fPendingSave)
+	{
+		m_fPendingSave = false;
+
+		// We don't want to trigger the commit callback
+		m_pAlertList->setCommitOnSelectionChange(false);
+		m_pAlertList->setSelectedByValue(m_idCurEntry, true);
+		m_pAlertList->setCommitOnSelectionChange(true);
+
+		LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD().with("cur_sel", m_idCurEntry).with("new_sel", idNewEntry).with("close", fCloseFloater),  
+			boost::bind(&LLFloaterChatAlerts::onEntrySaveChangesCallback, this, _1, _2));
+	}
+}
+
+void LLFloaterChatAlerts::onEntrySaveChangesCallback(const LLSD& notification, const LLSD& response)
+{
+	const LLUUID idCurSel = notification["payload"]["cur_sel"].asUUID();
+	const LLUUID idNewSel = notification["payload"]["new_sel"].asUUID();
+
+	// Only process if the current item still matches
+	if (idCurSel == m_idCurEntry)
+	{
+		S32 idxOption = LLNotificationsUtil::getSelectedOption(notification, response);
+		switch(idxOption)
+		{
+			case 0:   // "Yes"
+				onEntrySave();
+			case 1:   // "No"
+				if (idNewSel.notNull())
+				{
+					// Select existing
+					m_pAlertList->setCommitOnSelectionChange(false);
+					m_pAlertList->setSelectedByValue(idNewSel, true);
+					m_pAlertList->setCommitOnSelectionChange(true);
+					refreshEntry(false);
+				}
+				else
+				{
+					// Create new
+					m_pAlertList->deselectAllItems(true);
+					refreshEntry(true);
+					m_pKeywordEditor->setFocus(true);
+				}
+
+				if (notification["payload"]["close"].asBoolean())
+				{
+					closeFloater();
+				}
+
+				break;
+			case 2:   // "Cancel"
+			default:
+		        LLAppViewer::instance()->abortQuit();
+				break;
+		}
+	}
+}
+
+void LLFloaterChatAlerts::onEntryRevert()
+{
+	refreshEntry(false);
+}
+
+void LLFloaterChatAlerts::onEntrySelect()
+{
+	// Don't do anything if the user clicked on the current entry a second time
+	if (m_idCurEntry == m_pAlertList->getSelectedValue().asUUID())
+	{
+		return;
+	}
+
+	if (isEntryDirty())
+	{
+		m_fPendingSave = true;
+		doOnIdleOneTime(boost::bind(&LLFloaterChatAlerts::onEntrySaveChanges, this, m_pAlertList->getSelectedValue().asUUID(), false));
+	}
+	else
+	{
+		refreshEntry(false);
+		m_pKeywordEditor->setFocus(true);
+	}
+}
+
+void LLFloaterChatAlerts::onSoundClearItem()
+{
+	m_fSoundChanged = true;
+	m_idSoundItem.setNull();
+	refreshSound();
+}
+
+void LLFloaterChatAlerts::onToggleChatAlerts(const LLSD& sdValue)
+{
+	m_fChatAlertsEnabled = sdValue.asBoolean();
+	refresh();
+}
+
+void LLFloaterChatAlerts::onToggleTriggerType()
+{
+	// Make sure at least one of the trigger checkboxes is checked at all times
+	if ( (!m_pTriggerChat->get()) && (!m_pTriggerIM->get()) && (!m_pTriggerGroup->get()) )
+	{
+		m_pTriggerChat->set(true);
+	}
+}
+
+void LLFloaterChatAlerts::refresh()
+{
+	m_pAlertList->setEnabled(m_fChatAlertsEnabled);
+	m_pAlertList->clearRows();
+
+	findChild<LLButton>("alerts_new_btn")->setEnabled(m_fChatAlertsEnabled);
+
+	refreshList();
+}
+
+void LLFloaterChatAlerts::refreshList()
+{
+	m_pAlertList->clearRows();
+
+	// Set-up a row we can just reuse
+	LLSD sdRow; 
+	LLSD& sdColumns = sdRow["columns"];
+	sdColumns[0]["column"] = "alert_keyword";
+	sdColumns[0]["type"] = "text";
+
+	const LLTextParser::highlight_list_t& highlights = LLTextParser::instance().getHighlights();
+	for (LLTextParser::highlight_list_t::const_iterator itHighlight = highlights.begin(); 
+			itHighlight != highlights.end(); ++itHighlight)
+	{
+		const LLHighlightEntry& entry = *itHighlight;
+
+		sdColumns[0]["value"] = entry.mPattern;
+		sdRow["value"] = entry.getId();
+
+		m_pAlertList->addElement(sdRow, ADD_BOTTOM);
+	}
+	m_pAlertList->sortByColumnIndex(0, true);
+	m_pAlertList->setEnabled(m_fChatAlertsEnabled);
+
+	refreshEntry(false);
+}
+
+void LLFloaterChatAlerts::refreshEntry(bool fNewEntry)
+{
+	m_fNewEntry = fNewEntry;
+	m_idCurEntry = m_pAlertList->getSelectedValue().asUUID();;
+	const LLHighlightEntry* pEntry = (m_idCurEntry.notNull()) ? LLTextParser::instance().getHighlightById(m_idCurEntry) : NULL;
+	bool fEnable = (NULL != pEntry) || (fNewEntry);
+
+	findChild<LLButton>("alerts_delete_btn")->setEnabled(m_idCurEntry.notNull());
+	findChild<LLButton>("alerts_save_btn")->setEnabled(fEnable);
+	findChild<LLButton>("alerts_revert_btn")->setEnabled(m_idCurEntry.notNull());
+
+	findChild<LLUICtrl>("alerts_keyword_label")->setEnabled(fEnable);
+	m_pKeywordEditor->setEnabled(fEnable);
+	m_pKeywordEditor->setText( (pEntry) ? pEntry->mPattern : "" );
+	m_pKeywordEditor->resetDirty();
+	m_pKeywordCase->setEnabled(fEnable);
+	m_pKeywordCase->set( (pEntry) ? pEntry->mCaseSensitive : false );
+	m_pKeywordCase->resetDirty();
+	findChild<LLUICtrl>("alerts_color_label")->setEnabled(fEnable);
+	m_pColorCtrl->setEnabled(fEnable);
+	m_pColorCtrl->set( (pEntry) ? pEntry->mColor : LLColor4::white );
+	m_pColorCtrl->resetDirty();
+
+	findChild<LLView>("sound_drop_target")->setEnabled(fEnable);
+	findChild<LLUICtrl>("alerts_sound_label")->setEnabled(fEnable);
+	m_fSoundChanged = false;
+	m_idSoundItem = (pEntry) ? pEntry->mSoundItem : LLUUID::null;
+	refreshSound();
+
+	findChild<LLUICtrl>("alerts_trigger_label")->setEnabled(fEnable);
+	m_pTriggerChat->setEnabled(fEnable);
+	m_pTriggerChat->set( (pEntry) ? pEntry->mCategoryMask & LLHighlightEntry::CAT_NEARBYCHAT: fNewEntry );
+	m_pTriggerChat->resetDirty();
+	m_pTriggerIM->setEnabled(fEnable);
+	m_pTriggerIM->set( (pEntry) ? pEntry->mCategoryMask & LLHighlightEntry::CAT_IM : fNewEntry );
+	m_pTriggerIM->resetDirty();
+	m_pTriggerGroup->setEnabled(fEnable);
+	m_pTriggerGroup->set( (pEntry) ? pEntry->mCategoryMask & LLHighlightEntry::CAT_GROUP : fNewEntry );
+	m_pTriggerGroup->resetDirty();
+}
+
+void LLFloaterChatAlerts::refreshSound()
+{
+	const LLViewerInventoryItem* pItem = gInventory.getItem(m_idSoundItem);
+	if (pItem)
+	{
+		std::string strIconName = LLInventoryIcon::getIconName(pItem->getType(), pItem->getInventoryType(), pItem->getFlags());
+
+		m_idSoundItem = pItem->getUUID();
+		m_pSoundIconCtrl->setValue(strIconName);
+		m_pSoundIconCtrl->setVisible(true);
+		m_pSoundEditor->setText(pItem->getName());
+		m_pSoundClearBtn->setVisible(true);
+
+		if ( (gAssetStorage) && (!gAssetStorage->hasLocalAsset(pItem->getAssetUUID(), LLAssetType::AT_SOUND)) && (gAudiop) )
+		{
+			gAssetStorage->getAssetData(pItem->getAssetUUID(), LLAssetType::AT_SOUND, LLAudioEngine::assetCallback, NULL);
+		}
+	}
+	else
+	{
+		m_pSoundIconCtrl->setVisible(false);
+		m_pSoundEditor->setText( (m_idSoundItem.notNull()) ? m_idSoundItem.asString() : LLStringUtil::null );
+		m_pSoundClearBtn->setVisible(false);
+	}
+}
+
+// ============================================================================
diff --git a/indra/newview/llfloaterchatalerts.h b/indra/newview/llfloaterchatalerts.h
new file mode 100644
index 0000000000000000000000000000000000000000..b8b3a2a45f321a686a0c182da93e5e9876c00475
--- /dev/null
+++ b/indra/newview/llfloaterchatalerts.h
@@ -0,0 +1,125 @@
+/** 
+ *
+ * Copyright (c) 2012, Kitty Barnett
+ * 
+ * The source code in this file is provided to you under the terms of the 
+ * GNU Lesser General Public License, version 2.1, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
+ * PARTICULAR PURPOSE. Terms of the LGPL can be found in doc/LGPL-licence.txt 
+ * in this distribution, or online at http://www.gnu.org/licenses/lgpl-2.1.txt
+ * 
+ * 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.
+ * 
+ */
+#ifndef LLFLOATERCHATALERTS_H
+#define LLFLOATERCHATALERTS_H
+
+#include "llfloater.h"
+
+class LLButton;
+class LLCheckBoxCtrl;
+class LLColorSwatchCtrl;
+class LLIconCtrl;
+class LLLineEditor;
+class LLScrollListCtrl;
+
+// ============================================================================
+
+class LLFloaterChatAlerts : public LLFloater
+{
+	friend class LLFloaterReg;
+private:
+	LLFloaterChatAlerts(const LLSD& sdKey);
+public:
+	/*virtual*/ ~LLFloaterChatAlerts();
+	/*virtual*/ BOOL canClose();
+	/*virtual*/ void onOpen(const LLSD& sdKey);
+	/*virtual*/ void onClose(bool app_quitting);
+	/*virtual*/ BOOL postBuild();
+	/*virtual*/ S32  notifyParent(const LLSD& sdInfo);
+
+public:
+	bool isEntryDirty() const;
+protected:
+	void onEntryNew();
+	void onEntryDelete();
+	void onEntrySave();
+	void onEntrySaveChanges(const LLUUID& idNewEntry, bool fCloseFloater);
+	void onEntrySaveChangesCallback(const LLSD& notification, const LLSD& response);
+	void onEntryRevert();
+	void onEntrySelect();
+	void onSoundClearItem();
+	void onToggleChatAlerts(const LLSD& sdValue);
+	void onToggleTriggerType();
+	void refresh();
+	void refreshList();
+	void refreshEntry(bool fNewEntry);
+	void refreshSound();
+
+protected:
+	LLScrollListCtrl*  m_pAlertList;
+	bool               m_fNewEntry;
+	LLUUID             m_idCurEntry;
+	LLLineEditor*      m_pKeywordEditor;
+	LLCheckBoxCtrl*    m_pKeywordCase;
+	LLColorSwatchCtrl* m_pColorCtrl;
+
+	bool               m_fSoundChanged;
+	LLUUID             m_idSoundItem;
+	LLIconCtrl*        m_pSoundIconCtrl;
+	LLLineEditor*      m_pSoundEditor;
+	LLButton*          m_pSoundClearBtn;
+
+	LLCheckBoxCtrl*    m_pTriggerChat;
+	LLCheckBoxCtrl*    m_pTriggerIM;
+	LLCheckBoxCtrl*    m_pTriggerGroup;
+	bool               m_fPendingSave;
+
+	bool m_fChatAlertsEnabled;
+	boost::signals2::connection mChatAlertsConnection;
+};
+
+// ============================================================================
+// LLSoundDropTarget helper class
+//
+
+class LLSoundDropTarget : public LLView
+{
+public:
+	struct Params : public LLInitParam::Block<Params, LLView::Params>
+	{
+		Params()
+		{
+			changeDefault(mouse_opaque, false);
+			changeDefault(follows.flags, FOLLOWS_ALL);
+		}
+	};
+
+	LLSoundDropTarget(const Params&);
+	virtual ~LLSoundDropTarget();
+
+	/*
+	 * Member functions
+	 */
+public:
+	typedef boost::signals2::signal<void (const LLUUID&)> drop_signal_t;
+	boost::signals2::connection setDropCallback(const drop_signal_t::slot_type& cb);
+
+	/*
+	 * LLView overrides
+	 */
+public:
+	/*virtual*/ BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg);
+
+	/*
+	 * Member variables
+	 */
+protected:
+	drop_signal_t* m_pDropSignal;
+};
+
+// ============================================================================
+
+#endif  // LLFLOATERCHATALERTS_H
diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp
index e2ae19bdf4e221e112fb0286a5d2015abcf0ae1f..76fd9d911508ce79420deff7fa193950351d0bfb 100644
--- a/indra/newview/llfloaterimsession.cpp
+++ b/indra/newview/llfloaterimsession.cpp
@@ -180,7 +180,11 @@ void LLFloaterIMSession::newIMCallback(const LLSD& data)
 		LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance<LLFloaterIMSession>("impanel", session_id);
 
         // update if visible, otherwise will be updated when opened
-		if (floater && floater->isInVisibleChain())
+//		if (floater && floater->isInVisibleChain())
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-09-18 (Catznip-3.3)
+		// Add messages (but not notifications) as they come in (otherwise keyword alerts only trigger when the IM is opened)
+		if ( (floater && floater->isInVisibleChain()) || (!data.has("notification_id")) || (LLNotificationsUtil::find(data["notification_id"].asUUID()) == NULL) )
+// [/SL:KB]
 		{
 			floater->updateMessages();
 		}
@@ -971,8 +975,15 @@ void LLFloaterIMSession::reloadMessages(bool clean_messages/* = false*/)
 
 	mChatHistory->clear();
 	mLastMessageIndex = -1;
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-27 (Catznip-3.3)
+	bool fParseMask = mChatHistory->getParseHighlightTypeMask();
+	mChatHistory->setParseHighlightTypeMask(LLChatHistory::PARSE_NONE);
+// [/SL:KB]
 	updateMessages();
 	mInputEditor->setFont(LLViewerChat::getChatFont());
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-27 (Catznip-3.3)
+	mChatHistory->setParseHighlightTypeMask(fParseMask);
+// [/SL:KB]
 }
 
 // static
@@ -1120,6 +1131,13 @@ void LLFloaterIMSession::processAgentListUpdates(const LLSD& body)
 	// the vectors need to be sorted for computing the intersection and difference
 	std::sort(mInvitedParticipants.begin(), mInvitedParticipants.end());
     std::sort(joined_uuids.begin(), joined_uuids.end());
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-27 (Catznip-3.3)
+	bool fParseMask = mChatHistory->getParseHighlightTypeMask();
+	mChatHistory->setParseHighlightTypeMask(LLChatHistory::PARSE_NONE);
+// [/SL:KB]
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-27 (Catznip-3.3)
+	mChatHistory->setParseHighlightTypeMask(fParseMask);
+// [/SL:KB]
 
     uuid_vec_t intersection; // uuids of invited residents who have joined the conversation
 	std::set_intersection(mInvitedParticipants.begin(), mInvitedParticipants.end(),
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 3f8726cc406db0a40e905793cbf88585000aea9e..198747b69a0947b932c95cb9b5ac4588c5c18a7d 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -137,6 +137,9 @@
 #include "llstatview.h"
 #include "llstatusbar.h"		// sendMoneyBalanceRequest(), owns L$ balance
 #include "llsurface.h"
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-07-17 (Catznip-3.3)
+#include "lltextparser.h"
+// [/SL:KB]
 #include "lltexturecache.h"
 #include "lltexturefetch.h"
 #include "lltoolmgr.h"
@@ -283,6 +286,9 @@ void apply_udp_blacklist(const std::string& csv);
 bool process_login_success_response();
 void on_benefits_failed_callback(const LLSD& notification, const LLSD& response);
 void transition_back_to_login_panel(const std::string& emsg);
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-09-22 (Catznip-3.3)
+void handleLoadChatAlertSounds();
+// [/SL:KB]
 
 void callback_cache_name(const LLUUID& id, const std::string& full_name, bool is_group)
 {
@@ -995,6 +1001,13 @@ bool idle_startup()
 		
 		LLRenderMuteList::getInstance()->loadFromFile();
 
+// [SL:KB] - Patch: Control-TextParser | Checked: 2012-09-22 (Catznip-3.3)
+		if ( (LLTextParser::instance().loadKeywords()) && (LLTextParser::instance().getHighlightCount() > 0) )
+		{
+			LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&handleLoadChatAlertSounds));
+		}
+// [/SL:KB]
+
 		//-------------------------------------------------
 		// Handle startup progress screen
 		//-------------------------------------------------
@@ -3671,3 +3684,28 @@ void transition_back_to_login_panel(const std::string& emsg)
 	gSavedSettings.setBOOL("AutoLogin", FALSE);
 }
 
+
+// [SL:KB] - Patch: Chat--Alerts | Checked: 2012-09-22 (Catznip-3.3)
+void handleLoadChatAlertSounds()
+{
+	const LLTextParser::highlight_list_t& highlights = LLTextParser::instance().getHighlights();
+
+	uuid_vec_t assetIDs;
+	for (LLTextParser::highlight_list_t::const_iterator itHighlight = highlights.begin(); itHighlight != highlights.end(); ++itHighlight)
+	{
+		const LLHighlightEntry& entry = *itHighlight;
+		if ( (entry.mSoundAsset.notNull()) && (assetIDs.end() == std::find(assetIDs.begin(), assetIDs.end(), entry.mSoundAsset)) )
+		{
+			assetIDs.push_back(entry.mSoundAsset);
+		}
+	}
+
+	for (uuid_vec_t::const_iterator itAssetID = assetIDs.begin(); itAssetID != assetIDs.end(); ++itAssetID)
+	{
+		if ( (gAssetStorage) && (!gAssetStorage->hasLocalAsset(*itAssetID, LLAssetType::AT_SOUND)) && (gAudiop) )
+		{
+			gAssetStorage->getAssetData(*itAssetID, LLAssetType::AT_SOUND, LLAudioEngine::assetCallback, NULL);
+		}
+	}
+}
+// [/SL:KB]
diff --git a/indra/newview/lltoastimpanel.cpp b/indra/newview/lltoastimpanel.cpp
index c4c4b13a2fbfebcf71bfdcad2276e6c62c23269f..87051658696992fa6606ec726bf9ceea5c4e163b 100644
--- a/indra/newview/lltoastimpanel.cpp
+++ b/indra/newview/lltoastimpanel.cpp
@@ -36,7 +36,10 @@
 #include "llnotifications.h"
 #include "llinstantmessage.h"
 #include "lltooltip.h"
-
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-29 (Catznip-3.3)
+#include "lltextparser.h"
+#include "llviewercontrol.h"
+// [/SL:KB]
 #include "llviewerchat.h"
 
 const S32 LLToastIMPanel::DEFAULT_MESSAGE_MAX_LINE_COUNT	= 6;
@@ -68,30 +71,83 @@ LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) :	LLToastPanel(p.notif
 	std::string title = mIsGroupMsg ? im_session->mName : p.from;
 	mAvatarName->setValue(title);
 
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-08-29 (Catznip-3.3)
+	mMessage->clear();
+
 	//Handle IRC styled /me messages.
 	std::string prefix = p.message.substr(0, 4);
+	std::string message;
 	if (prefix == "/me " || prefix == "/me'")
 	{
-		//style_params.font.style = "UNDERLINE";
-		mMessage->clear();
-		
-		style_params.font.style ="ITALIC";
+		style_params.font.style = "ITALIC";
 		mMessage->appendText(p.from, FALSE, style_params);
 
 		style_params.font.style = "ITALIC";
-		mMessage->appendText(p.message.substr(3), FALSE, style_params);
+		message = p.message.substr(3);
 	}
 	else
 	{
+		message = p.message;
+		style_params.font.style = "NORMAL";
+
 		if (mIsGroupMsg)
 		{
 			LLAvatarName avatar_name;
 			LLAvatarNameCache::get(p.avatar_id, &avatar_name);
-			p.message = "[From " + avatar_name.getDisplayName() + "]\n" + p.message;
+			mMessage->appendText("[From " + avatar_name.getDisplayName() + "]\n", FALSE, style_params);
+		}
+	}
+
+	static LLCachedControl<bool> sEnableChatAlerts(gSavedSettings, "ChatAlerts", false);
+	if ( (sEnableChatAlerts) && (im_session) )
+	{
+		S32 nHighlightMask = mMessage->getHighlightsMask();
+
+		switch (im_session->mSessionType)
+		{
+			case LLIMModel::LLIMSession::P2P_SESSION:
+				mMessage->setHighlightsMask(nHighlightMask | LLHighlightEntry::CAT_IM);
+				break;
+			case LLIMModel::LLIMSession::GROUP_SESSION:
+			case LLIMModel::LLIMSession::ADHOC_SESSION:
+				mMessage->setHighlightsMask(nHighlightMask | LLHighlightEntry::CAT_GROUP);
+				break;
+			default:
+				break;
 		}
-		style_params.font.style =  "NORMAL";
-		mMessage->setText(p.message, style_params);
+
+		mMessage->appendText(message, FALSE, style_params);
+		mMessage->setHighlightsMask(nHighlightMask & ~(LLHighlightEntry::CAT_IM | LLHighlightEntry::CAT_GROUP));
+	}
+	else
+	{
+		mMessage->appendText(message, FALSE, style_params);
 	}
+// [/SL:KB]
+//	//Handle IRC styled /me messages.
+//	std::string prefix = p.message.substr(0, 4);
+//	if (prefix == "/me " || prefix == "/me'")
+//	{
+//		//style_params.font.style = "UNDERLINE";
+//		mMessage->clear();
+//		
+//		style_params.font.style ="ITALIC";
+//		mMessage->appendText(p.from, FALSE, style_params);
+//
+//		style_params.font.style = "ITALIC";
+//		mMessage->appendText(p.message.substr(3), FALSE, style_params);
+//	}
+//	else
+//	{
+//		if (mIsGroupMsg)
+//		{
+//			LLAvatarName avatar_name;
+//			LLAvatarNameCache::get(p.avatar_id, &avatar_name);
+//			p.message = "[From " + avatar_name.getDisplayName() + "]\n" + p.message;
+//		}
+//		style_params.font.style =  "NORMAL";
+//		mMessage->setText(p.message, style_params);
+//	}
 
 	mTime->setValue(p.time);
 	mSessionID = p.session_id;
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 289b1d71cd975efcdf0a4ecd1121e9666219943e..0777ee2b9d4baa79de7f50f3b21b4e884acf303c 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -5,6 +5,7 @@
  * $LicenseInfo:firstyear=2007&license=viewerlgpl$
  * Second Life Viewer Source Code
  * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2010-2016, Kitty Barnett
  * 
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -60,6 +61,9 @@
 #include "llfloaterbvhpreview.h"
 #include "llfloatercamera.h"
 #include "llfloatercamerapresets.h"
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-07-17 (Catznip-3.3)
+#include "llfloaterchatalerts.h"
+// [/SL:KB]
 #include "llfloaterchatvoicevolume.h"
 #include "llfloaterconversationlog.h"
 #include "llfloaterconversationpreview.h"
@@ -231,6 +235,9 @@ void LLViewerFloaterReg::registerFloaters()
 
 	LLFloaterReg::add("camera", "floater_camera.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCamera>);
 	LLFloaterReg::add("camera_presets", "floater_camera_presets.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCameraPresets>);
+// [SL:KB] - Patch: Chat-Alerts | Checked: 2012-07-17 (Catznip-3.3)
+	LLFloaterReg::add("chat_alerts", "floater_chat_alerts.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChatAlerts>);
+// [/SL:KB]
 	LLFloaterReg::add("chat_voice", "floater_voice_chat_volume.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChatVoiceVolume>);
 	LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterIMNearbyChat::buildFloater);
 	LLFloaterReg::add("compile_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCompileQueue>);
diff --git a/indra/newview/skins/default/xui/en/floater_chat_alerts.xml b/indra/newview/skins/default/xui/en/floater_chat_alerts.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7a66fec2beac75b274bb903fc2fd0e9dbedff537
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_chat_alerts.xml
@@ -0,0 +1,239 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ border="false"
+ can_close="true"
+ can_minimize="true"
+ can_resize="false"
+ height="370"
+ name="floater_chat_alerts"
+ positioning="centered"
+ title="Chat Keyword Alerts"
+ width="490">
+  <sound_drop_target
+   bottom="-5"
+   layout="topleft"
+   left="5"
+   name="sound_drop_target"
+   right="-5"
+   top="5" />
+
+  <check_box
+   control_name="ChatAlerts"
+   follows="left|top"
+   height="16"
+   label="Enable alerts"
+   layout="topleft"
+   left="20"
+   name="alerts_check"
+   top="10"
+   width="150" />
+
+  <scroll_list
+   draw_heading="true"
+   follows="all"
+   height="150"
+   layout="topleft"
+   left="20"
+   name="alerts_list"
+   top_pad="5"
+   width="450">
+      <scroll_list.columns
+       label="Keyword"
+       name="alert_keyword" />
+  </scroll_list>
+  <button
+   follows="left|bottom"
+   height="22"
+   label="New"
+   layout="topleft"
+   left="20"
+   name="alerts_new_btn"
+   top_pad="5"
+   width="90" />
+  <button
+   follows="left|bottom"
+   height="22"
+   label="Delete"
+   layout="topleft"
+   left_pad="5"
+   name="alerts_delete_btn"
+   top_delta="0"
+   width="90" />
+
+  <view_border
+   bevel_style="none"
+   border_thickness="1"
+   follows="left|bottom"
+   height="0"
+   layout="topleft"
+   left="20"
+   name="divisor"
+   top_pad="10"
+   width="450" />
+
+  <text
+   follows="left|bottom"
+   height="10"
+   layout="topleft"
+   left="20"
+   name="alerts_keyword_label"
+   text_readonly_color="LabelDisabledColor"
+   top_pad="15"
+   type="string"
+   width="100">
+    Keyword :
+  </text>
+  <line_editor
+   follows="left|bottom"
+   height="23"
+   layout="topleft"
+   left_pad="5"
+   name="alerts_keyword"
+   top_delta="-5"
+   width="225" />
+  <check_box
+   follows="left|bottom"
+   height="16"
+   layout="topleft"
+   label="Case-sensitive"
+   left_pad="10"
+   name="alerts_keyword_case"
+   top_delta="3"
+   width="100" />
+
+  <text
+   follows="top|bottom"
+   height="10"
+   layout="topleft"
+   left="20"
+   name="alerts_color_label"
+   text_readonly_color="LabelDisabledColor"
+   top_pad="12"
+   type="string"
+   width="100"
+  >
+    Highlight Color :
+  </text>
+  <color_swatch
+   can_apply_immediately="true"
+   color="Red"
+   follows="left|bottom"
+   height="24"
+   label_height="0"
+   layout="topleft"
+   left_pad="6"
+   name="alerts_highlight_color"
+   top_delta="-5"
+   width="44" >
+  </color_swatch>
+
+  <text
+   follows="left|bottom"
+   height="10"
+   layout="topleft"
+   left="20"
+   name="alerts_sound_label"
+   text_readonly_color="LabelDisabledColor"
+   top_pad="10"
+   type="string"
+   width="100"
+  >
+    Play sound :
+  </text>
+  <line_editor
+   enabled="false"
+   follows="bottom|left|right"
+   halign="center"
+   height="23"
+   label="Drag and drop inventory item here"
+   layout="topleft"
+   left_pad="5"
+   max_length_bytes="90"
+   name="alerts_sound_name"
+   tab_stop="false"
+   text_color="EmphasisColor"
+   text_pad_left="25"
+   text_pad_right="23"
+   text_tentative_color="EmphasisColor"
+   top_delta="-5"
+   width="300" />
+  <icon
+   enabled="false"
+   follows="bottom|left"
+   height="15"
+   layout="topleft"
+   left_delta="5"
+   mouse_opaque="true"
+   name="alerts_sound_icon"
+   visible="false"
+   top_delta="3"
+   width="15" />
+  <button
+   follows="bottom|right"
+   height="13"
+   image_unselected="Icon_Close_Toast"
+   image_selected="Icon_Close_Toast"
+   layout="topleft"
+   left_delta="275"
+   name="alerts_sound_clear_btn"
+   top_delta="2"
+   visible="false"
+   width="13" />
+
+  <text
+   follows="top|bottom"
+   height="10"
+   layout="topleft"
+   left="20"
+   name="alerts_trigger_label"
+   text_readonly_color="LabelDisabledColor"
+   top_pad="12"
+   type="string"
+   width="100">
+    Trigger on :
+  </text>
+  <check_box
+   follows="left|bottom"
+   height="16"
+   label="Chat"
+   layout="topleft"
+   left_pad="5"
+   name="alerts_trigger_chat"
+   width="50"
+   top_delta="0" />
+  <check_box
+   follows="left|bottom"
+   height="16"
+   label="IMs"
+   layout="topleft"
+   left_pad="15"
+   name="alerts_trigger_im"
+   width="50"
+   top_delta="0" />
+  <check_box
+   follows="left|bottom"
+   height="16"
+   label="Groups"
+   layout="topleft"
+   left_pad="15"
+   name="alerts_trigger_group"
+   width="50"
+   top_delta="0" />
+
+  <button
+   height="22"
+   follows="left|bottom"
+   name="alerts_save_btn"
+   label="Save Entry"
+   left="20"
+   top_pad="10"
+   width="90" />
+  <button
+   height="22"
+   follows="left|bottom"
+   name="alerts_revert_btn"
+   label="Revert Entry"
+   left_pad="10"
+   top_delta="0"
+   width="90" />
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 94bc0321b69d69a94ba979e547a48c33ed6380c4..ef26c30a5b5b5ccc843afc8eb5d1e8e42ee31564 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -317,19 +317,6 @@
             <menu_item_check.on_click
              function="Communicate.NearbyChat"/>
         </menu_item_check>
-        <menu_item_check
-         label="Speak"
-         name="Speak">
-            <menu_item_check.on_check
-             function="Agent.IsMicrophoneOn"
-             parameter="speak" />
-            <menu_item_check.on_enable
-             function="Agent.IsActionAllowed"
-             parameter="speak" />
-            <menu_item_check.on_click
-             function="Agent.ToggleMicrophone"
-             parameter="speak" />
-        </menu_item_check>
         <menu_item_check
          name="Conversation Log..."
          label="Conversation Log...">
@@ -342,7 +329,41 @@
              function="Floater.Toggle"
              parameter="conversation" />
         </menu_item_check>
+        <menu_item_check
+         label="Chat Alerts..."
+         name="Chat Alerts">
+            <menu_item_check.on_check
+             function="Floater.Visible"
+             parameter="chat_alerts" />
+            <menu_item_check.on_click
+             function="Floater.Toggle"
+             parameter="chat_alerts" />
+        </menu_item_check>
+        <menu_item_check
+         label="Gestures..."
+         name="Gestures"
+         shortcut="control|G">
+            <menu_item_check.on_check
+             function="Floater.Visible"
+             parameter="gestures" />
+            <menu_item_check.on_click
+             function="Floater.Toggle"
+             parameter="gestures" />
+        </menu_item_check>
         <menu_item_separator/>
+        <menu_item_check
+         label="Speak"
+         name="Speak">
+            <menu_item_check.on_check
+             function="Agent.IsMicrophoneOn"
+             parameter="speak" />
+            <menu_item_check.on_enable
+             function="Agent.IsActionAllowed"
+             parameter="speak" />
+            <menu_item_check.on_click
+             function="Agent.ToggleMicrophone"
+             parameter="speak" />
+        </menu_item_check>
         <menu
          label="Voice morphing"
          name="VoiceMorphing"
@@ -379,17 +400,6 @@
                  function="Communicate.VoiceMorphing.PremiumPerk" />
             </menu_item_call>
         </menu>
-        <menu_item_check
-         label="Gestures..."
-         name="Gestures"
-         shortcut="control|G">
-            <menu_item_check.on_check
-             function="Floater.Visible"
-             parameter="gestures" />
-            <menu_item_check.on_click
-             function="Floater.Toggle"
-             parameter="gestures" />
-        </menu_item_check>
         <menu_item_separator/>
         <menu_item_check
          label="Friends"
diff --git a/indra/newview/skins/default/xui/en/panel_chat_item.xml b/indra/newview/skins/default/xui/en/panel_chat_item.xml
index 1ef99649e67d27cd2a46c75406f6fe9adb1f702f..cd3ce595d9f9383e0f11bfd1f4f9c2a3907f515b 100644
--- a/indra/newview/skins/default/xui/en/panel_chat_item.xml
+++ b/indra/newview/skins/default/xui/en/panel_chat_item.xml
@@ -23,6 +23,7 @@
       text_color="white"
       word_wrap="true"
       mouse_opaque="true"
+      parse_highlights="true"
       valign="top"
       name="msg_text">
 	</text_chat>
diff --git a/indra/newview/skins/default/xui/en/panel_instant_message.xml b/indra/newview/skins/default/xui/en/panel_instant_message.xml
index 2e5d65090227d73ef8be4f64582cfcf9f39f1d26..3aeeb1509776daaefc276dad0c6f36d5e36f5ff0 100644
--- a/indra/newview/skins/default/xui/en/panel_instant_message.xml
+++ b/indra/newview/skins/default/xui/en/panel_instant_message.xml
@@ -90,6 +90,7 @@
      layout="topleft"
      left="10"
      name="message"
+     parse_highlights="true"
      text_color="White"
      top="33"
      use_ellipses="true"