summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llui/CMakeLists.txt2
-rw-r--r--indra/llui/llchatentry.cpp19
-rw-r--r--indra/llui/llchatentry.h2
-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/lltextbase.cpp4
-rw-r--r--indra/llui/lltextbase.h2
-rw-r--r--indra/llui/lltexteditor.cpp50
-rw-r--r--indra/llui/lltexteditor.h4
-rw-r--r--indra/llui/llurlentry.cpp4
-rw-r--r--indra/newview/CMakeLists.txt2
-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/llfloaterchatmentionpicker.cpp183
-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/llviewerfloaterreg.cpp2
-rw-r--r--indra/newview/skins/default/xui/en/floater_chat_mention_picker.xml31
24 files changed, 617 insertions, 24 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index a0314cb5f2..908e94b24c 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 da5afd0386..55e4beafb6 100644
--- a/indra/llui/llchatentry.cpp
+++ b/indra/llui/llchatentry.cpp
@@ -51,6 +51,7 @@ LLChatEntry::LLChatEntry(const Params& p)
mCurrentHistoryLine = mLineHistory.begin();
mAutoIndent = false;
+ mShowChatMentionPicker = true;
keepSelectionOnReturn(true);
}
@@ -249,3 +250,21 @@ void LLChatEntry::enableSingleLineMode(bool single_line_mode)
mPrevLinesCount = -1;
setWordWrap(!single_line_mode);
}
+
+LLWString LLChatEntry::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/llchatentry.h b/indra/llui/llchatentry.h
index 5621ede1e7..bb5eb8024d 100644
--- a/indra/llui/llchatentry.h
+++ b/indra/llui/llchatentry.h
@@ -68,6 +68,8 @@ public:
void enableSingleLineMode(bool single_line_mode);
boost::signals2::connection setTextExpandedCallback(const commit_signal_t::slot_type& cb);
+ LLWString getConvertedText() const;
+
private:
/**
diff --git a/indra/llui/llchatmentionhelper.cpp b/indra/llui/llchatmentionhelper.cpp
new file mode 100644
index 0000000000..98d846b947
--- /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)
+{
+ 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..4da8c8264e
--- /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);
+ 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 53f39766c6..25fe9f2556 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 6d75e9f282..2a06ded5cb 100644
--- a/indra/llui/llflatlistview.h
+++ b/indra/llui/llflatlistview.h
@@ -299,6 +299,8 @@ public:
virtual S32 notify(const LLSD& info) ;
+ 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/lltextbase.cpp b/indra/llui/lltextbase.cpp
index cb682a3625..5f62763683 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -2322,14 +2322,14 @@ 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;
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index fa8d22c819..a2895e6bd6 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -676,7 +676,7 @@ 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 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);
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index fe4cce29ab..bd726c3d49 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,18 @@ 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);
+ setCursorPos(mention_start_pos);
+
+ appendTextImpl(name_url, LLStyle::Params(), true);
+ }
+}
+
bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
{
bool handled = false;
@@ -1103,6 +1117,7 @@ void LLTextEditor::removeCharOrTab()
}
tryToShowEmojiHelper();
+ tryToShowMentionHelper();
}
else
{
@@ -1128,6 +1143,7 @@ void LLTextEditor::removeChar()
setCursorPos(mCursorPos - 1);
removeChar(mCursorPos);
tryToShowEmojiHelper();
+ tryToShowMentionHelper();
}
else
{
@@ -1189,6 +1205,7 @@ void LLTextEditor::addChar(llwchar wc)
setCursorPos(mCursorPos + addChar( mCursorPos, wc ));
tryToShowEmojiHelper();
+ tryToShowMentionHelper();
if (!mReadOnly && mAutoreplaceCallback != NULL)
{
@@ -1247,6 +1264,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() )
@@ -1884,9 +1926,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 &&
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index b2b14b01e2..e38908734f 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);
@@ -258,6 +260,7 @@ protected:
S32 remove(S32 pos, S32 length, bool group_with_next_op);
void tryToShowEmojiHelper();
+ void tryToShowMentionHelper();
void focusLostHelper();
void updateAllowingLanguageInput();
bool hasPreeditString() const;
@@ -295,6 +298,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 ce5ff0ff75..9657dc9527 100644
--- a/indra/llui/llurlentry.cpp
+++ b/indra/llui/llurlentry.cpp
@@ -580,7 +580,7 @@ LLUrlEntrySimpleSecondlifeURL::LLUrlEntrySimpleSecondlifeURL()
//
LLUrlEntryAgent::LLUrlEntryAgent()
{
- mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/\\w+",
+ mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/(mention|(?!mention)\\w+)",
boost::regex::perl|boost::regex::icase);
mMenuName = "menu_url_agent.xml";
mIcon = "Generic_Person";
@@ -784,7 +784,7 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url)
{
// *NOTE: Could look up a badge here by calling getIDStringFromUrl()
// and looking up the badge for the agent.
- return mIcon;
+ return LLStringUtil::endsWith(url, "/mention") ? std::string() : mIcon;
}
//
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index fff1597fd9..98151e2f4d 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -201,6 +201,7 @@ set(viewer_SOURCE_FILES
llfloatercamera.cpp
llfloatercamerapresets.cpp
llfloaterchangeitemthumbnail.cpp
+ llfloaterchatmentionpicker.cpp
llfloaterchatvoicevolume.cpp
llfloaterclassified.cpp
llfloatercolorpicker.cpp
@@ -870,6 +871,7 @@ set(viewer_HEADER_FILES
llfloaterbuyland.h
llfloatercamerapresets.h
llfloaterchangeitemthumbnail.h
+ llfloaterchatmentionpicker.h
llfloatercamera.h
llfloaterchatvoicevolume.h
llfloaterclassified.h
diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp
index 8f858fe4e1..f206474e71 100644
--- a/indra/newview/llavatarlist.cpp
+++ b/indra/newview/llavatarlist.cpp
@@ -141,6 +141,7 @@ LLAvatarList::LLAvatarList(const Params& p)
, mShowSpeakingIndicator(p.show_speaking_indicator)
, mShowPermissions(p.show_permissions_granted)
, mShowCompleteName(false)
+, mForceCompleteName(false)
{
setCommitOnSelectionChange(true);
@@ -177,7 +178,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
@@ -364,7 +365,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;
@@ -404,6 +405,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)
{
@@ -418,7 +424,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);
@@ -432,6 +438,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);
}
@@ -550,6 +557,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 af5bfefcde..f99da93a3d 100644
--- a/indra/newview/llavatarlist.h
+++ b/indra/newview/llavatarlist.h
@@ -96,11 +96,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();
@@ -113,6 +115,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:
@@ -127,6 +130,7 @@ private:
bool mShowSpeakingIndicator;
bool mShowPermissions;
bool mShowCompleteName;
+ bool mForceCompleteName;
LLTimer* mLITUpdateTimer; // last interaction time update timer
std::string mIconParamName;
@@ -138,6 +142,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 880910d18e..6ef45ed160 100644
--- a/indra/newview/llavatarlistitem.cpp
+++ b/indra/newview/llavatarlistitem.cpp
@@ -78,6 +78,7 @@ LLAvatarListItem::LLAvatarListItem(bool not_from_ui_factory/* = true*/)
mShowProfileBtn(true),
mShowPermissions(false),
mShowCompleteName(false),
+ mForceCompleteName(false),
mHovered(false),
mAvatarNameCacheConnection(),
mGreyOutUsername("")
@@ -324,13 +325,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)
@@ -417,8 +416,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 2e4c597d30..2ec7a41055 100644
--- a/indra/newview/llavatarlistitem.h
+++ b/indra/newview/llavatarlistitem.h
@@ -106,7 +106,7 @@ public:
void setShowPermissions(bool show) { mShowPermissions = 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;
@@ -220,6 +220,7 @@ private:
bool mHovered;
bool mShowCompleteName;
+ bool mForceCompleteName;
std::string mGreyOutUsername;
void fetchAvatarName();
diff --git a/indra/newview/llfloaterchatmentionpicker.cpp b/indra/newview/llfloaterchatmentionpicker.cpp
new file mode 100644
index 0000000000..dda2cd83f6
--- /dev/null
+++ b/indra/newview/llfloaterchatmentionpicker.cpp
@@ -0,0 +1,183 @@
+/**
+ * @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:
+ 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 50e765c236..96aac8c1e6 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/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 95c2a77ba8..4d9c2f3281 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"
@@ -353,6 +354,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/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>