summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2025-04-30 09:40:14 +0800
committerErik Kundiman <erik@megapahit.org>2025-04-30 09:40:14 +0800
commitc0a2498fa1ff074fa29b31bbd63b2ac6f69b38bb (patch)
tree0909d51200a3de225f20ebe7d0aa7cb95fb380e1
parent39cc9a1706340d6f84b152593a4d8aaeeaa88b56 (diff)
parentd9e55c44152064133796bfb08f1da524387c1300 (diff)
Merge tag 'Second_Life_Release#d9e55c44-Second_Life_Release#d9e55c4-2025.04' into 2025.04
-rw-r--r--.github/workflows/.gitattributes1
-rw-r--r--.github/workflows/qatest.yaml10
-rw-r--r--indra/llmessage/llpacketring.cpp10
-rw-r--r--indra/llrender/llglslshader.cpp6
-rw-r--r--indra/llui/CMakeLists.txt2
-rw-r--r--indra/llui/llchatentry.cpp1
-rw-r--r--indra/llui/llchatmentionhelper.cpp151
-rw-r--r--indra/llui/llchatmentionhelper.h66
-rw-r--r--indra/llui/llflatlistview.cpp6
-rw-r--r--indra/llui/llflatlistview.h4
-rw-r--r--indra/llui/llstyle.cpp8
-rw-r--r--indra/llui/llstyle.h21
-rw-r--r--indra/llui/lltextbase.cpp145
-rw-r--r--indra/llui/lltextbase.h17
-rw-r--r--indra/llui/lltexteditor.cpp82
-rw-r--r--indra/llui/lltexteditor.h6
-rw-r--r--indra/llui/llurlentry.cpp67
-rw-r--r--indra/llui/llurlentry.h42
-rw-r--r--indra/llui/llurlmatch.cpp10
-rw-r--r--indra/llui/llurlmatch.h11
-rw-r--r--indra/llui/llurlregistry.cpp20
-rw-r--r--indra/llui/llurlregistry.h3
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/VIEWER_VERSION.txt2
-rw-r--r--indra/newview/gltfscenemanager.cpp6
-rw-r--r--indra/newview/llappviewer.cpp2
-rw-r--r--indra/newview/llavatarlist.cpp18
-rw-r--r--indra/newview/llavatarlist.h7
-rw-r--r--indra/newview/llavatarlistitem.cpp17
-rw-r--r--indra/newview/llavatarlistitem.h3
-rw-r--r--indra/newview/llfloaterbulkpermission.cpp16
-rw-r--r--indra/newview/llfloaterbulkpermission.h7
-rw-r--r--indra/newview/llfloaterchatmentionpicker.cpp184
-rw-r--r--indra/newview/llfloaterchatmentionpicker.h58
-rw-r--r--indra/newview/llfloaterimnearbychat.cpp2
-rw-r--r--indra/newview/llfloaterimsession.cpp2
-rw-r--r--indra/newview/llfloaterimsessiontab.cpp2
-rw-r--r--indra/newview/llfloaternewfeaturenotification.cpp26
-rw-r--r--indra/newview/llinventorymodel.cpp1
-rw-r--r--indra/newview/llmeshrepository.cpp205
-rw-r--r--indra/newview/llmeshrepository.h68
-rw-r--r--indra/newview/llpanelface.cpp573
-rw-r--r--indra/newview/llpanelface.h3
-rw-r--r--indra/newview/llpanelprofile.cpp2
-rw-r--r--indra/newview/llstartup.cpp2
-rw-r--r--indra/newview/llsurfacepatch.cpp3
-rw-r--r--indra/newview/lltexturectrl.cpp3
-rw-r--r--indra/newview/llviewerchat.cpp8
-rw-r--r--indra/newview/llviewerfloaterreg.cpp2
-rw-r--r--indra/newview/llvoavatar.cpp10
-rw-r--r--indra/newview/llvoavatar.h1
-rw-r--r--indra/newview/llvoavatarself.cpp2
-rw-r--r--indra/newview/llvovolume.cpp7
-rw-r--r--indra/newview/skins/default/colors.xml6
-rw-r--r--indra/newview/skins/default/xui/en/floater_chat_mention_picker.xml31
-rw-r--r--indra/newview/skins/default/xui/en/panel_tools_texture.xml8
56 files changed, 1534 insertions, 444 deletions
diff --git a/.github/workflows/.gitattributes b/.github/workflows/.gitattributes
new file mode 100644
index 0000000000..6dc1cca42a
--- /dev/null
+++ b/.github/workflows/.gitattributes
@@ -0,0 +1 @@
+qatest.yaml -text eol=crlf
diff --git a/.github/workflows/qatest.yaml b/.github/workflows/qatest.yaml
index 7f3a5242e9..4892cfaae3 100644
--- a/.github/workflows/qatest.yaml
+++ b/.github/workflows/qatest.yaml
@@ -1,4 +1,7 @@
name: Run QA Test # Runs automated tests on a self-hosted QA machine
+permissions:
+ contents: read
+ #pull-requests: write # maybe need to re-add this later
on:
workflow_run:
@@ -15,11 +18,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Debug Workflow Variables
+ env:
+ HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
+ HEAD_COMMIT_MSG: ${{ github.event.workflow_run.head_commit.message }}
run: |
echo "Workflow Conclusion: ${{ github.event.workflow_run.conclusion }}"
- echo "Workflow Head Branch: ${{ github.event.workflow_run.head_branch }}"
+ echo "Workflow Head Branch: $HEAD_BRANCH"
echo "Workflow Run ID: ${{ github.event.workflow_run.id }}"
- echo "Head Commit Message: ${{ github.event.workflow_run.head_commit.message }}"
+ echo "Head Commit Message: $HEAD_COMMIT_MSG"
echo "GitHub Ref: ${{ github.ref }}"
echo "GitHub Ref Name: ${{ github.ref_name }}"
echo "GitHub Event Name: ${{ github.event_name }}"
diff --git a/indra/llmessage/llpacketring.cpp b/indra/llmessage/llpacketring.cpp
index eb6650c6c5..b8284334ea 100644
--- a/indra/llmessage/llpacketring.cpp
+++ b/indra/llmessage/llpacketring.cpp
@@ -209,8 +209,14 @@ S32 LLPacketRing::receiveOrDropBufferedPacket(char *datap, bool drop)
if (!drop)
{
- assert(packet_size > 0);
- memcpy(datap, packet->getData(), packet_size);
+ if (packet_size > 0)
+ {
+ memcpy(datap, packet->getData(), packet_size);
+ }
+ else
+ {
+ assert(false);
+ }
}
else
{
diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp
index e0c2db058a..aac5e3abc8 100644
--- a/indra/llrender/llglslshader.cpp
+++ b/indra/llrender/llglslshader.cpp
@@ -1114,8 +1114,8 @@ void LLGLSLShader::bind()
void LLGLSLShader::bind(U8 variant)
{
- llassert(mGLTFVariants.size() == LLGLSLShader::NUM_GLTF_VARIANTS);
- llassert(variant < LLGLSLShader::NUM_GLTF_VARIANTS);
+ llassert_always(mGLTFVariants.size() == LLGLSLShader::NUM_GLTF_VARIANTS);
+ llassert_always(variant < LLGLSLShader::NUM_GLTF_VARIANTS);
mGLTFVariants[variant].bind();
}
@@ -1123,7 +1123,7 @@ void LLGLSLShader::bind(bool rigged)
{
if (rigged)
{
- llassert(mRiggedVariant);
+ llassert_always(mRiggedVariant);
mRiggedVariant->bind();
}
else
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 5991a5b35e..83b3a220a0 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -18,6 +18,7 @@ set(llui_SOURCE_FILES
llbadgeowner.cpp
llbutton.cpp
llchatentry.cpp
+ llchatmentionhelper.cpp
llcheckboxctrl.cpp
llclipboard.cpp
llcombobox.cpp
@@ -130,6 +131,7 @@ set(llui_HEADER_FILES
llcallbackmap.h
llchatentry.h
llchat.h
+ llchatmentionhelper.h
llcheckboxctrl.h
llclipboard.h
llcombobox.h
diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp
index e8d942b8af..7506cd99c0 100644
--- a/indra/llui/llchatentry.cpp
+++ b/indra/llui/llchatentry.cpp
@@ -52,6 +52,7 @@ LLChatEntry::LLChatEntry(const Params& p)
mCurrentHistoryLine = mLineHistory.begin();
mAutoIndent = false;
+ mShowChatMentionPicker = true;
keepSelectionOnReturn(true);
}
diff --git a/indra/llui/llchatmentionhelper.cpp b/indra/llui/llchatmentionhelper.cpp
new file mode 100644
index 0000000000..f7769b2cbe
--- /dev/null
+++ b/indra/llui/llchatmentionhelper.cpp
@@ -0,0 +1,151 @@
+/**
+* @file llchatmentionhelper.cpp
+*
+* $LicenseInfo:firstyear=2025&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2025, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation;
+* version 2.1 of the License only.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*
+* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+* $/LicenseInfo$
+*/
+
+#include "linden_common.h"
+
+#include "llchatmentionhelper.h"
+#include "llfloater.h"
+#include "llfloaterreg.h"
+#include "lluictrl.h"
+
+constexpr char CHAT_MENTION_HELPER_FLOATER[] = "chat_mention_picker";
+
+bool LLChatMentionHelper::isActive(const LLUICtrl* ctrl) const
+{
+ return mHostHandle.get() == ctrl;
+}
+
+bool LLChatMentionHelper::isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos) const
+{
+ if (cursor_pos <= 0 || cursor_pos > static_cast<S32>(wtext.size()))
+ return false;
+
+ // Find the beginning of the current word
+ S32 start = cursor_pos - 1;
+ while (start > 0 && wtext[start - 1] != U32(' ') && wtext[start - 1] != U32('\n'))
+ {
+ --start;
+ }
+
+ if (wtext[start] != U32('@'))
+ return false;
+
+ if (mention_start_pos)
+ *mention_start_pos = start;
+
+ S32 word_length = cursor_pos - start;
+
+ if (word_length == 1)
+ {
+ return true;
+ }
+
+ // Get the name after '@'
+ std::string name = wstring_to_utf8str(wtext.substr(start + 1, word_length - 1));
+ LLStringUtil::toLower(name);
+ for (const auto& av_name : mAvatarNames)
+ {
+ if (av_name == name || av_name.find(name) == 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void LLChatMentionHelper::showHelper(LLUICtrl* host_ctrl, S32 local_x, S32 local_y, const std::string& av_name, std::function<void(std::string)> cb)
+{
+ if (mHelperHandle.isDead())
+ {
+ LLFloater* av_picker_floater = LLFloaterReg::getInstance(CHAT_MENTION_HELPER_FLOATER);
+ mHelperHandle = av_picker_floater->getHandle();
+ mHelperCommitConn = av_picker_floater->setCommitCallback([&](LLUICtrl* ctrl, const LLSD& param) { onCommitName(param.asString()); });
+ }
+ setHostCtrl(host_ctrl);
+ mNameCommitCb = cb;
+
+ S32 floater_x, floater_y;
+ if (!host_ctrl->localPointToOtherView(local_x, local_y, &floater_x, &floater_y, gFloaterView))
+ {
+ LL_WARNS() << "Cannot show helper for non-floater controls." << LL_ENDL;
+ return;
+ }
+
+ LLFloater* av_picker_floater = mHelperHandle.get();
+ LLRect rect = av_picker_floater->getRect();
+ rect.setLeftTopAndSize(floater_x, floater_y + rect.getHeight(), rect.getWidth(), rect.getHeight());
+ av_picker_floater->setRect(rect);
+ av_picker_floater->openFloater(LLSD().with("av_name", av_name));
+}
+
+void LLChatMentionHelper::hideHelper(const LLUICtrl* ctrl)
+{
+ if ((ctrl && !isActive(ctrl)))
+ {
+ return;
+ }
+ setHostCtrl(nullptr);
+}
+
+bool LLChatMentionHelper::handleKey(const LLUICtrl* ctrl, KEY key, MASK mask)
+{
+ if (mHelperHandle.isDead() || !isActive(ctrl))
+ {
+ return false;
+ }
+
+ return mHelperHandle.get()->handleKey(key, mask, true);
+}
+
+void LLChatMentionHelper::onCommitName(std::string name_url)
+{
+ if (!mHostHandle.isDead() && mNameCommitCb)
+ {
+ mNameCommitCb(name_url);
+ }
+}
+
+void LLChatMentionHelper::setHostCtrl(LLUICtrl* host_ctrl)
+{
+ const LLUICtrl* pCurHostCtrl = mHostHandle.get();
+ if (pCurHostCtrl != host_ctrl)
+ {
+ mHostCtrlFocusLostConn.disconnect();
+ mHostHandle.markDead();
+ mNameCommitCb = {};
+
+ if (!mHelperHandle.isDead())
+ {
+ mHelperHandle.get()->closeFloater();
+ }
+
+ if (host_ctrl)
+ {
+ mHostHandle = host_ctrl->getHandle();
+ mHostCtrlFocusLostConn = host_ctrl->setFocusLostCallback(std::bind([&]() { hideHelper(getHostCtrl()); }));
+ }
+ }
+}
diff --git a/indra/llui/llchatmentionhelper.h b/indra/llui/llchatmentionhelper.h
new file mode 100644
index 0000000000..5f95d06f31
--- /dev/null
+++ b/indra/llui/llchatmentionhelper.h
@@ -0,0 +1,66 @@
+/**
+* @file llchatmentionhelper.h
+* @brief Header file for LLChatMentionHelper
+*
+* $LicenseInfo:firstyear=2025&license=viewerlgpl$
+* Second Life Viewer Source Code
+* Copyright (C) 2025, Linden Research, Inc.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation;
+* version 2.1 of the License only.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*
+* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+* $/LicenseInfo$
+*/
+
+#pragma once
+
+#include "llhandle.h"
+#include "llsingleton.h"
+
+#include <boost/signals2.hpp>
+
+class LLFloater;
+class LLUICtrl;
+
+class LLChatMentionHelper : public LLSingleton<LLChatMentionHelper>
+{
+ LLSINGLETON(LLChatMentionHelper) {}
+ ~LLChatMentionHelper() override {}
+
+public:
+
+ bool isActive(const LLUICtrl* ctrl) const;
+ bool isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos = nullptr) const;
+ void showHelper(LLUICtrl* host_ctrl, S32 local_x, S32 local_y, const std::string& av_name, std::function<void(std::string)> commit_cb);
+ void hideHelper(const LLUICtrl* ctrl = nullptr);
+
+ bool handleKey(const LLUICtrl* ctrl, KEY key, MASK mask);
+ void onCommitName(std::string name_url);
+
+ void updateAvatarList(std::vector<std::string> av_names) { mAvatarNames = av_names; }
+
+protected:
+ void setHostCtrl(LLUICtrl* host_ctrl);
+ LLUICtrl* getHostCtrl() const { return mHostHandle.get(); }
+
+private:
+ LLHandle<LLUICtrl> mHostHandle;
+ LLHandle<LLFloater> mHelperHandle;
+ boost::signals2::connection mHostCtrlFocusLostConn;
+ boost::signals2::connection mHelperCommitConn;
+ std::function<void(std::string)> mNameCommitCb;
+
+ std::vector<std::string> mAvatarNames;
+};
diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp
index b8c833f4fd..8178bada42 100644
--- a/indra/llui/llflatlistview.cpp
+++ b/indra/llui/llflatlistview.cpp
@@ -459,6 +459,7 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p)
, mNoItemsCommentTextbox(NULL)
, mIsConsecutiveSelection(false)
, mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape)
+ , mFocusOnItemClicked(true)
{
mBorderThickness = getBorderWidth();
@@ -610,7 +611,10 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
return;
}
- setFocus(true);
+ if (mFocusOnItemClicked)
+ {
+ setFocus(true);
+ }
bool select_item = !isSelected(item_pair);
diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h
index 0ea3115f30..6271231183 100644
--- a/indra/llui/llflatlistview.h
+++ b/indra/llui/llflatlistview.h
@@ -299,6 +299,8 @@ public:
virtual S32 notify(const LLSD& info) override;
+ void setFocusOnItemClicked(bool b) { mFocusOnItemClicked = b; }
+
virtual ~LLFlatListView();
protected:
@@ -423,6 +425,8 @@ private:
bool mKeepSelectionVisibleOnReshape;
+ bool mFocusOnItemClicked;
+
/** All pairs of the list */
pairs_list_t mItemPairs;
diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp
index 4714665e8b..7a0e620d61 100644
--- a/indra/llui/llstyle.cpp
+++ b/indra/llui/llstyle.cpp
@@ -38,11 +38,13 @@ LLStyle::Params::Params()
color("color", LLColor4::black),
readonly_color("readonly_color", LLColor4::black),
selected_color("selected_color", LLColor4::black),
+ highlight_bg_color("highlight_bg_color", LLColor4::green),
alpha("alpha", 1.f),
font("font", LLStyle::getDefaultFont()),
image("image"),
link_href("href"),
- is_link("is_link")
+ is_link("is_link"),
+ draw_highlight_bg("draw_highlight_bg", false)
{}
@@ -51,12 +53,14 @@ LLStyle::LLStyle(const LLStyle::Params& p)
mColor(p.color),
mReadOnlyColor(p.readonly_color),
mSelectedColor(p.selected_color),
+ mHighlightBgColor(p.highlight_bg_color),
mFont(p.font()),
mLink(p.link_href),
mIsLink(p.is_link.isProvided() ? p.is_link : !p.link_href().empty()),
mDropShadow(p.drop_shadow),
mImagep(p.image()),
- mAlpha(p.alpha)
+ mAlpha(p.alpha),
+ mDrawHighlightBg(p.draw_highlight_bg)
{}
void LLStyle::setFont(const LLFontGL* font)
diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h
index 0c78fe5a9f..71c3f88109 100644
--- a/indra/llui/llstyle.h
+++ b/indra/llui/llstyle.h
@@ -43,15 +43,25 @@ public:
Optional<LLFontGL::ShadowType> drop_shadow;
Optional<LLUIColor> color,
readonly_color,
- selected_color;
+ selected_color,
+ highlight_bg_color;
Optional<F32> alpha;
Optional<const LLFontGL*> font;
Optional<LLUIImage*> image;
Optional<std::string> link_href;
Optional<bool> is_link;
+ Optional<bool> draw_highlight_bg;
Params();
};
LLStyle(const Params& p = Params());
+
+ enum EUnderlineLink
+ {
+ UNDERLINE_ALWAYS = 0,
+ UNDERLINE_ON_HOVER,
+ UNDERLINE_NEVER
+ };
+
public:
const LLUIColor& getColor() const { return mColor; }
void setColor(const LLUIColor &color) { mColor = color; }
@@ -84,6 +94,9 @@ public:
bool isImage() const { return mImagep.notNull(); }
+ bool getDrawHighlightBg() const { return mDrawHighlightBg; }
+ const LLUIColor& getHighlightBgColor() const { return mHighlightBgColor; }
+
bool operator==(const LLStyle &rhs) const
{
return
@@ -91,11 +104,13 @@ public:
&& mColor == rhs.mColor
&& mReadOnlyColor == rhs.mReadOnlyColor
&& mSelectedColor == rhs.mSelectedColor
+ && mHighlightBgColor == rhs.mHighlightBgColor
&& mFont == rhs.mFont
&& mLink == rhs.mLink
&& mImagep == rhs.mImagep
&& mDropShadow == rhs.mDropShadow
- && mAlpha == rhs.mAlpha;
+ && mAlpha == rhs.mAlpha
+ && mDrawHighlightBg == rhs.mDrawHighlightBg;
}
bool operator!=(const LLStyle& rhs) const { return !(*this == rhs); }
@@ -112,11 +127,13 @@ private:
LLUIColor mColor;
LLUIColor mReadOnlyColor;
LLUIColor mSelectedColor;
+ LLUIColor mHighlightBgColor;
const LLFontGL* mFont;
LLPointer<LLUIImage> mImagep;
F32 mAlpha;
bool mVisible;
bool mIsLink;
+ bool mDrawHighlightBg;
};
typedef LLPointer<LLStyle> LLStyleSP;
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index 41e7094163..c1b01f420b 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -460,6 +460,62 @@ std::vector<LLRect> LLTextBase::getSelectionRects()
return selection_rects;
}
+std::vector<std::pair<LLRect, LLUIColor>> LLTextBase::getHighlightedBgRects()
+{
+ std::vector<std::pair<LLRect, LLUIColor>> highlight_rects;
+
+ LLRect content_display_rect = getVisibleDocumentRect();
+
+ // binary search for line that starts before top of visible buffer
+ line_list_t::const_iterator line_iter =
+ std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom());
+ line_list_t::const_iterator end_iter =
+ std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top());
+
+ for (; line_iter != end_iter; ++line_iter)
+ {
+ segment_set_t::iterator segment_iter;
+ S32 segment_offset;
+ getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset);
+
+ // Use F32 otherwise a string of multiple segments
+ // will accumulate a large error
+ F32 left_precise = (F32)line_iter->mRect.mLeft;
+ F32 right_precise = (F32)line_iter->mRect.mLeft;
+
+ for (; segment_iter != mSegments.end(); ++segment_iter)
+ {
+ LLTextSegmentPtr segmentp = *segment_iter;
+
+ S32 segment_line_start = segmentp->getStart() + segment_offset;
+ S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd);
+
+ if (segment_line_start > segment_line_end)
+ break;
+
+ F32 segment_width = 0;
+ S32 segment_height = 0;
+
+ S32 num_chars = segment_line_end - segment_line_start;
+ segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height);
+ right_precise += segment_width;
+
+ if (segmentp->getStyle()->getDrawHighlightBg())
+ {
+ LLRect selection_rect;
+ selection_rect.mLeft = (S32)left_precise;
+ selection_rect.mRight = (S32)right_precise;
+ selection_rect.mBottom = line_iter->mRect.mBottom;
+ selection_rect.mTop = line_iter->mRect.mTop;
+
+ highlight_rects.push_back(std::pair(selection_rect, segmentp->getStyle()->getHighlightBgColor()));
+ }
+ left_precise += segment_width;
+ }
+ }
+ return highlight_rects;
+}
+
// Draws the black box behind the selected text
void LLTextBase::drawSelectionBackground()
{
@@ -529,6 +585,71 @@ void LLTextBase::drawSelectionBackground()
}
}
+void LLTextBase::drawHighlightedBackground()
+{
+ if (!mLineInfoList.empty())
+ {
+ std::vector<std::pair<LLRect, LLUIColor>> highlight_rects = getHighlightedBgRects();
+
+ if (highlight_rects.empty())
+ return;
+
+ gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
+
+ LLRect content_display_rect = getVisibleDocumentRect();
+
+ for (std::vector<std::pair<LLRect, LLUIColor>>::iterator rect_it = highlight_rects.begin();
+ rect_it != highlight_rects.end(); ++rect_it)
+ {
+ LLRect selection_rect = rect_it->first;
+ const LLColor4& color = rect_it->second;
+ if (mScroller)
+ {
+ // If scroller is On content_display_rect has correct rect and safe to use as is
+ // Note: we might need to account for border
+ selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom);
+ }
+ else
+ {
+ // If scroller is Off content_display_rect will have rect from document, adjusted to text width, heigh and position
+ // and we have to acount for offset depending on position
+ S32 v_delta = 0;
+ S32 h_delta = 0;
+ switch (mVAlign)
+ {
+ case LLFontGL::TOP:
+ v_delta = mVisibleTextRect.mTop - content_display_rect.mTop - mVPad;
+ break;
+ case LLFontGL::VCENTER:
+ v_delta = (llmax(mVisibleTextRect.getHeight() - content_display_rect.mTop, -content_display_rect.mBottom) + (mVisibleTextRect.mBottom - content_display_rect.mBottom)) / 2;
+ break;
+ case LLFontGL::BOTTOM:
+ v_delta = mVisibleTextRect.mBottom - content_display_rect.mBottom;
+ break;
+ default:
+ break;
+ }
+ switch (mHAlign)
+ {
+ case LLFontGL::LEFT:
+ h_delta = mVisibleTextRect.mLeft - content_display_rect.mLeft + mHPad;
+ break;
+ case LLFontGL::HCENTER:
+ h_delta = (llmax(mVisibleTextRect.getWidth() - content_display_rect.mLeft, -content_display_rect.mRight) + (mVisibleTextRect.mRight - content_display_rect.mRight)) / 2;
+ break;
+ case LLFontGL::RIGHT:
+ h_delta = mVisibleTextRect.mRight - content_display_rect.mRight;
+ break;
+ default:
+ break;
+ }
+ selection_rect.translate(h_delta, v_delta);
+ }
+ gl_rect_2d(selection_rect, color);
+ }
+ }
+}
+
void LLTextBase::drawCursor()
{
F32 alpha = getDrawContext().mAlpha;
@@ -1399,6 +1520,7 @@ void LLTextBase::draw()
drawChild(mDocumentView);
}
+ drawHighlightedBackground();
drawSelectionBackground();
drawText();
drawCursor();
@@ -2200,20 +2322,20 @@ static LLUIImagePtr image_from_icon_name(const std::string& icon_name)
}
-void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params)
+void LLTextBase::appendTextImpl(const std::string& new_text, const LLStyle::Params& input_params, bool force_slurl)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;
LLStyle::Params style_params(getStyleParams());
style_params.overwriteFrom(input_params);
S32 part = (S32)LLTextParser::WHOLE;
- if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).
+ if ((mParseHTML || force_slurl) && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).
{
S32 start=0,end=0;
LLUrlMatch match;
std::string text = new_text;
while (LLUrlRegistry::instance().findUrl(text, match,
- boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons))
+ boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons, force_slurl))
{
start = match.getStart();
end = match.getEnd()+1;
@@ -2245,7 +2367,7 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
}
// output the styled Url
- appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly());
+ appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.getUnderline());
bool tooltip_required = !match.getTooltip().empty();
// set the tooltip for the Url label
@@ -2260,7 +2382,7 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
{
link_params.color = LLColor4::grey;
link_params.readonly_color = LLColor4::grey;
- appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly());
+ appendAndHighlightTextImpl(label, part, link_params, match.getUnderline());
// set the tooltip for the query part of url
if (tooltip_required)
@@ -2428,7 +2550,7 @@ void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const s
insertStringNoUndo(getLength(), widget_wide_text, &segments);
}
-void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
+void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link)
{
// Save old state
S32 selection_start = mSelectionStart;
@@ -2458,7 +2580,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
S32 cur_length = getLength();
LLStyleConstSP sp(new LLStyle(highlight_params));
LLTextSegmentPtr segmentp;
- if (underline_on_hover_only || mSkipLinkUnderline)
+ if ((underline_link == e_underline::UNDERLINE_ON_HOVER) || mSkipLinkUnderline)
{
highlight_params.font.style("NORMAL");
LLStyleConstSP normal_sp(new LLStyle(highlight_params));
@@ -2482,7 +2604,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
S32 segment_start = old_length;
S32 segment_end = old_length + static_cast<S32>(wide_text.size());
LLStyleConstSP sp(new LLStyle(style_params));
- if (underline_on_hover_only || mSkipLinkUnderline)
+ if ((underline_link == e_underline::UNDERLINE_ON_HOVER) || mSkipLinkUnderline)
{
LLStyle::Params normal_style_params(style_params);
normal_style_params.font.style("NORMAL");
@@ -2516,7 +2638,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig
}
}
-void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only)
+void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link)
{
if (new_text.empty())
{
@@ -2531,7 +2653,7 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlig
if (pos != start)
{
std::string str = std::string(new_text,start,pos-start);
- appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
+ appendAndHighlightTextImpl(str, highlight_part, style_params, underline_link);
}
appendLineBreakSegment(style_params);
start = pos+1;
@@ -2539,7 +2661,7 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlig
}
std::string str = std::string(new_text, start, new_text.length() - start);
- appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only);
+ appendAndHighlightTextImpl(str, highlight_part, style_params, underline_link);
}
@@ -3336,6 +3458,7 @@ LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 e
mLastGeneration(-1)
{
mFontHeight = mStyle->getFont()->getLineHeight();
+ mCanEdit = !mStyle->getDrawHighlightBg();
LLUIImagePtr image = mStyle->getImage();
if (image.notNull())
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index e62b56963d..8ca653acb9 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -35,6 +35,7 @@
#include "llstyle.h"
#include "llkeywords.h"
#include "llpanel.h"
+#include "llurlmatch.h"
#include <string>
#include <vector>
@@ -139,7 +140,7 @@ public:
/*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const;
/*virtual*/ void updateLayout(const class LLTextBase& editor);
/*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect);
- /*virtual*/ bool canEdit() const { return true; }
+ /*virtual*/ bool canEdit() const { return mCanEdit; }
/*virtual*/ const LLUIColor& getColor() const { return mStyle->getColor(); }
/*virtual*/ LLStyleConstSP getStyle() const { return mStyle; }
/*virtual*/ void setStyle(LLStyleConstSP style) { mStyle = style; }
@@ -161,6 +162,8 @@ protected:
virtual const LLWString& getWText() const;
virtual const S32 getLength() const;
+ void setAllowEdit(bool can_edit) { mCanEdit = can_edit; }
+
protected:
class LLTextBase& mEditor;
LLStyleConstSP mStyle;
@@ -169,6 +172,8 @@ protected:
std::string mTooltip;
boost::signals2::connection mImageLoadedConnection;
+ bool mCanEdit { true };
+
// font rendering
LLFontVertexBuffer mFontBufferPreSelection;
LLFontVertexBuffer mFontBufferSelection;
@@ -606,6 +611,7 @@ protected:
bool operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const;
};
typedef std::multiset<LLTextSegmentPtr, compare_segment_end> segment_set_t;
+ typedef LLStyle::EUnderlineLink e_underline;
// member functions
LLTextBase(const Params &p);
@@ -619,12 +625,13 @@ protected:
virtual void drawSelectionBackground(); // draws the black box behind the selected text
void drawCursor();
void drawText();
+ void drawHighlightedBackground();
// modify contents
S32 insertStringNoUndo(S32 pos, const LLWString &wstr, segment_vec_t* segments = NULL); // returns num of chars actually inserted
S32 removeStringNoUndo(S32 pos, S32 length);
S32 overwriteCharNoUndo(S32 pos, llwchar wc);
- void appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& stylep, bool underline_on_hover_only = false);
+ void appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& stylep, e_underline underline_link = e_underline::UNDERLINE_ALWAYS);
// manage segments
@@ -672,8 +679,9 @@ protected:
// avatar names are looked up.
void replaceUrl(const std::string &url, const std::string &label, const std::string& icon);
- void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params());
- void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only = false);
+ void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params(), bool force_slurl = false);
+ void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link = e_underline::UNDERLINE_ALWAYS);
+ S32 normalizeUri(std::string& uri);
protected:
// virtual
@@ -683,6 +691,7 @@ protected:
}
std::vector<LLRect> getSelectionRects();
+ std::vector<std::pair<LLRect, LLUIColor>> getHighlightedBgRects();
protected:
// text segmentation and flow
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index fe4cce29ab..cfe729be06 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -60,6 +60,7 @@
#include "llurlregistry.h"
#include "lltooltip.h"
#include "llmenugl.h"
+#include "llchatmentionhelper.h"
#include <queue>
#include "llcombobox.h"
@@ -270,6 +271,7 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) :
mPrevalidator(p.prevalidator()),
mShowContextMenu(p.show_context_menu),
mShowEmojiHelper(p.show_emoji_helper),
+ mShowChatMentionPicker(false),
mEnableTooltipPaste(p.enable_tooltip_paste),
mPassDelete(false),
mKeepSelectionOnReturn(false)
@@ -714,6 +716,30 @@ void LLTextEditor::handleEmojiCommit(llwchar emoji)
}
}
+void LLTextEditor::handleMentionCommit(std::string name_url)
+{
+ S32 mention_start_pos;
+ if (LLChatMentionHelper::instance().isCursorInNameMention(getWText(), mCursorPos, &mention_start_pos))
+ {
+ remove(mention_start_pos, mCursorPos - mention_start_pos, true);
+ insert(mention_start_pos, utf8str_to_wstring(name_url), false, LLTextSegmentPtr());
+
+ std::string new_text(wstring_to_utf8str(getConvertedText()));
+ clear();
+ appendTextImpl(new_text, LLStyle::Params(), true);
+
+ segment_set_t::const_iterator it = getSegIterContaining(mention_start_pos);
+ if (it != mSegments.end())
+ {
+ setCursorPos((*it)->getEnd() + 1);
+ }
+ else
+ {
+ setCursorPos(mention_start_pos);
+ }
+ }
+}
+
bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
{
bool handled = false;
@@ -1103,6 +1129,7 @@ void LLTextEditor::removeCharOrTab()
}
tryToShowEmojiHelper();
+ tryToShowMentionHelper();
}
else
{
@@ -1128,6 +1155,7 @@ void LLTextEditor::removeChar()
setCursorPos(mCursorPos - 1);
removeChar(mCursorPos);
tryToShowEmojiHelper();
+ tryToShowMentionHelper();
}
else
{
@@ -1189,6 +1217,7 @@ void LLTextEditor::addChar(llwchar wc)
setCursorPos(mCursorPos + addChar( mCursorPos, wc ));
tryToShowEmojiHelper();
+ tryToShowMentionHelper();
if (!mReadOnly && mAutoreplaceCallback != NULL)
{
@@ -1247,6 +1276,31 @@ void LLTextEditor::tryToShowEmojiHelper()
}
}
+void LLTextEditor::tryToShowMentionHelper()
+{
+ if (mReadOnly || !mShowChatMentionPicker)
+ return;
+
+ S32 mention_start_pos;
+ LLWString text(getWText());
+ if (LLChatMentionHelper::instance().isCursorInNameMention(text, mCursorPos, &mention_start_pos))
+ {
+ const LLRect cursor_rect(getLocalRectFromDocIndex(mention_start_pos));
+ std::string name_part(wstring_to_utf8str(text.substr(mention_start_pos, mCursorPos - mention_start_pos)));
+ name_part.erase(0, 1);
+ auto cb = [this](std::string name_url)
+ {
+ handleMentionCommit(name_url);
+ };
+ LLChatMentionHelper::instance().showHelper(this, cursor_rect.mLeft, cursor_rect.mTop, name_part, cb);
+ }
+ else
+ {
+ LLChatMentionHelper::instance().hideHelper();
+ }
+}
+
+
void LLTextEditor::addLineBreakChar(bool group_together)
{
if( !getEnabled() )
@@ -1873,7 +1927,7 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask )
// not handled and let the parent take care of field movement.
if (KEY_TAB == key && mTabsToNextField)
{
- return false;
+ return mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask);
}
if (mReadOnly && mScroller)
@@ -1884,9 +1938,13 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask )
}
else
{
- if (!mReadOnly && mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask))
+ if (!mReadOnly)
{
- return true;
+ if ((mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) ||
+ (mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask)))
+ {
+ return true;
+ }
}
if (mEnableTooltipPaste &&
@@ -3083,3 +3141,21 @@ S32 LLTextEditor::spacesPerTab()
{
return SPACES_PER_TAB;
}
+
+LLWString LLTextEditor::getConvertedText() const
+{
+ LLWString text = getWText();
+ S32 diff = 0;
+ for (auto segment : mSegments)
+ {
+ if (segment && segment->getStyle() && segment->getStyle()->getDrawHighlightBg())
+ {
+ S32 seg_length = segment->getEnd() - segment->getStart();
+ std::string slurl = segment->getStyle()->getLinkHREF();
+
+ text.replace(segment->getStart() + diff, seg_length, utf8str_to_wstring(slurl));
+ diff += (S32)slurl.size() - seg_length;
+ }
+ }
+ return text;
+}
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index 353a7b93a0..882bb145df 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -95,6 +95,8 @@ public:
void insertEmoji(llwchar emoji);
void handleEmojiCommit(llwchar emoji);
+ void handleMentionCommit(std::string name_url);
+
// mousehandler overrides
virtual bool handleMouseDown(S32 x, S32 y, MASK mask);
virtual bool handleMouseUp(S32 x, S32 y, MASK mask);
@@ -212,6 +214,8 @@ public:
void setPassDelete(bool b) { mPassDelete = b; }
+ LLWString getConvertedText() const;
+
protected:
void showContextMenu(S32 x, S32 y);
void drawPreeditMarker();
@@ -254,6 +258,7 @@ protected:
S32 remove(S32 pos, S32 length, bool group_with_next_op);
void tryToShowEmojiHelper();
+ void tryToShowMentionHelper();
void focusLostHelper();
void updateAllowingLanguageInput();
bool hasPreeditString() const;
@@ -291,6 +296,7 @@ protected:
bool mAutoIndent;
bool mParseOnTheFly;
+ bool mShowChatMentionPicker;
void updateLinkSegments();
void keepSelectionOnReturn(bool keep) { mKeepSelectionOnReturn = keep; }
diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp
index 77f132e9d8..34138da34d 100644
--- a/indra/llui/llurlentry.cpp
+++ b/indra/llui/llurlentry.cpp
@@ -29,7 +29,6 @@
#include "llurlentry.h"
#include "lluictrl.h"
#include "lluri.h"
-#include "llurlmatch.h"
#include "llurlregistry.h"
#include "lluriparser.h"
@@ -48,7 +47,7 @@
// Utility functions
std::string localize_slapp_label(const std::string& url, const std::string& full_name);
-
+LLUUID LLUrlEntryBase::sAgentID(LLUUID::null);
LLUrlEntryBase::LLUrlEntryBase()
{
}
@@ -68,7 +67,7 @@ std::string LLUrlEntryBase::getIcon(const std::string &url)
return mIcon;
}
-LLStyle::Params LLUrlEntryBase::getStyle() const
+LLStyle::Params LLUrlEntryBase::getStyle(const std::string &url) const
{
LLStyle::Params style_params;
style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor");
@@ -667,10 +666,14 @@ std::string LLUrlEntryAgent::getTooltip(const std::string &string) const
return LLTrans::getString("TooltipAgentUrl");
}
-bool LLUrlEntryAgent::underlineOnHoverOnly(const std::string &string) const
+LLStyle::EUnderlineLink LLUrlEntryAgent::getUnderline(const std::string& string) const
{
std::string url = getUrl(string);
- return LLStringUtil::endsWith(url, "/about") || LLStringUtil::endsWith(url, "/inspect");
+ if (LLStringUtil::endsWith(url, "/about") || LLStringUtil::endsWith(url, "/inspect"))
+ {
+ return LLStyle::EUnderlineLink::UNDERLINE_ON_HOVER;
+ }
+ return LLStyle::EUnderlineLink::UNDERLINE_ALWAYS;
}
std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
@@ -712,11 +715,12 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa
}
}
-LLStyle::Params LLUrlEntryAgent::getStyle() const
+LLStyle::Params LLUrlEntryAgent::getStyle(const std::string &url) const
{
- LLStyle::Params style_params = LLUrlEntryBase::getStyle();
+ LLStyle::Params style_params = LLUrlEntryBase::getStyle(url);
style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor");
style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor");
+
return style_params;
}
@@ -751,6 +755,10 @@ std::string localize_slapp_label(const std::string& url, const std::string& full
{
return LLTrans::getString("SLappAgentRemoveFriend") + " " + full_name;
}
+ if (LLStringUtil::endsWith(url, "/mention"))
+ {
+ return "@" + full_name;
+ }
return full_name;
}
@@ -762,6 +770,34 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url)
return mIcon;
}
+///
+/// LLUrlEntryAgentMention Describes a chat mention Url, e.g.,
+/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/mention
+///
+LLUrlEntryAgentMention::LLUrlEntryAgentMention()
+{
+ mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/mention", boost::regex::perl | boost::regex::icase);
+ mMenuName = "menu_url_agent.xml";
+ mIcon = std::string();
+}
+
+LLStyle::EUnderlineLink LLUrlEntryAgentMention::getUnderline(const std::string& string) const
+{
+ return LLStyle::EUnderlineLink::UNDERLINE_NEVER;
+}
+
+LLStyle::Params LLUrlEntryAgentMention::getStyle(const std::string& url) const
+{
+ LLStyle::Params style_params = LLUrlEntryAgent::getStyle(url);
+ style_params.font.style = "NORMAL";
+ style_params.draw_highlight_bg = true;
+
+ LLUUID agent_id(getIDStringFromUrl(url));
+ style_params.highlight_bg_color = LLUIColorTable::instance().getColor((agent_id == sAgentID) ? "ChatSelfMentionHighlight" : "ChatMentionHighlight");
+
+ return style_params;
+}
+
//
// LLUrlEntryAgentName describes a Second Life agent name Url, e.g.,
// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username)
@@ -823,7 +859,7 @@ std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLab
}
}
-LLStyle::Params LLUrlEntryAgentName::getStyle() const
+LLStyle::Params LLUrlEntryAgentName::getStyle(const std::string &url) const
{
// don't override default colors
return LLStyle::Params().is_link(false);
@@ -959,9 +995,9 @@ std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCa
}
}
-LLStyle::Params LLUrlEntryGroup::getStyle() const
+LLStyle::Params LLUrlEntryGroup::getStyle(const std::string &url) const
{
- LLStyle::Params style_params = LLUrlEntryBase::getStyle();
+ LLStyle::Params style_params = LLUrlEntryBase::getStyle(url);
style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor");
style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor");
return style_params;
@@ -1037,7 +1073,6 @@ std::string LLUrlEntryChat::getLabel(const std::string &url, const LLUrlLabelCal
}
// LLUrlEntryParcel statics.
-LLUUID LLUrlEntryParcel::sAgentID(LLUUID::null);
LLUUID LLUrlEntryParcel::sSessionID(LLUUID::null);
LLHost LLUrlEntryParcel::sRegionHost;
bool LLUrlEntryParcel::sDisconnected(false);
@@ -1371,17 +1406,17 @@ std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const
return LLUrlEntryBase::getTooltip(string);
}
-bool LLUrlEntrySLLabel::underlineOnHoverOnly(const std::string &string) const
+LLStyle::EUnderlineLink LLUrlEntrySLLabel::getUnderline(const std::string& string) const
{
std::string url = getUrl(string);
- LLUrlMatch match;
+ LLUrlMatch match;
if (LLUrlRegistry::instance().findUrl(url, match))
{
- return match.underlineOnHoverOnly();
+ return match.getUnderline();
}
// unrecognized URL? should not happen
- return LLUrlEntryBase::underlineOnHoverOnly(string);
+ return LLUrlEntryBase::getUnderline(string);
}
//
@@ -1445,7 +1480,7 @@ std::string LLUrlEntryNoLink::getLabel(const std::string &url, const LLUrlLabelC
return getUrl(url);
}
-LLStyle::Params LLUrlEntryNoLink::getStyle() const
+LLStyle::Params LLUrlEntryNoLink::getStyle(const std::string &url) const
{
// Don't render as URL (i.e. no context menu or hand cursor).
return LLStyle::Params().is_link(false);
diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h
index fffee88496..740e99acfd 100644
--- a/indra/llui/llurlentry.h
+++ b/indra/llui/llurlentry.h
@@ -85,7 +85,7 @@ public:
virtual std::string getIcon(const std::string &url);
/// Return the style to render the displayed text
- virtual LLStyle::Params getStyle() const;
+ virtual LLStyle::Params getStyle(const std::string &url) const;
/// Given a matched Url, return a tooltip string for the hyperlink
virtual std::string getTooltip(const std::string &string) const { return mTooltip; }
@@ -96,11 +96,12 @@ public:
/// Return the name of a SL location described by this Url, if any
virtual std::string getLocation(const std::string &url) const { return ""; }
- /// Should this link text be underlined only when mouse is hovered over it?
- virtual bool underlineOnHoverOnly(const std::string &string) const { return false; }
+ virtual LLStyle::EUnderlineLink getUnderline(const std::string& string) const { return LLStyle::EUnderlineLink::UNDERLINE_ALWAYS; }
virtual bool isTrusted() const { return false; }
+ virtual bool getSkipProfileIcon(const std::string& string) const { return false; }
+
virtual LLUUID getID(const std::string &string) const { return LLUUID::null; }
bool isLinkDisabled() const;
@@ -109,6 +110,8 @@ public:
virtual bool isSLURLvalid(const std::string &url) const { return true; };
+ static void setAgentID(const LLUUID& id) { sAgentID = id; }
+
protected:
std::string getIDStringFromUrl(const std::string &url) const;
std::string escapeUrl(const std::string &url) const;
@@ -130,6 +133,8 @@ protected:
std::string mMenuName;
std::string mTooltip;
std::multimap<std::string, LLUrlEntryObserver> mObservers;
+
+ static LLUUID sAgentID;
};
///
@@ -224,9 +229,11 @@ public:
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
/*virtual*/ std::string getIcon(const std::string &url);
/*virtual*/ std::string getTooltip(const std::string &string) const;
- /*virtual*/ LLStyle::Params getStyle() const;
+ /*virtual*/ LLStyle::Params getStyle(const std::string &url) const;
/*virtual*/ LLUUID getID(const std::string &string) const;
- /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const;
+
+ LLStyle::EUnderlineLink getUnderline(const std::string& string) const;
+
protected:
/*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon);
private:
@@ -237,6 +244,19 @@ private:
};
///
+/// LLUrlEntryAgentMention Describes a chat mention Url, e.g.,
+/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/mention
+class LLUrlEntryAgentMention : public LLUrlEntryAgent
+{
+public:
+ LLUrlEntryAgentMention();
+
+ LLStyle::Params getStyle(const std::string& url) const;
+ LLStyle::EUnderlineLink getUnderline(const std::string& string) const;
+ bool getSkipProfileIcon(const std::string& string) const { return true; };
+};
+
+///
/// LLUrlEntryAgentName Describes a Second Life agent name Url, e.g.,
/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username)
/// that displays various forms of user name
@@ -257,7 +277,7 @@ public:
mAvatarNameCacheConnections.clear();
}
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
- /*virtual*/ LLStyle::Params getStyle() const;
+ /*virtual*/ LLStyle::Params getStyle(const std::string &url) const;
protected:
// override this to pull out relevant name fields
virtual std::string getName(const LLAvatarName& avatar_name) = 0;
@@ -339,7 +359,7 @@ class LLUrlEntryGroup : public LLUrlEntryBase
public:
LLUrlEntryGroup();
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
- /*virtual*/ LLStyle::Params getStyle() const;
+ /*virtual*/ LLStyle::Params getStyle(const std::string &url) const;
/*virtual*/ LLUUID getID(const std::string &string) const;
private:
void onGroupNameReceived(const LLUUID& id, const std::string& name, bool is_group);
@@ -411,17 +431,15 @@ public:
// Processes parcel label and triggers notifying observers.
static void processParcelInfo(const LLParcelData& parcel_data);
- // Next 4 setters are used to update agent and viewer connection information
+ // Next setters are used to update agent and viewer connection information
// upon events like user login, viewer disconnect and user changing region host.
// These setters are made public to be accessible from newview and should not be
// used in other cases.
- static void setAgentID(const LLUUID& id) { sAgentID = id; }
static void setSessionID(const LLUUID& id) { sSessionID = id; }
static void setRegionHost(const LLHost& host) { sRegionHost = host; }
static void setDisconnected(bool disconnected) { sDisconnected = disconnected; }
private:
- static LLUUID sAgentID;
static LLUUID sSessionID;
static LLHost sRegionHost;
static bool sDisconnected;
@@ -486,7 +504,7 @@ public:
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
/*virtual*/ std::string getUrl(const std::string &string) const;
/*virtual*/ std::string getTooltip(const std::string &string) const;
- /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const;
+ LLStyle::EUnderlineLink getUnderline(const std::string& string) const;
};
///
@@ -510,7 +528,7 @@ public:
LLUrlEntryNoLink();
/*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
/*virtual*/ std::string getUrl(const std::string &string) const;
- /*virtual*/ LLStyle::Params getStyle() const;
+ /*virtual*/ LLStyle::Params getStyle(const std::string &url) const;
};
///
diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp
index bfa3b167b1..f093934ca9 100644
--- a/indra/llui/llurlmatch.cpp
+++ b/indra/llui/llurlmatch.cpp
@@ -37,8 +37,9 @@ LLUrlMatch::LLUrlMatch() :
mIcon(""),
mMenuName(""),
mLocation(""),
- mUnderlineOnHoverOnly(false),
- mTrusted(false)
+ mUnderline(e_underline::UNDERLINE_ALWAYS),
+ mTrusted(false),
+ mSkipProfileIcon(false)
{
}
@@ -46,7 +47,7 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std
const std::string& query, const std::string &tooltip,
const std::string &icon, const LLStyle::Params& style,
const std::string &menu, const std::string &location,
- const LLUUID& id, bool underline_on_hover_only, bool trusted)
+ const LLUUID& id, e_underline underline, bool trusted, bool skip_icon)
{
mStart = start;
mEnd = end;
@@ -60,6 +61,7 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std
mMenuName = menu;
mLocation = location;
mID = id;
- mUnderlineOnHoverOnly = underline_on_hover_only;
+ mUnderline = underline;
mTrusted = trusted;
+ mSkipProfileIcon = skip_icon;
}
diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h
index 887796bb37..418a21f963 100644
--- a/indra/llui/llurlmatch.h
+++ b/indra/llui/llurlmatch.h
@@ -79,18 +79,20 @@ public:
/// return the SL location that this Url describes, or "" if none.
std::string getLocation() const { return mLocation; }
- /// Should this link text be underlined only when mouse is hovered over it?
- bool underlineOnHoverOnly() const { return mUnderlineOnHoverOnly; }
+ typedef LLStyle::EUnderlineLink e_underline;
+ e_underline getUnderline() const { return mUnderline; }
/// Return true if Url is trusted.
bool isTrusted() const { return mTrusted; }
+ bool getSkipProfileIcon() const { return mSkipProfileIcon; }
+
/// Change the contents of this match object (used by LLUrlRegistry)
void setValues(U32 start, U32 end, const std::string &url, const std::string &label,
const std::string& query, const std::string &tooltip, const std::string &icon,
const LLStyle::Params& style, const std::string &menu,
const std::string &location, const LLUUID& id,
- bool underline_on_hover_only = false, bool trusted = false);
+ e_underline underline = e_underline::UNDERLINE_ALWAYS, bool trusted = false, bool skip_icon = false);
const LLUUID& getID() const { return mID; }
private:
@@ -105,8 +107,9 @@ private:
std::string mLocation;
LLUUID mID;
LLStyle::Params mStyle;
- bool mUnderlineOnHoverOnly;
+ e_underline mUnderline;
bool mTrusted;
+ bool mSkipProfileIcon;
};
#endif
diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp
index cec1ddfc57..02d88c83fb 100644
--- a/indra/llui/llurlregistry.cpp
+++ b/indra/llui/llurlregistry.cpp
@@ -62,6 +62,8 @@ LLUrlRegistry::LLUrlRegistry()
registerUrl(new LLUrlEntryAgentUserName());
// LLUrlEntryAgent*Name must appear before LLUrlEntryAgent since
// LLUrlEntryAgent is a less specific (catchall for agent urls)
+ mUrlEntryAgentMention = new LLUrlEntryAgentMention();
+ registerUrl(mUrlEntryAgentMention);
registerUrl(new LLUrlEntryAgent());
registerUrl(new LLUrlEntryChat());
registerUrl(new LLUrlEntryGroup());
@@ -155,7 +157,7 @@ static bool stringHasUrl(const std::string &text)
text.find("@") != std::string::npos);
}
-bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted)
+bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted, bool skip_non_mentions)
{
// avoid costly regexes if there is clearly no URL in the text
if (! stringHasUrl(text))
@@ -176,6 +178,11 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL
continue;
}
+ if (skip_non_mentions && (mUrlEntryAgentMention != *it))
+ {
+ continue;
+ }
+
LLUrlEntryBase *url_entry = *it;
U32 start = 0, end = 0;
@@ -233,12 +240,13 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL
match_entry->getQuery(url),
match_entry->getTooltip(url),
match_entry->getIcon(url),
- match_entry->getStyle(),
+ match_entry->getStyle(url),
match_entry->getMenuName(),
match_entry->getLocation(url),
match_entry->getID(url),
- match_entry->underlineOnHoverOnly(url),
- match_entry->isTrusted());
+ match_entry->getUnderline(url),
+ match_entry->isTrusted(),
+ match_entry->getSkipProfileIcon(url));
return true;
}
@@ -274,7 +282,9 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr
match.getMenuName(),
match.getLocation(),
match.getID(),
- match.underlineOnHoverOnly());
+ match.getUnderline(),
+ false,
+ match.getSkipProfileIcon());
return true;
}
return false;
diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h
index c22af0dbc4..b9502f4592 100644
--- a/indra/llui/llurlregistry.h
+++ b/indra/llui/llurlregistry.h
@@ -75,7 +75,7 @@ public:
/// your callback is invoked if the matched Url's label changes in the future
bool findUrl(const std::string &text, LLUrlMatch &match,
const LLUrlLabelCallback &cb = &LLUrlRegistryNullCallback,
- bool is_content_trusted = false);
+ bool is_content_trusted = false, bool skip_non_mentions = false);
/// a slightly less efficient version of findUrl for wide strings
bool findUrl(const LLWString &text, LLUrlMatch &match,
@@ -101,6 +101,7 @@ private:
LLUrlEntryBase* mUrlEntrySLLabel;
LLUrlEntryBase* mUrlEntryNoLink;
LLUrlEntryBase* mUrlEntryKeybinding;
+ LLUrlEntryBase* mUrlEntryAgentMention;
};
#endif
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 77d9171f3f..473ca60728 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -212,6 +212,7 @@ set(viewer_SOURCE_FILES
llfloatercamera.cpp
llfloatercamerapresets.cpp
llfloaterchangeitemthumbnail.cpp
+ llfloaterchatmentionpicker.cpp
llfloaterchatvoicevolume.cpp
llfloaterclassified.cpp
llfloatercolorpicker.cpp
@@ -887,6 +888,7 @@ set(viewer_HEADER_FILES
llfloaterbuyland.h
llfloatercamerapresets.h
llfloaterchangeitemthumbnail.h
+ llfloaterchatmentionpicker.h
llfloatercamera.h
llfloaterchatvoicevolume.h
llfloaterclassified.h
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index 991d8e5c5f..099f298456 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-7.1.13
+7.1.14
diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp
index bf3fada3bd..9a381f9ba6 100644
--- a/indra/newview/gltfscenemanager.cpp
+++ b/indra/newview/gltfscenemanager.cpp
@@ -643,6 +643,12 @@ void GLTFSceneManager::render(Asset& asset, U8 variant)
return;
}
+ if (gGLTFPBRMetallicRoughnessProgram.mGLTFVariants.size() <= variant)
+ {
+ llassert(false); // mGLTFVariants should have been initialized
+ return;
+ }
+
for (U32 ds = 0; ds < 2; ++ds)
{
RenderData& rd = asset.mRenderData[ds];
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 87b165b739..3cc84666ec 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -485,7 +485,7 @@ static void deferred_ui_audio_callback(const LLUUID& uuid)
bool create_text_segment_icon_from_url_match(LLUrlMatch* match,LLTextBase* base)
{
- if(!match || !base || base->getPlainText())
+ if (!match || match->getSkipProfileIcon() || !base || base->getPlainText())
return false;
LLUUID match_id = match->getID();
diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp
index 52cd86951d..5f9d03ca66 100644
--- a/indra/newview/llavatarlist.cpp
+++ b/indra/newview/llavatarlist.cpp
@@ -147,6 +147,7 @@ LLAvatarList::LLAvatarList(const Params& p)
, mShowSpeakingIndicator(p.show_speaking_indicator)
, mShowPermissions(p.show_permissions_granted)
, mShowCompleteName(false)
+, mForceCompleteName(false)
{
setCommitOnSelectionChange(true);
@@ -183,7 +184,7 @@ void LLAvatarList::setShowIcons(std::string param_name)
std::string LLAvatarList::getAvatarName(LLAvatarName av_name)
{
- return mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName();
+ return mShowCompleteName? av_name.getCompleteName(false, mForceCompleteName) : av_name.getDisplayName();
}
// virtual
@@ -381,7 +382,7 @@ void LLAvatarList::updateAvatarNames()
for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++)
{
LLAvatarListItem* item = static_cast<LLAvatarListItem*>(*it);
- item->setShowCompleteName(mShowCompleteName);
+ item->setShowCompleteName(mShowCompleteName, mForceCompleteName);
item->updateAvatarName();
}
mNeedUpdateNames = false;
@@ -421,6 +422,11 @@ boost::signals2::connection LLAvatarList::setItemDoubleClickCallback(const mouse
return mItemDoubleClickSignal.connect(cb);
}
+boost::signals2::connection LLAvatarList::setItemClickedCallback(const mouse_signal_t::slot_type& cb)
+{
+ return mItemClickedSignal.connect(cb);
+}
+
//virtual
S32 LLAvatarList::notifyParent(const LLSD& info)
{
@@ -435,7 +441,7 @@ S32 LLAvatarList::notifyParent(const LLSD& info)
void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, bool is_online, EAddPosition pos)
{
LLAvatarListItem* item = new LLAvatarListItem();
- item->setShowCompleteName(mShowCompleteName);
+ item->setShowCompleteName(mShowCompleteName, mForceCompleteName);
// This sets the name as a side effect
item->setAvatarId(id, mSessionID, mIgnoreOnlineStatus);
item->setOnline(mIgnoreOnlineStatus ? true : is_online);
@@ -451,6 +457,7 @@ void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, bool is
item->setDoubleClickCallback(boost::bind(&LLAvatarList::onItemDoubleClicked, this, _1, _2, _3, _4));
+ item->setMouseDownCallback(boost::bind(&LLAvatarList::onItemClicked, this, _1, _2, _3, _4));
addItem(item, id, pos);
}
@@ -609,6 +616,11 @@ void LLAvatarList::onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask)
mItemDoubleClickSignal(ctrl, x, y, mask);
}
+void LLAvatarList::onItemClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask)
+{
+ mItemClickedSignal(ctrl, x, y, mask);
+}
+
bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const
{
const LLAvatarListItem* avatar_item1 = dynamic_cast<const LLAvatarListItem*>(item1);
diff --git a/indra/newview/llavatarlist.h b/indra/newview/llavatarlist.h
index 37ad578a20..97b4f05985 100644
--- a/indra/newview/llavatarlist.h
+++ b/indra/newview/llavatarlist.h
@@ -98,11 +98,13 @@ public:
boost::signals2::connection setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb);
+ boost::signals2::connection setItemClickedCallback(const mouse_signal_t::slot_type& cb);
+
virtual S32 notifyParent(const LLSD& info);
void handleDisplayNamesOptionChanged();
- void setShowCompleteName(bool show) { mShowCompleteName = show;};
+ void setShowCompleteName(bool show, bool force = false) { mShowCompleteName = show; mForceCompleteName = force; };
protected:
void refresh();
@@ -117,6 +119,7 @@ protected:
void updateLastInteractionTimes();
void rebuildNames();
void onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask);
+ void onItemClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask);
void updateAvatarNames();
private:
@@ -133,6 +136,7 @@ private:
bool mShowSpeakingIndicator;
bool mShowPermissions;
bool mShowCompleteName;
+ bool mForceCompleteName;
LLTimer* mLITUpdateTimer; // last interaction time update timer
std::string mIconParamName;
@@ -144,6 +148,7 @@ private:
commit_signal_t mRefreshCompleteSignal;
mouse_signal_t mItemDoubleClickSignal;
+ mouse_signal_t mItemClickedSignal;
};
/** Abstract comparator for avatar items */
diff --git a/indra/newview/llavatarlistitem.cpp b/indra/newview/llavatarlistitem.cpp
index 7be4f4eeb8..1db7d63309 100644
--- a/indra/newview/llavatarlistitem.cpp
+++ b/indra/newview/llavatarlistitem.cpp
@@ -80,6 +80,7 @@ LLAvatarListItem::LLAvatarListItem(bool not_from_ui_factory/* = true*/)
mShowProfileBtn(true),
mShowPermissions(false),
mShowCompleteName(false),
+ mForceCompleteName(false),
mHovered(false),
mAvatarNameCacheConnection(),
mGreyOutUsername("")
@@ -350,13 +351,11 @@ void LLAvatarListItem::setShowProfileBtn(bool show)
void LLAvatarListItem::showSpeakingIndicator(bool visible)
{
- // Already done? Then do nothing.
- if (mSpeakingIndicator->getVisible() == (bool)visible)
- return;
-// Disabled to not contradict with SpeakingIndicatorManager functionality. EXT-3976
-// probably this method should be totally removed.
-// mSpeakingIndicator->setVisible(visible);
-// updateChildren();
+ if (mSpeakingIndicator)
+ {
+ mSpeakingIndicator->setIsActiveChannel(visible);
+ mSpeakingIndicator->setShowParticipantsSpeaking(visible);
+ }
}
void LLAvatarListItem::setAvatarIconVisible(bool visible)
@@ -443,8 +442,8 @@ void LLAvatarListItem::onAvatarNameCache(const LLAvatarName& av_name)
mAvatarNameCacheConnection.disconnect();
mGreyOutUsername = "";
- std::string name_string = mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName();
- if(av_name.getCompleteName() != av_name.getUserName())
+ std::string name_string = mShowCompleteName? av_name.getCompleteName(false, mForceCompleteName) : av_name.getDisplayName();
+ if(av_name.getCompleteName(false, mForceCompleteName) != av_name.getUserName())
{
mGreyOutUsername = "[ " + av_name.getUserName(true) + " ]";
LLStringUtil::toLower(mGreyOutUsername);
diff --git a/indra/newview/llavatarlistitem.h b/indra/newview/llavatarlistitem.h
index 630a7ec751..f9381f95e3 100644
--- a/indra/newview/llavatarlistitem.h
+++ b/indra/newview/llavatarlistitem.h
@@ -110,7 +110,7 @@ public:
void showAvatarDistance(bool show);
void showLastInteractionTime(bool show);
void setAvatarIconVisible(bool visible);
- void setShowCompleteName(bool show) { mShowCompleteName = show;};
+ void setShowCompleteName(bool show, bool force = false) { mShowCompleteName = show; mForceCompleteName = force;};
const LLUUID& getAvatarId() const;
std::string getAvatarName() const;
@@ -228,6 +228,7 @@ private:
bool mHovered;
bool mShowCompleteName;
+ bool mForceCompleteName;
std::string mGreyOutUsername;
void fetchAvatarName();
diff --git a/indra/newview/llfloaterbulkpermission.cpp b/indra/newview/llfloaterbulkpermission.cpp
index c09c02d32b..74c5079268 100644
--- a/indra/newview/llfloaterbulkpermission.cpp
+++ b/indra/newview/llfloaterbulkpermission.cpp
@@ -89,9 +89,17 @@ bool LLFloaterBulkPermission::postBuild()
{
mBulkChangeNextOwnerTransfer = true;
}
+
+ mQueueOutputList = getChild<LLScrollListCtrl>("queue output");
return true;
}
+void LLFloaterBulkPermission::onClose(bool app_quitting)
+{
+ removeVOInventoryListener();
+ LLFloater::onClose(app_quitting);
+}
+
void LLFloaterBulkPermission::doApply()
{
// Inspects a stream of selected object contents and adds modifiable ones to the given array.
@@ -216,7 +224,7 @@ void LLFloaterBulkPermission::onCommitCopy()
bool LLFloaterBulkPermission::start()
{
// note: number of top-level objects to modify is mObjectIDs.size().
- getChild<LLScrollListCtrl>("queue output")->setCommentText(getString("start_text"));
+ mQueueOutputList->setCommentText(getString("start_text"));
return nextObject();
}
@@ -239,7 +247,7 @@ bool LLFloaterBulkPermission::nextObject()
if(isDone() && !mDone)
{
- getChild<LLScrollListCtrl>("queue output")->setCommentText(getString("done_text"));
+ mQueueOutputList->setCommentText(getString("done_text"));
mDone = true;
}
return successful_start;
@@ -294,8 +302,6 @@ void LLFloaterBulkPermission::doCheckUncheckAll(bool check)
void LLFloaterBulkPermission::handleInventory(LLViewerObject* viewer_obj, LLInventoryObject::object_list_t* inv)
{
- LLScrollListCtrl* list = getChild<LLScrollListCtrl>("queue output");
-
LLInventoryObject::object_list_t::const_iterator it = inv->begin();
LLInventoryObject::object_list_t::const_iterator end = inv->end();
for ( ; it != end; ++it)
@@ -362,7 +368,7 @@ void LLFloaterBulkPermission::handleInventory(LLViewerObject* viewer_obj, LLInve
status_text.setArg("[STATUS]", "");
}
- list->setCommentText(status_text.getString());
+ mQueueOutputList->setCommentText(status_text.getString());
//TODO if we are an object inside an object we should check a recuse flag and if set
//open the inventory of the object and recurse - Michelle2 Zenovka
diff --git a/indra/newview/llfloaterbulkpermission.h b/indra/newview/llfloaterbulkpermission.h
index 23ca45b611..0b61022e0c 100644
--- a/indra/newview/llfloaterbulkpermission.h
+++ b/indra/newview/llfloaterbulkpermission.h
@@ -41,7 +41,8 @@ class LLFloaterBulkPermission : public LLFloater, public LLVOInventoryListener
friend class LLFloaterReg;
public:
- bool postBuild();
+ bool postBuild() override;
+ void onClose(bool app_quitting) override;
private:
@@ -57,7 +58,7 @@ private:
/*virtual*/ void inventoryChanged(LLViewerObject* obj,
LLInventoryObject::object_list_t* inv,
S32 serial_num,
- void* queue);
+ void* queue) override;
// This is called by inventoryChanged
void handleInventory(LLViewerObject* viewer_obj,
@@ -85,7 +86,7 @@ private:
private:
// UI
- LLScrollListCtrl* mMessages;
+ LLScrollListCtrl* mQueueOutputList = nullptr;
LLButton* mCloseBtn;
// Object Queue
diff --git a/indra/newview/llfloaterchatmentionpicker.cpp b/indra/newview/llfloaterchatmentionpicker.cpp
new file mode 100644
index 0000000000..1cfed122a9
--- /dev/null
+++ b/indra/newview/llfloaterchatmentionpicker.cpp
@@ -0,0 +1,184 @@
+/**
+ * @file llfloaterchatmentionpicker.cpp
+ *
+ * $LicenseInfo:firstyear=2025&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2025, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llfloaterchatmentionpicker.h"
+
+#include "llavatarlist.h"
+#include "llfloaterimcontainer.h"
+#include "llchatmentionhelper.h"
+#include "llparticipantlist.h"
+
+LLUUID LLFloaterChatMentionPicker::sSessionID(LLUUID::null);
+
+LLFloaterChatMentionPicker::LLFloaterChatMentionPicker(const LLSD& key)
+: LLFloater(key), mAvatarList(NULL)
+{
+ // This floater should hover on top of our dependent (with the dependent having the focus)
+ setFocusStealsFrontmost(false);
+ setBackgroundVisible(false);
+ setAutoFocus(false);
+}
+
+bool LLFloaterChatMentionPicker::postBuild()
+{
+ mAvatarList = getChild<LLAvatarList>("avatar_list");
+ mAvatarList->setShowCompleteName(true, true);
+ mAvatarList->setFocusOnItemClicked(false);
+ mAvatarList->setItemClickedCallback([this](LLUICtrl* ctrl, S32 x, S32 y, MASK mask)
+ {
+ if (LLAvatarListItem* item = dynamic_cast<LLAvatarListItem*>(ctrl))
+ {
+ selectResident(item->getAvatarId());
+ }
+ });
+ mAvatarList->setRefreshCompleteCallback([this](LLUICtrl* ctrl, const LLSD& param)
+ {
+ if (mAvatarList->numSelected() == 0)
+ {
+ mAvatarList->selectFirstItem();
+ }
+ });
+
+ return LLFloater::postBuild();
+}
+
+void LLFloaterChatMentionPicker::onOpen(const LLSD& key)
+{
+ buildAvatarList();
+ mAvatarList->setNameFilter(key.has("av_name") ? key["av_name"].asString() : "");
+
+ gFloaterView->adjustToFitScreen(this, false);
+}
+
+uuid_vec_t LLFloaterChatMentionPicker::getParticipantIds()
+{
+ LLParticipantList* item = dynamic_cast<LLParticipantList*>(LLFloaterIMContainer::getInstance()->getSessionModel(sSessionID));
+ if (!item)
+ {
+ LL_WARNS() << "Participant list is missing" << LL_ENDL;
+ return {};
+ }
+
+ uuid_vec_t avatar_ids;
+ LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin();
+ LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd();
+ while (current_participant_model != end_participant_model)
+ {
+ LLConversationItem* participant_model = dynamic_cast<LLConversationItem*>(*current_participant_model);
+ if (participant_model)
+ {
+ avatar_ids.push_back(participant_model->getUUID());
+ }
+ current_participant_model++;
+ }
+ return avatar_ids;
+}
+
+void LLFloaterChatMentionPicker::buildAvatarList()
+{
+ uuid_vec_t& avatar_ids = mAvatarList->getIDs();
+ avatar_ids = getParticipantIds();
+ updateAvatarList(avatar_ids);
+ mAvatarList->setDirty();
+}
+
+void LLFloaterChatMentionPicker::selectResident(const LLUUID& id)
+{
+ if (id.isNull())
+ return;
+
+ setValue(stringize("secondlife:///app/agent/", id.asString(), "/mention "));
+ onCommit();
+ LLChatMentionHelper::instance().hideHelper();
+}
+
+void LLFloaterChatMentionPicker::onClose(bool app_quitting)
+{
+ if (!app_quitting)
+ {
+ LLChatMentionHelper::instance().hideHelper();
+ }
+}
+
+bool LLFloaterChatMentionPicker::handleKey(KEY key, MASK mask, bool called_from_parent)
+{
+ if (mask == MASK_NONE)
+ {
+ switch (key)
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ return mAvatarList->handleKey(key, mask, called_from_parent);
+ case KEY_RETURN:
+ case KEY_TAB:
+ selectResident(mAvatarList->getSelectedUUID());
+ return true;
+ case KEY_ESCAPE:
+ LLChatMentionHelper::instance().hideHelper();
+ return true;
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ return true;
+ default:
+ break;
+ }
+ }
+ return LLFloater::handleKey(key, mask, called_from_parent);
+}
+
+void LLFloaterChatMentionPicker::goneFromFront()
+{
+ LLChatMentionHelper::instance().hideHelper();
+}
+
+void LLFloaterChatMentionPicker::updateSessionID(LLUUID session_id)
+{
+ sSessionID = session_id;
+
+ LLParticipantList* item = dynamic_cast<LLParticipantList*>(LLFloaterIMContainer::getInstance()->getSessionModel(sSessionID));
+ if (!item)
+ {
+ LL_WARNS() << "Participant list is missing" << LL_ENDL;
+ return;
+ }
+
+ uuid_vec_t avatar_ids = getParticipantIds();
+ updateAvatarList(avatar_ids);
+}
+
+void LLFloaterChatMentionPicker::updateAvatarList(uuid_vec_t& avatar_ids)
+{
+ std::vector<std::string> av_names;
+ for (auto& id : avatar_ids)
+ {
+ LLAvatarName av_name;
+ LLAvatarNameCache::get(id, &av_name);
+ av_names.push_back(utf8str_tolower(av_name.getAccountName()));
+ av_names.push_back(utf8str_tolower(av_name.getDisplayName()));
+ }
+ LLChatMentionHelper::instance().updateAvatarList(av_names);
+}
diff --git a/indra/newview/llfloaterchatmentionpicker.h b/indra/newview/llfloaterchatmentionpicker.h
new file mode 100644
index 0000000000..8d221d7a89
--- /dev/null
+++ b/indra/newview/llfloaterchatmentionpicker.h
@@ -0,0 +1,58 @@
+/**
+ * @file llfloaterchatmentionpicker.h
+ *
+ * $LicenseInfo:firstyear=2025&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2025, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LLFLOATERCHATMENTIONPICKER_H
+#define LLFLOATERCHATMENTIONPICKER_H
+
+#include "llfloater.h"
+
+class LLAvatarList;
+
+class LLFloaterChatMentionPicker : public LLFloater
+{
+public:
+ LLFloaterChatMentionPicker(const LLSD& key);
+
+ virtual bool postBuild() override;
+ virtual void goneFromFront() override;
+
+ void buildAvatarList();
+
+ static uuid_vec_t getParticipantIds();
+ static void updateSessionID(LLUUID session_id);
+ static void updateAvatarList(uuid_vec_t& avatar_ids);
+
+private:
+
+ void onOpen(const LLSD& key) override;
+ void onClose(bool app_quitting) override;
+ virtual bool handleKey(KEY key, MASK mask, bool called_from_parent) override;
+ void selectResident(const LLUUID& id);
+
+ static LLUUID sSessionID;
+ LLAvatarList* mAvatarList;
+};
+
+#endif
diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp
index db6f9ac22a..b649514bff 100644
--- a/indra/newview/llfloaterimnearbychat.cpp
+++ b/indra/newview/llfloaterimnearbychat.cpp
@@ -586,7 +586,7 @@ void LLFloaterIMNearbyChat::sendChat( EChatType type )
{
if (mInputEditor)
{
- LLWString text = mInputEditor->getWText();
+ LLWString text = mInputEditor->getConvertedText();
LLWStringUtil::trim(text);
LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.
if (!text.empty())
diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp
index 185274981b..84a9fad708 100644
--- a/indra/newview/llfloaterimsession.cpp
+++ b/indra/newview/llfloaterimsession.cpp
@@ -251,7 +251,7 @@ void LLFloaterIMSession::sendMsgFromInputEditor()
{
if (mInputEditor)
{
- LLWString text = mInputEditor->getWText();
+ LLWString text = mInputEditor->getConvertedText();
LLWStringUtil::trim(text);
LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.
if(!text.empty())
diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp
index 553aa1ff4b..cd428c67ef 100644
--- a/indra/newview/llfloaterimsessiontab.cpp
+++ b/indra/newview/llfloaterimsessiontab.cpp
@@ -35,6 +35,7 @@
#include "llavatariconctrl.h"
#include "llchatentry.h"
#include "llchathistory.h"
+#include "llfloaterchatmentionpicker.h"
#include "llchiclet.h"
#include "llchicletbar.h"
#include "lldraghandle.h"
@@ -485,6 +486,7 @@ void LLFloaterIMSessionTab::onFocusReceived()
LLIMModel::instance().sendNoUnreadMessages(mSessionID);
}
+ LLFloaterChatMentionPicker::updateSessionID(mSessionID);
super::onFocusReceived();
}
diff --git a/indra/newview/llfloaternewfeaturenotification.cpp b/indra/newview/llfloaternewfeaturenotification.cpp
index 369727ff1e..1badcdd3d9 100644
--- a/indra/newview/llfloaternewfeaturenotification.cpp
+++ b/indra/newview/llfloaternewfeaturenotification.cpp
@@ -43,12 +43,28 @@ bool LLFloaterNewFeatureNotification::postBuild()
setCanDrag(false);
getChild<LLButton>("close_btn")->setCommitCallback(boost::bind(&LLFloaterNewFeatureNotification::onCloseBtn, this));
- const std::string title_txt = "title_txt";
- const std::string dsc_txt = "description_txt";
- std::string feature = "_" + getKey().asString();
+ if (getKey().isString())
+ {
+ const std::string title_txt = "title_txt";
+ const std::string dsc_txt = "description_txt";
- getChild<LLUICtrl>(title_txt)->setValue(getString(title_txt + feature));
- getChild<LLUICtrl>(dsc_txt)->setValue(getString(dsc_txt + feature));
+ std::string feature = "_" + getKey().asString();
+ if (hasString(title_txt + feature))
+ {
+ getChild<LLUICtrl>(title_txt)->setValue(getString(title_txt + feature));
+ getChild<LLUICtrl>(dsc_txt)->setValue(getString(dsc_txt + feature));
+ }
+ else
+ {
+ // Show blank
+ LL_WARNS() << "Feature \"" << getKey().asString() << "\" not found for feature notification" << LL_ENDL;
+ }
+ }
+ else
+ {
+ // Show blank
+ LL_WARNS() << "Feature notification without a feature" << LL_ENDL;
+ }
if (getKey().asString() == "gltf")
{
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index de1c7a7ed8..c9e9d50e19 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -2768,6 +2768,7 @@ bool LLInventoryModel::loadSkeleton(
bool is_cache_obsolete = false;
if (loadFromFile(inventory_filename, categories, items, categories_to_update, is_cache_obsolete))
{
+ LL_PROFILE_ZONE_NAMED("loadFromFile");
// We were able to find a cache of files. So, use what we
// found to generate a set of categories we should add. We
// will go through each category loaded and if the version
diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp
index 48c80842b9..851107b3be 100644
--- a/indra/newview/llmeshrepository.cpp
+++ b/indra/newview/llmeshrepository.cpp
@@ -544,6 +544,66 @@ bool RequestStats::isDelayed() const
return mTimer.getStarted() && !mTimer.hasExpired();
}
+F32 calculate_score(LLVOVolume* object)
+{
+ if (!object)
+ {
+ return -1.f;
+ }
+ LLDrawable* drawable = object->mDrawable;
+ if (!drawable)
+ {
+ return -1;
+ }
+ if (drawable->isState(LLDrawable::RIGGED) || object->isAttachment())
+ {
+ LLVOAvatar* avatar = object->getAvatar();
+ LLDrawable* av_drawable = avatar ? avatar->mDrawable : nullptr;
+ if (avatar && av_drawable)
+ {
+ // See LLVOVolume::calcLOD()
+ F32 radius;
+ if (avatar->isControlAvatar())
+ {
+ const LLVector3* box = avatar->getLastAnimExtents();
+ LLVector3 diag = box[1] - box[0];
+ radius = diag.magVec() * 0.5f;
+ }
+ else
+ {
+ // Volume in a rigged mesh attached to a regular avatar.
+ const LLVector3* box = avatar->getLastAnimExtents();
+ LLVector3 diag = box[1] - box[0];
+ radius = diag.magVec();
+
+ if (!avatar->isSelf() && !avatar->hasFirstFullAttachmentData())
+ {
+ // slightly deprioritize avatars that are still receiving data
+ radius *= 0.9f;
+ }
+ }
+ return radius / llmax(av_drawable->mDistanceWRTCamera, 1.f);
+ }
+ }
+ return drawable->getRadius() / llmax(drawable->mDistanceWRTCamera, 1.f);
+}
+
+void PendingRequestBase::updateScore()
+{
+ mScore = 0;
+ if (mTrackedData)
+ {
+ for (LLVOVolume* volume : mTrackedData->mVolumes)
+ {
+ F32 cur_score = calculate_score(volume);
+ if (cur_score > 0)
+ {
+ mScore = llmax(mScore, cur_score);
+ }
+ }
+ }
+}
+
LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMaterial& material)
{
LLPointer< LLViewerFetchedTexture > * ppTex = static_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData);
@@ -1809,42 +1869,36 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
//static
void LLMeshRepoThread::incActiveLODRequests()
{
- LLMutexLock lock(gMeshRepo.mThread->mMutex);
++LLMeshRepoThread::sActiveLODRequests;
}
//static
void LLMeshRepoThread::decActiveLODRequests()
{
- LLMutexLock lock(gMeshRepo.mThread->mMutex);
--LLMeshRepoThread::sActiveLODRequests;
}
//static
void LLMeshRepoThread::incActiveHeaderRequests()
{
- LLMutexLock lock(gMeshRepo.mThread->mMutex);
++LLMeshRepoThread::sActiveHeaderRequests;
}
//static
void LLMeshRepoThread::decActiveHeaderRequests()
{
- LLMutexLock lock(gMeshRepo.mThread->mMutex);
--LLMeshRepoThread::sActiveHeaderRequests;
}
//static
void LLMeshRepoThread::incActiveSkinRequests()
{
- LLMutexLock lock(gMeshRepo.mThread->mMutex);
++LLMeshRepoThread::sActiveSkinRequests;
}
//static
void LLMeshRepoThread::decActiveSkinRequests()
{
- LLMutexLock lock(gMeshRepo.mThread->mMutex);
--LLMeshRepoThread::sActiveSkinRequests;
}
@@ -2226,7 +2280,7 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes
if (gMeshRepo.mLoadingSkins.find(mesh_id) == gMeshRepo.mLoadingSkins.end())
{
- gMeshRepo.mLoadingSkins[mesh_id] = {}; // add an empty vector to indicate to main thread that we are loading skin info
+ gMeshRepo.mLoadingSkins[mesh_id]; // add an empty vector to indicate to main thread that we are loading skin info
}
}
@@ -4218,13 +4272,13 @@ void LLMeshRepository::unregisterMesh(LLVOVolume* vobj)
{
for (auto& param : lod)
{
- vector_replace_with_last(param.second, vobj);
+ vector_replace_with_last(param.second.mVolumes, vobj);
}
}
for (auto& skin_pair : mLoadingSkins)
{
- vector_replace_with_last(skin_pair.second, vobj);
+ vector_replace_with_last(skin_pair.second.mVolumes, vobj);
}
}
@@ -4247,16 +4301,17 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para
mesh_load_map::iterator iter = mLoadingMeshes[new_lod].find(mesh_id);
if (iter != mLoadingMeshes[new_lod].end())
{ //request pending for this mesh, append volume id to list
- auto it = std::find(iter->second.begin(), iter->second.end(), vobj);
- if (it == iter->second.end()) {
- iter->second.push_back(vobj);
+ auto it = std::find(iter->second.mVolumes.begin(), iter->second.mVolumes.end(), vobj);
+ if (it == iter->second.mVolumes.end()) {
+ iter->second.addVolume(vobj);
}
}
else
{
//first request for this mesh
- mLoadingMeshes[new_lod][mesh_id].push_back(vobj);
- mPendingRequests.emplace_back(new PendingRequestLOD(mesh_params, new_lod));
+ std::shared_ptr<PendingRequestBase> request(new PendingRequestLOD(mesh_params, new_lod));
+ mPendingRequests.emplace_back(request);
+ mLoadingMeshes[new_lod][mesh_id].initData(vobj, request);
LLMeshRepository::sLODPending++;
}
}
@@ -4315,50 +4370,6 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para
return new_lod;
}
-F32 calculate_score(LLVOVolume* object)
-{
- if (!object)
- {
- return -1.f;
- }
- LLDrawable* drawable = object->mDrawable;
- if (!drawable)
- {
- return -1;
- }
- if (drawable->isState(LLDrawable::RIGGED) || object->isAttachment())
- {
- LLVOAvatar* avatar = object->getAvatar();
- LLDrawable* av_drawable = avatar ? avatar->mDrawable : nullptr;
- if (avatar && av_drawable)
- {
- // See LLVOVolume::calcLOD()
- F32 radius;
- if (avatar->isControlAvatar())
- {
- const LLVector3* box = avatar->getLastAnimExtents();
- LLVector3 diag = box[1] - box[0];
- radius = diag.magVec() * 0.5f;
- }
- else
- {
- // Volume in a rigged mesh attached to a regular avatar.
- const LLVector3* box = avatar->getLastAnimExtents();
- LLVector3 diag = box[1] - box[0];
- radius = diag.magVec();
-
- if (!avatar->isSelf() && !avatar->hasFirstFullAttachmentData())
- {
- // slightly deprioritize avatars that are still receiving data
- radius *= 0.9f;
- }
- }
- return radius / llmax(av_drawable->mDistanceWRTCamera, 1.f);
- }
- }
- return drawable->getRadius() / llmax(drawable->mDistanceWRTCamera, 1.f);
-}
-
void LLMeshRepository::notifyLoadedMeshes()
{ //called from main thread
LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH);
@@ -4495,13 +4506,20 @@ void LLMeshRepository::notifyLoadedMeshes()
{
LLMutexTrylock lock1(mMeshMutex);
LLMutexTrylock lock2(mThread->mMutex);
+ LLMutexTrylock lock3(mThread->mHeaderMutex);
+ LLMutexTrylock lock4(mThread->mPendingMutex);
static U32 hold_offs(0);
- if (! lock1.isLocked() || ! lock2.isLocked())
+ if (! lock1.isLocked() || ! lock2.isLocked() || ! lock3.isLocked() || ! lock4.isLocked())
{
// If we can't get the locks, skip and pick this up later.
+ // Eventually thread queue will be free enough
++hold_offs;
sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs);
+ if (hold_offs > 4)
+ {
+ LL_WARNS_ONCE() << "High mesh thread holdoff" << LL_ENDL;
+ }
return;
}
hold_offs = 0;
@@ -4548,61 +4566,25 @@ void LLMeshRepository::notifyLoadedMeshes()
if (mPendingRequests.size() > push_count)
{
+ LL_PROFILE_ZONE_NAMED("Mesh score update");
// More requests than the high-water limit allows so
// sort and forward the most important.
- //calculate "score" for pending requests
-
- //create score map
- std::map<LLUUID, F32> score_map;
-
- for (U32 i = 0; i < LLVolumeLODGroup::NUM_LODS; ++i)
- {
- for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter)
- {
- F32 max_score = 0.f;
- for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter)
- {
- F32 cur_score = calculate_score(*obj_iter);
- if (cur_score >= 0.f)
- {
- max_score = llmax(max_score, cur_score);
- }
- }
-
- score_map[iter->first] = max_score;
- }
- }
- for (mesh_load_map::iterator iter = mLoadingSkins.begin(); iter != mLoadingSkins.end(); ++iter)
- {
- F32 max_score = 0.f;
- for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter)
- {
- F32 cur_score = calculate_score(*obj_iter);
- if (cur_score >= 0.f)
- {
- max_score = llmax(max_score, cur_score);
- }
- }
-
- score_map[iter->first] = max_score;
- }
-
- //set "score" for pending requests
- for (std::unique_ptr<PendingRequestBase>& req_p : mPendingRequests)
+ // update "score" for pending requests
+ for (std::shared_ptr<PendingRequestBase>& req_p : mPendingRequests)
{
- req_p->setScore(score_map[req_p->getId()]);
+ req_p->checkScore();
}
//sort by "score"
std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count,
mPendingRequests.end(), PendingRequestBase::CompareScoreGreater());
}
- LLMutexTrylock lock3(mThread->mHeaderMutex);
- LLMutexTrylock lock4(mThread->mPendingMutex);
while (!mPendingRequests.empty() && push_count > 0)
{
- std::unique_ptr<PendingRequestBase>& req_p = mPendingRequests.front();
+ std::shared_ptr<PendingRequestBase>& req_p = mPendingRequests.front();
+ // todo: check hasTrackedData here and erase request if none
+ // since this is supposed to mean that request was removed
switch (req_p->getRequestType())
{
case MESH_REQUEST_LOD:
@@ -4657,7 +4639,7 @@ void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo* info)
skin_load_map::iterator iter = mLoadingSkins.find(info->mMeshID);
if (iter != mLoadingSkins.end())
{
- for (LLVOVolume* vobj : iter->second)
+ for (LLVOVolume* vobj : iter->second.mVolumes)
{
if (vobj)
{
@@ -4673,7 +4655,7 @@ void LLMeshRepository::notifySkinInfoUnavailable(const LLUUID& mesh_id)
skin_load_map::iterator iter = mLoadingSkins.find(mesh_id);
if (iter != mLoadingSkins.end())
{
- for (LLVOVolume* vobj : iter->second)
+ for (LLVOVolume* vobj : iter->second.mVolumes)
{
if (vobj)
{
@@ -4737,7 +4719,7 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol
}
//notify waiting LLVOVolume instances that their requested mesh is available
- for (LLVOVolume* vobj : obj_iter->second)
+ for (LLVOVolume* vobj : obj_iter->second.mVolumes)
{
if (vobj)
{
@@ -4767,7 +4749,7 @@ void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params,
LLPrimitive::getVolumeManager()->unrefVolume(sys_volume);
}
- for (LLVOVolume* vobj : obj_iter->second)
+ for (LLVOVolume* vobj : obj_iter->second.mVolumes)
{
if (vobj)
{
@@ -4810,16 +4792,17 @@ const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, LLVOV
skin_load_map::iterator iter = mLoadingSkins.find(mesh_id);
if (iter != mLoadingSkins.end())
{ //request pending for this mesh, append volume id to list
- auto it = std::find(iter->second.begin(), iter->second.end(), requesting_obj);
- if (it == iter->second.end()) {
- iter->second.push_back(requesting_obj);
+ auto it = std::find(iter->second.mVolumes.begin(), iter->second.mVolumes.end(), requesting_obj);
+ if (it == iter->second.mVolumes.end()) {
+ iter->second.addVolume(requesting_obj);
}
}
else
{
//first request for this mesh
- mLoadingSkins[mesh_id].push_back(requesting_obj);
- mPendingRequests.emplace_back(new PendingRequestUUID(mesh_id, MESH_REQUEST_SKIN));
+ std::shared_ptr<PendingRequestBase> request(new PendingRequestUUID(mesh_id, MESH_REQUEST_SKIN));
+ mLoadingSkins[mesh_id].initData(requesting_obj, request);
+ mPendingRequests.emplace_back(request);
}
}
}
diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h
index b9acb3573f..0847c29d0d 100644
--- a/indra/newview/llmeshrepository.h
+++ b/indra/newview/llmeshrepository.h
@@ -168,7 +168,6 @@ public:
void submitRequest(Request* request);
static S32 llcdCallback(const char*, S32, S32);
- void cancel();
void setMeshData(LLCDMeshData& mesh, bool vertex_based);
void doDecomposition();
@@ -206,19 +205,19 @@ private:
LLFrameTimer mTimer;
};
-
+class MeshLoadData;
class PendingRequestBase
{
public:
struct CompareScoreGreater
{
- bool operator()(const std::unique_ptr<PendingRequestBase>& lhs, const std::unique_ptr<PendingRequestBase>& rhs)
+ bool operator()(const std::shared_ptr<PendingRequestBase>& lhs, const std::shared_ptr<PendingRequestBase>& rhs)
{
return lhs->mScore > rhs->mScore; // greatest = first
}
};
- PendingRequestBase() : mScore(0.f) {};
+ PendingRequestBase() : mScore(0.f), mTrackedData(nullptr), mScoreDirty(true) {};
virtual ~PendingRequestBase() {}
bool operator<(const PendingRequestBase& rhs) const
@@ -226,14 +225,34 @@ public:
return mId < rhs.mId;
}
- void setScore(F32 score) { mScore = score; }
F32 getScore() const { return mScore; }
+ void checkScore()
+ {
+ constexpr F32 EXPIRE_TIME_SECS = 8.f;
+ if (mScoreTimer.getElapsedTimeF32() > EXPIRE_TIME_SECS || mScoreDirty)
+ {
+ updateScore();
+ mScoreDirty = false;
+ mScoreTimer.reset();
+ }
+ };
+
LLUUID getId() const { return mId; }
virtual EMeshRequestType getRequestType() const = 0;
+ void trackData(MeshLoadData* data) { mTrackedData = data; mScoreDirty = true; }
+ void untrackData() { mTrackedData = nullptr; }
+ bool hasTrackedData() { return mTrackedData != nullptr; }
+ void setScoreDirty() { mScoreDirty = true; }
+
protected:
- F32 mScore;
+ void updateScore();
+
LLUUID mId;
+ F32 mScore;
+ bool mScoreDirty;
+ LLTimer mScoreTimer;
+ MeshLoadData* mTrackedData;
};
class PendingRequestLOD : public PendingRequestBase
@@ -267,6 +286,37 @@ private:
EMeshRequestType mRequestType;
};
+
+class MeshLoadData
+{
+public:
+ MeshLoadData() {}
+ ~MeshLoadData()
+ {
+ if (std::shared_ptr<PendingRequestBase> request = mRequest.lock())
+ {
+ request->untrackData();
+ }
+ }
+ void initData(LLVOVolume* vol, std::shared_ptr<PendingRequestBase>& request)
+ {
+ mVolumes.push_back(vol);
+ request->trackData(this);
+ mRequest = request;
+ }
+ void addVolume(LLVOVolume* vol)
+ {
+ mVolumes.push_back(vol);
+ if (std::shared_ptr<PendingRequestBase> request = mRequest.lock())
+ {
+ request->setScoreDirty();
+ }
+ }
+ std::vector<LLVOVolume*> mVolumes;
+private:
+ std::weak_ptr<PendingRequestBase> mRequest;
+};
+
class LLMeshHeader
{
public:
@@ -814,7 +864,7 @@ public:
static void metricsProgress(unsigned int count);
static void metricsUpdate();
- typedef std::unordered_map<LLUUID, std::vector<LLVOVolume*> > mesh_load_map;
+ typedef std::unordered_map<LLUUID, MeshLoadData> mesh_load_map;
mesh_load_map mLoadingMeshes[4];
typedef std::unordered_map<LLUUID, LLPointer<LLMeshSkinInfo>> skin_map;
@@ -825,11 +875,11 @@ public:
LLMutex* mMeshMutex;
- typedef std::vector <std::unique_ptr<PendingRequestBase> > pending_requests_vec;
+ typedef std::vector <std::shared_ptr<PendingRequestBase> > pending_requests_vec;
pending_requests_vec mPendingRequests;
//list of mesh ids awaiting skin info
- typedef std::unordered_map<LLUUID, std::vector<LLVOVolume*> > skin_load_map;
+ typedef std::unordered_map<LLUUID, MeshLoadData > skin_load_map;
skin_load_map mLoadingSkins;
//list of mesh ids awaiting decompositions
diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp
index 3a164ec94b..5b059516cd 100644
--- a/indra/newview/llpanelface.cpp
+++ b/indra/newview/llpanelface.cpp
@@ -1106,6 +1106,63 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
updateVisibility(objectp);
+ bool missing_asset = false;
+ {
+ LLGLenum image_format = GL_RGB;
+ bool identical_image_format = false;
+ LLSelectedTE::getImageFormat(image_format, identical_image_format, missing_asset);
+
+ if (!missing_asset)
+ {
+ mIsAlpha = false;
+ switch (image_format)
+ {
+ case GL_RGBA:
+ case GL_ALPHA:
+ {
+ mIsAlpha = true;
+ }
+ break;
+
+ case GL_RGB:
+ break;
+ default:
+ {
+ LL_WARNS() << "Unexpected tex format in LLPanelFace...resorting to no alpha" << LL_ENDL;
+ }
+ break;
+ }
+ }
+ else
+ {
+ // Don't know image's properties, use material's mode value
+ mIsAlpha = true;
+ }
+
+ // Diffuse Alpha Mode
+ // Init to the default that is appropriate for the alpha content of the asset
+ //
+ U8 alpha_mode = mIsAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
+
+ bool identical_alpha_mode = false;
+
+ // See if that's been overridden by a material setting for same...
+ //
+ LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode, mIsAlpha);
+
+ // it is invalid to have any alpha mode other than blend if transparency is greater than zero ...
+ // Want masking? Want emissive? Tough! You get BLEND!
+ alpha_mode = (transparency > 0.f) ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : alpha_mode;
+
+ // ... unless there is no alpha channel in the texture, in which case alpha mode MUST be none
+ alpha_mode = mIsAlpha ? alpha_mode : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
+
+ mComboAlphaMode->getSelectionInterface()->selectNthItem(alpha_mode);
+ updateAlphaControls();
+
+ mExcludeWater &= (LLMaterial::DIFFUSE_ALPHA_MODE_BLEND == alpha_mode);
+ }
+
// Water exclusion
{
mCheckHideWater->setEnabled(editable && !has_pbr_material && !isMediaTexSelected());
@@ -1188,65 +1245,11 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
// Texture
{
- LLGLenum image_format = GL_RGB;
- bool identical_image_format = false;
- bool missing_asset = false;
- LLSelectedTE::getImageFormat(image_format, identical_image_format, missing_asset);
-
- if (!missing_asset)
- {
- mIsAlpha = false;
- switch (image_format)
- {
- case GL_RGBA:
- case GL_ALPHA:
- {
- mIsAlpha = true;
- }
- break;
-
- case GL_RGB: break;
- default:
- {
- LL_WARNS() << "Unexpected tex format in LLPanelFace...resorting to no alpha" << LL_ENDL;
- }
- break;
- }
- }
- else
- {
- // Don't know image's properties, use material's mode value
- mIsAlpha = true;
- }
-
if (LLViewerMedia::getInstance()->textureHasMedia(id))
{
mBtnAlign->setEnabled(editable);
}
- // Diffuse Alpha Mode
-
- // Init to the default that is appropriate for the alpha content of the asset
- //
- U8 alpha_mode = mIsAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
-
- bool identical_alpha_mode = false;
-
- // See if that's been overridden by a material setting for same...
- //
- LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode, mIsAlpha);
-
- // it is invalid to have any alpha mode other than blend if transparency is greater than zero ...
- // Want masking? Want emissive? Tough! You get BLEND!
- alpha_mode = (transparency > 0.f) ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : alpha_mode;
-
- // ... unless there is no alpha channel in the texture, in which case alpha mode MUST be none
- alpha_mode = mIsAlpha ? alpha_mode : LLMaterial::DIFFUSE_ALPHA_MODE_NONE;
-
- mComboAlphaMode->getSelectionInterface()->selectNthItem(alpha_mode);
-
- updateAlphaControls();
-
if (mTextureCtrl)
{
if (identical_diffuse)
@@ -4009,6 +4012,85 @@ void LLPanelFace::onPasteColor(LLViewerObject* objectp, S32 te)
}
}
+void set_item_availability(
+ const LLUUID& id,
+ LLSD& dest,
+ const std::string& modifier,
+ bool is_creator,
+ std::map<LLUUID, LLUUID> &asset_item_map,
+ LLViewerObject* objectp)
+{
+ if (id.isNull())
+ {
+ return;
+ }
+
+ LLUUID item_id;
+ bool from_library = get_is_predefined_texture(id);
+ bool full_perm = from_library;
+ full_perm |= is_creator;
+
+ if (!full_perm)
+ {
+ std::map<LLUUID, LLUUID>::iterator iter = asset_item_map.find(id);
+ if (iter != asset_item_map.end())
+ {
+ item_id = iter->second;
+ }
+ else
+ {
+ // What this does is simply searches inventory for item with same asset id,
+ // as result it is Hightly unreliable, leaves little control to user, borderline hack
+ // but there are little options to preserve permissions - multiple inventory
+ // items might reference same asset and inventory search is expensive.
+ bool no_transfer = false;
+ if (objectp->getInventoryItemByAsset(id))
+ {
+ no_transfer = !objectp->getInventoryItemByAsset(id)->getIsFullPerm();
+ }
+ item_id = get_copy_free_item_by_asset_id(id, no_transfer);
+ // record value to avoid repeating inventory search when possible
+ asset_item_map[id] = item_id;
+ }
+ }
+
+ if (item_id.notNull() && gInventory.isObjectDescendentOf(item_id, gInventory.getLibraryRootFolderID()))
+ {
+ full_perm = true;
+ from_library = true;
+ }
+
+ dest[modifier + "itemfullperm"] = full_perm;
+ dest[modifier + "fromlibrary"] = from_library;
+
+ // If full permission object, texture is free to copy,
+ // but otherwise we need to check inventory and extract permissions
+ //
+ // Normally we care only about restrictions for current user and objects
+ // don't inherit any 'next owner' permissions from texture, so there is
+ // no need to record item id if full_perm==true
+ if (!full_perm && item_id.notNull())
+ {
+ LLViewerInventoryItem* itemp = gInventory.getItem(item_id);
+ if (itemp)
+ {
+ LLPermissions item_permissions = itemp->getPermissions();
+ if (item_permissions.allowOperationBy(PERM_COPY,
+ gAgent.getID(),
+ gAgent.getGroupID()))
+ {
+ dest[modifier + "itemid"] = item_id;
+ dest[modifier + "itemfullperm"] = itemp->getIsFullPerm();
+ if (!itemp->isFinished())
+ {
+ // needed for dropTextureAllFaces
+ LLInventoryModelBackgroundFetch::instance().start(item_id, false);
+ }
+ }
+ }
+ }
+}
+
void LLPanelFace::onCopyTexture()
{
LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject();
@@ -4046,6 +4128,7 @@ void LLPanelFace::onCopyTexture()
if (tep)
{
LLSD te_data;
+ LLUUID pbr_id = objectp->getRenderMaterialID(te);
// asLLSD() includes media
te_data["te"] = tep->asLLSD();
@@ -4054,21 +4137,20 @@ void LLPanelFace::onCopyTexture()
te_data["te"]["bumpshiny"] = tep->getBumpShiny();
te_data["te"]["bumpfullbright"] = tep->getBumpShinyFullbright();
te_data["te"]["texgen"] = tep->getTexGen();
- te_data["te"]["pbr"] = objectp->getRenderMaterialID(te);
+ te_data["te"]["pbr"] = pbr_id;
if (tep->getGLTFMaterialOverride() != nullptr)
{
te_data["te"]["pbr_override"] = tep->getGLTFMaterialOverride()->asJSON();
}
- if (te_data["te"].has("imageid"))
+ if (te_data["te"].has("imageid") || pbr_id.notNull())
{
- LLUUID item_id;
- LLUUID id = te_data["te"]["imageid"].asUUID();
- bool from_library = get_is_predefined_texture(id);
- bool full_perm = from_library;
+ LLUUID img_id = te_data["te"]["imageid"].asUUID();
+ bool pbr_from_library = false;
+ bool pbr_full_perm = false;
+ bool is_creator = false;
- if (!full_perm
- && objectp->permCopy()
+ if (objectp->permCopy()
&& objectp->permTransfer()
&& objectp->permModify())
{
@@ -4078,66 +4160,31 @@ void LLPanelFace::onCopyTexture()
std::string creator_app_link;
LLUUID creator_id;
LLSelectMgr::getInstance()->selectGetCreator(creator_id, creator_app_link);
- full_perm = objectp->mOwnerID == creator_id;
+ is_creator = objectp->mOwnerID == creator_id;
}
- if (id.notNull() && !full_perm)
+ // check permissions for blin-phong/diffuse image and for pbr asset
+ if (img_id.notNull())
{
- std::map<LLUUID, LLUUID>::iterator iter = asset_item_map.find(id);
- if (iter != asset_item_map.end())
- {
- item_id = iter->second;
- }
- else
- {
- // What this does is simply searches inventory for item with same asset id,
- // as result it is Hightly unreliable, leaves little control to user, borderline hack
- // but there are little options to preserve permissions - multiple inventory
- // items might reference same asset and inventory search is expensive.
- bool no_transfer = false;
- if (objectp->getInventoryItemByAsset(id))
- {
- no_transfer = !objectp->getInventoryItemByAsset(id)->getIsFullPerm();
- }
- item_id = get_copy_free_item_by_asset_id(id, no_transfer);
- // record value to avoid repeating inventory search when possible
- asset_item_map[id] = item_id;
- }
+ set_item_availability(img_id, te_data["te"], "img", is_creator, asset_item_map, objectp);
}
-
- if (item_id.notNull() && gInventory.isObjectDescendentOf(item_id, gInventory.getLibraryRootFolderID()))
+ if (pbr_id.notNull())
{
- full_perm = true;
- from_library = true;
- }
+ set_item_availability(pbr_id, te_data["te"], "pbr", is_creator, asset_item_map, objectp);
- {
- te_data["te"]["itemfullperm"] = full_perm;
- te_data["te"]["fromlibrary"] = from_library;
-
- // If full permission object, texture is free to copy,
- // but otherwise we need to check inventory and extract permissions
- //
- // Normally we care only about restrictions for current user and objects
- // don't inherit any 'next owner' permissions from texture, so there is
- // no need to record item id if full_perm==true
- if (!full_perm && !from_library && item_id.notNull())
+ // permissions for overrides
+ // Overrides do not permit no-copy textures
+ LLGLTFMaterial* override = tep->getGLTFMaterialOverride();
+ if (override != nullptr)
{
- LLViewerInventoryItem* itemp = gInventory.getItem(item_id);
- if (itemp)
+ for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
{
- LLPermissions item_permissions = itemp->getPermissions();
- if (item_permissions.allowOperationBy(PERM_COPY,
- gAgent.getID(),
- gAgent.getGroupID()))
+ LLUUID& texture_id = override->mTextureId[i];
+ if (texture_id.notNull())
{
- te_data["te"]["imageitemid"] = item_id;
- te_data["te"]["itemfullperm"] = itemp->getIsFullPerm();
- if (!itemp->isFinished())
- {
- // needed for dropTextureAllFaces
- LLInventoryModelBackgroundFetch::instance().start(item_id, false);
- }
+ const std::string prefix = "pbr" + std::to_string(i);
+ te_data["te"][prefix + "imageid"] = texture_id;
+ set_item_availability(texture_id, te_data["te"], prefix, is_creator, asset_item_map, objectp);
}
}
}
@@ -4201,6 +4248,44 @@ void LLPanelFace::onCopyTexture()
}
}
+bool get_full_permission(const LLSD& te, const std::string &prefix)
+{
+ return te.has(prefix + "itemfullperm") && te[prefix+"itemfullperm"].asBoolean();
+}
+
+bool LLPanelFace::validateInventoryItem(const LLSD& te, const std::string& prefix)
+{
+ if (te.has(prefix + "itemid"))
+ {
+ LLUUID item_id = te[prefix + "itemid"].asUUID();
+ if (item_id.notNull())
+ {
+ LLViewerInventoryItem* itemp = gInventory.getItem(item_id);
+ if (!itemp)
+ {
+ // image might be in object's inventory, but it can be not up to date
+ LLSD notif_args;
+ static std::string reason = getString("paste_error_inventory_not_found");
+ notif_args["REASON"] = reason;
+ LLNotificationsUtil::add("FacePasteFailed", notif_args);
+ return false;
+ }
+ }
+ }
+ else
+ {
+ // Item was not found on 'copy' stage
+ // Since this happened at copy, might be better to either show this
+ // at copy stage or to drop clipboard here
+ LLSD notif_args;
+ static std::string reason = getString("paste_error_inventory_not_found");
+ notif_args["REASON"] = reason;
+ LLNotificationsUtil::add("FacePasteFailed", notif_args);
+ return false;
+ }
+ return true;
+}
+
void LLPanelFace::onPasteTexture()
{
if (!mClipboardParams.has("texture"))
@@ -4265,39 +4350,49 @@ void LLPanelFace::onPasteTexture()
for (; iter != end; ++iter)
{
const LLSD& te_data = *iter;
- if (te_data.has("te") && te_data["te"].has("imageid"))
+ if (te_data.has("te"))
{
- bool full_perm = te_data["te"].has("itemfullperm") && te_data["te"]["itemfullperm"].asBoolean();
- full_perm_object &= full_perm;
- if (!full_perm)
+ if (te_data["te"].has("imageid"))
{
- if (te_data["te"].has("imageitemid"))
+ bool full_perm = get_full_permission(te_data["te"], "img");
+ full_perm_object &= full_perm;
+ if (!full_perm)
{
- LLUUID item_id = te_data["te"]["imageitemid"].asUUID();
- if (item_id.notNull())
+ if (!validateInventoryItem(te_data["te"], "img"))
{
- LLViewerInventoryItem* itemp = gInventory.getItem(item_id);
- if (!itemp)
- {
- // image might be in object's inventory, but it can be not up to date
- LLSD notif_args;
- static std::string reason = getString("paste_error_inventory_not_found");
- notif_args["REASON"] = reason;
- LLNotificationsUtil::add("FacePasteFailed", notif_args);
- return;
- }
+ return;
}
}
- else
+ }
+ if (te_data["te"].has("pbr"))
+ {
+ bool full_perm = get_full_permission(te_data["te"], "pbr");
+ full_perm_object &= full_perm;
+ if (!full_perm)
{
- // Item was not found on 'copy' stage
- // Since this happened at copy, might be better to either show this
- // at copy stage or to drop clipboard here
- LLSD notif_args;
- static std::string reason = getString("paste_error_inventory_not_found");
- notif_args["REASON"] = reason;
- LLNotificationsUtil::add("FacePasteFailed", notif_args);
- return;
+ if (!validateInventoryItem(te_data["te"], "pbr"))
+ {
+ return;
+ }
+ }
+ if (te_data["te"].has("pbr_override"))
+ {
+ for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
+ {
+ const std::string prefix = "pbr" + std::to_string(i);
+ if (te_data["te"].has(prefix + "imageid"))
+ {
+ bool full_perm = get_full_permission(te_data["te"], prefix);
+ full_perm_object &= full_perm;
+ if (!full_perm)
+ {
+ if (!validateInventoryItem(te_data["te"], prefix))
+ {
+ return;
+ }
+ }
+ }
+ }
}
}
}
@@ -4322,6 +4417,71 @@ void LLPanelFace::onPasteTexture()
selected_objects->applyToTEs(&navigate_home_func);
}
+void get_item_and_permissions(const LLUUID &id, LLViewerInventoryItem*& itemp, bool& full_perm, bool& from_library, const LLSD &data, const std::string &prefix)
+{
+ full_perm = get_full_permission(data, prefix);
+ from_library = data.has(prefix + "fromlibrary") && data.get(prefix + "fromlibrary").asBoolean();
+ LLViewerInventoryItem* itemp_res = NULL;
+
+ if (data.has(prefix + "itemid"))
+ {
+ LLUUID item_id = data.get(prefix + "itemid").asUUID();
+ if (item_id.notNull())
+ {
+ LLViewerInventoryItem* itemp = gInventory.getItem(item_id);
+ if (itemp && itemp->isFinished())
+ {
+ // dropTextureAllFaces will fail if incomplete
+ itemp_res = itemp;
+ }
+ else
+ {
+ // Theoretically shouldn't happend, but if it does happen, we
+ // might need to add a notification to user that paste will fail
+ // since inventory isn't fully loaded
+ LL_WARNS() << "Item " << item_id << " is incomplete, paste might fail silently." << LL_ENDL;
+ }
+ }
+ }
+
+ // for case when item got removed from inventory after we pressed 'copy'
+ // or texture got pasted into previous object
+ if (!itemp_res && !full_perm)
+ {
+ // Due to checks for imageitemid in LLPanelFace::onPasteTexture() this should no longer be reachable.
+ LL_INFOS() << "Item " << data.get(prefix + "itemid").asUUID() << " no longer in inventory." << LL_ENDL;
+ // Todo: fix this, we are often searching same texture multiple times (equal to number of faces)
+ // Perhaps just mPanelFace->onPasteTexture(objectp, te, &asset_to_item_id_map); ? Not pretty, but will work
+ LLViewerInventoryCategory::cat_array_t cats;
+ LLViewerInventoryItem::item_array_t items;
+ LLAssetIDMatches asset_id_matches(id);
+ gInventory.collectDescendentsIf(LLUUID::null,
+ cats,
+ items,
+ LLInventoryModel::INCLUDE_TRASH,
+ asset_id_matches);
+
+ // Extremely unreliable and perfomance unfriendly.
+ // But we need this to check permissions and it is how texture control finds items
+ for (S32 i = 0; i < items.size(); i++)
+ {
+ LLViewerInventoryItem* itemp = items[i];
+ if (itemp && itemp->isFinished())
+ {
+ // dropTextureAllFaces will fail if incomplete
+ LLPermissions item_permissions = itemp->getPermissions();
+ if (item_permissions.allowOperationBy(PERM_COPY,
+ gAgent.getID(),
+ gAgent.getGroupID()))
+ {
+ itemp_res = itemp;
+ break; // first match
+ }
+ }
+ }
+ }
+}
+
void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te)
{
LLSD te_data;
@@ -4345,77 +4505,22 @@ void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te)
if (te_data.has("te"))
{
// Texture
- bool full_perm = te_data["te"].has("itemfullperm") && te_data["te"]["itemfullperm"].asBoolean();
- bool from_library = te_data["te"].has("fromlibrary") && te_data["te"]["fromlibrary"].asBoolean();
if (te_data["te"].has("imageid"))
{
+ bool img_full_perm = false;
+ bool img_from_library = false;
const LLUUID& imageid = te_data["te"]["imageid"].asUUID(); //texture or asset id
- LLViewerInventoryItem* itemp_res = NULL;
+ LLViewerInventoryItem* img_itemp_res = NULL;
- if (te_data["te"].has("imageitemid"))
- {
- LLUUID item_id = te_data["te"]["imageitemid"].asUUID();
- if (item_id.notNull())
- {
- LLViewerInventoryItem* itemp = gInventory.getItem(item_id);
- if (itemp && itemp->isFinished())
- {
- // dropTextureAllFaces will fail if incomplete
- itemp_res = itemp;
- }
- else
- {
- // Theoretically shouldn't happend, but if it does happen, we
- // might need to add a notification to user that paste will fail
- // since inventory isn't fully loaded
- LL_WARNS() << "Item " << item_id << " is incomplete, paste might fail silently." << LL_ENDL;
- }
- }
- }
- // for case when item got removed from inventory after we pressed 'copy'
- // or texture got pasted into previous object
- if (!itemp_res && !full_perm)
- {
- // Due to checks for imageitemid in LLPanelFace::onPasteTexture() this should no longer be reachable.
- LL_INFOS() << "Item " << te_data["te"]["imageitemid"].asUUID() << " no longer in inventory." << LL_ENDL;
- // Todo: fix this, we are often searching same texture multiple times (equal to number of faces)
- // Perhaps just mPanelFace->onPasteTexture(objectp, te, &asset_to_item_id_map); ? Not pretty, but will work
- LLViewerInventoryCategory::cat_array_t cats;
- LLViewerInventoryItem::item_array_t items;
- LLAssetIDMatches asset_id_matches(imageid);
- gInventory.collectDescendentsIf(LLUUID::null,
- cats,
- items,
- LLInventoryModel::INCLUDE_TRASH,
- asset_id_matches);
-
- // Extremely unreliable and perfomance unfriendly.
- // But we need this to check permissions and it is how texture control finds items
- for (S32 i = 0; i < items.size(); i++)
- {
- LLViewerInventoryItem* itemp = items[i];
- if (itemp && itemp->isFinished())
- {
- // dropTextureAllFaces will fail if incomplete
- LLPermissions item_permissions = itemp->getPermissions();
- if (item_permissions.allowOperationBy(PERM_COPY,
- gAgent.getID(),
- gAgent.getGroupID()))
- {
- itemp_res = itemp;
- break; // first match
- }
- }
- }
- }
+ get_item_and_permissions(imageid, img_itemp_res, img_full_perm, img_from_library, te_data["te"], "img");
- if (itemp_res)
+ if (img_itemp_res)
{
if (te == -1) // all faces
{
LLToolDragAndDrop::dropTextureAllFaces(objectp,
- itemp_res,
- from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT,
+ img_itemp_res,
+ img_from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT,
LLUUID::null,
false);
}
@@ -4423,15 +4528,15 @@ void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te)
{
LLToolDragAndDrop::dropTextureOneFace(objectp,
te,
- itemp_res,
- from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT,
+ img_itemp_res,
+ img_from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT,
LLUUID::null,
false,
0);
}
}
// not an inventory item or no complete items
- else if (full_perm)
+ else if (img_full_perm)
{
// Either library, local or existed as fullperm when user made a copy
LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(imageid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE);
@@ -4459,17 +4564,65 @@ void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te)
// PBR/GLTF
if (te_data["te"].has("pbr"))
{
- objectp->setRenderMaterialID(te, te_data["te"]["pbr"].asUUID(), false /*managing our own update*/);
- tep->setGLTFRenderMaterial(nullptr);
- tep->setGLTFMaterialOverride(nullptr);
+ const LLUUID pbr_id = te_data["te"]["pbr"].asUUID();
+ bool pbr_full_perm = false;
+ bool pbr_from_library = false;
+ LLViewerInventoryItem* pbr_itemp_res = NULL;
+
+ get_item_and_permissions(pbr_id, pbr_itemp_res, pbr_full_perm, pbr_from_library, te_data["te"], "pbr");
+ bool allow = true;
+
+ // check overrides first since they don't need t be moved to inventory
if (te_data["te"].has("pbr_override"))
{
- LLGLTFMaterialList::queueApply(objectp, te, te_data["te"]["pbr"].asUUID(), te_data["te"]["pbr_override"]);
+ for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
+ {
+ const std::string prefix = "pbr" + std::to_string(i);
+ if (te_data["te"].has(prefix + "imageid"))
+ {
+ LLUUID tex_id = te_data["te"][prefix + "imageid"];
+
+ bool full_perm = false;
+ bool from_library = false;
+ LLViewerInventoryItem* itemp_res = NULL;
+ get_item_and_permissions(tex_id, itemp_res, full_perm, from_library, te_data["te"], prefix);
+ allow = full_perm;
+ if (!allow) break;
+ }
+ }
}
- else
+
+ if (allow && pbr_itemp_res)
+ {
+ if (pbr_itemp_res)
+ {
+ allow = LLToolDragAndDrop::handleDropMaterialProtections(
+ objectp,
+ pbr_itemp_res,
+ pbr_from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT,
+ pbr_id);
+ }
+ else
+ {
+ allow = pbr_full_perm;
+ }
+ }
+
+ if (allow)
{
- LLGLTFMaterialList::queueApply(objectp, te, te_data["te"]["pbr"].asUUID());
+ objectp->setRenderMaterialID(te, te_data["te"]["pbr"].asUUID(), false /*managing our own update*/);
+ tep->setGLTFRenderMaterial(nullptr);
+ tep->setGLTFMaterialOverride(nullptr);
+
+ if (te_data["te"].has("pbr_override"))
+ {
+ LLGLTFMaterialList::queueApply(objectp, te, te_data["te"]["pbr"].asUUID(), te_data["te"]["pbr_override"]);
+ }
+ else
+ {
+ LLGLTFMaterialList::queueApply(objectp, te, te_data["te"]["pbr"].asUUID());
+ }
}
}
else
diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h
index 1ee9bf2cf7..ce3dd8bdea 100644
--- a/indra/newview/llpanelface.h
+++ b/indra/newview/llpanelface.h
@@ -264,6 +264,9 @@ public: // needs to be accessible to selection manager
void onCopyTexture();
void onPasteTexture();
void onPasteTexture(LLViewerObject* objectp, S32 te);
+private:
+ // for copy/paste operations
+ bool validateInventoryItem(const LLSD& te, const std::string& prefix);
protected:
void menuDoToSelected(const LLSD& userdata);
diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp
index 132098ba99..1fa1c9587f 100644
--- a/indra/newview/llpanelprofile.cpp
+++ b/indra/newview/llpanelprofile.cpp
@@ -328,7 +328,7 @@ public:
}
const std::string verb = params[1].asString();
- if (verb == "about")
+ if (verb == "about" || verb == "mention")
{
LLAvatarActions::showProfile(avatar_id);
return true;
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 9a26ebc5f9..72295ffba9 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -3601,7 +3601,7 @@ bool process_login_success_response()
// Agent id needed for parcel info request in LLUrlEntryParcel
// to resolve parcel name.
- LLUrlEntryParcel::setAgentID(gAgentID);
+ LLUrlEntryBase::setAgentID(gAgentID);
text = response["session_id"].asString();
if(!text.empty()) gAgentSessionID.set(text);
diff --git a/indra/newview/llsurfacepatch.cpp b/indra/newview/llsurfacepatch.cpp
index 6f99da5957..875af76c10 100644
--- a/indra/newview/llsurfacepatch.cpp
+++ b/indra/newview/llsurfacepatch.cpp
@@ -210,7 +210,6 @@ void LLSurfacePatch::eval(const U32 x, const U32 y, const U32 stride, LLVector3
llassert_always(vertex && normal && tex1);
U32 surface_stride = mSurfacep->getGridsPerEdge();
- U32 texture_stride = mSurfacep->getGridsPerEdge() - 1;
U32 point_offset = x + y*surface_stride;
*normal = getNormal(x, y);
@@ -223,7 +222,7 @@ void LLSurfacePatch::eval(const U32 x, const U32 y, const U32 stride, LLVector3
// tex0 is used for ownership overlay
LLVector3 rel_pos = pos_agent - mSurfacep->getOriginAgent();
- LLVector3 tex_pos = rel_pos * (1.f / (texture_stride * mSurfacep->getMetersPerGrid()));
+ LLVector3 tex_pos = rel_pos * (1.f / (surface_stride * mSurfacep->getMetersPerGrid()));
tex0->mV[0] = tex_pos.mV[0];
tex0->mV[1] = tex_pos.mV[1];
diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp
index 35057a910a..20127f5f27 100644
--- a/indra/newview/lltexturectrl.cpp
+++ b/indra/newview/lltexturectrl.cpp
@@ -88,7 +88,8 @@ bool get_is_predefined_texture(LLUUID asset_id)
|| asset_id == DEFAULT_OBJECT_NORMAL
|| asset_id == BLANK_OBJECT_NORMAL
|| asset_id == IMG_WHITE
- || asset_id == LLUUID(SCULPT_DEFAULT_TEXTURE))
+ || asset_id == LLUUID(SCULPT_DEFAULT_TEXTURE)
+ || asset_id == BLANK_MATERIAL_ASSET_ID)
{
return true;
}
diff --git a/indra/newview/llviewerchat.cpp b/indra/newview/llviewerchat.cpp
index 8b01c4ef88..2ca2c5c07d 100644
--- a/indra/newview/llviewerchat.cpp
+++ b/indra/newview/llviewerchat.cpp
@@ -36,6 +36,7 @@
#include "llviewerregion.h"
#include "llworld.h"
#include "llinstantmessage.h" //SYSTEM_FROM
+#include "llurlregistry.h"
// LLViewerChat
LLViewerChat::font_change_signal_t LLViewerChat::sChatFontChangedSignal;
@@ -222,6 +223,13 @@ void LLViewerChat::formatChatMsg(const LLChat& chat, std::string& formated_msg)
{
std::string tmpmsg = chat.mText;
+ // show @name instead of slurl for chat mentions
+ LLUrlMatch match;
+ while (LLUrlRegistry::instance().findUrl(tmpmsg, match, LLUrlRegistryNullCallback, false, true))
+ {
+ tmpmsg.replace(match.getStart(), match.getEnd() - match.getStart() + 1, match.getLabel());
+ }
+
if(chat.mChatStyle == CHAT_STYLE_IRC)
{
formated_msg = chat.mFromName + tmpmsg.substr(3);
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 7d138dfb3b..39c5b9f218 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -58,6 +58,7 @@
#include "llfloatercamera.h"
#include "llfloatercamerapresets.h"
#include "llfloaterchangeitemthumbnail.h"
+#include "llfloaterchatmentionpicker.h"
#include "llfloaterchatvoicevolume.h"
#include "llfloaterclassified.h"
#include "llfloaterconversationlog.h"
@@ -356,6 +357,7 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("chat_voice", "floater_voice_chat_volume.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChatVoiceVolume>);
LLFloaterReg::add("change_item_thumbnail", "floater_change_item_thumbnail.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChangeItemThumbnail>);
LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterIMNearbyChat::buildFloater);
+ LLFloaterReg::add("chat_mention_picker", "floater_chat_mention_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChatMentionPicker>);
LLFloaterReg::add("classified", "floater_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterClassified>);
LLFloaterReg::add("compile_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCompileQueue>);
LLFloaterReg::add("conversation", "floater_conversation_log.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterConversationLog>);
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index 3a52908b8a..a6d50af025 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -6418,6 +6418,16 @@ LLJoint *LLVOAvatar::getJoint( S32 joint_num )
return pJoint;
}
+void LLVOAvatar::initAllJoints()
+{
+ getJointAliases();
+ for (auto& alias : mJointAliasMap)
+ {
+ mJointMap[alias.first] = mRoot->findJoint(alias.second);
+ }
+ // ignore mScreen and mRoot
+}
+
//-----------------------------------------------------------------------------
// getRiggedMeshID
//
diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h
index a2232d21a2..ab27c5752d 100644
--- a/indra/newview/llvoavatar.h
+++ b/indra/newview/llvoavatar.h
@@ -204,6 +204,7 @@ public:
virtual LLJoint* getJoint(const std::string &name);
LLJoint* getJoint(S32 num);
+ void initAllJoints();
//if you KNOW joint_num is a valid animated joint index, use getSkeletonJoint for efficiency
inline LLJoint* getSkeletonJoint(S32 joint_num) { return mSkeleton[joint_num]; }
diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp
index f23af5afa4..90ff4067f2 100644
--- a/indra/newview/llvoavatarself.cpp
+++ b/indra/newview/llvoavatarself.cpp
@@ -225,6 +225,8 @@ void LLVOAvatarSelf::initInstance()
doPeriodically(update_avatar_rez_metrics, 5.0);
doPeriodically(boost::bind(&LLVOAvatarSelf::checkStuckAppearance, this), 30.0);
+ initAllJoints(); // mesh thread uses LLVOAvatarSelf as a joint source
+
mInitFlags |= 1<<2;
}
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 3fb7a3c156..6b3dccf89c 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -860,8 +860,11 @@ void LLVOVolume::updateTextureVirtualSize(bool forced)
// animated faces get moved to a smaller partition to reduce
// side-effects of their updates (see shrinkWrap in
// LLVOVolume::animateTextures).
- mDrawable->getSpatialGroup()->dirtyGeom();
- gPipeline.markRebuild(mDrawable->getSpatialGroup());
+ if (mDrawable->getSpatialGroup())
+ {
+ mDrawable->getSpatialGroup()->dirtyGeom();
+ gPipeline.markRebuild(mDrawable->getSpatialGroup());
+ }
}
}
diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml
index bc9aef8974..c549a7dddb 100644
--- a/indra/newview/skins/default/colors.xml
+++ b/indra/newview/skins/default/colors.xml
@@ -1004,4 +1004,10 @@
<color
name="OutfitSnapshotMacMask2"
value="0.1 0.1 0.1 1"/>
+ <color
+ name="ChatMentionHighlight"
+ value="0.82 0.91 0.98 0.15" />
+ <color
+ name="ChatSelfMentionHighlight"
+ value="0.85 0.75 0.40 0.5" />
</colors>
diff --git a/indra/newview/skins/default/xui/en/floater_chat_mention_picker.xml b/indra/newview/skins/default/xui/en/floater_chat_mention_picker.xml
new file mode 100644
index 0000000000..bbad99f932
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_chat_mention_picker.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ name="chat_mention_picker"
+ title="CHOOSE RESIDENT"
+ single_instance="true"
+ can_minimize="false"
+ can_tear_off="false"
+ can_resize="true"
+ auto_close="true"
+ layout="topleft"
+ min_width="250"
+ chrome="true"
+ height="125"
+ width="310">
+ <avatar_list
+ allow_select="true"
+ follows="all"
+ height="120"
+ width="306"
+ ignore_online_status="true"
+ layout="topleft"
+ left="3"
+ keep_one_selected="true"
+ multi_select="false"
+ show_info_btn="false"
+ show_profile_btn="false"
+ show_speaking_indicator="false"
+ name="avatar_list"
+ right="-1"
+ top="2" />
+</floater>
diff --git a/indra/newview/skins/default/xui/en/panel_tools_texture.xml b/indra/newview/skins/default/xui/en/panel_tools_texture.xml
index af6a9b94d9..b7db9dec96 100644
--- a/indra/newview/skins/default/xui/en/panel_tools_texture.xml
+++ b/indra/newview/skins/default/xui/en/panel_tools_texture.xml
@@ -968,8 +968,8 @@
label_width="205"
layout="topleft"
left="10"
- min_val="-100"
- max_val="100"
+ min_val="-10000"
+ max_val="10000"
name="gltfTextureScaleU"
top_delta="34"
width="265" />
@@ -981,8 +981,8 @@
label_width="205"
layout="topleft"
left="10"
- min_val="-100"
- max_val="100"
+ min_val="-10000"
+ max_val="10000"
name="gltfTextureScaleV"
width="265" />
<spinner