diff options
author | Maxim Nikolenko <maximnproductengine@lindenlab.com> | 2025-04-25 22:31:53 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-25 22:31:53 +0300 |
commit | 17e974689c76a2b76cf27e3a1c9dde371f4f877f (patch) | |
tree | ef2c959fa556b19407d72ccc581cc9e55d1bb525 | |
parent | 25c11f09655db011d27fec6887854d1200f63b19 (diff) | |
parent | 1eb34b43fb88a0b7551d9083ab46ba7df2feab14 (diff) |
Merge pull request #3943 from secondlife/maxim/2025.04-chat-mention
#3758 initial chat mention support
35 files changed, 926 insertions, 84 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 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 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/llappviewer.cpp b/indra/newview/llappviewer.cpp index edc70030b4..7e7aa521d3 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -475,7 +475,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 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..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 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/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 08605f7cf4..c81746a48a 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 3973036cc6..4f60f98b49 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -3566,7 +3566,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/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 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/colors.xml b/indra/newview/skins/default/colors.xml index f0af4acf20..cb190d3d34 100644 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -1000,4 +1000,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> |