From 2d113743f2943a1ab614a41d29cef7e13bc4e878 Mon Sep 17 00:00:00 2001
From: Mnikolenko Productengine <mnikolenko@productengine.com>
Date: Thu, 14 Jan 2021 18:02:09 +0200
Subject: [PATCH] SL-14504 FIXED Classified links always launch your Profile
 Classifieds

---
 indra/newview/CMakeLists.txt                  |   2 +
 indra/newview/llpanelclassified.cpp           | 564 ++++++++++++++++++
 indra/newview/llpanelclassified.h             | 175 ++++++
 indra/newview/llpanelprofile.cpp              |   1 +
 indra/newview/llpanelprofileclassifieds.cpp   |  62 +-
 .../skins/default/xui/en/floater_picks.xml    |  21 +
 .../default/xui/en/panel_classified_info.xml  |  37 +-
 7 files changed, 824 insertions(+), 38 deletions(-)
 create mode 100644 indra/newview/llpanelclassified.cpp
 create mode 100644 indra/newview/llpanelclassified.h
 create mode 100644 indra/newview/skins/default/xui/en/floater_picks.xml

diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index e47a04a1f94..1efa4e6c238 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -433,6 +433,7 @@ set(viewer_SOURCE_FILES
     llpanelavatar.cpp
     llpanelavatartag.cpp
     llpanelblockedlist.cpp
+    llpanelclassified.cpp
     llpanelcontents.cpp
     llpaneleditsky.cpp
     llpaneleditwater.cpp
@@ -1057,6 +1058,7 @@ set(viewer_HEADER_FILES
     llpanelavatar.h
     llpanelavatartag.h
     llpanelblockedlist.h
+    llpanelclassified.h
     llpanelcontents.h
     llpaneleditsky.h
     llpaneleditwater.h
diff --git a/indra/newview/llpanelclassified.cpp b/indra/newview/llpanelclassified.cpp
new file mode 100644
index 00000000000..183000ceac2
--- /dev/null
+++ b/indra/newview/llpanelclassified.cpp
@@ -0,0 +1,564 @@
+/** 
+ * @file llpanelclassified.cpp
+ * @brief LLPanelClassifiedInfo class implementation
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2021, 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$
+ */
+
+// Display of a classified used both for the global view in the
+// Find directory, and also for each individual user's classified in their
+// profile.
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llpanelclassified.h"
+
+#include "lldispatcher.h"
+#include "llfloaterreg.h"
+#include "llparcel.h"
+
+#include "llagent.h"
+#include "llclassifiedflags.h"
+#include "lliconctrl.h"
+#include "lltexturectrl.h"
+#include "llfloaterworldmap.h"
+#include "llviewergenericmessage.h"	// send_generic_message
+#include "llviewerregion.h"
+#include "llscrollcontainer.h"
+#include "llcorehttputil.h"
+
+//static
+LLPanelClassifiedInfo::panel_list_t LLPanelClassifiedInfo::sAllPanels;
+static LLPanelInjector<LLPanelClassifiedInfo> t_panel_panel_classified_info("panel_classified_info");
+
+// "classifiedclickthrough"
+// strings[0] = classified_id
+// strings[1] = teleport_clicks
+// strings[2] = map_clicks
+// strings[3] = profile_clicks
+class LLDispatchClassifiedClickThrough : public LLDispatchHandler
+{
+public:
+	virtual bool operator()(
+		const LLDispatcher* dispatcher,
+		const std::string& key,
+		const LLUUID& invoice,
+		const sparam_t& strings)
+	{
+		if (strings.size() != 4) return false;
+		LLUUID classified_id(strings[0]);
+		S32 teleport_clicks = atoi(strings[1].c_str());
+		S32 map_clicks = atoi(strings[2].c_str());
+		S32 profile_clicks = atoi(strings[3].c_str());
+
+		LLPanelClassifiedInfo::setClickThrough(
+			classified_id, teleport_clicks, map_clicks, profile_clicks, false);
+
+		return true;
+	}
+};
+static LLDispatchClassifiedClickThrough sClassifiedClickThrough;
+
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+LLPanelClassifiedInfo::LLPanelClassifiedInfo()
+ : LLPanel()
+ , mInfoLoaded(false)
+ , mScrollingPanel(NULL)
+ , mScrollContainer(NULL)
+ , mScrollingPanelMinHeight(0)
+ , mScrollingPanelWidth(0)
+ , mSnapshotStreched(false)
+ , mTeleportClicksOld(0)
+ , mMapClicksOld(0)
+ , mProfileClicksOld(0)
+ , mTeleportClicksNew(0)
+ , mMapClicksNew(0)
+ , mProfileClicksNew(0)
+ , mSnapshotCtrl(NULL)
+{
+	sAllPanels.push_back(this);
+}
+
+LLPanelClassifiedInfo::~LLPanelClassifiedInfo()
+{
+	sAllPanels.remove(this);
+}
+
+BOOL LLPanelClassifiedInfo::postBuild()
+{
+	childSetAction("show_on_map_btn", boost::bind(&LLPanelClassifiedInfo::onMapClick, this));
+	childSetAction("teleport_btn", boost::bind(&LLPanelClassifiedInfo::onTeleportClick, this));
+
+	mScrollingPanel = getChild<LLPanel>("scroll_content_panel");
+	mScrollContainer = getChild<LLScrollContainer>("profile_scroll");
+
+	mScrollingPanelMinHeight = mScrollContainer->getScrolledViewRect().getHeight();
+	mScrollingPanelWidth = mScrollingPanel->getRect().getWidth();
+
+	mSnapshotCtrl = getChild<LLTextureCtrl>("classified_snapshot");
+	mSnapshotRect = getDefaultSnapshotRect();
+
+	return TRUE;
+}
+
+void LLPanelClassifiedInfo::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */)
+{
+	LLPanel::reshape(width, height, called_from_parent);
+
+	if (!mScrollContainer || !mScrollingPanel)
+		return;
+
+	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
+
+	S32 scroll_height = mScrollContainer->getRect().getHeight();
+	if (mScrollingPanelMinHeight >= scroll_height)
+	{
+		mScrollingPanel->reshape(mScrollingPanelWidth, mScrollingPanelMinHeight);
+	}
+	else
+	{
+		mScrollingPanel->reshape(mScrollingPanelWidth + scrollbar_size, scroll_height);
+	}
+
+	mSnapshotRect = getDefaultSnapshotRect();
+	stretchSnapshot();
+}
+
+void LLPanelClassifiedInfo::onOpen(const LLSD& key)
+{
+	LLUUID avatar_id = key["classified_creator_id"];
+	if(avatar_id.isNull())
+	{
+		return;
+	}
+
+	if(getAvatarId().notNull())
+	{
+		LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this);
+	}
+
+	setAvatarId(avatar_id);
+
+	resetData();
+	resetControls();
+	scrollToTop();
+
+	setClassifiedId(key["classified_id"]);
+	setClassifiedName(key["classified_name"]);
+	setDescription(key["classified_desc"]);
+	setSnapshotId(key["classified_snapshot_id"]);
+	setFromSearch(key["from_search"]);
+
+	LL_INFOS() << "Opening classified [" << getClassifiedName() << "] (" << getClassifiedId() << ")" << LL_ENDL;
+
+	LLAvatarPropertiesProcessor::getInstance()->addObserver(getAvatarId(), this);
+	LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId());
+	gGenericDispatcher.addHandler("classifiedclickthrough", &sClassifiedClickThrough);
+
+	if (gAgent.getRegion())
+	{
+		// While we're at it let's get the stats from the new table if that
+		// capability exists.
+		std::string url = gAgent.getRegion()->getCapability("SearchStatRequest");
+		if (!url.empty())
+		{
+			LL_INFOS() << "Classified stat request via capability" << LL_ENDL;
+			LLSD body;
+			LLUUID classifiedId = getClassifiedId();
+			body["classified_id"] = classifiedId;
+			LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, body,
+				boost::bind(&LLPanelClassifiedInfo::handleSearchStatResponse, classifiedId, _1));
+		}
+	}
+	// Update classified click stats.
+	// *TODO: Should we do this when opening not from search?
+	sendClickMessage("profile");
+
+	setInfoLoaded(false);
+}
+
+/*static*/
+void LLPanelClassifiedInfo::handleSearchStatResponse(LLUUID classifiedId, LLSD result)
+{
+    S32 teleport = result["teleport_clicks"].asInteger();
+    S32 map = result["map_clicks"].asInteger();
+    S32 profile = result["profile_clicks"].asInteger();
+    S32 search_teleport = result["search_teleport_clicks"].asInteger();
+    S32 search_map = result["search_map_clicks"].asInteger();
+    S32 search_profile = result["search_profile_clicks"].asInteger();
+
+    LLPanelClassifiedInfo::setClickThrough(classifiedId,
+        teleport + search_teleport,
+        map + search_map,
+        profile + search_profile,
+        true);
+}
+
+void LLPanelClassifiedInfo::processProperties(void* data, EAvatarProcessorType type)
+{
+	if(APT_CLASSIFIED_INFO == type)
+	{
+		LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(data);
+		if(c_info && getClassifiedId() == c_info->classified_id)
+		{
+			setClassifiedName(c_info->name);
+			setDescription(c_info->description);
+			setSnapshotId(c_info->snapshot_id);
+			setParcelId(c_info->parcel_id);
+			setPosGlobal(c_info->pos_global);
+			setSimName(c_info->sim_name);
+
+			setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global));
+			getChild<LLUICtrl>("category")->setValue(LLClassifiedInfo::sCategories[c_info->category]);
+
+			static std::string mature_str = getString("type_mature");
+			static std::string pg_str = getString("type_pg");
+			static LLUIString  price_str = getString("l$_price");
+			static std::string date_fmt = getString("date_fmt");
+
+			bool mature = is_cf_mature(c_info->flags);
+			getChild<LLUICtrl>("content_type")->setValue(mature ? mature_str : pg_str);
+			getChild<LLIconCtrl>("content_type_moderate")->setVisible(mature);
+			getChild<LLIconCtrl>("content_type_general")->setVisible(!mature);
+
+			std::string auto_renew_str = is_cf_auto_renew(c_info->flags) ? 
+				getString("auto_renew_on") : getString("auto_renew_off");
+			getChild<LLUICtrl>("auto_renew")->setValue(auto_renew_str);
+
+			price_str.setArg("[PRICE]", llformat("%d", c_info->price_for_listing));
+			getChild<LLUICtrl>("price_for_listing")->setValue(LLSD(price_str));
+
+			std::string date_str = date_fmt;
+			LLStringUtil::format(date_str, LLSD().with("datetime", (S32) c_info->creation_date));
+			getChild<LLUICtrl>("creation_date")->setValue(date_str);
+
+			setInfoLoaded(true);
+
+			LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this);
+		}
+	}
+}
+
+void LLPanelClassifiedInfo::resetData()
+{
+	setClassifiedName(LLStringUtil::null);
+	setDescription(LLStringUtil::null);
+	setClassifiedLocation(LLStringUtil::null);
+	setClassifiedId(LLUUID::null);
+	setSnapshotId(LLUUID::null);
+	setPosGlobal(LLVector3d::zero);
+	setParcelId(LLUUID::null);
+	setSimName(LLStringUtil::null);
+	setFromSearch(false);
+
+	// reset click stats
+	mTeleportClicksOld	= 0;
+	mMapClicksOld		= 0;
+	mProfileClicksOld	= 0;
+	mTeleportClicksNew	= 0;
+	mMapClicksNew		= 0;
+	mProfileClicksNew	= 0;
+
+	getChild<LLUICtrl>("category")->setValue(LLStringUtil::null);
+	getChild<LLUICtrl>("content_type")->setValue(LLStringUtil::null);
+	getChild<LLUICtrl>("click_through_text")->setValue(LLStringUtil::null);
+	getChild<LLUICtrl>("price_for_listing")->setValue(LLStringUtil::null);
+	getChild<LLUICtrl>("auto_renew")->setValue(LLStringUtil::null);
+	getChild<LLUICtrl>("creation_date")->setValue(LLStringUtil::null);
+	getChild<LLUICtrl>("click_through_text")->setValue(LLStringUtil::null);
+	getChild<LLIconCtrl>("content_type_moderate")->setVisible(FALSE);
+	getChild<LLIconCtrl>("content_type_general")->setVisible(FALSE);
+}
+
+void LLPanelClassifiedInfo::resetControls()
+{
+	bool is_self = getAvatarId() == gAgent.getID();
+
+	getChildView("edit_btn")->setEnabled(is_self);
+	getChildView("edit_btn")->setVisible( is_self);
+	getChildView("price_layout_panel")->setVisible( is_self);
+	getChildView("clickthrough_layout_panel")->setVisible( is_self);
+}
+
+void LLPanelClassifiedInfo::setClassifiedName(const std::string& name)
+{
+	getChild<LLUICtrl>("classified_name")->setValue(name);
+}
+
+std::string LLPanelClassifiedInfo::getClassifiedName()
+{
+	return getChild<LLUICtrl>("classified_name")->getValue().asString();
+}
+
+void LLPanelClassifiedInfo::setDescription(const std::string& desc)
+{
+	getChild<LLUICtrl>("classified_desc")->setValue(desc);
+}
+
+std::string LLPanelClassifiedInfo::getDescription()
+{
+	return getChild<LLUICtrl>("classified_desc")->getValue().asString();
+}
+
+void LLPanelClassifiedInfo::setClassifiedLocation(const std::string& location)
+{
+	getChild<LLUICtrl>("classified_location")->setValue(location);
+}
+
+std::string LLPanelClassifiedInfo::getClassifiedLocation()
+{
+	return getChild<LLUICtrl>("classified_location")->getValue().asString();
+}
+
+void LLPanelClassifiedInfo::setSnapshotId(const LLUUID& id)
+{
+	mSnapshotCtrl->setValue(id);
+	mSnapshotStreched = false;
+}
+
+void LLPanelClassifiedInfo::draw()
+{
+	LLPanel::draw();
+
+	// Stretch in draw because it takes some time to load a texture,
+	// going to try to stretch snapshot until texture is loaded
+	if(!mSnapshotStreched)
+	{
+		stretchSnapshot();
+	}
+}
+
+LLUUID LLPanelClassifiedInfo::getSnapshotId()
+{
+	return getChild<LLUICtrl>("classified_snapshot")->getValue().asUUID();
+}
+
+// static
+void LLPanelClassifiedInfo::setClickThrough(
+	const LLUUID& classified_id,
+	S32 teleport,
+	S32 map,
+	S32 profile,
+	bool from_new_table)
+{
+	LL_INFOS() << "Click-through data for classified " << classified_id << " arrived: ["
+			<< teleport << ", " << map << ", " << profile << "] ("
+			<< (from_new_table ? "new" : "old") << ")" << LL_ENDL;
+
+	for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter)
+	{
+		LLPanelClassifiedInfo* self = *iter;
+		if (self->getClassifiedId() != classified_id)
+		{
+			continue;
+		}
+
+		// *HACK: Skip LLPanelClassifiedEdit instances: they don't display clicks data.
+		// Those instances should not be in the list at all.
+		if (typeid(*self) != typeid(LLPanelClassifiedInfo))
+		{
+			continue;
+		}
+
+		LL_INFOS() << "Updating classified info panel" << LL_ENDL;
+
+		// We need to check to see if the data came from the new stat_table 
+		// or the old classified table. We also need to cache the data from 
+		// the two separate sources so as to display the aggregate totals.
+
+		if (from_new_table)
+		{
+			self->mTeleportClicksNew = teleport;
+			self->mMapClicksNew = map;
+			self->mProfileClicksNew = profile;
+		}
+		else
+		{
+			self->mTeleportClicksOld = teleport;
+			self->mMapClicksOld = map;
+			self->mProfileClicksOld = profile;
+		}
+
+		static LLUIString ct_str = self->getString("click_through_text_fmt");
+
+		ct_str.setArg("[TELEPORT]",	llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld));
+		ct_str.setArg("[MAP]",		llformat("%d", self->mMapClicksNew + self->mMapClicksOld));
+		ct_str.setArg("[PROFILE]",	llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld));
+
+		self->getChild<LLUICtrl>("click_through_text")->setValue(ct_str.getString());
+		// *HACK: remove this when there is enough room for click stats in the info panel
+		self->getChildView("click_through_text")->setToolTip(ct_str.getString());  
+
+		LL_INFOS() << "teleport: " << llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld)
+				<< ", map: "    << llformat("%d", self->mMapClicksNew + self->mMapClicksOld)
+				<< ", profile: " << llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld)
+				<< LL_ENDL;
+	}
+}
+
+// static
+std::string LLPanelClassifiedInfo::createLocationText(
+	const std::string& original_name, 
+	const std::string& sim_name, 
+	const LLVector3d& pos_global)
+{
+	std::string location_text;
+	
+	location_text.append(original_name);
+
+	if (!sim_name.empty())
+	{
+		if (!location_text.empty()) 
+			location_text.append(", ");
+		location_text.append(sim_name);
+	}
+
+	if (!location_text.empty()) 
+		location_text.append(" ");
+
+	if (!pos_global.isNull())
+	{
+		S32 region_x = ll_round((F32)pos_global.mdV[VX]) % REGION_WIDTH_UNITS;
+		S32 region_y = ll_round((F32)pos_global.mdV[VY]) % REGION_WIDTH_UNITS;
+		S32 region_z = ll_round((F32)pos_global.mdV[VZ]);
+		location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z));
+	}
+
+	return location_text;
+}
+
+void LLPanelClassifiedInfo::stretchSnapshot()
+{
+	// *NOTE dzaporozhan
+	// Could be moved to LLTextureCtrl
+
+	LLViewerFetchedTexture* texture = mSnapshotCtrl->getTexture();
+
+	if(!texture)
+	{
+		return;
+	}
+
+	if(0 == texture->getOriginalWidth() || 0 == texture->getOriginalHeight())
+	{
+		// looks like texture is not loaded yet
+		return;
+	}
+
+	LLRect rc = mSnapshotRect;
+	// *HACK dzaporozhan
+	// LLTextureCtrl uses BTN_HEIGHT_SMALL as bottom for texture which causes
+	// drawn texture to be smaller than expected. (see LLTextureCtrl::draw())
+	// Lets increase texture height to force texture look as expected.
+	rc.mBottom -= BTN_HEIGHT_SMALL;
+
+	F32 t_width = texture->getFullWidth();
+	F32 t_height = texture->getFullHeight();
+
+	F32 ratio = llmin<F32>( (rc.getWidth() / t_width), (rc.getHeight() / t_height) );
+
+	t_width *= ratio;
+	t_height *= ratio;
+
+	rc.setCenterAndSize(rc.getCenterX(), rc.getCenterY(), llfloor(t_width), llfloor(t_height));
+	mSnapshotCtrl->setShape(rc);
+
+	mSnapshotStreched = true;
+}
+
+LLRect LLPanelClassifiedInfo::getDefaultSnapshotRect()
+{
+	// Using scroll container makes getting default rect a hard task
+	// because rect in postBuild() and in first reshape() is not the same.
+	// Using snapshot_panel makes it easier to reshape snapshot.
+	return getChild<LLUICtrl>("snapshot_panel")->getLocalRect();
+}
+
+void LLPanelClassifiedInfo::scrollToTop()
+{
+	LLScrollContainer* scrollContainer = findChild<LLScrollContainer>("profile_scroll");
+	if (scrollContainer)
+		scrollContainer->goToTop();
+}
+
+// static
+// *TODO: move out of the panel
+void LLPanelClassifiedInfo::sendClickMessage(
+		const std::string& type,
+		bool from_search,
+		const LLUUID& classified_id,
+		const LLUUID& parcel_id,
+		const LLVector3d& global_pos,
+		const std::string& sim_name)
+{
+	if (gAgent.getRegion())
+	{
+		// You're allowed to click on your own ads to reassure yourself
+		// that the system is working.
+		LLSD body;
+		body["type"]			= type;
+		body["from_search"]		= from_search;
+		body["classified_id"]	= classified_id;
+		body["parcel_id"]		= parcel_id;
+		body["dest_pos_global"]	= global_pos.getValue();
+		body["region_name"]		= sim_name;
+
+		std::string url = gAgent.getRegion()->getCapability("SearchStatTracking");
+		LL_INFOS() << "Sending click msg via capability (url=" << url << ")" << LL_ENDL;
+		LL_INFOS() << "body: [" << body << "]" << LL_ENDL;
+        LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body,
+            "SearchStatTracking Click report sent.", "SearchStatTracking Click report NOT sent.");
+	}
+}
+
+void LLPanelClassifiedInfo::sendClickMessage(const std::string& type)
+{
+	sendClickMessage(
+		type,
+		fromSearch(),
+		getClassifiedId(),
+		getParcelId(),
+		getPosGlobal(),
+		getSimName());
+}
+
+void LLPanelClassifiedInfo::onMapClick()
+{
+	sendClickMessage("map");
+	LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal());
+	LLFloaterReg::showInstance("world_map", "center");
+}
+
+void LLPanelClassifiedInfo::onTeleportClick()
+{
+	if (!getPosGlobal().isExactlyZero())
+	{
+		sendClickMessage("teleport");
+		gAgent.teleportViaLocation(getPosGlobal());
+		LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal());
+	}
+}
+
+//EOF
diff --git a/indra/newview/llpanelclassified.h b/indra/newview/llpanelclassified.h
new file mode 100644
index 00000000000..471becd0f7f
--- /dev/null
+++ b/indra/newview/llpanelclassified.h
@@ -0,0 +1,175 @@
+/** 
+ * @file llpanelclassified.h
+ * @brief LLPanelClassifiedInfo class definition
+ *
+ * $LicenseInfo:firstyear=2021&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2021, 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$
+ */
+
+// Display of a classified used both for the global view in the
+// Find directory, and also for each individual user's classified in their
+// profile.
+#ifndef LL_LLPANELCLASSIFIED_H
+#define LL_LLPANELCLASSIFIED_H
+
+#include "llavatarpropertiesprocessor.h"
+#include "llclassifiedinfo.h"
+#include "llfloater.h"
+#include "llpanel.h"
+#include "llrect.h"
+
+class LLScrollContainer;
+class LLTextureCtrl;
+
+class LLPanelClassifiedInfo : public LLPanel, public LLAvatarPropertiesObserver
+{
+	LOG_CLASS(LLPanelClassifiedInfo);
+public:
+
+	LLPanelClassifiedInfo();
+	virtual ~LLPanelClassifiedInfo();
+
+	/*virtual*/ void onOpen(const LLSD& key);
+
+	/*virtual*/ BOOL postBuild();
+
+	/*virtual*/ void processProperties(void* data, EAvatarProcessorType type);
+
+	void setAvatarId(const LLUUID& avatar_id) { mAvatarId = avatar_id; }
+
+	LLUUID& getAvatarId() { return mAvatarId; }
+
+	void setSnapshotId(const LLUUID& id);
+
+	LLUUID getSnapshotId();
+
+	void setClassifiedId(const LLUUID& id) { mClassifiedId = id; }
+
+	LLUUID& getClassifiedId() { return mClassifiedId; }
+
+	void setClassifiedName(const std::string& name);
+
+	std::string getClassifiedName();
+
+	void setDescription(const std::string& desc);
+
+	std::string getDescription();
+
+	void setClassifiedLocation(const std::string& location);
+
+	std::string getClassifiedLocation();
+
+	void setPosGlobal(const LLVector3d& pos) { mPosGlobal = pos; }
+
+	LLVector3d& getPosGlobal() { return mPosGlobal; }
+
+	void setParcelId(const LLUUID& id) { mParcelId = id; }
+
+	LLUUID getParcelId() { return mParcelId; }
+
+	void setSimName(const std::string& sim_name) { mSimName = sim_name; }
+
+	std::string getSimName() { return mSimName; }
+
+	void setFromSearch(bool val) { mFromSearch = val; }
+
+	bool fromSearch() { return mFromSearch; }
+
+	bool getInfoLoaded() { return mInfoLoaded; }
+
+	void setInfoLoaded(bool loaded) { mInfoLoaded = loaded; }
+
+	static void setClickThrough(
+		const LLUUID& classified_id,
+		S32 teleport,
+		S32 map,
+		S32 profile,
+		bool from_new_table);
+
+	static void sendClickMessage(
+			const std::string& type,
+			bool from_search,
+			const LLUUID& classified_id,
+			const LLUUID& parcel_id,
+			const LLVector3d& global_pos,
+			const std::string& sim_name);
+
+	/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
+
+	/*virtual*/ void draw();
+
+protected:
+
+	virtual void resetData();
+
+	virtual void resetControls();
+
+	static std::string createLocationText(
+		const std::string& original_name,
+		const std::string& sim_name, 
+		const LLVector3d& pos_global);
+
+	void stretchSnapshot();
+	void sendClickMessage(const std::string& type);
+
+	LLRect getDefaultSnapshotRect();
+
+	void scrollToTop();
+
+	void onMapClick();
+	void onTeleportClick();
+
+	bool mSnapshotStreched;
+	LLRect mSnapshotRect;
+	LLTextureCtrl* mSnapshotCtrl;
+
+private:
+
+	LLUUID mAvatarId;
+	LLUUID mClassifiedId;
+	LLVector3d mPosGlobal;
+	LLUUID mParcelId;
+	std::string mSimName;
+	bool mFromSearch;
+	bool mInfoLoaded;
+
+	LLScrollContainer*		mScrollContainer;
+	LLPanel*				mScrollingPanel;
+
+	S32 mScrollingPanelMinHeight;
+	S32 mScrollingPanelWidth;
+
+	// Needed for stat tracking
+	S32 mTeleportClicksOld;
+	S32 mMapClicksOld;
+	S32 mProfileClicksOld;
+	S32 mTeleportClicksNew;
+	S32 mMapClicksNew;
+	S32 mProfileClicksNew;
+
+    static void handleSearchStatResponse(LLUUID classifiedId, LLSD result);
+
+
+	typedef std::list<LLPanelClassifiedInfo*> panel_list_t;
+	static panel_list_t sAllPanels;
+};
+
+#endif // LL_LLPANELCLASSIFIED_H
diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp
index 2cd5aecc2e3..82515c8d4af 100644
--- a/indra/newview/llpanelprofile.cpp
+++ b/indra/newview/llpanelprofile.cpp
@@ -63,6 +63,7 @@
 #include "llpanelprofilepicks.h"
 #include "lltrans.h"
 #include "llviewercontrol.h"
+#include "llviewermenu.h" //is_agent_mappable
 #include "llvoiceclient.h"
 #include "llweb.h"
 
diff --git a/indra/newview/llpanelprofileclassifieds.cpp b/indra/newview/llpanelprofileclassifieds.cpp
index 1f638bdeb63..7cc9406e08f 100644
--- a/indra/newview/llpanelprofileclassifieds.cpp
+++ b/indra/newview/llpanelprofileclassifieds.cpp
@@ -37,6 +37,7 @@
 #include "llcorehttputil.h"
 #include "lldispatcher.h"
 #include "llfloaterreg.h"
+#include "llfloatersidepanelcontainer.h"
 #include "llfloaterworldmap.h"
 #include "lliconctrl.h"
 #include "lllineeditor.h"
@@ -70,13 +71,16 @@ LLPanelProfileClassified::panel_list_t LLPanelProfileClassified::sAllPanels;
 static LLPanelInjector<LLPanelProfileClassifieds> t_panel_profile_classifieds("panel_profile_classifieds");
 static LLPanelInjector<LLPanelProfileClassified> t_panel_profile_classified("panel_profile_classified");
 
-class LLClassifiedHandler : public LLCommandHandler
+class LLClassifiedHandler : public LLCommandHandler, public LLAvatarPropertiesObserver
 {
 public:
     // throttle calls from untrusted browsers
     LLClassifiedHandler() : LLCommandHandler("classified", UNTRUSTED_THROTTLE) {}
-
-    bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web)
+	
+	std::set<LLUUID> mClassifiedIds;
+	std::string mRequestVerb;
+    
+	bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web)
     {
         if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableClassifieds"))
         {
@@ -109,7 +113,10 @@ class LLClassifiedHandler : public LLCommandHandler
         const std::string verb = params[1].asString();
         if (verb == "about")
         {
-            LLAvatarActions::showClassified(gAgent.getID(), classified_id, false);
+            mRequestVerb = verb;
+            mClassifiedIds.insert(classified_id);
+            LLAvatarPropertiesProcessor::getInstance()->addObserver(LLUUID(), this);
+            LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(classified_id);
             return true;
         }
         else if (verb == "edit")
@@ -120,6 +127,53 @@ class LLClassifiedHandler : public LLCommandHandler
 
         return false;
     }
+
+    void openClassified(LLAvatarClassifiedInfo* c_info)
+    {
+        if (mRequestVerb == "about")
+        {
+            if (c_info->creator_id == gAgent.getID())
+            {
+                LLAvatarActions::showClassified(gAgent.getID(), c_info->classified_id, false);
+            }
+            else
+            {
+                LLSD params;
+                params["id"] = c_info->creator_id;
+                params["open_tab_name"] = "panel_picks";
+                params["show_tab_panel"] = "classified_details";
+                params["classified_id"] = c_info->classified_id;
+                params["classified_creator_id"] = c_info->creator_id;
+                params["classified_snapshot_id"] = c_info->snapshot_id;
+                params["classified_name"] = c_info->name;
+                params["classified_desc"] = c_info->description;
+                params["from_search"] = true;
+                LLFloaterSidePanelContainer::showPanel("picks", params);
+            }
+        }
+    }
+
+    void processProperties(void* data, EAvatarProcessorType type)
+    {
+        if (APT_CLASSIFIED_INFO != type)
+        {
+            return;
+        }
+
+        // is this the classified that we asked for?
+        LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(data);
+        if (!c_info || mClassifiedIds.find(c_info->classified_id) == mClassifiedIds.end())
+        {
+            return;
+        }
+
+        // open the detail side tray for this classified
+        openClassified(c_info);
+
+        // remove our observer now that we're done
+        mClassifiedIds.erase(c_info->classified_id);
+        LLAvatarPropertiesProcessor::getInstance()->removeObserver(LLUUID(), this);
+    }
 };
 LLClassifiedHandler gClassifiedHandler;
 
diff --git a/indra/newview/skins/default/xui/en/floater_picks.xml b/indra/newview/skins/default/xui/en/floater_picks.xml
new file mode 100644
index 00000000000..3cd6abafe54
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_picks.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<floater
+ positioning="cascading"
+ can_close="true"
+ can_resize="false"
+ height="572"
+ help_topic="sidebar_me"
+ min_width="333"
+ min_height="440"
+ name="floater_picks"
+ save_rect="true"
+ save_visibility="false"
+ reuse_instance="true"
+ title="Picks"
+ width="333" >
+   <panel
+    class="panel_classified_info"
+    name="main_panel"
+    filename="panel_classified_info.xml"
+    follows="all"/>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/panel_classified_info.xml b/indra/newview/skins/default/xui/en/panel_classified_info.xml
index d4a2745d1d6..04a0bc800d8 100644
--- a/indra/newview/skins/default/xui/en/panel_classified_info.xml
+++ b/indra/newview/skins/default/xui/en/panel_classified_info.xml
@@ -38,28 +38,15 @@
   name="auto_renew_off">
     Disabled
  </panel.string>
-    <button
-     follows="top|left"
-     height="24"
-     image_hover_unselected="BackButton_Over"
-     image_pressed="BackButton_Press"
-     image_unselected="BackButton_Off"
-     layout="topleft"
-     name="back_btn"
-     left="10"
-     tab_stop="false"
-     top="2"
-     width="30"
-     use_draw_context_alpha="false" />
     <text
      follows="top|left|right"
      font="SansSerifHugeBold"
      height="26"
      layout="topleft"
-     left_pad="4"
+     left="12"
      name="title"
      text_color="LtGray"
-     top="0"
+     top="2"
      value="Classified Info"
      use_ellipses="true"
      width="275" />
@@ -420,7 +407,7 @@
 		         height="23"
 		         label="Teleport"
 		         layout="topleft"
-		         left="0"
+		         left="2"
 		         name="teleport_btn"
 		         top="0"
 		         width="101" />	
@@ -443,24 +430,6 @@
 		         top="0"
 		         width="100" />
 		  </layout_panel>	  
-		  
-		  <layout_panel
-			  follows="bottom|left|right"
-			  height="23"
-			  layout="bottomleft"
-			  left_pad="3"
-			  name="edit_btn_lp"
-		      auto_resize="true"
-			  width="101">
-			  <button
-		         follows="bottom|left|right"
-		         height="23"
-		         label="Edit"
-		         layout="topleft"
-		         name="edit_btn"
-		         top="0"
-		         width="101" />
-		  </layout_panel>
 	   </layout_stack>
     </panel>
 </panel>
-- 
GitLab