From 0ee0a5eff41a3763b1fc3fc45a36f87fc6eedac5 Mon Sep 17 00:00:00 2001
From: Paul ProductEngine <pguslisty@productengine.com>
Date: Fri, 27 Jul 2012 22:25:17 +0300
Subject: [PATCH] CHUI-151 FIXED (Implement conversation log)

A brief explanation of what have been implemented. More information can be found in comments.

1. Created conversation history viewer (llfloaterconversationpreview)
2. Created LLConversation and LLConversationLog classes which represent and hold data of conversations (llconversationlog)
3. Created LLConversationLogList and LLConversationLogListItem which are the visual representation of LLConversationLog and LLConversation respectively
4. Created LLFloaterConversationLog - which holds and displays LLConversationLogList
---
 indra/newview/CMakeLists.txt                  |  10 +
 indra/newview/app_settings/settings.xml       |  22 +
 indra/newview/llappviewer.cpp                 |   4 +
 indra/newview/llconversationlog.cpp           | 336 ++++++++++++++
 indra/newview/llconversationlog.h             | 164 +++++++
 indra/newview/llconversationloglist.cpp       | 422 ++++++++++++++++++
 indra/newview/llconversationloglist.h         | 143 ++++++
 indra/newview/llconversationloglistitem.cpp   | 157 +++++++
 indra/newview/llconversationloglistitem.h     |  77 ++++
 indra/newview/llfloaterconversationlog.cpp    | 127 ++++++
 indra/newview/llfloaterconversationlog.h      |  61 +++
 .../newview/llfloaterconversationpreview.cpp  | 112 +++++
 indra/newview/llfloaterconversationpreview.h  |  51 +++
 indra/newview/llimfloater.cpp                 |  12 +
 indra/newview/llimfloater.h                   |   6 +
 indra/newview/llimview.cpp                    |  17 +-
 indra/newview/llimview.h                      |  10 +-
 indra/newview/llstartup.cpp                   |   3 +
 indra/newview/llviewerfloaterreg.cpp          |   4 +
 indra/newview/llviewermessage.cpp             |   5 +-
 indra/newview/llvoicevivox.cpp                |   1 +
 .../xui/en/floater_conversation_log.xml       |  84 ++++
 .../xui/en/floater_conversation_preview.xml   |  53 +++
 .../default/xui/en/floater_im_container.xml   |   1 +
 .../xui/en/menu_conversation_log_gear.xml     | 134 ++++++
 .../xui/en/menu_conversation_log_view.xml     |  37 ++
 .../default/xui/en/menu_participant_view.xml  |  16 +
 .../en/panel_conversation_log_list_item.xml   | 108 +++++
 28 files changed, 2167 insertions(+), 10 deletions(-)
 create mode 100644 indra/newview/llconversationlog.cpp
 create mode 100644 indra/newview/llconversationlog.h
 create mode 100644 indra/newview/llconversationloglist.cpp
 create mode 100644 indra/newview/llconversationloglist.h
 create mode 100644 indra/newview/llconversationloglistitem.cpp
 create mode 100644 indra/newview/llconversationloglistitem.h
 create mode 100644 indra/newview/llfloaterconversationlog.cpp
 create mode 100644 indra/newview/llfloaterconversationlog.h
 create mode 100644 indra/newview/llfloaterconversationpreview.cpp
 create mode 100644 indra/newview/llfloaterconversationpreview.h
 create mode 100644 indra/newview/skins/default/xui/en/floater_conversation_log.xml
 create mode 100644 indra/newview/skins/default/xui/en/floater_conversation_preview.xml
 create mode 100644 indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml
 create mode 100644 indra/newview/skins/default/xui/en/menu_conversation_log_view.xml
 create mode 100644 indra/newview/skins/default/xui/en/menu_participant_view.xml
 create mode 100644 indra/newview/skins/default/xui/en/panel_conversation_log_list_item.xml

diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index b31b99f47c4..3fe1aec5ffe 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -130,6 +130,9 @@ set(viewer_SOURCE_FILES
     llcommandlineparser.cpp
     llcompilequeue.cpp
     llconfirmationmanager.cpp
+    llconversationlog.cpp
+    llconversationloglist.cpp
+    llconversationloglistitem.cpp
     llcurrencyuimanager.cpp
     llcylinder.cpp
     lldateutil.cpp
@@ -185,6 +188,8 @@ set(viewer_SOURCE_FILES
     llfloaterbuyland.cpp
     llfloatercamera.cpp
     llfloatercolorpicker.cpp
+    llfloaterconversationlog.cpp
+    llfloaterconversationpreview.cpp
     llfloaterdeleteenvpreset.cpp
     llfloaterdestinations.cpp
     llfloaterdisplayname.cpp
@@ -687,6 +692,9 @@ set(viewer_HEADER_FILES
     llcommandlineparser.h
     llcompilequeue.h
     llconfirmationmanager.h
+    llconversationlog.h
+    llconversationloglist.h
+    llconversationloglistitem.h
     llcurrencyuimanager.h
     llcylinder.h
     lldateutil.h
@@ -742,6 +750,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 da3ff2d1ee2..7a5abba9711 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10004,6 +10004,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 1174d108d2b..fe3a1ebf65a 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"
@@ -1757,6 +1758,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 00000000000..df9350407de
--- /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 00000000000..700472ca8b0
--- /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;
+class 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 00000000000..0433719a891
--- /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 00000000000..dff34a74c62
--- /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 00000000000..fc2e7578640
--- /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 00000000000..deba7d4563e
--- /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 00000000000..569ba12ed6c
--- /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 00000000000..40dd2666634
--- /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 00000000000..e8554bb066e
--- /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 00000000000..cfc7c344852
--- /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 22ce3cd42b9..f1d7d1c04f2 100644
--- a/indra/newview/llimfloater.cpp
+++ b/indra/newview/llimfloater.cpp
@@ -59,6 +59,8 @@
 #include "llviewerchat.h"
 #include "llnotificationmanager.h"
 
+floater_showed_signal_t LLIMFloater::sIMFloaterShowedSignal;
+
 LLIMFloater::LLIMFloater(const LLUUID& session_id)
   : LLIMConversation(session_id),
 	mLastMessageIndex(-1),
@@ -765,6 +767,11 @@ void LLIMFloater::setVisible(BOOL visible)
 			chiclet->setToggleState(false);
 		}
 	}
+
+	if (visible)
+	{
+		sIMFloaterShowedSignal(mSessionID);
+	}
 }
 
 BOOL LLIMFloater::getVisible()
@@ -1334,3 +1341,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 2ac11ded20a..7e45cf42c23 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 @@ class LLIMFloater
 
 	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/llimview.cpp b/indra/newview/llimview.cpp
index cdbb7c7cca1..c66c0cd8654 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
@@ -3315,6 +3319,7 @@ class LLViewerChatterBoxInvitation : public LLHTTPNode
 				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 80bf315aa85..fa9d20ca530 100644
--- a/indra/newview/llimview.h
+++ b/indra/newview/llimview.h
@@ -70,10 +70,11 @@ class LLIMModel :  public LLSingleton<LLIMModel>
 			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 @@ class LLIMModel :  public LLSingleton<LLIMModel>
 		//if IM session is created for a voice call
 		bool mStartedAsIMCall;
 
+		bool mHasOfflineMessage;
+
 	private:
 		void onAdHocNameCache(const LLAvatarName& av_name);
 
@@ -181,10 +184,10 @@ class LLIMModel :  public LLSingleton<LLIMModel>
 	 * @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 @@ class LLIMMgr : public LLSingleton<LLIMMgr>
 					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/llstartup.cpp b/indra/newview/llstartup.cpp
index 65fd6d70197..4cf6ff55a4c 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"
@@ -1266,6 +1267,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 bf12b083218..5c662af875f 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"
@@ -188,6 +190,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>);
 
@@ -253,6 +256,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 03c113ecb3a..20887f78320 100755
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -2396,6 +2396,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data)
 				from_id,
 				name,
 				buffer,
+				IM_OFFLINE == offline,
 				LLStringUtil::null,
 				dialog,
 				parent_estate_id,
@@ -2435,7 +2436,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);
 				}
 
@@ -2448,6 +2449,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data)
 					from_id,
 					name,
 					buffer,
+					IM_OFFLINE == offline,
 					LLStringUtil::null,
 					dialog,
 					parent_estate_id,
@@ -2788,6 +2790,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 df1d3f29557..f4c88403a5e 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -4072,6 +4072,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 00000000000..1c5800e25f6
--- /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 00000000000..27b744aefbe
--- /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 e5ef80e352c..e8ef3c1df92 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 00000000000..b8d0eef956d
--- /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 00000000000..4ab8cb4f7d0
--- /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 00000000000..6401b0e3b7e
--- /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 00000000000..3c98e32e7df
--- /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
-- 
GitLab