This commit is contained in:
Ansariel
2026-04-02 10:05:53 +02:00
316 changed files with 10681 additions and 6948 deletions
+2 -2
View File
@@ -386,7 +386,7 @@ jobs:
tar -xJf "${{ needs.build.outputs.viewer_channel }}.sym.tar.xz" -C _artifacts
- name: Post Windows symbols
if: env.BUGSPLAT_DATABASE && env.SYMBOL_UPLOAD_CLIENT_ID
uses: BugSplat-Git/symbol-upload@095d163ae9ceb006d286a731dcd35cf6a1b458c8
uses: BugSplat-Git/symbol-upload@f56c6c85e5d04890d5358fb378fa782d3e3800d8
with:
clientId: "${{ env.SYMBOL_UPLOAD_CLIENT_ID }}"
clientSecret: "${{ env.SYMBOL_UPLOAD_CLIENT_SECRET }}"
@@ -414,7 +414,7 @@ jobs:
name: macOS-symbols
- name: Post Mac symbols
if: env.BUGSPLAT_DATABASE && env.SYMBOL_UPLOAD_CLIENT_ID
uses: BugSplat-Git/symbol-upload@095d163ae9ceb006d286a731dcd35cf6a1b458c8
uses: BugSplat-Git/symbol-upload@f56c6c85e5d04890d5358fb378fa782d3e3800d8
with:
clientId: "${{ env.SYMBOL_UPLOAD_CLIENT_ID }}"
clientSecret: "${{ env.SYMBOL_UPLOAD_CLIENT_SECRET }}"
+2 -2
View File
@@ -440,9 +440,9 @@ jobs:
id: version
shell: bash
run: |
if [ -r "indra/newview/VIEWER_VERSION.txt" ]
if [ -r "indra/newview/VIEWER_VERSION_FS.txt" ]
then
viewer_version="$(<"indra/newview/VIEWER_VERSION.txt")"
viewer_version="$(<"indra/newview/VIEWER_VERSION_FS.txt")"
echo "viewer_version=$viewer_version" >> "$GITHUB_OUTPUT"
fi
viewer_build=$(git rev-list --count HEAD)
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
run: pip install discord-webhook
- name: Download Build Artifacts
uses: dawidd6/action-download-artifact@v16
uses: dawidd6/action-download-artifact@v19
id: download
with:
workflow: build_viewer.yml
+2 -2
View File
@@ -41,7 +41,7 @@ jobs:
setup_files: ${{ steps.get-files.outputs.setup_files }}
steps:
- name: Download Build Artifacts
uses: dawidd6/action-download-artifact@v16
uses: dawidd6/action-download-artifact@v19
id: download
with:
workflow: build_viewer.yml
@@ -115,7 +115,7 @@ jobs:
# wait-for-completion: true
# output-artifact-directory: 'application-signed'
- name: Azure Trusted Signing
uses: azure/trusted-signing-action@v1.1.0
uses: azure/trusted-signing-action@v1.2.0
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
+1 -1
View File
@@ -72,7 +72,7 @@ jobs:
echo "build_run_number=${{ github.event.inputs.build_run_number }}" >> "$GITHUB_OUTPUT"
fi
- name: Download Build Artifacts
uses: dawidd6/action-download-artifact@v16
uses: dawidd6/action-download-artifact@v19
id: download_build_info
with:
workflow: build_viewer.yml
+2 -2
View File
@@ -2724,11 +2724,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<key>archive</key>
<map>
<key>hash</key>
<string>52bfa9057a5d34cd841e93624c03a0cdcdf7b12a</string>
<string>05e30110611a8fd71c79eebe1fc33f429d98efb0</string>
<key>hash_algorithm</key>
<string>sha1</string>
<key>url</key>
<string>https://3p.firestormviewer.org/viewer_fonts-1.0.0.242151216-common-242151216.tar.bz2</string>
<string>https://3p.firestormviewer.org/viewer_fonts-1.1.2-common-260701806.tar.bz2</string>
</map>
<key>name</key>
<string>common</string>
+4 -2
View File
@@ -2,7 +2,9 @@
# Construct the viewer version number based on the indra/VIEWER_VERSION file
if (NOT DEFINED VIEWER_SHORT_VERSION) # will be true in indra/, false in indra/newview/
set(VIEWER_VERSION_BASE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/newview/VIEWER_VERSION.txt")
# <FS:Ansariel> Use own version file to avoid LL's year-as-major-version thing
# set(VIEWER_VERSION_BASE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/newview/VIEWER_VERSION.txt")
set(VIEWER_VERSION_BASE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/newview/VIEWER_VERSION_FS.txt")
if ( EXISTS ${VIEWER_VERSION_BASE_FILE} )
file(STRINGS ${VIEWER_VERSION_BASE_FILE} VIEWER_SHORT_VERSION REGEX "^[0-9]+\\.[0-9]+\\.[0-9]+")
@@ -47,7 +49,7 @@ if (NOT DEFINED VIEWER_SHORT_VERSION) # will be true in indra/, false in indra/n
endif ("${VIEWER_VERSION_REVISION}" STREQUAL "")
# <FS:PP>
set(VIEWER_VERSION_LL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/newview/VIEWER_VERSION_LL.txt")
set(VIEWER_VERSION_LL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/newview/VIEWER_VERSION.txt")
if (EXISTS ${VIEWER_VERSION_LL_FILE})
file(STRINGS ${VIEWER_VERSION_LL_FILE} VIEWER_VERSION_LL LIMIT_COUNT 1)
string(STRIP "${VIEWER_VERSION_LL}" VIEWER_VERSION_LL)
+22 -10
View File
@@ -1629,6 +1629,7 @@ bool LLModel::loadDecomposition(LLSD& header, std::istream& is)
}
LLMeshSkinInfo::LLMeshSkinInfo():
mBindShapeMatrix(LLMatrix4a::identity()), // <FS:Beq/> Keep LLMeshSkinInfo default state in one place
mPelvisOffset(0.0),
mLockScaleIfJointPosition(false),
mInvalidJointsScrubbed(false),
@@ -1636,22 +1637,34 @@ LLMeshSkinInfo::LLMeshSkinInfo():
{
}
LLMeshSkinInfo::LLMeshSkinInfo(LLSD& skin):
mPelvisOffset(0.0),
mLockScaleIfJointPosition(false),
mInvalidJointsScrubbed(false),
mJointNumsInitialized(false)
// <FS:Beq> Keep LLMeshSkinInfo default state in one place
// mPelvisOffset(0.0),
// mLockScaleIfJointPosition(false),
// mInvalidJointsScrubbed(false),
// mJointNumsInitialized(false)
// {
// mBindShapeMatrix = LLMatrix4a::identity();
LLMeshSkinInfo()
{
// </FS:Beq>
fromLLSD(skin);
}
LLMeshSkinInfo::LLMeshSkinInfo(const LLUUID& mesh_id, LLSD& skin) :
mMeshID(mesh_id),
mPelvisOffset(0.0),
mLockScaleIfJointPosition(false),
mInvalidJointsScrubbed(false),
mJointNumsInitialized(false)
// <FS:Beq> Keep LLMeshSkinInfo default state in one place
// mMeshID(mesh_id),
// mPelvisOffset(0.0),
// mLockScaleIfJointPosition(false),
// mInvalidJointsScrubbed(false),
// mJointNumsInitialized(false)
// {
// mBindShapeMatrix = LLMatrix4a::identity();
LLMeshSkinInfo()
{
// </FS:Beq>
mMeshID = mesh_id;
fromLLSD(skin);
}
@@ -2388,4 +2401,3 @@ bool LLImportMaterial::operator<(const LLImportMaterial &rhs) const
return mFullbright < rhs.mFullbright;
}
+1
View File
@@ -443,6 +443,7 @@ void LLFlatListView::sort()
mItemPairs.sort(ComparatorAdaptor(*mItemComparator));
rearrangeItems();
notifyParentItemsRectChanged(); // <FS:PP> FIRE-36479 Multiple nested scrollbars in Appearance window
}
bool LLFlatListView::updateValue(const LLSD& old_value, const LLSD& new_value)
+2
View File
@@ -113,6 +113,7 @@ public:
virtual bool isText() const { return false; }
virtual bool needsToolTip() const { return ! mToolTip.empty(); }
virtual void setColor(const LLColor4&) {}
virtual void setUseColor(bool) {} // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
virtual void onCommit() {};
virtual bool handleClick() { return false; }
@@ -151,6 +152,7 @@ public:
/*virtual*/ void highlightText(S32 offset, S32 num_chars);
/*virtual*/ void setColor(const LLColor4&);
/*virtual*/ void setUseColor(bool use_color) { mUseColor = use_color; } // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
/*virtual*/ bool isText() const;
/*virtual*/ const std::string & getToolTip() const;
/*virtual*/ bool needsToolTip() const;
+8 -1
View File
@@ -124,6 +124,7 @@ set(viewer_SOURCE_FILES
fsfloaterexport.cpp
fsfloatergroup.cpp
fsfloatergrouptitles.cpp
fsgrouptitleregionmgr.cpp
fsfloaterimport.cpp
fsfloaterim.cpp
fsfloaterimcontainer.cpp
@@ -163,6 +164,7 @@ set(viewer_SOURCE_FILES
fspanelface.cpp
fspanelimcontrolpanel.cpp
fspanellogin.cpp
fspanelpreferenceuisounds.cpp
fspanelprefs.cpp
fspanelradar.cpp
fsparticipantlist.cpp
@@ -198,7 +200,9 @@ set(viewer_SOURCE_FILES
llagentwearablesfetch.cpp
vjlocalmesh.cpp
vjfloaterlocalmesh.cpp
fslocalmeshimportbase.cpp
vjlocalmeshimportdae.cpp
fslocalmeshimportgltf.cpp
gltfscenemanager.cpp
# <FS:Ansariel> Group GLTF files into their subfolders
@@ -976,6 +980,7 @@ set(viewer_HEADER_FILES
fsfloaterexport.h
fsfloatergroup.h
fsfloatergrouptitles.h
fsgrouptitleregionmgr.h
fsfloaterimport.h
fsfloaterim.h
fsfloaterimcontainer.h
@@ -1016,6 +1021,7 @@ set(viewer_HEADER_FILES
fspanelface.h
fspanelimcontrolpanel.h
fspanellogin.h
fspanelpreferenceuisounds.h
fspanelprefs.h
fspanelradar.h
fsparticipantlist.h
@@ -1051,7 +1057,9 @@ set(viewer_HEADER_FILES
omnifilterengine.h
vjlocalmesh.h
vjfloaterlocalmesh.h
fslocalmeshimportbase.h
vjlocalmeshimportdae.h
fslocalmeshimportgltf.h
gltfscenemanager.h
groupchatlistener.h
@@ -3140,4 +3148,3 @@ if (LL_TESTS)
endif (LL_TESTS)
check_message_template(${VIEWER_BINARY_NAME})
+1
View File
@@ -0,0 +1 @@
7.2.4
-1
View File
@@ -1 +0,0 @@
7.2.3
+2 -4
View File
@@ -229,10 +229,8 @@
icon="Command_Search_Icon"
label_ref="Command_Search_Label"
tooltip_ref="Command_Search_Tooltip"
execute_function="Floater.ToggleOrBringToFront"
execute_parameters="search"
is_running_function="Floater.IsOpen"
is_running_parameters="search"
execute_function="Avatar.ToggleSearch"
is_running_function="Avatar.SearchVisible"
/>
<command name="snapshot"
available_in_toybox="true"
+102 -3
View File
@@ -24,6 +24,17 @@
<key>Value</key>
<integer>1</integer>
</map>
<key>FSEventPollCoreRetries</key>
<map>
<key>Comment</key>
<string>DEBUG: EventQueueGet llcorehttp retry count. 0 keeps current 7.2.3 behavior; 5 matches 7.2.2 behaviour.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>U32</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSUseV2Friends</key>
<map>
<key>Comment</key>
@@ -465,6 +476,17 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>FSContactSetsColorizeFriends</key>
<map>
<key>Comment</key>
<string>Whether to color a friends list entry based on their friends groups</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSMuteAllGroups</key>
<map>
<key>Comment</key>
@@ -3284,6 +3306,28 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>FSCloseChatOnReturnInMouselook</key>
<map>
<key>Comment</key>
<string>When CloseChatOnReturn is enabled, only deselect nearby chat in mouselook</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>FSCloseChatOnReturnOnlyForNearbyChatControl</key>
<map>
<key>Comment</key>
<string>When CloseChatOnReturn is enabled, only deselect nearby chat bar</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>CmdLineChannel</key>
<map>
<key>Comment</key>
@@ -9819,7 +9863,7 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>PlaySoundChatMention</key>
<map>
<key>Comment</key>
<string>Plays a sound when got mentioned in a chat</string>
<string>Plays a sound when got mentioned in a chat - UNUSED IN FS - see PlayModeUISndChatMention</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
@@ -16689,6 +16733,17 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<string>4174f859-0d3d-c517-c424-72923dc21f65</string>
</map>
<key>UISndNearbyChat</key>
<map>
<key>Comment</key>
<string>Sound file for nearby chat messages from other avatars (uuid for sound asset)</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>String</string>
<key>Value</key>
<string>a3f48b85-c29f-1f97-ebb6-644b7c053512</string>
</map>
<key>UISndNewIncomingIMSession</key>
<map>
<key>Comment</key>
@@ -18210,7 +18265,7 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Type</key>
<string>S32</string>
<key>Value</key>
<integer>1</integer>
<integer>0</integer>
</map>
<key>WaterEditPresets</key>
<map>
@@ -22329,6 +22384,17 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>1</integer>
</map>
<key>PlayModeUISndChatMention</key>
<map>
<key>Comment</key>
<string>Holds state for Prefs &gt; Sound/Media &gt; UI Sounds - UISndChatMention.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>PlayModeUISndClick</key>
<map>
<key>Comment</key>
@@ -22505,6 +22571,17 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>1</integer>
</map>
<key>PlayModeUISndNearbyChat</key>
<map>
<key>Comment</key>
<string>Holds state for Prefs &gt; Sound/Media &gt; UI Sounds - UISndNearbyChat.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>PlayModeUISndNewIncomingIMSession</key>
<map>
<key>Comment</key>
@@ -26656,6 +26733,28 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Value</key>
<integer>0</integer>
</map>
<key>FSLocalMeshAutoReload</key>
<map>
<key>Comment</key>
<string>Automatically reload local mesh files when they change on disk.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
</map>
<key>FSLocalMeshAutoReloadPeriod</key>
<map>
<key>Comment</key>
<string>How often, in seconds, the viewer should scan local mesh files for changes.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>3.0</real>
</map>
<key>FSShowEmojiButton</key>
<map>
<key>Comment</key>
@@ -27389,7 +27488,7 @@ Change of this parameter will affect the layout of buttons in notification toast
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
<integer>0</integer>
</map>
</map>
</llsd>
@@ -999,6 +999,17 @@
<key>Value</key>
<string>0</string>
</map>
<key>FSGesturesEnabled</key>
<map>
<key>Comment</key>
<string>Enable gesture playback and trigger handling viewer-wide - for your own gestures</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>UseLSLFlightAssist</key>
<map>
<key>Comment</key>
+48 -4
View File
@@ -39,7 +39,26 @@ FSFloaterAddToContactSet::FSFloaterAddToContactSet(const LLSD& target)
: LLFloater(target),
mContactSetsCombo(nullptr)
{
if (target.isArray())
if (target.isMap())
{
mIsMoveOperation = (target["operation"].asString() == "move");
mSourceSet = target["source_set"].asString();
const LLSD ids = target["ids"];
if (ids.isArray())
{
mHasMultipleAgents = true;
for (LLSD::array_const_iterator it = ids.beginArray(); it != ids.endArray(); ++it)
{
mAgentIDs.push_back((*it).asUUID());
}
}
else if (target.has("id"))
{
mHasMultipleAgents = false;
mAgentID = target["id"].asUUID();
}
}
else if (target.isArray())
{
mHasMultipleAgents = true;
for (LLSD::array_const_iterator it = target.beginArray(); it != target.endArray(); ++it)
@@ -92,22 +111,41 @@ bool FSFloaterAddToContactSet::postBuild()
void FSFloaterAddToContactSet::onClickAdd()
{
const std::string set = mContactSetsCombo->getSimple();
if (set.empty())
{
return;
}
if (!mHasMultipleAgents)
{
if (mAgentID.isNull())
{
return;
}
mAgentIDs.push_back(mAgentID);
}
else if (mAgentIDs.empty())
{
return;
}
LGGContactSets::instance().addToSet(mAgentIDs, set);
if (mIsMoveOperation && set != mSourceSet)
{
for (const LLUUID& id : mAgentIDs)
{
LGGContactSets::instance().removeFriendFromSet(id, mSourceSet);
}
}
if (mHasMultipleAgents)
if (!mIsMoveOperation && mHasMultipleAgents)
{
LLSD args;
args["COUNT"] = llformat("%d", mAgentIDs.size());
args["SET"] = set;
LLNotificationsUtil::add("AddToContactSetMultipleSuccess", args);
}
else
else if (!mIsMoveOperation)
{
LLSD args;
args["NAME"] = LLSLURL("agent", mAgentID, "inspect").getSLURLString();
@@ -144,6 +182,7 @@ void FSFloaterAddToContactSet::populateContactSets()
mContactSetsCombo->clearRows();
std::vector<std::string> contact_sets = LGGContactSets::getInstance()->getAllContactSets();
bool isEmptySet = true;
if (contact_sets.empty())
{
mContactSetsCombo->add(getString("no_sets"), LLSD("No Set"));
@@ -152,8 +191,13 @@ void FSFloaterAddToContactSet::populateContactSets()
{
for (auto const& set_name : contact_sets)
{
if (mIsMoveOperation && set_name == mSourceSet)
{
continue;
}
mContactSetsCombo->add(set_name);
isEmptySet = false;
}
}
getChild<LLButton>("add_btn")->setEnabled(!contact_sets.empty());
getChild<LLButton>("add_btn")->setEnabled(!isEmptySet);
}
+3 -1
View File
@@ -52,9 +52,11 @@ private:
void updateSets(LGGContactSets::EContactSetUpdate type);
boost::signals2::connection mContactSetChangedConnection;
bool mHasMultipleAgents;
bool mHasMultipleAgents{ false };
bool mIsMoveOperation{ false };
LLUUID mAgentID;
uuid_vec_t mAgentIDs;
std::string mSourceSet;
LLComboBox* mContactSetsCombo;
};
+83 -41
View File
@@ -50,6 +50,7 @@
#include "lltooldraganddrop.h"
#include "llviewermenu.h"
#include "llvoiceclient.h"
#include "lggcontactsets.h"
// [RLVa:KB] - @pay
#include "rlvactions.h"
// [/RLVa:KB]
@@ -73,6 +74,7 @@ FSFloaterContacts::FSFloaterContacts(const LLSD& seed)
LLAvatarTracker::instance().addObserver(this);
// For notification when SIP online status changes.
LLVoiceClient::addObserver(this);
mContactSetChangedConnection = LGGContactSets::getInstance()->setContactSetChangeCallback(boost::bind(&FSFloaterContacts::onContactSetsChanged, this, _1));
}
FSFloaterContacts::~FSFloaterContacts()
@@ -81,6 +83,11 @@ FSFloaterContacts::~FSFloaterContacts()
LLVoiceClient::removeObserver(this);
LLAvatarTracker::instance().removeObserver(this);
if (mContactSetChangedConnection.connected())
{
mContactSetChangedConnection.disconnect();
}
if (mRlvBehaviorCallbackConnection.connected())
{
mRlvBehaviorCallbackConnection.disconnect();
@@ -181,6 +188,7 @@ bool FSFloaterContacts::postBuild()
gSavedSettings.getControl("FSFriendListColumnShowDisplayName")->getSignal()->connect(boost::bind(&FSFloaterContacts::onColumnDisplayModeChanged, this, "FSFriendListColumnShowDisplayName"));
gSavedSettings.getControl("FSFriendListColumnShowFullName")->getSignal()->connect(boost::bind(&FSFloaterContacts::onColumnDisplayModeChanged, this, "FSFriendListColumnShowFullName"));
gSavedSettings.getControl("FSFriendListColumnShowPermissions")->getSignal()->connect(boost::bind(&FSFloaterContacts::onColumnDisplayModeChanged, this, std::string()));
gSavedSettings.getControl("FSContactSetsColorizeFriends")->getSignal()->connect(boost::bind(&FSFloaterContacts::onDisplayNameChanged, this));
onColumnDisplayModeChanged();
LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&FSFloaterContacts::onDisplayNameChanged, this));
@@ -266,7 +274,7 @@ void FSFloaterContacts::onOpen(const LLSD& key)
// first set the tear-off host to the conversations container
setHost(floater_container);
// clear the tear-off host right after, the "last host used" will still stick
setHost(NULL);
setHost(nullptr);
// reparent to floater view
gFloaterView->addChild(this);
}
@@ -381,7 +389,7 @@ void FSFloaterContacts::onDeleteFriendButtonClicked()
}
}
bool FSFloaterContacts::isItemsFreeOfFriends(const uuid_vec_t& uuids)
bool FSFloaterContacts::isItemsFreeOfFriends(const uuid_vec_t& uuids) const
{
const LLAvatarTracker& av_tracker = LLAvatarTracker::instance();
for (const auto& id : uuids)
@@ -715,7 +723,7 @@ void FSFloaterContacts::addFriend(const LLUUID& agent_id)
update_gen_column["column"] = "friend_last_update_generation";
update_gen_column["value"] = relationInfo->getChangeSerialNum();
mFriendsList->addElement(element, ADD_BOTTOM);
updateFriendItemColor(mFriendsList->addElement(element, ADD_BOTTOM), agent_id);
}
void FSFloaterContacts::onMapButtonClicked()
@@ -767,6 +775,7 @@ void FSFloaterContacts::updateFriendItem(const LLUUID& agent_id, const LLRelatio
itemp->getColumn(LIST_FRIEND_USER_NAME)->setValue(av_name.getUserNameForDisplay());
itemp->getColumn(LIST_FRIEND_DISPLAY_NAME)->setValue(av_name.getDisplayName());
itemp->getColumn(LIST_FRIEND_NAME)->setValue(getFullName(av_name));
updateFriendItemColor(itemp, agent_id);
// render name of online friends in bold text
LLFontGL::StyleFlags font_style = ((isOnline || isOnlineSIP) ? LLFontGL::BOLD : LLFontGL::NORMAL);
@@ -786,6 +795,30 @@ void FSFloaterContacts::updateFriendItem(const LLUUID& agent_id, const LLRelatio
itemp->setEnabled(true);
}
void FSFloaterContacts::updateFriendItemColor(LLScrollListItem* item, const LLUUID& agent_id) const
{
LLColor4 name_color;
const bool has_contact_set_color = LGGContactSets::getInstance()->hasFriendColorThatShouldShow(agent_id, ContactSetType::FRIENDS, name_color);
LLScrollListCell* user_name_cell = item->getColumn(LIST_FRIEND_USER_NAME);
LLScrollListCell* display_name_cell = item->getColumn(LIST_FRIEND_DISPLAY_NAME);
LLScrollListCell* full_name_cell = item->getColumn(LIST_FRIEND_NAME);
if (has_contact_set_color)
{
user_name_cell->setColor(name_color);
display_name_cell->setColor(name_color);
full_name_cell->setColor(name_color);
user_name_cell->setUseColor(true);
display_name_cell->setUseColor(true);
full_name_cell->setUseColor(true);
}
else
{
user_name_cell->setUseColor(false);
display_name_cell->setUseColor(false);
full_name_cell->setUseColor(false);
}
}
void FSFloaterContacts::updateFriendItem(const LLUUID& agent_id, const LLRelationship* relationship, const LLUUID& request_id)
{
disconnectAvatarNameCacheConnection(request_id);
@@ -899,7 +932,7 @@ void FSFloaterContacts::onSelectName()
applyRightsToFriends();
}
void FSFloaterContacts::confirmModifyRights(rights_map_t& ids, EGrantRevoke command)
void FSFloaterContacts::confirmModifyRights(const rights_map_t& ids, EGrantRevoke command)
{
if (ids.empty())
{
@@ -907,48 +940,44 @@ void FSFloaterContacts::confirmModifyRights(rights_map_t& ids, EGrantRevoke comm
}
LLSD args;
if (ids.size() > 0)
{
rights_map_t* rights = new rights_map_t(ids);
rights_map_t* rights = new rights_map_t(ids);
// for single friend, show their name
if (ids.size() == 1)
// for single friend, show their name
if (ids.size() == 1)
{
args["NAME"] = LLSLURL("agent", ids.begin()->first, "completename").getSLURLString();
if (command == GRANT)
{
args["NAME"] = LLSLURL("agent", ids.begin()->first, "completename").getSLURLString();
if (command == GRANT)
{
LLNotificationsUtil::add("GrantModifyRights",
args,
LLSD(),
boost::bind(&FSFloaterContacts::modifyRightsConfirmation, this, _1, _2, rights));
}
else
{
LLNotificationsUtil::add("RevokeModifyRights",
args,
LLSD(),
boost::bind(&FSFloaterContacts::modifyRightsConfirmation, this, _1, _2, rights));
}
LLNotificationsUtil::add("GrantModifyRights",
args,
LLSD(),
boost::bind(&FSFloaterContacts::modifyRightsConfirmation, this, _1, _2, rights));
}
else
{
if (command == GRANT)
{
LLNotificationsUtil::add("GrantModifyRightsMultiple",
args,
LLSD(),
boost::bind(&FSFloaterContacts::modifyRightsConfirmation, this, _1, _2, rights));
}
else
{
LLNotificationsUtil::add("RevokeModifyRightsMultiple",
args,
LLSD(),
boost::bind(&FSFloaterContacts::modifyRightsConfirmation, this, _1, _2, rights));
}
LLNotificationsUtil::add("RevokeModifyRights",
args,
LLSD(),
boost::bind(&FSFloaterContacts::modifyRightsConfirmation, this, _1, _2, rights));
}
}
else
{
if (command == GRANT)
{
LLNotificationsUtil::add("GrantModifyRightsMultiple",
args,
LLSD(),
boost::bind(&FSFloaterContacts::modifyRightsConfirmation, this, _1, _2, rights));
}
else
{
LLNotificationsUtil::add("RevokeModifyRightsMultiple",
args,
LLSD(),
boost::bind(&FSFloaterContacts::modifyRightsConfirmation, this, _1, _2, rights));
}
}
}
bool FSFloaterContacts::modifyRightsConfirmation(const LLSD& notification, const LLSD& response, rights_map_t* rights)
@@ -1076,7 +1105,7 @@ void FSFloaterContacts::applyRightsToFriends()
}
}
void FSFloaterContacts::sendRightsGrant(rights_map_t& ids)
void FSFloaterContacts::sendRightsGrant(const rights_map_t& ids)
{
if (ids.empty())
{
@@ -1264,6 +1293,7 @@ void FSFloaterContacts::onDisplayNameChanged()
item->getColumn(LIST_FRIEND_USER_NAME)->setValue(av_name.getUserNameForDisplay());
item->getColumn(LIST_FRIEND_DISPLAY_NAME)->setValue(av_name.getDisplayName());
item->getColumn(LIST_FRIEND_NAME)->setValue(getFullName(av_name));
updateFriendItemColor(item, item->getUUID());
}
else
{
@@ -1274,7 +1304,7 @@ void FSFloaterContacts::onDisplayNameChanged()
mFriendsList->setNeedsSort();
}
std::string FSFloaterContacts::getFullName(const LLAvatarName& av_name)
std::string FSFloaterContacts::getFullName(const LLAvatarName& av_name) const
{
if (av_name.isDisplayNameDefault() || !gSavedSettings.getBOOL("UseDisplayNames"))
{
@@ -1376,6 +1406,18 @@ void FSFloaterContacts::onGroupFilterEdit(const std::string& search_string)
mGroupList->setNameFilter(search_string);
}
void FSFloaterContacts::onContactSetsChanged(LGGContactSets::EContactSetUpdate type)
{
if (!mFriendsList)
{
return;
}
if (type == LGGContactSets::UPDATED_LISTS || type == LGGContactSets::UPDATED_MEMBERS)
{
onDisplayNameChanged();
}
}
// <FS:TJ> [FIRE-35804] Allow the IM floater to have separate transparency
// This is specifically for making the filter editors such as mFriendFilter and mGroupFilter always active opacity when the IM floater is focused
// Otherwise if they aren't active, it will use either the IM opacity, or inactive opacity, whatever is smaller
+16 -13
View File
@@ -32,7 +32,7 @@
#include "llavatarnamecache.h"
#include "llcallingcard.h"
#include "llfloater.h"
#include "llscrolllistcolumn.h"
#include "lggcontactsets.h"
#include "rlvhandler.h"
class FSScrollListCtrl;
@@ -66,16 +66,16 @@ public:
void openTab(std::string_view name);
LLPanel* getPanelByName(std::string_view panel_name);
void sortFriendList();
void onDisplayNameChanged();
void resetFriendFilter();
void sortFriendList();
void onDisplayNameChanged();
void resetFriendFilter();
// <FS:TJ> [FIRE-35804] Allow the IM floater to have separate transparency
F32 onGetFilterOpacityCallback(ETypeTransparency type, F32 alpha);
// </FS:TJ>
private:
typedef std::vector<LLScrollListItem*> listitem_vec_t;
using listitem_vec_t = std::vector<LLScrollListItem*>;
std::string getActiveTabName() const;
LLUUID getCurrentItemID() const;
@@ -96,7 +96,7 @@ private:
LIST_FRIEND_UPDATE_GEN
};
typedef std::map<LLUUID, S32> rights_map_t;
using rights_map_t = std::map<LLUUID, S32>;
void refreshRightsChangeList();
void refreshUI();
void updateFriendCount();
@@ -105,18 +105,19 @@ private:
void addFriend(const LLUUID& agent_id);
void updateFriendItem(const LLUUID& agent_id, const LLRelationship* relationship);
void updateFriendItem(const LLUUID& agent_id, const LLRelationship* relationship, const LLUUID& request_id);
void updateFriendItemColor(LLScrollListItem* item, const LLUUID& agent_id) const;
typedef enum
enum EGrantRevoke
{
GRANT,
REVOKE
} EGrantRevoke;
void confirmModifyRights(rights_map_t& ids, EGrantRevoke command);
void sendRightsGrant(rights_map_t& ids);
};
void confirmModifyRights(const rights_map_t& ids, EGrantRevoke command);
void sendRightsGrant(const rights_map_t& ids);
bool modifyRightsConfirmation(const LLSD& notification, const LLSD& response, rights_map_t* rights);
bool isItemsFreeOfFriends(const uuid_vec_t& uuids);
bool isItemsFreeOfFriends(const uuid_vec_t& uuids) const;
// misc callbacks
static void onAvatarPicked(const uuid_vec_t& ids, const std::vector<LLAvatarName>& names);
@@ -128,6 +129,7 @@ private:
std::string& tooltip_msg);
void onFriendFilterEdit(const std::string& search_string);
void onGroupFilterEdit(const std::string& search_string);
void onContactSetsChanged(LGGContactSets::EContactSetUpdate type);
// friend buttons
void onViewProfileButtonClicked();
@@ -196,12 +198,13 @@ private:
void updateRlvRestrictions(ERlvBehaviour behavior);
boost::signals2::connection mRlvBehaviorCallbackConnection{};
boost::signals2::connection mContactSetChangedConnection{};
std::string getFullName(const LLAvatarName& av_name);
std::string getFullName(const LLAvatarName& av_name) const;
void setDirtyNames(const LLUUID& request_id);
typedef std::map<LLUUID, LLAvatarNameCache::callback_connection_t> avatar_name_cb_t;
using avatar_name_cb_t = std::map<LLUUID, LLAvatarNameCache::callback_connection_t>;
avatar_name_cb_t mAvatarNameCacheConnections;
void disconnectAvatarNameCacheConnection(const LLUUID& request_id);
};
@@ -37,6 +37,7 @@
#include "llcolorswatch.h"
#include "lllineeditor.h"
#include "llnotificationsutil.h"
#include "lltexteditor.h"
#include "llviewercontrol.h"
FSFloaterContactSetConfiguration::FSFloaterContactSetConfiguration(const LLSD& target_set)
@@ -67,6 +68,24 @@ bool FSFloaterContactSetConfiguration::postBuild()
mNotificationCheckBox = getChild<LLCheckBoxCtrl>("show_set_notifications");
mNotificationCheckBox->setCommitCallback(boost::bind(&FSFloaterContactSetConfiguration::onCommitSetNotifications, this));
mSortByOnlineStatusCheckBox = getChild<LLCheckBoxCtrl>("sort_by_online_status");
mSortByOnlineStatusCheckBox->setCommitCallback(boost::bind(&FSFloaterContactSetConfiguration::onCommitSetSortByOnlineStatus, this));
mAutoresponseBusyEnabledCheckBox = getChild<LLCheckBoxCtrl>("set_autoresponse_busy_enabled");
mAutoresponseBusyEnabledCheckBox->setCommitCallback(boost::bind(&FSFloaterContactSetConfiguration::onCommitSetAutoresponseBusy, this));
mAutoresponseBusyEditor = getChild<LLTextEditor>("set_autoresponse_busy");
mAutoresponseBusyEditor->setCommitCallback(boost::bind(&FSFloaterContactSetConfiguration::onCommitSetAutoresponseBusy, this));
mAutoresponseModeEnabledCheckBox = getChild<LLCheckBoxCtrl>("set_autoresponse_mode_enabled");
mAutoresponseModeEnabledCheckBox->setCommitCallback(boost::bind(&FSFloaterContactSetConfiguration::onCommitSetAutoresponseMode, this));
mAutoresponseModeEditor = getChild<LLTextEditor>("set_autoresponse_mode");
mAutoresponseModeEditor->setCommitCallback(boost::bind(&FSFloaterContactSetConfiguration::onCommitSetAutoresponseMode, this));
mAutoresponseNonFriendsEnabledCheckBox = getChild<LLCheckBoxCtrl>("set_autoresponse_nonfriends_enabled");
mAutoresponseNonFriendsEnabledCheckBox->setCommitCallback(boost::bind(&FSFloaterContactSetConfiguration::onCommitSetAutoresponseNonFriends, this));
mAutoresponseNonFriendsEditor = getChild<LLTextEditor>("set_autoresponse_nonfriends");
mAutoresponseNonFriendsEditor->setCommitCallback(boost::bind(&FSFloaterContactSetConfiguration::onCommitSetAutoresponseNonFriends, this));
return true;
}
@@ -79,9 +98,26 @@ void FSFloaterContactSetConfiguration::draw()
void FSFloaterContactSetConfiguration::onOpen(const LLSD& target_set)
{
bool enabled = false;
std::string response;
mSetSwatch->set(LGGContactSets::getInstance()->getSetColor(mContactSet), true);
mGlobalSwatch->set(LGGContactSets::getInstance()->getDefaultColor(), true);
mNotificationCheckBox->set(LGGContactSets::getInstance()->getNotifyForSet(mContactSet));
mSortByOnlineStatusCheckBox->set(LGGContactSets::getInstance()->getSortByOnlineStatusForSet(mContactSet));
LGGContactSets::getInstance()->getAutoresponseForSet(mContactSet, ContactSetAutoresponseMode::BUSY, enabled, response);
mAutoresponseBusyEnabledCheckBox->set(enabled);
mAutoresponseBusyEditor->setText(response);
LGGContactSets::getInstance()->getAutoresponseForSet(mContactSet, ContactSetAutoresponseMode::AUTORESPONSE, enabled, response);
mAutoresponseModeEnabledCheckBox->set(enabled);
mAutoresponseModeEditor->setText(response);
LGGContactSets::getInstance()->getAutoresponseForSet(mContactSet, ContactSetAutoresponseMode::AUTORESPONSE_NONFRIENDS, enabled, response);
mAutoresponseNonFriendsEnabledCheckBox->set(enabled);
mAutoresponseNonFriendsEditor->setText(response);
updateAutoresponseEditorEnabledState();
}
void FSFloaterContactSetConfiguration::onCommitSetColor()
@@ -94,11 +130,34 @@ void FSFloaterContactSetConfiguration::onCommitSetNotifications()
LGGContactSets::getInstance()->setNotifyForSet(mContactSet, mNotificationCheckBox->getValue().asBoolean());
}
void FSFloaterContactSetConfiguration::onCommitSetSortByOnlineStatus()
{
LGGContactSets::getInstance()->setSortByOnlineStatusForSet(mContactSet, mSortByOnlineStatusCheckBox->getValue().asBoolean());
}
void FSFloaterContactSetConfiguration::onCommitDefaultColor()
{
LGGContactSets::getInstance()->setDefaultColor(mGlobalSwatch->get());
}
void FSFloaterContactSetConfiguration::onCommitSetAutoresponseBusy()
{
LGGContactSets::getInstance()->setAutoresponseForSet(mContactSet, ContactSetAutoresponseMode::BUSY, mAutoresponseBusyEnabledCheckBox->getValue().asBoolean(), mAutoresponseBusyEditor->getText());
updateAutoresponseEditorEnabledState();
}
void FSFloaterContactSetConfiguration::onCommitSetAutoresponseMode()
{
LGGContactSets::getInstance()->setAutoresponseForSet(mContactSet, ContactSetAutoresponseMode::AUTORESPONSE, mAutoresponseModeEnabledCheckBox->getValue().asBoolean(), mAutoresponseModeEditor->getText());
updateAutoresponseEditorEnabledState();
}
void FSFloaterContactSetConfiguration::onCommitSetAutoresponseNonFriends()
{
LGGContactSets::getInstance()->setAutoresponseForSet(mContactSet, ContactSetAutoresponseMode::AUTORESPONSE_NONFRIENDS, mAutoresponseNonFriendsEnabledCheckBox->getValue().asBoolean(), mAutoresponseNonFriendsEditor->getText());
updateAutoresponseEditorEnabledState();
}
void FSFloaterContactSetConfiguration::onRenameSet()
{
std::string new_name = mSetName->getText();
@@ -132,3 +191,10 @@ void FSFloaterContactSetConfiguration::setFrustumOrigin(LLView* frustumOrigin)
mFrustumOrigin = frustumOrigin->getHandle();
}
}
void FSFloaterContactSetConfiguration::updateAutoresponseEditorEnabledState()
{
mAutoresponseBusyEditor->setEnabled(mAutoresponseBusyEnabledCheckBox->getValue().asBoolean());
mAutoresponseModeEditor->setEnabled(mAutoresponseModeEnabledCheckBox->getValue().asBoolean());
mAutoresponseNonFriendsEditor->setEnabled(mAutoresponseNonFriendsEnabledCheckBox->getValue().asBoolean());
}
@@ -35,6 +35,7 @@
class LLColorSwatchCtrl;
class LLCheckBoxCtrl;
class LLLineEditor;
class LLTextEditor;
class LLButton;
class FSFloaterContactSetConfiguration : public LLFloater
@@ -50,8 +51,13 @@ private:
~FSFloaterContactSetConfiguration() = default;
void onCommitSetColor();
void onCommitSetNotifications();
void onCommitSetSortByOnlineStatus();
void onCommitDefaultColor();
void onCommitSetAutoresponseBusy();
void onCommitSetAutoresponseMode();
void onCommitSetAutoresponseNonFriends();
void onRenameSet();
void updateAutoresponseEditorEnabledState();
void updateTitle();
@@ -59,6 +65,13 @@ private:
std::string mContactSet;
LLCheckBoxCtrl* mNotificationCheckBox;
LLCheckBoxCtrl* mSortByOnlineStatusCheckBox;
LLCheckBoxCtrl* mAutoresponseBusyEnabledCheckBox;
LLTextEditor* mAutoresponseBusyEditor;
LLCheckBoxCtrl* mAutoresponseModeEnabledCheckBox;
LLTextEditor* mAutoresponseModeEditor;
LLCheckBoxCtrl* mAutoresponseNonFriendsEnabledCheckBox;
LLTextEditor* mAutoresponseNonFriendsEditor;
LLColorSwatchCtrl* mSetSwatch;
LLColorSwatchCtrl* mGlobalSwatch;
LLLineEditor* mSetName;
+169 -8
View File
@@ -28,10 +28,14 @@
#include "fsfloatergrouptitles.h"
#include "fscommon.h"
#include "fsgrouptitleregionmgr.h"
#include "llcheckboxctrl.h"
#include "llfiltereditor.h"
#include "llgroupactions.h"
#include "llmenugl.h"
#include "llscrolllistctrl.h"
#include "lltrans.h"
#include "llviewermenu.h"
/////////////////////////////////////////////////////
// FSGroupTitlesObserver class
@@ -79,6 +83,17 @@ FSFloaterGroupTitles::~FSFloaterGroupTitles()
gAgent.removeListener(this);
LLGroupMgr::getInstance()->removeObserver(this);
if (mAssignmentsChangedConnection.connected())
{
mAssignmentsChangedConnection.disconnect();
}
if (LLContextMenu* menu = mClearRegionMenuHandle.get())
{
gMenuHolder->removeChild(menu);
delete menu;
}
// Clean up still registered observers
clearObservers();
}
@@ -88,16 +103,30 @@ bool FSFloaterGroupTitles::postBuild()
mActivateButton = getChild<LLButton>("btnActivate");
mRefreshButton = getChild<LLButton>("btnRefresh");
mInfoButton = getChild<LLButton>("btnInfo");
mSetRegionButton = getChild<LLButton>("btnSetRegion");
mSetRegionManualButton = getChild<LLButton>("btnSetRegionManual");
mClearRegionButton = getChild<LLButton>("btnClearRegion");
mNoneOnUnassigned = getChild<LLCheckBoxCtrl>("none_on_unassigned");
mTitleList = getChild<LLScrollListCtrl>("title_list");
mFilterEditor = getChild<LLFilterEditor>("filter_input");
mActivateButton->setCommitCallback(boost::bind(&FSFloaterGroupTitles::activateGroupTitle, this));
mRefreshButton->setCommitCallback(boost::bind(&FSFloaterGroupTitles::refreshGroupTitles, this));
mInfoButton->setCommitCallback(boost::bind(&FSFloaterGroupTitles::openGroupInfo, this));
mSetRegionButton->setCommitCallback(boost::bind(&FSFloaterGroupTitles::onSetRegion, this));
mSetRegionManualButton->setCommitCallback(boost::bind(&FSFloaterGroupTitles::onSetRegionManual, this));
mClearRegionButton->setCommitCallback(boost::bind(&FSFloaterGroupTitles::onClearRegion, this));
mNoneOnUnassigned->setCommitCallback(boost::bind(&FSFloaterGroupTitles::onNoneOnUnassignedToggle, this));
mTitleList->setDoubleClickCallback(boost::bind(&FSFloaterGroupTitles::activateGroupTitle, this));
mTitleList->setCommitCallback(boost::bind(&FSFloaterGroupTitles::selectedTitleChanged, this));
mFilterEditor->setCommitCallback(boost::bind(&FSFloaterGroupTitles::onFilterEdit, this, _2));
mNoneOnUnassigned->set(FSGroupTitleRegionMgr::getInstance()->getNoneOnUnassigned());
mSetRegionButton->setEnabled(false);
mSetRegionManualButton->setEnabled(false);
mClearRegionButton->setEnabled(false);
mAssignmentsChangedConnection = FSGroupTitleRegionMgr::getInstance()->setAssignmentsChangedCallback([this]() { updateRegionColumn(); });
mTitleList->sortByColumn("title_sort_column", true);
mTitleList->setFilterColumn(0);
@@ -162,6 +191,7 @@ void FSFloaterGroupTitles::addListItem(const LLUUID& group_id, const LLUUID& rol
const std::string& group_name, bool is_active, bool is_group)
{
std::string font_style = (is_active ? "BOLD" : "NORMAL");
std::string region_name = FSGroupTitleRegionMgr::getInstance()->getRegionForTitle(group_id, role_id);
LLSD item;
item["id"] = group_id.asString() + role_id.asString(); // Only combination of group id and role id is unique!
@@ -173,18 +203,22 @@ void FSFloaterGroupTitles::addListItem(const LLUUID& group_id, const LLUUID& rol
item["columns"][1]["type"] = "text";
item["columns"][1]["font"]["style"] = font_style;
item["columns"][1]["value"] = group_name;
item["columns"][2]["column"] = "role_id";
item["columns"][2]["column"] = "regionname";
item["columns"][2]["type"] = "text";
item["columns"][2]["value"] = role_id;
item["columns"][3]["column"] = "group_id";
item["columns"][2]["font"]["style"] = font_style;
item["columns"][2]["value"] = region_name;
item["columns"][3]["column"] = "role_id";
item["columns"][3]["type"] = "text";
item["columns"][3]["value"] = group_id;
item["columns"][4]["column"] = "title_sort_column";
item["columns"][3]["value"] = role_id;
item["columns"][4]["column"] = "group_id";
item["columns"][4]["type"] = "text";
item["columns"][4]["value"] = (is_group ? ("1_" + title) : "0");
item["columns"][5]["column"] = "name_sort_column";
item["columns"][4]["value"] = group_id;
item["columns"][5]["column"] = "title_sort_column";
item["columns"][5]["type"] = "text";
item["columns"][5]["value"] = (is_group ? ("1_" + group_name) : "0");
item["columns"][5]["value"] = (is_group ? ("1_" + title) : "0");
item["columns"][6]["column"] = "name_sort_column";
item["columns"][6]["type"] = "text";
item["columns"][6]["value"] = (is_group ? ("1_" + group_name) : "0");
mTitleList->addElement(item);
@@ -268,7 +302,18 @@ void FSFloaterGroupTitles::selectedTitleChanged()
if (selected_item)
{
LLUUID group_id = selected_item->getColumn(mTitleList->getColumn("group_id")->mIndex)->getValue().asUUID();
LLUUID role_id = selected_item->getColumn(mTitleList->getColumn("role_id")->mIndex)->getValue().asUUID();
mInfoButton->setEnabled(group_id.notNull());
const bool has_region = !FSGroupTitleRegionMgr::getInstance()->getRegionForTitle(group_id, role_id).empty();
mSetRegionButton->setEnabled(true);
mSetRegionManualButton->setEnabled(true);
mClearRegionButton->setEnabled(has_region);
}
else
{
mSetRegionButton->setEnabled(false);
mSetRegionManualButton->setEnabled(false);
mClearRegionButton->setEnabled(false);
}
}
@@ -300,3 +345,119 @@ void FSFloaterGroupTitles::onFilterEdit(const std::string& search_string)
// Apply new filter.
mTitleList->setFilterString(mFilterSubStringOrig);
}
void FSFloaterGroupTitles::onSetRegion()
{
auto* selected_item = mTitleList->getFirstSelected();
if (!selected_item)
{
return;
}
const auto group_id = selected_item->getColumn(mTitleList->getColumn("group_id")->mIndex)->getValue().asUUID();
const auto role_id = selected_item->getColumn(mTitleList->getColumn("role_id")->mIndex)->getValue().asUUID();
FSGroupTitleRegionMgr::getInstance()->setAssignmentForCurrentRegion(group_id, role_id);
selectedTitleChanged();
}
void FSFloaterGroupTitles::onSetRegionManual()
{
auto* selected_item = mTitleList->getFirstSelected();
if (!selected_item)
{
return;
}
const auto group_id = selected_item->getColumn(mTitleList->getColumn("group_id")->mIndex)->getValue().asUUID();
const auto role_id = selected_item->getColumn(mTitleList->getColumn("role_id")->mIndex)->getValue().asUUID();
FSGroupTitleRegionMgr::getInstance()->showRegionInputDialog(group_id, role_id);
}
void FSFloaterGroupTitles::onClearRegion()
{
auto* selected_item = mTitleList->getFirstSelected();
if (!selected_item)
{
return;
}
const auto group_id = selected_item->getColumn(mTitleList->getColumn("group_id")->mIndex)->getValue().asUUID();
const auto role_id = selected_item->getColumn(mTitleList->getColumn("role_id")->mIndex)->getValue().asUUID();
auto* mgr = FSGroupTitleRegionMgr::getInstance();
std::vector<std::string> regions = mgr->getRegionDisplayNamesForTitle(group_id, role_id);
if (regions.empty())
{
return;
}
if (regions.size() == 1)
{
mgr->clearAssignmentByRegion(regions[0]);
selectedTitleChanged();
return;
}
// Multiple regions, build a popup menu
if (LLContextMenu* old_menu = mClearRegionMenuHandle.get())
{
gMenuHolder->removeChild(old_menu);
delete old_menu;
}
LLContextMenu::Params menu_params;
menu_params.name("clear_region_menu");
menu_params.visible(false);
LLContextMenu* menu = LLUICtrlFactory::create<LLContextMenu>(menu_params);
for (const auto& region : regions)
{
LLMenuItemCallGL::Params item_params;
item_params.label(region);
item_params.name(region);
item_params.on_click.function(
[mgr, region](LLUICtrl*, const LLSD&)
{
mgr->clearAssignmentByRegion(region);
});
menu->addChild(LLUICtrlFactory::create<LLMenuItemCallGL>(item_params));
}
menu->addSeparator();
LLMenuItemCallGL::Params clear_all_params;
clear_all_params.label(getString("ClearAllRegions"));
clear_all_params.name("clear_all_regions");
clear_all_params.on_click.function(
[mgr, group_id, role_id](LLUICtrl*, const LLSD&)
{
mgr->clearAssignment(group_id, role_id);
});
menu->addChild(LLUICtrlFactory::create<LLMenuItemCallGL>(clear_all_params));
mClearRegionMenuHandle = menu->getHandle();
gMenuHolder->addChild(menu);
S32 screen_x, screen_y;
mClearRegionButton->localPointToScreen(0, mClearRegionButton->getRect().getHeight(), &screen_x, &screen_y);
menu->show(screen_x, screen_y, mClearRegionButton);
}
void FSFloaterGroupTitles::onNoneOnUnassignedToggle()
{
FSGroupTitleRegionMgr::getInstance()->setNoneOnUnassigned(mNoneOnUnassigned->get());
}
void FSFloaterGroupTitles::updateRegionColumn()
{
const auto region_col_idx = mTitleList->getColumn("regionname")->mIndex;
const auto group_id_col_idx = mTitleList->getColumn("group_id")->mIndex;
const auto role_id_col_idx = mTitleList->getColumn("role_id")->mIndex;
auto* mgr = FSGroupTitleRegionMgr::getInstance();
for (auto* item : mTitleList->getAllData())
{
const auto group_id = item->getColumn(group_id_col_idx)->getValue().asUUID();
const auto role_id = item->getColumn(role_id_col_idx)->getValue().asUUID();
if (auto* cell = item->getColumn(region_col_idx))
{
cell->setValue(mgr->getRegionForTitle(group_id, role_id));
}
}
}
+16
View File
@@ -31,8 +31,11 @@
#include "llagent.h"
#include "llgroupmgr.h"
#include <boost/signals2.hpp>
class FSFloaterGroupTitles;
class LLCheckBoxCtrl;
class LLContextMenu;
class LLFilterEditor;
class LLScrollListCtrl;
@@ -79,15 +82,28 @@ private:
void openGroupInfo();
void onFilterEdit(const std::string& search_string);
void onSetRegion();
void onSetRegionManual();
void onClearRegion();
void onNoneOnUnassignedToggle();
void updateRegionColumn();
LLButton* mActivateButton;
LLButton* mRefreshButton;
LLButton* mInfoButton;
LLButton* mSetRegionButton;
LLButton* mSetRegionManualButton;
LLButton* mClearRegionButton;
LLCheckBoxCtrl* mNoneOnUnassigned;
LLScrollListCtrl* mTitleList;
LLFilterEditor* mFilterEditor;
std::string mFilterSubString;
std::string mFilterSubStringOrig;
boost::signals2::connection mAssignmentsChangedConnection;
LLHandle<LLContextMenu> mClearRegionMenuHandle;
typedef std::map<LLUUID, FSGroupTitlesObserver*> observer_map_t;
observer_map_t mGroupTitleObserverMap;
};
+1 -1
View File
@@ -1529,7 +1529,7 @@ void FSFloaterIM::sessionInitReplyReceived(const LLUUID& im_session_id)
// updating "Call" button from group/ad-hoc control panel here to enable it without placing into draw() (EXT-4796)
if (LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(im_session_id); session)
{
if ((session->isGroupSessionType() && gAgent.isInGroup(im_session_id)) || session->isAdHocSessionType())
if ((session->isGroupSessionType() && gAgent.isInGroup(im_session_id)) || session->isAdHocSessionType() || session->isP2PSessionType())
{
updateCallButton();
}
+7
View File
@@ -365,8 +365,15 @@ void FSFloaterIMContainer::addFloater(LLFloater* floaterp,
// [/SL:KB]
}
LLFloater* active_floater = getActiveFloater();
LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point);
if (!select_added_floater && active_floater)
{
selectFloater(active_floater);
}
LLUUID session_id = floaterp->getKey();
mSessions[session_id] = floaterp;
floaterp->mCloseSignal.connect(boost::bind(&FSFloaterIMContainer::onCloseFloater, this, session_id));
+6 -1
View File
@@ -40,6 +40,7 @@
#include "fsfloaterimcontainer.h"
#include "fsnearbychathub.h"
#include "llagent.h" // gAgent
#include "llagentcamera.h" // gAgentCamera
#include "llanimationstates.h" // ANIM_AGENT_WHISPER, ANIM_AGENT_TALK, ANIM_AGENT_SHOUT
#include "llautoreplace.h"
#include "llavatarnamecache.h"
@@ -923,7 +924,11 @@ void FSFloaterNearbyChat::sendChat( EChatType type )
// If the user wants to stop chatting on hitting return, lose focus
// and go out of chat mode.
if (gSavedSettings.getBOOL("CloseChatOnReturn") && gSavedSettings.getBOOL("FSUnfocusChatHistoryOnReturn"))
const bool in_mouselook = gAgentCamera.cameraMouselook();
const bool closeChatOnReturn = gSavedSettings.getBOOL("CloseChatOnReturn")
&& !(!in_mouselook && gSavedSettings.getBOOL("FSCloseChatOnReturnInMouselook"))
&& !gSavedSettings.getBOOL("FSCloseChatOnReturnOnlyBar");
if (closeChatOnReturn && gSavedSettings.getBOOL("FSUnfocusChatHistoryOnReturn"))
{
stopChat();
}
+560
View File
@@ -0,0 +1,560 @@
/**
* @file fsgrouptitleregionmgr.cpp
* @brief Per-region automatic group title switching manager
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Copyright (c) 2026 The Phoenix Firestorm Project, 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
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "fsgrouptitleregionmgr.h"
#include "llagent.h"
#include "lldir.h"
#include "llgroupactions.h"
#include "llgroupmgr.h"
#include "llnotificationsutil.h"
#include "llsdserialize.h"
#include "llviewerregion.h"
#include "llworldmap.h"
#include "llworldmapmessage.h"
#include "rlvactions.h"
static constexpr char REGION_GROUP_TITLES_FILE[] = "region_group_titles.xml";
static constexpr S32 MAX_REGION_NAME_LENGTH = 63;
static constexpr F32 REGION_VALIDATION_TIMEOUT = 10.f;
// ---------------------------------------------------------------------------
// Construction / destruction
// ---------------------------------------------------------------------------
FSGroupTitleRegionMgr::FSGroupTitleRegionMgr()
{
mRegionChangedConnection = gAgent.addRegionChangedCallback([this]() { onRegionChanged(); });
}
FSGroupTitleRegionMgr::~FSGroupTitleRegionMgr()
{
cancelPendingValidation();
if (mLoginRetryTimer)
{
delete mLoginRetryTimer;
mLoginRetryTimer = nullptr;
}
if (mRegionChangedConnection.connected())
{
gAgent.removeRegionChangedCallback(mRegionChangedConnection);
mRegionChangedConnection.disconnect();
}
}
// ---------------------------------------------------------------------------
// Persistence
// ---------------------------------------------------------------------------
std::string FSGroupTitleRegionMgr::getFilename() const
{
auto path = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "");
if (!path.empty())
{
path = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, REGION_GROUP_TITLES_FILE);
}
return path;
}
void FSGroupTitleRegionMgr::loadFromDisk()
{
if (mLoginRetryTimer)
{
delete mLoginRetryTimer;
mLoginRetryTimer = nullptr;
}
mAssignments.clear();
mNoneOnUnassigned = false;
mLastAppliedRegion.clear();
const auto filename = getFilename();
if (filename.empty())
{
LL_WARNS() << "Could not determine per-account path for region group titles" << LL_ENDL;
mDataLoaded = true;
return;
}
llifstream file(filename.c_str());
if (!file.is_open())
{
LL_INFOS() << "No region group titles file found at: " << filename << LL_ENDL;
mDataLoaded = true;
return;
}
LLSD data;
if (LLSDSerialize::fromXML(data, file) == LLSDParser::PARSE_FAILURE)
{
LL_WARNS() << "Failed to parse region group titles file: " << filename << LL_ENDL;
mDataLoaded = true;
return;
}
file.close();
mNoneOnUnassigned = data["none_on_unassigned"].asBoolean();
if (data.has("assignments") && data["assignments"].isMap())
{
const auto& assignments = data["assignments"];
for (auto it = assignments.beginMap(); it != assignments.endMap(); ++it)
{
FSRegionTitleAssignment assignment;
assignment.mGroupID = it->second["group_id"].asUUID();
assignment.mRoleID = it->second["role_id"].asUUID();
assignment.mDisplayName = it->second["display_name"].asString();
if (assignment.mDisplayName.empty())
{
assignment.mDisplayName = it->first;
}
mAssignments[it->first] = assignment;
}
}
mDataLoaded = true;
LL_INFOS() << "Loaded " << mAssignments.size() << " region group title assignments" << LL_ENDL;
// The region changed callback likely already fired during login before data was loaded (and was ignored via the mDataLoaded guard).
// Try now and schedule a deferred retry in case the region handshake hasn't finished yet (region name would be empty, causing an early return).
onRegionChanged();
static constexpr F32 LOGIN_RETRY_DELAY = 5.f;
mLoginRetryTimer = LLEventTimer::run_after(LOGIN_RETRY_DELAY, [this]()
{
mLoginRetryTimer = nullptr;
mLastAppliedRegion.clear();
onRegionChanged();
});
}
void FSGroupTitleRegionMgr::saveToDisk()
{
const auto filename = getFilename();
if (filename.empty())
{
LL_WARNS() << "Could not determine per-account path for saving region group titles" << LL_ENDL;
return;
}
LLSD assignments;
for (const auto& [region, assignment] : mAssignments)
{
LLSD entry;
entry["group_id"] = assignment.mGroupID;
entry["role_id"] = assignment.mRoleID;
entry["display_name"] = assignment.mDisplayName;
assignments[region] = entry;
}
LLSD data;
data["none_on_unassigned"] = mNoneOnUnassigned;
data["assignments"] = assignments;
llofstream file(filename.c_str());
if (!file.is_open())
{
LL_WARNS() << "Could not open file for writing: " << filename << LL_ENDL;
return;
}
LLSDSerialize::toPrettyXML(data, file);
file.close();
}
// ---------------------------------------------------------------------------
// Assignment CRUD
// ---------------------------------------------------------------------------
void FSGroupTitleRegionMgr::setAssignment(const LLUUID& group_id, const LLUUID& role_id, const std::string& region_name)
{
const auto normalized = normalizeRegionName(region_name);
if (normalized.empty())
{
return;
}
if (auto it = mAssignments.find(normalized); it != mAssignments.end())
{
if (it->second.mGroupID == group_id && it->second.mRoleID == role_id)
{
return;
}
mAssignments.erase(it);
}
mAssignments[normalized] = { group_id, role_id, sanitizeRegionName(region_name) };
saveToDisk();
mAssignmentsChangedSignal();
}
void FSGroupTitleRegionMgr::setAssignmentForCurrentRegion(const LLUUID& group_id, const LLUUID& role_id)
{
const auto* region = gAgent.getRegion();
if (!region)
{
return;
}
setAssignment(group_id, role_id, region->getName());
}
void FSGroupTitleRegionMgr::clearAssignment(const LLUUID& group_id, const LLUUID& role_id)
{
bool changed = false;
for (auto it = mAssignments.begin(); it != mAssignments.end(); )
{
if (it->second.mGroupID == group_id && it->second.mRoleID == role_id)
{
it = mAssignments.erase(it);
changed = true;
}
else
{
++it;
}
}
if (changed)
{
saveToDisk();
mAssignmentsChangedSignal();
}
}
void FSGroupTitleRegionMgr::clearAssignmentByRegion(const std::string& region_name)
{
if (auto it = mAssignments.find(normalizeRegionName(region_name)); it != mAssignments.end())
{
mAssignments.erase(it);
saveToDisk();
mAssignmentsChangedSignal();
}
}
// ---------------------------------------------------------------------------
// Lookups
// ---------------------------------------------------------------------------
std::string FSGroupTitleRegionMgr::getRegionForTitle(const LLUUID& group_id, const LLUUID& role_id) const
{
std::string result;
for (const auto& [region, assignment] : mAssignments)
{
if (assignment.mGroupID == group_id && assignment.mRoleID == role_id)
{
if (!result.empty())
{
result += ", ";
}
result += assignment.mDisplayName;
}
}
return result;
}
std::vector<std::string> FSGroupTitleRegionMgr::getRegionDisplayNamesForTitle(const LLUUID& group_id, const LLUUID& role_id) const
{
std::vector<std::string> result;
for (const auto& [region, assignment] : mAssignments)
{
if (assignment.mGroupID == group_id && assignment.mRoleID == role_id)
{
result.push_back(assignment.mDisplayName);
}
}
return result;
}
bool FSGroupTitleRegionMgr::getAssignmentForRegion(const std::string& region_name, LLUUID& group_id, LLUUID& role_id) const
{
if (auto it = mAssignments.find(normalizeRegionName(region_name)); it != mAssignments.end())
{
group_id = it->second.mGroupID;
role_id = it->second.mRoleID;
return true;
}
return false;
}
// ---------------------------------------------------------------------------
// Preferences
// ---------------------------------------------------------------------------
void FSGroupTitleRegionMgr::setNoneOnUnassigned(bool enabled)
{
mNoneOnUnassigned = enabled;
saveToDisk();
}
boost::signals2::connection FSGroupTitleRegionMgr::setAssignmentsChangedCallback(const assignments_changed_signal_t::slot_type& cb)
{
return mAssignmentsChangedSignal.connect(cb);
}
// ---------------------------------------------------------------------------
// Input sanitization / normalization
// ---------------------------------------------------------------------------
std::string FSGroupTitleRegionMgr::sanitizeRegionName(const std::string& input)
{
std::string result = input;
LLStringUtil::trim(result);
std::string collapsed;
collapsed.reserve(result.size());
bool prev_space = false;
for (const auto c : result)
{
if (c == ' ')
{
if (!prev_space)
{
collapsed += c;
}
prev_space = true;
}
else if (static_cast<unsigned char>(c) >= 32)
{
collapsed += c;
prev_space = false;
}
}
if (static_cast<S32>(collapsed.size()) > MAX_REGION_NAME_LENGTH)
{
collapsed.resize(MAX_REGION_NAME_LENGTH);
}
return collapsed;
}
std::string FSGroupTitleRegionMgr::normalizeRegionName(const std::string& name)
{
auto normalized = sanitizeRegionName(name);
LLStringUtil::toLower(normalized);
return normalized;
}
// ---------------------------------------------------------------------------
// Region input dialog (offloaded from floater)
// ---------------------------------------------------------------------------
void FSGroupTitleRegionMgr::showRegionInputDialog(const LLUUID& group_id, const LLUUID& role_id)
{
LLSD payload;
payload["group_id"] = group_id;
payload["role_id"] = role_id;
LLNotificationsUtil::add("FSSetTitleRegion", LLSD(), payload, &FSGroupTitleRegionMgr::onRegionInputCallback);
}
bool FSGroupTitleRegionMgr::onRegionInputCallback(const LLSD& notification, const LLSD& response)
{
if (LLNotificationsUtil::getSelectedOption(notification, response) != 0)
{
return false;
}
const auto region_name = sanitizeRegionName(response["region_name"].asString());
if (region_name.empty())
{
return false;
}
const auto group_id = notification["payload"]["group_id"].asUUID();
const auto role_id = notification["payload"]["role_id"].asUUID();
FSGroupTitleRegionMgr::getInstance()->validateAndSetAssignment(group_id, role_id, region_name);
return false;
}
// ---------------------------------------------------------------------------
// Async region name validation
// ---------------------------------------------------------------------------
void FSGroupTitleRegionMgr::validateAndSetAssignment(const LLUUID& group_id, const LLUUID& role_id, const std::string& region_name)
{
const auto sanitized = sanitizeRegionName(region_name);
if (sanitized.empty())
{
return;
}
cancelPendingValidation();
mPendingGroupID = group_id;
mPendingRoleID = role_id;
mPendingRegionName = sanitized;
mHasPendingValidation = true;
LLWorldMapMessage::getInstance()->sendNamedRegionRequest(
sanitized,
[this](U64 region_handle, const std::string&, const LLUUID&, bool)
{
onValidationResult(region_handle);
},
"",
false
);
mValidationTimer = LLEventTimer::run_after(REGION_VALIDATION_TIMEOUT, [this]()
{
mValidationTimer = nullptr;
onValidationTimeout();
});
}
void FSGroupTitleRegionMgr::cancelPendingValidation()
{
mHasPendingValidation = false;
if (mValidationTimer)
{
delete mValidationTimer;
mValidationTimer = nullptr;
}
}
void FSGroupTitleRegionMgr::onValidationResult(U64 region_handle)
{
if (!mHasPendingValidation)
{
return;
}
auto* sim_info = LLWorldMap::getInstance()->simInfoFromHandle(region_handle);
if (!sim_info)
{
cancelPendingValidation();
LLSD args;
args["REGION"] = mPendingRegionName;
LLNotificationsUtil::add("FSSetTitleRegionNotFound", args);
return;
}
const auto canonical_name = sim_info->getName();
if (normalizeRegionName(canonical_name) != normalizeRegionName(mPendingRegionName))
{
cancelPendingValidation();
LLSD args;
args["REGION"] = mPendingRegionName;
LLNotificationsUtil::add("FSSetTitleRegionNotFound", args);
return;
}
const auto group_id = mPendingGroupID;
const auto role_id = mPendingRoleID;
cancelPendingValidation();
setAssignment(group_id, role_id, canonical_name);
}
void FSGroupTitleRegionMgr::onValidationTimeout()
{
if (!mHasPendingValidation)
{
return;
}
mHasPendingValidation = false;
LLSD args;
args["REGION"] = mPendingRegionName;
LLNotificationsUtil::add("FSSetTitleRegionNotFound", args);
}
// ---------------------------------------------------------------------------
// Region change handler
// ---------------------------------------------------------------------------
void FSGroupTitleRegionMgr::onRegionChanged()
{
if (!mDataLoaded)
{
return;
}
const auto* region = gAgent.getRegion();
if (!region)
{
return;
}
if (RlvActions::isRlvEnabled() && !RlvActions::canChangeActiveGroup())
{
LL_DEBUGS() << "Skipping region-based group title switch: RLVa @setgroup lock active" << LL_ENDL;
return;
}
const auto region_name = normalizeRegionName(region->getName());
if (region_name.empty())
{
return;
}
if (region_name == mLastAppliedRegion)
{
return;
}
mLastAppliedRegion = region_name;
LLUUID group_id;
LLUUID role_id;
if (getAssignmentForRegion(region_name, group_id, role_id))
{
if (group_id.notNull() && !gAgent.isInGroup(group_id))
{
LL_WARNS() << "Region '" << region->getName() << "' has a title assignment for a group we are no longer in, skipping" << LL_ENDL;
return;
}
bool title_already_active = false;
if (group_id.notNull())
{
if (const auto* group_data = LLGroupMgr::getInstance()->getGroupData(group_id))
{
for (const auto& title : group_data->mTitles)
{
if (title.mRoleID == role_id && title.mSelected)
{
title_already_active = true;
break;
}
}
}
}
LL_INFOS() << "Region '" << region->getName() << "', switching to assigned group title" << LL_ENDL;
if (group_id.notNull() && !title_already_active)
{
LLGroupMgr::getInstance()->sendGroupTitleUpdate(group_id, role_id);
}
if (gAgent.getGroupID() != group_id)
{
LLGroupActions::activate(group_id);
}
}
else if (mNoneOnUnassigned && gAgent.getGroupID().notNull())
{
LL_INFOS() << "Region '" << region->getName() << "' has no title preset, deactivating group" << LL_ENDL;
LLGroupActions::activate(LLUUID::null);
}
}
+97
View File
@@ -0,0 +1,97 @@
/**
* @file fsgrouptitleregionmgr.h
* @brief Per-region automatic group title switching manager
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Copyright (c) 2026 The Phoenix Firestorm Project, 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
*
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
* http://www.firestormviewer.org
* $/LicenseInfo$
*/
#ifndef FS_GROUPTITLEREGIONMGR_H
#define FS_GROUPTITLEREGIONMGR_H
#include "lleventtimer.h"
#include "llsingleton.h"
#include "lluuid.h"
#include <boost/signals2.hpp>
#include <map>
#include <string>
#include <vector>
struct FSRegionTitleAssignment
{
LLUUID mGroupID;
LLUUID mRoleID;
std::string mDisplayName;
};
class FSGroupTitleRegionMgr : public LLSingleton<FSGroupTitleRegionMgr>
{
LOG_CLASS(FSGroupTitleRegionMgr);
LLSINGLETON(FSGroupTitleRegionMgr);
~FSGroupTitleRegionMgr();
public:
void loadFromDisk();
void saveToDisk();
void setAssignment(const LLUUID& group_id, const LLUUID& role_id, const std::string& region_name);
void setAssignmentForCurrentRegion(const LLUUID& group_id, const LLUUID& role_id);
void clearAssignment(const LLUUID& group_id, const LLUUID& role_id);
void clearAssignmentByRegion(const std::string& region_name);
std::string getRegionForTitle(const LLUUID& group_id, const LLUUID& role_id) const;
std::vector<std::string> getRegionDisplayNamesForTitle(const LLUUID& group_id, const LLUUID& role_id) const;
bool getAssignmentForRegion(const std::string& region_name, LLUUID& group_id, LLUUID& role_id) const;
void setNoneOnUnassigned(bool enabled);
bool getNoneOnUnassigned() const { return mNoneOnUnassigned; }
void showRegionInputDialog(const LLUUID& group_id, const LLUUID& role_id);
static std::string sanitizeRegionName(const std::string& input);
using assignments_changed_signal_t = boost::signals2::signal<void()>;
boost::signals2::connection setAssignmentsChangedCallback(const assignments_changed_signal_t::slot_type& cb);
private:
void validateAndSetAssignment(const LLUUID& group_id, const LLUUID& role_id, const std::string& region_name);
void onRegionChanged();
void cancelPendingValidation();
void onValidationResult(U64 region_handle);
void onValidationTimeout();
std::string getFilename() const;
static std::string normalizeRegionName(const std::string& name);
static bool onRegionInputCallback(const LLSD& notification, const LLSD& response);
bool mNoneOnUnassigned = false;
bool mDataLoaded = false;
std::string mLastAppliedRegion;
using assignment_map_t = std::map<std::string, FSRegionTitleAssignment>;
assignment_map_t mAssignments;
bool mHasPendingValidation = false;
LLUUID mPendingGroupID;
LLUUID mPendingRoleID;
std::string mPendingRegionName;
LLEventTimer* mValidationTimer = nullptr;
LLEventTimer* mLoginRetryTimer = nullptr;
boost::signals2::connection mRegionChangedConnection;
assignments_changed_signal_t mAssignmentsChangedSignal;
};
#endif // FS_GROUPTITLEREGIONMGR_H
+157
View File
@@ -0,0 +1,157 @@
/**
* @file fslocalmeshimportbase.cpp
* @brief Shared helpers for local mesh importers
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Local Mesh contribution source code
* Copyright (C) 2026, The Phoenix Firestorm Project, 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
*
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "fslocalmeshimportbase.h"
#include "llerror.h"
#include "llskinningutil.h"
#include "llvoavatarself.h"
void FSLocalMeshImportBase::setLod(LLLocalMeshFileLOD lod)
{
mLod = lod;
mLoadingLog.clear();
}
void FSLocalMeshImportBase::pushLog(const std::string& who, const std::string& what, bool is_error)
{
std::string log_msg = "[ " + who + " ] ";
if (is_error)
{
log_msg += "[ ERROR ] ";
}
log_msg += what;
mLoadingLog.push_back(log_msg);
if (mLogToInfo)
{
LL_INFOS("LocalMesh") << log_msg << LL_ENDL;
}
}
void FSLocalMeshImportBase::postProcessObject(LLLocalMeshObject& object, const LLMatrix4& scene_transform, bool compute_bounds)
{
if (compute_bounds)
{
object.computeObjectBoundingBox();
object.computeObjectTransform(scene_transform);
}
object.normalizeFaceValues(mLod);
}
bool FSLocalMeshImportBase::enforceRigJointLimit(const std::string& who,
LLLocalMeshObject& object,
LLPointer<LLMeshSkinInfo> skininfop,
U32 recognized_joint_count)
{
const S32 max_joints = LLSkinningUtil::getMaxJointCount();
if ((S32)recognized_joint_count <= max_joints)
{
return true;
}
const std::string warning = "WARNING: Skinning disabled for object \"" + object.getObjectName()
+ "\" due to too many joints: " + std::to_string(recognized_joint_count)
+ ", maximum: " + std::to_string(max_joints) + ".";
pushLog(who, warning);
LL_WARNS("LocalMesh") << who << ": " << warning << LL_ENDL;
if (skininfop.notNull())
{
skininfop->mJointNames.clear();
skininfop->mJointNums.clear();
skininfop->mInvBindMatrix.clear();
skininfop->mAlternateBindMatrix.clear();
skininfop->mBindPoseMatrix.clear();
skininfop->mBindShapeMatrix = LLMatrix4a::identity();
skininfop->mInvalidJointsScrubbed = false;
skininfop->mJointNumsInitialized = false;
skininfop->updateHash();
}
object.setObjectMeshSkinInfo(LLPointer<LLMeshSkinInfo>());
for (S32 lod = 0; lod < LOCAL_NUM_LODS; ++lod)
{
for (auto& face : object.getFaces(static_cast<LLLocalMeshFileLOD>(lod)))
{
face->getSkin().clear();
}
}
return false;
}
FSLocalMeshImportBase::JointMap FSLocalMeshImportBase::loadJointMap()
{
JointMap joint_map = gAgentAvatarp->getJointAliases();
// unfortunately getSortedJointNames clears the ref vector, and we need two extra lists.
std::vector<std::string> extra_names, more_extra_names;
gAgentAvatarp->getSortedJointNames(1, extra_names);
gAgentAvatarp->getSortedJointNames(2, more_extra_names);
extra_names.reserve(more_extra_names.size());
extra_names.insert(extra_names.end(), more_extra_names.begin(), more_extra_names.end());
// add the extras to jointmap
for (const auto& extra_name : extra_names)
{
joint_map[extra_name] = extra_name;
}
return joint_map;
}
LLMatrix4 FSLocalMeshImportBase::buildNormalizedTransformation(const LLLocalMeshObject& object)
{
LLVector4 inverse_translation = object.getObjectTranslation() * -1.f;
LLVector4 object_size = object.getObjectSize();
LLMatrix4 normalized_transformation;
LLMatrix4 mesh_scale;
normalized_transformation.setTranslation(LLVector3(inverse_translation));
mesh_scale.initScale(LLVector3(object_size));
mesh_scale *= normalized_transformation;
normalized_transformation = mesh_scale;
return normalized_transformation;
}
void FSLocalMeshImportBase::buildBindPoseMatrix(LLPointer<LLMeshSkinInfo> skininfop)
{
if (skininfop.isNull())
{
return;
}
skininfop->mBindPoseMatrix.resize(skininfop->mInvBindMatrix.size());
for (U32 i = 0; i < skininfop->mInvBindMatrix.size(); ++i)
{
matMul(skininfop->mBindShapeMatrix, skininfop->mInvBindMatrix[i], skininfop->mBindPoseMatrix[i]);
}
}
+59
View File
@@ -0,0 +1,59 @@
/**
* @file fslocalmeshimportbase.h
* @brief Shared helpers for local mesh importers
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Local Mesh contribution source code
* Copyright (C) 2026, The Phoenix Firestorm Project, 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
*
* $/LicenseInfo$
*/
#pragma once
#include "vjlocalmesh.h"
#include <map>
#include <string>
#include <utility>
#include <vector>
class FSLocalMeshImportBase
{
public:
using JointMap = std::map<std::string, std::string, std::less<>>;
using loadFile_return = std::pair<bool, std::vector<std::string>>;
virtual ~FSLocalMeshImportBase() = default;
protected:
void setLod(LLLocalMeshFileLOD lod);
void pushLog(const std::string& who, const std::string& what, bool is_error = false);
void postProcessObject(LLLocalMeshObject& object, const LLMatrix4& scene_transform, bool compute_bounds);
bool enforceRigJointLimit(const std::string& who,
LLLocalMeshObject& object,
LLPointer<LLMeshSkinInfo> skininfop,
U32 recognized_joint_count);
static JointMap loadJointMap();
static LLMatrix4 buildNormalizedTransformation(const LLLocalMeshObject& object);
static void buildBindPoseMatrix(LLPointer<LLMeshSkinInfo> skininfop);
protected:
LLLocalMeshFileLOD mLod{ LLLocalMeshFileLOD::LOCAL_LOD_HIGH };
std::vector<std::string> mLoadingLog;
bool mLogToInfo{ true };
};
File diff suppressed because it is too large Load Diff
+71
View File
@@ -0,0 +1,71 @@
/**
* @file fslocalmeshimportgltf.h
* @author Beq Janus
* @brief Local Mesh glTF importer header
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Phoenix Firestorm Viewer Source Code
* Copyright (C) 2026, Beq Janus.
*
* 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
*
* $/LicenseInfo$
*/
#pragma once
#include "fslocalmeshimportbase.h"
// GLM headers
#include <glm/mat4x4.hpp>
// STL headers
#include <string>
#include <vector>
namespace LL
{
namespace GLTF
{
class Asset;
class Primitive;
class Node;
}
}
class FSLocalMeshImportGLTF : public FSLocalMeshImportBase
{
public:
using loadFile_return = FSLocalMeshImportBase::loadFile_return;
FSLocalMeshImportGLTF();
loadFile_return loadFile(const std::string& filename,
LLLocalMeshFileLOD lod,
std::vector<std::unique_ptr<LLLocalMeshObject>>& object_vector);
private:
bool processNodeMesh(const LL::GLTF::Asset& asset, const LL::GLTF::Node& node, LLLocalMeshObject* object);
bool appendPrimitiveToObject(const LL::GLTF::Asset& asset,
const LL::GLTF::Primitive& prim,
const glm::mat4& mesh_transform,
bool flip_winding,
const std::vector<S32>& joint_index_remap,
const std::vector<std::string>& skin_joint_names,
LLLocalMeshObject* object,
S32 skin_idx);
bool initSkinInfo(const LL::GLTF::Asset& asset, S32 skin_idx, LLLocalMeshObject* object);
void finalizeSkinInfo(LLLocalMeshObject* object) const;
private:
std::vector<S32> mParentMap;
};
+20 -5
View File
@@ -134,6 +134,14 @@ void FSNearbyChatControl::setTextPadding(S32 left, S32 right)
void FSNearbyChatControl::applyTextPadding()
{
LLRect base_rect = mScroller ? mScroller->getContentWindowRect() : getLocalRect();
const LLRect local_rect = getLocalRect();
if (base_rect.getHeight() < local_rect.getHeight())
{
base_rect.mTop = local_rect.mTop;
base_rect.mBottom = local_rect.mBottom;
}
base_rect.mLeft = llmin(base_rect.mRight, base_rect.mLeft + mTextPadLeft);
base_rect.mRight = llmax(base_rect.mLeft, base_rect.mRight - mTextPadRight);
@@ -144,16 +152,23 @@ void FSNearbyChatControl::applyTextPadding()
}
}
void FSNearbyChatControl::autohide()
void FSNearbyChatControl::autohide(bool after_send)
{
if (isDefault())
{
if (gSavedSettings.getBOOL("CloseChatOnReturn"))
const bool in_mouselook = gAgentCamera.cameraMouselook();
const bool closeChatOnReturn = gSavedSettings.getBOOL("CloseChatOnReturn")
&& !(!in_mouselook && gSavedSettings.getBOOL("FSCloseChatOnReturnInMouselook"));
const bool autohideChatBar = gSavedSettings.getBOOL("AutohideChatBar");
bool hide_chatbar = false;
if (closeChatOnReturn)
{
setFocus(false);
hide_chatbar = autohideChatBar;
}
if (gAgentCamera.cameraMouselook() || gSavedSettings.getBOOL("AutohideChatBar"))
if (hide_chatbar || (!after_send && autohideChatBar))
{
FSNearbyChat::instance().showDefaultChatBar(false);
}
@@ -176,7 +191,7 @@ bool FSNearbyChatControl::handleKeyHere(KEY key, MASK mask)
if (key == KEY_ESCAPE && mask == MASK_NONE)
{
// we let ESC key go through to the rest of the UI code, so don't set handled = true
autohide();
autohide(false);
gAgent.stopTyping();
}
else if (KEY_RETURN == key)
@@ -222,7 +237,7 @@ bool FSNearbyChatControl::handleKeyHere(KEY key, MASK mask)
FSNearbyChat::instance().sendChat(getConvertedText(), type);
setText(LLStringExplicit(""));
autohide();
autohide(true);
return true;
}
+1 -1
View File
@@ -67,7 +67,7 @@ private:
void applyTextPadding();
// Unfocus and autohide chat bar accordingly if we are the default chat bar
void autohide();
void autohide(bool after_send);
void updateRlvRestrictions(ERlvBehaviour behavior);
void updateEmojiHelperSetting(const LLSD& data);
+157 -15
View File
@@ -38,6 +38,7 @@
#include "llcallingcard.h"
#include "llfloateravatarpicker.h"
#include "llfloaterreg.h"
#include "llfiltereditor.h"
#include "llnotificationsutil.h"
#include "llpanelpeoplemenus.h"
#include "llslurl.h"
@@ -45,13 +46,38 @@
constexpr U32 MAX_SELECTIONS = 20;
static LLPanelInjector<FSPanelContactSets> t_panel_contact_sets("contact_sets_panel");
class FSAvatarItemOnlineStatusComparator : public LLAvatarItemComparator
{
public:
virtual ~FSAvatarItemOnlineStatusComparator() = default;
protected:
bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const override
{
const bool online_1 = LLAvatarTracker::instance().isBuddyOnline(avatar_item1->getAvatarId());
const bool online_2 = LLAvatarTracker::instance().isBuddyOnline(avatar_item2->getAvatarId());
if (online_1 != online_2)
{
return online_1;
}
std::string name1 = getComparableName(avatar_item1);
std::string name2 = getComparableName(avatar_item2);
return name1 < name2;
}
};
static const FSAvatarItemOnlineStatusComparator FS_ONLINE_STATUS_COMPARATOR;
FSPanelContactSets::FSPanelContactSets() : LLPanel()
{
LLAvatarTracker::instance().addObserver(this);
mContactSetChangedConnection = LGGContactSets::getInstance()->setContactSetChangeCallback(boost::bind(&FSPanelContactSets::updateSets, this, _1));
}
FSPanelContactSets::~FSPanelContactSets()
{
LLAvatarTracker::instance().removeObserver(this);
if (mContactSetChangedConnection.connected())
{
mContactSetChangedConnection.disconnect();
@@ -64,6 +90,7 @@ bool FSPanelContactSets::postBuild()
childSetAction("remove_set_btn", boost::bind(&FSPanelContactSets::onClickRemoveSet, this));
childSetAction("config_btn", boost::bind(&FSPanelContactSets::onClickConfigureSet, this, _1));
childSetAction("add_btn", boost::bind(&FSPanelContactSets::onClickAddAvatar, this, _1));
childSetAction("move_btn", boost::bind(&FSPanelContactSets::onClickMoveAvatar, this));
childSetAction("remove_btn", boost::bind(&FSPanelContactSets::onClickRemoveAvatar, this));
childSetAction("profile_btn", boost::bind(&FSPanelContactSets::onClickOpenProfile, this));
childSetAction("start_im_btn", boost::bind(&FSPanelContactSets::onClickStartIM, this));
@@ -76,7 +103,15 @@ bool FSPanelContactSets::postBuild()
mContactSetCombo->setCommitCallback(boost::bind(&FSPanelContactSets::refreshSetList, this));
refreshContactSets();
if (LLFilterEditor* filter = getChild<LLFilterEditor>("contact_sets_filter_input", false))
{
filter->setCommitCallback(boost::bind(&FSPanelContactSets::onFilterEdit, this, _2));
}
mAvatarList = getChild<LLAvatarList>("contact_list");
mAvatarList->setUseContactSetColors(true);
mAvatarList->setUseContactSetListStyle(true);
mAvatarList->setAvatarDropCallback(boost::bind(&FSPanelContactSets::handleAvatarDrop, this, _1, _2));
mAvatarList->setCommitCallback(boost::bind(&FSPanelContactSets::onSelectAvatar, this));
mAvatarList->setItemDoubleClickCallback(boost::bind(&FSPanelContactSets::onClickStartIM, this));
mAvatarList->setNoItemsCommentText(getString("empty_list"));
@@ -107,17 +142,7 @@ void FSPanelContactSets::generateAvatarList(const std::string& contact_set)
if (contact_set == CS_SET_ALL_SETS)
{
avatars = LGGContactSets::getInstance()->getListOfNonFriends();
// "All sets" includes buddies
LLAvatarTracker::buddy_map_t all_buddies;
LLAvatarTracker::instance().copyBuddyList(all_buddies);
for (LLAvatarTracker::buddy_map_t::const_iterator buddy = all_buddies.begin();
buddy != all_buddies.end();
++buddy)
{
avatars.push_back(buddy->first);
}
avatars = LGGContactSets::getInstance()->getFriendsInAnySet();
}
else if (contact_set == CS_SET_NO_SETS)
{
@@ -151,10 +176,19 @@ void FSPanelContactSets::generateAvatarList(const std::string& contact_set)
}
}
getChild<LLTextBox>("member_count")->setTextArg("[COUNT]", llformat("%d", avatars.size()));
updateAvatarListSorting();
mAvatarList->setDirty();
resetControls();
}
void FSPanelContactSets::changed(U32 changed_mask)
{
if ((changed_mask & LLFriendObserver::ONLINE) && mAvatarList && shouldSortByOnlineStatus())
{
mAvatarList->sort();
}
}
void FSPanelContactSets::resetControls()
{
bool mutable_set = (!LGGContactSets::getInstance()->isInternalSetName(mContactSetCombo->getValue().asString()));
@@ -164,11 +198,12 @@ void FSPanelContactSets::resetControls()
childSetEnabled("remove_set_btn", mutable_set);
childSetEnabled("config_btn", mutable_set);
childSetEnabled("add_btn", mutable_set);
childSetEnabled("move_btn", (mutable_set && has_selection));
childSetEnabled("remove_btn", (mutable_set && has_selection));
childSetEnabled("profile_btn", has_selection);
childSetEnabled("start_im_btn", has_selection);
childSetEnabled("offer_teleport_btn", has_selection); // Should probably check if they're online...
childSetEnabled("set_pseudonym_btn", (mAvatarSelections.size() == 1));
childSetEnabled("set_pseudonym_btn", has_selection);
childSetEnabled("remove_pseudonym_btn", (has_selection
&& LGGContactSets::getInstance()->hasPseudonym(mAvatarSelections)));
childSetEnabled("remove_displayname_btn", (has_selection
@@ -218,6 +253,62 @@ void FSPanelContactSets::refreshSetList()
resetControls();
}
void FSPanelContactSets::updateAvatarListSorting()
{
if (!mAvatarList)
{
return;
}
if (shouldSortByOnlineStatus())
{
mAvatarList->setComparator(&FS_ONLINE_STATUS_COMPARATOR);
mAvatarList->sort();
}
else
{
mAvatarList->sortByName();
}
}
bool FSPanelContactSets::shouldSortByOnlineStatus() const
{
if (!mContactSetCombo)
{
return false;
}
const std::string selected_set = mContactSetCombo->getValue().asString();
if (LGGContactSets::getInstance()->isInternalSetName(selected_set))
{
return false;
}
return LGGContactSets::getInstance()->getSortByOnlineStatusForSet(selected_set);
}
bool FSPanelContactSets::handleAvatarDrop(const LLUUID& avatar_id, bool drop)
{
if (!mContactSetCombo || avatar_id.isNull())
{
return false;
}
const std::string set_name = mContactSetCombo->getValue().asString();
if (LGGContactSets::getInstance()->isInternalSetName(set_name))
{
return false;
}
if (drop)
{
uuid_vec_t ids{ avatar_id };
LGGContactSets::instance().addToSet(ids, set_name);
}
return true;
}
void FSPanelContactSets::onClickAddAvatar(LLUICtrl* ctrl)
{
LLFloater* root_floater = gFloaterView->getParentFloater(this);
@@ -259,6 +350,22 @@ void FSPanelContactSets::onClickRemoveAvatar()
LLNotificationsUtil::add((selected_size > 1 ? "RemoveContactsFromSet" : "RemoveContactFromSet"), args, payload, &LGGContactSets::handleRemoveAvatarFromSetCallback);
}
void FSPanelContactSets::onClickMoveAvatar()
{
if (!(mContactSetCombo && !mAvatarSelections.empty()))
{
return;
}
const std::string set = mContactSetCombo->getValue().asString();
if (LGGContactSets::getInstance()->isInternalSetName(set))
{
return;
}
LLAvatarActions::moveToContactSet(mAvatarSelections, set);
}
void FSPanelContactSets::onClickAddSet()
{
LLNotificationsUtil::add("AddNewContactSet", LLSD(), LLSD(), &LGGContactSets::handleAddContactSetCallback);
@@ -311,10 +418,26 @@ void FSPanelContactSets::onClickOfferTeleport()
void FSPanelContactSets::onClickSetPseudonym()
{
if (mAvatarSelections.empty())
{
return;
}
LLSD payload, args;
args["AVATAR"] = LLSLURL("agent", mAvatarSelections.front(), "about").getSLURLString();
payload["id"] = mAvatarSelections.front();
LLNotificationsUtil::add("SetAvatarPseudonym", args, payload, &LGGContactSets::handleSetAvatarPseudonymCallback);
if (mAvatarSelections.size() == 1)
{
args["AVATAR"] = LLSLURL("agent", mAvatarSelections.front(), "about").getSLURLString();
payload["id"] = mAvatarSelections.front();
}
else
{
args["COUNT"] = llformat("%d", static_cast<S32>(mAvatarSelections.size()));
for (const LLUUID& id : mAvatarSelections)
{
payload["ids"].append(id);
}
}
LLNotificationsUtil::add((mAvatarSelections.size() > 1 ? "SetAvatarPseudonymMultiple" : "SetAvatarPseudonym"), args, payload, &LGGContactSets::handleSetAvatarPseudonymCallback);
}
void FSPanelContactSets::onClickRemovePseudonym()
@@ -340,3 +463,22 @@ void FSPanelContactSets::onClickRemoveDisplayName()
}
}
}
void FSPanelContactSets::onFilterEdit(const std::string& search_string)
{
std::string trimmed_search = search_string;
LLStringUtil::trimHead(trimmed_search);
std::string search_upper = trimmed_search;
LLStringUtil::toUpper(search_upper);
if (mFilterSubString == search_upper)
{
return;
}
mFilterSubString = search_upper;
if (mAvatarList)
{
mAvatarList->setNameFilter(trimmed_search);
}
}
+9 -1
View File
@@ -32,17 +32,19 @@
#include "lggcontactsets.h"
#include "llavatarlist.h"
#include "llcallingcard.h"
#include "llcombobox.h"
#include "llpanel.h"
#include <boost/signals2.hpp>
class FSPanelContactSets : public LLPanel
class FSPanelContactSets : public LLPanel, public LLFriendObserver
{
public:
FSPanelContactSets();
bool postBuild() override;
void refreshSetList();
void changed(U32 changed_mask) override;
private:
~FSPanelContactSets();
@@ -51,6 +53,7 @@ private:
void generateAvatarList(const std::string& contact_set);
void onClickAddAvatar(LLUICtrl* ctrl);
void handlePickerCallback(const uuid_vec_t& ids, const std::string& set);
void onClickMoveAvatar();
void onClickRemoveAvatar();
void onClickOpenProfile();
void onClickStartIM();
@@ -61,9 +64,13 @@ private:
void onClickRemoveDisplayName();
void onClickSetPseudonym();
void onClickRemovePseudonym();
void onFilterEdit(const std::string& search_string);
bool handleAvatarDrop(const LLUUID& avatar_id, bool drop);
void refreshContactSets();
void resetControls();
void updateAvatarListSorting();
bool shouldSortByOnlineStatus() const;
void updateSets(LGGContactSets::EContactSetUpdate type);
boost::signals2::connection mContactSetChangedConnection;
@@ -72,6 +79,7 @@ private:
LLComboBox* mContactSetCombo{ nullptr };
LLAvatarList* mAvatarList{ nullptr };
std::string mFilterSubString{};
};
#endif // FS_PANELCONTACTSETS_H
+3
View File
@@ -923,6 +923,9 @@ void FSPanelLogin::loadLoginPage()
// login page (web) content version
params["login_content_version"] = gSavedSettings.getString("LoginContentVersion");
// skin
params["skin"] = gSavedSettings.getString("FSInternalSkinCurrent") + " " + gSavedSettings.getString("FSInternalSkinCurrentTheme");
// No version popup
if (gSavedSettings.getBOOL("FSNoVersionPopup"))
{
+629
View File
@@ -0,0 +1,629 @@
/**
* @file fspanelpreferenceuisounds.cpp
* @brief FSPanelPreferenceUISounds class: UI Sounds preferences tab
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Copyright (C) 2026, Firestorm Viewer Project
*
* 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.
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "fspanelpreferenceuisounds.h"
#include "llcheckboxctrl.h"
#include "llclipboard.h"
#include "llcombobox.h"
#include "llcontrol.h"
#include "llfiltereditor.h"
#include "lllineeditor.h"
#include "llmenugl.h"
#include "llscrolllistcell.h"
#include "llscrolllistctrl.h"
#include "lltextbox.h"
#include "llui.h"
#include "llviewercontrol.h"
#include "llviewermenu.h"
#ifdef OPENSIM
#include "llviewernetwork.h"
#endif
static LLPanelInjector<FSPanelPreferenceUISounds> t_pref_ui_sounds("fs_panel_preference_ui_sounds");
static constexpr S32 COL_LABEL = 0;
static constexpr S32 COL_CHECK = 1;
static constexpr S32 COL_STATUS = 2;
// ---------------------------------------------------------------------------
FSPanelPreferenceUISounds::FSPanelPreferenceUISounds()
: LLPanelPreference()
{
mCommitCallbackRegistrar.add("Pref.SelectUISound", boost::bind(&FSPanelPreferenceUISounds::onSelectSound, this));
mCommitCallbackRegistrar.add("Pref.UpdateUISoundFilter", boost::bind(&FSPanelPreferenceUISounds::onUpdateFilter, this));
mCommitCallbackRegistrar.add("Pref.CommitUISoundUUID", boost::bind(&FSPanelPreferenceUISounds::onCommitUUID, this));
mCommitCallbackRegistrar.add("Pref.CommitUISoundPlayCheck", boost::bind(&FSPanelPreferenceUISounds::onCommitPlayCheck, this));
mCommitCallbackRegistrar.add("Pref.CommitUISoundPlayCombo", boost::bind(&FSPanelPreferenceUISounds::onCommitPlayCombo, this));
mCommitCallbackRegistrar.add("Pref.PreviewSelectedUISound", boost::bind(&FSPanelPreferenceUISounds::onPreviewSound, this));
mCommitCallbackRegistrar.add("Pref.ResetSelectedUISound", boost::bind(&FSPanelPreferenceUISounds::onResetSound, this));
}
bool FSPanelPreferenceUISounds::postBuild()
{
mSoundsList = getChild<LLScrollListCtrl>("ui_sounds_list");
mFilter = getChild<LLFilterEditor>("ui_sound_filter");
mUUIDEditor = getChild<LLLineEditor>("ui_sound_uuid");
mPlayCheck = getChild<LLCheckBoxCtrl>("ui_sound_play_checkbox");
mPlayCombo = getChild<LLComboBox>("ui_sound_play_combo");
mSelectedLabel = getChild<LLTextBox>("ui_sound_selected_label");
mSettingName = getChild<LLTextBox>("ui_sound_setting_name");
mSoundsList->setFilterColumn(COL_LABEL);
mSoundsList->setDoubleClickCallback(boost::bind(&FSPanelPreferenceUISounds::onDoubleClick, this));
mSoundsList->setRightMouseDownCallback(boost::bind(&FSPanelPreferenceUISounds::onRightClick, this, _1, _2, _3, _4));
LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
registrar.add("UISounds.CopyUUID", boost::bind(&FSPanelPreferenceUISounds::onCopyUUID, this));
LLContextMenu::Params menu_params;
menu_params.name("ui_sounds_context_menu");
menu_params.visible(false);
LLContextMenu* menu = LLUICtrlFactory::create<LLContextMenu>(menu_params);
LLMenuItemCallGL::Params item_params;
item_params.label("Copy sound UUID");
item_params.name("copy_uuid");
item_params.on_click.function_name = "UISounds.CopyUUID";
menu->addChild(LLUICtrlFactory::create<LLMenuItemCallGL>(item_params));
mContextMenuHandle = menu->getHandle();
gMenuHolder->addChild(menu);
applyLocalizedLabels();
buildList();
refreshEditor();
return LLPanelPreference::postBuild();
}
// ---------------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------------
void FSPanelPreferenceUISounds::refreshList()
{
applyLocalizedLabels();
buildList();
refreshEditor();
}
// ---------------------------------------------------------------------------
// List building
// ---------------------------------------------------------------------------
FSPanelPreferenceUISounds::UISoundEntry FSPanelPreferenceUISounds::makeEntry(
std::string_view sound,
const std::optional<std::string_view>& label_override,
bool combo,
bool inverted)
{
UISoundEntry e;
e.sound_setting = sound;
e.playmode_setting = "PlayMode" + e.sound_setting;
e.label_control = label_override.value_or("textFS" + e.sound_setting.substr(5));
e.uses_combo = combo;
e.inverted_bool = inverted;
return e;
}
void FSPanelPreferenceUISounds::buildList()
{
if (!mSoundsList)
{
return;
}
const S32 prev_sel = getSelectedIndex();
if (mEntries.empty())
{
mEntries = {
makeEntry("UISndAlert"),
makeEntry("UISndBadKeystroke"),
makeEntry("UISndClick"),
makeEntry("UISndClickRelease"),
makeEntry("UISndHealthReductionF"),
makeEntry("UISndHealthReductionM"),
makeEntry("UISndMoneyChangeDown"),
makeEntry("UISndMoneyChangeUp"),
makeEntry("UISndNearbyChat"),
makeEntry("UISndNewIncomingIMSession", std::nullopt, true),
makeEntry("UISndNewIncomingGroupIMSession", std::nullopt, true),
makeEntry("UISndNewIncomingConfIMSession", std::nullopt, true),
makeEntry("UISndStartIM"),
makeEntry("UISndChatMention"),
makeEntry("UISndObjectCreate"),
makeEntry("UISndObjectDelete"),
makeEntry("UISndObjectRezIn"),
makeEntry("UISndObjectRezOut"),
makeEntry("UISndSnapshot", std::nullopt, false, true),
makeEntry("UISndTeleportOut"),
makeEntry("UISndPieMenuAppear"),
makeEntry("UISndPieMenuHide"),
makeEntry("UISndPieMenuSliceHighlight0"),
makeEntry("UISndPieMenuSliceHighlight1"),
makeEntry("UISndPieMenuSliceHighlight2"),
makeEntry("UISndPieMenuSliceHighlight3"),
makeEntry("UISndPieMenuSliceHighlight4"),
makeEntry("UISndPieMenuSliceHighlight5"),
makeEntry("UISndPieMenuSliceHighlight6"),
makeEntry("UISndPieMenuSliceHighlight7"),
makeEntry("UISndTyping"),
makeEntry("UISndWindowClose"),
makeEntry("UISndWindowOpen"),
makeEntry("UISndScriptFloaterOpen"),
makeEntry("UISndScriptFloaterClose"),
makeEntry("UISndFriendOnline"),
makeEntry("UISndFriendOffline"),
makeEntry("UISndFriendshipOffer"),
makeEntry("UISndTeleportOffer"),
makeEntry("UISndInventoryOffer"),
makeEntry("UISndIncomingVoiceCall"),
makeEntry("UISndGroupInvitation"),
makeEntry("UISndGroupNotice"),
makeEntry("UISndQuestionExperience"),
makeEntry("UISndInvalidOp"),
makeEntry("UISndMovelockToggle"),
makeEntry("UISndFootsteps"),
makeEntry("UISndTrackerBeacon"),
makeEntry("UISndMicToggle"),
makeEntry("UISndRestart"),
makeEntry("UISndRestartOpenSim"),
};
}
mSoundsList->deleteAllItems();
for (size_t i = 0; i < mEntries.size(); ++i)
{
UISoundEntry& entry = mEntries[i];
#ifdef OPENSIM
const bool show_opensim = LLGridManager::instance().isInOpenSim();
#else
const bool show_opensim = false;
#endif
if (entry.sound_setting == "UISndRestartOpenSim" && !show_opensim)
{
continue;
}
entry.display_label = resolveEntryLabel(entry);
LLSD row;
row["id"] = LLSD::Integer(i);
row["columns"][COL_LABEL]["column"] = "ui_sound_label";
row["columns"][COL_LABEL]["value"] = entry.display_label;
if (entry.uses_combo)
{
row["columns"][COL_CHECK]["column"] = "ui_sound_check";
row["columns"][COL_CHECK]["value"] = "";
row["columns"][COL_STATUS]["column"] = "ui_sound_status";
row["columns"][COL_STATUS]["value"] = getModeLabel(entry);
}
else
{
bool val = gSavedSettings.getBOOL(entry.playmode_setting);
bool is_enabled = entry.inverted_bool ? !val : val;
row["columns"][COL_CHECK]["type"] = "checkbox";
row["columns"][COL_CHECK]["column"] = "ui_sound_check";
row["columns"][COL_CHECK]["value"] = is_enabled;
row["columns"][COL_STATUS]["column"] = "ui_sound_status";
row["columns"][COL_STATUS]["value"] = getModeLabel(entry);
}
mSoundsList->addElement(row);
}
if (prev_sel >= 0)
{
for (LLScrollListItem* row : mSoundsList->getAllData())
{
if (row && row->getValue().asInteger() == prev_sel)
{
mSoundsList->selectNthItem(mSoundsList->getItemIndex(row));
break;
}
}
}
if (mSoundsList->getItemCount() > 0 && !mSoundsList->getFirstSelected())
{
mSoundsList->selectFirstItem();
}
}
// ---------------------------------------------------------------------------
// Editor refresh
// ---------------------------------------------------------------------------
void FSPanelPreferenceUISounds::refreshEditor()
{
if (!mSelectedLabel || !mSettingName || !mUUIDEditor || !mPlayCheck || !mPlayCombo)
{
return;
}
const auto entry = getSelectedEntry();
const bool has_sel = entry.has_value();
mUUIDEditor->setEnabled(has_sel);
if (!has_sel)
{
mSelectedLabel->setValue(getPanelString("ui_sound_select_prompt"));
mSettingName->setValue(LLStringUtil::null);
mUUIDEditor->setValue(LLSD());
mPlayCheck->setVisible(false);
mPlayCombo->setVisible(false);
return;
}
mSelectedLabel->setValue(entry->display_label.empty() ? entry->sound_setting : entry->display_label);
mSettingName->setValue(entry->sound_setting);
mUUIDEditor->setValue(gSavedSettings.getString(entry->sound_setting));
if (LLControlVariable* ctrl = gSavedSettings.getControl(entry->sound_setting))
{
mUUIDEditor->setToolTip(ctrl->getDefault().asString());
}
if (entry->uses_combo)
{
mPlayCheck->setVisible(false);
mPlayCombo->setVisible(true);
mPlayCombo->setValue(static_cast<int>(gSavedSettings.getU32(entry->playmode_setting)));
}
else
{
mPlayCombo->setVisible(false);
mPlayCheck->setVisible(true);
bool val = gSavedSettings.getBOOL(entry->playmode_setting);
mPlayCheck->setValue(entry->inverted_bool ? !val : val);
}
}
// ---------------------------------------------------------------------------
// Callbacks
// ---------------------------------------------------------------------------
void FSPanelPreferenceUISounds::onSelectSound()
{
if (!mSoundsList)
{
return;
}
if (LLScrollListItem* selected = mSoundsList->getFirstSelected())
{
S32 idx = selected->getValue().asInteger();
if (idx >= 0 && idx < static_cast<S32>(mEntries.size()))
{
const UISoundEntry& entry = mEntries[idx];
if (!entry.uses_combo)
{
bool checked = selected->getColumn(COL_CHECK)->getValue().asBoolean();
bool val = gSavedSettings.getBOOL(entry.playmode_setting);
bool was_enabled = entry.inverted_bool ? !val : val;
if (checked != was_enabled)
{
gSavedSettings.setBOOL(entry.playmode_setting, entry.inverted_bool ? !checked : checked);
if (LLScrollListText* text_cell = dynamic_cast<LLScrollListText*>(selected->getColumn(COL_STATUS)))
{
text_cell->setText(getModeLabel(entry));
}
}
}
}
}
refreshEditor();
}
void FSPanelPreferenceUISounds::onUpdateFilter()
{
if (mSoundsList && mFilter)
{
mSoundsList->setFilterString(mFilter->getValue().asString());
}
}
void FSPanelPreferenceUISounds::onCommitUUID()
{
const auto entry = getSelectedEntry();
if (!entry || !mUUIDEditor)
{
return;
}
gSavedSettings.setString(entry->sound_setting, mUUIDEditor->getValue().asString());
}
void FSPanelPreferenceUISounds::onCommitPlayCheck()
{
const auto entry = getSelectedEntry();
if (!entry || entry->uses_combo || !mPlayCheck)
{
return;
}
bool checked = mPlayCheck->getValue().asBoolean();
gSavedSettings.setBOOL(entry->playmode_setting, entry->inverted_bool ? !checked : checked);
LLScrollListItem* selected = mSoundsList ? mSoundsList->getFirstSelected() : nullptr;
if (selected)
{
selected->getColumn(COL_CHECK)->setValue(checked);
if (LLScrollListText* text_cell = dynamic_cast<LLScrollListText*>(selected->getColumn(COL_STATUS)))
{
text_cell->setText(getModeLabel(*entry));
}
}
}
void FSPanelPreferenceUISounds::onCommitPlayCombo()
{
const auto entry = getSelectedEntry();
if (!entry || !entry->uses_combo || !mPlayCombo)
{
return;
}
U32 mode = static_cast<U32>(mPlayCombo->getValue().asInteger());
gSavedSettings.setU32(entry->playmode_setting, mode);
LLScrollListItem* selected = mSoundsList ? mSoundsList->getFirstSelected() : nullptr;
if (selected)
{
if (LLScrollListText* text_cell = dynamic_cast<LLScrollListText*>(selected->getColumn(COL_STATUS)))
{
text_cell->setText(getModeLabel(*entry));
}
}
}
void FSPanelPreferenceUISounds::onPreviewSound()
{
const auto entry = getSelectedEntry();
if (entry)
{
make_ui_sound(entry->sound_setting.c_str(), true);
}
}
void FSPanelPreferenceUISounds::onResetSound()
{
const auto entry = getSelectedEntry();
if (!entry)
{
return;
}
if (LLControlVariable* ctrl = gSavedSettings.getControl(entry->sound_setting))
{
ctrl->resetToDefault();
}
if (LLControlVariable* ctrl = gSavedSettings.getControl(entry->playmode_setting))
{
ctrl->resetToDefault();
}
LLScrollListItem* selected = mSoundsList ? mSoundsList->getFirstSelected() : nullptr;
if (selected)
{
if (entry->uses_combo)
{
if (LLScrollListText* text_cell = dynamic_cast<LLScrollListText*>(selected->getColumn(COL_STATUS)))
{
text_cell->setText(getModeLabel(*entry));
}
}
else
{
bool val = gSavedSettings.getBOOL(entry->playmode_setting);
bool is_enabled = entry->inverted_bool ? !val : val;
selected->getColumn(COL_CHECK)->setValue(is_enabled);
if (LLScrollListText* text_cell = dynamic_cast<LLScrollListText*>(selected->getColumn(COL_STATUS)))
{
text_cell->setText(getModeLabel(*entry));
}
}
}
refreshEditor();
}
void FSPanelPreferenceUISounds::onDoubleClick()
{
onPreviewSound();
}
void FSPanelPreferenceUISounds::onRightClick(LLUICtrl* ctrl, S32 x, S32 y, MASK mask)
{
mSoundsList->selectItemAt(x, y, mask);
refreshEditor();
const auto entry = getSelectedEntry();
if (!entry)
{
return;
}
LLContextMenu* menu = mContextMenuHandle.get();
if (menu)
{
S32 screen_x, screen_y;
mSoundsList->localPointToScreen(x, y, &screen_x, &screen_y);
menu->show(screen_x, screen_y, mSoundsList);
}
}
void FSPanelPreferenceUISounds::onCopyUUID()
{
const auto entry = getSelectedEntry();
if (!entry)
{
return;
}
std::string uuid = gSavedSettings.getString(entry->sound_setting);
LLClipboard::instance().copyToClipboard(utf8str_to_wstring(uuid), 0, static_cast<S32>(uuid.size()));
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
void FSPanelPreferenceUISounds::applyLocalizedLabels()
{
if (mPlayCheck)
{
std::string lbl = getPanelString("ui_sound_play_this");
if (!lbl.empty()) mPlayCheck->setLabel(lbl);
}
LLContextMenu* menu = mContextMenuHandle.get();
if (menu)
{
std::string lbl = getPanelString("ui_sound_copy_uuid");
if (!lbl.empty())
{
if (LLMenuItemGL* item = menu->findChild<LLMenuItemGL>("copy_uuid"))
{
item->setLabel(lbl);
}
}
}
if (mPlayCombo)
{
static const struct { const char* value; const char* key; } rows[] = {
{"1", "ui_sound_playmode_new_session"},
{"2", "ui_sound_playmode_every_message"},
{"3", "ui_sound_playmode_not_focus"},
{"0", "ui_sound_playmode_mute"},
};
for (const auto& r : rows)
{
LLScrollListItem* item = mPlayCombo->getItemByValue(LLSD(r.value));
if (!item)
continue;
LLScrollListCell* cell = item->getColumn(0);
if (LLScrollListText* tc = dynamic_cast<LLScrollListText*>(cell))
{
std::string s = getPanelString(r.key);
if (!s.empty()) tc->setText(s);
}
}
mPlayCombo->updateLabel();
}
}
std::string FSPanelPreferenceUISounds::getPanelString(std::string_view name) const
{
if (hasString(name))
{
return getString(name);
}
LLView* ancestor = getParent();
while (ancestor)
{
LLPanel* panel = dynamic_cast<LLPanel*>(ancestor);
if (panel && panel->hasString(name))
{
return panel->getString(name);
}
ancestor = ancestor->getParent();
}
return LLStringUtil::null;
}
std::string FSPanelPreferenceUISounds::resolveEntryLabel(const UISoundEntry& entry) const
{
std::string label = getPanelString(entry.label_control);
if (label.empty())
{
label = entry.sound_setting;
}
LLStringUtil::trim(label);
if (!label.empty() && label.back() == ':')
{
label.pop_back();
}
return label;
}
std::string FSPanelPreferenceUISounds::getModeLabel(const UISoundEntry& entry) const
{
if (entry.uses_combo)
{
const U32 mode = gSavedSettings.getU32(entry.playmode_setting);
switch (mode)
{
case 1:
return getPanelString("ui_sound_playmode_new_session");
case 2:
return getPanelString("ui_sound_playmode_every_message");
case 3:
return getPanelString("ui_sound_playmode_not_focus");
case 0:
default:
return getPanelString("ui_sound_playmode_mute");
}
}
const bool enabled = gSavedSettings.getBOOL(entry.playmode_setting);
if (entry.inverted_bool)
{
return enabled ? getPanelString("ui_sound_playmode_mute") : getPanelString("ui_sound_play_this");
}
return enabled ? getPanelString("ui_sound_play_this") : getPanelString("ui_sound_playmode_mute");
}
S32 FSPanelPreferenceUISounds::getSelectedIndex() const
{
if (!mSoundsList)
{
return -1;
}
LLScrollListItem* selected = mSoundsList->getFirstSelected();
if (!selected)
{
return -1;
}
return selected->getValue().asInteger();
}
std::optional<FSPanelPreferenceUISounds::UISoundEntry> FSPanelPreferenceUISounds::getSelectedEntry() const
{
S32 idx = getSelectedIndex();
if (idx < 0 || idx >= static_cast<S32>(mEntries.size()))
{
return {};
}
return mEntries[idx];
}
+89
View File
@@ -0,0 +1,89 @@
/**
* @file fspanelpreferenceuisounds.h
* @brief FSPanelPreferenceUISounds class UI Sounds preferences tab
*
* $LicenseInfo:firstyear=2026&license=viewerlgpl$
* Copyright (C) 2026, Firestorm Viewer Project
*
* 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.
* $/LicenseInfo$
*/
#ifndef FS_PANEL_PREFERENCE_UI_SOUNDS_H
#define FS_PANEL_PREFERENCE_UI_SOUNDS_H
#include "llfloaterpreference.h"
#include <optional>
#include <vector>
class LLCheckBoxCtrl;
class LLContextMenu;
class LLScrollListCtrl;
class LLFilterEditor;
class LLLineEditor;
class LLComboBox;
class LLTextBox;
class FSPanelPreferenceUISounds : public LLPanelPreference
{
public:
FSPanelPreferenceUISounds();
bool postBuild() override;
void refreshList();
private:
struct UISoundEntry
{
std::string sound_setting;
std::string playmode_setting;
std::string label_control;
std::string display_label;
bool uses_combo = false;
bool inverted_bool = false;
};
void buildList();
static UISoundEntry makeEntry(std::string_view sound, const std::optional<std::string_view>& label_override = std::nullopt, bool combo = false, bool inverted = false);
void refreshEditor();
void onSelectSound();
void onUpdateFilter();
void onCommitUUID();
void onCommitPlayCheck();
void onCommitPlayCombo();
void onPreviewSound();
void onResetSound();
void onDoubleClick();
void onRightClick(LLUICtrl* ctrl, S32 x, S32 y, MASK mask);
void onCopyUUID();
void applyLocalizedLabels();
std::string resolveEntryLabel(const UISoundEntry& entry) const;
std::string getModeLabel(const UISoundEntry& entry) const;
std::string getPanelString(std::string_view name) const;
S32 getSelectedIndex() const;
std::optional<UISoundEntry> getSelectedEntry() const;
LLScrollListCtrl* mSoundsList = nullptr;
LLFilterEditor* mFilter = nullptr;
LLLineEditor* mUUIDEditor = nullptr;
LLCheckBoxCtrl* mPlayCheck = nullptr;
LLComboBox* mPlayCombo = nullptr;
LLTextBox* mSelectedLabel = nullptr;
LLTextBox* mSettingName = nullptr;
std::vector<UISoundEntry> mEntries;
LLHandle<LLContextMenu> mContextMenuHandle;
LOG_CLASS(FSPanelPreferenceUISounds);
};
#endif // FS_PANEL_PREFERENCE_UI_SOUNDS_H
+3 -4
View File
@@ -30,7 +30,6 @@
// libs
#include "llfiltereditor.h"
#include "llmenugl.h"
#include "lluictrlfactory.h"
#include "llmenubutton.h"
#include "lltoggleablemenu.h"
@@ -39,6 +38,7 @@
#include "fscommon.h"
#include "fsradarmenu.h"
#include "llavataractions.h"
#include "llcallingcard.h"
#include "llnetmap.h"
#include "llpanelblockedlist.h"
#include "llviewercontrol.h" // for gSavedSettings
@@ -59,14 +59,13 @@ public:
LLAvatarTracker::instance().addObserver(this);
}
~FSButtonsUpdater()
~FSButtonsUpdater() override
{
LLAvatarTracker::instance().removeObserver(this);
}
/*virtual*/ void changed(U32 mask)
void changed(U32) override
{
(void) mask;
update();
}
};
-1
View File
@@ -31,7 +31,6 @@
#include "fsradar.h"
#include "fsradarlistctrl.h"
#include "llcallingcard.h" // for avatar tracker
class LLButton;
class LLFilterEditor;
+4 -4
View File
@@ -50,7 +50,6 @@
#include "lltracker.h"
#include "lltrans.h"
#include "llviewercontrol.h" // for gSavedSettings
#include "llviewermenu.h" // for gMenuHolder
#include "llviewerparcelmgr.h"
#include "llvoavatar.h"
#include "llvoiceclient.h"
@@ -60,13 +59,14 @@
#include "rlvactions.h"
#include "rlvhandler.h"
constexpr F32 FS_RADAR_LIST_UPDATE_INTERVAL = 1.f;
/**
* Periodically updates the nearby people list while the Nearby tab is active.
*
* The period is defined by FS_RADAR_LIST_UPDATE_INTERVAL constant.
*/
constexpr F32 FS_RADAR_LIST_UPDATE_INTERVAL = 1.f;
class FSRadarListUpdater : public FSRadar::Updater, public LLEventTimer
{
LOG_CLASS(FSRadarListUpdater);
@@ -80,7 +80,7 @@ public:
mEventTimer.start();
}
/*virtual*/ bool tick()
bool tick() override
{
update();
return false;
+2 -3
View File
@@ -86,13 +86,12 @@ public:
class Updater
{
public:
typedef std::function<void()> callback_t;
using callback_t = std::function<void()>;
Updater(callback_t cb)
: mCallback(cb)
{ }
virtual ~Updater()
{ }
virtual ~Updater() = default;
protected:
void update()
+5
View File
@@ -551,6 +551,8 @@ bool FSVirtualTrackpad::handleMouseDown(S32 x, S32 y, MASK mask)
{
if (isPointInTouchArea(x, y))
{
mWheelClicksSinceLastDelta = 0;
determineThumbClickError(x, y);
updateClickErrorIfInfiniteScrolling();
gFocusMgr.setMouseCapture(this);
@@ -605,6 +607,9 @@ bool FSVirtualTrackpad::handleScrollWheel(S32 x, S32 y, S32 clicks)
mWheelClicksSinceLastDelta = -1 * clicks * changeAmount;
mValueDeltaX = mValueDeltaY = mValueDeltaZ = 0;
mPinchValueDeltaX = mPinchValueDeltaY = mPinchValueDeltaZ = 0;
if (mDoingPinchMode)
{
if ((mask & (MASK_SHIFT | MASK_ALT)) == MASK_ALT)
+204 -4
View File
@@ -43,6 +43,48 @@
constexpr F32 COLOR_DAMPENING = 0.8f;
constexpr char CONTACT_SETS_FILE[] = "settings_friends_groups.xml";
namespace
{
struct contact_set_autoresponse_ref_t
{
bool* enabled;
std::string* message;
};
struct const_contact_set_autoresponse_ref_t
{
const bool* enabled;
const std::string* message;
};
contact_set_autoresponse_ref_t get_autoresponse_ref(LGGContactSets::ContactSet* set, ContactSetAutoresponseMode mode)
{
switch (mode)
{
case ContactSetAutoresponseMode::BUSY:
return { &set->mAutoresponseBusyEnabled, &set->mAutoresponseBusy };
case ContactSetAutoresponseMode::AUTORESPONSE:
return { &set->mAutoresponseModeEnabled, &set->mAutoresponseMode };
case ContactSetAutoresponseMode::AUTORESPONSE_NONFRIENDS:
return { &set->mAutoresponseNonFriendsEnabled, &set->mAutoresponseNonFriends };
}
return { nullptr, nullptr };
}
const_contact_set_autoresponse_ref_t get_autoresponse_ref(const LGGContactSets::ContactSet* set, ContactSetAutoresponseMode mode)
{
switch (mode)
{
case ContactSetAutoresponseMode::BUSY:
return { &set->mAutoresponseBusyEnabled, &set->mAutoresponseBusy };
case ContactSetAutoresponseMode::AUTORESPONSE:
return { &set->mAutoresponseModeEnabled, &set->mAutoresponseMode };
case ContactSetAutoresponseMode::AUTORESPONSE_NONFRIENDS:
return { &set->mAutoresponseNonFriendsEnabled, &set->mAutoresponseNonFriends };
}
return { nullptr, nullptr };
}
}
LGGContactSets::LGGContactSets()
{
}
@@ -110,6 +152,13 @@ LLSD LGGContactSets::exportContactSet(std::string_view set_name)
ret["groupname"] = set->mName;
ret["color"] = set->mColor.getValue();
ret["notices"] = set->mNotify;
ret["sort_by_online_status"] = set->mSortByOnlineStatus;
ret["autoresponse_busy_enabled"] = set->mAutoresponseBusyEnabled;
ret["autoresponse_busy"] = set->mAutoresponseBusy;
ret["autoresponse_mode_enabled"] = set->mAutoresponseModeEnabled;
ret["autoresponse_mode"] = set->mAutoresponseMode;
ret["autoresponse_nonfriends_enabled"] = set->mAutoresponseNonFriendsEnabled;
ret["autoresponse_nonfriends"] = set->mAutoresponseNonFriends;
for (const auto& friend_id : set->mFriends)
{
ret["friends"][friend_id.asString()] = "";
@@ -212,6 +261,13 @@ LLSD LGGContactSets::exportToLLSD()
{
output[name]["color"] = set->mColor.getValue();
output[name]["notify"] = set->mNotify;
output[name]["sort_by_online_status"] = set->mSortByOnlineStatus;
output[name]["autoresponse_busy_enabled"] = set->mAutoresponseBusyEnabled;
output[name]["autoresponse_busy"] = set->mAutoresponseBusy;
output[name]["autoresponse_mode_enabled"] = set->mAutoresponseModeEnabled;
output[name]["autoresponse_mode"] = set->mAutoresponseMode;
output[name]["autoresponse_nonfriends_enabled"] = set->mAutoresponseNonFriendsEnabled;
output[name]["autoresponse_nonfriends"] = set->mAutoresponseNonFriends;
for (const auto& friend_id : set->mFriends)
{
output[name]["friends"][friend_id.asString()] = "";
@@ -276,6 +332,46 @@ void LGGContactSets::importFromLLSD(const LLSD& data)
}
new_set->mNotify = notify;
bool sort_by_online_status = false;
if (set_data.has("sort_by_online_status"))
{
sort_by_online_status = set_data["sort_by_online_status"].asBoolean();
}
new_set->mSortByOnlineStatus = sort_by_online_status;
bool autoresponse_busy_enabled = false;
if (set_data.has("autoresponse_busy_enabled"))
{
autoresponse_busy_enabled = set_data["autoresponse_busy_enabled"].asBoolean();
}
new_set->mAutoresponseBusyEnabled = autoresponse_busy_enabled;
if (set_data.has("autoresponse_busy"))
{
new_set->mAutoresponseBusy = LLStringFn::strip_invalid_xml(set_data["autoresponse_busy"].asString());
}
bool autoresponse_mode_enabled = false;
if (set_data.has("autoresponse_mode_enabled"))
{
autoresponse_mode_enabled = set_data["autoresponse_mode_enabled"].asBoolean();
}
new_set->mAutoresponseModeEnabled = autoresponse_mode_enabled;
if (set_data.has("autoresponse_mode"))
{
new_set->mAutoresponseMode = LLStringFn::strip_invalid_xml(set_data["autoresponse_mode"].asString());
}
bool autoresponse_nonfriends_enabled = false;
if (set_data.has("autoresponse_nonfriends_enabled"))
{
autoresponse_nonfriends_enabled = set_data["autoresponse_nonfriends_enabled"].asBoolean();
}
new_set->mAutoresponseNonFriendsEnabled = autoresponse_nonfriends_enabled;
if (set_data.has("autoresponse_nonfriends"))
{
new_set->mAutoresponseNonFriends = LLStringFn::strip_invalid_xml(set_data["autoresponse_nonfriends"].asString());
}
if (set_data.has("friends"))
{
for (const auto& [av_key, av_value] : llsd::inMap(set_data["friends"]))
@@ -503,6 +599,7 @@ bool LGGContactSets::hasFriendColorThatShouldShow(const LLUUID& friend_id, Conta
static LLCachedControl<bool> fsContactSetsColorizeTag(gSavedSettings,"FSContactSetsColorizeNameTag", false);
static LLCachedControl<bool> fsContactSetsColorizeRadar(gSavedSettings,"FSContactSetsColorizeRadar", false);
static LLCachedControl<bool> fsContactSetsColorizeMiniMap(gSavedSettings,"FSContactSetsColorizeMiniMap", false);
static LLCachedControl<bool> fsContactSetsColorizeFriends(gSavedSettings,"FSContactSetsColorizeFriends", false);
switch (type)
{
@@ -523,6 +620,10 @@ bool LGGContactSets::hasFriendColorThatShouldShow(const LLUUID& friend_id, Conta
if (!fsContactSetsColorizeMiniMap)
return false;
break;
case ContactSetType::FRIENDS:
if (!fsContactSetsColorizeFriends)
return false;
break;
};
/// don't show friend color if they are no longer a friend
@@ -753,7 +854,7 @@ uuid_vec_t LGGContactSets::getListOfPseudonymAvs() const
for (const auto& [id, pseudonym] : mPseudonyms)
{
pseudonyms.emplace_back(pseudonym);
pseudonyms.emplace_back(id);
}
return pseudonyms;
@@ -929,8 +1030,15 @@ void LGGContactSets::addSet(std::string_view set_name)
{
ContactSet* set = new ContactSet();
set->mName = set_name;
set->mColor = LLColor4::red;
set->mColor = getDefaultColor();
set->mNotify = false;
set->mSortByOnlineStatus = false;
set->mAutoresponseBusyEnabled = false;
set->mAutoresponseBusy.clear();
set->mAutoresponseModeEnabled = false;
set->mAutoresponseMode.clear();
set->mAutoresponseNonFriendsEnabled = false;
set->mAutoresponseNonFriends.clear();
mContactSets[set_name.data()] = set;
saveToDisk();
mChangedSignal(UPDATED_LISTS);
@@ -1002,6 +1110,86 @@ bool LGGContactSets::getNotifyForSet(std::string_view set_name) const
return false;
}
void LGGContactSets::setSortByOnlineStatusForSet(std::string_view set_name, bool sort_by_online_status)
{
ContactSet* set = getContactSet(set_name);
if (set)
{
set->mSortByOnlineStatus = sort_by_online_status;
saveToDisk();
mChangedSignal(UPDATED_MEMBERS);
}
}
bool LGGContactSets::getSortByOnlineStatusForSet(std::string_view set_name) const
{
if (ContactSet* set = getContactSet(set_name); set)
{
return set->mSortByOnlineStatus;
}
return false;
}
void LGGContactSets::setAutoresponseForSet(std::string_view set_name, ContactSetAutoresponseMode mode, bool enabled, std::string_view response)
{
if (ContactSet* set = getContactSet(set_name); set)
{
contact_set_autoresponse_ref_t autoresponse = get_autoresponse_ref(set, mode);
if (autoresponse.enabled && autoresponse.message)
{
*autoresponse.enabled = enabled;
*autoresponse.message = LLStringFn::strip_invalid_xml(std::string(response));
saveToDisk();
}
}
}
void LGGContactSets::getAutoresponseForSet(std::string_view set_name, ContactSetAutoresponseMode mode, bool& enabled, std::string& response) const
{
enabled = false;
response.clear();
if (const ContactSet* set = getContactSet(set_name); set)
{
const_contact_set_autoresponse_ref_t autoresponse = get_autoresponse_ref(set, mode);
if (autoresponse.enabled && autoresponse.message)
{
enabled = *autoresponse.enabled;
response = *autoresponse.message;
}
}
}
bool LGGContactSets::getAutoresponseForFriend(const LLUUID& friend_id, ContactSetAutoresponseMode mode, std::string& response) const
{
response.clear();
U32 best_set_size = U32_MAX;
bool found = false;
for (const auto& set_name : getFriendSets(friend_id))
{
const ContactSet* set = getContactSet(set_name);
if (!set)
{
continue;
}
const_contact_set_autoresponse_ref_t autoresponse = get_autoresponse_ref(set, mode);
if (!autoresponse.enabled || !autoresponse.message || !(*autoresponse.enabled) || autoresponse.message->empty())
{
continue;
}
const U32 set_size = static_cast<U32>(set->mFriends.size());
if (!found || set_size < best_set_size)
{
response = *autoresponse.message;
best_set_size = set_size;
found = true;
}
}
return found;
}
void LGGContactSets::setSetColor(std::string_view set_name, const LLColor4& color)
{
if (ContactSet* set = getContactSet(set_name); set)
@@ -1100,8 +1288,20 @@ bool LGGContactSets::handleSetAvatarPseudonymCallback(const LLSD& notification,
if (S32 option = LLNotificationsUtil::getSelectedOption(notification, response); option == 0)
{
const std::string pseudonym(response["message"].asString());
const LLUUID id(notification["payload"]["id"].asUUID());
LGGContactSets::getInstance()->setPseudonym(id, pseudonym);
LGGContactSets* contact_sets = LGGContactSets::getInstance();
if (notification["payload"].has("ids"))
{
for (const auto& item : llsd::inArray(notification["payload"]["ids"]))
{
const LLUUID id(item.asUUID());
contact_sets->setPseudonym(id, pseudonym);
}
}
else
{
const LLUUID id(notification["payload"]["id"].asUUID());
contact_sets->setPseudonym(id, pseudonym);
}
}
return false;
}
+22 -2
View File
@@ -32,7 +32,15 @@ enum class ContactSetType
IM,
TAG,
RADAR,
MINIMAP
MINIMAP,
FRIENDS
};
enum class ContactSetAutoresponseMode
{
BUSY,
AUTORESPONSE,
AUTORESPONSE_NONFRIENDS
};
constexpr char CS_SET_ALL_SETS[] = "All Sets";
@@ -93,12 +101,18 @@ public:
void removeNonFriendFromList(const LLUUID& non_friend_id, bool save_changes = true);
bool isNonFriend(const LLUUID& non_friend_id) const;
bool isFriendInAnySet(const LLUUID& friend_id) const;
uuid_vec_t getFriendsInAnySet() const;
uuid_vec_t getListOfNonFriends() const;
uuid_vec_t getListOfPseudonymAvs() const;
bool notifyForFriend(const LLUUID& friend_id) const;
void setNotifyForSet(std::string_view set_name, bool notify);
bool getNotifyForSet(std::string_view set_name) const;
void setSortByOnlineStatusForSet(std::string_view set_name, bool sort_by_online_status);
bool getSortByOnlineStatusForSet(std::string_view set_name) const;
void setAutoresponseForSet(std::string_view set_name, ContactSetAutoresponseMode mode, bool enabled, std::string_view response);
void getAutoresponseForSet(std::string_view set_name, ContactSetAutoresponseMode mode, bool& enabled, std::string& response) const;
bool getAutoresponseForFriend(const LLUUID& friend_id, ContactSetAutoresponseMode mode, std::string& response) const;
bool callbackAliasReset(const LLSD& notification, const LLSD& response);
@@ -116,7 +130,14 @@ public:
std::string mName;
uuid_set_t mFriends;
bool mNotify;
bool mSortByOnlineStatus;
LLColor4 mColor;
bool mAutoresponseBusyEnabled;
std::string mAutoresponseBusy;
bool mAutoresponseModeEnabled;
std::string mAutoresponseMode;
bool mAutoresponseNonFriendsEnabled;
std::string mAutoresponseNonFriends;
};
ContactSet* getContactSet(std::string_view set_name) const;
@@ -142,7 +163,6 @@ public:
private:
void toneDownColor(LLColor4& color) const;
uuid_vec_t getFriendsInSet(std::string_view set_name) const;
uuid_vec_t getFriendsInAnySet() const;
void setPseudonym(const LLUUID& friend_id, std::string_view pseudonym);
bool hasVisuallyDifferentPseudonym(const LLUUID& friend_id) const;
+82 -67
View File
@@ -1,11 +1,6 @@
#!/bin/bash
# Enhanced Firestorm Viewer Installation Script
# Features:
# - Creates a timestamped backup of existing installation
# - Allows installation with custom suffixes for parallel versions (e.g., beta, release, debug)
# - Supports both system-wide and user-specific installations
# - Provides command-line options for flexibility
# Firestorm Viewer Installation Script
# ANSI color codes for styling
VT102_STYLE_NORMAL='\E[0m'
@@ -20,8 +15,8 @@ tarball_path="${RUN_PATH}"
DEFAULT_SYSTEM_INSTALL_DIR="/opt/firestorm"
DEFAULT_USER_INSTALL_DIR="$HOME/firestorm"
# Backup directory base
BACKUP_BASE_DIR="${DEFAULT_SYSTEM_INSTALL_DIR}_backups"
# Default number of retained backups
DEFAULT_RETAIN_BACKUPS=5
# Function to display usage information
usage() {
@@ -30,33 +25,12 @@ usage() {
echo "Options:"
echo " --suffix <suffix> Append a suffix to the installation directory (e.g., beta, release, debug)"
echo " --install-dir <directory> Specify a custom installation directory"
echo " --retain <count> Number of backup versions to keep (default: ${DEFAULT_RETAIN_BACKUPS})"
echo " --yes, -y Non-interactive mode (accept defaults)"
echo " --help, -h Display this help message"
exit 1
}
# Function to prompt the user with a yes/no question
prompt() {
local prompt_message="$1"
local input
echo -n "$prompt_message"
while read -r input; do
case "$input" in
[Yy]* )
return 1
;;
[Nn]* )
return 0
;;
* )
echo "Please enter yes or no."
echo -n "$prompt_message"
;;
esac
done
}
# Function to display warning messages in red
warn() {
echo -e "${VT102_COLOR_RED}$1${VT102_STYLE_NORMAL}"
@@ -79,6 +53,38 @@ backup_previous_installation() {
echo " - Backup created successfully."
}
# Function to remove old backups beyond configured retention
prune_old_backups() {
local install_dir="$1"
local retain="$2"
local backups=()
local i
local path
shopt -s nullglob
for path in "${install_dir}"-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9]; do
[[ -e "$path" ]] || continue
backups+=("$path")
done
shopt -u nullglob
if (( ${#backups[@]} > 0 )); then
mapfile -t backups < <(printf '%s\n' "${backups[@]}" | sort -r)
fi
echo " - Backup retention: found ${#backups[@]} backups, retaining ${retain}"
# Nothing to prune.
if (( ${#backups[@]} <= retain )); then
return
fi
for (( i=retain; i<${#backups[@]}; i++ )); do
echo " - Removing old backup: ${backups[i]}"
rm -rf -- "${backups[i]}" || die "Failed to remove old backup: ${backups[i]}"
done
}
# Function to install Firestorm Viewer to the specified directory
install_to_prefix() {
local install_prefix="$1"
@@ -88,9 +94,18 @@ install_to_prefix() {
backup_previous_installation "$install_prefix"
fi
# Run retention exactly once per invocation.
# This still cleans pre-existing backup clutter when no current install exists.
prune_old_backups "$install_prefix" "$RETAIN_BACKUPS"
# Create the installation directory
mkdir -p "$install_prefix" || die "Failed to create installation directory: $install_prefix"
# Prevent recursive copy if install_prefix is inside tarball_path
if [[ "$(readlink -f "$install_prefix")" == "$(readlink -f "$tarball_path")"* ]]; then
die "Cannot install into a subdirectory of the source package."
fi
echo " - Installing Firestorm Viewer to $install_prefix"
# Copy all files from the tarball to the installation directory
@@ -106,15 +121,21 @@ homedir_install() {
warn "this script as the root user, or with the 'sudo' command."
echo
local install_prefix="$DEFAULT_USER_INSTALL_DIR"
if [[ -n "$SUFFIX" ]]; then
install_prefix="${install_prefix}_${SUFFIX}"
local install_prefix
if [[ -n "$CUSTOM_INSTALL_DIR" ]]; then
install_prefix="$CUSTOM_INSTALL_DIR"
elif [[ -n "$SUFFIX" ]]; then
install_prefix="${DEFAULT_USER_INSTALL_DIR}_${SUFFIX}"
else
install_prefix="$DEFAULT_USER_INSTALL_DIR"
fi
echo -n "Enter the desired installation directory [${install_prefix}]: "
read -r user_input
if [[ -n "$user_input" ]]; then
install_prefix="$user_input"
if [[ "$NON_INTERACTIVE" != "true" ]]; then
echo -n "Enter the desired installation directory [${install_prefix}]: "
read -r user_input
if [[ -n "$user_input" ]]; then
install_prefix="$user_input"
fi
fi
install_to_prefix "${install_prefix}"
@@ -134,10 +155,12 @@ root_install() {
local install_prefix="$default_prefix"
fi
echo -n "Enter the desired installation directory [${install_prefix}]: "
read -r user_input
if [[ -n "$user_input" ]]; then
install_prefix="$user_input"
if [[ "$NON_INTERACTIVE" != "true" ]]; then
echo -n "Enter the desired installation directory [${install_prefix}]: "
read -r user_input
if [[ -n "$user_input" ]]; then
install_prefix="$user_input"
fi
fi
install_to_prefix "$install_prefix"
@@ -168,6 +191,18 @@ parse_arguments() {
die "Error: --install-dir requires a non-empty option argument."
fi
;;
--retain)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
RETAIN_BACKUPS="$2"
shift 2
else
die "Error: --retain requires a non-negative integer argument."
fi
;;
--yes|-y)
NON_INTERACTIVE="true"
shift
;;
--help|-h)
usage
;;
@@ -178,43 +213,23 @@ parse_arguments() {
done
}
# Function to download Firestorm Viewer (optional enhancement)
download_firestorm() {
# Define Firestorm Viewer download URL
# NOTE: Replace this URL with the actual download link as needed
FIRESTORM_URL="https://firestormviewer.org/download/linux/firestorm.tar.gz"
# Temporary download location
TEMP_DOWNLOAD="/tmp/firestorm.tar.gz"
echo "Downloading Firestorm Viewer from $FIRESTORM_URL..."
wget -O "$TEMP_DOWNLOAD" "$FIRESTORM_URL" || die "Failed to download Firestorm Viewer!"
echo "Download completed."
# Extract the tarball to the run path
tar -xzf "$TEMP_DOWNLOAD" -C "$tarball_path" --strip-components=1 || die "Failed to extract Firestorm Viewer tarball!"
echo "Extraction completed."
# Clean up the downloaded tarball
rm "$TEMP_DOWNLOAD"
}
# Main installation workflow
main() {
echo "Starting Firestorm Viewer installation script..."
RETAIN_BACKUPS="${DEFAULT_RETAIN_BACKUPS}"
NON_INTERACTIVE="false"
# Parse command-line arguments
parse_arguments "$@"
echo "Installation Options:"
echo " Suffix: ${SUFFIX:-None}"
echo " Custom Install Directory: ${CUSTOM_INSTALL_DIR:-None}"
echo " Retained Backups: ${RETAIN_BACKUPS}"
echo " Non-interactive: ${NON_INTERACTIVE}"
echo
# Optionally, download Firestorm Viewer if not already present
# Uncomment the following line if you want the script to handle downloading
# download_firestorm
# Determine if the script is run as root
if [[ "$EUID" -eq 0 ]]; then
root_install
+3
View File
@@ -3562,6 +3562,9 @@ bool LLAppViewer::initConfiguration()
// </FS:Ansariel>
{
// successfully handed off URL to existing instance, exit
// <FS:PP> FIRE-36191: Mark this helper process for clean shutdown so platform startup code can exit without treating this path as a launch failure
forceQuit();
// </FS:PP>
return false;
}
}
+6
View File
@@ -133,6 +133,12 @@ int main( int argc, char **argv )
bool ok = viewer_app_ptr->init();
if(!ok)
{
// <FS:PP> FIRE-36191: A secondary helper process can intentionally quit after handing an external SLURL to the already-running viewer instance
if (LLApp::isQuitting())
{
return 0;
}
// </FS:PP>
LL_WARNS() << "Application init failed." << LL_ENDL;
return -1;
}
+7 -1
View File
@@ -90,7 +90,13 @@ bool initViewer()
bool ok = gViewerAppPtr->init();
if(!ok)
{
LL_WARNS("InitOSX") << "Application init failed." << LL_ENDL;
// <FS:PP> FIRE-36191: A secondary helper process can intentionally quit after handing an external SLURL to the already-running viewer instance
// LL_WARNS("InitOSX") << "Application init failed." << LL_ENDL;
if (!LLApp::isQuitting())
{
LL_WARNS("InitOSX") << "Application init failed." << LL_ENDL;
}
// </FS:PP>
}
else if (!gHandleSLURL.empty())
{
+6
View File
@@ -544,6 +544,12 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,
bool ok = viewer_app_ptr->init();
if (!ok)
{
// <FS:PP> FIRE-36191: A secondary helper process can intentionally quit after handing an external SLURL to the already-running viewer instance
if (LLApp::isQuitting())
{
return 0;
}
// </FS:PP>
LL_WARNS() << "Application init failed." << LL_ENDL;
return -1;
}
+32 -4
View File
@@ -32,6 +32,22 @@
const char* LLAutoReplace::SETTINGS_FILE_NAME = "autoreplace.xml";
// <FS:PP> FIRE-36304 Allow some punctuation in autoreplace keywords
namespace
{
bool isAutoReplaceKeywordChar(llwchar character)
{
return LLWStringUtil::isPartOfWord(character)
|| character == L'('
|| character == L')'
|| character == L'.'
|| character == L','
|| character == L'-'
|| character == L'_';
}
}
// </FS:PP>
void LLAutoReplace::autoreplaceCallback(S32& replacement_start, S32& replacement_length, LLWString& replacement_string, S32& cursor_pos, const LLWString& input_text)
{
// make sure these returned values are cleared in case there is no replacement
@@ -46,7 +62,10 @@ void LLAutoReplace::autoreplaceCallback(S32& replacement_start, S32& replacement
if (word_end < 0 ) { word_end = 0; }; // <FS:Beq/> FIRE-34765
bool at_space = (input_text[word_end] == ' ');
bool have_word = (LLWStringUtil::isPartOfWord(input_text[word_end]));
// <FS:PP> FIRE-36304 Allow some punctuation in autoreplace keywords
// bool have_word = (LLWStringUtil::isPartOfWord(input_text[word_end]));
bool have_word = isAutoReplaceKeywordChar(input_text[word_end]);
// </FS:PP>
if (at_space || have_word)
{
@@ -54,7 +73,10 @@ void LLAutoReplace::autoreplaceCallback(S32& replacement_start, S32& replacement
{
// find out if this space immediately follows a word
word_end--;
have_word = (LLWStringUtil::isPartOfWord(input_text[word_end]));
// <FS:PP> FIRE-36304 Allow some punctuation in autoreplace keywords
// have_word = (LLWStringUtil::isPartOfWord(input_text[word_end]));
have_word = isAutoReplaceKeywordChar(input_text[word_end]);
// </FS:PP>
}
if (have_word)
{
@@ -62,7 +84,10 @@ void LLAutoReplace::autoreplaceCallback(S32& replacement_start, S32& replacement
std::string word;
S32 word_start = word_end;
for (S32 back_one = word_start - 1;
back_one >= 0 && LLWStringUtil::isPartOfWord(input_text[back_one]);
// <FS:PP> FIRE-36304 Allow some punctuation in autoreplace keywords
// back_one >= 0 && LLWStringUtil::isPartOfWord(input_text[back_one]);
back_one >= 0 && isAutoReplaceKeywordChar(input_text[back_one]);
// </FS:PP>
back_one--
)
{
@@ -717,7 +742,10 @@ bool LLAutoReplaceSettings::addEntryToList(LLWString keyword, LLWString replacem
bool isOneWord = true;
for (S32 character = 0; isOneWord && character < keyword.size(); character++ )
{
if ( ! LLWStringUtil::isPartOfWord(keyword[character]) )
// <FS:PP> FIRE-36304 Allow some punctuation in autoreplace keywords
// if ( ! LLWStringUtil::isPartOfWord(keyword[character]) )
if ( ! isAutoReplaceKeywordChar(keyword[character]) )
// </FS:PP>
{
LL_WARNS("AutoReplace") << "keyword '" << wstring_to_utf8str(keyword) << "' not a single word (len "<<keyword.size()<<" '"<<character<<"')" << LL_ENDL;
isOneWord = false;
+25
View File
@@ -1634,6 +1634,31 @@ void LLAvatarActions::addToContactSet(const uuid_vec_t& agent_ids)
LLFloaterReg::showInstance("fs_add_contact", data, true);
}
}
void LLAvatarActions::moveToContactSet(const uuid_vec_t& agent_ids, const std::string& source_set)
{
if (agent_ids.empty() || source_set.empty())
{
return;
}
LLSD data;
data["operation"] = "move";
data["source_set"] = source_set;
if (agent_ids.size() == 1)
{
data["id"] = agent_ids.front();
}
else
{
for (const LLUUID& id : agent_ids)
{
data["ids"].append(id);
}
}
LLFloaterReg::showInstance("fs_add_contact", data, true);
}
// [/FS:CR] Add to contact set
//== private methods ========================================================================================
+1
View File
@@ -223,6 +223,7 @@ public:
*/
static void addToContactSet(const LLUUID& agent_id);
static void addToContactSet(const uuid_vec_t& agent_ids);
static void moveToContactSet(const uuid_vec_t& agent_ids, const std::string& source_set);
/**
* Checks whether we can offer a teleport to the avatar, only offline friends
+86 -4
View File
@@ -41,12 +41,14 @@
#include "llavatarnamecache.h"
#include "llcallingcard.h" // for LLAvatarTracker
#include "llcachename.h"
#include "llinventory.h" // <FS:PP> FIRE-31146 Contact Sets - drag-and-drop support
#include "lllistcontextmenu.h"
#include "llrecentpeople.h"
#include "lluuid.h"
#include "llvoiceclient.h"
#include "llviewercontrol.h" // for gSavedSettings
#include "lltooldraganddrop.h"
#include "lggcontactsets.h" // <FS:PP> FIRE-36478: Ignore alias quotation marks when comparing names from contact sets
// [RLVa:KB] - Checked: 2010-06-04 (RLVa-1.2.2a)
#include "rlvhandler.h"
#include "rlvactions.h"
@@ -193,6 +195,8 @@ LLAvatarList::LLAvatarList(const Params& p)
, mShowVoiceVolume(p.show_voice_volume)
, mShowUsername(gSavedSettings.getBOOL("NameTagShowUsernames"))
, mShowDisplayName(gSavedSettings.getBOOL("UseDisplayNames"))
, mUseContactSetColors(false) // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
, mUseContactSetListStyle(false) // <FS:PP> FIRE-31733: Make contact sets lists more readable
{
setCommitOnSelectionChange(true);
@@ -211,10 +215,26 @@ LLAvatarList::LLAvatarList(const Params& p)
// <FS:Ansariel> FIRE-1089: List needs to update also if we change the username setting
gSavedSettings.getControl("NameTagShowUsernames")->getSignal()->connect(boost::bind(&LLAvatarList::handleDisplayNamesOptionChanged, this));
// <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
gSavedSettings.getControl("FSContactSetsColorizeFriends")->getSignal()->connect(boost::bind(&LLAvatarList::refreshNames, this));
// <FS:Ansariel> Update voice volume slider on RLVa shownames restriction update
mRlvBehaviorCallbackConnection = gRlvHandler.setBehaviourCallback(boost::bind(&LLAvatarList::updateRlvRestrictions, this, _1, _2));
}
// <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
void LLAvatarList::setUseContactSetColors(bool use_colors)
{
mUseContactSetColors = use_colors;
mNeedUpdateNames = true;
}
void LLAvatarList::setUseContactSetListStyle(bool use_style)
{
mUseContactSetListStyle = use_style;
mNeedUpdateNames = true;
}
// </FS:PP>
void LLAvatarList::handleDisplayNamesOptionChanged()
{
@@ -494,6 +514,8 @@ void LLAvatarList::updateAvatarNames()
for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++)
{
LLAvatarListItem* item = static_cast<LLAvatarListItem*>(*it);
item->setUseContactSetColors(mUseContactSetColors); // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
item->setUseContactSetListStyle(mUseContactSetListStyle); // <FS:PP> FIRE-31733: Make contact sets lists more readable
item->setShowCompleteName(mShowCompleteName, mForceCompleteName);
item->updateAvatarName();
}
@@ -594,6 +616,8 @@ void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, bool is
item->setShowPermissions(mShowPermissions);
item->showUsername(mShowUsername);
item->showDisplayName(mShowDisplayName);
item->setUseContactSetColors(mUseContactSetColors); // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
item->setUseContactSetListStyle(mUseContactSetListStyle); // <FS:PP> FIRE-31733: Make contact sets lists more readable
item->setDoubleClickCallback(boost::bind(&LLAvatarList::onItemDoubleClicked, this, _1, _2, _3, _4));
item->setMouseDownCallback(boost::bind(&LLAvatarList::onItemClicked, this, _1, _2, _3, _4));
@@ -668,6 +692,46 @@ bool LLAvatarList::handleHover(S32 x, S32 y, MASK mask)
return handled;
}
// <FS:PP> FIRE-31146 Contact Sets - drag-and-drop support
bool LLAvatarList::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg)
{
if (!mAvatarDropCallback)
{
return LLFlatListViewEx::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg);
}
LLUUID avatar_id;
if (cargo_type == DAD_PERSON)
{
if (cargo_data)
{
avatar_id = *static_cast<LLUUID*>(cargo_data);
}
}
else if (cargo_type == DAD_CALLINGCARD)
{
if (LLInventoryItem* item = static_cast<LLInventoryItem*>(cargo_data); item)
{
avatar_id = item->getCreatorUUID();
}
}
else
{
*accept = ACCEPT_NO;
return true;
}
if (avatar_id.isNull())
{
*accept = ACCEPT_NO;
return true;
}
*accept = mAvatarDropCallback(avatar_id, drop) ? ACCEPT_YES_MULTI : ACCEPT_NO;
return true;
}
// </FS:PP>
void LLAvatarList::setVisible(bool visible)
{
if (!visible && mContextMenu )
@@ -763,14 +827,32 @@ bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2)
bool LLAvatarItemNameComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const
{
std::string name1 = avatar_item1->getAvatarName();
std::string name2 = avatar_item2->getAvatarName();
// <FS:PP> FIRE-36478: Ignore alias quotation marks when comparing names from contact sets
// std::string name1 = avatar_item1->getAvatarName();
// std::string name2 = avatar_item2->getAvatarName();
LLStringUtil::toUpper(name1);
LLStringUtil::toUpper(name2);
// LLStringUtil::toUpper(name1);
// LLStringUtil::toUpper(name2);
std::string name1 = getComparableName(avatar_item1);
std::string name2 = getComparableName(avatar_item2);
// </FS:PP>
return name1 < name2;
}
// <FS:PP> FIRE-36478: Ignore alias quotation marks when comparing names from contact sets
std::string LLAvatarItemComparator::getComparableName(const LLAvatarListItem* avatar_item)
{
std::string name = avatar_item->getAvatarName();
if (LGGContactSets::getInstance()->hasPseudonym(avatar_item->getAvatarId()) && !name.empty() && (name.front() == '\'' || name.front() == '"'))
{
name.erase(0, 1);
}
LLStringUtil::toUpper(name);
return name;
}
// </FS:PP>
bool LLAvatarItemAgentOnTopComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const
{
//keep agent on top, if first is agent,
+15
View File
@@ -27,6 +27,8 @@
#ifndef LL_LLAVATARLIST_H
#define LL_LLAVATARLIST_H
#include <functional> // <FS:PP> FIRE-31146 Contact Sets - drag-and-drop support
#include "llflatlistview.h"
#include "llavatarlistitem.h"
#include "rlvdefines.h"
@@ -48,6 +50,8 @@ class LLAvatarList : public LLFlatListViewEx
{
LOG_CLASS(LLAvatarList);
public:
using avatar_drop_cb_t = std::function<bool(const LLUUID&, bool)>; // <FS:PP> FIRE-31146 Contact Sets - drag-and-drop support
struct Params : public LLInitParam::Block<Params, LLFlatListViewEx::Params>
{
Optional<bool> ignore_online_status, // show all items as online
@@ -85,6 +89,8 @@ public:
void showDisplayName(bool visible);
void showUsername(bool visible);
void showVoiceVolume(bool visible);
void setUseContactSetColors(bool use_colors); // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
void setUseContactSetListStyle(bool use_style); // <FS:PP> FIRE-31733: Make contact sets lists more readable
// <FS:Ansariel> [FS Communication UI]
//void sortByName();
void sortByName(bool agent_on_top = false);
@@ -100,6 +106,11 @@ public:
/*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask);
/*virtual*/ bool handleHover(S32 x, S32 y, MASK mask);
// <FS:PP> FIRE-31146 Contact Sets - drag-and-drop support
bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg);
void setAvatarDropCallback(const avatar_drop_cb_t& cb) { mAvatarDropCallback = cb; }
// </FS:PP>
// Return true if filter has at least one match.
bool filterHasMatches();
@@ -161,6 +172,8 @@ private:
// [/RLVa:KB]
bool mShowDisplayName;
bool mShowUsername;
bool mUseContactSetColors; // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
bool mUseContactSetListStyle; // <FS:PP> FIRE-31733: Make contact sets lists more readable
LLTimer* mLITUpdateTimer; // last interaction time update timer
std::string mIconParamName;
@@ -173,6 +186,7 @@ private:
commit_signal_t mRefreshCompleteSignal;
mouse_signal_t mItemDoubleClickSignal;
mouse_signal_t mItemClickedSignal;
avatar_drop_cb_t mAvatarDropCallback; // <FS:PP> FIRE-31146 Contact Sets - drag-and-drop support
// <FS:Ansariel> Update voice volume slider on RLVa shownames restriction update
boost::signals2::connection mRlvBehaviorCallbackConnection;
@@ -192,6 +206,7 @@ public:
virtual bool compare(const LLPanel* item1, const LLPanel* item2) const;
protected:
static std::string getComparableName(const LLAvatarListItem* avatar_item); // <FS:PP> FIRE-36478: Ignore alias quotation marks when comparing names from contact sets
/**
* Returns true if avatar_item1 < avatar_item2, false otherwise
+37 -1
View File
@@ -52,6 +52,7 @@
#include "lfsimfeaturehandler.h" // <FS:CR> Opensim
#include "llavatarlist.h"
#include "lggcontactsets.h" // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
bool LLAvatarListItem::sStaticInitialized = false;
S32 LLAvatarListItem::sLeftPadding = 0;
@@ -299,6 +300,20 @@ void LLAvatarListItem::setAvatarName(const std::string& name)
setNameInternal(name, mHighlihtSubstring);
}
// <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
void LLAvatarListItem::setUseContactSetColors(bool use_colors)
{
mUseContactSetColors = use_colors;
setNameInternal(mAvatarName->getText(), mHighlihtSubstring);
}
void LLAvatarListItem::setUseContactSetListStyle(bool use_style)
{
mUseContactSetListStyle = use_style;
setNameInternal(mAvatarName->getText(), mHighlihtSubstring);
}
// </FS:PP>
void LLAvatarListItem::setAvatarToolTip(const std::string& tooltip)
{
mAvatarName->setToolTip(tooltip);
@@ -586,7 +601,28 @@ void LLAvatarListItem::setNameInternal(const std::string& name, const std::strin
//{
// LLTextUtil::textboxSetHighlightedVal(mAvatarName, mAvatarNameStyle, name, highlight);
//}
LLTextUtil::textboxSetHighlightedVal(mAvatarName, mAvatarNameStyle, name, highlight);
LLStyle::Params avatar_name_style = mAvatarNameStyle;
if (mUseContactSetListStyle)
{
const LLAvatarListItem::Params& params = LLUICtrlFactory::getDefaultParams<LLAvatarListItem>();
if (mOnlineStatus == E_ONLINE && params.group_moderator_style.isProvided() && params.group_moderator_style().font.isProvided())
{
avatar_name_style.font = params.group_moderator_style().font();
}
if (params.online_style.isProvided() && params.online_style().color.isProvided())
{
avatar_name_style.color = params.online_style().color();
}
}
if (mUseContactSetColors)
{
LLColor4 contact_set_color;
if (LGGContactSets::getInstance()->hasFriendColorThatShouldShow(mAvatarId, ContactSetType::FRIENDS, contact_set_color))
{
avatar_name_style.color = contact_set_color;
}
}
LLTextUtil::textboxSetHighlightedVal(mAvatarName, avatar_name_style, name, highlight);
// </FS:Ansariel>
}
+5
View File
@@ -41,6 +41,7 @@ class LLOutputMonitorCtrl;
class LLAvatarName;
class LLIconCtrl;
class LLUICtrl;
enum class ContactSetType; // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
class LLAvatarListItem : public LLPanel, public LLFriendObserver
{
@@ -117,6 +118,8 @@ public:
void showLastInteractionTime(bool show);
void setAvatarIconVisible(bool visible);
void setShowCompleteName(bool show, bool force = false) { mShowCompleteName = show; mForceCompleteName = force;};
void setUseContactSetColors(bool use_colors); // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
void setUseContactSetListStyle(bool use_style); // <FS:PP> FIRE-31733: Make contact sets lists more readable
// [RLVa:KB] - Checked: RLVa-1.2.0
void setRlvCheckShowNames(bool fRlvCheckShowNames);
void updateRlvRestrictions();
@@ -266,6 +269,8 @@ private:
bool mShowCompleteName;
bool mForceCompleteName;
bool mUseContactSetColors{ false }; // <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
bool mUseContactSetListStyle{ false }; // <FS:PP> FIRE-31733: Make contact sets lists more readable
std::string mGreyOutUsername;
void fetchAvatarName();
+4 -5
View File
@@ -655,7 +655,7 @@ bool LLRenderPass::uploadMatrixPalette(LLDrawInfo& params)
}
//static
bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo)
bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, const LLMeshSkinInfo* skinInfo) // <FS:Beq/> be defensive about UAF with skinInfo during LocalMesh
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
@@ -682,7 +682,7 @@ bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinI
// Returns true if rendering should proceed
//static
bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin)
bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, const LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin)// <FS:Beq/> be defensive about UAF with skinInfo during LocalMesh
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
@@ -719,7 +719,7 @@ bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinI
// Returns true if rendering should proceed
//static
bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, const LLGLSLShader*& lastAvatarShader, bool& skipLastSkin)
bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, const LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, const LLGLSLShader*& lastAvatarShader, bool& skipLastSkin)// <FS:Beq/> be defensive about UAF with skinInfo during LocalMesh
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR;
@@ -920,5 +920,4 @@ void LLRenderPass::pushUntexturedRiggedGLTFBatch(LLDrawInfo& params, const LLVOA
{
pushUntexturedGLTFBatch(params);
}
}
}
+8 -3
View File
@@ -387,9 +387,14 @@ public:
void pushUntexturedBatch(LLDrawInfo& params);
void pushBumpBatch(LLDrawInfo& params, bool texture, bool batch_textures = false);
static bool uploadMatrixPalette(LLDrawInfo& params);
static bool uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo);
static bool uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin);
static bool uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, const LLGLSLShader*& lastAvatarShader, bool& skipLastSkin);
// <FS:Beq> be defensive about UAF with skinInfo during LocalMesh
// static bool uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo);
// static bool uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin);
// static bool uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, const LLGLSLShader*& lastAvatarShader, bool& skipLastSkin);
// </FS:Beq>
static bool uploadMatrixPalette(LLVOAvatar* avatar, const LLMeshSkinInfo* skinInfo);
static bool uploadMatrixPalette(LLVOAvatar* avatar, const LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin);
static bool uploadMatrixPalette(LLVOAvatar* avatar, const LLMeshSkinInfo* skinInfo, const LLVOAvatar*& lastAvatar, U64& lastMeshId, const LLGLSLShader*& lastAvatarShader, bool& skipLastSkin);
virtual void renderGroup(LLSpatialGroup* group, U32 type, bool texture = true);
virtual void renderRiggedGroup(LLSpatialGroup* group, U32 type, bool texture = true);
};
+44 -15
View File
@@ -41,6 +41,7 @@
#include "lleventfilter.h"
#include "llsdutil.h" // <FS:ND/> for ll_pretty_print_sd
#include "llviewercontrol.h" // <FS:Beq/> Debug setting for FIRE-36454 etc.
#include "llviewernetwork.h"
namespace LLEventPolling
@@ -76,9 +77,9 @@ namespace Details
// We will wait RETRY_SECONDS + (errorCount * RETRY_SECONDS_INC) before retrying after an error.
// This means we attempt to recover relatively quickly but back off giving more time to recover
// until we finally give up after MAX_EVENT_POLL_HTTP_ERRORS attempts.
constexpr F32 EVENT_POLL_ERROR_RETRY_SECONDS = 15.f; // ~ half of a normal timeout.
constexpr F32 EVENT_POLL_ERROR_RETRY_SECONDS_INC = 5.f; // ~ half of a normal timeout.
constexpr S32 MAX_EVENT_POLL_HTTP_ERRORS = 10; // ~5 minutes, by the above rules.
constexpr F32 EVENT_POLL_ERROR_RETRY_SECONDS = 1.f;
constexpr F32 EVENT_POLL_ERROR_RETRY_SECONDS_INC = 3.f;
constexpr S32 MAX_EVENT_POLL_ERRORS = 15; // ~5 minutes, by the above rules.
constexpr F64 MIN_SECONDS_PASSED = 10.0; // Minimum time we expect the server to hold the request.
int LLEventPollImpl::sNextCounter = 1;
@@ -161,6 +162,7 @@ namespace Details
int errorCount = 0;
int counter = mCounter; // saved on the stack for logging.
LLTimer message_time;
static LLCachedControl<U32> event_poll_core_retries(gSavedSettings, "FSEventPollCoreRetries", 0U); // <FS:Beq/> FIRE-36454 TP Disconnects in 7.2.3
LL_DEBUGS("LLEventPollImpl") << " <" << counter << "> entering coroutine." << LL_ENDL;
@@ -169,7 +171,13 @@ namespace Details
// This is a loop with its own waitToRetry implementation,
// so disable retries.
LLCore::HttpOptions::ptr_t httpOpts = std::make_shared<LLCore::HttpOptions>();
httpOpts->setRetries(0);
// <FS:Beq> FIRE-36454 TP Disconnects in 7.2.3
// httpOpts->setRetries(0);
const U32 llcore_retries = event_poll_core_retries();
httpOpts->setRetries(llcore_retries);
LL_DEBUGS("LLEventPollImpl") << "<" << counter << "> Event poll llcore retries set to "
<< llcore_retries << " for " << mSenderIp << LL_ENDL;
// </FS:Beq>
// <FS:Ansariel> Restore pre-coro behavior (60s timeout, no retries)
#ifdef OPENSIM
if (LLGridManager::instance().isInOpenSim())
@@ -229,8 +237,14 @@ namespace Details
{
// Server is supposed to hold request for 20 to 30 seconds.
// If it didn't hold the request at least for 10s, treat as an error.
// <FS:Beq> FIRE-36454 TP Disconnects in 7.2.3
// LL_WARNS("LLEventPollImpl") << "Response arrived too early, status: " << status.toTerseString()
// << ", time passed: " << message_time.getElapsedSeconds() << LL_ENDL;
LL_WARNS("LLEventPollImpl") << "Response arrived too early, status: " << status.toTerseString()
<< ", time passed: " << message_time.getElapsedSeconds() << LL_ENDL;
<< ", time passed: " << message_time.getElapsedSeconds()
<< ", llcore retries: " << llcore_retries
<< ", sender: " << mSenderIp << LL_ENDL;
// </FS:Beq>
}
else
{
@@ -261,30 +275,45 @@ namespace Details
// some cases the server gets ahead of the viewer and will
// return a 404 error (Not Found) before the cancel event
// comes back in the queue
LL_WARNS("LLEventPollImpl") << "<" << counter << "> Canceling coroutine, status: " << status.toTerseString() << LL_ENDL;
// <FS:Beq> FIRE-36454 TP Disconnects in 7.2.3
// LL_WARNS("LLEventPollImpl") << "<" << counter << "> Canceling coroutine, status: " << status.toTerseString()
// << ", time passed: " << message_time.getElapsedSeconds() << LL_ENDL;
LL_WARNS("LLEventPollImpl") << "<" << counter << "> Canceling coroutine, status: " << status.toTerseString()
<< ", time passed: " << message_time.getElapsedSeconds()
<< ", llcore retries: " << llcore_retries
<< ", sender: " << mSenderIp << LL_ENDL;
// </FS:Beq>
break;
}
else if (!status.isHttpStatus())
{
/// Some LLCore or LIBCurl error was returned. This is unlikely to be recoverable
LL_WARNS("LLEventPollImpl") << "<" << counter << "> Critical error from poll request returned from libraries. Canceling coroutine." << LL_ENDL;
break;
// Some LLCore or LIBCurl error was returned.
LL_WARNS("LLEventPollImpl") << "<" << counter << "> Error from poll request returned from libraries. " << status.toTerseString() << LL_ENDL;
}
else
{
LL_WARNS("LLEventPollImpl") << "<" << counter << "> Error result from LLCoreHttpUtil::HttpCoroHandler. Code "
<< status.toTerseString() << ": '" << httpResults["message"] << "'" << LL_ENDL;
}
LL_WARNS("LLEventPollImpl") << "<" << counter << "> Error result from LLCoreHttpUtil::HttpCoroHandler. Code "
<< status.toTerseString() << ": '" << httpResults["message"] << "'" << LL_ENDL;
if (errorCount < MAX_EVENT_POLL_HTTP_ERRORS)
if (errorCount < MAX_EVENT_POLL_ERRORS)
{ // An unanticipated error has been received from our poll
// request. Calculate a timeout and wait for it to expire(sleep)
// before trying again. The sleep time is increased by 5 seconds
// before trying again. The sleep time is increased by 3 seconds
// for each consecutive error.
++errorCount;
F32 waitToRetry = EVENT_POLL_ERROR_RETRY_SECONDS
+ errorCount * EVENT_POLL_ERROR_RETRY_SECONDS_INC;
++errorCount;
LL_WARNS("LLEventPollImpl") << "<" << counter << "> Retrying in " << waitToRetry <<
" seconds, error count is now " << errorCount << LL_ENDL;
// <FS:Beq> FIRE-36454 TP Disconnects in 7.2.3
// " seconds, error count is now " << errorCount << LL_ENDL;
" seconds, error count is now " << errorCount
<< ", llcore retries: " << llcore_retries
<< ", sender: " << mSenderIp << LL_ENDL;
// </FS:Beq>
llcoro::suspendUntilTimeout(waitToRetry);
+3 -2
View File
@@ -40,6 +40,7 @@
#include "llviewertexture.h"
#include "lldrawable.h"
#include "lljoint.h"
#include "llmodel.h"// <FS:Beq/> be defensive about UAF with skinInfo during LocalMesh
class LLFacePool;
class LLVolume;
@@ -49,7 +50,7 @@ class LLVertexProgram;
class LLViewerTexture;
class LLGeometryManager;
class LLDrawInfo;
class LLMeshSkinInfo;
// class LLMeshSkinInfo;// <FS:Beq/> be defensive about UAF with skinInfo during LocalMesh
const F32 MIN_ALPHA_SIZE = 1024.f;
// <FS:minerjr>
@@ -272,7 +273,7 @@ public:
LLMatrix4* mNormalMapMatrix;
LLDrawInfo* mDrawInfo;
LLVOAvatar* mAvatar = nullptr;
LLMeshSkinInfo* mSkinInfo = nullptr;
LLConstPointer<LLMeshSkinInfo> mSkinInfo;// <FS:Beq/> be defensive about UAF with skinInfo during LocalMesh
// return mSkinInfo->mHash or 0 if mSkinInfo is null
U64 getSkinHash();
+158 -23
View File
@@ -47,6 +47,7 @@
#include "llavatarnamecache.h" // IDEVO
#include "llbutton.h"
#include "llcachename.h"
#include "llcombobox.h" // <FS:PP> FIRE-34809 Contact Sets support in avatar picker
#include "lllineeditor.h"
#include "llscrolllistctrl.h"
#include "llscrolllistitem.h"
@@ -62,6 +63,7 @@
#include "fsavatarsearchmenu.h"
#include "fsscrolllistctrl.h"
#include "lggcontactsets.h" // <FS:PP> FIRE-34809 Contact Sets support in avatar picker
#include "lltransientfloatermgr.h"
static const U32 AVATAR_PICKER_SEARCH_TIMEOUT = 180U;
@@ -118,6 +120,7 @@ LLFloaterAvatarPicker::LLFloaterAvatarPicker(const LLSD& key)
mNearMeListComplete(false),
mCloseOnSelect(false),
mExcludeAgentFromSearchResults(false),
mAllowMultipleSelection(false), // <FS:PP> FIRE-34809 Contact Sets support in avatar picker
mContextConeOpacity (0.f),
mContextConeInAlpha(CONTEXT_CONE_IN_ALPHA),
mContextConeOutAlpha(CONTEXT_CONE_OUT_ALPHA),
@@ -194,6 +197,11 @@ bool LLFloaterAvatarPicker::postBuild()
getChild<LLPanel>("SearchPanelUUID")->setDefaultBtn("FindUUID");
// </FS:Ansariel>
// <FS:PP> FIRE-34809 Contact Sets support in avatar picker
getChild<LLComboBox>("ContactSetSelector")->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onContactSetSelected, this));
populateContactSets();
// </FS:PP>
setAllowMultiple(false);
center();
@@ -333,38 +341,80 @@ void LLFloaterAvatarPicker::onBtnSelect()
if(mSelectionCallback)
{
std::string acvtive_panel_name;
LLScrollListCtrl* list = NULL;
// LLScrollListCtrl* list = NULL; - <FS:PP> FIRE-34809 Contact Sets support in avatar picker
LLPanel* active_panel = getChild<LLTabContainer>("ResidentChooserTabs")->getCurrentPanel();
if(active_panel)
{
acvtive_panel_name = active_panel->getName();
}
if(acvtive_panel_name == "SearchPanel")
// <FS:PP> FIRE-34809 Contact Sets support in avatar picker
uuid_vec_t avatar_ids;
std::vector<LLAvatarName> avatar_names;
if (acvtive_panel_name == "ContactSetsPanel")
{
list = getChild<LLScrollListCtrl>("SearchResults");
if (!mAllowMultipleSelection)
{
return;
}
LLComboBox* contact_sets_combo = getChild<LLComboBox>("ContactSetSelector");
if (contact_sets_combo)
{
const std::string set_name = contact_sets_combo->getSimple();
if (LGGContactSets::ContactSet* contact_set = LGGContactSets::instance().getContactSet(set_name); contact_set)
{
avatar_ids.reserve(contact_set->mFriends.size());
avatar_names.reserve(contact_set->mFriends.size());
for (const LLUUID& avatar_id : contact_set->mFriends)
{
if (avatar_id.isNull())
{
continue;
}
avatar_ids.push_back(avatar_id);
LLAvatarName av_name;
LLAvatarNameCache::get(avatar_id, &av_name);
avatar_names.push_back(av_name);
}
}
}
if (!avatar_ids.empty())
{
mSelectionCallback(avatar_ids, avatar_names);
}
}
else if(acvtive_panel_name == "NearMePanel")
else
{
list = getChild<LLScrollListCtrl>("NearMe");
}
else if (acvtive_panel_name == "FriendsPanel")
{
list = getChild<LLScrollListCtrl>("Friends");
}
// <FS:Ansariel> Search by UUID
else if (acvtive_panel_name == "SearchPanelUUID")
{
list = getChild<LLScrollListCtrl>("SearchResultsUUID");
}
// </FS:Ansariel>
LLScrollListCtrl* list = NULL;
// </FS:PP>
if(acvtive_panel_name == "SearchPanel")
{
list = getChild<LLScrollListCtrl>("SearchResults");
}
else if(acvtive_panel_name == "NearMePanel")
{
list = getChild<LLScrollListCtrl>("NearMe");
}
else if (acvtive_panel_name == "FriendsPanel")
{
list = getChild<LLScrollListCtrl>("Friends");
}
// <FS:Ansariel> Search by UUID
else if (acvtive_panel_name == "SearchPanelUUID")
{
list = getChild<LLScrollListCtrl>("SearchResultsUUID");
}
// </FS:Ansariel>
if(list)
{
uuid_vec_t avatar_ids;
std::vector<LLAvatarName> avatar_names;
getSelectedAvatarData(list, avatar_ids, avatar_names);
mSelectionCallback(avatar_ids, avatar_names);
}
if(list)
{
// <FS:PP> FIRE-34809 Contact Sets support in avatar picker
// uuid_vec_t avatar_ids;
// std::vector<LLAvatarName> avatar_names;
// </FS:PP>
getSelectedAvatarData(list, avatar_ids, avatar_names);
mSelectionCallback(avatar_ids, avatar_names);
}
} // <FS:PP> End bracket only; FIRE-34809 Contact Sets support in avatar picker
}
getChild<LLScrollListCtrl>("SearchResults")->deselectAllItems(true);
getChild<LLScrollListCtrl>("NearMe")->deselectAllItems(true);
@@ -516,6 +566,44 @@ void LLFloaterAvatarPicker::populateFriend()
// </FS:Ansariel>
}
// <FS:PP> FIRE-34809 Contact Sets support in avatar picker
void LLFloaterAvatarPicker::populateContactSets()
{
LLComboBox* contact_sets_combo = getChild<LLComboBox>("ContactSetSelector");
if (!contact_sets_combo)
{
return;
}
contact_sets_combo->clearRows();
const LGGContactSets::string_vec_t contact_sets = LGGContactSets::instance().getAllContactSets();
for (const std::string& set_name : contact_sets)
{
contact_sets_combo->add(set_name);
}
contact_sets_combo->sortByName();
const bool has_contact_sets = !contact_sets.empty();
contact_sets_combo->setEnabled(has_contact_sets);
if (!has_contact_sets)
{
onList();
return;
}
contact_sets_combo->selectFirstItem();
onContactSetSelected();
}
void LLFloaterAvatarPicker::onContactSetSelected()
{
onList();
}
// </FS:PP>
void LLFloaterAvatarPicker::drawFrustum()
{
static LLCachedControl<F32> max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f);
@@ -567,6 +655,25 @@ bool LLFloaterAvatarPicker::visibleItemsSelected() const
return getChild<LLScrollListCtrl>("SearchResultsUUID")->getFirstSelectedIndex() >= 0;
}
// </FS:Ansariel>
// <FS:PP> FIRE-34809 Contact Sets support in avatar picker
else if (active_panel == getChild<LLPanel>("ContactSetsPanel"))
{
if (!mAllowMultipleSelection)
{
return false;
}
LLComboBox* contact_sets_combo = getChild<LLComboBox>("ContactSetSelector");
if (!contact_sets_combo)
{
return false;
}
const std::string set_name = contact_sets_combo->getSimple();
if (LGGContactSets::ContactSet* contact_set = LGGContactSets::instance().getContactSet(set_name); contact_set)
{
return !contact_set->mFriends.empty();
}
}
// </FS:PP>
return false;
}
@@ -758,6 +865,24 @@ void LLFloaterAvatarPicker::setAllowMultiple(bool allow_multiple)
getChild<LLScrollListCtrl>("Friends")->setAllowMultipleSelection(allow_multiple);
// <FS:Ansariel> Search by UUID
getChild<LLScrollListCtrl>("SearchResultsUUID")->setAllowMultipleSelection(allow_multiple);
// <FS:PP> FIRE-34809 Contact Sets support in avatar picker
mAllowMultipleSelection = allow_multiple;
LLTabContainer* tabs = getChild<LLTabContainer>("ResidentChooserTabs");
LLPanel* contact_sets_panel = getChild<LLPanel>("ContactSetsPanel");
if (tabs && contact_sets_panel)
{
const S32 tab_index = tabs->getIndexForPanel(contact_sets_panel);
if (tab_index >= 0)
{
tabs->enableTabButton(tab_index, allow_multiple);
if (!allow_multiple && tabs->getCurrentPanel() == contact_sets_panel)
{
tabs->selectTabByName("FriendsPanel");
}
}
}
// </FS:PP>
}
LLScrollListCtrl* LLFloaterAvatarPicker::getActiveList()
@@ -1083,6 +1208,16 @@ bool LLFloaterAvatarPicker::isSelectBtnEnabled()
list = getChild<LLScrollListCtrl>("SearchResultsUUID");
}
// </FS:Ansariel>
// <FS:PP> FIRE-34809 Contact Sets support in avatar picker
else if (acvtive_panel_name == "ContactSetsPanel")
{
if (!mAllowMultipleSelection)
{
return false;
}
return mOkButtonValidateSignal.num_slots() ? mOkButtonValidateSignal(uuid_vec_t{}) : true;
}
// </FS:PP>
if(list)
{
+6
View File
@@ -97,6 +97,11 @@ private:
void populateFriend();
bool visibleItemsSelected() const; // Returns true if any items in the current tab are selected.
// <FS:PP> FIRE-34809 Contact Sets support in avatar picker
void populateContactSets();
void onContactSetSelected();
// </FS:PP>
static void findByIdCoro(std::string url, LLUUID query_id, LLUUID agent_id, std::string floater_key);
static void findByNameCoro(std::string url, LLUUID mQueryID, std::string mName);
void find();
@@ -112,6 +117,7 @@ private:
bool mNearMeListComplete;
bool mCloseOnSelect;
bool mExcludeAgentFromSearchResults;
bool mAllowMultipleSelection; // <FS:PP> FIRE-34809 Contact Sets support in avatar picker
LLHandle <LLView> mFrustumOrigin;
F32 mContextConeOpacity;
F32 mContextConeInAlpha;
+57 -1
View File
@@ -44,13 +44,18 @@
#include "llpanelplaces.h"
#include "llpanelprofile.h"
#include "lltabcontainer.h"
LLFloaterDirectory::LLFloaterDirectory(const std::string& name)
: LLFloater(name),
mPanelAvatarp(nullptr),
mPanelGroupp(nullptr),
mPanelPlacep(nullptr),
mPanelClassifiedp(nullptr),
mPanelEventp(nullptr)
mPanelEventp(nullptr),
// <FS:Ansariel> Add "open profile" button
mOpenProfileBtn(nullptr),
mDirectoryTabs(nullptr)
{
}
@@ -87,9 +92,45 @@ bool LLFloaterDirectory::postBuild()
mPanelClassifiedp->setBackgroundVisible(false);
mPanelEventp = findChild<LLPanelEventInfo>("panel_event_info");
// <FS:Ansariel> Add "open profile" button
mOpenProfileBtn = getChild<LLButton>("open_profile_btn");
mOpenProfileBtn->setCommitCallback([&](LLUICtrl*, const LLSD&) {
auto* currentDirBrowserPanel = dynamic_cast<LLPanelDirBrowser*>(mDirectoryTabs->getCurrentPanel());
if (currentDirBrowserPanel)
currentDirBrowserPanel->openProfile();
});
mDirectoryTabs = getChild<LLTabContainer>("Directory Tabs");
mDirectoryTabs->setCommitCallback([&](LLUICtrl*, const LLSD&) { updateProfileButtonVisibility(); });
// </FS:Ansariel>
return true;
}
// <FS:PP> FIRE-36483 Menu, navbar and toolbar button must open the same search window
void LLFloaterDirectory::onOpen(const LLSD& key)
{
LLFloater::onOpen(key);
if (!key.has("query"))
{
return;
}
LLPanelDirWeb* panel_dir_web = findChild<LLPanelDirWeb>("panel_dir_web");
if (!panel_dir_web || !mDirectoryTabs)
{
return;
}
const std::string category = key.has("category") ? key["category"].asString() : "standard";
const std::string query = key["query"].asString();
const std::string collection = key.has("collection") ? key["collection"].asString() : "";
mDirectoryTabs->selectTabByName("panel_dir_web");
panel_dir_web->navigateToSearchPage(category, query, collection);
}
// </FS:PP>
void LLFloaterDirectory::hideAllDetailPanels()
{
if (mPanelAvatarp) mPanelAvatarp->setVisible(false);
@@ -97,4 +138,19 @@ void LLFloaterDirectory::hideAllDetailPanels()
if (mPanelPlacep) mPanelPlacep->setVisible(false);
if (mPanelClassifiedp) mPanelClassifiedp->setVisible(false);
if (mPanelEventp) mPanelEventp->setVisible(false);
mOpenProfileBtn->setVisible(false); // <FS:Ansariel> Add "open profile" button
}
// <FS:Ansariel> Add "open profile" button
void LLFloaterDirectory::updateProfileButtonVisibility()
{
std::string selected_tab = mDirectoryTabs->getCurrentPanel()->getName();
if (selected_tab == "panel_dir_people")
mOpenProfileBtn->setVisible(mPanelAvatarp->getVisible());
else if (selected_tab == "panel_dir_groups")
mOpenProfileBtn->setVisible(mPanelGroupp->getVisible());
else
mOpenProfileBtn->setVisible(false);
}
// </FS:Ansariel>
+10
View File
@@ -54,6 +54,9 @@ class LLPanelGroup;
class LLPanelPlaces;
class LLPanelClassifiedInfo;
class LLButton;
class LLTabContainer;
// Floater to find people, places, things
class LLFloaterDirectory : public LLFloater
{
@@ -64,6 +67,9 @@ public:
void hideAllDetailPanels();
bool postBuild() override;
void onOpen(const LLSD& key) override; // <FS:PP> FIRE-36483 Menu, navbar and toolbar button must open the same search window
void updateProfileButtonVisibility(); // <FS:Ansariel> Add "open profile" button
public:
LLPanelProfileSecondLife* mPanelAvatarp;
@@ -72,6 +78,10 @@ public:
LLPanelPlaces* mPanelPlacep;
LLPanelClassifiedInfo* mPanelClassifiedp;
// <FS:Ansariel> Add "open profile" button
LLButton* mOpenProfileBtn;
LLTabContainer* mDirectoryTabs;
private:
static LLFloaterDirectory *sInstance;
};
+77 -11
View File
@@ -56,6 +56,14 @@ bool item_name_precedes( LLInventoryItem* a, LLInventoryItem* b )
return LLStringUtil::precedesDict( a->getName(), b->getName() );
}
// <FS:PP> FIRE-36169 Gestures enable/disable switch
static bool are_gestures_enabled()
{
static LLCachedControl<bool> gestures_enabled(gSavedPerAccountSettings, "FSGesturesEnabled", true);
return gestures_enabled;
}
// </FS:PP>
class LLFloaterGestureObserver : public LLGestureManagerObserver
{
public:
@@ -207,6 +215,7 @@ bool LLFloaterGesture::postBuild()
getChild<LLButton>("activate_btn")->setClickedCallback(boost::bind(&LLFloaterGesture::onActivateBtnClick, this));
getChild<LLUICtrl>("FSShowOnlyActiveGestures")->setCommitCallback(boost::bind(&LLFloaterGesture::refreshForActiveSort, this)); // <FS:PP> FIRE-5646: Option to show only active gestures
getChild<LLUICtrl>("FSGesturesEnabled")->setCommitCallback(boost::bind(&LLFloaterGesture::onGesturesEnabledChanged, this)); // <FS:PP> FIRE-36169 Gestures enable/disable switch
getChild<LLUICtrl>("new_gesture_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickNew, this));
getChild<LLButton>("del_btn")->setClickedCallback(boost::bind(&LLFloaterGesture::onDeleteSelected, this));
@@ -236,6 +245,7 @@ bool LLFloaterGesture::postBuild()
// Update button labels
onCommitList();
updateGesturesEnabledState(); // <FS:PP> FIRE-36169 Gestures enable/disable switch
return true;
}
@@ -465,6 +475,13 @@ void LLFloaterGesture::getSelectedIds(uuid_vec_t& ids)
bool LLFloaterGesture::isActionEnabled(const LLSD& command)
{
// <FS:PP> FIRE-36169 Gestures enable/disable switch
if (!are_gestures_enabled())
{
return false;
}
// </FS:PP>
// paste copy_uuid edit_gesture
std::string command_name = command.asString();
if("paste" == command_name)
@@ -507,6 +524,8 @@ bool LLFloaterGesture::isActionEnabled(const LLSD& command)
void LLFloaterGesture::onClickPlay()
{
if (!are_gestures_enabled()) return; // <FS:PP> FIRE-36169 Gestures enable/disable switch
const LLUUID& item_id = mGestureList->getCurrentID();
if(item_id.isNull()) return;
@@ -533,6 +552,8 @@ void LLFloaterGesture::onClickPlay()
void LLFloaterGesture::onClickNew()
{
if (!are_gestures_enabled()) return; // <FS:PP> FIRE-36169 Gestures enable/disable switch
LLPointer<LLInventoryCallback> cb = new GestureShowCallback();
create_inventory_item(gAgent.getID(),
gAgent.getSessionID(),
@@ -549,6 +570,8 @@ void LLFloaterGesture::onClickNew()
void LLFloaterGesture::onActivateBtnClick()
{
if (!are_gestures_enabled()) return; // <FS:PP> FIRE-36169 Gestures enable/disable switch
uuid_vec_t ids;
getSelectedIds(ids);
if(ids.empty())
@@ -626,6 +649,8 @@ void LLFloaterGesture::onGestureRename(const LLSD& notification, const LLSD& res
void LLFloaterGesture::onCopyPasteAction(const LLSD& command)
{
if (!are_gestures_enabled()) return; // <FS:PP> FIRE-36169 Gestures enable/disable switch
std::string command_name = command.asString();
// Since we select this command, the inventory items must have already arrived
if("copy_gesture" == command_name)
@@ -676,6 +701,8 @@ void LLFloaterGesture::onCopyPasteAction(const LLSD& command)
void LLFloaterGesture::onClickEdit()
{
if (!are_gestures_enabled()) return; // <FS:PP> FIRE-36169 Gestures enable/disable switch
const LLUUID& item_id = mGestureList->getCurrentID();
LLInventoryItem* item = gInventory.getItem(item_id);
@@ -703,10 +730,36 @@ void LLFloaterGesture::onCommitList()
getChildView("play_btn")->setVisible( true);
getChildView("stop_btn")->setVisible( false);
}
updateGesturesEnabledState(); // <FS:PP> FIRE-36169 Gestures enable/disable switch
}
// <FS:PP> FIRE-36169 Gestures enable/disable switch
void LLFloaterGesture::onGesturesEnabledChanged()
{
updateGesturesEnabledState();
}
void LLFloaterGesture::updateGesturesEnabledState()
{
const bool gestures_enabled = are_gestures_enabled();
mGestureList->setEnabled(gestures_enabled);
getChildView("edit_btn")->setEnabled(gestures_enabled);
getChildView("play_btn")->setEnabled(gestures_enabled);
getChildView("stop_btn")->setEnabled(gestures_enabled);
getChildView("new_gesture_btn")->setEnabled(gestures_enabled);
getChildView("del_btn")->setEnabled(gestures_enabled);
getChildView("activate_btn")->setEnabled(gestures_enabled);
getChildView("gear_btn")->setEnabled(gestures_enabled);
getChildView("FSShowOnlyActiveGestures")->setEnabled(gestures_enabled);
}
// </FS:PP>
void LLFloaterGesture::onDeleteSelected()
{
if (!are_gestures_enabled()) return; // <FS:PP> FIRE-36169 Gestures enable/disable switch
uuid_vec_t ids;
getSelectedIds(ids);
if(ids.empty())
@@ -771,15 +824,17 @@ void LLFloaterGesture::playGesture(LLUUID item_id)
// <FS:ND> Try to update an item that already exists. Return true on success, false if such an item does not exist yet.
#define COL_TRIGGER 0
#define COL_SHORTCUT 1
#define COL_KEY 2
#define COL_NAME 3
#define COL_ACTIVE 0
#define COL_NAME 1
#define COL_TRIGGER 2
#define COL_KEY 3
#define COL_SHORTCUT 4
#define UI_COL_NAME 0
#define UI_COL_TRIGGER 1
#define UI_COL_KEY 2
#define UI_COL_SHORTCUT 3
#define UI_COL_ACTIVE 0
#define UI_COL_NAME 1
#define UI_COL_TRIGGER 2
#define UI_COL_KEY 3
#define UI_COL_SHORTCUT 4
bool LLFloaterGesture::updateItem( LLUUID const &aItem, LLSD const &aData )
{
@@ -793,12 +848,15 @@ bool LLFloaterGesture::updateItem( LLUUID const &aItem, LLSD const &aData )
if( !pItem )
return false;
std::string sDummyShortcut = "---";
std::string sDummyKey = "~~~";
const std::string sDummyShortcut = "---";
const std::string sDummyKey = "~~~";
if( aData[ "columns" ][ COL_NAME ][ "value" ] != "" )
pItem->getColumn( UI_COL_NAME )->setValue( aData[ "columns" ][ COL_NAME ][ "value" ] );
if( aData[ "columns" ][ COL_TRIGGER ][ "value" ] != "" )
pItem->getColumn( UI_COL_TRIGGER )->setValue( aData[ "columns" ][ COL_TRIGGER ][ "value" ] );
if( aData[ "columns" ][ COL_SHORTCUT ][ "value" ] != sDummyShortcut )
pItem->getColumn( UI_COL_SHORTCUT )->setValue( aData[ "columns" ][ COL_SHORTCUT ][ "value" ] );
@@ -807,7 +865,15 @@ bool LLFloaterGesture::updateItem( LLUUID const &aItem, LLSD const &aData )
pItem->getColumn( UI_COL_NAME )->setValue( aData[ "columns" ][ COL_NAME ][ "value" ] );
LLFontGL::StyleFlags oStyle = LLGestureMgr::getInstance()->isGestureActive(aItem) ? LLFontGL::BOLD : LLFontGL::NORMAL;
bool is_gesture_active = LLGestureMgr::getInstance()->isGestureActive(aItem);
LLFontGL::StyleFlags oStyle = is_gesture_active ? LLFontGL::BOLD : LLFontGL::NORMAL;
pItem->getColumn(UI_COL_ACTIVE)->setValue(is_gesture_active ? "Activate_Checkmark" : "");
LLScrollListIcon* icon = dynamic_cast<LLScrollListIcon*>(pItem->getColumn(UI_COL_ACTIVE));
if (is_gesture_active && icon)
{
icon->setIconSize(10);
}
LLScrollListText *pTextItem = dynamic_cast< LLScrollListText* >( pItem->getColumn( UI_COL_NAME ) );
+5
View File
@@ -98,6 +98,11 @@ private:
void onDeleteSelected();
void onRenameSelected();
// <FS:PP> FIRE-36169 Gestures enable/disable switch
void onGesturesEnabledChanged();
void updateGesturesEnabledState();
// </FS:PP>
static void onGestureRename(const LLSD& notification, const LLSD& response);
LLUUID mSelectedID;
+39 -16
View File
@@ -54,6 +54,7 @@
#include "llviewertexturelist.h"
// PoundLife END
#include "llvovolume.h"
#include "llgltfmaterial.h"
// <FS:Ansariel> FIRE-22292: Configurable columns
#include "llmenugl.h"
@@ -648,32 +649,54 @@ void LLFloaterInspect::getObjectTextureMemory(LLViewerObject* object, U32& objec
for (U8 j = 0; j < te_count; j++)
{
LLViewerTexture* img = object->getTEImage(j);
if (img)
LLTextureEntry* te = object->getTE(j);
if (!te)
{
calculateTextureMemory(img, object_texture_list, object_texture_memory, object_vram_memory);
continue;
}
// materials per face
if (object->getTE(j)->getMaterialParams().notNull())
// If a PBR (GLTF) render material is present, count this instead of legacy textures.
if (LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial())
{
uuid = object->getTE(j)->getMaterialParams()->getNormalID();
if (uuid.notNull())
for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
{
LLViewerTexture* img = gTextureList.getImage(uuid);
if (img)
const LLUUID& tex_id = gltf_mat->mTextureId[i];
if (tex_id.notNull())
{
calculateTextureMemory(img, object_texture_list, object_texture_memory, object_vram_memory);
if (LLViewerTexture* img = gTextureList.getImage(tex_id))
{
calculateTextureMemory(img, object_texture_list, object_texture_memory, object_vram_memory);
}
}
}
uuid = object->getTE(j)->getMaterialParams()->getSpecularID();
if (uuid.notNull())
}
else
{
// Legacy diffuse per face
if (LLViewerTexture* img = object->getTEImage(j))
{
LLViewerTexture* img = gTextureList.getImage(uuid);
if (img)
calculateTextureMemory(img, object_texture_list, object_texture_memory, object_vram_memory);
}
// Legacy materials per face (normal/specular)
if (te->getMaterialParams().notNull())
{
uuid = te->getMaterialParams()->getNormalID();
if (uuid.notNull())
{
calculateTextureMemory(img, object_texture_list, object_texture_memory, object_vram_memory);
if (LLViewerTexture* img = gTextureList.getImage(uuid))
{
calculateTextureMemory(img, object_texture_list, object_texture_memory, object_vram_memory);
}
}
uuid = te->getMaterialParams()->getSpecularID();
if (uuid.notNull())
{
if (LLViewerTexture* img = gTextureList.getImage(uuid))
{
calculateTextureMemory(img, object_texture_list, object_texture_memory, object_vram_memory);
}
}
}
}
+17 -1
View File
@@ -101,6 +101,22 @@ void LLFloaterMarketplace::openMarketplaceURL(const std::string& url)
// static
bool LLFloaterMarketplace::isMarketplaceURL(const std::string& url)
{
auto trimURL = [](std::string_view url) -> std::string_view
{
if (url.starts_with("https://"))
url.remove_prefix(8);
else if (url.starts_with("http://"))
url.remove_prefix(7);
while (!url.empty() && url.back() == '/')
url.remove_suffix(1);
return url;
};
static LLCachedControl<std::string> marketplace_url(gSavedSettings, "MarketplaceURL", "https://marketplace.secondlife.com/");
return url.starts_with(marketplace_url());
std::string_view marketplace_url_trimmed = trimURL(marketplace_url());
std::string_view url_trimmed = trimURL(url);
return !marketplace_url_trimmed.empty() && url_trimmed.starts_with(marketplace_url_trimmed);
}
+9 -24
View File
@@ -26,7 +26,6 @@
*/
/*
* App-wide preferences. Note that these are not per-user,
* because we need to load many preferences before we have
* a login name.
@@ -38,7 +37,6 @@
#include "message.h"
#include "llfloaterautoreplacesettings.h"
#include "llviewertexturelist.h"
#include "llagent.h"
#include "llagentcamera.h"
#include "llcheckboxctrl.h"
@@ -58,8 +56,6 @@
#include "llfloatersidepanelcontainer.h"
// <FS:Ansariel> [FS communication UI]
//#include "llfloaterimsession.h"
#include "fsfloaterim.h"
#include "fsfloaternearbychat.h"
// </FS:Ansariel> [FS communication UI]
#include "llkeyboard.h"
#include "llmodaldialog.h"
@@ -136,17 +132,16 @@
#include "fsavatarrenderpersistence.h"
#include "fsdroptarget.h"
#include "fsfloaterimcontainer.h"
#include "fspanelpreferenceuisounds.h" // <FS:PP> UI Sounds
#include "growlmanager.h"
#include "lfsimfeaturehandler.h"
#include "llaudioengine.h" // <FS:Ansariel> Output device selection
#include "llavatarname.h" // <FS:CR> Deeper name cache stuffs
#include "llclipboard.h" // <FS:Zi> Support preferences search SLURLs
#include "lldiriterator.h" // <Kadah> for populating the fonts combo
#include "llline.h"
#include "llfiltereditor.h" // <FS:Zi> FIRE-19539 - Include the alert messages in Prefs>Notifications>Alerts in preference Search.
#include "lllocationhistory.h"
#include "llpanelblockedlist.h"
#include "llpanelmaininventory.h"
#include "llspellcheck.h"
#include "lltoolbarview.h"
#include "lltoolpie.h"
#include "llviewermenufile.h" // <FS:LO> FIRE-23606 Reveal path to external script editor in prefernces
@@ -166,9 +161,6 @@
#endif
// </FS:LO>
// <FS:Zi> FIRE-19539 - Include the alert messages in Prefs>Notifications>Alerts in preference Search.
#include "llfiltereditor.h"
#include "llviewershadermgr.h"
//<FS:HG> FIRE-6340, FIRE-6567 - Setting Bandwidth issues
//const F32 BANDWIDTH_UPDATER_TIMEOUT = 0.5f;
char const* const VISIBILITY_DEFAULT = "default";
@@ -970,7 +962,7 @@ LLFloaterPreference::~LLFloaterPreference()
// bool has_first_selected = (mDisabledPopups->getFirstSelected()!=NULL);
// mEnablePopupBtn->setEnabled(has_first_selected);
//
// has_first_selected = (mEnabledPopups.getFirstSelected()!=NULL);
// has_first_selected = (mEnabledPopups->getFirstSelected()!=NULL);
// mDisablePopupBtn->setEnabled(has_first_selected);
//
// LLFloater::draw();
@@ -3131,9 +3123,6 @@ void LLFloaterPreference::onLogChatHistorySaved()
// <FS:PP> Load UI Sounds tabs settings
void LLFloaterPreference::updateUISoundsControls()
{
getChild<LLComboBox>("PlayModeUISndNewIncomingIMSession")->setValue((int)gSavedSettings.getU32("PlayModeUISndNewIncomingIMSession")); // 0, 1, 2, 3. Shared with Chat > Notifications > "When receiving Instant Messages"
getChild<LLComboBox>("PlayModeUISndNewIncomingGroupIMSession")->setValue((int)gSavedSettings.getU32("PlayModeUISndNewIncomingGroupIMSession")); // 0, 1, 2, 3. Shared with Chat > Notifications > "When receiving Group Instant Messages"
getChild<LLComboBox>("PlayModeUISndNewIncomingConfIMSession")->setValue((int)gSavedSettings.getU32("PlayModeUISndNewIncomingConfIMSession")); // 0, 1, 2, 3. Shared with Chat > Notifications > "When receiving AdHoc Instant Messages"
#ifdef OPENSIM
// <FS:Beq> OpenSim has option to not attenuate nearby local voice by distance
auto earPosGroup = findChild<LLRadioGroup>("ear_location");
@@ -3145,14 +3134,14 @@ void LLFloaterPreference::updateUISoundsControls()
earPosGroup->setIndexEnabled(hearNearbyVoiceFullVolume, LLGridManager::instance().isInOpenSim());
}
// <FS:Beq>
getChild<LLTextBox>("textFSRestartOpenSim")->setVisible(true);
getChild<LLLineEditor>("UISndRestartOpenSim")->setVisible(true);
getChild<LLButton>("Prev_UISndRestartOpenSim")->setVisible(true);
getChild<LLButton>("Def_UISndRestartOpenSim")->setVisible(true);
getChild<LLCheckBoxCtrl>("PlayModeUISndRestartOpenSim")->setVisible(true);
#endif
getChild<LLComboBox>("UseLSLFlightAssist")->setValue((int)gSavedPerAccountSettings.getF32("UseLSLFlightAssist")); // Flight Assist combo box; Not sound-related, but better to place it here instead of creating whole new void
if (FSPanelPreferenceUISounds* ui_sounds_panel = findChild<FSPanelPreferenceUISounds>("UI Sounds tab"))
{
ui_sounds_panel->refreshList();
}
// FIRE-9856: Mute sound effects disable plays sound from collisions and plays sound from gestures checkbox not disable after restart/relog
bool mute_sound_effects = gSavedSettings.getBOOL("MuteSounds");
bool mute_all_sounds = gSavedSettings.getBOOL("MuteAudio");
@@ -4036,11 +4025,6 @@ void LLPanelPreferenceGraphics::onPresetsListChange()
//{
// instance->saveSettings(); //make cancel work correctly after changing the preset
//}
//else
//{
// std::string dummy;
// instance->saveGraphicsPreset(dummy);
//}
}
void LLPanelPreferenceGraphics::setPresetText()
@@ -4128,6 +4112,7 @@ bool LLPanelPreferenceGraphics::hasDirtyChilds()
view_stack.push_back(*iter);
}
}
return false;
}
+49
View File
@@ -50,6 +50,7 @@
#include "lldelayedgestureerror.h"
#include "llinventorymodel.h"
#include "llviewermessage.h"
#include "llviewercontrol.h" // <FS:PP> FIRE-36169 Gestures enable/disable switch
#include "llvoavatarself.h"
#include "llviewerstats.h"
// <FS:Ansariel> [FS Communication UI]
@@ -71,6 +72,14 @@ const F32 MAX_WAIT_ANIM_SECS = 60.f;
// This should be relatively long, but not too long. 10 minutes is enough
const F32 MAX_WAIT_KEY_SECS = 60.f * 10.f;
// <FS:PP> FIRE-36169 Gestures enable/disable switch
static bool are_gestures_enabled()
{
static LLCachedControl<bool> gestures_enabled(gSavedPerAccountSettings, "FSGesturesEnabled", true);
return gestures_enabled;
}
// </FS:PP>
// Lightweight constructor.
// init() does the heavy lifting.
LLGestureMgr::LLGestureMgr()
@@ -543,6 +552,7 @@ void LLGestureMgr::replaceGesture(const LLUUID& item_id, const LLUUID& new_asset
void LLGestureMgr::playGesture(LLMultiGesture* gesture, bool fromKeyPress)
{
if (!gesture) return;
if (!are_gestures_enabled()) return; // <FS:PP> FIRE-36169 Gestures enable/disable switch
// [RLVa:KB] - Checked: RLVa-2.0.0 | Handles: @sendgesture
if (!RlvActions::canPlayGestures())
@@ -627,6 +637,8 @@ void LLGestureMgr::playGesture(LLMultiGesture* gesture, bool fromKeyPress)
// Convenience function that looks up the item_id for you.
void LLGestureMgr::playGesture(const LLUUID& item_id)
{
if (!are_gestures_enabled()) return; // <FS:PP> FIRE-36169 Gestures enable/disable switch
const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id);
item_map_t::iterator it = mActive.find(base_item_id);
@@ -644,6 +656,17 @@ void LLGestureMgr::playGesture(const LLUUID& item_id)
// and (as a minor side effect) has multiple spaces in a row replaced by single spaces.
bool LLGestureMgr::triggerAndReviseString(const std::string &utf8str, std::string* revised_string)
{
// <FS:PP> FIRE-36169 Gestures enable/disable switch
if (!are_gestures_enabled())
{
if (revised_string)
{
*revised_string = utf8str;
}
return false;
}
// </FS:PP>
std::string tokenized = utf8str;
bool found_gestures = false;
@@ -737,6 +760,8 @@ bool LLGestureMgr::triggerAndReviseString(const std::string &utf8str, std::strin
bool LLGestureMgr::triggerGesture(KEY key, MASK mask)
{
if (!are_gestures_enabled()) return false; // <FS:PP> FIRE-36169 Gestures enable/disable switch
std::vector <LLMultiGesture *> matching;
item_map_t::iterator it;
@@ -772,6 +797,8 @@ bool LLGestureMgr::triggerGesture(KEY key, MASK mask)
bool LLGestureMgr::triggerGestureRelease(KEY key, MASK mask)
{
if (!are_gestures_enabled()) return false; // <FS:PP> FIRE-36169 Gestures enable/disable switch
std::vector <LLMultiGesture *> matching;
item_map_t::iterator it;
@@ -811,6 +838,21 @@ struct IsGesturePlaying
void LLGestureMgr::update()
{
// <FS:PP> FIRE-36169 Gestures enable/disable switch
if (!are_gestures_enabled())
{
if (!mPlaying.empty())
{
std::vector<LLMultiGesture*> playing_copy = mPlaying;
for (LLMultiGesture* gesture : playing_copy)
{
stopGesture(gesture);
}
}
return;
}
// </FS:PP>
S32 i;
for (i = 0; i < (S32)mPlaying.size(); ++i)
{
@@ -1487,6 +1529,13 @@ void LLGestureMgr::notifyObservers()
bool LLGestureMgr::matchPrefix(const std::string& in_str, std::string* out_str)
{
// <FS:PP> FIRE-36169 Gestures enable/disable switch
if (!are_gestures_enabled())
{
return false;
}
// </FS:PP>
auto in_len = in_str.length();
//return whole trigger, if received text equals to it
+65 -3
View File
@@ -325,7 +325,7 @@ void LLGroupList::refresh()
sort();
// <FS:PP> Group favorites / pinning
if (has_favorites && has_non_favorites && !have_filter)
if (mShowFavoritesSeparator && has_favorites && has_non_favorites && !have_filter)
{
addFavoritesSeparator();
}
@@ -347,14 +347,50 @@ void LLGroupList::refresh()
else
{
clear();
S32 regular_group_count = 0; // <FS:PP> FIRE-32401: Contact Sets on groups list in profile
for (group_map_t::iterator it = mGroups.begin(); it != mGroups.end(); ++it)
{
addNewItem(it->second, it->first, LLUUID::null, ADD_BOTTOM);
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
if (it->second.notNull())
{
++regular_group_count;
}
// </FS:PP>
}
// Sort the list.
sort();
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
if (!mSecondaryGroups.empty())
{
bool contact_set_separator_added = false;
for (const std::string& name : mSecondaryGroups)
{
if (name.empty())
{
continue;
}
if (!contact_set_separator_added && regular_group_count > 0)
{
LLGroupListSeparator* separator = new LLGroupListSeparator();
static const LLUUID SECONDARY_SEPARATOR_UUID("00000000-0000-0000-0000-000000000002");
addItem(separator, SECONDARY_SEPARATOR_UUID, ADD_BOTTOM, false);
contact_set_separator_added = true;
}
LLGroupListItem* item = addNewItem(LLUUID::null, name, LLUUID::null, ADD_BOTTOM);
if (item)
{
if (auto color_it = mSecondaryGroupColors.find(name); color_it != mSecondaryGroupColors.end())
{
item->setCustomTextColor(color_it->second);
}
}
}
}
// </FS:PP>
}
setDirty(false);
@@ -382,13 +418,31 @@ void LLGroupList::setGroups(const std::map< std::string,LLUUID> group_list)
setDirty(true);
}
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
void LLGroupList::setSecondaryGroups(const std::vector<std::string>& group_names, const std::map<std::string, LLColor4>& group_colors)
{
mSecondaryGroups = group_names;
mSecondaryGroupColors = group_colors;
setDirty(true);
}
std::string LLGroupList::getSelectedGroupName()
{
if (const LLGroupListItem* selected_item = dynamic_cast<const LLGroupListItem*>(getSelectedItem()))
{
return selected_item->getGroupName();
}
return LLStringUtil::null;
}
// </FS:PP>
//////////////////////////////////////////////////////////////////////////
// PRIVATE Section
//////////////////////////////////////////////////////////////////////////
// <FS:PP> Group favorites / pinning
// <FS:PP> Group favorites / pinning; FIRE-32401: Contact Sets on groups list in profile
// void LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos, bool visible_in_profile)
void LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos, bool visible_in_profile, bool is_favorite)
LLGroupListItem* LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos, bool visible_in_profile, bool is_favorite)
// </FS:PP>
{
LLGroupListItem* item = new LLGroupListItem(mForAgent, mShowIcons);
@@ -413,6 +467,7 @@ void LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LL
addItem(item, id, pos);
// setCommentVisible(false);
return item; // <FS:PP> FIRE-32401: Contact Sets on groups list in profile
}
// virtual
@@ -759,6 +814,13 @@ void LLGroupListItem::setVisibleInProfile(bool visible)
mGroupNameBox->setColor(LLUIColorTable::instance().getColor((visible ? "GroupVisibleInProfile" : "GroupHiddenInProfile"), LLColor4::red).get());
}
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
void LLGroupListItem::setCustomTextColor(const LLColor4& color)
{
mGroupNameBox->setColor(color);
}
// </FS:PP>
//////////////////////////////////////////////////////////////////////////
// Private Section
//////////////////////////////////////////////////////////////////////////
+19 -2
View File
@@ -34,10 +34,16 @@
#include "llpanel.h"
#include "llstyle.h"
#include "lltoggleablemenu.h"
#include "v4color.h" // <FS:PP> FIRE-32401: Contact Sets on groups list in profile
#include "llgroupmgr.h"
#include <boost/signals2.hpp> // <FS:PP> Group favorites / pinning
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
#include <map>
#include <vector>
class LLGroupListItem;
// </FS:PP>
/**
* Auto-updating list of agent groups.
@@ -70,7 +76,12 @@ public:
bool getIconsVisible() const { return mShowIcons; }
void setIconsVisible(bool show_icons) { mShowIcons = show_icons; }
void setShowNone(bool show_none) { mShowNone = show_none; }
void setShowFavoritesSeparator(bool show) { mShowFavoritesSeparator = show; setDirty(true); } // <FS:PP> Group favorites / pinning
void setGroups(const std::map< std::string,LLUUID> group_list);
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
void setSecondaryGroups(const std::vector<std::string>& group_names, const std::map<std::string, LLColor4>& group_colors = {});
std::string getSelectedGroupName();
// </FS:PP>
LLToggleableMenu* getContextMenu() const { return mContextMenuHandle.get(); }
@@ -79,9 +90,9 @@ public:
private:
void setDirty(bool val = true) { mDirty = val; }
void refresh();
// <FS:PP> Group favorites / pinning
// <FS:PP> Group favorites / pinning; FIRE-32401: Contact Sets on groups list in profile
// void addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos = ADD_BOTTOM, bool visible_in_profile = true);
void addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos = ADD_BOTTOM, bool visible_in_profile = true, bool is_favorite = false);
LLGroupListItem* addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos = ADD_BOTTOM, bool visible_in_profile = true, bool is_favorite = false);
void addFavoritesSeparator();
void onFavoritesChanged();
// </FS:PP>
@@ -99,8 +110,13 @@ private:
bool mForAgent;
bool mShowNone;
bool mShowFavoritesSeparator{ true }; // <FS:PP> Group favorites / pinning
typedef std::map< std::string,LLUUID> group_map_t;
group_map_t mGroups;
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
std::vector<std::string> mSecondaryGroups;
std::map<std::string, LLColor4> mSecondaryGroupColors;
// </FS:PP>
boost::signals2::connection mFavoritesChangedConnection; // <FS:PP> Group favorites / pinning
};
@@ -144,6 +160,7 @@ public:
void setGroupIconID(const LLUUID& group_icon_id);
void setGroupIconVisible(bool visible);
void setFavorite(bool favorite) { mIsFavorite = favorite; } // <FS:PP> Group favorites / pinning
void setCustomTextColor(const LLColor4& color); // <FS:PP> FIRE-32401: Contact Sets on groups list in profile
virtual void changed(LLGroupChange gc);
+55 -38
View File
@@ -74,6 +74,7 @@
#include "fscommon.h"
#include "fsdata.h"
#include "fskeywords.h"
#include "lggcontactsets.h" // <FS:PP> FIRE-17006 Autoresponse based on Contact Set
#include "llagentui.h"
#include "llavataractions.h"
#include "llgiveinventory.h"
@@ -159,6 +160,58 @@ static std::string clean_name_from_task_im(const std::string& msg,
return msg;
}
// <FS> FS autoresponse feature
std::string LLIMProcessing::getAutoresponseTextForAvatar(const LLUUID& from_id, bool is_do_not_disturb, bool is_autorespond, bool is_autorespond_nonfriends, bool is_afk, bool send_away_response, bool is_friend)
{
std::string response;
if (is_do_not_disturb)
{
std::string override_response;
if (LGGContactSets::getInstance()->getAutoresponseForFriend(from_id, ContactSetAutoresponseMode::BUSY, override_response))
{
response = override_response;
}
else
{
response = gSavedPerAccountSettings.getString("DoNotDisturbModeResponse");
}
}
else if (is_autorespond_nonfriends && !is_friend)
{
std::string override_response;
if (LGGContactSets::getInstance()->getAutoresponseForFriend(from_id, ContactSetAutoresponseMode::AUTORESPONSE_NONFRIENDS, override_response))
{
response = override_response;
}
else
{
response = gSavedPerAccountSettings.getString("FSAutorespondNonFriendsResponse");
}
}
else if (is_autorespond)
{
std::string override_response;
if (LGGContactSets::getInstance()->getAutoresponseForFriend(from_id, ContactSetAutoresponseMode::AUTORESPONSE, override_response))
{
response = override_response;
}
else
{
response = gSavedPerAccountSettings.getString("FSAutorespondModeResponse");
}
}
else if (is_afk && send_away_response)
{
response = gSavedPerAccountSettings.getString("FSAwayAvatarResponse");
}
else
{
LL_WARNS() << "Unknown auto-response mode" << LL_ENDL;
}
return response;
}
// </FS>
const std::string NOT_ONLINE_MSG("User not online - message will be stored and delivered later.");
const std::string NOT_ONLINE_INVENTORY("User not online - inventory has been saved.");
void translate_if_needed(std::string& message)
@@ -944,28 +997,7 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
// <FS:Ansariel> FS autoresponse feature
std::string my_name;
LLAgentUI::buildFullname(my_name);
if (is_do_not_disturb)
{
response = gSavedPerAccountSettings.getString("DoNotDisturbModeResponse");
}
else if (is_autorespond_nonfriends && !is_friend)
{
response = gSavedPerAccountSettings.getString("FSAutorespondNonFriendsResponse");
}
else if (is_autorespond)
{
response = gSavedPerAccountSettings.getString("FSAutorespondModeResponse");
}
// <FS:PP> FIRE-10500: Autoresponse for (Away)
else if (is_afk && FSSendAwayAvatarResponse)
{
response = gSavedPerAccountSettings.getString("FSAwayAvatarResponse");
}
// </FS:PP>
else
{
LL_WARNS() << "Unknown auto-response mode" << LL_ENDL;
}
response = LLIMProcessing::getAutoresponseTextForAvatar(from_id, is_do_not_disturb, is_autorespond, is_autorespond_nonfriends, is_afk, FSSendAwayAvatarResponse, is_friend);
pack_instant_message(
gMessageSystem,
gAgent.getID(),
@@ -1973,22 +2005,7 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
std::string my_name;
std::string response;
LLAgentUI::buildFullname(my_name);
if (is_do_not_disturb)
{
response = gSavedPerAccountSettings.getString("DoNotDisturbModeResponse");
}
else if (is_autorespond_nonfriends && !is_friend)
{
response = gSavedPerAccountSettings.getString("FSAutorespondNonFriendsResponse");
}
else if (is_autorespond)
{
response = gSavedPerAccountSettings.getString("FSAutorespondModeResponse");
}
else if (is_afk && FSSendAwayAvatarResponse)
{
response = gSavedPerAccountSettings.getString("FSAwayAvatarResponse");
}
response = LLIMProcessing::getAutoresponseTextForAvatar(from_id, is_do_not_disturb, is_autorespond, is_autorespond_nonfriends, is_afk, FSSendAwayAvatarResponse, is_friend);
if (!response.empty())
{
+3
View File
@@ -32,6 +32,9 @@
class LLIMProcessing
{
public:
// <FS> FS autoresponse feature
static std::string getAutoresponseTextForAvatar(const LLUUID& from_id, bool is_do_not_disturb, bool is_autorespond, bool is_autorespond_nonfriends, bool is_afk, bool send_away_response, bool is_friend);
// Pre-process message for IM manager
static void processNewMessage(LLUUID from_id,
bool from_group,
+11 -23
View File
@@ -60,6 +60,7 @@
#include "llmd5.h"
#include "llmutelist.h"
#include "llrecentpeople.h"
#include "llimprocessing.h" // <FS> FS autoresponse feature
#include "llviewermessage.h"
#include "llviewerwindow.h"
#include "llnotifications.h"
@@ -240,7 +241,10 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
bool store_dnd_message = false; // flag storage of a dnd message
bool is_session_focused = session_floater->isTornOff() && session_floater->hasFocus();
bool contains_mention = LLUrlRegistry::getInstance()->containsAgentMention(msg["message"].asString());
static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlaySoundChatMention", false);
// <FS:PP> UI Sounds
// static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlaySoundChatMention", false);
static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlayModeUISndChatMention", false);
// </FS:PP>
bool play_snd_mention = contains_mention && play_snd_mention_pref && (msg["source_type"].asInteger() != CHAT_SOURCE_OBJECT);
if (!LLFloater::isVisible(im_box) || im_box->isMinimized())
{
@@ -481,7 +485,10 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg)
im_container->addFlashingSession(session_id);
}
static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlaySoundChatMention", false);
// <FS:PP> UI Sounds
// static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlaySoundChatMention", false);
static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlayModeUISndChatMention", false);
// </FS:PP>
if (!gAgent.isDoNotDisturb() && play_snd_mention_pref && (msg["source_type"].asInteger() != CHAT_SOURCE_OBJECT) && LLUrlRegistry::getInstance()->containsAgentMention(msg["message"].asString()))
{
make_ui_sound("UISndChatMention");
@@ -3552,7 +3559,7 @@ void LLIMMgr::addMessage(
static LLCachedControl<U32> PlayModeUISndNewIncomingIMSession(gSavedSettings, "PlayModeUISndNewIncomingIMSession");
static LLCachedControl<U32> PlayModeUISndNewIncomingGroupIMSession(gSavedSettings, "PlayModeUISndNewIncomingGroupIMSession");
static LLCachedControl<U32> PlayModeUISndNewIncomingConfIMSession(gSavedSettings, "PlayModeUISndNewIncomingConfIMSession");
static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlaySoundChatMention", false);
static LLCachedControl<bool> play_snd_mention_pref(gSavedSettings, "PlayModeUISndChatMention", false);
bool play_snd_mention = play_snd_mention_pref && LLUrlRegistry::getInstance()->containsAgentMention(msg);
bool do_not_disturb = gAgent.isDoNotDisturb();
bool is_group_chat = false;
@@ -4754,26 +4761,7 @@ void typingNameCallback(const LLUUID& av_id, const LLAvatarName& av_name, const
std::string my_name;
std::string response;
LLAgentUI::buildFullname(my_name);
if (is_busy)
{
response = gSavedPerAccountSettings.getString("DoNotDisturbModeResponse");
}
else if (is_autorespond_nonfriends && !is_friend)
{
response = gSavedPerAccountSettings.getString("FSAutorespondNonFriendsResponse");
}
else if (is_autorespond)
{
response = gSavedPerAccountSettings.getString("FSAutorespondModeResponse");
}
else if (is_afk && FSSendAwayAvatarResponse)
{
response = gSavedPerAccountSettings.getString("FSAwayAvatarResponse");
}
else
{
LL_WARNS() << "Unknown auto-response mode" << LL_ENDL;
}
response = LLIMProcessing::getAutoresponseTextForAvatar(av_id, is_busy, is_autorespond, is_autorespond_nonfriends, is_afk, FSSendAwayAvatarResponse, is_friend);
pack_instant_message(
gMessageSystem,
gAgent.getID(),
+38 -7
View File
@@ -504,7 +504,10 @@ void LLInvFVBridge::removeBatch(std::vector<LLFolderViewModelItem*>& batch)
item = (LLViewerInventoryItem*)model->getItem(bridge->getUUID());
if (item)
{
if(LLAssetType::AT_GESTURE == item->getType())
// <FS:PP> FIRE-36458 Prevent gesture from being deactivated when its link is moved to trash
// if(LLAssetType::AT_GESTURE == item->getType())
if(LLAssetType::AT_GESTURE == item->getType() && !item->getIsLinkType())
// </FS:PP>
{
LLGestureMgr::instance().deactivateGesture(item->getUUID());
}
@@ -520,7 +523,10 @@ void LLInvFVBridge::removeBatch(std::vector<LLFolderViewModelItem*>& batch)
gInventory.collectDescendents( cat->getUUID(), descendent_categories, descendent_items, false );
for (j=0; j<descendent_items.size(); j++)
{
if(LLAssetType::AT_GESTURE == descendent_items[j]->getType())
// <FS:PP> FIRE-36458 Prevent gesture from being deactivated when its link is moved to trash
// if(LLAssetType::AT_GESTURE == descendent_items[j]->getType())
if(LLAssetType::AT_GESTURE == descendent_items[j]->getType() && !descendent_items[j]->getIsLinkType())
// </FS:PP>
{
LLGestureMgr::instance().deactivateGesture(descendent_items[j]->getUUID());
}
@@ -3301,6 +3307,7 @@ bool LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat,
{
LLInventoryItem* item = descendent_items[i];
if (item->getType() == LLAssetType::AT_GESTURE
&& !item->getIsLinkType() // <FS:PP> FIRE-36458 Prevent gesture from being deactivated when its link is moved to trash
&& LLGestureMgr::instance().isGestureActive(item->getUUID()))
{
LLGestureMgr::instance().deactivateGesture(item->getUUID());
@@ -4894,6 +4901,8 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items
const LLUUID& marketplace_listings_id = model->getMarketplaceListingsUUID();
const LLUUID &outfits_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
const bool is_mp_listings_folder = isMarketplaceListingsFolder(); // <FS:PP> FIRE-36377 Do not show duplicated "new folder" in MP listings window context menu
// <FS:Ansariel> FIRE-11628: Option to delete broken links from AO folder
if (mUUID == AOEngine::instance().getAOFolder())
{
@@ -4939,7 +4948,10 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items
{
disabled_items.push_back(std::string("New Folder"));
}
if (isMarketplaceListingsFolder())
// <FS:PP> FIRE-36377 Do not show duplicated "new folder" in MP listings window context menu
// if (isMarketplaceListingsFolder())
if (is_mp_listings_folder)
// </FS:PP>
{
addMarketplaceContextMenuOptions(flags, items, disabled_items);
if (LLMarketplaceData::instance().isUpdating(mUUID))
@@ -5077,10 +5089,20 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items
// Do not allow to create 2-level subfolder in the Calling Card/Friends folder. EXT-694.
if (!LLFriendCardsManager::instance().isCategoryInFriendFolder(cat))
{
items.push_back(std::string("New Folder"));
menu_items_added = true;
// <FS:PP> FIRE-36377 Do not show duplicated "new folder" in MP listings window context menu
// items.push_back(std::string("New Folder"));
// menu_items_added = true;
if (!is_mp_listings_folder)
{
items.push_back(std::string("New Folder"));
menu_items_added = true;
}
// </FS:PP>
}
if (!isMarketplaceListingsFolder())
// <FS:PP> FIRE-36377 Do not show duplicated "new folder" in MP listings window context menu
// if (!isMarketplaceListingsFolder())
if (!is_mp_listings_folder)
// </FS:PP>
{
items.push_back(std::string("upload_options"));
items.push_back(std::string("upload_def"));
@@ -6435,6 +6457,7 @@ bool LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item,
if (accept && drop)
{
if (inv_item->getType() == LLAssetType::AT_GESTURE
&& !inv_item->getIsLinkType() // <FS:PP> FIRE-36458 Prevent gesture from being deactivated when its link is moved to trash
&& LLGestureMgr::instance().isGestureActive(inv_item->getUUID()) && move_is_into_trash)
{
LLGestureMgr::instance().deactivateGesture(inv_item->getUUID());
@@ -7671,7 +7694,15 @@ bool LLGestureBridge::removeItem()
// This will also force close the preview window, if it exists.
// This may actually delete *this, if mUUID is in the COF.
LLGestureMgr::instance().deactivateGesture(item_id);
// <FS:PP> FIRE-36458 Prevent gesture from being deactivated when its link is moved to trash
// LLGestureMgr::instance().deactivateGesture(item_id);
LLViewerInventoryItem* item = getItem();
if (item && !item->getIsLinkType())
{
LLGestureMgr::instance().deactivateGesture(item_id);
}
// </FS:PP>
// If deactivateGesture deleted *this, then return out immediately.
if (!model->getObject(item_id))
+2
View File
@@ -3444,6 +3444,7 @@ bool dragItemIntoFolder(LLUUID folder_id, LLInventoryItem* inv_item, bool drop,
if (accept && drop)
{
if (inv_item->getType() == LLAssetType::AT_GESTURE
&& !inv_item->getIsLinkType() // <FS:PP> FIRE-36458 Prevent gesture from being deactivated when its link is moved to trash
&& LLGestureMgr::instance().isGestureActive(inv_item->getUUID()) && move_is_into_trash)
{
LLGestureMgr::instance().deactivateGesture(inv_item->getUUID());
@@ -3980,6 +3981,7 @@ bool dragCategoryIntoFolder(LLUUID dest_id, LLInventoryCategory* inv_cat,
{
LLInventoryItem* item = descendent_items[i];
if (item->getType() == LLAssetType::AT_GESTURE
&& !item->getIsLinkType() // <FS:PP> FIRE-36458 Prevent gesture from being deactivated when its link is moved to trash
&& LLGestureMgr::instance().isGestureActive(item->getUUID()))
{
LLGestureMgr::instance().deactivateGesture(item->getUUID());
+1
View File
@@ -4481,6 +4481,7 @@ void LLInventoryModel::removeCategory(const LLUUID& category_id)
const LLViewerInventoryItem* item = (*iter);
const LLUUID& item_id = item->getUUID();
if (item->getType() == LLAssetType::AT_GESTURE
&& !item->getIsLinkType() // <FS:PP> FIRE-36458 Prevent gesture from being deactivated when its link is moved to trash
&& LLGestureMgr::instance().isGestureActive(item_id))
{
LLGestureMgr::instance().deactivateGesture(item_id);
+5
View File
@@ -553,6 +553,11 @@ void LLLandmarkActions::copySLURLtoClipboard(const LLUUID& landmarkInventoryItem
void copy_slurl_to_clipboard_callback(const std::string& slurl)
{
if (slurl.empty())
{
LLNotificationsUtil::add("LandmarkLocationUnknown");
return;
}
gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl));
LLSD args;
args["SLURL"] = slurl;
+4 -1
View File
@@ -876,7 +876,10 @@ void LLNavigationBar::invokeSearch(std::string search_text)
collections.append("destinations");
collections.append("places");
key["collections"] = collections;
LLFloaterReg::showInstance("search", key);
// <FS:PP> FIRE-36483 Menu, navbar and toolbar button must open the same search window
//LLFloaterReg::showInstance("search", key);
LLFloaterReg::showInstance(gSavedSettings.getBOOL("FSUseFSLegacySearch") ? "search" : "legacy_search", key);
// </FS:PP>
}
void LLNavigationBar::clearHistoryCache()
+52 -4
View File
@@ -95,6 +95,15 @@ bool LLPanelDirBrowser::postBuild()
mNextPageBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { nextPage(); });
mNextPageBtn->setVisible(false);
// <FS:Ansariel> Port over search term history
mSearchComboBox = findChild<LLSearchComboBox>("name");
if (mSearchComboBox)
{
mSearchComboBox->setCommitCallback(boost::bind(&LLPanelDirBrowser::onClickSearchCore, this));
fillSearchComboBox();
}
// </FS:Ansariel>
return true;
}
@@ -176,11 +185,19 @@ void LLPanelDirBrowser::updateResultCount()
{
// Item count be off by a few if bogus items sent from database
// Just use the number of results per page. JC
result_text = llformat(">%d found", mResultsPerPage);
// <FS:Ansariel> Make this localizable
//result_text = llformat(">%d found", mResultsPerPage);
LLStringUtil::format_map_t args;
args["COUNT"] = llformat(">%d", mResultsPerPage);
result_text = getString("found_text", args);
}
else
{
result_text = llformat("%d found", result_count);
// <FS:Ansariel> Make this localizable
//result_text = llformat("%d found", result_count);
LLStringUtil::format_map_t args;
args["COUNT"] = llformat("%d", mResultsPerPage);
result_text = getString("found_text", args);
}
childSetValue("result_text", result_text);
@@ -190,7 +207,9 @@ void LLPanelDirBrowser::updateResultCount()
// add none found response
if (list->getItemCount() == 0)
{
list->setCommentText(std::string("None found.")); // *TODO: Translate
// <FS:Ansariel> Make this localizable
//list->setCommentText(std::string("None found.")); // *TODO: Translate
list->setCommentText(getString("not_found_text"));
list->operateOnAll(LLCtrlListInterface::OP_DESELECT);
}
}
@@ -372,6 +391,10 @@ void LLPanelDirBrowser::showDetailPanel(S32 type, LLSD id)
}
break;
}
// <FS:Ansariel> Add "open profile" button
mSelectedID = id;
mFloaterDirectory->updateProfileButtonVisibility();
}
@@ -1064,7 +1087,9 @@ void LLPanelDirBrowser::setupNewSearch()
// ready the list for results
list->operateOnAll(LLCtrlListInterface::OP_DELETE);
list->setCommentText(LLTrans::getString("Searching"));
// <FS:Ansariel> Make this localizable
//list->setCommentText(LLTrans::getString("Searching"));
list->setCommentText(getString("searching_text"));
list->setEnabled(false);
mResultsReceived = 0;
@@ -1084,6 +1109,13 @@ void LLPanelDirBrowser::onClickSearchCore(void* userdata)
LLPanelDirBrowser* self = (LLPanelDirBrowser*)userdata;
if (!self) return;
// <FS:Ansariel> Port over search term history
if (self->mSearchComboBox && !self->mSearchComboBox->getValue().asString().empty())
{
LLSearchHistory::getInstance()->addEntry(self->mSearchComboBox->getValue().asString());
}
// </FS:Ansariel>
self->resetSearchStart();
self->performQuery();
}
@@ -1157,3 +1189,19 @@ S32 LLPanelDirBrowser::showNextButton(S32 rows)
}
return rows;
}
// <FS:Ansariel> Port over search term history
void LLPanelDirBrowser::fillSearchComboBox()
{
if (!mSearchComboBox)
{
return;
}
LLSearchHistory::getInstance()->load();
for (const auto& item : LLSearchHistory::getInstance()->getSearchHistoryList())
{
mSearchComboBox->add(item.search_query);
}
}
// </FS:Ansariel>
+9
View File
@@ -30,6 +30,7 @@
#include "llpanel.h"
#include "llframetimer.h"
#include "llsearchcombobox.h"
class LLMessageSystem;
class LLFloaterDirectory;
@@ -101,6 +102,8 @@ public:
std::string filterShortWords( const std::string source_string, int shortest_word_length, bool& was_filtered );
virtual void openProfile() {}; // <FS:Ansariel> Add "open profile" button
protected:
void updateResultCount();
@@ -114,6 +117,9 @@ protected:
// rows to display.
S32 showNextButton(S32 rows);
// <FS:Ansariel> Port over search term history
void fillSearchComboBox();
protected:
LLUUID mSearchID; // Unique ID for a pending search
LLUUID mWantSelectID; // scroll item to select on arrival
@@ -137,6 +143,9 @@ protected:
LLFloaterDirectory* mFloaterDirectory;
LLButton* mPrevPageBtn;
LLButton* mNextPageBtn;
LLUUID mSelectedID; // <FS:Ansariel> Add "open profile" button
LLSearchComboBox* mSearchComboBox{ nullptr }; // <FS:Ansariel> Port over search term history
};
constexpr S32 RESULTS_PER_PAGE_DEFAULT = 100;
+4 -2
View File
@@ -61,8 +61,10 @@ bool LLPanelDirClassified::postBuild()
{
LLPanelDirBrowser::postBuild();
childSetAction("Search", onClickSearchCore, this);
setDefaultBtn("Search");
// <FS:Ansariel> Port over search term history
//childSetAction("Search", onClickSearchCore, this);
//setDefaultBtn("Search");
// </FS:Ansariel>
return true;
}
+8 -3
View File
@@ -66,8 +66,10 @@ bool LLPanelDirEvents::postBuild()
childSetCommitCallback("mature", onCommitMature, this);
childSetAction("Search", LLPanelDirBrowser::onClickSearchCore, this);
setDefaultBtn("Search");
// <FS:Ansariel> Port over search term history
//childSetAction("Search", LLPanelDirBrowser::onClickSearchCore, this);
//setDefaultBtn("Search");
// </FS:Ansariel>
onDateModeCallback(NULL, this);
@@ -173,7 +175,10 @@ void LLPanelDirEvents::performQueryOrDelete(U32 event_id)
U32 cat_id = childGetValue("category_combo").asInteger();
params << cat_id << "|";
params << childGetValue("event_search_text").asString();
// <FS:PP> FIRE-36501 Events don't have line_editor "event_search_text", but search_combo_box "name"
// params << childGetValue("event_search_text").asString();
params << childGetValue("name").asString();
// </FS:PP>
// send the message
if (0 == event_id)
+23 -4
View File
@@ -33,6 +33,8 @@
#include "llviewercontrol.h"
#include "llsearcheditor.h"
#include "llgroupactions.h"
static LLPanelInjector<LLPanelDirGroups> t_panel_dir_groups("panel_dir_groups");
LLPanelDirGroups::LLPanelDirGroups()
@@ -48,8 +50,10 @@ bool LLPanelDirGroups::postBuild()
//getChild<LLLineEditor>("name")->setKeystrokeCallback(boost::bind(&LLPanelDirBrowser::onKeystrokeName, _1, _2), NULL);
childSetAction("Search", &LLPanelDirBrowser::onClickSearchCore, this);
setDefaultBtn( "Search" );
// <FS:Ansariel> Port over search term history
//childSetAction("Search", &LLPanelDirBrowser::onClickSearchCore, this);
//setDefaultBtn( "Search" );
// </FS:Ansariel>
return true;
}
@@ -61,7 +65,12 @@ LLPanelDirGroups::~LLPanelDirGroups()
// virtual
void LLPanelDirGroups::performQuery()
{
if (childGetValue("name").asString().length() < mMinSearchChars)
// <FS:PP> Improve query sanitization
// if (childGetValue("name").asString().length() < mMinSearchChars)
std::string search_text = childGetValue("name").asString();
LLStringUtil::trim(search_text);
if (search_text.length() < mMinSearchChars)
// </FS:PP>
{
return;
}
@@ -84,7 +93,17 @@ void LLPanelDirGroups::performQuery()
sendDirFindQuery(
gMessageSystem,
mSearchID,
childGetValue("name").asString(),
// <FS:PP> Search by UUID
// childGetValue("name").asString(),
search_text,
// </FS:PP>
scope,
mSearchStart);
}
// <FS:Ansariel> Add "open profile" button
void LLPanelDirGroups::openProfile()
{
LLGroupActions::show(mSelectedID);
}
// </FS:Ansariel>
+2
View File
@@ -40,6 +40,8 @@ public:
/*virtual*/ void performQuery();
static void onClickSearch(void *userdata);
void openProfile(); // <FS:Ansariel> Add "open profile" button
};
#endif
+111 -4
View File
@@ -34,6 +34,13 @@
#include "llqueryflags.h"
#include "llnotificationsutil.h"
// <FS:PP> Search by UUID
#include "llavatarnamecache.h"
#include "llscrolllistctrl.h"
// </FS:PP>
#include "llavataractions.h"
static LLPanelInjector<LLPanelDirPeople> t_panel_dir_people("panel_dir_people");
LLPanelDirPeople::LLPanelDirPeople()
@@ -48,31 +55,73 @@ bool LLPanelDirPeople::postBuild()
//getChild<LLLineEditor>("name")->setKeystrokeCallback(boost::bind(&LLPanelDirBrowser::onKeystrokeName, _1, _2), NULL);
childSetAction("Search", &LLPanelDirBrowser::onClickSearchCore, this);
setDefaultBtn( "Search" );
// <FS:Ansariel> Port over search term history
//childSetAction("Search", &LLPanelDirBrowser::onClickSearchCore, this);
//setDefaultBtn( "Search" );
// </FS:Ansariel>
return true;
}
LLPanelDirPeople::~LLPanelDirPeople()
{
// <FS:PP> Search by UUID
if (mAvatarNameCallbackConnection.connected())
{
mAvatarNameCallbackConnection.disconnect();
}
// </FS:PP>
}
// virtual
void LLPanelDirPeople::performQuery()
{
if (childGetValue("name").asString().length() < mMinSearchChars)
// <FS:PP> Improve query sanitization
// if (childGetValue("name").asString().length() < mMinSearchChars)
// {
// return;
// }
std::string search_text = childGetValue("name").asString();
LLStringUtil::replaceChar(search_text, '.', ' ');
LLStringUtil::trim(search_text);
if (search_text.length() < mMinSearchChars)
{
return;
}
if (LLUUID::validate(search_text))
{
setupNewSearch();
if (mAvatarNameCallbackConnection.connected())
{
mAvatarNameCallbackConnection.disconnect();
}
const LLUUID avatar_id(search_text);
LLAvatarName av_name;
if (LLAvatarNameCache::get(avatar_id, &av_name))
{
addUUIDAvatarResult(avatar_id, av_name);
}
else
{
mAvatarNameCallbackConnection = LLAvatarNameCache::get(avatar_id, boost::bind(&LLPanelDirPeople::onAvatarNameCallback, this, _1, _2));
}
return;
}
// </FS:PP>
// filter short words out of the query string
// and indidate if we did have to filter it
// The shortest username is 2 characters long.
const S32 SHORTEST_WORD_LEN = 2;
bool query_was_filtered = false;
std::string query_string = LLPanelDirBrowser::filterShortWords(
childGetValue("name").asString(),
// <FS:PP> Improve query sanitization
// childGetValue("name").asString(),
search_text,
// </FS:PP>
SHORTEST_WORD_LEN,
query_was_filtered );
@@ -103,3 +152,61 @@ void LLPanelDirPeople::performQuery()
scope,
mSearchStart);
}
// <FS:PP> Search by UUID
void LLPanelDirPeople::onAvatarNameCallback(const LLUUID& id, const LLAvatarName& av_name)
{
if (mAvatarNameCallbackConnection.connected())
{
mAvatarNameCallbackConnection.disconnect();
}
addUUIDAvatarResult(id, av_name);
}
void LLPanelDirPeople::addUUIDAvatarResult(const LLUUID& id, const LLAvatarName& av_name)
{
LLScrollListCtrl* list = getChild<LLScrollListCtrl>("results");
if (!list)
{
return;
}
std::string avatar_name = av_name.getUserName();
if (avatar_name.empty())
{
avatar_name = av_name.getDisplayName();
}
if (avatar_name.empty())
{
avatar_name = id.asString();
}
LLSD content;
content["type"] = AVATAR_CODE;
content["name"] = avatar_name;
LLSD row;
row["id"] = id;
row["columns"][0]["column"] = "icon";
row["columns"][0]["type"] = "icon";
row["columns"][0]["value"] = "icon_avatar_offline.tga";
row["columns"][1]["column"] = "name";
row["columns"][1]["value"] = avatar_name;
row["columns"][1]["font"] = "SANSSERIF";
list->addElement(row);
mResultsContents[id.asString()] = content;
mHaveSearchResults = true;
updateResultCount();
list->setEnabled(true);
list->selectFirstItem();
onCommitList(nullptr, this);
}
// </FS:PP>
// <FS:Ansariel> Add "open profile" button
void LLPanelDirPeople::openProfile()
{
LLAvatarActions::showProfile(mSelectedID);
}
// </FS:Ansariel>
+12
View File
@@ -27,9 +27,11 @@
#ifndef LL_LLPANELDIRPEOPLE_H
#define LL_LLPANELDIRPEOPLE_H
#include <boost/signals2/connection.hpp> // <FS:PP> Search by UUID
#include "llpaneldirbrowser.h"
class LLLineEditor;
class LLFloaterDirectory;
class LLAvatarName; // <FS:PP> Search by UUID
class LLPanelDirPeople : public LLPanelDirBrowser
{
@@ -43,6 +45,16 @@ public:
static void onClickSearch(void *userdata);
static void onKeystrokeName(LLLineEditor* line, void* data);
void openProfile(); // <FS:Ansariel> Add "open profile" button
// <FS:PP> Search by UUID
private:
void onAvatarNameCallback(const LLUUID& id, const LLAvatarName& av_name);
void addUUIDAvatarResult(const LLUUID& id, const LLAvatarName& av_name);
boost::signals2::connection mAvatarNameCallbackConnection;
// </FS:PP>
};
#endif
+4 -2
View File
@@ -59,8 +59,10 @@ bool LLPanelDirPlaces::postBuild()
//getChild<LLLineEditor>("name")->setKeystrokeCallback(boost::bind(&LLPanelDirBrowser::onKeystrokeName, _1, _2), NULL);
childSetAction("Search", &LLPanelDirBrowser::onClickSearchCore, this);
setDefaultBtn("Search");
// <FS:Ansariel> Port over search term history
//childSetAction("Search", &LLPanelDirBrowser::onClickSearchCore, this);
//setDefaultBtn("Search");
// </FS:Ansariel>
mCurrentSortColumn = "dwell";
mCurrentSortAscending = false;
+19 -2
View File
@@ -32,6 +32,7 @@
#include "llbutton.h"
#include "llfloaterdirectory.h"
#include "lltextbox.h"
#include "lluri.h" // <FS:PP> FIRE-36483 Menu, navbar and toolbar button must open the same search window
#include "llviewercontrol.h"
#include "llweb.h"
@@ -89,11 +90,27 @@ void LLPanelDirWeb::onVisibilityChange(bool new_visibility)
void LLPanelDirWeb::navigateToDefaultPage()
{
// <FS:PP> FIRE-36483 Menu, navbar and toolbar button must open the same search window
navigateToSearchPage("standard", "");
}
void LLPanelDirWeb::navigateToSearchPage(const std::string& category, const std::string& query, const std::string& collection)
{
// </FS:PP>
std::string url = gSavedSettings.getString("SearchURL");
LLSD subs;
subs["QUERY"] = "";
subs["TYPE"] = "standard";
// <FS:PP> FIRE-36483 Menu, navbar and toolbar button must open the same search window
// subs["QUERY"] = "";
// subs["TYPE"] = "standard";
subs["QUERY"] = LLURI::escape(query);
subs["TYPE"] = category.empty() ? "standard" : category;
subs["COLLECTION"] = "";
if (subs["TYPE"].asString() == "standard" && !collection.empty())
{
subs["COLLECTION"] = "&collection_chosen=" + collection;
}
// </FS:PP>
// Default to PG
std::string maturity = "g";
if (gAgent.prefersAdult())
+4
View File
@@ -48,6 +48,10 @@ public:
void navigateToDefaultPage();
// <FS:PP> FIRE-36483 Menu, navbar and toolbar button must open the same search window
void navigateToSearchPage(const std::string& category, const std::string& query, const std::string& collection = "");
// </FS:PP>
void setFloaterDirectory(LLFloaterDirectory* floater) { mFloaterDirectory = floater; }
protected:
+228 -24
View File
@@ -140,12 +140,16 @@ protected:
if (online1 == online2)
{
std::string name1 = item1->getAvatarName();
std::string name2 = item2->getAvatarName();
// <FS:PP> FIRE-36478: Ignore alias quotation marks when comparing names from contact sets
// std::string name1 = item1->getAvatarName();
// std::string name2 = item2->getAvatarName();
LLStringUtil::toUpper(name1);
LLStringUtil::toUpper(name2);
// LLStringUtil::toUpper(name1);
// LLStringUtil::toUpper(name2);
std::string name1 = getComparableName(item1);
std::string name2 = getComparableName(item2);
// </FS:PP>
return name1 < name2;
}
@@ -153,6 +157,30 @@ protected:
}
};
// <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
class LLAvatarItemOnlineSipStatusComparator : public LLAvatarItemComparator
{
public:
LLAvatarItemOnlineSipStatusComparator() {};
protected:
virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const
{
const bool online1 = LLAvatarTracker::instance().isBuddyOnline(item1->getAvatarId());
const bool online2 = LLAvatarTracker::instance().isBuddyOnline(item2->getAvatarId());
if (online1 == online2)
{
std::string name1 = getComparableName(item1);
std::string name2 = getComparableName(item2);
return name1 < name2;
}
return online1 > online2;
}
};
// </FS:PP>
/** Compares avatar items by distance between you and them */
class LLAvatarItemDistanceComparator : public LLAvatarItemComparator
{
@@ -242,12 +270,16 @@ protected:
if (arr_time1 == arr_time2)
{
std::string name1 = item1->getAvatarName();
std::string name2 = item2->getAvatarName();
// <FS:PP> FIRE-36478: Ignore alias quotation marks when comparing names from contact sets
// std::string name1 = item1->getAvatarName();
// std::string name2 = item2->getAvatarName();
LLStringUtil::toUpper(name1);
LLStringUtil::toUpper(name2);
// LLStringUtil::toUpper(name1);
// LLStringUtil::toUpper(name2);
std::string name1 = getComparableName(item1);
std::string name2 = getComparableName(item2);
// </FS:PP>
return name1 < name2;
}
@@ -257,6 +289,7 @@ protected:
static const LLAvatarItemRecentComparator RECENT_COMPARATOR;
static const LLAvatarItemStatusComparator STATUS_COMPARATOR;
static const LLAvatarItemOnlineSipStatusComparator ONLINE_SIP_STATUS_COMPARATOR; // <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
static LLAvatarItemDistanceComparator DISTANCE_COMPARATOR;
static const LLAvatarItemRecentSpeakerComparator RECENT_SPEAKER_COMPARATOR;
static LLAvatarItemRecentArrivalComparator RECENT_ARRIVAL_COMPARATOR;
@@ -604,6 +637,7 @@ LLPanelPeople::LLPanelPeople()
mCommitCallbackRegistrar.add("ContactSet.Action", boost::bind(&LLPanelPeople::onContactSetsMenuItemClicked, this, _2));
mEnableCallbackRegistrar.add("ContactSet.Enable", boost::bind(&LLPanelPeople::onContactSetsEnable, this, _2));
mContactSetChangedConnection = LGGContactSets::getInstance()->setContactSetChangeCallback(boost::bind(&LLPanelPeople::updateContactSets, this, _1));
LLAvatarTracker::instance().addObserver(this);
// [/FS:CR]
// <FS:Ansariel> FIRE-10839: Customizable radar columns (needed for Vintage skin)
@@ -620,7 +654,10 @@ LLPanelPeople::~LLPanelPeople()
delete mFriendListUpdater;
delete mRecentListUpdater;
LLVoiceClient::removeObserver(this);
// <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
// LLVoiceClient::removeObserver(this);
LLVoiceClient::removeObserver(static_cast<LLVoiceClientStatusObserver*>(this));
// </FS:PP>
mNearbyFilterCommitConnection.disconnect();
mFriedsFilterCommitConnection.disconnect();
@@ -628,8 +665,10 @@ LLPanelPeople::~LLPanelPeople()
mRecentFilterCommitConnection.disconnect();
// [FS:CR] Contact sets
mContactSetsFilterCommitConnection.disconnect();
if (mContactSetChangedConnection.connected())
mContactSetChangedConnection.disconnect();
LLAvatarTracker::instance().removeObserver(this);
// [/FS:CR]
}
@@ -754,6 +793,18 @@ bool LLPanelPeople::postBuild()
mRecentFilterCommitConnection = recent_tab->getChild<LLFilterEditor>("recent_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2));
mGroupsFilterCommitConnection = group_tab->getChild<LLFilterEditor>("groups_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2));
// <FS:PP> FIRE-17568: Search in Contact Sets
if (LLFilterEditor* contact_sets_filter = getChild<LLFilterEditor>("contact_sets_filter_input", true))
{
mContactSetsFilterCommitConnection = contact_sets_filter->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2));
}
// </FS:PP>
// <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
mOnlineFriendList->setUseContactSetColors(true);
mAllFriendList->setUseContactSetColors(true);
// </FS:PP>
// <FS:Ansariel> Use Firestorm radar menu handler
//mNearbyList->setContextMenu(&LLPanelPeopleMenus::gNearbyPeopleContextMenu);
// </FS:Ansariel>
@@ -814,6 +865,9 @@ bool LLPanelPeople::postBuild()
mContactSetList = getChild<LLAvatarList>("contact_list");
if (mContactSetList)
{
mContactSetList->setUseContactSetColors(true);
mContactSetList->setUseContactSetListStyle(true);
mContactSetList->setAvatarDropCallback(boost::bind(&LLPanelPeople::handleAvatarDropToCurrentContactSet, this, _1, _2));
mContactSetList->setCommitCallback(boost::bind(&LLPanelPeople::updateButtons, this));
mContactSetList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1));
mContactSetList->setNoItemsCommentText(getString("empty_list"));
@@ -837,7 +891,10 @@ bool LLPanelPeople::postBuild()
// Must go after setting commit callback and initializing all pointers to children.
mTabContainer->selectTabByName(NEARBY_TAB_NAME);
LLVoiceClient::addObserver(this);
// <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
// LLVoiceClient::addObserver(this);
LLVoiceClient::addObserver(static_cast<LLVoiceClientStatusObserver*>(this));
// </FS:PP> FIRE-21531: Sort Contact Sets by Online Status
// call this method in case some list is empty and buttons can be in inconsistent state
updateButtons();
@@ -858,9 +915,26 @@ void LLPanelPeople::onChange(EStatusType status, const LLSD& channelInfo, bool p
return;
}
// <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
if (mContactSetList && shouldSortByOnlineStatusForCurrentSet())
{
mContactSetList->sort();
}
// </FS:PP>
updateButtons();
}
// <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
void LLPanelPeople::changed(U32 mask)
{
if ((mask & LLFriendObserver::ONLINE) && mContactSetList && shouldSortByOnlineStatusForCurrentSet())
{
mContactSetList->sort();
}
}
// </FS:PP>
void LLPanelPeople::updateFriendListHelpText()
{
// show special help text for just created account to help finding friends. EXT-4836
@@ -934,6 +1008,14 @@ void LLPanelPeople::updateFriendList()
mOnlineFriendList->setDirty(true, !mOnlineFriendList->filterHasMatches());// do force update if list do NOT have items
mAllFriendList->setDirty(true, !mAllFriendList->filterHasMatches());
//update trash and other buttons according to a selected item
// <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
if (mContactSetList && shouldSortByOnlineStatusForCurrentSet())
{
mContactSetList->sort();
}
// </FS:PP>
updateButtons();
showFriendsAccordionsIfNeeded();
@@ -1236,6 +1318,15 @@ void LLPanelPeople::onFilterEdit(const std::string& search_string)
{
mRecentList->setNameFilter(filter);
}
// <FS:PP> FIRE-17568: Search in Contact Sets
else if (cur_tab == CONTACT_SETS_TAB_NAME)
{
if (mContactSetList)
{
mContactSetList->setNameFilter(filter);
}
}
// </FS:PP>
}
void LLPanelPeople::onGroupLimitInfo()
@@ -1266,6 +1357,13 @@ void LLPanelPeople::onTabSelected(const LLSD& param)
showFriendsAccordionsIfNeeded();
// <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
if (getActiveTabName() == CONTACT_SETS_TAB_NAME && mContactSetList && shouldSortByOnlineStatusForCurrentSet())
{
mContactSetList->sort();
}
// </FS:PP>
// <FS:AO> Layout panels will not initialize at a constant size, force it here.
if (mTabContainer->getCurrentPanel()->getName() == NEARBY_TAB_NAME)
{
@@ -1772,6 +1870,17 @@ bool LLPanelPeople::updateNearbyArrivalTime()
// [FS:CR] Contact sets
void LLPanelPeople::updateContactSets(LGGContactSets::EContactSetUpdate type)
{
// <FS:PP> FIRE-32748 Colorize Friends List with Contact Sets
if (mOnlineFriendList)
{
mOnlineFriendList->refreshNames();
}
if (mAllFriendList)
{
mAllFriendList->refreshNames();
}
// </FS:PP>
switch (type)
{
case LGGContactSets::UPDATED_LISTS:
@@ -1812,17 +1921,7 @@ void LLPanelPeople::generateContactList(const std::string& contact_set)
if (contact_set == CS_SET_ALL_SETS)
{
avatars = LGGContactSets::getInstance()->getListOfNonFriends();
// "All sets" includes buddies
LLAvatarTracker::buddy_map_t all_buddies;
LLAvatarTracker::instance().copyBuddyList(all_buddies);
for (LLAvatarTracker::buddy_map_t::const_iterator buddy = all_buddies.begin();
buddy != all_buddies.end();
++buddy)
{
avatars.push_back(buddy->first);
}
avatars = LGGContactSets::getInstance()->getFriendsInAnySet();
}
else if (contact_set == CS_SET_NO_SETS)
{
@@ -1851,6 +1950,7 @@ void LLPanelPeople::generateContactList(const std::string& contact_set)
}
}
}
updateContactSetListSorting();
mContactSetList->setDirty();
}
@@ -1860,6 +1960,62 @@ void LLPanelPeople::generateCurrentContactList()
generateContactList(mContactSetCombo->getValue().asString());
}
void LLPanelPeople::updateContactSetListSorting()
{
if (!mContactSetList)
{
return;
}
if (shouldSortByOnlineStatusForCurrentSet())
{
mContactSetList->setComparator(&ONLINE_SIP_STATUS_COMPARATOR);
mContactSetList->sort();
}
else
{
mContactSetList->sortByName();
}
}
bool LLPanelPeople::shouldSortByOnlineStatusForCurrentSet() const
{
if (!mContactSetCombo)
{
return false;
}
const std::string set_name = mContactSetCombo->getValue().asString();
if (LGGContactSets::getInstance()->isInternalSetName(set_name))
{
return false;
}
return LGGContactSets::getInstance()->getSortByOnlineStatusForSet(set_name);
}
bool LLPanelPeople::handleAvatarDropToCurrentContactSet(const LLUUID& avatar_id, bool drop)
{
if (!mContactSetCombo || avatar_id.isNull())
{
return false;
}
const std::string set_name = mContactSetCombo->getValue().asString();
if (LGGContactSets::getInstance()->isInternalSetName(set_name))
{
return false;
}
if (drop)
{
uuid_vec_t ids{ avatar_id };
LGGContactSets::instance().addToSet(ids, set_name);
}
return true;
}
bool LLPanelPeople::onContactSetsEnable(const LLSD& userdata)
{
std::string item = userdata.asString();
@@ -1945,6 +2101,10 @@ void LLPanelPeople::onContactSetsMenuItemClicked(const LLSD& userdata)
}
LLNotificationsUtil::add((selected_size > 1 ? "RemoveContactsFromSet" : "RemoveContactFromSet"), args, payload, &LGGContactSets::handleRemoveAvatarFromSetCallback);
}
else if (chosen_item == "move_contact")
{
moveSelectedContactsToSet();
}
else if (chosen_item == "set_config")
{
LLFloater* root_floater = gFloaterView->getParentFloater(this);
@@ -1993,9 +2153,20 @@ void LLPanelPeople::onContactSetsMenuItemClicked(const LLSD& userdata)
if (selected_uuids.empty()) return;
LLSD payload, args;
args["AVATAR"] = LLSLURL("agent", selected_uuids.front(), "about").getSLURLString();
payload["id"] = selected_uuids.front();
LLNotificationsUtil::add("SetAvatarPseudonym", args, payload, &LGGContactSets::handleSetAvatarPseudonymCallback);
if (selected_uuids.size() == 1)
{
args["AVATAR"] = LLSLURL("agent", selected_uuids.front(), "about").getSLURLString();
payload["id"] = selected_uuids.front();
}
else
{
args["COUNT"] = llformat("%d", static_cast<S32>(selected_uuids.size()));
for (const LLUUID& id : selected_uuids)
{
payload["ids"].append(id);
}
}
LLNotificationsUtil::add((selected_uuids.size() > 1 ? "SetAvatarPseudonymMultiple" : "SetAvatarPseudonym"), args, payload, &LGGContactSets::handleSetAvatarPseudonymCallback);
}
else if (chosen_item == "remove_pseudonym")
{
@@ -2036,6 +2207,29 @@ void LLPanelPeople::handlePickerCallback(const uuid_vec_t& ids, const std::strin
LGGContactSets::instance().addToSet(ids, set);
}
void LLPanelPeople::moveSelectedContactsToSet()
{
if (!mContactSetCombo)
{
return;
}
const std::string source_set = mContactSetCombo->getValue().asString();
if (LGGContactSets::getInstance()->isInternalSetName(source_set))
{
return;
}
uuid_vec_t selected_uuids;
getCurrentItemIDs(selected_uuids);
if (selected_uuids.empty())
{
return;
}
LLAvatarActions::moveToContactSet(selected_uuids, source_set);
}
// [/FS:CR]
// <FS:Ansariel> FIRE-10839: Customizable radar columns (needed for Vintage skin)
@@ -2079,6 +2273,16 @@ bool LLPanelPeople::handleKeyHere(KEY key, MASK mask)
getChild<LLFilterEditor>("recent_filter_input")->setFocus(true);
return true;
}
// <FS:PP> FIRE-17568: Search in Contact Sets
else if (cur_tab == CONTACT_SETS_TAB_NAME)
{
if (LLFilterEditor* filter_editor = getChild<LLFilterEditor>("contact_sets_filter_input", true))
{
filter_editor->setFocus(true);
return true;
}
}
// </FS:PP>
}
return LLPanel::handleKeyHere(key, mask);
+7
View File
@@ -55,6 +55,7 @@ class FSPanelRadar;
class LLPanelPeople
: public LLPanel
, public LLFriendObserver // <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
, public LLVoiceClientStatusObserver
{
LOG_CLASS(LLPanelPeople);
@@ -68,6 +69,7 @@ public:
// Implements LLVoiceClientStatusObserver::onChange() to enable call buttons
// when voice is available
void onChange(EStatusType status, const LLSD& channelInfo, bool proximal) override;
void changed(U32 mask) override; // <FS:PP> FIRE-21531: Sort Contact Sets by Online Status
// <FS:Ansariel> CTRL-F focusses local search editor
bool handleKeyHere(KEY key, MASK mask) override;
@@ -199,14 +201,19 @@ private:
boost::signals2::connection mFriedsFilterCommitConnection;
boost::signals2::connection mGroupsFilterCommitConnection;
boost::signals2::connection mRecentFilterCommitConnection;
boost::signals2::connection mContactSetsFilterCommitConnection; // <FS:PP> FIRE-17568: Search in Contact Sets
// [FS:CR] Contact sets
bool onContactSetsEnable(const LLSD& userdata);
void onContactSetsMenuItemClicked(const LLSD& userdata);
void handlePickerCallback(const uuid_vec_t& ids, const std::string& set);
void moveSelectedContactsToSet();
void refreshContactSets();
void generateContactList(const std::string& contact_set);
void generateCurrentContactList();
void updateContactSetListSorting();
bool shouldSortByOnlineStatusForCurrentSet() const;
bool handleAvatarDropToCurrentContactSet(const LLUUID& avatar_id, bool drop);
void updateContactSets(LGGContactSets::EContactSetUpdate type);
boost::signals2::connection mContactSetChangedConnection;
+84 -4
View File
@@ -81,6 +81,7 @@
#include "llvoiceclient.h"
#include "llweb.h"
#include "llviewernetwork.h" // <FS:Beq> For LLGridManager
#include "lggcontactsets.h" // <FS:PP> FIRE-32401: Contact Sets on groups list in profile
#include "fsdata.h"
#include "fsradar.h" // <FS:Zi> Update notes in radar when edited
@@ -1043,7 +1044,10 @@ void LLPanelProfileSecondLife::resetData()
setDescriptionText(LLStringUtil::null);
mGroups.clear();
mGroupList->setGroups(mGroups);
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
// mGroupList->setGroups(mGroups);
refreshGroupAndContactSetList();
// </FS:PP>
bool own_profile = getSelfProfile();
mCanSeeOnlineIcon->setVisible(false);
@@ -1169,7 +1173,11 @@ void LLPanelProfileSecondLife::processProfileProperties(const LLAvatarData* avat
mGroups[group_data.group_name] = group_data.group_id;
}
mGroupList->setGroups(mGroups);
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
// mGroupList->setGroups(mGroups);
refreshGroupAndContactSetList();
// </FS:PP>
} // </FS>
// <FS:Beq> Restore UDP profiles
@@ -1213,15 +1221,73 @@ void LLPanelProfileSecondLife::processGroupProperties(const LLAvatarGroups* avat
mGroups[group_data.group_name] = group_data.group_id;
}
mGroupList->setGroups(mGroups);
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
// mGroupList->setGroups(mGroups);
refreshGroupAndContactSetList();
// </FS:PP>
}
// </FS>
// <FS:PP> FIRE-32401: Contact Sets on groups list in profile
void LLPanelProfileSecondLife::refreshGroupAndContactSetList()
{
std::vector<std::string> profile_contact_sets = LGGContactSets::instance().getFriendSets(getAvatarId());
std::vector<std::string> filtered_contact_sets;
std::map<std::string, LLColor4> profile_contact_set_colors;
mProfileContactSets.clear();
for (const std::string& set_name : profile_contact_sets)
{
if (set_name.empty() || LGGContactSets::instance().isInternalSetName(set_name))
{
continue;
}
filtered_contact_sets.push_back(set_name);
mProfileContactSets.insert(set_name);
}
static LLCachedControl<bool> fsContactSetsColorizeFriends(gSavedSettings, "FSContactSetsColorizeFriends", false);
if (fsContactSetsColorizeFriends)
{
for (const std::string& set_name : filtered_contact_sets)
{
profile_contact_set_colors[set_name] = LGGContactSets::instance().getSetColor(set_name);
}
}
mGroupList->setShowFavoritesSeparator(!getSelfProfile());
mGroupList->setGroups(mGroups);
mGroupList->setSecondaryGroups(filtered_contact_sets, profile_contact_set_colors);
}
// void LLPanelProfileSecondLife::openGroupProfile()
// {
// LLUUID group_id = mGroupList->getSelectedUUID();
// LLGroupActions::show(group_id);
// }
void LLPanelProfileSecondLife::openGroupProfile()
{
const std::string selected_name = mGroupList->getSelectedGroupName();
if (selected_name.empty())
{
return;
}
LLUUID group_id = mGroupList->getSelectedUUID();
LLGroupActions::show(group_id);
if (group_id.notNull())
{
LLGroupActions::show(group_id);
return;
}
if (!selected_name.empty() && mProfileContactSets.find(selected_name) != mProfileContactSets.end())
{
LLFloaterReg::showInstance("imcontacts", LLSD("contact_sets"));
}
}
// </FS:PP>
void LLPanelProfileSecondLife::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name)
{
@@ -2307,6 +2373,13 @@ void LLPanelProfileSecondLife::onHideAgeCallback()
void LLPanelProfileSecondLife::onSaveDescriptionChanges()
{
// <FS:Trish> Fix applying changes when exiting profile causing the preview text to apply if preview mode is active
if (mPreview)
{
onCommitMenu(LLSD("preview"));
}
// </FS:Trish>
mDescriptionText = mDescriptionEdit->getValue().asString();
if (!gAgent.getRegionCapability(PROFILE_PROPERTIES_CAP).empty())
{
@@ -2972,6 +3045,13 @@ void LLPanelProfileFirstLife::onSetDescriptionDirty()
void LLPanelProfileFirstLife::onSaveDescriptionChanges()
{
// <FS:Trish> Fix applying changes when exiting profile causing the preview text to apply if preview mode is active
if (mPreview)
{
onClickPreview();
}
// </FS:Trish>
mCurrentDescription = mDescriptionEdit->getValue().asString();
if (!gAgent.getRegionCapability(PROFILE_PROPERTIES_CAP).empty())
{
+4
View File
@@ -36,6 +36,8 @@
#include "llvoiceclient.h"
#include "rlvhandler.h"
#include <set> // <FS:PP> FIRE-32401: Contact Sets on groups list in profile
// class LLPanelProfileClassifieds;
// class LLTabContainer;
@@ -147,6 +149,7 @@ protected:
*/
// <FS> OpenSim
void processGroupProperties(const LLAvatarGroups* avatar_groups);
void refreshGroupAndContactSetList(); // <FS:PP> FIRE-32401: Contact Sets on groups list in profile
/**
* Fills common for Avatar profile and My Profile fields.
@@ -231,6 +234,7 @@ private:
private:
typedef std::map<std::string, LLUUID> group_map_t;
group_map_t mGroups;
std::set<std::string> mProfileContactSets; // <FS:PP> FIRE-32401: Contact Sets on groups list in profile
void openGroupProfile();
LLTextBox* mStatusText; // <FS:Ansariel> Fix LL UI/UX design accident
+13
View File
@@ -784,6 +784,12 @@ void LLPanelProfilePick::apply()
{
if ((mNewPick || getIsLoaded()) && isDirty())
{
// <FS:Trish> Fix applying changes when exiting profile causing the preview text to apply if preview mode is active
if (mPreview)
{
onClickPreview();
}
// </FS:Trish>
sendUpdate();
}
}
@@ -847,6 +853,13 @@ void LLPanelProfilePick::onClickTeleport()
void LLPanelProfilePick::enableSaveButton(bool enable)
{
//<FS:Trish> Fix Save Buttons being enabled in various cases while preview mode is on
if (mPreview)
{
enable = false;
}
//</FS:Trish>
childSetVisible("save_changes_lp", enable);
childSetVisible("save_btn_lp", enable && !mNewPick);
+19 -2
View File
@@ -52,11 +52,20 @@
#include "llviewerregion.h"
#include "llviewerstats.h"
#include "llviewerassetupload.h"
#include "llviewercontrol.h" // <FS:PP> FIRE-36169 Gestures enable/disable switch
std::string NONE_LABEL;
std::string SHIFT_LABEL;
std::string CTRL_LABEL;
// <FS:PP> FIRE-36169 Gestures enable/disable switch
static bool are_gestures_enabled()
{
static LLCachedControl<bool> gestures_enabled(gSavedPerAccountSettings, "FSGesturesEnabled", true);
return gestures_enabled;
}
// </FS:PP>
void dialog_refresh_all();
// used for getting
@@ -656,7 +665,11 @@ void LLPreviewGesture::refresh()
mSaveBtn->setEnabled(false);
// Make sure preview button is enabled, so we can stop it
mPreviewBtn->setEnabled(true);
// <FS:PP> FIRE-36169 Gestures enable/disable switch
// mPreviewBtn->setEnabled(true);
mPreviewBtn->setEnabled(are_gestures_enabled());
// </FS:PP>
return;
}
@@ -774,7 +787,10 @@ void LLPreviewGesture::refresh()
mActiveCheck->set(active);
// Can only preview if there are steps
mPreviewBtn->setEnabled(step_count > 0);
// <FS:PP> FIRE-36169 Gestures enable/disable switch
// mPreviewBtn->setEnabled(step_count > 0);
mPreviewBtn->setEnabled(step_count > 0 && are_gestures_enabled());
// </FS:PP>
// And can only save if changes have been made
mSaveBtn->setEnabled(mDirty);
@@ -1783,6 +1799,7 @@ void LLPreviewGesture::onClickSave(void* data)
void LLPreviewGesture::onClickPreview(void* data)
{
LLPreviewGesture* self = (LLPreviewGesture*)data;
if (!are_gestures_enabled()) return; // <FS:PP> FIRE-36169 Gestures enable/disable switch
if (!self->mPreviewGesture)
{

Some files were not shown because too many files have changed in this diff Show More