From dba034ee100dae4b62ddf12523835a413a25f189 Mon Sep 17 00:00:00 2001
From: dolphin <dolphin@lindenlab.com>
Date: Thu, 20 Mar 2014 16:32:31 -0700
Subject: [PATCH] Experience log panel

---
 indra/newview/llexperiencelog.cpp             | 209 +++++++++++++++
 indra/newview/llexperiencelog.h               |  44 ++++
 indra/newview/llfloaterexperiences.cpp        |   2 +
 indra/newview/llfloaterreporter.cpp           |  12 +-
 indra/newview/llfloaterreporter.h             |   4 +-
 indra/newview/llpanelexperiencelog.cpp        | 246 ++++++++++++++----
 indra/newview/llpanelexperiencelog.h          |  36 ++-
 indra/newview/llstartup.cpp                   |   8 +-
 .../xui/en/floater_experienceprofile.xml      |   4 +-
 .../skins/default/xui/en/notifications.xml    |   4 +-
 .../default/xui/en/panel_experience_log.xml   | 152 +++++++++++
 .../newview/skins/default/xui/en/strings.xml  |  10 +-
 12 files changed, 648 insertions(+), 83 deletions(-)
 create mode 100644 indra/newview/skins/default/xui/en/panel_experience_log.xml

diff --git a/indra/newview/llexperiencelog.cpp b/indra/newview/llexperiencelog.cpp
index 9842bdc31c..110ce46835 100644
--- a/indra/newview/llexperiencelog.cpp
+++ b/indra/newview/llexperiencelog.cpp
@@ -26,3 +26,212 @@
 
 #include "llviewerprecompiledheaders.h"
 #include "llexperiencelog.h"
+
+#include "lldispatcher.h"
+#include "llsdserialize.h"
+#include "llviewergenericmessage.h"
+#include "llnotificationsutil.h"
+#include "lltrans.h"
+#include "llerror.h"
+#include "lldate.h"
+
+
+class LLExperienceLogDispatchHandler : public LLDispatchHandler
+{
+public:
+	virtual bool operator()(
+		const LLDispatcher* dispatcher,
+		const std::string& key,
+		const LLUUID& invoice,
+		const sparam_t& strings)
+	{
+		LLSD message;
+
+		sparam_t::const_iterator it = strings.begin();
+		if(it != strings.end()){
+			const std::string& llsdRaw = *it++;
+			std::istringstream llsdData(llsdRaw);
+			if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length()))
+			{
+				llwarns << "LLExperienceLogDispatchHandler: Attempted to read parameter data into LLSD but failed:" << llsdRaw << llendl;
+			}
+		}
+		message["public_id"] = invoice;
+
+		// Object Name
+		if(it != strings.end())
+		{
+			message["ObjectName"] = *it++;
+		}
+
+		// parcel Name
+		if(it != strings.end())
+		{
+			message["ParcelName"] = *it++;
+		}
+
+		LLExperienceLog::instance().handleExperienceMessage(message);
+		return true;
+	}
+};
+
+static LLExperienceLogDispatchHandler experience_log_dispatch_handler;
+
+void LLExperienceLog::handleExperienceMessage(LLSD& message)
+{
+	time_t now;
+	time(&now);
+	char day[16];/* Flawfinder: ignore */
+	char time_of_day[16];/* Flawfinder: ignore */
+	strftime(day, 16, "%Y-%m-%d", localtime(&now));
+	strftime(time_of_day, 16, " %H:%M:%S", localtime(&now));
+	message["Time"] = time_of_day;
+
+	if(mNotifyNewEvent)
+	{
+		notify(message);
+	}
+	if(!mEvents.has(day)){
+		mEvents[day] = LLSD::emptyArray();
+	}
+	mEvents[day].append(message);
+}
+
+LLExperienceLog::LLExperienceLog()
+	: mMaxDays(7)
+	, mPageSize(25)
+	, mNotifyNewEvent(false)
+{
+}
+
+void LLExperienceLog::initialize()
+{
+	loadEvents();
+	if(!gGenericDispatcher.isHandlerPresent("ExperienceEvent"))
+	{
+		gGenericDispatcher.addHandler("ExperienceEvent", &experience_log_dispatch_handler);
+	}
+}
+
+std::string LLExperienceLog::getFilename()
+{
+	return gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "experience_events.xml");
+}
+
+
+std::string LLExperienceLog::getPermissionString( const LLSD& message, const std::string& base )
+{
+	std::ostringstream buf;
+	if(message.has("Permission"))
+	{
+		buf << base << message["Permission"].asInteger();
+		std::string entry;
+		if(LLTrans::findString(entry, buf.str()))
+		{
+			buf.str(entry);
+		}
+		else
+		{
+			buf.str();
+		}
+	}
+
+	if(buf.str().empty())
+	{
+		buf << base << "Unknown";
+
+		buf.str(LLTrans::getString(buf.str(), message));
+	}
+
+	return buf.str();
+}
+
+void LLExperienceLog::notify( LLSD& message )
+{
+	message["EventType"] = getPermissionString(message, "ExperiencePermission");
+	if(message.has("IsAttachment") && message["IsAttachment"].asBoolean())
+	{
+		LLNotificationsUtil::add("ExperienceEventAttachment", message);
+	}
+	else
+	{
+		LLNotificationsUtil::add("ExperienceEvent", message);
+	}
+	message.erase("EventType");
+}
+
+void LLExperienceLog::saveEvents()
+{
+	eraseExpired();
+	std::string filename = getFilename();
+	LLSD settings = LLSD::emptyMap().with("Events", mEvents);
+
+	settings["MaxDays"] = (int)mMaxDays;
+	settings["Notify"] = mNotifyNewEvent;
+	settings["PageSize"] = (int)mPageSize;
+
+	llofstream stream(filename);
+	LLSDSerialize::toPrettyXML(settings, stream);
+}
+
+
+void LLExperienceLog::loadEvents()
+{
+	LLSD settings = LLSD::emptyMap();
+
+	std::string filename = getFilename();
+	llifstream stream(filename);
+	LLSDSerialize::fromXMLDocument(settings, stream);
+
+	if(settings.has("MaxDays"))
+	{
+		mMaxDays = (U32)settings["MaxDays"].asInteger();
+	}
+	if(settings.has("Notify"))
+	{
+		mNotifyNewEvent = settings["Notify"].asBoolean();
+	}
+	if(settings.has("PageSize"))
+	{
+		mPageSize = (U32)settings["PageSize"].asInteger();
+	}
+	mEvents.clear();
+	if(mMaxDays > 0 && settings.has("Events"))
+	{
+		mEvents = settings["Events"];
+	}
+
+	eraseExpired();
+}
+
+LLExperienceLog::~LLExperienceLog()
+{
+	saveEvents();
+}
+
+void LLExperienceLog::eraseExpired()
+{
+	while(mEvents.size() > mMaxDays && mMaxDays > 0)
+	{
+		mEvents.erase(mEvents.beginMap()->first);
+	}
+}
+
+const LLSD& LLExperienceLog::getEvents() const
+{
+	return mEvents;
+}
+
+void LLExperienceLog::clear()
+{
+	mEvents.clear();
+}
+
+void LLExperienceLog::setMaxDays( U32 val )
+{
+	mMaxDays = val;
+	if(mMaxDays > 0)
+	{
+		eraseExpired();
+	}
+}
diff --git a/indra/newview/llexperiencelog.h b/indra/newview/llexperiencelog.h
index 73d82f4fad..26ffab49f9 100644
--- a/indra/newview/llexperiencelog.h
+++ b/indra/newview/llexperiencelog.h
@@ -29,5 +29,49 @@
 #ifndef LL_LLEXPERIENCELOG_H
 #define LL_LLEXPERIENCELOG_H
 
+#include "llsingleton.h"
+
+class LLExperienceLog : public LLSingleton<LLExperienceLog>
+{
+public:
+	void initialize();
+
+	U32 getMaxDays() const { return mMaxDays; }
+	void setMaxDays(U32 val);
+
+	bool getNotifyNewEvent() const { return mNotifyNewEvent; }
+	void setNotifyNewEvent(bool val) { mNotifyNewEvent = val; }
+
+	U32 getPageSize() const { return mPageSize; }
+	void setPageSize(U32 val) { mPageSize = val; }
+
+	const LLSD& getEvents()const;
+	void clear();
+
+	virtual ~LLExperienceLog();
+
+	static void notify(LLSD& message);
+	static std::string getFilename();
+	static std::string getPermissionString(const LLSD& message, const std::string& base);
+protected:
+	LLExperienceLog();
+	void handleExperienceMessage(LLSD& message);
+
+
+	void loadEvents();
+	void saveEvents();
+	void eraseExpired();
+
+	LLSD mEvents;
+	U32 mMaxDays;
+	U32 mPageSize;
+	bool mNotifyNewEvent;
+
+	friend class LLExperienceLogDispatchHandler;
+	friend class LLSingleton<LLExperienceLog>;
+};
+
+
+
 
 #endif // LL_LLEXPERIENCELOG_H
diff --git a/indra/newview/llfloaterexperiences.cpp b/indra/newview/llfloaterexperiences.cpp
index f958a988dc..1654419826 100644
--- a/indra/newview/llfloaterexperiences.cpp
+++ b/indra/newview/llfloaterexperiences.cpp
@@ -35,6 +35,7 @@
 #include "llexperiencecache.h"
 #include "llevents.h"
 #include "llnotificationsutil.h"
+#include "llpanelexperiencelog.h"
 
 
 
@@ -115,6 +116,7 @@ BOOL LLFloaterExperiences::postBuild()
 #if SHOW_RECENT_TAB
 	addTab("Recent_Experiences_Tab", false);
 #endif //SHOW_RECENT_TAB
+	getChild<LLTabContainer>("xp_tabs")->addTabPanel(new LLPanelExperienceLog());
     resizeToTabs();
 
    
diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp
index a58e2a4791..27e26d4fda 100755
--- a/indra/newview/llfloaterreporter.cpp
+++ b/indra/newview/llfloaterreporter.cpp
@@ -248,7 +248,7 @@ void LLFloaterReporter::getExperienceInfo(const LLUUID& experience_id)
 		}
 		
 		LLUICtrl* details = getChild<LLUICtrl>("details_edit");
-		details->setValue(desc.str());
+		details->setValue(details->getValue().asString()+desc.str());
 	}
 }
 
@@ -497,7 +497,7 @@ void LLFloaterReporter::showFromMenu(EReportType report_type)
 }
 
 // static
-void LLFloaterReporter::show(const LLUUID& object_id, const std::string& avatar_name)
+void LLFloaterReporter::show(const LLUUID& object_id, const std::string& avatar_name, const LLUUID& experience_id)
 {
 	LLFloaterReporter* f = LLFloaterReg::showTypedInstance<LLFloaterReporter>("reporter");
 
@@ -510,6 +510,10 @@ void LLFloaterReporter::show(const LLUUID& object_id, const std::string& avatar_
 	{
 		f->setFromAvatarID(object_id);
 	}
+	if(experience_id.notNull())
+	{
+		f->getExperienceInfo(experience_id);
+	}
 
 	// Need to deselect on close
 	f->mDeselectOnClose = TRUE;
@@ -532,9 +536,9 @@ void LLFloaterReporter::showFromExperience( const LLUUID& experience_id )
 
 
 // static
-void LLFloaterReporter::showFromObject(const LLUUID& object_id)
+void LLFloaterReporter::showFromObject(const LLUUID& object_id, const LLUUID& experience_id)
 {
-	show(object_id);
+	show(object_id, LLStringUtil::null, experience_id);
 }
 
 // static
diff --git a/indra/newview/llfloaterreporter.h b/indra/newview/llfloaterreporter.h
index de3aa9ca5e..5eb5c20665 100755
--- a/indra/newview/llfloaterreporter.h
+++ b/indra/newview/llfloaterreporter.h
@@ -88,7 +88,7 @@ public:
 	// Enables all buttons
 	static void showFromMenu(EReportType report_type);
 
-	static void showFromObject(const LLUUID& object_id);
+	static void showFromObject(const LLUUID& object_id, const LLUUID& experience_id = LLUUID::null);
 	static void showFromAvatar(const LLUUID& avatar_id, const std::string avatar_name);
 	static void showFromExperience(const LLUUID& experience_id);
 
@@ -107,7 +107,7 @@ public:
 	void setPickedObjectProperties(const std::string& object_name, const std::string& owner_name, const LLUUID owner_id);
 
 private:
-	static void show(const LLUUID& object_id, const std::string& avatar_name = LLStringUtil::null);
+	static void show(const LLUUID& object_id, const std::string& avatar_name = LLStringUtil::null, const LLUUID& experience_id = LLUUID::null);
 
 	void takeScreenshot();
 	void sendReportViaCaps(std::string url);
diff --git a/indra/newview/llpanelexperiencelog.cpp b/indra/newview/llpanelexperiencelog.cpp
index 009889a59f..e0e522e276 100644
--- a/indra/newview/llpanelexperiencelog.cpp
+++ b/indra/newview/llpanelexperiencelog.cpp
@@ -27,92 +27,224 @@
 
 #include "llviewerprecompiledheaders.h"
 #include "llpanelexperiencelog.h"
-#include "lldispatcher.h"
-#include "llsdserialize.h"
-#include "llviewergenericmessage.h"
-#include "llnotificationsutil.h"
-#include "lltrans.h"
 
+#include "llexperiencelog.h"
+#include "llexperiencecache.h"
+#include "llbutton.h"
+#include "llscrolllistctrl.h"
+#include "llcombobox.h"
+#include "llspinctrl.h"
+#include "llcheckboxctrl.h"
+#include "llfloaterreg.h"
+#include "llfloaterreporter.h"
+#include "llinventoryfunctions.h"
 
-class LLExperienceLogDispatchHandler : public LLDispatchHandler
+
+#define BTN_PROFILE_XP "btn_profile_xp"
+#define BTN_REPORT_XP "btn_report_xp"
+
+static LLPanelInjector<LLPanelExperienceLog> register_experiences_panel("experience_log");
+
+
+LLPanelExperienceLog::LLPanelExperienceLog(  )
+	: mEventList(NULL)
+	, mPageSize(25)
+	, mCurrentPage(0)
 {
-public:
-	virtual bool operator()(
-		const LLDispatcher* dispatcher,
-		const std::string& key,
-		const LLUUID& invoice,
-		const sparam_t& strings)
+	buildFromFile("panel_experience_log.xml");
+}
+
+BOOL LLPanelExperienceLog::postBuild( void )
+{
+	LLExperienceLog* log = LLExperienceLog::getInstance();
+	mEventList = getChild<LLScrollListCtrl>("experience_log_list");
+	mEventList->setCommitCallback(boost::bind(&LLPanelExperienceLog::onSelectionChanged, this));
+
+	getChild<LLButton>("btn_clear")->setCommitCallback(boost::bind(&LLExperienceLog::clear, log));
+	getChild<LLButton>("btn_clear")->setCommitCallback(boost::bind(&LLPanelExperienceLog::refresh, this));
+
+	getChild<LLButton>(BTN_PROFILE_XP)->setCommitCallback(boost::bind(&LLPanelExperienceLog::onProfileExperience, this));
+	getChild<LLButton>(BTN_REPORT_XP)->setCommitCallback(boost::bind(&LLPanelExperienceLog::onReportExperience, this));
+	getChild<LLButton>("btn_notify")->setCommitCallback(boost::bind(&LLPanelExperienceLog::onNotify, this));
+	getChild<LLButton>("btn_next")->setCommitCallback(boost::bind(&LLPanelExperienceLog::onNext, this));
+	getChild<LLButton>("btn_prev")->setCommitCallback(boost::bind(&LLPanelExperienceLog::onPrev, this));
+
+	LLCheckBoxCtrl* check = getChild<LLCheckBoxCtrl>("notify_all");
+	check->set(log->getNotifyNewEvent());
+	check->setCommitCallback(boost::bind(&LLPanelExperienceLog::notifyChanged, this));
+
+
+	LLSpinCtrl* spin = getChild<LLSpinCtrl>("logsizespinner");
+	spin->set(log->getMaxDays());
+	spin->setCommitCallback(boost::bind(&LLPanelExperienceLog::logSizeChanged, this));
+
+	mPageSize = log->getPageSize();
+	refresh();
+	return TRUE;
+}
+
+LLPanelExperienceLog* LLPanelExperienceLog::create()
+{
+	return new LLPanelExperienceLog();
+}
+
+void LLPanelExperienceLog::refresh()
+{
+	mEventList->deleteAllItems();
+	const LLSD& events = LLExperienceLog::instance().getEvents();
+
+	if(events.size() == 0)
 	{
-		LLSD message;
+		mEventList->setCommentText(getString("no_events"));
+		return;
+	}
 
-		sparam_t::const_iterator it = strings.begin();
-		if(it != strings.end()){
-			const std::string& llsdRaw = *it++;
-			std::istringstream llsdData(llsdRaw);
-			if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length()))
-			{
-				llwarns << "LLExperienceLogDispatchHandler: Attempted to read parameter data into LLSD but failed:" << llsdRaw << llendl;
-			}
-		}
-		message["public_id"] = invoice;
+	setAllChildrenEnabled(FALSE);
+
+	LLSD item;
+	bool waiting = false;
+	LLUUID waiting_id;
 
-		// Object Name
-		if(it != strings.end())
+	int itemsToSkip = mPageSize*mCurrentPage;
+	int items = 0;
+	bool moreItems = false;
+
+	for(LLSD::map_const_iterator day = events.beginMap(); day != events.endMap() ; ++day)
+	{
+		const LLSD& dayArray = day->second;
+		int size = dayArray.size();
+		if(itemsToSkip > size)
 		{
-			message["ObjectName"] = *it++;
+			itemsToSkip -= size;
+			continue;
 		}
-
-		// parcel Name
-		if(it != strings.end())
+		if(items >= mPageSize && size > 0)
+		{
+			moreItems = true;
+			break;
+		}
+		for(int i = itemsToSkip ; i < dayArray.size(); i++)
 		{
-			message["ParcelName"] = *it++;
+			if(items >= mPageSize)
+			{
+				moreItems = true;
+				break;
+			}
+			const LLSD& event = dayArray[i];
+			LLUUID id = event[LLExperienceCache::EXPERIENCE_ID].asUUID();
+			const LLSD& experience = LLExperienceCache::get(id);
+			if(experience.isUndefined()){
+				waiting = true;
+				waiting_id = id;
+			}
+			if(!waiting)
+			{
+				item["id"] = event;
+
+				LLSD& columns = item["columns"];
+				columns[0]["column"] = "time";
+				columns[0]["value"] = day->first+event["Time"].asString();
+				columns[1]["column"] = "event";
+				columns[1]["value"] = LLExperienceLog::getPermissionString(event, "ExperiencePermissionShort");
+				columns[2]["column"] = "experience_name";
+				columns[2]["value"] = experience[LLExperienceCache::NAME].asString();
+				columns[3]["column"] = "object_name";
+				columns[3]["value"] = event["ObjectName"].asString();
+				mEventList->addElement(item);
+			}
+			++items;
 		}
+	}
+	if(waiting)
+	{
+		mEventList->deleteAllItems();
+		mEventList->setCommentText(getString("loading"));
+		LLExperienceCache::get(waiting_id, boost::bind(&LLPanelExperienceLog::refresh, this));
+	}
+	else
+	{
+		setAllChildrenEnabled(TRUE);
 
-		LLExperienceLog::instance().handleExperienceMessage(message);
-		return true;
+		mEventList->setEnabled(TRUE);
+		getChild<LLButton>("btn_next")->setEnabled(moreItems);
+		getChild<LLButton>("btn_prev")->setEnabled(mCurrentPage>0);
+		getChild<LLButton>("btn_clear")->setEnabled(mEventList->getItemCount()>0);
+		onSelectionChanged();
 	}
-};
+}
 
-static LLExperienceLogDispatchHandler experience_log_dispatch_handler;
+void LLPanelExperienceLog::onProfileExperience()
+{
+	LLSD& event = getSelectedEvent();
+	if(event.isDefined())
+	{
+		LLFloaterReg::showInstance("experience_profile", event[LLExperienceCache::EXPERIENCE_ID].asUUID(), true);
+	}
+}
 
-void LLExperienceLog::handleExperienceMessage(LLSD& message)
+void LLPanelExperienceLog::onReportExperience()
 {
-	std::ostringstream str;
-	if(message.has("Permission"))
+	LLSD& event = getSelectedEvent();
+	if(event.isDefined())
 	{
-		str << "ExperiencePermission" << message["Permission"].asInteger();
-		std::string entry;
-		if(LLTrans::findString(entry, str.str()))
-		{
-			str.str(entry);
-		}
-		else
-		{
-			str.str();
-		}
+		LLFloaterReporter::showFromExperience(event[LLExperienceCache::EXPERIENCE_ID].asUUID());
 	}
+}
 
-	if(str.str().empty())
+void LLPanelExperienceLog::onNotify()
+{
+	LLSD& event = getSelectedEvent();
+	if(event.isDefined())
 	{
-		str.str(LLTrans::getString("ExperiencePermissionUnknown", message));
+		LLExperienceLog::instance().notify(event);
 	}
+}
+
+void LLPanelExperienceLog::onNext()
+{
+	mCurrentPage++;
+	refresh();
+}
 
-	message["EventType"] = str.str();
-	if(message.has("IsAttachment") && message["IsAttachment"].asBoolean())
+void LLPanelExperienceLog::onPrev()
+{
+	if(mCurrentPage>0)
 	{
-		LLNotificationsUtil::add("ExperienceEventAttachment", message);
+		mCurrentPage--;
+		refresh();
 	}
-	else
+}
+
+void LLPanelExperienceLog::notifyChanged()
+{
+	LLExperienceLog::instance().setNotifyNewEvent(getChild<LLCheckBoxCtrl>("notify_all")->get());
+}
+
+void LLPanelExperienceLog::logSizeChanged()
+{
+	int value = (int)(getChild<LLSpinCtrl>("logsizespinner")->get());
+	bool dirty = value > 0 && value < LLExperienceLog::instance().getMaxDays();
+	LLExperienceLog::instance().setMaxDays(value);
+	if(dirty)
 	{
-		LLNotificationsUtil::add("ExperienceEvent", message);
+		refresh();
 	}
 }
 
-LLExperienceLog::LLExperienceLog()
+void LLPanelExperienceLog::onSelectionChanged()
 {
+	bool enabled = (1 == mEventList->getNumSelected());
+	getChild<LLButton>(BTN_REPORT_XP)->setEnabled(enabled);
+	getChild<LLButton>(BTN_PROFILE_XP)->setEnabled(enabled);
+	getChild<LLButton>("btn_notify")->setEnabled(enabled);
 }
 
-void LLExperienceLog::initialize()
+LLSD LLPanelExperienceLog::getSelectedEvent()
 {
-	gGenericDispatcher.addHandler("ExperienceEvent", &experience_log_dispatch_handler);
+	LLScrollListItem* item = mEventList->getFirstSelected();
+	if(item)
+	{
+		return item->getValue();
+	}
+	return LLSD();
 }
diff --git a/indra/newview/llpanelexperiencelog.h b/indra/newview/llpanelexperiencelog.h
index 7d2a24872a..e4edd216d5 100644
--- a/indra/newview/llpanelexperiencelog.h
+++ b/indra/newview/llpanelexperiencelog.h
@@ -25,24 +25,38 @@
  */
 
 
-
 #ifndef LL_LLPANELEXPERIENCELOG_H
 #define LL_LLPANELEXPERIENCELOG_H
 
-#include "llsingleton.h"
+#include "llpanel.h"
+class LLScrollListCtrl;
 
+class LLPanelExperienceLog
+	: public LLPanel 
+{
+public:
+	LLPanelExperienceLog();
 
+	static LLPanelExperienceLog* create();
 
-class LLExperienceLog : public LLSingleton<LLExperienceLog>
-{
-	friend class LLSingleton<LLExperienceLog>;
+	/*virtual*/ BOOL postBuild(void);
+
+	void refresh();
 protected:
-	LLExperienceLog();
-	
-public:
-	void initialize();
-	void handleExperienceMessage(LLSD& message);
+	void logSizeChanged();
+	void notifyChanged();
+	void onNext();
+	void onNotify();
+	void onPrev();
+	void onProfileExperience();
+	void onReportExperience();
+	void onSelectionChanged();
+
+	LLSD getSelectedEvent();
+private:
+	LLScrollListCtrl* mEventList;
+	U32 mPageSize;
+	U32 mCurrentPage;
 };
 
-
 #endif // LL_LLPANELEXPERIENCELOG_H
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index b5f976080a..b3633740ca 100755
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -198,7 +198,7 @@
 #include "llevents.h"
 #include "llstartuplistener.h"
 #include "lltoolbarview.h"
-#include "llpanelexperiencelog.h"
+#include "llexperiencelog.h"
 
 #if LL_WINDOWS
 #include "lldxhardware.h"
@@ -1303,6 +1303,9 @@ bool idle_startup()
 		// object is created.  I think this must be done after setting the region.  JC
 		gAgent.setPositionAgent(agent_start_position_region);
 
+		display_startup();
+		LLStartUp::initExperiences();
+
 		display_startup();
 		LLStartUp::setStartupState( STATE_MULTIMEDIA_INIT );
 		
@@ -1413,9 +1416,6 @@ bool idle_startup()
 		LLStartUp::initNameCache();
 		display_startup();
 
-		LLStartUp::initExperiences();
-		display_startup();
-
 		// update the voice settings *after* gCacheName initialization
 		// so that we can construct voice UI that relies on the name cache
 		LLVoiceClient::getInstance()->updateSettings();
diff --git a/indra/newview/skins/default/xui/en/floater_experienceprofile.xml b/indra/newview/skins/default/xui/en/floater_experienceprofile.xml
index 67a7d5dad0..c6a749b45f 100644
--- a/indra/newview/skins/default/xui/en/floater_experienceprofile.xml
+++ b/indra/newview/skins/default/xui/en/floater_experienceprofile.xml
@@ -410,9 +410,9 @@
                 label="Report Abuse"
                 layout="topleft"
                 name="report_btn"
-                width="288"
+                width="94"
                 top_pad="3"
-                left="10"
+                left="107"
                 enabled="true"/>
             </layout_panel>
           </layout_stack>
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index b230e36cc7..edcc401b9a 100755
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -7074,7 +7074,7 @@ Unable to acquire a new experience:
     icon="notify.tga"
     name="ExperienceEvent"
     persist="false"
-    type="notify">
+    type="notifytip">
     An object was allowed to [EventType] by the secondlife:///app/experience/[public_id]/profile experience.
     Owner: secondlife:///app/agent/[OwnerID]/inspect
     Object Name: [ObjectName]
@@ -7085,7 +7085,7 @@ Unable to acquire a new experience:
     icon="notify.tga"
     name="ExperienceEventAttachment"
     persist="false"
-    type="notify">
+    type="notifytip">
     An attachment was allowed to [EventType] by the secondlife:///app/experience/[public_id]/profile experience.
     Owner: secondlife:///app/agent/[OwnerID]/inspect
   </notification>
diff --git a/indra/newview/skins/default/xui/en/panel_experience_log.xml b/indra/newview/skins/default/xui/en/panel_experience_log.xml
new file mode 100644
index 0000000000..6869a135d8
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/panel_experience_log.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+
+<panel
+  layout="topleft"
+  top="3"
+  left="3"
+  width="500"
+  height="300"
+  label="EVENTS"
+  bg_opaque_color="0 0.5 0 0.3"
+  follows="all">
+  <string
+    name="no_events"
+    value="No events."/>
+  <string
+    name="loading"
+    value="loading..."/>
+  <layout_stack
+    layout="topleft"
+    top="0"
+    left="4" 
+    right="-1"
+    bottom="-4"
+    orientation="vertical"
+    follows="all">
+    <layout_panel
+      layout="topleft"
+      top="0"
+      left="1"
+      right="-1"
+      height="250"
+      follows="all">
+      <scroll_list
+        draw_heading="true"
+        left="1"
+        right="-86"
+        height="250"
+        follows="all"
+        name="experience_log_list">
+        <columns
+          width="100"
+          user_resize="true"
+          name="time"
+          label="Time"/>
+        <columns
+          width="100"
+          user_resize="true"
+          name="event"
+          label="Event"/>
+        <columns
+          width="100"
+          user_resize="true"
+          name="experience_name"
+          label="Experience"/>
+        <columns
+          width="100"
+          user_resize="true"
+          name="object_name"
+          label="Object"/>
+      </scroll_list>
+      
+      <button
+        layout="topleft"
+        follows="top|right"
+        name="btn_notify"
+        label="Notify"
+        top_pad="-225"
+        left_pad="5"
+        right="-1"
+        enabled="false"/>
+      
+      <button
+        layout="topleft"
+        follows="top|right"
+        name="btn_profile_xp"
+        label="Profile"
+        top_pad="5"
+        right="-1"
+        enabled="false"/>
+
+      <button
+        layout="topleft"
+        follows="top|right"
+        name="btn_report_xp"
+        label="Report"
+        top_pad="5"
+        right="-1"
+        enabled="false"/>
+    </layout_panel>
+    <layout_panel
+      layout="topleft"
+      top="0"
+      left="1"
+      right="-1"
+      height="30"
+      min_height="30"
+      follows="all"
+      name="button_panel"
+      visible="true">
+      <check_box
+        top="0"
+        follows="top|left"
+        height="26"
+        label="Notify All Events   Days"
+        width="140"
+        name="notify_all" />
+
+      <spinner
+        top="5"
+        control_name="LogDays"
+        decimal_digits="0"
+        follows="left|top"
+        height="23"
+        increment="1"
+        initial_value="7"
+        label_width="30"
+        layout="topleft"
+        left_pad="5"
+        max_val="14"
+        min_val="0"
+        name="logsizespinner"
+        width="40" />
+      <button
+        top="5"
+        left="280"
+        layout="topleft"
+        follows="top|left"
+        name="btn_clear"
+        label="Clear"
+        width="80"
+        enabled="false"/>
+      <button
+        top="5"
+        layout="topleft"
+        left_pad="5"
+        width="20"
+        follows="top|left"
+        name="btn_prev"
+        label="&lt;"
+        enabled="false"/>
+      <button
+        top="5"
+        layout="topleft"
+        follows="top|left"
+        name="btn_next"
+        label="&gt;"
+        left_pad="5"
+        width="20"
+        enabled="false"/>
+    </layout_panel>
+  </layout_stack>
+</panel>
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index dc9e96e2f2..37aaf941af 100755
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -3992,7 +3992,15 @@ Try enclosing path to the editor with double quotes.
   <string name="ExperiencePermission10">control your camera</string>
   <string name="ExperiencePermission11">teleport you</string>
   <string name="ExperiencePermission12">automatically accept experience permissions</string>
-  <string name="ExperiencePermissionUnknown">perform an unknown operation: [Permission]</string>
+  <string name="ExperiencePermissionShortUnknown">perform an unknown operation: [Permission]</string>
+  <string name="ExperiencePermissionShort1">Take Controls</string>
+  <string name="ExperiencePermissionShort3">Override Animations</string>
+  <string name="ExperiencePermissionShort4">Attach</string>
+  <string name="ExperiencePermissionShort9">Track Camera</string>
+  <string name="ExperiencePermissionShort10">Control Camera</string>
+  <string name="ExperiencePermissionShort11">Teleport</string>
+  <string name="ExperiencePermissionShort12">Permission</string>
+  <string name="ExperiencePermissionShortUnknown">Unknown: [Permission]</string>
   
   <!-- Conversation log messages -->
   <string name="logging_calls_disabled_log_empty">
-- 
GitLab