diff options
34 files changed, 2251 insertions, 81 deletions
| diff --git a/indra/llcommon/llinitparam.cpp b/indra/llcommon/llinitparam.cpp index bb160b3c0b..451b638a3f 100644 --- a/indra/llcommon/llinitparam.cpp +++ b/indra/llcommon/llinitparam.cpp @@ -181,7 +181,8 @@ namespace LLInitParam  	bool BaseBlock::submitValue(Parser::name_stack_t& name_stack, Parser& p, bool silent)  	{ -		if (!deserializeBlock(p, std::make_pair(name_stack.begin(), name_stack.end()), true)) +		Parser::name_stack_range_t range = std::make_pair(name_stack.begin(), name_stack.end()); +		if (!deserializeBlock(p, range, true))  		{  			if (!silent)  			{ @@ -321,7 +322,7 @@ namespace LLInitParam  		return true;  	} -	bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool ignored) +	bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t& name_stack_range, bool ignored)  	{  		BlockDescriptor& block_data = mostDerivedBlockDescriptor();  		bool names_left = name_stack_range.first != name_stack_range.second; diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h index 545f53ffe6..2f767c234e 100644 --- a/indra/llcommon/llinitparam.h +++ b/indra/llcommon/llinitparam.h @@ -572,7 +572,7 @@ namespace LLInitParam  		};  		typedef bool(*merge_func_t)(Param&, const Param&, bool); -		typedef bool(*deserialize_func_t)(Param&, Parser&, const Parser::name_stack_range_t&, bool); +		typedef bool(*deserialize_func_t)(Param&, Parser&, Parser::name_stack_range_t&, bool);  		typedef void(*serialize_func_t)(const Param&, Parser&, Parser::name_stack_t&, const Param* diff_param);  		typedef void(*inspect_func_t)(const Param&, Parser&, Parser::name_stack_t&, S32 min_count, S32 max_count);  		typedef bool(*validation_func_t)(const Param*); @@ -633,38 +633,38 @@ namespace LLInitParam  		class BaseBlock*				mCurrentBlockPtr;		// pointer to block currently being constructed  	}; -		//TODO: implement in terms of owned_ptr -		template<typename T> +	//TODO: implement in terms of owned_ptr +	template<typename T>  	class LazyValue -		{ +	{  		public:  		LazyValue() -				: mPtr(NULL) -			{} +		: mPtr(NULL) +		{}  		~LazyValue() -			{ -				delete mPtr; -			} +		{ +			delete mPtr; +		}  		LazyValue(const T& value) -			{ +		{  			mPtr = new T(value);  		}  		LazyValue(const LazyValue& other)  		:	mPtr(NULL) -				{ +		{  			*this = other; -				} +		}  		LazyValue& operator = (const LazyValue& other)  		{  			if (!other.mPtr) -				{ +			{  				delete mPtr; -					mPtr = NULL; -				} +				mPtr = NULL; +			}  			else  			{  				if (!mPtr) @@ -675,9 +675,9 @@ namespace LLInitParam  				{  					*mPtr = *(other.mPtr);  				} -				} -				return *this;  			} +			return *this; +		}  		bool operator==(const LazyValue& other) const  		{ @@ -685,13 +685,13 @@ namespace LLInitParam  			return *mPtr == *other.mPtr;  		} -			bool empty() const -			{ -				return mPtr == NULL; -			} +		bool empty() const +		{ +			return mPtr == NULL; +		} -			void set(const T& other) -			{ +		void set(const T& other) +		{  			if (!mPtr)  			{  				mPtr = new T(other); @@ -702,36 +702,36 @@ namespace LLInitParam  			}  		} -			const T& get() const -			{ +		const T& get() const +		{  			return *ensureInstance(); -			} +		} -			T& get() -			{ +		T& get() +		{  			return *ensureInstance();  		}  		operator const T&() const  		{   			return get();  -			} +		} -		private: -			// lazily allocate an instance of T -			T* ensureInstance() const +	private: +		// lazily allocate an instance of T +		T* ensureInstance() const +		{ +			if (mPtr == NULL)  			{ -				if (mPtr == NULL) -				{ -					mPtr = new T(); -				} -				return mPtr; -			} +				mPtr = new T();			 +                        } +			return mPtr; +		} -		private: +	private: -			mutable T* mPtr; -		}; +		mutable T* mPtr; +	};  	// root class of all parameter blocks @@ -838,7 +838,7 @@ namespace LLInitParam  		// Blocks can override this to do custom tracking of changes  		virtual void paramChanged(const Param& changed_param, bool user_provided) {} -		bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); +		bool deserializeBlock(Parser& p, Parser::name_stack_range_t& name_stack_range, bool new_name);  		void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const;  		bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const; @@ -913,7 +913,7 @@ namespace LLInitParam  		}  		U32 getEnclosingBlockOffset() const -	{ +		{  			return ((U32)mEnclosingBlockOffsetHigh << 16) | (U32)mEnclosingBlockOffsetLow;  		} @@ -970,7 +970,7 @@ namespace LLInitParam  		bool isProvided() const { return Param::anyProvided(); } -		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) +		static bool deserializeParam(Param& param, Parser& parser, Parser::name_stack_range_t& name_stack_range, bool new_name)  		{   			self_t& typed_param = static_cast<self_t&>(param);  			// no further names in stack, attempt to parse value now @@ -1128,7 +1128,7 @@ namespace LLInitParam  			}  		} -		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) +		static bool deserializeParam(Param& param, Parser& parser, Parser::name_stack_range_t& name_stack_range, bool new_name)  		{   			self_t& typed_param = static_cast<self_t&>(param);  			// attempt to parse block... @@ -1287,7 +1287,7 @@ namespace LLInitParam  		}  	}; -	// container of non-block parameters +	// list of non-block parameters  	template <typename VALUE_TYPE, typename NAME_VALUE_LOOKUP>  	class TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, NOT_BLOCK>   	:	public Param @@ -1316,7 +1316,7 @@ namespace LLInitParam  		bool isProvided() const { return Param::anyProvided(); } -		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) +		static bool deserializeParam(Param& param, Parser& parser, Parser::name_stack_range_t& name_stack_range, bool new_name)  		{   			Parser::name_stack_range_t new_name_stack_range(name_stack_range);  			self_t& typed_param = static_cast<self_t&>(param); @@ -1499,7 +1499,7 @@ namespace LLInitParam  		}  	}; -	// container of block parameters +	// list of block parameters  	template <typename VALUE_TYPE, typename NAME_VALUE_LOOKUP>  	class TypedParam<VALUE_TYPE, NAME_VALUE_LOOKUP, true, IS_A_BLOCK>   	:	public Param @@ -1528,30 +1528,35 @@ namespace LLInitParam  		bool isProvided() const { return Param::anyProvided(); } -		static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name)  +		static bool deserializeParam(Param& param, Parser& parser, Parser::name_stack_range_t& name_stack_range, bool new_name)   		{   			Parser::name_stack_range_t new_name_stack_range(name_stack_range);  			self_t& typed_param = static_cast<self_t&>(param);  			bool new_value = false; +			bool new_array_value = false;  			// pop first element if empty string  			if (new_name_stack_range.first != new_name_stack_range.second && new_name_stack_range.first->first.empty())  			{ -				new_value |= new_name_stack_range.first->second; +				new_array_value = new_name_stack_range.first->second;  				++new_name_stack_range.first;  			} -			if (new_name || typed_param.mValues.empty()) + +			if (new_name || new_array_value || typed_param.mValues.empty())  			{  				new_value = true;  				typed_param.mValues.push_back(value_t());  			} -  			param_value_t& value = typed_param.mValues.back();  			// attempt to parse block...  			if(value.deserializeBlock(parser, new_name_stack_range, new_name))  			{  				typed_param.setProvided(); +				if (new_array_value) +				{ +					name_stack_range.first->second = false; +				}  				return true;  			}  			else if(named_value_t::valueNamesExist()) @@ -1565,6 +1570,10 @@ namespace LLInitParam  					{  						typed_param.mValues.back().setValueName(name);  						typed_param.setProvided(); +						if (new_array_value) +						{ +							name_stack_range.first->second = false; +						}  						return true;  					} @@ -2039,7 +2048,7 @@ namespace LLInitParam  				}  			} -			static bool deserializeParam(Param& param, Parser& parser, const Parser::name_stack_range_t& name_stack_range, bool new_name) +			static bool deserializeParam(Param& param, Parser& parser, Parser::name_stack_range_t& name_stack_range, bool new_name)  			{  				if (name_stack_range.first == name_stack_range.second)  				{ @@ -2062,7 +2071,7 @@ namespace LLInitParam  			// dummy writer interfaces  			template<typename T>  			Deprecated& operator =(const T& val) -		{ +			{  				// do nothing  				return *this;  			} @@ -2178,14 +2187,14 @@ namespace LLInitParam  			return mValue.getValue();  		} -		bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name) +		bool deserializeBlock(Parser& p, Parser::name_stack_range_t& name_stack_range, bool new_name)  		{  			if (new_name)  			{  				resetToDefault();  			}  			return mValue.deserializeBlock(p, name_stack_range, new_name); -		} +			}  		void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const self_t* diff_block = NULL) const  		{ @@ -2271,12 +2280,12 @@ namespace LLInitParam  			return mValue.getValue();  		} -		bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name) +		bool deserializeBlock(Parser& p, Parser::name_stack_range_t& name_stack_range, bool new_name)  		{  			if (new_name)  			{  				mCurParam = getBlockDescriptor().mAllParams.begin(); -		} +			}  			if (name_stack_range.first == name_stack_range.second   				&& mCurParam != getBlockDescriptor().mAllParams.end())  			{ @@ -2288,7 +2297,7 @@ namespace LLInitParam  				if (deserialize_func   					&& paramp   					&& deserialize_func(*paramp, p, name_stack_range, new_name)) -		{ +				{  					++mCurParam;  					return true;  				} @@ -2300,7 +2309,7 @@ namespace LLInitParam  			else  			{  				return mValue.deserializeBlock(p, name_stack_range, new_name); -		} +			}  		}  		void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const self_t* diff_block = NULL) const @@ -2353,7 +2362,7 @@ namespace LLInitParam  		:	T(),  			mValidated(false)  		{} -	 +  		ParamValue(const default_value_t& value)  		:	T(value.getValue()),  			mValidated(false) @@ -2401,7 +2410,7 @@ namespace LLInitParam  			return mValue.get().getValue();  		} -		bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name) +		bool deserializeBlock(Parser& p, Parser::name_stack_range_t& name_stack_range, bool new_name)  		{  			return mValue.get().deserializeBlock(p, name_stack_range, new_name);  		} @@ -2425,7 +2434,7 @@ namespace LLInitParam  		{  			return source.mValue.empty() || mValue.get().mergeBlock(block_data, source.getValue(), overwrite);  		} -			 +  		bool validateBlock(bool emit_errors = true) const  		{  			return mValue.empty() || mValue.get().validateBlock(emit_errors); @@ -2510,7 +2519,7 @@ namespace LLInitParam  		LLSD& getValue() { return mValue; }  		// block param interface -		LL_COMMON_API bool deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack_range, bool new_name); +		LL_COMMON_API bool deserializeBlock(Parser& p, Parser::name_stack_range_t& name_stack_range, bool new_name);  		LL_COMMON_API void serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block = NULL) const;  		bool inspectBlock(Parser& p, Parser::name_stack_t name_stack = Parser::name_stack_t(), S32 min_count = 0, S32 max_count = S32_MAX) const  		{ @@ -2540,10 +2549,10 @@ namespace LLInitParam  		} EValueAge;  		typedef ParamValue<T>			derived_t; -		typedef CustomParamValue<T>				self_t; -		typedef Block<derived_t>				block_t; +		typedef CustomParamValue<T>		self_t; +		typedef Block<derived_t>		block_t;  		typedef T						default_value_t; -		typedef T								value_t; +		typedef T						value_t;  		typedef void					baseblock_base_class_t; @@ -2553,7 +2562,7 @@ namespace LLInitParam  			mValidated(false)  		{} -		bool deserializeBlock(Parser& parser, Parser::name_stack_range_t name_stack_range, bool new_name) +		bool deserializeBlock(Parser& parser, Parser::name_stack_range_t& name_stack_range, bool new_name)  		{  			derived_t& typed_param = static_cast<derived_t&>(*this);  			// try to parse direct value T diff --git a/indra/llcommon/llsdparam.cpp b/indra/llcommon/llsdparam.cpp index 54c8389772..9f4460a988 100644 --- a/indra/llcommon/llsdparam.cpp +++ b/indra/llcommon/llsdparam.cpp @@ -303,7 +303,7 @@ namespace LLInitParam  {  	// LLSD specialization  	// block param interface -	bool ParamValue<LLSD, NOT_BLOCK>::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name) +	bool ParamValue<LLSD, NOT_BLOCK>::deserializeBlock(Parser& p, Parser::name_stack_range_t& name_stack, bool new_name)  	{  		if (name_stack.first == name_stack.second  			&& p.readValue<LLSD>(mValue)) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index eb88a6b251..b71f13a450 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -131,6 +131,9 @@ set(viewer_SOURCE_FILES      llcommandlineparser.cpp      llcompilequeue.cpp      llconfirmationmanager.cpp +    llconversationlog.cpp +    llconversationloglist.cpp +    llconversationloglistitem.cpp      llcurrencyuimanager.cpp      llcylinder.cpp      lldateutil.cpp @@ -187,6 +190,8 @@ set(viewer_SOURCE_FILES      llfloaterbuyland.cpp      llfloatercamera.cpp      llfloatercolorpicker.cpp +    llfloaterconversationlog.cpp +    llfloaterconversationpreview.cpp      llfloaterdeleteenvpreset.cpp      llfloaterdestinations.cpp      llfloaterdisplayname.cpp @@ -692,6 +697,9 @@ set(viewer_HEADER_FILES      llcommandlineparser.h      llcompilequeue.h      llconfirmationmanager.h +    llconversationlog.h +    llconversationloglist.h +    llconversationloglistitem.h      llcurrencyuimanager.h      llcylinder.h      lldateutil.h @@ -748,6 +756,8 @@ set(viewer_HEADER_FILES      llfloaterbuyland.h      llfloatercamera.h      llfloatercolorpicker.h +    llfloaterconversationlog.h +    llfloaterconversationpreview.h      llfloaterdeleteenvpreset.h      llfloaterdestinations.h      llfloaterdisplayname.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 325cb1f583..cbf5edc423 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -10052,6 +10052,28 @@        <key>Value</key>        <integer>2</integer>      </map> +    <key>CallLogSortOrder</key> +    <map> +      <key>Comment</key> +      <string>Specifies sort order for Call Log (0 = by name, 1 = by date)</string> +      <key>Persist</key> +      <integer>1</integer> +      <key>Type</key> +      <string>U32</string> +      <key>Value</key> +      <integer>2</integer> +    </map> +    <key>SortFriendsFirst</key> +    <map> +      <key>Comment</key> +      <string>Specifies whether friends will be sorted first in Call Log</string> +      <key>Persist</key> +      <integer>1</integer> +      <key>Type</key> +      <string>Boolean</string> +      <key>Value</key> +      <integer>1</integer> +    </map>      <key>ShowPGSearchAll</key>          <map>        <key>Comment</key> diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index efa24796e5..e58bac8e96 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -59,6 +59,7 @@  #include "llares.h"   #include "llcurl.h"  #include "llcalc.h" +#include "llconversationlog.h"  #include "lltexturestats.h"  #include "lltexturestats.h"  #include "llviewerwindow.h" @@ -1824,6 +1825,9 @@ bool LLAppViewer::cleanup()  	// save mute list. gMuteList used to also be deleted here too.  	LLMuteList::getInstance()->cache(gAgent.getID()); +	//save call log list +	LLConversationLog::instance().cache(); +  	if (mPurgeOnExit)  	{  		llinfos << "Purging all cache files on exit" << llendflush; diff --git a/indra/newview/llconversationlog.cpp b/indra/newview/llconversationlog.cpp new file mode 100644 index 0000000000..df9350407d --- /dev/null +++ b/indra/newview/llconversationlog.cpp @@ -0,0 +1,336 @@ +/** + * @file llconversationlog.h + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagent.h" +#include "llconversationlog.h" +#include "lltrans.h" + +struct Conversation_params +{ +	Conversation_params(time_t time) +	:	mTime(time), +		mTimestamp(LLConversation::createTimestamp(time)) +	{} + +	time_t		mTime; +	std::string	mTimestamp; +	SessionType	mConversationType; +	std::string	mConversationName; +	std::string	mHistoryFileName; +	LLUUID		mSessionID; +	LLUUID		mParticipantID; +	bool		mIsVoice; +	bool		mHasOfflineIMs; +}; + +/************************************************************************/ +/*             LLConversation implementation                            */ +/************************************************************************/ + +LLConversation::LLConversation(const Conversation_params& params) +:	mTime(params.mTime), +	mTimestamp(params.mTimestamp), +	mConversationType(params.mConversationType), +	mConversationName(params.mConversationName), +	mHistoryFileName(params.mHistoryFileName), +	mSessionID(params.mSessionID), +	mParticipantID(params.mParticipantID), +	mIsVoice(params.mIsVoice), +	mHasOfflineIMs(params.mHasOfflineIMs) +{ +	setListenIMFloaterOpened(); +} + +LLConversation::LLConversation(const LLIMModel::LLIMSession& session) +:	mTime(time_corrected()), +	mTimestamp(createTimestamp(mTime)), +	mConversationType(session.mSessionType), +	mConversationName(session.mName), +	mHistoryFileName(session.mHistoryFileName), +	mSessionID(session.mSessionID), +	mParticipantID(session.mOtherParticipantID), +	mIsVoice(session.mStartedAsIMCall), +	mHasOfflineIMs(session.mHasOfflineMessage) +{ +	setListenIMFloaterOpened(); +} + +LLConversation::LLConversation(const LLConversation& conversation) +{ +	mTime				= conversation.getTime(); +	mTimestamp			= conversation.getTimestamp(); +	mConversationType	= conversation.getConversationType(); +	mConversationName	= conversation.getConversationName(); +	mHistoryFileName	= conversation.getHistoryFileName(); +	mSessionID			= conversation.getSessionID(); +	mParticipantID		= conversation.getParticipantID(); +	mIsVoice			= conversation.isVoice(); +	mHasOfflineIMs		= conversation.hasOfflineMessages(); + +	setListenIMFloaterOpened(); +} + +LLConversation::~LLConversation() +{ +	mIMFloaterShowedConnection.disconnect(); +} + +void LLConversation::onIMFloaterShown(const LLUUID& session_id) +{ +	if (mSessionID == session_id) +	{ +		mHasOfflineIMs = false; +	} +} + +// static +const std::string LLConversation::createTimestamp(const time_t& utc_time) +{ +	std::string timeStr; +	LLSD substitution; +	substitution["datetime"] = (S32) utc_time; + +	timeStr = "["+LLTrans::getString ("TimeMonth")+"]/[" +				 +LLTrans::getString ("TimeDay")+"]/[" +				 +LLTrans::getString ("TimeYear")+"] [" +				 +LLTrans::getString ("TimeHour")+"]:[" +				 +LLTrans::getString ("TimeMin")+"]"; + + +	LLStringUtil::format (timeStr, substitution); +	return timeStr; +} + +void LLConversation::setListenIMFloaterOpened() +{ +	LLIMFloater* floater = LLIMFloater::findInstance(mSessionID); + +	bool has_offline_ims = !mIsVoice && mHasOfflineIMs; +	bool ims_are_read = LLIMFloater::isVisible(floater) && floater->hasFocus(); + +	// we don't need to listen for im floater with this conversation is opened +	// if floater is already opened or this conversation doesn't have unread offline messages +	if (has_offline_ims && !ims_are_read) +	{ +		mIMFloaterShowedConnection = LLIMFloater::setIMFloaterShowedCallback(boost::bind(&LLConversation::onIMFloaterShown, this, _1)); +	} +} +/************************************************************************/ +/*             LLConversationLog implementation                         */ +/************************************************************************/ + +LLConversationLog::LLConversationLog() +{ +	loadFromFile(getFileName()); + +	LLIMMgr::instance().addSessionObserver(this); +	LLAvatarTracker::instance().addObserver(this); +} +void LLConversationLog::logConversation(const LLConversation& conversation) +{ +	mConversations.push_back(conversation); +	notifyObservers(); +} + +void LLConversationLog::removeConversation(const LLConversation& conversation) +{ +	conversations_vec_t::iterator conv_it = mConversations.begin(); +	for(; conv_it != mConversations.end(); ++conv_it) +	{ +		if (conv_it->getSessionID() == conversation.getSessionID() && conv_it->getTime() == conversation.getTime()) +		{ +			mConversations.erase(conv_it); +			notifyObservers(); +			return; +		} +	} +} + +const LLConversation* LLConversationLog::getConversation(const LLUUID& session_id) +{ +	conversations_vec_t::const_iterator conv_it = mConversations.begin(); +	for(; conv_it != mConversations.end(); ++conv_it) +	{ +		if (conv_it->getSessionID() == session_id) +		{ +			return &*conv_it; +		} +	} + +	return NULL; +} + +void LLConversationLog::addObserver(LLConversationLogObserver* observer) +{ +	mObservers.insert(observer); +} + +void LLConversationLog::removeObserver(LLConversationLogObserver* observer) +{ +	mObservers.erase(observer); +} + +void LLConversationLog::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) +{ +	LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); +	if (session) +	{ +		LLConversation conversation(*session); +		LLConversationLog::instance().logConversation(conversation); +	} +} + +// LLFriendObserver +void LLConversationLog::changed(U32 mask) +{ +	if (mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE)) +	{ +		notifyObservers(); +	} +} + +void LLConversationLog::cache() +{ +	saveToFile(getFileName()); +} + +std::string LLConversationLog::getFileName() +{ +	std::string agent_id_string; +	gAgent.getID().toString(agent_id_string); + +	return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, agent_id_string) + ".call_log"; +} + +bool LLConversationLog::saveToFile(const std::string& filename) +{ +	if(!filename.size()) +	{ +		llwarns << "Call log list filename is empty!" << llendl; +		return false; +	} + +	LLFILE* fp = LLFile::fopen(filename, "wb"); +	if (!fp) +	{ +		llwarns << "Couldn't open call log list" << filename << llendl; +		return false; +	} + +	std::string participant_id; +	std::string conversation_id; + +	conversations_vec_t::const_iterator conv_it = mConversations.begin(); +	for (; conv_it != mConversations.end(); ++conv_it) +	{ +		conv_it->getSessionID().toString(conversation_id); +		conv_it->getParticipantID().toString(participant_id); + +		// examples of two file entries +		// [1343221177] 0 1 0 John Doe| 7e4ec5be-783f-49f5-71dz-16c58c64c145 4ec62a74-c246-0d25-2af6-846beac2aa55 john.doe| +		// [1343222639] 2 0 0 Ad-hoc Conference| c3g67c89-c479-4c97-b21d-32869bcfe8rc 68f1c33e-4135-3e3e-a897-8c9b23115c09 Ad-hoc Conference hash597394a0-9982-766d-27b8-c75560213b9a| + +		fprintf(fp, "[%d] %d %d %d %s| %s %s %s|\n", +				(S32)conv_it->getTime(), +				(S32)conv_it->getConversationType(), +				(S32)conv_it->isVoice(), +				(S32)conv_it->hasOfflineMessages(), +				     conv_it->getConversationName().c_str(), +				participant_id.c_str(), +				conversation_id.c_str(), +				conv_it->getHistoryFileName().c_str()); +	} +	fclose(fp); +	return true; +} +bool LLConversationLog::loadFromFile(const std::string& filename) +{ +	if(!filename.size()) +	{ +		llwarns << "Call log list filename is empty!" << llendl; +		return false; +	} + +	LLFILE* fp = LLFile::fopen(filename, "rb"); +	if (!fp) +	{ +		llwarns << "Couldn't open call log list" << filename << llendl; +		return false; +	} + +	char buffer[MAX_STRING]; +	char conv_name_buffer[MAX_STRING]; +	char part_id_buffer[MAX_STRING]; +	char conv_id_buffer[MAX_STRING]; +	char history_file_name[MAX_STRING]; +	int is_voice; +	int has_offline_ims; +	int stype; +	S32 time; + +	while (!feof(fp) && fgets(buffer, MAX_STRING, fp)) +	{ +		conv_name_buffer[0] = '\0'; +		part_id_buffer[0]	= '\0'; +		conv_id_buffer[0]	= '\0'; + +		sscanf(buffer, "[%d] %d %d %d %[^|]| %s %s %[^|]|", +				&time, +				&stype, +				&is_voice, +				&has_offline_ims, +				conv_name_buffer, +				part_id_buffer, +				conv_id_buffer, +				history_file_name); + +		Conversation_params params(time); +		params.mConversationType = (SessionType)stype; +		params.mIsVoice = is_voice; +		params.mHasOfflineIMs = has_offline_ims; +		params.mConversationName = std::string(conv_name_buffer); +		params.mParticipantID = LLUUID(part_id_buffer); +		params.mSessionID = LLUUID(conv_id_buffer); +		params.mHistoryFileName = std::string(history_file_name); + +		LLConversation conversation(params); +		mConversations.push_back(conversation); +	} +	fclose(fp); + +	notifyObservers(); +	return true; +} + +void LLConversationLog::notifyObservers() +{ +	std::set<LLConversationLogObserver*>::const_iterator iter = mObservers.begin(); +	for (; iter != mObservers.end(); ++iter) +	{ +		(*iter)->changed(); +	} +} diff --git a/indra/newview/llconversationlog.h b/indra/newview/llconversationlog.h new file mode 100644 index 0000000000..18865bb80e --- /dev/null +++ b/indra/newview/llconversationlog.h @@ -0,0 +1,164 @@ +/** + * @file llconversationlog.h + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LLCONVERSATIONLOG_H_ +#define LLCONVERSATIONLOG_H_ + +#include "llcallingcard.h" +#include "llimfloater.h" +#include "llimview.h" + +class LLConversationLogObserver; +struct Conversation_params; + +typedef LLIMModel::LLIMSession::SType SessionType; + +/* + * This class represents a particular session(conversation) of any type(im/voice/p2p/group/...) by storing some of session's data. + * Each LLConversation object has a corresponding visual representation in a form of LLConversationLogListItem. + */ +class LLConversation +{ +public: + +	LLConversation(const Conversation_params& params); +	LLConversation(const LLIMModel::LLIMSession& session); +	LLConversation(const LLConversation& conversation); + +	~LLConversation(); + +	const SessionType&	getConversationType()	const	{ return mConversationType; } +	const std::string&	getConversationName()	const	{ return mConversationName; } +	const std::string&	getHistoryFileName()	const	{ return mHistoryFileName; } +	const LLUUID&		getSessionID()			const	{ return mSessionID; } +	const LLUUID&		getParticipantID()		const	{ return mParticipantID; } +	const std::string&	getTimestamp()			const	{ return mTimestamp; } +	const time_t&		getTime()				const	{ return mTime; } +	bool				isVoice()				const	{ return mIsVoice; } +	bool				hasOfflineMessages()	const	{ return mHasOfflineIMs; } + +	/* +	 * Resets flag of unread offline message to false when im floater with this conversation is opened. +	 */ +	void onIMFloaterShown(const LLUUID& session_id); + +	/* +	 * returns string representation(in form of: mm/dd/yyyy hh:mm) of time when conversation was started +	 */ +	static const std::string createTimestamp(const time_t& utc_time); + +private: + +	/* +	 * If conversation has unread offline messages sets callback for opening LLIMFloater +	 * with this conversation. +	 */ +	void setListenIMFloaterOpened(); + +	boost::signals2::connection mIMFloaterShowedConnection; + +	time_t			mTime; // start time of conversation +	SessionType		mConversationType; +	std::string		mConversationName; +	std::string		mHistoryFileName; +	LLUUID			mSessionID; +	LLUUID			mParticipantID; +	bool			mIsVoice; +	bool			mHasOfflineIMs; +	std::string		mTimestamp; // conversation start time in form of: mm/dd/yyyy hh:mm +}; + +/** + * LLConversationLog stores all agent's conversations. + * This class is responsible for creating and storing LLConversation objects when im or voice session starts. + * Also this class saves/retrieves conversations to/from file. + * + * Also please note that it may be several conversations with the same sessionID stored in the conversation log. + * To distinguish two conversations with the same sessionID it's also needed to compare their creation date. + */ + +class LLConversationLog : public LLSingleton<LLConversationLog>, LLIMSessionObserver, LLFriendObserver +{ +	friend class LLSingleton<LLConversationLog>; +public: + +	/** +	 * adds conversation to the conversation list and notifies observers +	 */ +	void logConversation(const LLConversation& conversation); +	void removeConversation(const LLConversation& conversation); + +	/** +	 * Returns first conversation with matched session_id +	 */ +	const LLConversation* getConversation(const LLUUID& session_id); + +	void addObserver(LLConversationLogObserver* observer); +	void removeObserver(LLConversationLogObserver* observer); + +	const std::vector<LLConversation>& getConversations() { return mConversations; } + +	// LLIMSessionObserver triggers +	virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id); +	virtual void sessionVoiceOrIMStarted(const LLUUID& session_id){}							// Stub +	virtual void sessionRemoved(const LLUUID& session_id){}										// Stub +	virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id){}	// Stub + +	// LLFriendObserver trigger +	virtual void changed(U32 mask); + +	/** +	 * public method which is called on viewer exit to save conversation log +	 */ +	void cache(); + +private: + +	LLConversationLog(); +	void notifyObservers(); + +	/** +	 * constructs file name in which conversations log will be saved +	 * file name template: agentID.call_log. +	 * For example: a086icaa-782d-88d0-ae29-987a55c99sss.call_log +	 */ +	std::string getFileName(); + +	bool saveToFile(const std::string& filename); +	bool loadFromFile(const std::string& filename); + +	typedef std::vector<LLConversation> conversations_vec_t; +	std::vector<LLConversation>				mConversations; +	std::set<LLConversationLogObserver*>	mObservers; +}; + +class LLConversationLogObserver +{ +public: +	virtual ~LLConversationLogObserver(){} +	virtual void changed() = 0; +}; + +#endif /* LLCONVERSATIONLOG_H_ */ diff --git a/indra/newview/llconversationloglist.cpp b/indra/newview/llconversationloglist.cpp new file mode 100644 index 0000000000..0433719a89 --- /dev/null +++ b/indra/newview/llconversationloglist.cpp @@ -0,0 +1,422 @@ +/** + * @file llconversationloglist.cpp + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llavataractions.h" +#include "llagent.h" +#include "llfloaterreg.h" +#include "llfloaterconversationpreview.h" +#include "llgroupactions.h" +#include "llconversationloglist.h" +#include "llconversationloglistitem.h" +#include "llviewermenu.h" + +static LLDefaultChildRegistry::Register<LLConversationLogList> r("conversation_log_list"); + +static LLConversationLogListNameComparator NAME_COMPARATOR; +static LLConversationLogListDateComparator DATE_COMPARATOR; + +LLConversationLogList::LLConversationLogList(const Params& p) +:	LLFlatListViewEx(p), +	mIsDirty(true) +{ +	LLConversationLog::instance().addObserver(this); + +	// Set up context menu. +	LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; +	LLUICtrl::EnableCallbackRegistry::ScopedRegistrar check_registrar; +	LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + +	registrar.add		("Calllog.Action",	boost::bind(&LLConversationLogList::onCustomAction,	this, _2)); +	check_registrar.add ("Calllog.Check",	boost::bind(&LLConversationLogList::isActionChecked,this, _2)); +	enable_registrar.add("Calllog.Enable",	boost::bind(&LLConversationLogList::isActionEnabled,this, _2)); + +	LLToggleableMenu* context_menu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>( +									"menu_conversation_log_gear.xml", +									gMenuHolder, +									LLViewerMenuHolderGL::child_registry_t::instance()); +	if(context_menu) +	{ +		mContextMenu = context_menu->getHandle(); +	} + +	mIsFriendsOnTop = gSavedSettings.getBOOL("SortFriendsFirst"); +} + +LLConversationLogList::~LLConversationLogList() +{ +	if (mContextMenu.get()) +	{ +		mContextMenu.get()->die(); +	} + +	LLConversationLog::instance().removeObserver(this); +} + +void LLConversationLogList::draw() +{ +	if (mIsDirty) +	{ +		refresh(); +	} +	LLFlatListViewEx::draw(); +} + +BOOL LLConversationLogList::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ +	BOOL handled = LLUICtrl::handleRightMouseDown(x, y, mask); + +	LLToggleableMenu* context_menu = mContextMenu.get(); +	{ +		context_menu->buildDrawLabels(); +	if (context_menu && size()) +		context_menu->updateParent(LLMenuGL::sMenuContainer); +		LLMenuGL::showPopup(this, context_menu, x, y); +	} + +	return handled; +} + +void LLConversationLogList::setNameFilter(const std::string& filter) +{ +	std::string filter_upper = filter; +	LLStringUtil::toUpper(filter_upper); +	if (mNameFilter != filter_upper) +	{ +		mNameFilter = filter_upper; +		setDirty(); +	} +} + +bool LLConversationLogList::findInsensitive(std::string haystack, const std::string& needle_upper) +{ +    LLStringUtil::toUpper(haystack); +    return haystack.find(needle_upper) != std::string::npos; +} + +void LLConversationLogList::sortByName() +{ +	setComparator(&NAME_COMPARATOR); +	sort(); +} + +void LLConversationLogList::sortByDate() +{ +	setComparator(&DATE_COMPARATOR); +	sort(); +} + +void LLConversationLogList::toggleSortFriendsOnTop() +{ +	mIsFriendsOnTop = !mIsFriendsOnTop; +	gSavedSettings.setBOOL("SortFriendsFirst", mIsFriendsOnTop); +	sort(); +} + +void LLConversationLogList::changed() +{ +	refresh(); +} + +void LLConversationLogList::addNewItem(const LLConversation* conversation) +{ +	LLConversationLogListItem* item = new LLConversationLogListItem(&*conversation); +	if (!mNameFilter.empty()) +	{ +		item->highlightNameDate(mNameFilter); +	} +	addItem(item, conversation->getSessionID(), ADD_TOP); +} + +void LLConversationLogList::refresh() +{ +	rebuildList(); +	sort(); + +	mIsDirty = false; +} + +void LLConversationLogList::rebuildList() +{ +	clear(); + +	bool have_filter = !mNameFilter.empty(); + +	const std::vector<LLConversation>& conversations = LLConversationLog::instance().getConversations(); +	std::vector<LLConversation>::const_iterator iter = conversations.begin(); + +	for (; iter != conversations.end(); ++iter) +	{ +		bool not_found = have_filter && !findInsensitive(iter->getConversationName(), mNameFilter) && !findInsensitive(iter->getTimestamp(), mNameFilter); +		if (not_found) +			continue; + +		addNewItem(&*iter); +	} +} + +void LLConversationLogList::onCustomAction(const LLSD& userdata) +{ +	const std::string command_name = userdata.asString(); +	const LLUUID& selected_id = getSelectedConversation()->getParticipantID(); +	LLIMModel::LLIMSession::SType stype = getSelectedSessionType(); + +	if ("im" == command_name) +	{ +		switch (stype) +		{ +		case LLIMModel::LLIMSession::P2P_SESSION: +			LLAvatarActions::startIM(selected_id); +			break; + +		case LLIMModel::LLIMSession::GROUP_SESSION: +			LLGroupActions::startIM(selected_id); +			break; + +		default: +			break; +		} +	} +	else if ("call" == command_name) +	{ +		switch (stype) +		{ +		case LLIMModel::LLIMSession::P2P_SESSION: +			LLAvatarActions::startCall(selected_id); +			break; + +		case LLIMModel::LLIMSession::GROUP_SESSION: +			LLGroupActions::startCall(selected_id); +			break; + +		default: +			break; +		} +	} +	else if ("view_profile" == command_name) +	{ +		switch (stype) +		{ +		case LLIMModel::LLIMSession::P2P_SESSION: +			LLAvatarActions::showProfile(selected_id); +			break; + +		case LLIMModel::LLIMSession::GROUP_SESSION: +			LLGroupActions::show(selected_id); +			break; + +		default: +			break; +		} +	} +	else if ("chat_history" == command_name) +	{ +		const LLUUID& session_id = getSelectedConversation()->getSessionID(); +		LLFloaterReg::showInstance("preview_conversation", session_id, true); +	} +	else if ("offer_teleport" == command_name) +	{ +		LLAvatarActions::offerTeleport(selected_id); +	} +	else if("add_rem_friend" == command_name) +	{ +		if (LLAvatarActions::isFriend(selected_id)) +		{ +			LLAvatarActions::removeFriendDialog(selected_id); +		} +		else +		{ +			LLAvatarActions::requestFriendshipDialog(selected_id); +		} +	} +	else if ("invite_to_group" == command_name) +	{ +		LLAvatarActions::inviteToGroup(selected_id); +	} +	else if ("show_on_map" == command_name) +	{ +		LLAvatarActions::showOnMap(selected_id); +	} +	else if ("share" == command_name) +	{ +		LLAvatarActions::share(selected_id); +	} +	else if ("pay" == command_name) +	{ +		LLAvatarActions::pay(selected_id); +	} +	else if ("block" == command_name) +	{ +		LLAvatarActions::toggleBlock(selected_id); +	} +} + +bool LLConversationLogList::isActionEnabled(const LLSD& userdata) +{ +	if (numSelected() != 1) +	{ +		return false; +	} + +	const std::string command_name = userdata.asString(); + +	LLIMModel::LLIMSession::SType stype = getSelectedSessionType(); +	const LLUUID& selected_id = getSelectedConversation()->getParticipantID(); + +	bool is_p2p   = LLIMModel::LLIMSession::P2P_SESSION == stype; +	bool is_group = LLIMModel::LLIMSession::GROUP_SESSION == stype; + +	if ("can_im" == command_name || "can_view_profile" == command_name) +	{ +		return is_p2p || is_group; +	} +	else if ("can_view_chat_history" == command_name) +	{ +		return true; +	} +	else if ("can_call"	== command_name) +	{ +		return (is_p2p || is_group) && LLAvatarActions::canCall(); +	} +	else if ("add_rem_friend"		== command_name || +			 "can_invite_to_group"	== command_name || +			 "can_share"			== command_name || +			 "can_block"			== command_name || +			 "can_pay"				== command_name) +	{ +		return is_p2p; +	} +	else if("can_offer_teleport" == command_name) +	{ +		return is_p2p && LLAvatarActions::canOfferTeleport(selected_id); +	} +	else if ("can_show_on_map") +	{ +		return is_p2p && ((LLAvatarTracker::instance().isBuddyOnline(selected_id) && is_agent_mappable(selected_id)) || gAgent.isGodlike()); +	} + +	return false; +} + +bool LLConversationLogList::isActionChecked(const LLSD& userdata) +{ +	const std::string command_name = userdata.asString(); + +	const LLUUID& selected_id = getSelectedConversation()->getParticipantID(); +	bool is_p2p = LLIMModel::LLIMSession::P2P_SESSION == getSelectedSessionType(); + +	if ("is_blocked" == command_name) +	{ +		return is_p2p && LLAvatarActions::isBlocked(selected_id); +	} +	else if ("is_friend" == command_name) +	{ +		return is_p2p && LLAvatarActions::isFriend(selected_id); +	} + +	return false; +} + +LLIMModel::LLIMSession::SType LLConversationLogList::getSelectedSessionType() +{ +	const LLConversationLogListItem* item = getSelectedConversationPanel(); + +	if (item) +	{ +		return item->getConversation()->getConversationType(); +	} + +	return LLIMModel::LLIMSession::NONE_SESSION; +} + +const LLConversationLogListItem* LLConversationLogList::getSelectedConversationPanel() +{ +	LLPanel* panel = LLFlatListViewEx::getSelectedItem(); +	LLConversationLogListItem* conv_panel = dynamic_cast<LLConversationLogListItem*>(panel); + +	return conv_panel; +} + +const LLConversation* LLConversationLogList::getSelectedConversation() +{ +	const LLConversationLogListItem* panel = getSelectedConversationPanel(); + +	if (panel) +	{ +		return panel->getConversation(); +	} + +	return NULL; +} + +bool LLConversationLogListItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const +{ +	const LLConversationLogListItem* conversation_item1 = dynamic_cast<const LLConversationLogListItem*>(item1); +	const LLConversationLogListItem* conversation_item2 = dynamic_cast<const LLConversationLogListItem*>(item2); + +	if (!conversation_item1 || !conversation_item2) +	{ +		llerror("conversation_item1 and conversation_item2 cannot be null", 0); +		return true; +	} + +	return doCompare(conversation_item1, conversation_item2); +} + +bool LLConversationLogListNameComparator::doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const +{ +	std::string name1 = conversation1->getConversation()->getConversationName(); +	std::string name2 = conversation2->getConversation()->getConversationName(); +	const LLUUID& id1 = conversation1->getConversation()->getParticipantID(); +	const LLUUID& id2 = conversation2->getConversation()->getParticipantID(); + +	LLStringUtil::toUpper(name1); +	LLStringUtil::toUpper(name2); + +	bool friends_first = gSavedSettings.getBOOL("SortFriendsFirst"); +	if (friends_first && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) +	{ +		return LLAvatarActions::isFriend(id1); +	} + +	return name1 < name2; +} + +bool LLConversationLogListDateComparator::doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const +{ +	time_t date1 = conversation1->getConversation()->getTime(); +	time_t date2 = conversation2->getConversation()->getTime(); +	const LLUUID& id1 = conversation1->getConversation()->getParticipantID(); +	const LLUUID& id2 = conversation2->getConversation()->getParticipantID(); + +	bool friends_first = gSavedSettings.getBOOL("SortFriendsFirst"); +	if (friends_first && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) +	{ +		return LLAvatarActions::isFriend(id1); +	} + +	return date1 > date2; +} diff --git a/indra/newview/llconversationloglist.h b/indra/newview/llconversationloglist.h new file mode 100644 index 0000000000..dff34a74c6 --- /dev/null +++ b/indra/newview/llconversationloglist.h @@ -0,0 +1,143 @@ +/** + * @file llconversationloglist.h + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LLCONVERSATIONLOGLIST_H_ +#define LLCONVERSATIONLOGLIST_H_ + +#include "llconversationlog.h" +#include "llflatlistview.h" +#include "lltoggleablemenu.h" + +class LLConversationLogListItem; + +/** + * List of all agent's conversations. I.e. history of conversations. + * This list represents contents of the LLConversationLog. + * Each change in LLConversationLog leads to rebuilding this list, so + * it's always in actual state. + */ + +class LLConversationLogList: public LLFlatListViewEx, public LLConversationLogObserver +{ +	LOG_CLASS(LLConversationLogList); +public: +	struct Params : public LLInitParam::Block<Params, LLFlatListViewEx::Params> +	{ +		Params(){}; +	}; + +	LLConversationLogList(const Params& p); +	virtual ~LLConversationLogList(); + +	virtual void draw(); + +	virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + +	LLToggleableMenu*	getContextMenu() const { return mContextMenu.get(); } + +	void addNewItem(const LLConversation* conversation); +	void setNameFilter(const std::string& filter); +	void sortByName(); +	void sortByDate(); +	void toggleSortFriendsOnTop(); +	bool getSortFriendsOnTop() const { return mIsFriendsOnTop; } + +	/** +	 * Changes from LLConversationLogObserver +	 */ +	virtual void changed(); + +private: + +	void setDirty(bool dirty = true) { mIsDirty = dirty; } +	void refresh(); + +	/** +	 * Clears list and re-adds items from LLConverstationLog +	 * If filter is not empty re-adds items which match the filter +	 */ +	void rebuildList(); + +	bool findInsensitive(std::string haystack, const std::string& needle_upper); + +	void onCustomAction (const LLSD& userdata); +	bool isActionEnabled(const LLSD& userdata); +	bool isActionChecked(const LLSD& userdata); + +	LLIMModel::LLIMSession::SType getSelectedSessionType(); +	const LLConversationLogListItem* getSelectedConversationPanel(); +	const LLConversation* getSelectedConversation(); + +	LLHandle<LLToggleableMenu>	mContextMenu; +	bool mIsDirty; +	bool mIsFriendsOnTop; +	std::string mNameFilter; +}; + +/** + * Abstract comparator for ConversationLogList items + */ +class LLConversationLogListItemComparator : public LLFlatListView::ItemComparator +{ +	LOG_CLASS(LLConversationLogListItemComparator); + +public: +	LLConversationLogListItemComparator() {}; +	virtual ~LLConversationLogListItemComparator() {}; + +	virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; + +protected: + +	virtual bool doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const = 0; +}; + +class LLConversationLogListNameComparator : public LLConversationLogListItemComparator +{ +	LOG_CLASS(LLConversationLogListNameComparator); + +public: +	LLConversationLogListNameComparator() {}; +	virtual ~LLConversationLogListNameComparator() {}; + +protected: + +	virtual bool doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const; +}; + +class LLConversationLogListDateComparator : public LLConversationLogListItemComparator +{ +	LOG_CLASS(LLConversationLogListDateComparator); + +public: +	LLConversationLogListDateComparator() {}; +	virtual ~LLConversationLogListDateComparator() {}; + +protected: + +	virtual bool doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const; +}; + +#endif /* LLCONVERSATIONLOGLIST_H_ */ diff --git a/indra/newview/llconversationloglistitem.cpp b/indra/newview/llconversationloglistitem.cpp new file mode 100644 index 0000000000..fc2e757864 --- /dev/null +++ b/indra/newview/llconversationloglistitem.cpp @@ -0,0 +1,157 @@ +/** + * @file llconversationloglistitem.cpp + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// llui +#include "lliconctrl.h" +#include "lltextbox.h" +#include "lltextutil.h" + +// newview +#include "llavatariconctrl.h" +#include "llconversationlog.h" +#include "llconversationloglistitem.h" +#include "llgroupiconctrl.h" +#include "llinventoryicon.h" + +LLConversationLogListItem::LLConversationLogListItem(const LLConversation* conversation) +:	LLPanel(), +	mConversation(conversation), +	mConversationName(NULL), +	mConversationDate(NULL) +{ +	buildFromFile("panel_conversation_log_list_item.xml"); + +	LLIMFloater* floater = LLIMFloater::findInstance(mConversation->getSessionID()); + +	bool has_offline_ims = !mConversation->isVoice() && mConversation->hasOfflineMessages(); +	bool ims_are_read = LLIMFloater::isVisible(floater) && floater->hasFocus(); + +	if (has_offline_ims && !ims_are_read) +	{ +		mIMFloaterShowedConnection = LLIMFloater::setIMFloaterShowedCallback(boost::bind(&LLConversationLogListItem::onIMFloaterShown, this, _1)); +	} +} + +LLConversationLogListItem::~LLConversationLogListItem() +{ +	mIMFloaterShowedConnection.disconnect(); +} + +BOOL LLConversationLogListItem::postBuild() +{ +	initIcons(); + +	// set conversation name +	mConversationName = getChild<LLTextBox>("conversation_name"); +	mConversationName->setValue(mConversation->getConversationName()); + +	// set conversation date and time +	mConversationDate = getChild<LLTextBox>("date_time"); +	mConversationDate->setValue(mConversation->getTimestamp()); + +	getChild<LLButton>("delete_btn")->setClickedCallback(boost::bind(&LLConversationLogListItem::onRemoveBtnClicked, this)); + +	return TRUE; +} + +void LLConversationLogListItem::initIcons() +{ +	switch (mConversation->getConversationType()) +	{ +		case LLIMModel::LLIMSession::P2P_SESSION: +		case LLIMModel::LLIMSession::ADHOC_SESSION: +		{ +			LLAvatarIconCtrl* avatar_icon = getChild<LLAvatarIconCtrl>("avatar_icon"); +			avatar_icon->setVisible(TRUE); +			avatar_icon->setValue(mConversation->getParticipantID()); +			break; +		} +		case LLIMModel::LLIMSession::GROUP_SESSION: +		{ +			LLGroupIconCtrl* group_icon = getChild<LLGroupIconCtrl>("group_icon"); +			group_icon->setVisible(TRUE); +			group_icon->setValue(mConversation->getSessionID()); +			break; +		} +		default: +			break; +	} + +	if (mConversation->isVoice()) +	{ +		getChild<LLIconCtrl>("voice_session_icon")->setVisible(TRUE); +	} +	else +	{ +		if (mConversation->hasOfflineMessages()) +		{ +			getChild<LLIconCtrl>("unread_ims_icon")->setVisible(TRUE); +		} +	} +} + +void LLConversationLogListItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ +	getChildView("hovered_icon")->setVisible(true); +	LLPanel::onMouseEnter(x, y, mask); +} + +void LLConversationLogListItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ +	getChildView("hovered_icon")->setVisible(false); +	LLPanel::onMouseLeave(x, y, mask); +} + +void LLConversationLogListItem::setValue(const LLSD& value) +{ +	if (!value.isMap() || !value.has("selected")) +	{ +		return; +	} + +	getChildView("selected_icon")->setVisible(value["selected"]); +} + +void LLConversationLogListItem::onIMFloaterShown(const LLUUID& session_id) +{ +	if (mConversation->getSessionID() == session_id) +	{ +		getChild<LLIconCtrl>("unread_ims_icon")->setVisible(FALSE); +	} +} + +void LLConversationLogListItem::onRemoveBtnClicked() +{ +	LLConversationLog::instance().removeConversation(*mConversation); +} + +void LLConversationLogListItem::highlightNameDate(const std::string& highlited_text) +{ +	LLStyle::Params params; +	LLTextUtil::textboxSetHighlightedVal(mConversationName, params, mConversation->getConversationName(), highlited_text); +	LLTextUtil::textboxSetHighlightedVal(mConversationDate, params, mConversation->getTimestamp(), highlited_text); +} diff --git a/indra/newview/llconversationloglistitem.h b/indra/newview/llconversationloglistitem.h new file mode 100644 index 0000000000..deba7d4563 --- /dev/null +++ b/indra/newview/llconversationloglistitem.h @@ -0,0 +1,77 @@ +/** + * @file llconversationloglistitem.h + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LLCONVERSATIONLOGLISTITEM_H_ +#define LLCONVERSATIONLOGLISTITEM_H_ + +#include "llimfloater.h" +#include "llpanel.h" + +class LLTextBox; +class LLConversation; + +/** + * This class is a visual representation of LLConversation, each of which is LLConversationLog entry. + * LLConversationLogList consists of these LLConversationLogListItems. + * LLConversationLogListItem consists of: + *		conversaion_type_icon + *		conversaion_name + *		conversaion_date + * Also LLConversationLogListItem holds pointer to its LLConversationLog. + */ + +class LLConversationLogListItem : public LLPanel +{ +public: +	LLConversationLogListItem(const LLConversation* conversation); +	virtual ~LLConversationLogListItem(); + +	void onMouseEnter(S32 x, S32 y, MASK mask); +	void onMouseLeave(S32 x, S32 y, MASK mask); + +	virtual void setValue(const LLSD& value); + +	virtual BOOL postBuild(); + +	void onIMFloaterShown(const LLUUID& session_id); +	void onRemoveBtnClicked(); + +	const LLConversation* getConversation() const { return mConversation; } + +	void highlightNameDate(const std::string& highlited_text); + +private: + +	void initIcons(); + +	const LLConversation* mConversation; + +	LLTextBox*		mConversationName; +	LLTextBox*		mConversationDate; + +	boost::signals2::connection mIMFloaterShowedConnection; +}; + +#endif /* LLCONVERSATIONLOGITEM_H_ */ diff --git a/indra/newview/llfloaterconversationlog.cpp b/indra/newview/llfloaterconversationlog.cpp new file mode 100644 index 0000000000..c77a9e74bb --- /dev/null +++ b/indra/newview/llfloaterconversationlog.cpp @@ -0,0 +1,127 @@ +/** + * @file llfloaterconversationlog.cpp + * @brief Functionality of the "conversation log" floater + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ +#include "llviewerprecompiledheaders.h" + +#include "llconversationloglist.h" +#include "llfiltereditor.h" +#include "llfloaterconversationlog.h" +#include "llmenubutton.h" + +LLFloaterConversationLog::LLFloaterConversationLog(const LLSD& key) +:	LLFloater(key), +	mConversationLogList(NULL) +{ +	mCommitCallbackRegistrar.add("CallLog.Action",	boost::bind(&LLFloaterConversationLog::onCustomAction,  this, _2)); +	mEnableCallbackRegistrar.add("CallLog.Check",	boost::bind(&LLFloaterConversationLog::isActionChecked, this, _2)); +} + +BOOL LLFloaterConversationLog::postBuild() +{ +	mConversationLogList = getChild<LLConversationLogList>("conversation_log_list"); + +	switch (gSavedSettings.getU32("CallLogSortOrder")) +	{ +	case E_SORT_BY_NAME: +		mConversationLogList->sortByName(); +		break; + +	case E_SORT_BY_DATE: +		mConversationLogList->sortByDate(); +		break; +	} + +	// Use the context menu of the Conversation list for the Conversation tab gear menu. +	LLToggleableMenu* conversations_gear_menu = mConversationLogList->getContextMenu(); +	if (conversations_gear_menu) +	{ +		getChild<LLMenuButton>("conversations_gear_btn")->setMenu(conversations_gear_menu, LLMenuButton::MP_BOTTOM_LEFT); +	} + +	getChild<LLFilterEditor>("people_filter_input")->setCommitCallback(boost::bind(&LLFloaterConversationLog::onFilterEdit, this, _2)); + +	return LLFloater::postBuild(); +} + +void LLFloaterConversationLog::draw() +{ +	LLFloater::draw(); +} + +void LLFloaterConversationLog::onFilterEdit(const std::string& search_string) +{ +	std::string filter = search_string; +	LLStringUtil::trimHead(filter); + +	mConversationLogList->setNameFilter(filter); +} + + +void LLFloaterConversationLog::onCustomAction (const LLSD& userdata) +{ +	const std::string command_name = userdata.asString(); + +	if ("sort_by_name" == command_name) +	{ +		mConversationLogList->sortByName(); +		gSavedSettings.setU32("CallLogSortOrder", E_SORT_BY_NAME); +	} +	else if ("sort_by_date" == command_name) +	{ +		mConversationLogList->sortByDate(); +		gSavedSettings.setU32("CallLogSortOrder", E_SORT_BY_DATE); +	} +	else if ("sort_friends_on_top" == command_name) +	{ +		mConversationLogList->toggleSortFriendsOnTop(); +	} +} + +bool LLFloaterConversationLog::isActionEnabled(const LLSD& userdata) +{ +	return true; +} + +bool LLFloaterConversationLog::isActionChecked(const LLSD& userdata) +{ +	const std::string command_name = userdata.asString(); + +	U32 sort_order = gSavedSettings.getU32("CallLogSortOrder"); + +	if ("sort_by_name" == command_name) +	{ +		return sort_order == E_SORT_BY_NAME; +	} +	else if ("sort_by_date" == command_name) +	{ +		return sort_order == E_SORT_BY_DATE; +	} +	else if ("sort_friends_on_top" == command_name) +	{ +		return gSavedSettings.getBOOL("SortFriendsFirst"); +	} + +	return false; +} diff --git a/indra/newview/llfloaterconversationlog.h b/indra/newview/llfloaterconversationlog.h new file mode 100644 index 0000000000..7d788c0290 --- /dev/null +++ b/indra/newview/llfloaterconversationlog.h @@ -0,0 +1,61 @@ +/** + * @file llfloaterconversationlog.h + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERCONVERSATIONLOG_H_ +#define LL_LLFLOATERCONVERSATIONLOG_H_ + +#include "llfloater.h" + +class LLConversationLogList; + +class LLFloaterConversationLog : public LLFloater +{ +public: + +	typedef enum e_sort_oder{ +		E_SORT_BY_NAME = 0, +		E_SORT_BY_DATE = 1, +	} ESortOrder; + +	LLFloaterConversationLog(const LLSD& key); +	virtual ~LLFloaterConversationLog(){}; + +	virtual BOOL postBuild(); + +	virtual void draw(); + +	void onFilterEdit(const std::string& search_string); + +private: + +	void onCustomAction (const LLSD& userdata); +	bool isActionEnabled(const LLSD& userdata); +	bool isActionChecked(const LLSD& userdata); + +	LLConversationLogList* mConversationLogList; +}; + + +#endif /* LLFLOATERCONVERSATIONLOG_H_ */ diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp new file mode 100644 index 0000000000..e8554bb066 --- /dev/null +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -0,0 +1,112 @@ +/** + * @file llfloaterconversationpreview.cpp + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llconversationlog.h" +#include "llfloaterconversationpreview.h" +#include "llimview.h" +#include "lllineeditor.h" + +LLFloaterConversationPreview::LLFloaterConversationPreview(const LLSD& session_id) +:	LLFloater(session_id), +	mChatHistory(NULL), +	mSessionID(session_id.asUUID()) +{} + +BOOL LLFloaterConversationPreview::postBuild() +{ +	mChatHistory = getChild<LLChatHistory>("chat_history"); + +	const LLConversation* conv = LLConversationLog::instance().getConversation(mSessionID); +	if (conv) +	{ +		std::string name = conv->getConversationName(); +		LLStringUtil::format_map_t args; +		args["[NAME]"] = name; +		std::string title = getString("Title", args); +		setTitle(title); + +		getChild<LLLineEditor>("description")->setValue(name); +	} + +	return LLFloater::postBuild(); +} + +void LLFloaterConversationPreview::draw() +{ +	LLFloater::draw(); +} + +void LLFloaterConversationPreview::onOpen(const LLSD& session_id) +{ +	const LLConversation* conv = LLConversationLog::instance().getConversation(session_id); +	if (!conv) +	{ +		return; +	} +	std::list<LLSD> messages; +	std::string file = conv->getHistoryFileName(); +	LLLogChat::loadAllHistory(file, messages); + +	if (messages.size()) +	{ +		std::ostringstream message; +		std::list<LLSD>::const_iterator iter = messages.begin(); +		for (; iter != messages.end(); ++iter) +		{ +			LLSD msg = *iter; + +			std::string time	= msg["time"].asString(); +			LLUUID from_id		= msg["from_id"].asUUID(); +			std::string from	= msg["from"].asString(); +			std::string message	= msg["message"].asString(); +			bool is_history	= msg["is_history"].asBoolean(); + +			LLChat chat; +			chat.mFromID = from_id; +			chat.mSessionID = session_id; +			chat.mFromName = from; +			chat.mTimeStr = time; +			chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; +			chat.mText = message; + +			appendMessage(chat); +		} +	} +} + +void LLFloaterConversationPreview::appendMessage(const LLChat& chat) +{ +	if (!chat.mMuted) +	{ +		LLSD args; +		args["use_plain_text_chat_history"] = true; +		args["show_time"] = true; +		args["show_names_for_p2p_conv"] = true; + +		mChatHistory->appendMessage(chat); +	} +} diff --git a/indra/newview/llfloaterconversationpreview.h b/indra/newview/llfloaterconversationpreview.h new file mode 100644 index 0000000000..cfc7c34485 --- /dev/null +++ b/indra/newview/llfloaterconversationpreview.h @@ -0,0 +1,51 @@ +/** + * @file llfloaterconversationpreview.h + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERCONVERSATIONPREVIEW_H_ +#define LLFLOATERCONVERSATIONPREVIEW_H_ + +#include "llchathistory.h" +#include "llfloater.h" + +class LLFloaterConversationPreview : public LLFloater +{ +public: + +	LLFloaterConversationPreview(const LLSD& session_id); +	virtual ~LLFloaterConversationPreview(){}; + +	virtual BOOL postBuild(); + +	virtual void draw(); +	virtual void onOpen(const LLSD& session_id); + +private: +	void appendMessage(const LLChat& chat); + +	LLChatHistory*	mChatHistory; +	LLUUID			mSessionID; +}; + +#endif /* LLFLOATERCONVERSATIONPREVIEW_H_ */ diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index 260957011e..1b08c454b7 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -60,6 +60,8 @@  #include "llnotificationmanager.h"  #include "llautoreplace.h" +floater_showed_signal_t LLIMFloater::sIMFloaterShowedSignal; +  LLIMFloater::LLIMFloater(const LLUUID& session_id)    : LLIMConversation(session_id),  	mLastMessageIndex(-1), @@ -771,6 +773,11 @@ void LLIMFloater::setVisible(BOOL visible)  			chiclet->setToggleState(false);  		}  	} + +	if (visible) +	{ +		sIMFloaterShowedSignal(mSessionID); +	}  }  BOOL LLIMFloater::getVisible() @@ -1340,3 +1347,8 @@ void LLIMFloater::addToHost(const LLUUID& session_id)  	}  	}  } + +boost::signals2::connection LLIMFloater::setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb) +{ +	return LLIMFloater::sIMFloaterShowedSignal.connect(cb); +} diff --git a/indra/newview/llimfloater.h b/indra/newview/llimfloater.h index 2ac11ded20..7e45cf42c2 100644 --- a/indra/newview/llimfloater.h +++ b/indra/newview/llimfloater.h @@ -44,6 +44,8 @@ class LLChatHistory;  class LLInventoryItem;  class LLInventoryCategory; +typedef boost::signals2::signal<void(const LLUUID& session_id)> floater_showed_signal_t; +  /**   * Individual IM window that appears at the bottom of the screen,   * optionally "docked" to the bottom tray. @@ -125,7 +127,11 @@ public:  	bool getStartConferenceInSameFloater() const { return mStartConferenceInSameFloater; } +	static boost::signals2::connection setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb); +	static floater_showed_signal_t sIMFloaterShowedSignal; +  private: +  	// process focus events to set a currently active session  	/* virtual */ void onFocusLost();  	/* virtual */ void onFocusReceived(); diff --git a/indra/newview/llimfloatercontainer.cpp b/indra/newview/llimfloatercontainer.cpp index 386afab77f..adcd840dfc 100644 --- a/indra/newview/llimfloatercontainer.cpp +++ b/indra/newview/llimfloatercontainer.cpp @@ -107,6 +107,7 @@ BOOL LLIMFloaterContainer::postBuild()  	p.rect = mConversationsListPanel->getLocalRect();  	p.follows.flags = FOLLOWS_ALL;  	p.listener = base_item; +	p.root = NULL;  	mConversationsRoot = LLUICtrlFactory::create<LLFolderView>(p);  	mConversationsListPanel->addChild(mConversationsRoot); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index ae08b7f8b9..d88a558125 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -175,10 +175,11 @@ LLIMModel::LLIMModel()  	addNewMsgCallback(boost::bind(&toast_callback, _1));  } -LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice) +LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg)  :	mSessionID(session_id),  	mName(name),  	mType(type), +	mHasOfflineMessage(has_offline_msg),  	mParticipantUnreadMessageCount(0),  	mNumUnread(0),  	mOtherParticipantID(other_participant_id), @@ -375,6 +376,8 @@ void LLIMModel::LLIMSession::onVoiceChannelStateChanged(const LLVoiceChannel::ES  				break;  			}  		} +	default: +		break;  	}  	// Update speakers list when connected  	if (LLVoiceChannel::STATE_CONNECTED == new_state) @@ -676,7 +679,7 @@ void LLIMModel::testMessages()  //session name should not be empty  bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type,  -						   const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice) +						   const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg)  {  	if (name.empty())  	{ @@ -690,7 +693,7 @@ bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, co  		return false;  	} -	LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voice); +	LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg);  	mId2SessionMap[session_id] = session;  	// When notifying observer, name of session is used instead of "name", because they may not be the @@ -702,10 +705,10 @@ bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, co  } -bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, bool voice) +bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, bool voice, bool has_offline_msg)  {  	uuid_vec_t no_ids; -	return newSession(session_id, name, type, other_participant_id, no_ids, voice); +	return newSession(session_id, name, type, other_participant_id, no_ids, voice, has_offline_msg);  }  bool LLIMModel::clearSession(const LLUUID& session_id) @@ -2398,6 +2401,7 @@ void LLIMMgr::addMessage(  	const LLUUID& target_id,  	const std::string& from,  	const std::string& msg, +	bool  is_offline_msg,  	const std::string& session_name,  	EInstantMessage dialog,  	U32 parent_estate_id, @@ -2423,7 +2427,7 @@ void LLIMMgr::addMessage(  	bool new_session = !hasSession(new_session_id);  	if (new_session)  	{ -		LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id); +		LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, false, is_offline_msg);  		// When we get a new IM, and if you are a god, display a bit  		// of information about the source. This is to help liaisons @@ -3307,6 +3311,7 @@ public:  				from_id,  				name,  				buffer, +				IM_OFFLINE == offline,  				std::string((char*)&bin_bucket[0]),  				IM_SESSION_INVITE,  				message_params["parent_estate_id"].asInteger(), diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 80bf315aa8..fa9d20ca53 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -70,10 +70,11 @@ public:  			GROUP_SESSION,  			ADHOC_SESSION,  			AVALINE_SESSION, +			NONE_SESSION,  		} SType;  		LLIMSession(const LLUUID& session_id, const std::string& name,  -			const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice); +			const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg);  		virtual ~LLIMSession();  		void sessionInitReplyReceived(const LLUUID& new_session_id); @@ -133,6 +134,8 @@ public:  		//if IM session is created for a voice call  		bool mStartedAsIMCall; +		bool mHasOfflineMessage; +  	private:  		void onAdHocNameCache(const LLAvatarName& av_name); @@ -181,10 +184,10 @@ public:  	 * @param name session name should not be empty, will return false if empty  	 */  	bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id,  -		const uuid_vec_t& ids, bool voice = false); +		const uuid_vec_t& ids, bool voice = false, bool has_offline_msg = false);  	bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, -		const LLUUID& other_participant_id, bool voice = false); +		const LLUUID& other_participant_id, bool voice = false, bool has_offline_msg = false);  	/**  	 * Remove all session data associated with a session specified by session_id @@ -325,6 +328,7 @@ public:  					const LLUUID& target_id,  					const std::string& from,  					const std::string& msg, +					bool  is_offline_msg = false,  					const std::string& session_name = LLStringUtil::null,  					EInstantMessage dialog = IM_NOTHING_SPECIAL,  					U32 parent_estate_id = 0, diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 6a7ee3b6a0..be1cd2510d 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -659,6 +659,7 @@ LLFolderView * LLInventoryPanel::createFolderView(LLInvFVBridge * bridge, bool u  	p.allow_multiselect = mAllowMultiSelect;  	p.show_empty_message = mShowEmptyMessage;  	p.show_item_link_overlays = mShowItemLinkOverlays; +	p.root = NULL;  	return LLUICtrlFactory::create<LLFolderView>(p);  } diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index 5887f4d244..4f2c515bde 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -1563,6 +1563,7 @@ void LLPanelObjectInventory::reset()  	p.listener = LLTaskInvFVBridge::createObjectBridge(this, NULL);  	p.folder_indentation = -14; // subtract space normally reserved for folder expanders  	p.view_model = &mInventoryViewModel; +	p.root = NULL;  	mFolders = LLUICtrlFactory::create<LLFolderView>(p);  	// this ensures that we never say "searching..." or "no items found"  	//TODO RN: make this happen by manipulating filter object directly diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 0b70184920..68b4063df9 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -94,6 +94,7 @@  #include "llcallingcard.h"  #include "llconsole.h"  #include "llcontainerview.h" +#include "llconversationlog.h"  #include "lldebugview.h"  #include "lldrawable.h"  #include "lleventnotifier.h" @@ -1268,6 +1269,8 @@ bool idle_startup()  		display_startup();  		LLStartUp::setStartupState( STATE_MULTIMEDIA_INIT ); +		LLConversationLog::getInstance(); +  		return FALSE;  	} diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index e45426a8d6..795c171d28 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -50,6 +50,8 @@  #include "llfloaterbump.h"  #include "llfloaterbvhpreview.h"  #include "llfloatercamera.h" +#include "llfloaterconversationlog.h" +#include "llfloaterconversationpreview.h"  #include "llfloaterdeleteenvpreset.h"  #include "llfloaterdisplayname.h"  #include "llfloatereditdaycycle.h" @@ -189,6 +191,7 @@ void LLViewerFloaterReg::registerFloaters()  	LLFloaterReg::add("camera", "floater_camera.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCamera>);  	LLFloaterReg::add("chat_bar", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLNearbyChat>);  	LLFloaterReg::add("compile_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCompileQueue>); +	LLFloaterReg::add("conversation", "floater_conversation_log.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterConversationLog>);  	LLFloaterReg::add("destinations", "floater_destinations.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterDestinations>); @@ -262,6 +265,7 @@ void LLViewerFloaterReg::registerFloaters()  	LLFloaterReg::add("picks", "floater_picks.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSidePanelContainer>);  	LLFloaterReg::add("pref_joystick", "floater_joystick.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterJoystick>);  	LLFloaterReg::add("preview_anim", "floater_preview_animation.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLPreviewAnim>, "preview"); +	LLFloaterReg::add("preview_conversation", "floater_conversation_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterConversationPreview>);  	LLFloaterReg::add("preview_gesture", "floater_preview_gesture.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLPreviewGesture>, "preview");  	LLFloaterReg::add("preview_notecard", "floater_preview_notecard.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLPreviewNotecard>, "preview");  	LLFloaterReg::add("preview_script", "floater_script_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLPreviewLSL>, "preview"); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index d03a58e9e3..f4a3f31ba5 100755 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -2438,6 +2438,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data)  				from_id,  				name,  				buffer, +				IM_OFFLINE == offline,  				LLStringUtil::null,  				dialog,  				parent_estate_id, @@ -2477,7 +2478,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data)  				if (!gIMMgr->isNonFriendSessionNotified(session_id))  				{  					std::string message = LLTrans::getString("IM_unblock_only_groups_friends"); -					gIMMgr->addMessage(session_id, from_id, name, message); +					gIMMgr->addMessage(session_id, from_id, name, message, IM_OFFLINE == offline);  					gIMMgr->addNotifiedNonFriendSessionID(session_id);  				} @@ -2490,6 +2491,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data)  					from_id,  					name,  					buffer, +					IM_OFFLINE == offline,  					LLStringUtil::null,  					dialog,  					parent_estate_id, @@ -2830,6 +2832,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data)  			from_id,  			name,  			buffer, +			IM_OFFLINE == offline,  			ll_safe_string((char*)binary_bucket),  			IM_SESSION_INVITE,  			parent_estate_id, diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 820d1d73e1..f1bf4a6d75 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -3964,6 +3964,7 @@ void LLVivoxVoiceClient::messageEvent(  						session->mCallerID,  						session->mName.c_str(),  						message.c_str(), +						false,  						LLStringUtil::null,		// default arg  						IM_NOTHING_SPECIAL,		// default arg  						0,						// default arg diff --git a/indra/newview/skins/default/xui/en/floater_conversation_log.xml b/indra/newview/skins/default/xui/en/floater_conversation_log.xml new file mode 100644 index 0000000000..9cdeb0d788 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_conversation_log.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> + +<floater +  can_resize="true" +  positioning="cascading" +  height="400" +  min_height="100" +  min_width="390" +  layout="topleft" +  name="floater_conversation_log" +  save_rect="true" +  single_instance="true" +  reuse_instance="true" +  title="CONVERSATION LOG" +  width="450"> +	<panel +      follows="left|top|right" +      height="27" +      layout="topleft" +      left="0" +      name="buttons_panel" +      top="0"> +        <filter_editor +          follows="left|top|right" +          height="23" +          layout="topleft" +          left="8" +          label="Filter People" +          max_length_chars="300" +          name="people_filter_input" +          text_color="Black" +          text_pad_left="10" +          top="4" +          width="364" /> +        <menu_button +          follows="right" +          height="25" +          image_hover_unselected="Toolbar_Middle_Over" +          image_overlay="Conv_toolbar_sort" +          image_selected="Toolbar_Middle_Selected" +          image_unselected="Toolbar_Middle_Off" +          layout="topleft" +          left_pad="5" +          menu_filename="menu_conversation_log_view.xml" +          menu_position="bottomleft" +          name="conversation_view_btn" +          top="3" +          width="31" /> +        <menu_button +          follows="right" +          height="25" +          image_hover_unselected="Toolbar_Middle_Over" +          image_overlay="OptionsMenu_Off" +          image_selected="Toolbar_Middle_Selected" +          image_unselected="Toolbar_Middle_Off" +          layout="topleft" +          left_pad="2" +          name="conversations_gear_btn" +          top="3" +          width="31" /> +    </panel> +    <panel +      follows="all" +      height="370" +      layout="topleft" +      left="5" +      name="buttons_panel" +      right="-5" +      top_pad="5"> +    <conversation_log_list +      opaque="true" +      allow_select="true" +      follows="all" +      height="360" +      layout="topleft" +      left="3" +      keep_selection_visible_on_reshape="true" +      item_pad="2" +      multi_select="false" +      name="conversation_log_list" +      right="-3" +      top="5" /> +    </panel> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml new file mode 100644 index 0000000000..27b744aefb --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + can_resize="true" + default_tab_group="1" + height="361" + layout="topleft" + min_height="243" + min_width="234" + name="preview_conversation" + title="CONVERSATION:" + width="400"> +    <floater.string +     name="Title"> +        CONVERSATION: [NAME] +    </floater.string> +    <text +     type="string" +     length="1" +     follows="left|top" +     font="SansSerif" +     height="19" +     layout="topleft" +     left="10" +     name="desc txt" +     top="22" +     width="90"> +        Description: +    </text> +    <line_editor +     border_style="line" +     border_thickness="1" +     enabled="false" +     follows="left|top|right" +     font="SansSerif" +     height="22" +     layout="topleft" +     left_pad="0" +     max_length_bytes="127" +     name="description" +     width="296" /> +    <chat_history +     font="SansSerifSmall" +     follows="all" +     visible="true" +     height="310" +     name="chat_history" +     parse_highlights="true" +     parse_urls="true" +     left="5" +     width="390"> +    </chat_history> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_im_container.xml b/indra/newview/skins/default/xui/en/floater_im_container.xml index e5ef80e352..e8ef3c1df9 100644 --- a/indra/newview/skins/default/xui/en/floater_im_container.xml +++ b/indra/newview/skins/default/xui/en/floater_im_container.xml @@ -56,6 +56,7 @@                       image_overlay="Conv_toolbar_sort"                       image_selected="Toolbar_Middle_Selected"                       image_unselected="Toolbar_Middle_Off" +                     menu_filename="menu_participant_view.xml"                       layout="topleft"                       left="10"                       name="sort_btn" diff --git a/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml b/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml new file mode 100644 index 0000000000..b8d0eef956 --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<toggleable_menu + layout="topleft" + name="Conversation Context Menu"> +    <menu_item_call +     label="IM..." +     layout="topleft" +     name="IM"> +        <on_click +         function="Calllog.Action" +         parameter="im" /> +        <on_enable +         function="Calllog.Enable" +         parameter="can_im" /> +    </menu_item_call> +    <menu_item_call +     label="Voice call..." +     layout="topleft" +     name="Call"> +        <on_click +         function="Calllog.Action" +         parameter="call" /> +        <on_enable +         function="Calllog.Enable" +         parameter="can_call" /> +    </menu_item_call> +    <menu_item_call +     label="Open chat history..." +     layout="topleft" +     name="Chat history"> +        <on_click +         function="Calllog.Action" +         parameter="chat_history" /> +        <on_enable +         function="Calllog.Enable" +         parameter="can_view_chat_history" /> +    </menu_item_call> +    <menu_item_call +     label="View Profile" +     layout="topleft" +     name="View Profile"> +        <on_click +         function="Calllog.Action" +         parameter="view_profile" /> +        <on_enable +         function="Calllog.Enable" +         parameter="can_view_profile" /> +    </menu_item_call> +    <menu_item_call +    label="Offer Teleport" +    name="teleport"> +      <on_click +       function="Calllog.Action" +       parameter="offer_teleport"/> +      <on_enable +      function="Calllog.Enable" +      parameter="can_offer_teleport"/> +    </menu_item_call> +    <menu_item_separator /> +    <menu_item_check +     label="Add friend/Remove friend" +     layout="topleft" +     name="Friend_add_remove"> +        <menu_item_check.on_click +         function="Calllog.Action" +         parameter="add_rem_friend" /> +        <menu_item_check.on_check +         function="Calllog.Check" +         parameter="is_friend" /> +        <menu_item_check.on_enable +         function="Calllog.Enable" +         parameter="add_rem_friend" /> +    </menu_item_check> +    <menu_item_call +     label="Invite to group..." +     layout="topleft" +     name="Invite"> +        <on_click +         function="Calllog.Action" +         parameter="invite_to_group"/> +        <on_enable +         function="Calllog.Enable" +         parameter="can_invite_to_group" /> +    </menu_item_call> +    <menu_item_separator /> +    <menu_item_call +     label="Map" +     layout="topleft" +     name="Map"> +        <on_click +         function="Calllog.Action" +         parameter="show_on_map" /> +        <on_enable +         function="Calllog.Enable" +         parameter="can_show_on_map" /> +    </menu_item_call> +    <menu_item_call +     label="Share" +     layout="topleft" +     name="Share"> +        <on_click +         function="Calllog.Action" +         parameter="share" /> +        <on_enable +         function="Calllog.Enable" +         parameter="can_share" /> +    </menu_item_call> +    <menu_item_call +     label="Pay" +     layout="topleft" +     name="Pay"> +        <on_click +         function="Calllog.Action" +         parameter="pay" /> +        <on_enable +         function="Calllog.Enable" +         parameter="can_pay" /> +    </menu_item_call> +    <menu_item_check +     label="Block/Unblock" +     layout="topleft" +     name="Block/Unblock"> +        <menu_item_check.on_click +         function="Calllog.Action" +         parameter="block"/> +        <menu_item_check.on_check +         function="Calllog.Check" +         parameter="is_blocked" /> +        <menu_item_check.on_enable +         function="Calllog.Enable" +         parameter="can_block" /> +    </menu_item_check> + +</toggleable_menu> diff --git a/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml b/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml new file mode 100644 index 0000000000..4ab8cb4f7d --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<toggleable_menu +     name="menu_conversation_view" +     left="0" bottom="0" visible="false" +     mouse_opaque="false"> +  <menu_item_check +   label="Sort by name" +   name="sort_by_name"> +      <on_click +       function="CallLog.Action" +       parameter="sort_by_name"/> +      <on_check +       function="CallLog.Check" +       parameter="sort_by_name"/> +  </menu_item_check> +  <menu_item_check +   label="Sort by date" +   name="sort_by_date"> +      <on_click +       function="CallLog.Action" +       parameter="sort_by_date" /> +      <on_check +       function="CallLog.Check" +       parameter="sort_by_date" /> +  </menu_item_check> +  <menu_item_separator /> +  <menu_item_check +   label="Sort friends on top" +   name="sort_by_friends"> +      <on_click +       function="CallLog.Action" +       parameter="sort_friends_on_top" /> +      <on_check +       function="CallLog.Check" +       parameter="sort_friends_on_top" /> +  </menu_item_check> +</toggleable_menu> diff --git a/indra/newview/skins/default/xui/en/menu_participant_view.xml b/indra/newview/skins/default/xui/en/menu_participant_view.xml new file mode 100644 index 0000000000..6401b0e3b7 --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_participant_view.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<toggleable_menu + layout="topleft" + name="participant_manu_view"> +    <menu_item_check +         label="Open conversation log" +         name="Conversation" +         visible="true"> +        <menu_item_check.on_check +         function="Floater.Visible" +         parameter="conversation" /> +        <menu_item_check.on_click +         function="Floater.Toggle" +         parameter="conversation" /> +      </menu_item_check> +</toggleable_menu> diff --git a/indra/newview/skins/default/xui/en/panel_conversation_log_list_item.xml b/indra/newview/skins/default/xui/en/panel_conversation_log_list_item.xml new file mode 100644 index 0000000000..3c98e32e7d --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_conversation_log_list_item.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + follows="top|right|left" + height="23" + layout="topleft" + left="0" + name="conversation_log_list_item" + top="0" + width="380"> +    <icon +     height="24" +     follows="top|right|left" +     image_name="ListItem_Select" +     layout="topleft" +     left="0" +     name="selected_icon" +     top="0" +     visible="false" +     width="380" /> +    <icon +     follows="top|right|left" +     height="24" +     image_name="ListItem_Over" +     layout="topleft" +     left="0" +     name="hovered_icon" +     top="0" +     visible="false" +     width="380" /> +    <icon +     default_icon_name="voice_session_icon" +     follows="top|left" +     height="20" +     layout="topleft" +     left="5" +     image_name="Audio_Press" +     mouse_opaque="true" +     name="voice_session_icon" +     top="2" +     visible="false" +     width="20" /> +    <icon +     default_icon_name="incoming_unread_im_icon" +     follows="top|left" +     height="20" +     layout="topleft" +     left="5" +     image_name="Movement_Backward_Off" +     mouse_opaque="false" +     name="unread_ims_icon" +     top="2" +     visible="false" +     width="20" /> +    <avatar_icon +     default_icon_name="Generic_Person" +     follows="top|left" +     height="20" +     layout="topleft" +     left_pad="5" +     mouse_opaque="true" +     top="2" +     visible="false" +     width="20" /> +    <group_icon +     default_icon_name="Generic_Group" +     follows="top|left" +     height="20" +     layout="topleft" +     mouse_opaque="true" +     top="2" +     visible="false" +     width="20" /> +    <text +     follows="left|right" +     font="SansSerifSmall" +     font.color="DkGray" +     height="15" +     layout="topleft" +     left_pad="5" +     name="conversation_name" +     parse_urls="false" +     top="6" +     use_ellipses="true" +     width="180" /> +    <text +     follows="right" +     font="SansSerifSmall" +     font.color="DkGray" +     height="15" +     layout="topleft" +     left_pad="5" +     name="date_time" +     parse_urls="false" +     top="6" +     use_ellipses="true" +     width="110"/> +    <button +     name="delete_btn" +     layout="topleft" +     follows="top|right" +     image_unselected="Toast_CloseBtn" +     image_selected="Toast_CloseBtn" +     top="5" +     left_pad="0" +     height="14" +     width="14" +     tab_stop="false"/> +</panel>
\ No newline at end of file | 
