diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 7577ff8d24..6cbe3646ba 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -135,6 +135,7 @@ set(viewer_SOURCE_FILES
fsfloaterplacedetails.cpp
fsfloaterposestand.cpp
fsfloaterprofile.cpp
+ fsfloaterradar.cpp
fsfloatersearch.cpp
fsfloaterteleporthistory.cpp
fsfloatervoicecontrols.cpp
@@ -157,7 +158,9 @@ set(viewer_SOURCE_FILES
fspanelprofile.cpp
fspanelprofileclassifieds.cpp
fspose.cpp
+ fsradar.cpp
fsradarlistctrl.cpp
+ fsradarmenu.cpp
fsslurlcommand.cpp
fswsassetblacklist.cpp
groupchatlistener.cpp
@@ -796,6 +799,7 @@ set(viewer_HEADER_FILES
fsfloaterplacedetails.h
fsfloaterposestand.h
fsfloaterprofile.h
+ fsfloaterradar.h
fsfloatersearch.h
fsfloaterteleporthistory.h
fsfloatervoicecontrols.h
@@ -819,7 +823,9 @@ set(viewer_HEADER_FILES
fspanelprofile.h
fspanelprofileclassifieds.h
fspose.h
+ fsradar.h
fsradarlistctrl.h
+ fsradarmenu.h
fsslurl.h
fsslurlcommand.h
fswsassetblacklist.h
diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml
index 2cd58de7bb..ffb2fb4d19 100644
--- a/indra/newview/app_settings/commands.xml
+++ b/indra/newview/app_settings/commands.xml
@@ -465,4 +465,14 @@
tooltip_ref="Command_Snapshot_To_Disk_Tooltip"
execute_function="File.TakeSnapshotToDisk"
/>
+
diff --git a/indra/newview/chatbar_as_cmdline.cpp b/indra/newview/chatbar_as_cmdline.cpp
index 1ac5d8c914..4b67d96b28 100644
--- a/indra/newview/chatbar_as_cmdline.cpp
+++ b/indra/newview/chatbar_as_cmdline.cpp
@@ -35,6 +35,7 @@
#include "aoengine.h"
#include "fscommon.h"
+#include "fsradar.h"
#include "llagent.h"
#include "llagentcamera.h"
#include "llavatarlist.h"
@@ -47,7 +48,6 @@
#include "llfloatersidepanelcontainer.h"
#include "llinventorymodel.h"
#include "llnotificationmanager.h"
-#include "llpanelpeople.h"
#include "llparcel.h"
#include "lltooldraganddrop.h"
#include "lltrans.h"
@@ -1169,11 +1169,11 @@ LLUUID cmdline_partial_name2key(std::string partial_name)
std::string av_name;
LLStringUtil::toLower(partial_name);
- LLPanelPeople* panel_people = getPeoplePanel();
- if (panel_people)
+ FSRadar* radar = FSRadar::getInstance();
+ if (radar)
{
std::vector items;
- LLAvatarList* nearbyList = panel_people->getNearbyList();
+ LLAvatarList* nearbyList = radar->getNearbyList();
nearbyList->getItems(items);
for (std::vector::const_iterator itItem = items.begin(); itItem != items.end(); ++itItem)
@@ -1195,10 +1195,10 @@ LLUUID cmdline_partial_name2key(std::string partial_name)
void cmdline_tp2name(std::string target)
{
LLUUID avkey = cmdline_partial_name2key(target);
- LLPanelPeople* panel_people = getPeoplePanel();
- if (avkey.notNull() && panel_people)
+ FSRadar* radar = FSRadar::getInstance();
+ if (avkey.notNull() && radar)
{
- LLAvatarListItem* avatar_list_item = panel_people->getNearbyList()->getAvatarListItem(avkey);
+ LLAvatarListItem* avatar_list_item = radar->getNearbyList()->getAvatarListItem(avkey);
if (avatar_list_item)
{
LLVector3d pos = avatar_list_item->getPosition();
diff --git a/indra/newview/fsfloaterradar.cpp b/indra/newview/fsfloaterradar.cpp
new file mode 100644
index 0000000000..c5d2138afd
--- /dev/null
+++ b/indra/newview/fsfloaterradar.cpp
@@ -0,0 +1,473 @@
+/**
+ * @file fsfloaterradar.cpp
+ * @brief Firestorm radar floater implementation
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Copyright (c) 2013 Ansariel Hiller @ Second Life
+ *
+ * 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
+ *
+ * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
+ * http://www.firestormviewer.org
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "fsfloaterradar.h"
+
+// libs
+#include "llfloaterreg.h"
+#include "lllayoutstack.h"
+#include "llmenugl.h"
+#include "llnotificationsutil.h"
+#include "lleventtimer.h"
+#include "llfiltereditor.h"
+#include "lluictrlfactory.h"
+#include "llmenubutton.h"
+#include "lltoggleablemenu.h"
+
+// newview
+#include "fsradarmenu.h"
+#include "llavataractions.h"
+#include "llfloatersidepanelcontainer.h"
+#include "llnetmap.h"
+#include "llviewercontrol.h" // for gSavedSettings
+#include "llviewermenu.h" // for gMenuHolder
+#include "llvoiceclient.h"
+#include "rlvhandler.h"
+
+
+/**
+ * Update buttons on changes in our friend relations (STORM-557).
+ */
+class FSButtonsUpdater : public FSRadar::Updater, public LLFriendObserver
+{
+public:
+ FSButtonsUpdater(callback_t cb)
+ : FSRadar::Updater(cb)
+ {
+ LLAvatarTracker::instance().addObserver(this);
+ }
+
+ ~FSButtonsUpdater()
+ {
+ LLAvatarTracker::instance().removeObserver(this);
+ }
+
+ /*virtual*/ void changed(U32 mask)
+ {
+ (void) mask;
+ update();
+ }
+};
+
+
+//=============================================================================
+
+FSFloaterRadar::FSFloaterRadar(const LLSD& key)
+ : LLFloater(key),
+ mFilterSubString(LLStringUtil::null),
+ mFilterSubStringOrig(LLStringUtil::null),
+ mFilterEditor(NULL),
+ mRadarGearButton(NULL),
+ mMiniMap(NULL),
+ mRadarList(NULL)
+{
+ mButtonsUpdater = new FSButtonsUpdater(boost::bind(&FSFloaterRadar::updateButtons, this));
+ mCommitCallbackRegistrar.add("People.addFriend", boost::bind(&FSFloaterRadar::onAddFriendButtonClicked, this));
+}
+
+FSFloaterRadar::~FSFloaterRadar()
+{
+ mUpdateSignalConnection.disconnect();
+ delete mButtonsUpdater;
+
+ if(LLVoiceClient::instanceExists())
+ {
+ LLVoiceClient::getInstance()->removeObserver(this);
+ }
+
+ if (mRadarGearMenuHandle.get()) mRadarGearMenuHandle.get()->die();
+}
+
+BOOL FSFloaterRadar::postBuild()
+{
+ mFilterEditor = getChild("filter_input");
+ mFilterEditor->setCommitCallback(boost::bind(&FSFloaterRadar::onFilterEdit, this, _2));
+
+ // AO: radarlist takes over for nearbylist for presentation.
+ mRadarList = getChild("radar_list");
+ mRadarList->sortByColumn("range", TRUE); // sort by range
+ mRadarList->setFilterColumn(0);
+ mRadarList->setContextMenu(&FSFloaterRadarMenu::gFSRadarMenu);
+ mRadarList->setDoubleClickCallback(boost::bind(&FSFloaterRadar::onRadarListDoubleClicked, this));
+ mRadarList->setCommitCallback(boost::bind(&FSFloaterRadar::onRadarListCommitted, this));
+
+ mMiniMap = getChild("Net Map");
+
+ buttonSetAction("view_profile_btn", boost::bind(&FSFloaterRadar::onViewProfileButtonClicked, this));
+ buttonSetAction("im_btn", boost::bind(&FSFloaterRadar::onImButtonClicked, this));
+ buttonSetAction("call_btn", boost::bind(&FSFloaterRadar::onCallButtonClicked, this));
+ buttonSetAction("teleport_btn", boost::bind(&FSFloaterRadar::onTeleportButtonClicked, this));
+ buttonSetAction("share_btn", boost::bind(&FSFloaterRadar::onShareButtonClicked, this));
+
+ // Create menus.
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+ LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
+
+ registrar.add("Radar.Gear.Action", boost::bind(&FSFloaterRadar::onGearMenuItemClicked, this, _2));
+ registrar.add("Radar.NameFmt", boost::bind(&FSRadar::onRadarNameFmtClicked, _2));
+ registrar.add("Radar.ReportTo", boost::bind(&FSRadar::onRadarReportToClicked, _2));
+
+ enable_registrar.add("Radar.NameFmtCheck", boost::bind(&FSRadar::radarNameFmtCheck, _2));
+ enable_registrar.add("Radar.ReportToCheck", boost::bind(&FSRadar::radarReportToCheck, _2));
+
+ mRadarGearButton = getChild("nearby_view_sort_btn");
+
+ LLToggleableMenu* radar_gear = LLUICtrlFactory::getInstance()->createFromFile("menu_fs_radar_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
+ if (radar_gear)
+ {
+ mRadarGearMenuHandle = radar_gear->getHandle();
+ mRadarGearButton->setMenu(radar_gear);
+ }
+
+ LLVoiceClient::getInstance()->addObserver(this);
+
+ // Register for radar updates
+ mUpdateSignalConnection = FSRadar::getInstance()->setUpdateCallback(boost::bind(&FSFloaterRadar::updateNearby, this, _1, _2));
+
+ // call this method in case some list is empty and buttons can be in inconsistent state
+ updateButtons();
+
+ return TRUE;
+}
+
+// virtual
+void FSFloaterRadar::onOpen(const LLSD& key)
+{
+ // Fill radar with most recent data so we don't have a blank window until next radar update
+ FSRadar* radar = FSRadar::getInstance();
+ if (radar)
+ {
+ std::vector entries;
+ LLSD stats;
+ radar->getCurrentData(entries, stats);
+ updateNearby(entries, stats);
+ }
+ LLFloater::onOpen(key);
+}
+
+// virtual
+void FSFloaterRadar::onChange(EStatusType status, const std::string &channelURI, bool proximal)
+{
+ if (status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL)
+ {
+ return;
+ }
+
+ updateButtons();
+}
+
+void FSFloaterRadar::buttonSetEnabled(const std::string& btn_name, bool enabled)
+{
+ // To make sure we're referencing the right widget (a child of the button bar).
+ LLButton* button = getChild("button_bar")->getChild(btn_name);
+ button->setEnabled(enabled);
+}
+
+void FSFloaterRadar::buttonSetAction(const std::string& btn_name, const commit_signal_t::slot_type& cb)
+{
+ // To make sure we're referencing the right widget (a child of the button bar).
+ LLButton* button = getChild("button_bar")->getChild(btn_name);
+ button->setClickedCallback(cb);
+}
+
+void FSFloaterRadar::updateButtons()
+{
+ LLUUID selected_id;
+
+ uuid_vec_t selected_uuids;
+ getCurrentItemIDs(selected_uuids);
+
+ bool item_selected = (selected_uuids.size() == 1);
+ bool multiple_selected = (selected_uuids.size() >= 1);
+
+ // Check whether selected avatar is our friend.
+ bool is_friend = true;
+ if (item_selected)
+ {
+ selected_id = selected_uuids.front();
+ is_friend = LLAvatarTracker::instance().getBuddyInfo(selected_id) != NULL;
+ }
+ getChildView("add_friend_btn_nearby")->setEnabled(!is_friend && !gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES));
+
+
+ bool enable_calls = LLVoiceClient::getInstance()->isVoiceWorking() && LLVoiceClient::getInstance()->voiceEnabled();
+
+// [RLVa:KB] - Checked: 2010-06-04 (RLVa-1.2.2a) | Modified: RLVa-1.2.0d
+ if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES))
+ {
+ item_selected = multiple_selected = false;
+ }
+// [/RLBa:KB]
+
+ buttonSetEnabled("view_profile_btn",item_selected);
+ buttonSetEnabled("share_btn", item_selected);
+ buttonSetEnabled("im_btn", multiple_selected); // allow starting the friends conference for multiple selection
+ buttonSetEnabled("call_btn", multiple_selected && enable_calls);
+ buttonSetEnabled("teleport_btn", multiple_selected /* && LLAvatarActions::canOfferTeleport(selected_uuids) */ ); // LO - Dont block the TP button at all.
+}
+
+LLUUID FSFloaterRadar::getCurrentItemID() const
+{
+ LLScrollListItem* item = mRadarList->getFirstSelected();
+ if (item)
+ {
+ return item->getColumn(mRadarList->getColumn("uuid")->mIndex)->getValue().asUUID();
+ }
+
+ return LLUUID::null;
+}
+
+void FSFloaterRadar::getCurrentItemIDs(uuid_vec_t& selected_uuids) const
+{
+ for (size_t i = 0; i < mRadarList->getAllSelected().size(); ++i)
+ {
+ selected_uuids.push_back(mRadarList->getAllSelected().at(i)->getColumn(mRadarList->getColumn("uuid")->mIndex)->getValue().asUUID());
+ }
+}
+
+void FSFloaterRadar::onFilterEdit(const std::string& search_string)
+{
+ mFilterSubStringOrig = search_string;
+ LLStringUtil::trimHead(mFilterSubStringOrig);
+ // Searches are case-insensitive
+ std::string search_upper = mFilterSubStringOrig;
+ LLStringUtil::toUpper(search_upper);
+
+ if (mFilterSubString == search_upper)
+ {
+ return;
+ }
+
+ mFilterSubString = search_upper;
+
+ // Apply new filter.
+ mRadarList->setFilterString(mFilterSubStringOrig);
+}
+
+void FSFloaterRadar::onRadarListDoubleClicked()
+{
+ LLScrollListItem* item = mRadarList->getFirstSelected();
+ if (!item)
+ {
+ return;
+ }
+
+ LLUUID clicked_id = item->getColumn(mRadarList->getColumn("uuid")->mIndex)->getValue().asUUID();
+ std::string name = item->getColumn(mRadarList->getColumn("name")->mIndex)->getValue().asString();
+
+ FSRadar* radar = FSRadar::getInstance();
+ if (radar)
+ {
+ radar->zoomAvatar(clicked_id, name);
+ }
+}
+
+void FSFloaterRadar::onRadarListCommitted()
+{
+ uuid_vec_t selected_uuids;
+ LLUUID sVal = mRadarList->getSelectedValue().asUUID();
+ if (sVal.notNull())
+ {
+ selected_uuids.push_back(sVal);
+ mMiniMap->setSelected(selected_uuids);
+ }
+
+ updateButtons();
+}
+
+void FSFloaterRadar::onViewProfileButtonClicked()
+{
+ LLUUID id = getCurrentItemID();
+ LLAvatarActions::showProfile(id);
+}
+
+void FSFloaterRadar::onAddFriendButtonClicked()
+{
+ LLUUID id = getCurrentItemID();
+ if (id.notNull())
+ {
+ LLAvatarActions::requestFriendshipDialog(id);
+ }
+}
+
+void FSFloaterRadar::onImButtonClicked()
+{
+ uuid_vec_t selected_uuids;
+ getCurrentItemIDs(selected_uuids);
+ if ( selected_uuids.size() == 1 )
+ {
+ // if selected only one person then start up IM
+ LLAvatarActions::startIM(selected_uuids.at(0));
+ }
+ else if ( selected_uuids.size() > 1 )
+ {
+ // for multiple selection start up friends conference
+ LLAvatarActions::startConference(selected_uuids);
+ }
+}
+
+void FSFloaterRadar::onCallButtonClicked()
+{
+ uuid_vec_t selected_uuids;
+ getCurrentItemIDs(selected_uuids);
+
+ if (selected_uuids.size() == 1)
+ {
+ // initiate a P2P voice chat with the selected user
+ LLAvatarActions::startCall(getCurrentItemID());
+ }
+ else if (selected_uuids.size() > 1)
+ {
+ // initiate an ad-hoc voice chat with multiple users
+ LLAvatarActions::startAdhocCall(selected_uuids);
+ }
+}
+
+void FSFloaterRadar::onTeleportButtonClicked()
+{
+ uuid_vec_t selected_uuids;
+ getCurrentItemIDs(selected_uuids);
+ LLAvatarActions::offerTeleport(LLAvatarActions::canOfferTeleport(selected_uuids));
+}
+
+void FSFloaterRadar::onShareButtonClicked()
+{
+ LLAvatarActions::share(getCurrentItemID());
+}
+
+void FSFloaterRadar::onGearMenuItemClicked(const LLSD& userdata)
+{
+ std::string chosen_item = userdata.asString();
+
+ if (chosen_item == "panel_block_list_sidetray")
+ {
+ if (gSavedSettings.getBOOL("FSUseStandaloneBlocklistFloater"))
+ {
+ LLFloaterReg::showInstance("fs_blocklist", LLSD());
+ }
+ else
+ {
+ LLFloaterSidePanelContainer::showPanel("people", "panel_block_list_sidetray", LLSD());
+ }
+ }
+}
+
+void FSFloaterRadar::updateNearby(const std::vector& entries, const LLSD& stats)
+{
+ if (!getVisible())
+ {
+ return;
+ }
+
+ if (!mRadarList)
+ {
+ return;
+ }
+
+ // Store current selection and scroll position
+ static S32 uuidColumnIndex = mRadarList->getColumn("uuid")->mIndex;
+ std::vector selected_items = mRadarList->getAllSelected();
+ uuid_vec_t selected_ids;
+ for (size_t i = 0; i < selected_items.size(); i++)
+ {
+ selected_ids.push_back(selected_items.at(i)->getColumn(uuidColumnIndex)->getValue().asUUID());
+ }
+ S32 lastScroll = mRadarList->getScrollPos();
+
+ // Update list
+ mRadarList->clearRows();
+ const std::vector::const_iterator it_end = entries.end();
+ for (std::vector::const_iterator it = entries.begin(); it != it_end; ++it)
+ {
+ LLSD entry = (*it)["entry"];
+ LLSD options = (*it)["options"];
+
+ LLSD row_data;
+ row_data["value"] = entry["id"];
+ row_data["columns"][0]["column"] = "name";
+ row_data["columns"][0]["value"] = entry["name"];
+ row_data["columns"][1]["column"] = "voice_level";
+ row_data["columns"][1]["type"] = "icon";
+ row_data["columns"][1]["value"] = ""; // Need to set it after the row has been created because it's to big for the row
+ row_data["columns"][2]["column"] = "in_region";
+ row_data["columns"][2]["type"] = "icon";
+ row_data["columns"][2]["value"] = (entry["in_region"].asBoolean() ? "avatar_in_region" : "");
+ row_data["columns"][3]["column"] = "flags";
+ row_data["columns"][3]["value"] = entry["flags"];
+ row_data["columns"][4]["column"] = "age";
+ row_data["columns"][4]["value"] = entry["age"];
+ row_data["columns"][5]["column"] = "seen";
+ row_data["columns"][5]["value"] = entry["seen"];
+ row_data["columns"][6]["column"] = "range";
+ row_data["columns"][6]["value"] = entry["range"];
+ row_data["columns"][7]["column"] = "uuid"; // invisible column for referencing av-key the row belongs to
+ row_data["columns"][7]["value"] = entry["id"];
+
+ LLScrollListItem* row = mRadarList->addElement(row_data);
+
+ static S32 rangeColumnIndex = mRadarList->getColumn("range")->mIndex;
+ static S32 nameColumnIndex = mRadarList->getColumn("name")->mIndex;
+ static S32 voiceLevelColumnIndex = mRadarList->getColumn("voice_level")->mIndex;
+
+ LLScrollListText* radarRangeCell = (LLScrollListText*)row->getColumn(rangeColumnIndex);
+ radarRangeCell->setColor(LLColor4(options["range_color"]));
+ radarRangeCell->setFontStyle(options["range_style"].asInteger());
+
+ LLScrollListText* radarNameCell = (LLScrollListText*)row->getColumn(nameColumnIndex);
+ radarNameCell->setFontStyle(options["name_style"].asInteger());
+ if (options.has("name_color"))
+ {
+ radarNameCell->setColor(LLColor4(options["name_color"]));
+ }
+
+ LLScrollListText* voiceLevelCell = (LLScrollListText*)row->getColumn(voiceLevelColumnIndex);
+ if (entry.has("voice_level_icon"))
+ {
+ voiceLevelCell->setValue(entry["voice_level_icon"].asString());
+ }
+ }
+
+ LLStringUtil::format_map_t name_count_args;
+ name_count_args["[TOTAL]"] = stats["total"].asString();
+ name_count_args["[IN_REGION]"] = stats["region"].asString();
+ name_count_args["[IN_CHAT_RANGE]"] = stats["chatrange"].asString();
+ LLScrollListColumn* column = mRadarList->getColumn("name");
+ column->mHeader->setLabel(getString("avatar_name_count", name_count_args));
+ column->mHeader->setToolTipArgs(name_count_args);
+
+ // Restore scroll position
+ mRadarList->setScrollPos(lastScroll);
+
+ // Restore selection list
+ if (!selected_ids.empty())
+ {
+ mRadarList->selectMultiple(selected_ids);
+ }
+
+ updateButtons();
+}
diff --git a/indra/newview/fsfloaterradar.h b/indra/newview/fsfloaterradar.h
new file mode 100644
index 0000000000..0dfed91d26
--- /dev/null
+++ b/indra/newview/fsfloaterradar.h
@@ -0,0 +1,98 @@
+/**
+ * @file fsfloaterradar.h
+ * @brief Firestorm radar floater implementation
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Copyright (c) 2013 Ansariel Hiller @ Second Life
+ *
+ * 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
+ *
+ * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
+ * http://www.firestormviewer.org
+ * $/LicenseInfo$
+ */
+
+#ifndef FS_FLOATERRADAR_H
+#define FS_FLOATERRADAR_H
+
+#include "llfloater.h"
+
+#include "fsradar.h"
+#include "fsradarlistctrl.h"
+#include "llcallingcard.h" // for avatar tracker
+#include "llvoiceclient.h"
+
+class LLFilterEditor;
+class LLMenuButton;
+
+class FSFloaterRadar
+ : public LLFloater
+ , public LLVoiceClientStatusObserver
+{
+ LOG_CLASS(FSFloaterRadar);
+public:
+ FSFloaterRadar(const LLSD &);
+ virtual ~FSFloaterRadar();
+
+ /*virtual*/ BOOL postBuild();
+ /*virtual*/ void onOpen(const LLSD& key);
+
+ // Implements LLVoiceClientStatusObserver::onChange() to enable call buttons
+ // when voice is available
+ /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal);
+
+ static void onRadarNameFmtClicked(const LLSD& userdata);
+ static bool radarNameFmtCheck(const LLSD& userdata);
+ static void onRadarReportToClicked(const LLSD& userdata);
+ static bool radarReportToCheck(const LLSD& userdata);
+
+ void updateNearby(const std::vector& entries, const LLSD& stats);
+
+private:
+ void updateButtons();
+ LLUUID getCurrentItemID() const;
+ void getCurrentItemIDs(uuid_vec_t& selected_uuids) const;
+ void buttonSetEnabled(const std::string& btn_name, bool enabled);
+ void buttonSetAction(const std::string& btn_name, const commit_signal_t::slot_type& cb);
+
+ // UI callbacks
+ void onFilterEdit(const std::string& search_string);
+ void onViewProfileButtonClicked();
+ void onAddFriendButtonClicked();
+ void onImButtonClicked();
+ void onCallButtonClicked();
+ void onTeleportButtonClicked();
+ void onShareButtonClicked();
+ void onRadarListCommitted();
+ void onRadarListDoubleClicked();
+ void onGearMenuItemClicked(const LLSD& userdata);
+
+ LLFilterEditor* mFilterEditor;
+ FSRadarListCtrl* mRadarList;
+ LLNetMap* mMiniMap;
+
+ LLHandle mRadarGearMenuHandle;
+
+ FSRadar::Updater* mButtonsUpdater;
+
+ LLMenuButton* mRadarGearButton;
+
+ std::string mFilterSubString;
+ std::string mFilterSubStringOrig;
+
+ boost::signals2::connection mUpdateSignalConnection;
+};
+
+#endif // FS_FLOATERRADAR_H
diff --git a/indra/newview/fslslbridgerequest.cpp b/indra/newview/fslslbridgerequest.cpp
index a4dd322593..5fbd6d5d28 100644
--- a/indra/newview/fslslbridgerequest.cpp
+++ b/indra/newview/fslslbridgerequest.cpp
@@ -33,7 +33,7 @@
#include "fslslbridge.h"
#include
#include // for radar
-#include "llpanelpeople.h"
+#include "fsradar.h"
#include "llavatarlist.h"
#include "llavatarlistitem.h"
@@ -93,10 +93,10 @@ FSLSLBridgeRequestRadarPosResponder::FSLSLBridgeRequestRadarPosResponder()
}
void FSLSLBridgeRequestRadarPosResponder::result(const LLSD& content)
{
- LLPanelPeople* panel_people = getPeoplePanel();
- if (panel_people)
+ FSRadar* radar = FSRadar::getInstance();
+ if (radar)
{
- LLAvatarList* nearbyList = panel_people->getNearbyList();
+ LLAvatarList* nearbyList = radar->getNearbyList();
std::string strContent = content.asString();
//llinfos << "Got info: " << strContent << llendl;
diff --git a/indra/newview/fsradar.cpp b/indra/newview/fsradar.cpp
new file mode 100644
index 0000000000..da1ee067ac
--- /dev/null
+++ b/indra/newview/fsradar.cpp
@@ -0,0 +1,1053 @@
+/**
+ * @file fsradar.cpp
+ * @brief Firestorm radar implementation
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Copyright (c) 2013 Ansariel Hiller @ Second Life
+ *
+ * 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
+ *
+ * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
+ * http://www.firestormviewer.org
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "fsradar.h"
+
+// libs
+#include
+#include "llavatarnamecache.h"
+#include "llnotificationsutil.h"
+#include "lleventtimer.h"
+
+// newview
+#include "fscommon.h"
+#include "fslslbridge.h"
+#include "lggcontactsets.h"
+#include "llagent.h"
+#include "llavataractions.h"
+#include "llavatarconstants.h" // for range constants
+#include "llavatarlist.h"
+#include "llgroupactions.h"
+#include "llnotificationmanager.h"
+#include "lltracker.h"
+#include "llviewercontrol.h" // for gSavedSettings
+#include "llviewermenu.h" // for gMenuHolder
+#include "llvoavatar.h"
+#include "llvoiceclient.h"
+#include "llworld.h"
+#include "llspeakers.h"
+#include "rlvhandler.h"
+
+using namespace boost;
+
+#define FS_RADAR_LIST_UPDATE_INTERVAL 1
+
+std::string formatString(std::string text, const LLStringUtil::format_map_t& args)
+{
+ LLStringUtil::format(text, args);
+ return text;
+}
+
+/**
+ * Periodically updates the nearby people list while the Nearby tab is active.
+ *
+ * The period is defined by FS_NEARBY_LIST_UPDATE_INTERVAL constant.
+ */
+class FSRadarListUpdater : public FSRadar::Updater, public LLEventTimer
+{
+ LOG_CLASS(FSRadarListUpdater);
+
+public:
+ FSRadarListUpdater(callback_t cb)
+ : LLEventTimer(FS_RADAR_LIST_UPDATE_INTERVAL),
+ FSRadar::Updater(cb)
+ {
+ update();
+ mEventTimer.start();
+ }
+
+ /*virtual*/ BOOL tick()
+ {
+ update();
+ return FALSE;
+ }
+};
+
+//=============================================================================
+
+FSRadar::FSRadar() :
+ mNearbyList(NULL),
+ mRadarAlertRequest(false),
+ mRadarFrameCount(0),
+ mRadarLastBulkOffsetRequestTime(0),
+ mRadarLastRequestTime(0.f)
+{
+ // TODO: Ewwww ugly! Need to get rid of LLAvatarList -Ansariel
+ mNearbyList = new LLAvatarList(LLAvatarList::Params::Params());
+// [RLVa:KB] - Checked: 2010-04-05 (RLVa-1.2.2a) | Added: RLVa-1.2.0d
+ mNearbyList->setRlvCheckShowNames(true);
+// [/RLVa:KB]
+
+ mRadarListUpdater = new FSRadarListUpdater(boost::bind(&FSRadar::updateRadarList, this));
+}
+
+FSRadar::~FSRadar()
+{
+ delete mRadarListUpdater;
+ delete mNearbyList;
+}
+
+void FSRadar::radarAlertMsg(const LLUUID& agent_id, const LLAvatarName& av_name, const std::string& postMsg)
+{
+// Milkshake-style radar alerts
+ LLCachedControl milkshake_radar(gSavedSettings, "FSMilkshakeRadarToasts", false);
+
+ if (milkshake_radar)
+ {
+ LLSD payload = agent_id;
+ LLSD args;
+ args["NAME"] = getRadarName(av_name);
+ args["MESSAGE"] = postMsg;
+ LLNotificationPtr notification;
+ notification = LLNotificationsUtil::add("RadarAlert",
+ args,
+ payload.with("respond_on_mousedown", TRUE),
+ boost::bind(&LLAvatarActions::zoomIn, agent_id));
+ }
+ else
+ {
+//
+ LLChat chat;
+ chat.mText = postMsg;
+ chat.mSourceType = CHAT_SOURCE_SYSTEM;
+ chat.mFromName = getRadarName(av_name);
+ chat.mFromID = agent_id;
+ chat.mChatType = CHAT_TYPE_RADAR;
+ // FS:LO FIRE-1439 - Clickable avatar names on local chat radar crossing reports
+ LLSD args;
+ args["type"] = LLNotificationsUI::NT_NEARBYCHAT;
+ LLNotificationsUI::LLNotificationManager::instance().onChat(chat, args);
+ } //
+}
+
+void FSRadar::updateRadarList()
+{
+ //AO : Warning, reworked heavily for Firestorm.
+ if (!mNearbyList)
+ {
+ return;
+ }
+
+ //Configuration
+ LLWorld* world = LLWorld::getInstance();
+ LLMuteList* mutelist = LLMuteList::getInstance();
+
+ static const F32 chat_range_say = world->getSayDistance();
+ static const F32 chat_range_shout = world->getShoutDistance();
+
+ static const std::string str_chat_entering = LLTrans::getString("entering_chat_range");
+ static const std::string str_chat_leaving = LLTrans::getString("leaving_chat_range");
+ static const std::string str_draw_distance_entering = LLTrans::getString("entering_draw_distance");
+ static const std::string str_draw_distance_leaving = LLTrans::getString("leaving_draw_distance");
+ static const std::string str_region_entering = LLTrans::getString("entering_region");
+ static const std::string str_region_entering_distance = LLTrans::getString("entering_region_distance");
+ static const std::string str_region_leaving = LLTrans::getString("leaving_region");
+
+ static LLCachedControl RadarReportChatRangeEnter(gSavedSettings, "RadarReportChatRangeEnter");
+ static LLCachedControl RadarReportChatRangeLeave(gSavedSettings, "RadarReportChatRangeLeave");
+ static LLCachedControl RadarReportDrawRangeEnter(gSavedSettings, "RadarReportDrawRangeEnter");
+ static LLCachedControl RadarReportDrawRangeLeave(gSavedSettings, "RadarReportDrawRangeLeave");
+ static LLCachedControl RadarReportSimRangeEnter(gSavedSettings, "RadarReportSimRangeEnter");
+ static LLCachedControl RadarReportSimRangeLeave(gSavedSettings, "RadarReportSimRangeLeave");
+ static LLCachedControl RadarEnterChannelAlert(gSavedSettings, "RadarEnterChannelAlert");
+ static LLCachedControl RadarLeaveChannelAlert(gSavedSettings, "RadarLeaveChannelAlert");
+ static LLCachedControl nearMeRange(gSavedSettings, "NearMeRange");
+ static LLCachedControl limitRange(gSavedSettings, "LimitRadarByRange");
+ static LLCachedControl sUseLSLBridge(gSavedSettings, "UseLSLBridge");
+ static LLCachedControl RenderFarClip(gSavedSettings, "RenderFarClip");
+
+ F32 drawRadius(RenderFarClip);
+ const LLVector3d& posSelf = gAgent.getPositionGlobal();
+ LLViewerRegion* reg = gAgent.getRegion();
+ LLUUID regionSelf;
+ if (reg)
+ {
+ regionSelf = reg->getRegionID();
+ }
+ bool alertScripts = mRadarAlertRequest; // save the current value, so it doesn't get changed out from under us by another thread
+ std::vector items;
+ time_t now = time(NULL);
+
+ //STEP 0: Clear model data
+ mRadarEnterAlerts.clear();
+ mRadarLeaveAlerts.clear();
+ mRadarOffsetRequests.clear();
+ mRadarEntriesData.clear();
+ mAvatarStats.clear();
+
+ //STEP 1: Update our basic data model: detect Avatars & Positions in our defined range
+ std::vector positions;
+ uuid_vec_t avatar_ids;
+ if (limitRange)
+ {
+ world->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), nearMeRange);
+ }
+ else
+ {
+ world->getAvatars(&avatar_ids, &positions);
+ }
+ mNearbyList->getIDs() = avatar_ids; // copy constructor, refreshing underlying mNearbyList
+ mNearbyList->setDirty(true, true); // AO: These optional arguements force updating even when we're not a visible window.
+ mNearbyList->getItems(items);
+ LLLocalSpeakerMgr::getInstance()->update(TRUE);
+
+ //STEP 2: Transform detected model list data into more flexible multimap data structure;
+ //TS: Count avatars in chat range and in the same region
+ U32 inChatRange = 0;
+ U32 inSameRegion = 0;
+ std::vector::const_iterator
+ pos_it = positions.begin(),
+ pos_end = positions.end();
+ uuid_vec_t::const_iterator
+ item_it = avatar_ids.begin(),
+ item_end = avatar_ids.end();
+ for (;pos_it != pos_end && item_it != item_end; ++pos_it, ++item_it )
+ {
+ //
+ //2a. For each detected av, gather up all data we would want to display or use to drive alerts
+ //
+
+ LLUUID avId = static_cast(*item_it);
+ LLAvatarListItem* av = mNearbyList->getAvatarListItem(avId);
+ LLVector3d avPos = static_cast(*pos_it);
+ S32 seentime = 0;
+ LLUUID avRegion;
+
+ // Skip modelling this avatar if its basic data is either inaccessible, or it's a dummy placeholder
+ LLViewerRegion *reg = world->getRegionFromPosGlobal(avPos);
+ if (!reg || !av) // don't update this radar listing if data is inaccessible
+ {
+ continue;
+ }
+
+ // Try to get the avatar's viewer object - we will need it anyway later
+ LLVOAvatar* avVo = (LLVOAvatar*)gObjectList.findObject(avId);
+
+ static LLUICachedControl showdummyav("FSShowDummyAVsinRadar");
+ if (!showdummyav)
+ {
+ if (avVo && avVo->mIsDummy)
+ {
+ continue;
+ }
+ }
+
+ avRegion = reg->getRegionID();
+ if (lastRadarSweep.count(avId) > 1) // if we detect a multiple ID situation, get lastSeenTime from our cache instead
+ {
+ std::pair::iterator, std::multimap::iterator> dupeAvs;
+ dupeAvs = lastRadarSweep.equal_range(avId);
+ for (std::multimap::iterator it2 = dupeAvs.first; it2 != dupeAvs.second; ++it2)
+ {
+ if (it2->second.lastRegion == avRegion)
+ {
+ seentime = (S32)difftime(now, it2->second.firstSeen);
+ }
+ }
+ }
+ else
+ {
+ seentime = (S32)difftime(now, av->getFirstSeen());
+ }
+ //av->setFirstSeen(now - (time_t)seentime); // maintain compatibility with underlying list, deprecated
+ S32 hours = (S32)(seentime / 3600);
+ S32 mins = (S32)((seentime - hours * 3600) / 60);
+ S32 secs = (S32)((seentime - hours * 3600 - mins * 60));
+ std::string avSeenStr = llformat("%d:%02d:%02d", hours, mins, secs);
+ S32 avStatusFlags = av->getAvStatus();
+ std::string avFlagStr = "";
+ if (avStatusFlags & AVATAR_IDENTIFIED)
+ {
+ avFlagStr += "$";
+ }
+ std::string avAgeStr = av->getAvatarAge();
+ std::string avName = getRadarName(avId);
+ av->setAvatarName(avName); // maintain compatibility with underlying list; used in other locations!
+ U32 lastZOffsetTime = av->getLastZOffsetTime();
+ F32 avZOffset = av->getZOffset();
+ if (avPos[VZ] == AVATAR_UNKNOWN_Z_OFFSET) // if our official z position is AVATAR_UNKNOWN_Z_OFFSET, we need a correction.
+ {
+ // set correction if we have it
+ if (avZOffset > 0.1)
+ {
+ avPos[VZ] = avZOffset;
+ }
+
+ //schedule offset requests, if needed
+ if (sUseLSLBridge && (now > (mRadarLastBulkOffsetRequestTime + FSRADAR_COARSE_OFFSET_INTERVAL)) && (now > lastZOffsetTime + FSRADAR_COARSE_OFFSET_INTERVAL))
+ {
+ mRadarOffsetRequests.push_back(avId);
+ av->setLastZOffsetTime(now);
+ }
+ }
+ F32 avRange = (avPos[VZ] != AVATAR_UNKNOWN_Z_OFFSET ? dist_vec(avPos, posSelf) : AVATAR_UNKNOWN_RANGE);
+ av->setRange(avRange); // maintain compatibility with underlying list; used in other locations!
+ av->setPosition(avPos); // maintain compatibility with underlying list; used in other locations!
+
+ //
+ //2b. Process newly detected avatars
+ //
+ if (lastRadarSweep.count(avId) == 0)
+ {
+ // chat alerts
+ if (RadarReportChatRangeEnter && (avRange <= chat_range_say) && avRange > AVATAR_UNKNOWN_RANGE)
+ {
+ LLStringUtil::format_map_t args;
+ args["DISTANCE"] = llformat("%3.2f", avRange);
+ std::string message = formatString(str_chat_entering, args);
+ make_ui_sound("UISndRadarChatEnter"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, message));
+ }
+ if (RadarReportDrawRangeEnter && (avRange <= drawRadius) && avRange > AVATAR_UNKNOWN_RANGE)
+ {
+ LLStringUtil::format_map_t args;
+ args["DISTANCE"] = llformat("%3.2f", avRange);
+ std::string message = formatString(str_draw_distance_entering, args);
+ make_ui_sound("UISndRadarDrawEnter"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, message));
+ }
+ if (RadarReportSimRangeEnter && (avRegion == regionSelf))
+ {
+ make_ui_sound("UISndRadarSimEnter"); // FIRE-6069: Radar alerts sounds
+ if (avRange != AVATAR_UNKNOWN_RANGE) // Don't report an inaccurate range in localchat, if the true range is not known.
+ {
+ LLStringUtil::format_map_t args;
+ args["DISTANCE"] = llformat("%3.2f", avRange);
+ std::string message = formatString(str_region_entering_distance, args);
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, message));
+ }
+ else
+ {
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_region_entering));
+ }
+ }
+ if (RadarEnterChannelAlert || (alertScripts))
+ {
+ // Autodetect Phoenix chat UUID compatibility.
+ // If Leave channel alerts are not set, restrict reports to same-sim only.
+ if (!RadarLeaveChannelAlert)
+ {
+ if (avRegion == regionSelf)
+ {
+ mRadarEnterAlerts.push_back(avId);
+ }
+ }
+ else
+ {
+ mRadarEnterAlerts.push_back(avId);
+ }
+ }
+ }
+
+ //
+ // 2c. Process previously detected avatars
+ //
+ else
+ {
+ radarFields rf; // will hold the newest version
+ // Check for range crossing alert threshholds, being careful to handle double-listings
+ if (lastRadarSweep.count(avId) == 1) // normal case, check from last position
+ {
+ rf = lastRadarSweep.find(avId)->second;
+ if (RadarReportChatRangeEnter || RadarReportChatRangeLeave)
+ {
+ if (RadarReportChatRangeEnter && (avRange <= chat_range_say && avRange > AVATAR_UNKNOWN_RANGE) && (rf.lastDistance > chat_range_say || rf.lastDistance == AVATAR_UNKNOWN_RANGE))
+ {
+ LLStringUtil::format_map_t args;
+ args["DISTANCE"] = llformat("%3.2f", avRange);
+ std::string message = formatString(str_chat_entering, args);
+ make_ui_sound("UISndRadarChatEnter"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, message));
+ }
+ else if (RadarReportChatRangeLeave && (avRange > chat_range_say || avRange == AVATAR_UNKNOWN_RANGE) && (rf.lastDistance <= chat_range_say && rf.lastDistance > AVATAR_UNKNOWN_RANGE))
+ {
+ make_ui_sound("UISndRadarChatLeave"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_chat_leaving));
+ }
+ }
+ if (RadarReportDrawRangeEnter || RadarReportDrawRangeLeave)
+ {
+ if (RadarReportDrawRangeEnter && (avRange <= drawRadius && avRange > AVATAR_UNKNOWN_RANGE) && (rf.lastDistance > drawRadius || rf.lastDistance == AVATAR_UNKNOWN_RANGE))
+ {
+ LLStringUtil::format_map_t args;
+ args["DISTANCE"] = llformat("%3.2f", avRange);
+ std::string message = formatString(str_draw_distance_entering, args);
+ make_ui_sound("UISndRadarDrawEnter"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, message));
+ }
+ else if (RadarReportDrawRangeLeave && (avRange > drawRadius || avRange == AVATAR_UNKNOWN_RANGE) && (rf.lastDistance <= drawRadius && rf.lastDistance > AVATAR_UNKNOWN_RANGE))
+ {
+ make_ui_sound("UISndRadarDrawLeave"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_draw_distance_leaving));
+ }
+ }
+ if (RadarReportSimRangeEnter || RadarReportSimRangeLeave )
+ {
+ if (RadarReportSimRangeEnter && (avRegion == regionSelf) && (avRegion != rf.lastRegion))
+ {
+ make_ui_sound("UISndRadarSimEnter"); // FIRE-6069: Radar alerts sounds
+ if (avRange != AVATAR_UNKNOWN_RANGE) // Don't report an inaccurate range in localchat, if the true range is not known.
+ {
+ LLStringUtil::format_map_t args;
+ args["DISTANCE"] = llformat("%3.2f", avRange);
+ std::string message = formatString(str_region_entering_distance, args);
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, message));
+ }
+ else
+ {
+ LLAvatarNameCache::get(avId,boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_region_entering));
+ }
+ }
+ else if (RadarReportSimRangeLeave && (rf.lastRegion == regionSelf) && (avRegion != regionSelf))
+ {
+ make_ui_sound("UISndRadarSimLeave"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_region_leaving));
+ }
+ }
+ }
+ else if (lastRadarSweep.count(avId) > 1) // handle duplicates, from sim crossing oddness
+ {
+ // iterate through all the duplicates found, searching for the newest.
+ rf.firstSeen=0;
+ std::pair::iterator, std::multimap::iterator> dupeAvs;
+ dupeAvs = lastRadarSweep.equal_range(avId);
+ for (std::multimap::iterator it2 = dupeAvs.first; it2 != dupeAvs.second; ++it2)
+ {
+ if (it2->second.firstSeen > rf.firstSeen)
+ {
+ rf = it2->second;
+ }
+ }
+ lldebugs << "Duplicates detected for " << avName <<" , most recent is " << rf.firstSeen << llendl;
+
+ if (RadarReportChatRangeEnter || RadarReportChatRangeLeave)
+ {
+ if (RadarReportChatRangeEnter && (avRange <= chat_range_say && avRange > AVATAR_UNKNOWN_RANGE) && (rf.lastDistance > chat_range_say || rf.lastDistance == AVATAR_UNKNOWN_RANGE))
+ {
+ LLStringUtil::format_map_t args;
+ args["DISTANCE"] = llformat("%3.2f", avRange);
+ std::string message = formatString(str_chat_entering, args);
+ make_ui_sound("UISndRadarChatEnter"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, message));
+ }
+ else if (RadarReportChatRangeLeave && (avRange > chat_range_say || avRange == AVATAR_UNKNOWN_RANGE) && (rf.lastDistance <= chat_range_say && rf.lastDistance > AVATAR_UNKNOWN_RANGE))
+ {
+ make_ui_sound("UISndRadarChatLeave"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_chat_leaving));
+ }
+ }
+ if (RadarReportDrawRangeEnter || RadarReportDrawRangeLeave)
+ {
+ if (RadarReportDrawRangeEnter && (avRange <= drawRadius && avRange > AVATAR_UNKNOWN_RANGE) && (rf.lastDistance > drawRadius || rf.lastDistance == AVATAR_UNKNOWN_RANGE))
+ {
+ LLStringUtil::format_map_t args;
+ args["DISTANCE"] = llformat("%3.2f", avRange);
+ std::string message = formatString(str_draw_distance_entering, args);
+ make_ui_sound("UISndRadarDrawEnter"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, message));
+ }
+ else if (RadarReportDrawRangeLeave && (avRange > drawRadius || avRange == AVATAR_UNKNOWN_RANGE) && (rf.lastDistance <= drawRadius && rf.lastDistance > AVATAR_UNKNOWN_RANGE))
+ {
+ make_ui_sound("UISndRadarDrawLeave"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_draw_distance_leaving));
+ }
+ }
+ if (RadarReportSimRangeEnter || RadarReportSimRangeLeave)
+ {
+ if (RadarReportSimRangeEnter && (avRegion == regionSelf) && (avRegion != rf.lastRegion))
+ {
+ make_ui_sound("UISndRadarSimEnter"); // FIRE-6069: Radar alerts sounds
+ if (avRange != AVATAR_UNKNOWN_RANGE) // Don't report an inaccurate range in localchat, if the true range is not known.
+ {
+ LLStringUtil::format_map_t args;
+ args["DISTANCE"] = llformat("%3.2f", avRange);
+ std::string message = formatString(str_region_entering_distance, args);
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, message));
+ }
+ else
+ {
+ LLAvatarNameCache::get(avId,boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_region_entering));
+ }
+ }
+ else if (RadarReportSimRangeLeave && (rf.lastRegion == regionSelf) && (avRegion != regionSelf))
+ {
+ make_ui_sound("UISndRadarSimLeave"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(avId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_region_leaving));
+ }
+ }
+ }
+ //If we were manually asked to update an external source for all existing avatars, add them to the queue.
+ if (alertScripts)
+ {
+ mRadarEnterAlerts.push_back(avId);
+ }
+ }
+
+ //
+ //2d. Prepare data for presentation view for this avatar
+ //
+ if (regionSelf == avRegion)
+ {
+ inSameRegion++;
+ }
+
+ LLSD entry;
+ LLSD entry_options;
+
+ entry["id"] = avId;
+ entry["name"] = avName;
+ entry["in_region"] = (regionSelf == avRegion);
+ entry["flags"] = avFlagStr;
+ entry["age"] = avAgeStr;
+ entry["seen"] = avSeenStr;
+ entry["range"] = (avRange > AVATAR_UNKNOWN_RANGE ? llformat("%3.2f", avRange) : llformat(">%3.2f", drawRadius));
+
+ //AO: Set any range colors / styles
+ LLUIColor range_color;
+ if (avRange > AVATAR_UNKNOWN_RANGE)
+ {
+ if (avRange <= chat_range_say)
+ {
+ range_color = LLUIColorTable::instance().getColor("AvatarListItemChatRange", LLColor4::red);
+ inChatRange++;
+ }
+ else if (avRange <= chat_range_shout)
+ {
+ range_color = LLUIColorTable::instance().getColor("AvatarListItemShoutRange", LLColor4::white);
+ }
+ else
+ {
+ range_color = LLUIColorTable::instance().getColor("AvatarListItemBeyondShoutRange", LLColor4::white);
+ }
+ }
+ else
+ {
+ range_color = LLUIColorTable::instance().getColor("AvatarListItemBeyondShoutRange", LLColor4::white);
+ }
+ entry_options["range_color"] = range_color.get().getValue();
+
+ // Check if avatar is in draw distance and a VOAvatar instance actually exists
+ if (avRange <= drawRadius && avRange > AVATAR_UNKNOWN_RANGE && avVo)
+ {
+ entry_options["range_style"] = LLFontGL::BOLD;
+ }
+ else
+ {
+ entry_options["range_style"] = LLFontGL::NORMAL;
+ }
+
+ // Set friends colors / styles
+ LLFontGL::StyleFlags nameCellStyle = LLFontGL::NORMAL;
+ const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(avId);
+ if (relation)
+ {
+ nameCellStyle = (LLFontGL::StyleFlags)(nameCellStyle | LLFontGL::BOLD);
+ }
+ if (mutelist->isMuted(avId))
+ {
+ nameCellStyle = (LLFontGL::StyleFlags)(nameCellStyle | LLFontGL::ITALIC);
+ }
+ entry_options["name_style"] = nameCellStyle;
+
+ if (LGGContactSets::getInstance()->hasFriendColorThatShouldShow(avId, LGG_CS_RADAR))
+ {
+ LLColor4 name_color = LGGContactSets::getInstance()->getFriendColor(avId);
+ entry_options["name_color"] = name_color.getValue();
+ }
+
+ // Voice power level indicator
+ LLVoiceClient* voice_client = LLVoiceClient::getInstance();
+ if (voice_client->voiceEnabled() && voice_client->isVoiceWorking())
+ {
+ LLSpeaker* speaker = LLLocalSpeakerMgr::getInstance()->findSpeaker(avId);
+ if (speaker && speaker->isInVoiceChannel())
+ {
+ EVoicePowerLevel power_level = voice_client->getPowerLevel(avId);
+
+ switch (power_level)
+ {
+ case VPL_PTT_Off:
+ entry["voice_level_icon"] = "VoicePTT_Off";
+ break;
+ case VPL_PTT_On:
+ entry["voice_level_icon"] = "VoicePTT_On";
+ break;
+ case VPL_Level1:
+ entry["voice_level_icon"] = "VoicePTT_Lvl1";
+ break;
+ case VPL_Level2:
+ entry["voice_level_icon"] = "VoicePTT_Lvl2";
+ break;
+ case VPL_Level3:
+ entry["voice_level_icon"] = "VoicePTT_Lvl3";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // Save data for our listeners
+ LLSD entry_data;
+ entry_data["entry"] = entry;
+ entry_data["options"] = entry_options;
+ mRadarEntriesData.push_back(entry_data);
+ } // End STEP 2, all model/presentation row processing complete.
+
+ //
+ //STEP 3, process any bulk actions that require the whole model to be known first
+ //
+
+ //
+ //3a. dispatch requests for ZOffset updates, working around minimap's inaccurate height
+ //
+ if (mRadarOffsetRequests.size() > 0)
+ {
+ std::string prefix = "getZOffsets|";
+ std::string msg = "";
+ U32 updatesPerRequest=0;
+ while (mRadarOffsetRequests.size() > 0)
+ {
+ LLUUID avId = mRadarOffsetRequests.back();
+ mRadarOffsetRequests.pop_back();
+ msg = llformat("%s%s,", msg.c_str(), avId.asString().c_str());
+ if (++updatesPerRequest > FSRADAR_MAX_OFFSET_REQUESTS)
+ {
+ msg = msg.substr(0, msg.size() - 1);
+ FSLSLBridgeRequestResponder* responder = new FSLSLBridgeRequestRadarPosResponder();
+ FSLSLBridge::instance().viewerToLSL(prefix +msg, responder);
+ //llinfos << " OFFSET REQUEST SEGMENT"<< prefix << msg << llendl;
+ msg = "";
+ updatesPerRequest = 0;
+ }
+ }
+ if (updatesPerRequest > 0)
+ {
+ msg = msg.substr(0, msg.size() - 1);
+ FSLSLBridgeRequestResponder* responder = new FSLSLBridgeRequestRadarPosResponder();
+ FSLSLBridge::instance().viewerToLSL(prefix + msg, responder);
+ //llinfos << " OFFSET REQUEST FINAL " << prefix << msg << llendl;
+ }
+
+ // clear out the dispatch queue
+ mRadarOffsetRequests.clear();
+ mRadarLastBulkOffsetRequestTime = now;
+ }
+
+ //
+ //3b: process alerts for avatars that where here last frame, but gone this frame (ie, they left)
+ // as well as dispatch all earlier detected alerts for crossing range thresholds.
+ //
+
+ for (std::multimap::const_iterator i = lastRadarSweep.begin(); i != lastRadarSweep.end(); ++i)
+ {
+ LLUUID prevId = i->first;
+ if (!mNearbyList->contains(prevId))
+ {
+ radarFields rf = i->second;
+ if (RadarReportChatRangeLeave && (rf.lastDistance <= chat_range_say) && rf.lastDistance > AVATAR_UNKNOWN_RANGE)
+ {
+ make_ui_sound("UISndRadarChatLeave"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(prevId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_chat_leaving));
+ }
+ if (RadarReportDrawRangeLeave && (rf.lastDistance <= drawRadius) && rf.lastDistance > AVATAR_UNKNOWN_RANGE)
+ {
+ make_ui_sound("UISndRadarDrawLeave"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(prevId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_draw_distance_leaving));
+ }
+ if (RadarReportSimRangeLeave && (rf.lastRegion == regionSelf))
+ {
+ make_ui_sound("UISndRadarSimLeave"); // FIRE-6069: Radar alerts sounds
+ LLAvatarNameCache::get(prevId, boost::bind(&FSRadar::radarAlertMsg, this, _1, _2, str_region_leaving));
+ }
+
+ if (RadarLeaveChannelAlert)
+ {
+ mRadarLeaveAlerts.push_back(prevId);
+ }
+ }
+ }
+
+ static LLCachedControl RadarAlertChannel(gSavedSettings, "RadarAlertChannel");
+ U32 num_entering = mRadarEnterAlerts.size();
+ if (num_entering > 0)
+ {
+ mRadarFrameCount++;
+ S32 chan(RadarAlertChannel);
+ U32 num_this_pass = min(FSRADAR_MAX_AVATARS_PER_ALERT, num_entering);
+ std::string msg = llformat("%d,%d", mRadarFrameCount, num_this_pass);
+ U32 loop = 0;
+ while (loop < num_entering)
+ {
+ for (int i = 0; i < num_this_pass; i++)
+ {
+ msg = llformat("%s,%s", msg.c_str(), mRadarEnterAlerts[loop + i].asString().c_str());
+ }
+ LLMessageSystem* msgs = gMessageSystem;
+ msgs->newMessage("ScriptDialogReply");
+ msgs->nextBlock("AgentData");
+ msgs->addUUID("AgentID", gAgent.getID());
+ msgs->addUUID("SessionID", gAgent.getSessionID());
+ msgs->nextBlock("Data");
+ msgs->addUUID("ObjectID", gAgent.getID());
+ msgs->addS32("ChatChannel", chan);
+ msgs->addS32("ButtonIndex", 1);
+ msgs->addString("ButtonLabel", msg.c_str());
+ gAgent.sendReliableMessage();
+ loop += num_this_pass;
+ num_this_pass = min(FSRADAR_MAX_AVATARS_PER_ALERT, num_entering - loop);
+ msg = llformat("%d,%d", mRadarFrameCount, num_this_pass);
+ }
+ }
+ U32 num_leaving = mRadarLeaveAlerts.size();
+ if (num_leaving > 0)
+ {
+ mRadarFrameCount++;
+ S32 chan(RadarAlertChannel);
+ U32 num_this_pass = min(FSRADAR_MAX_AVATARS_PER_ALERT, num_leaving);
+ std::string msg = llformat("%d,-%d", mRadarFrameCount, min(FSRADAR_MAX_AVATARS_PER_ALERT, num_leaving));
+ U32 loop = 0;
+ while (loop < num_leaving)
+ {
+ for (int i = 0; i < num_this_pass; i++)
+ {
+ msg = llformat("%s,%s", msg.c_str(), mRadarLeaveAlerts[loop + i].asString().c_str());
+ }
+ LLMessageSystem* msgs = gMessageSystem;
+ msgs->newMessage("ScriptDialogReply");
+ msgs->nextBlock("AgentData");
+ msgs->addUUID("AgentID", gAgent.getID());
+ msgs->addUUID("SessionID", gAgent.getSessionID());
+ msgs->nextBlock("Data");
+ msgs->addUUID("ObjectID", gAgent.getID());
+ msgs->addS32("ChatChannel", chan);
+ msgs->addS32("ButtonIndex", 1);
+ msgs->addString("ButtonLabel", msg.c_str());
+ gAgent.sendReliableMessage();
+ loop += num_this_pass;
+ num_this_pass = min(FSRADAR_MAX_AVATARS_PER_ALERT, num_leaving - loop);
+ msg = llformat("%d,-%d", mRadarFrameCount, num_this_pass);
+ }
+ }
+
+ // reset any active alert requests
+ if (alertScripts)
+ {
+ mRadarAlertRequest = false;
+ }
+
+ //
+ //STEP 4: Cache our current model data, so we can compare it with the next fresh group of model data for fast change detection.
+ //
+
+ lastRadarSweep.clear();
+ for (std::vector::const_iterator itItem = items.begin(); itItem != items.end(); ++itItem)
+ {
+ LLAvatarListItem* av = static_cast(*itItem);
+ radarFields rf;
+ rf.avName = av->getAvatarName();
+ rf.lastDistance = av->getRange();
+ rf.firstSeen = av->getFirstSeen();
+ rf.lastStatus = av->getAvStatus();
+ rf.ZOffset = av->getZOffset();
+ rf.lastGlobalPos = av->getPosition();
+ // Ansariel: This seems to be wrong and isn't needed anywhere
+ //if ((rf.ZOffset > 0) && (rf.lastGlobalPos[VZ] < 1024)) // if our position may need an offset correction, see if we have one to apply
+ //{
+ // rf.lastGlobalPos[VZ] = rf.lastGlobalPos[VZ] + (1024 * rf.ZOffset);
+ //}
+ //rf.lastZOffsetTime = av->getLastZOffsetTime();
+ if (rf.lastGlobalPos != LLVector3d(0.0f, 0.0f, 0.0f))
+ {
+ LLViewerRegion* lastRegion = world->getRegionFromPosGlobal(rf.lastGlobalPos);
+ if (lastRegion)
+ {
+ rf.lastRegion = lastRegion->getRegionID();
+ }
+ }
+ else
+ {
+ rf.lastRegion = LLUUID(0);
+ }
+
+ lastRadarSweep.insert(std::pair(av->getAvatarId(), rf));
+ }
+
+ //
+ //STEP 5: Final data updates and notification of subscribers
+ //
+
+ mAvatarStats["total"] = llformat("%d", lastRadarSweep.size());
+ mAvatarStats["region"] = llformat("%d", inSameRegion);
+ mAvatarStats["chatrange"] = llformat("%d", inChatRange);
+
+ checkTracking();
+
+ // Inform our subscribers about updates
+ if (!mUpdateSignal.empty())
+ {
+ mUpdateSignal(mRadarEntriesData, mAvatarStats);
+ }
+}
+
+void FSRadar::requestRadarChannelAlertSync()
+{
+ F32 timeNow = gFrameTimeSeconds;
+ if ((timeNow - FSRADAR_CHAT_MIN_SPACING) > mRadarLastRequestTime)
+ {
+ mRadarLastRequestTime = timeNow;
+ mRadarAlertRequest = true;
+ }
+}
+
+void FSRadar::teleportToAvatar(const LLUUID& targetAv)
+// Teleports user to last scanned location of nearby avatar
+// Note: currently teleportViaLocation is disrupted by enforced landing points set on a parcel.
+{
+ std::vector items;
+ mNearbyList->getItems(items);
+ for (std::vector::const_iterator itItem = items.begin(); itItem != items.end(); ++itItem)
+ {
+ LLAvatarListItem* av = static_cast(*itItem);
+ if (av->getAvatarId() == targetAv)
+ {
+ LLVector3d avpos = av->getPosition();
+ if (avpos.mdV[VZ] == AVATAR_UNKNOWN_Z_OFFSET)
+ {
+ LLNotificationsUtil::add("TeleportToAvatarNotPossible");
+ }
+ else
+ {
+ gAgent.teleportViaLocation(avpos);
+ }
+ return;
+ }
+ }
+}
+
+//static
+void FSRadar::onRadarNameFmtClicked(const LLSD& userdata)
+{
+ std::string chosen_item = userdata.asString();
+ if (chosen_item == "DN")
+ {
+ gSavedSettings.setU32("RadarNameFormat", FSRADAR_NAMEFORMAT_DISPLAYNAME);
+ }
+ else if (chosen_item == "UN")
+ {
+ gSavedSettings.setU32("RadarNameFormat", FSRADAR_NAMEFORMAT_USERNAME);
+ }
+ else if (chosen_item == "DNUN")
+ {
+ gSavedSettings.setU32("RadarNameFormat", FSRADAR_NAMEFORMAT_DISPLAYNAME_USERNAME);
+ }
+ else if (chosen_item == "UNDN")
+ {
+ gSavedSettings.setU32("RadarNameFormat", FSRADAR_NAMEFORMAT_USERNAME_DISPLAYNAME);
+ }
+}
+
+//static
+bool FSRadar::radarNameFmtCheck(const LLSD& userdata)
+{
+ std::string menu_item = userdata.asString();
+ U32 name_format = gSavedSettings.getU32("RadarNameFormat");
+ switch (name_format)
+ {
+ case FSRADAR_NAMEFORMAT_DISPLAYNAME:
+ return (menu_item == "DN");
+ case FSRADAR_NAMEFORMAT_USERNAME:
+ return (menu_item == "UN");
+ case FSRADAR_NAMEFORMAT_DISPLAYNAME_USERNAME:
+ return (menu_item == "DNUN");
+ case FSRADAR_NAMEFORMAT_USERNAME_DISPLAYNAME:
+ return (menu_item == "UNDN");
+ default:
+ break;
+ }
+ return false;
+}
+
+// Milkshake-style radar alerts
+//static
+void FSRadar::onRadarReportToClicked(const LLSD& userdata)
+{
+ std::string chosen_item = userdata.asString();
+ if (chosen_item == "radar_toasts")
+ {
+ gSavedSettings.setBOOL("FSMilkshakeRadarToasts", TRUE);
+ }
+ else if (chosen_item == "radar_nearby_chat")
+ {
+ gSavedSettings.setBOOL("FSMilkshakeRadarToasts", FALSE);
+ }
+}
+
+//static
+bool FSRadar::radarReportToCheck(const LLSD& userdata)
+{
+ std::string menu_item = userdata.asString();
+ bool report_to = gSavedSettings.getBOOL("FSMilkshakeRadarToasts");
+ if (report_to)
+ {
+ return (menu_item == "radar_toasts");
+ }
+ else
+ {
+ return (menu_item == "radar_nearby_chat");
+ }
+}
+//
+
+std::string FSRadar::getRadarName(const LLAvatarName& avname)
+{
+// [RLVa:KB-FS] - Checked: 2011-06-11 (RLVa-1.3.1) | Added: RLVa-1.3.1
+ if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES))
+ {
+ return RlvStrings::getAnonym(avname);
+ }
+// [/RLVa:KB-FS]
+
+ U32 fmt = gSavedSettings.getU32("RadarNameFormat");
+ // if display names are enabled, allow a variety of formatting options, depending on menu selection
+ if (gSavedSettings.getBOOL("UseDisplayNames"))
+ {
+ if (fmt == FSRADAR_NAMEFORMAT_DISPLAYNAME)
+ {
+ return avname.mDisplayName;
+ }
+ else if (fmt == FSRADAR_NAMEFORMAT_USERNAME)
+ {
+ return avname.mUsername;
+ }
+ else if (fmt == FSRADAR_NAMEFORMAT_DISPLAYNAME_USERNAME)
+ {
+ std::string s1 = avname.mDisplayName;
+ to_lower(s1);
+ std::string s2 = avname.mUsername;
+ replace_all(s2, ".", " ");
+ if (s1.compare(s2) == 0)
+ {
+ return avname.mDisplayName;
+ }
+ else
+ {
+ return llformat("%s (%s)", avname.mDisplayName.c_str(), avname.mUsername.c_str());
+ }
+ }
+ else if (fmt == FSRADAR_NAMEFORMAT_USERNAME_DISPLAYNAME)
+ {
+ std::string s1 = avname.mDisplayName;
+ to_lower(s1);
+ std::string s2 = avname.mUsername;
+ replace_all(s2, ".", " ");
+ if (s1.compare(s2) == 0)
+ {
+ return avname.mDisplayName;
+ }
+ else
+ {
+ return llformat("%s (%s)", avname.mUsername.c_str(), avname.mDisplayName.c_str());
+ }
+ }
+ }
+
+ // else use legacy name lookups
+ return avname.mDisplayName; // will be mapped to legacyname automatically by the name cache
+}
+
+std::string FSRadar::getRadarName(const LLUUID& avId)
+{
+ LLAvatarName avname;
+
+ if (LLAvatarNameCache::get(avId, &avname)) // use the synchronous call. We poll every second so there's less value in using the callback form.
+ {
+ return getRadarName(avname);
+ }
+
+ // name not found. Temporarily fill in with the UUID. It's more distinguishable than (loading...)
+ return avId.asString();
+}
+
+void FSRadar::startTracking(const LLUUID& avatar_id)
+{
+ mTrackedAvatarId = avatar_id;
+ updateTracking();
+}
+
+void FSRadar::checkTracking()
+{
+ if (LLTracker::getTrackingStatus() == LLTracker::TRACKING_LOCATION
+ && LLTracker::getTrackedLocationType() == LLTracker::LOCATION_AVATAR)
+ {
+ updateTracking();
+ }
+}
+
+void FSRadar::updateTracking()
+{
+ std::multimap::const_iterator it;
+ it = lastRadarSweep.find(mTrackedAvatarId);
+ if (it != lastRadarSweep.end())
+ {
+ if (LLTracker::getTrackedPositionGlobal() != it->second.lastGlobalPos)
+ {
+ std::string targetName(it->second.avName);
+ if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES))
+ {
+ targetName = RlvStrings::getAnonym(targetName);
+ }
+ LLTracker::trackLocation(it->second.lastGlobalPos, targetName, "", LLTracker::LOCATION_AVATAR);
+ }
+ }
+ else
+ {
+ LLTracker::stopTracking(NULL);
+ }
+}
+
+void FSRadar::zoomAvatar(const LLUUID& avatar_id, const std::string& name)
+{
+ LLAvatarListItem* avl_item = mNearbyList->getAvatarListItem(avatar_id);
+
+ if (!avl_item)
+ {
+ return;
+ }
+
+ if (avl_item->getRange() <= gSavedSettings.getF32("RenderFarClip"))
+ {
+ handle_zoom_to_object(avatar_id, avl_item->getPosition());
+ }
+ else
+ {
+ LLStringUtil::format_map_t args;
+ args["AVATARNAME"] = name.c_str();
+ reportToNearbyChat(LLTrans::getString("camera_no_focus", args));
+ }
+}
diff --git a/indra/newview/fsradar.h b/indra/newview/fsradar.h
new file mode 100644
index 0000000000..9b5ee2cfeb
--- /dev/null
+++ b/indra/newview/fsradar.h
@@ -0,0 +1,143 @@
+/**
+ * @file fsradar.h
+ * @brief Firestorm radar implementation
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Copyright (c) 2013 Ansariel Hiller @ Second Life
+ *
+ * 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
+ *
+ * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
+ * http://www.firestormviewer.org
+ * $/LicenseInfo$
+ */
+
+#ifndef FS_RADAR_H
+#define FS_RADAR_H
+
+#include "llsingleton.h"
+
+class LLAvatarList;
+class LLAvatarName;
+
+const U32 FSRADAR_MAX_AVATARS_PER_ALERT = 6; // maximum number of UUIDs we can cram into a single channel radar alert message
+const U32 FSRADAR_COARSE_OFFSET_INTERVAL = 7; // seconds after which we query the bridge for a coarse location adjustment
+const U32 FSRADAR_MAX_OFFSET_REQUESTS = 60; // 2048 / UUID size, leaving overhead space
+const U32 FSRADAR_CHAT_MIN_SPACING = 6; // minimum delay between radar chat messages
+
+const U32 FSRADAR_NAMEFORMAT_DISPLAYNAME = 0;
+const U32 FSRADAR_NAMEFORMAT_USERNAME = 1;
+const U32 FSRADAR_NAMEFORMAT_DISPLAYNAME_USERNAME = 2;
+const U32 FSRADAR_NAMEFORMAT_USERNAME_DISPLAYNAME = 3;
+
+
+class FSRadar
+ : public LLSingleton
+{
+ LOG_CLASS(FSRadar);
+
+friend class LLSingleton;
+
+public:
+// [RLVa:KB] - Checked: 2010-04-05 (RLVa-1.2.0d) | Added: RLVa-1.2.0d
+ LLAvatarList* getNearbyList() { return mNearbyList; }
+// [/RLVa:KB]
+
+ void startTracking(const LLUUID& avatar_id);
+ void zoomAvatar(const LLUUID& avatar_id, const std::string& name);
+ void teleportToAvatar(const LLUUID& targetAv);
+ void requestRadarChannelAlertSync();
+
+ static void onRadarNameFmtClicked(const LLSD& userdata);
+ static bool radarNameFmtCheck(const LLSD& userdata);
+ static void onRadarReportToClicked(const LLSD& userdata);
+ static bool radarReportToCheck(const LLSD& userdata);
+
+ void getCurrentData(std::vector& entries, LLSD& stats) const { entries = mRadarEntriesData; stats = mAvatarStats; }
+
+ // internals
+ class Updater
+ {
+ public:
+ typedef boost::function callback_t;
+ Updater(callback_t cb)
+ : mCallback(cb)
+ { }
+
+ virtual ~Updater()
+ { }
+
+ protected:
+ void update()
+ {
+ mCallback();
+ }
+
+ callback_t mCallback;
+ };
+
+ typedef boost::signals2::signal& entries, const LLSD& stats)> radar_update_callback_t;
+ boost::signals2::connection setUpdateCallback(const radar_update_callback_t::slot_type& cb)
+ {
+ return mUpdateSignal.connect(cb);
+ }
+
+private:
+ FSRadar();
+ virtual ~FSRadar();
+
+ void updateRadarList();
+ void updateTracking();
+ void checkTracking();
+
+ std::string getRadarName(const LLUUID& avId);
+ std::string getRadarName(const LLAvatarName& avName);
+ void radarAlertMsg(const LLUUID& agent_id, const LLAvatarName& av_name, const std::string& postMsg);
+
+ LLAvatarList* mNearbyList;
+
+ Updater* mRadarListUpdater;
+
+ struct radarFields
+ {
+ std::string avName;
+ F32 lastDistance;
+ LLVector3d lastGlobalPos;
+ LLUUID lastRegion;
+ time_t firstSeen;
+ S32 lastStatus;
+ U32 ZOffset;
+ time_t lastZOffsetTime;
+
+ };
+
+ std::multimap < LLUUID, radarFields > lastRadarSweep;
+ std::vector mRadarEnterAlerts;
+ std::vector mRadarLeaveAlerts;
+ std::vector mRadarOffsetRequests;
+ std::vector mRadarEntriesData;
+
+ S32 mRadarFrameCount;
+ bool mRadarAlertRequest;
+ F32 mRadarLastRequestTime;
+ U32 mRadarLastBulkOffsetRequestTime;
+
+ LLUUID mTrackedAvatarId;
+ LLSD mAvatarStats;
+
+ radar_update_callback_t mUpdateSignal;
+};
+
+#endif // FS_RADAR_H
diff --git a/indra/newview/fsradarmenu.cpp b/indra/newview/fsradarmenu.cpp
new file mode 100644
index 0000000000..e57edc382b
--- /dev/null
+++ b/indra/newview/fsradarmenu.cpp
@@ -0,0 +1,250 @@
+/**
+ * @file fsradarmenu.cpp
+ * @brief Menu used by Firestorm radar
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (c) 2013 Ansariel Hiller @ Second Life
+ *
+ * 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
+ *
+ * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
+ * http://www.firestormviewer.org
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+// libs
+#include "llmenugl.h"
+#include "lluictrlfactory.h"
+
+#include "fsradarmenu.h"
+
+// newview
+#include "fsradar.h"
+#include "llagent.h"
+#include "llagentdata.h" // for gAgentID
+#include "llavataractions.h"
+#include "llcallingcard.h" // for LLAvatarTracker
+#include "llviewermenu.h" // for gMenuHolder
+
+namespace FSFloaterRadarMenu
+{
+
+FSRadarMenu gFSRadarMenu;
+
+//== NearbyMenu ===============================================================
+
+LLContextMenu* FSRadarMenu::createMenu()
+{
+ // set up the callbacks for all of the avatar menu items
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+ LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
+
+ if ( mUUIDs.size() == 1 )
+ {
+ // Set up for one person selected menu
+
+ const LLUUID& id = mUUIDs.front();
+ registrar.add("Avatar.Profile", boost::bind(&LLAvatarActions::showProfile, id));
+ registrar.add("Avatar.AddFriend", boost::bind(&LLAvatarActions::requestFriendshipDialog, id));
+ registrar.add("Avatar.RemoveFriend", boost::bind(&LLAvatarActions::removeFriendDialog, id));
+ registrar.add("Avatar.IM", boost::bind(&LLAvatarActions::startIM, id));
+ registrar.add("Avatar.Call", boost::bind(&LLAvatarActions::startCall, id));
+ registrar.add("Avatar.OfferTeleport", boost::bind(&FSRadarMenu::offerTeleport, this));
+ registrar.add("Avatar.GroupInvite", boost::bind(&LLAvatarActions::inviteToGroup, id));
+ registrar.add("Avatar.getScriptInfo", boost::bind(&LLAvatarActions::getScriptInfo, id));
+ registrar.add("Avatar.ShowOnMap", boost::bind(&LLAvatarActions::showOnMap, id));
+ registrar.add("Avatar.Share", boost::bind(&LLAvatarActions::share, id));
+ registrar.add("Avatar.Pay", boost::bind(&LLAvatarActions::pay, id));
+ registrar.add("Avatar.BlockUnblock", boost::bind(&LLAvatarActions::toggleBlock, id));
+ registrar.add("Avatar.ZoomIn", boost::bind(&LLAvatarActions::zoomIn, id));
+ registrar.add("Avatar.Report", boost::bind(&LLAvatarActions::report, id));
+ registrar.add("Avatar.Eject", boost::bind(&LLAvatarActions::landEject, id));
+ registrar.add("Avatar.Freeze", boost::bind(&LLAvatarActions::landFreeze, id));
+ registrar.add("Avatar.Kick", boost::bind(&LLAvatarActions::estateKick, id));
+ registrar.add("Avatar.TeleportHome", boost::bind(&LLAvatarActions::estateTeleportHome, id));
+ registrar.add("Avatar.EstateBan", boost::bind(&LLAvatarActions::estateBan, id));
+ registrar.add("Avatar.Derender", boost::bind(&LLAvatarActions::derender, id, false));
+ registrar.add("Avatar.DerenderPermanent", boost::bind(&LLAvatarActions::derender, id, true));
+ registrar.add("Nearby.People.TeleportToAvatar", boost::bind(&FSRadarMenu::teleportToAvatar, this));
+ registrar.add("Nearby.People.TrackAvatar", boost::bind(&FSRadarMenu::onTrackAvatarMenuItemClick, this));
+
+ enable_registrar.add("Avatar.EnableItem", boost::bind(&FSRadarMenu::enableContextMenuItem, this, _2));
+ enable_registrar.add("Avatar.CheckItem", boost::bind(&FSRadarMenu::checkContextMenuItem, this, _2));
+ enable_registrar.add("Avatar.VisibleZoomIn", boost::bind(&LLAvatarActions::canZoomIn, id));
+ enable_registrar.add("Avatar.VisibleFreezeEject", boost::bind(&LLAvatarActions::canLandFreezeOrEject, id));
+ enable_registrar.add("Avatar.VisibleKickTeleportHome", boost::bind(&LLAvatarActions::canEstateKickOrTeleportHome, id));
+
+ // create the context menu from the XUI
+ return createFromFile("menu_fs_radar.xml");
+ }
+ else
+ {
+ // Set up for multi-selected People
+
+ // registrar.add("Avatar.AddFriend", boost::bind(&LLAvatarActions::requestFriendshipDialog, mUUIDs)); // *TODO: unimplemented
+ registrar.add("Avatar.IM", boost::bind(&LLAvatarActions::startConference, mUUIDs));
+ registrar.add("Avatar.Call", boost::bind(&LLAvatarActions::startAdhocCall, mUUIDs));
+ registrar.add("Avatar.OfferTeleport", boost::bind(&FSRadarMenu::offerTeleport, this));
+ registrar.add("Avatar.RemoveFriend", boost::bind(&LLAvatarActions::removeFriendsDialog, mUUIDs));
+ // registrar.add("Avatar.Share", boost::bind(&LLAvatarActions::startIM, mUUIDs)); // *TODO: unimplemented
+ // registrar.add("Avatar.Pay", boost::bind(&LLAvatarActions::pay, mUUIDs)); // *TODO: unimplemented
+ registrar.add("Avatar.Eject", boost::bind(&LLAvatarActions::landEjectMultiple, mUUIDs));
+ registrar.add("Avatar.Freeze", boost::bind(&LLAvatarActions::landFreezeMultiple, mUUIDs));
+ registrar.add("Avatar.Kick", boost::bind(&LLAvatarActions::estateKickMultiple, mUUIDs));
+ registrar.add("Avatar.TeleportHome", boost::bind(&LLAvatarActions::estateTeleportHomeMultiple, mUUIDs));
+ registrar.add("Avatar.EstateBan", boost::bind(&LLAvatarActions::estateBanMultiple, mUUIDs));
+ registrar.add("Avatar.Derender", boost::bind(&LLAvatarActions::derenderMultiple, mUUIDs, false));
+ registrar.add("Avatar.DerenderPermanent", boost::bind(&LLAvatarActions::derenderMultiple, mUUIDs, true));
+
+ enable_registrar.add("Avatar.EnableItem", boost::bind(&FSRadarMenu::enableContextMenuItem, this, _2));
+ enable_registrar.add("Avatar.VisibleFreezeEject", boost::bind(&LLAvatarActions::canLandFreezeOrEjectMultiple, mUUIDs, false));
+ enable_registrar.add("Avatar.VisibleKickTeleportHome", boost::bind(&LLAvatarActions::canEstateKickOrTeleportHomeMultiple, mUUIDs, false));
+
+ // create the context menu from the XUI
+ return createFromFile("menu_fs_radar_multiselect.xml");
+ }
+}
+
+bool FSRadarMenu::enableContextMenuItem(const LLSD& userdata)
+{
+ std::string item = userdata.asString();
+
+ // Note: can_block and can_delete is used only for one person selected menu
+ // so we don't need to go over all uuids.
+
+ if (item == std::string("can_block"))
+ {
+ const LLUUID& id = mUUIDs.front();
+ return LLAvatarActions::canBlock(id);
+ }
+ else if (item == std::string("can_add"))
+ {
+ // We can add friends if:
+ // - there are selected people
+ // - and there are no friends among selection yet.
+
+ //EXT-7389 - disable for more than 1
+ if(mUUIDs.size() > 1)
+ {
+ return false;
+ }
+
+ bool result = (mUUIDs.size() > 0);
+
+ uuid_vec_t::const_iterator
+ id = mUUIDs.begin(),
+ uuids_end = mUUIDs.end();
+
+ for (;id != uuids_end; ++id)
+ {
+ if ( LLAvatarActions::isFriend(*id) )
+ {
+ result = false;
+ break;
+ }
+ }
+
+ return result;
+ }
+ else if (item == std::string("can_delete"))
+ {
+ // We can remove friends if:
+ // - there are selected people
+ // - and there are only friends among selection.
+
+ bool result = (mUUIDs.size() > 0);
+
+ uuid_vec_t::const_iterator
+ id = mUUIDs.begin(),
+ uuids_end = mUUIDs.end();
+
+ for (;id != uuids_end; ++id)
+ {
+ if ( !LLAvatarActions::isFriend(*id) )
+ {
+ result = false;
+ break;
+ }
+ }
+
+ return result;
+ }
+ else if (item == std::string("can_call"))
+ {
+ return LLAvatarActions::canCall();
+ }
+ else if (item == std::string("can_show_on_map"))
+ {
+ const LLUUID& id = mUUIDs.front();
+
+ return (LLAvatarTracker::instance().isBuddyOnline(id) && is_agent_mappable(id))
+ || gAgent.isGodlike();
+ }
+ // Prevent teleport button from being disabled when someone on your
+ // friends list logs out but is still in the region and you have
+ // multiple people selected.
+ //else if(item == std::string("can_offer_teleport"))
+ //{
+ // return LLAvatarActions::canOfferTeleport(mUUIDs);
+ //}
+ //
+ return false;
+}
+
+bool FSRadarMenu::checkContextMenuItem(const LLSD& userdata)
+{
+ std::string item = userdata.asString();
+ const LLUUID& id = mUUIDs.front();
+
+ if (item == std::string("is_blocked"))
+ {
+ return LLAvatarActions::isBlocked(id);
+ }
+
+ return false;
+}
+
+void FSRadarMenu::offerTeleport()
+{
+ // boost::bind cannot recognize overloaded method LLAvatarActions::offerTeleport(),
+ // so we have to use a wrapper.
+ LLAvatarActions::offerTeleport(mUUIDs);
+}
+
+void FSRadarMenu::teleportToAvatar()
+// AO: wrapper for functionality managed by LLPanelPeople, because it manages the nearby avatar list.
+// Will only work for avatars within radar range.
+{
+ FSRadar* radar = FSRadar::getInstance();
+ if (radar)
+ {
+ radar->teleportToAvatar(mUUIDs.front());
+ }
+}
+
+// Ansariel: Avatar tracking feature
+void FSRadarMenu::onTrackAvatarMenuItemClick()
+{
+ FSRadar* radar = FSRadar::getInstance();
+ if (radar)
+ {
+ radar->startTracking(mUUIDs.front());
+ }
+}
+
+} // namespace FSFloaterRadarMenu
diff --git a/indra/newview/fsradarmenu.h b/indra/newview/fsradarmenu.h
new file mode 100644
index 0000000000..01a0dba4b0
--- /dev/null
+++ b/indra/newview/fsradarmenu.h
@@ -0,0 +1,55 @@
+/**
+ * @file fsradarmenu.h
+ * @brief Menu used by Firestorm radar
+ *
+ * $LicenseInfo:firstyear=2013&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (c) 2013 Ansariel Hiller @ Second Life
+ *
+ * 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
+ *
+ * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
+ * http://www.firestormviewer.org
+ * $/LicenseInfo$
+ */
+
+#ifndef FS_RADARMENU_H
+#define FS_RADARMENU_H
+
+#include "lllistcontextmenu.h"
+
+namespace FSFloaterRadarMenu
+{
+
+/**
+ * Menu used in the nearby people list.
+ */
+class FSRadarMenu : public LLListContextMenu
+{
+public:
+ /*virtual*/ LLContextMenu* createMenu();
+private:
+ bool enableContextMenuItem(const LLSD& userdata);
+ bool checkContextMenuItem(const LLSD& userdata);
+ void offerTeleport();
+ void teleportToAvatar();
+ void onTrackAvatarMenuItemClick();
+};
+
+extern FSRadarMenu gFSRadarMenu;
+
+} // namespace FSFloaterRadarMenu
+
+#endif // FS_RADARMENU_H
diff --git a/indra/newview/fsslurlcommand.cpp b/indra/newview/fsslurlcommand.cpp
index 855193dec8..bdea6cb1c0 100644
--- a/indra/newview/fsslurlcommand.cpp
+++ b/indra/newview/fsslurlcommand.cpp
@@ -28,22 +28,21 @@
#include "fsslurlcommand.h"
#include "fscommon.h"
+#include "fsradar.h"
#include "llagent.h"
#include "llavataractions.h"
#include "llavatarlist.h"
#include "llavatarlistitem.h"
#include "llcommandhandler.h"
-#include "llfloatersidepanelcontainer.h"
#include "llnotificationsutil.h"
-#include "llpanelpeople.h"
LLAvatarListItem* getAvatarListItem(const LLUUID& avatar_id)
{
- LLPanelPeople* panel_people = getPeoplePanel();
- if (panel_people)
+ FSRadar* radar = FSRadar::getInstance();
+ if (radar)
{
- return panel_people->getNearbyList()->getAvatarListItem(avatar_id);
+ return radar->getNearbyList()->getAvatarListItem(avatar_id);
}
return NULL;
@@ -118,11 +117,11 @@ public:
{
if (gAgentID != target_id)
{
- LLPanelPeople* panel_people = getPeoplePanel();
+ FSRadar* radar = FSRadar::getInstance();
LLAvatarListItem* avatar_list_item = getAvatarListItem(target_id);
- if (avatar_list_item && panel_people)
+ if (avatar_list_item && radar)
{
- panel_people->startTracking(target_id);
+ radar->startTracking(target_id);
return true;
}
diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp
index f1e2e15896..22455d0b63 100644
--- a/indra/newview/llnetmap.cpp
+++ b/indra/newview/llnetmap.cpp
@@ -77,17 +77,13 @@
// [RLVa:KB] - Checked: 2010-04-19 (RLVa-1.2.0f)
#include "rlvhandler.h"
// [/RLVa:KB]
-#include "lltrans.h"
#include "llmutelist.h"
// Ansariel: For accessing the radar data
#include "llavatarlist.h"
#include "llavatarlistitem.h"
-#include "llpanelpeople.h"
-#include "llfloatersidepanelcontainer.h"
+#include "fsradar.h"
#include "lggcontactsets.h"
-
-#include "llavataractions.h"
#include "fscommon.h"
static LLDefaultChildRegistry::Register r1("net_map");
@@ -1018,10 +1014,10 @@ BOOL LLNetMap::handleToolTipAgent(const LLUUID& avatar_id)
// aka radar when above 1020m.
if (isHigher1020mBug)
{
- LLPanelPeople* panel_people = getPeoplePanel();
- if (panel_people)
+ FSRadar* radar = FSRadar::getInstance();
+ if (radar)
{
- LLAvatarListItem* avatar_list_item = panel_people->getNearbyList()->getAvatarListItem(avatar_id);
+ LLAvatarListItem* avatar_list_item = radar->getNearbyList()->getAvatarListItem(avatar_id);
if (avatar_list_item)
{
F32 radar_distance = avatar_list_item->getRange();
@@ -1710,10 +1706,10 @@ void LLNetMap::startTracking()
{
if (mClosestAgentRightClick.notNull())
{
- LLPanelPeople* panel_people = getPeoplePanel();
- if (panel_people != NULL)
+ FSRadar* radar = FSRadar::getInstance();
+ if (radar)
{
- panel_people->startTracking(mClosestAgentRightClick);
+ radar->startTracking(mClosestAgentRightClick);
}
}
}
diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp
index eb2be08798..a02a3d9266 100644
--- a/indra/newview/llpanelpeople.cpp
+++ b/indra/newview/llpanelpeople.cpp
@@ -30,20 +30,18 @@
#include "llavatarname.h"
#include "llfloaterreg.h"
#include "llfloatersidepanelcontainer.h"
+#include "llmenubutton.h"
#include "llmenugl.h"
#include "llnotificationsutil.h"
#include "lleventtimer.h"
#include "llfiltereditor.h"
#include "lltabcontainer.h"
-#include "lluictrlfactory.h"
-#include "llmenubutton.h"
#include "lltoggleablemenu.h"
-
+#include "lluictrlfactory.h"
#include "llpanelpeople.h"
// newview
-#include "llavatarpropertiesprocessor.h"
#include "llaccordionctrl.h"
#include "llaccordionctrltab.h"
#include "llagent.h"
@@ -66,31 +64,16 @@
#include "llvoiceclient.h"
#include "llworld.h"
#include "llspeakers.h"
-// [RLVa:KB] - Checked: 2010-06-04 (RLVa-1.2.2a)
-#include "rlvhandler.h"
-// [/RLVa:KB]
+
+// Firestorm includes
#include "fscontactsfloater.h"
+#include "fsradar.h"
#include "fsradarlistctrl.h"
+#include "fsradarmenu.h"
#include "llavatarconstants.h" // for range constants
-#include
Pose Stand
Snapshot to Disk
+ Radar
Information about the land you're visiting
@@ -4910,6 +4911,7 @@ Try enclosing path to the editor with double quotes.
Opens Teleport History
Avatar pose stand for adjusting attachments
Takes a quick snapshot and saves it to harddisk
+ Opens the radar for nearby avatars
currently in your bottom toolbar
currently in your left toolbar
@@ -5082,4 +5084,14 @@ Try enclosing path to the editor with double quotes.
None
Muting group chat from [NAME].
+
+
+ The camera cannot focus user [AVATARNAME] because they are outside your draw distance.
+ entered draw distance ([DISTANCE] m).
+ left draw distance.
+ entered chat range ([DISTANCE] m).
+ left chat range.
+ entered the region.
+ entered the region ([DISTANCE] m).
+ left the region.
diff --git a/indra/newview/skins/metaharper/xui/en/panel_people.xml b/indra/newview/skins/metaharper/xui/en/panel_people.xml
index dd533d52a4..9a6c657ec4 100644
--- a/indra/newview/skins/metaharper/xui/en/panel_people.xml
+++ b/indra/newview/skins/metaharper/xui/en/panel_people.xml
@@ -14,30 +14,6 @@
-
-
-
-
-
-
-
-
@@ -146,7 +122,7 @@ Looking for people to hang out with? Use the search box to find topics or conten
top="0" left="3"
height="140"
width="326"
- max_height="140"
+ min_dim="140"
follows="top"
mouse_opaque="false"
visibility_control="ShowRadarMinimap"
@@ -282,6 +258,7 @@ Looking for people to hang out with? Use the search box to find topics or conten
name="nearby_dummy_icon"
width="257" />
-
-
-
-
-
-
-
-
@@ -160,6 +136,7 @@ Looking for people to hang out with? Use the search box to find topics or conten
height="140"
width="313"
follows="all"
+ min_dim="140"
mouse_opaque="false"
visibility_control="ShowRadarMinimap"
name="minimaplayout"
@@ -362,6 +339,7 @@ Looking for people to hang out with? Use the search box to find topics or conten
layout="topleft"
follows="left|top" />