#3758 initial chat mention support
parent
90c7684112
commit
3e46d707a2
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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()); }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue