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"