From 5608c3998df99c9ea075c58f0f45fb23617ec2ed Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 8 Oct 2024 22:10:50 +0300 Subject: viewer#2270 The "More" button does not close the "Choose emoji" floater --- indra/llui/llemojihelper.cpp | 11 +++++++++++ indra/llui/llemojihelper.h | 7 +++++++ indra/llui/lltexteditor.cpp | 8 ++++++++ indra/llui/lltexteditor.h | 1 + 4 files changed, 27 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp index b9441a9c91..b2c59ce775 100644 --- a/indra/llui/llemojihelper.cpp +++ b/indra/llui/llemojihelper.cpp @@ -99,6 +99,7 @@ void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, c LLFloater* pHelperFloater = LLFloaterReg::getInstance(DEFAULT_EMOJI_HELPER_FLOATER); mHelperHandle = pHelperFloater->getHandle(); mHelperCommitConn = pHelperFloater->setCommitCallback(std::bind([&](const LLSD& sdValue) { onCommitEmoji(utf8str_to_wstring(sdValue.asStringRef())[0]); }, std::placeholders::_2)); + mHelperCloseConn = pHelperFloater->setCloseCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCloseHelper(ctrl, param); }); } setHostCtrl(hostctrl_p); mEmojiCommitCb = cb; @@ -148,6 +149,16 @@ void LLEmojiHelper::onCommitEmoji(llwchar emoji) } } +void LLEmojiHelper::onCloseHelper(LLUICtrl* ctrl, const LLSD& param) +{ + mCloseSignal(ctrl, param); +} + +boost::signals2::connection LLEmojiHelper::setCloseCallback(const commit_signal_t::slot_type& cb) +{ + return mCloseSignal.connect(cb); +} + void LLEmojiHelper::setHostCtrl(LLUICtrl* hostctrl_p) { const LLUICtrl* pCurHostCtrl = mHostHandle.get(); diff --git a/indra/llui/llemojihelper.h b/indra/llui/llemojihelper.h index 2834b06061..26840eef94 100644 --- a/indra/llui/llemojihelper.h +++ b/indra/llui/llemojihelper.h @@ -51,16 +51,23 @@ public: // Eventing bool handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask); void onCommitEmoji(llwchar emoji); + void onCloseHelper(LLUICtrl* ctrl, const LLSD& param); + + typedef boost::signals2::signal commit_signal_t; + boost::signals2::connection setCloseCallback(const commit_signal_t::slot_type& cb); protected: LLUICtrl* getHostCtrl() const { return mHostHandle.get(); } void setHostCtrl(LLUICtrl* hostctrl_p); private: + commit_signal_t mCloseSignal; + LLHandle mHostHandle; LLHandle mHelperHandle; boost::signals2::connection mHostCtrlFocusLostConn; boost::signals2::connection mHelperCommitConn; + boost::signals2::connection mHelperCloseConn; std::function mEmojiCommitCb; bool mIsHideDisabled; }; diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 3537c764b9..6c59ed0fd9 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -1211,6 +1211,14 @@ void LLTextEditor::showEmojiHelper() LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, LLStringUtil::null, cb); } +void LLTextEditor::hideEmojiHelper() +{ + if (mShowEmojiHelper) + { + LLEmojiHelper::instance().hideHelper(this); + } +} + void LLTextEditor::tryToShowEmojiHelper() { if (mReadOnly || !mShowEmojiHelper) diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index e9e7070414..b2b14b01e2 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -207,6 +207,7 @@ public: bool getShowContextMenu() const { return mShowContextMenu; } void showEmojiHelper(); + void hideEmojiHelper(); void setShowEmojiHelper(bool show); bool getShowEmojiHelper() const { return mShowEmojiHelper; } -- cgit v1.3 From ecfe76ac125e484a78f1cba67314f5195d8fdf71 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 21 Mar 2025 21:40:38 +0200 Subject: vp#404 Improve URL filter --- indra/llui/llurlentry.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 3cc0c05ffa..77f132e9d8 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -221,6 +221,16 @@ bool LLUrlEntryBase::isWikiLinkCorrect(const std::string &labeled_url) const }, L'\u002F'); // Solidus + std::replace_if(wlabel.begin(), + wlabel.end(), + [](const llwchar& chr) + { + return // Not a decomposition, but suficiently similar + (chr == L'\u04BA') // "Cyrillic Capital Letter Shha" + || (chr == L'\u04BB'); // "Cyrillic Small Letter Shha" + }, + L'\u0068'); // "Latin Small Letter H" + std::string label = wstring_to_utf8str(wlabel); if ((label.find(".com") != std::string::npos || label.find("www.") != std::string::npos) -- cgit v1.3 From 67921fae6d9f32464b42b6b3086de109c0761532 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 16 Apr 2025 22:39:02 +0300 Subject: #3922 out_of_range crash in preeditor --- indra/llui/lllineeditor.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 66b274c33f..45dab88e87 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -2505,9 +2505,24 @@ void LLLineEditor::resetPreedit() if (hasPreeditString()) { const S32 preedit_pos = mPreeditPositions.front(); - mText.erase(preedit_pos, mPreeditPositions.back() - preedit_pos); - mText.insert(preedit_pos, mPreeditOverwrittenWString); - setCursor(preedit_pos); + const S32 end = mPreeditPositions.back(); + const S32 len = end - preedit_pos; + const S32 size = mText.length(); + if (preedit_pos < size + && end <= size + && preedit_pos >= 0 + && len > 0) + { + mText.erase(preedit_pos, len); + mText.insert(preedit_pos, mPreeditOverwrittenWString); + setCursor(preedit_pos); + } + else + { + LL_WARNS() << "Index out of bounds. Start: " << preedit_pos + << ", end:" << end + << ", full string length: " << size << LL_ENDL; + } mPreeditWString.clear(); mPreeditOverwrittenWString.clear(); -- cgit v1.3 From 90c7684112714fd5ca2c8d73d8ca9bef3fc1e5d6 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Thu, 10 Apr 2025 17:57:16 +0300 Subject: #3758 add support for highlighted segments --- indra/llui/llstyle.cpp | 8 +- indra/llui/llstyle.h | 13 +++- indra/llui/lltextbase.cpp | 138 +++++++++++++++++++++++++++++++-- indra/llui/lltextbase.h | 8 +- indra/llui/llurlentry.cpp | 56 +++++++++---- indra/llui/llurlentry.h | 31 +++++--- indra/llui/llurlmatch.cpp | 10 ++- indra/llui/llurlmatch.h | 17 +++- indra/llui/llurlregistry.cpp | 11 ++- indra/newview/llappviewer.cpp | 2 +- indra/newview/llpanelprofile.cpp | 2 +- indra/newview/llstartup.cpp | 2 +- indra/newview/skins/default/colors.xml | 6 ++ 13 files changed, 247 insertions(+), 57 deletions(-) (limited to 'indra/llui') 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..2c86eb6db7 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -43,12 +43,14 @@ public: Optional drop_shadow; Optional color, readonly_color, - selected_color; + selected_color, + highlight_bg_color; Optional alpha; Optional font; Optional image; Optional link_href; Optional is_link; + Optional draw_highlight_bg; Params(); }; LLStyle(const Params& p = Params()); @@ -84,6 +86,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 +96,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 +119,13 @@ private: LLUIColor mColor; LLUIColor mReadOnlyColor; LLUIColor mSelectedColor; + LLUIColor mHighlightBgColor; const LLFontGL* mFont; LLPointer mImagep; F32 mAlpha; bool mVisible; bool mIsLink; + bool mDrawHighlightBg; }; typedef LLPointer LLStyleSP; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 41e7094163..cb682a3625 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -460,6 +460,62 @@ std::vector LLTextBase::getSelectionRects() return selection_rects; } +std::vector> LLTextBase::getHighlightedBgRects() +{ + std::vector> 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> highlight_rects = getHighlightedBgRects(); + + if (highlight_rects.empty()) + return; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLRect content_display_rect = getVisibleDocumentRect(); + + for (std::vector>::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(); @@ -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(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); } diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 76d4e160af..fa8d22c819 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 #include @@ -607,6 +608,7 @@ protected: bool operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const; }; typedef std::multiset segment_set_t; + typedef LLUrlMatch::EUnderlineLink e_underline; // member functions LLTextBase(const Params &p); @@ -620,12 +622,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 @@ -674,7 +677,7 @@ protected: 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 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: @@ -685,6 +688,7 @@ protected: } std::vector getSelectionRects(); + std::vector> getHighlightedBgRects(); protected: // text segmentation and flow diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 77f132e9d8..ce5ff0ff75 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,24 @@ std::string LLUrlEntryAgent::getTooltip(const std::string &string) const return LLTrans::getString("TooltipAgentUrl"); } -bool LLUrlEntryAgent::underlineOnHoverOnly(const std::string &string) const +LLUrlMatch::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 LLUrlMatch::EUnderlineLink::UNDERLINE_ON_HOVER; + } + else if (LLStringUtil::endsWith(url, "/mention")) + { + return LLUrlMatch::EUnderlineLink::UNDERLINE_NEVER; + } + return LLUrlMatch::EUnderlineLink::UNDERLINE_ALWAYS; +} + +bool LLUrlEntryAgent::getSkipProfileIcon(const std::string& string) const +{ + std::string url = getUrl(string); + return (LLStringUtil::endsWith(url, "/mention")) ? true : false; } std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) @@ -712,11 +725,19 @@ 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"); + if (LLStringUtil::endsWith(url, "/mention")) + { + 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; } @@ -751,6 +772,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; } @@ -823,7 +848,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 +984,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 +1062,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 +1395,17 @@ std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const return LLUrlEntryBase::getTooltip(string); } -bool LLUrlEntrySLLabel::underlineOnHoverOnly(const std::string &string) const +LLUrlMatch::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 +1469,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..af5b8f5d83 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -34,6 +34,7 @@ #include "llavatarname.h" #include "llhost.h" // for resolving parcel name by parcel id +#include "llurlmatch.h" #include #include @@ -85,7 +86,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 +97,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 LLUrlMatch::EUnderlineLink getUnderline(const std::string& string) const { return LLUrlMatch::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 +111,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 +134,8 @@ protected: std::string mMenuName; std::string mTooltip; std::multimap mObservers; + + static LLUUID sAgentID; }; /// @@ -224,9 +230,12 @@ 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; + + LLUrlMatch::EUnderlineLink getUnderline(const std::string& string) const; + bool getSkipProfileIcon(const std::string& string) const; + protected: /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); private: @@ -257,7 +266,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 +348,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 +420,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 +493,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; + LLUrlMatch::EUnderlineLink getUnderline(const std::string& string) const; }; /// @@ -510,7 +517,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..3e61abe118 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(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, EUnderlineLink 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 ba822fbda6..b05b3f2c82 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -47,6 +47,13 @@ class LLUrlMatch public: LLUrlMatch(); + enum EUnderlineLink + { + UNDERLINE_ALWAYS = 0, + UNDERLINE_ON_HOVER, + UNDERLINE_NEVER + }; + /// return true if this object does not contain a valid Url match yet bool empty() const { return mUrl.empty(); } @@ -80,18 +87,19 @@ 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; } + EUnderlineLink 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); + EUnderlineLink underline = UNDERLINE_ALWAYS, bool trusted = false, bool skip_icon = false); const LLUUID& getID() const { return mID; } private: @@ -106,8 +114,9 @@ private: std::string mLocation; LLUUID mID; LLStyle::Params mStyle; - bool mUnderlineOnHoverOnly; + EUnderlineLink mUnderline; bool mTrusted; + bool mSkipProfileIcon; }; #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index cec1ddfc57..6e6e3be9b4 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -233,12 +233,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 +275,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/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/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/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 @@ + + -- cgit v1.3 From 3e46d707a243e91046b3ab0af8f844d7f40f77b4 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 18 Apr 2025 17:48:02 +0300 Subject: #3758 initial chat mention support --- indra/llui/CMakeLists.txt | 2 + indra/llui/llchatentry.cpp | 19 +++ indra/llui/llchatentry.h | 2 + indra/llui/llchatmentionhelper.cpp | 151 +++++++++++++++++ indra/llui/llchatmentionhelper.h | 66 ++++++++ indra/llui/llflatlistview.cpp | 6 +- indra/llui/llflatlistview.h | 4 + indra/llui/lltextbase.cpp | 4 +- indra/llui/lltextbase.h | 2 +- indra/llui/lltexteditor.cpp | 50 +++++- indra/llui/lltexteditor.h | 4 + indra/llui/llurlentry.cpp | 4 +- indra/newview/CMakeLists.txt | 2 + indra/newview/llavatarlist.cpp | 18 +- indra/newview/llavatarlist.h | 7 +- indra/newview/llavatarlistitem.cpp | 17 +- indra/newview/llavatarlistitem.h | 3 +- indra/newview/llfloaterchatmentionpicker.cpp | 183 +++++++++++++++++++++ indra/newview/llfloaterchatmentionpicker.h | 58 +++++++ indra/newview/llfloaterimnearbychat.cpp | 2 +- indra/newview/llfloaterimsession.cpp | 2 +- indra/newview/llfloaterimsessiontab.cpp | 2 + indra/newview/llviewerfloaterreg.cpp | 2 + .../default/xui/en/floater_chat_mention_picker.xml | 31 ++++ 24 files changed, 617 insertions(+), 24 deletions(-) create mode 100644 indra/llui/llchatmentionhelper.cpp create mode 100644 indra/llui/llchatmentionhelper.h create mode 100644 indra/newview/llfloaterchatmentionpicker.cpp create mode 100644 indra/newview/llfloaterchatmentionpicker.h create mode 100644 indra/newview/skins/default/xui/en/floater_chat_mention_picker.xml (limited to 'indra/llui') diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index a0314cb5f2..908e94b24c 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -18,6 +18,7 @@ set(llui_SOURCE_FILES llbadgeowner.cpp llbutton.cpp llchatentry.cpp + llchatmentionhelper.cpp llcheckboxctrl.cpp llclipboard.cpp llcombobox.cpp @@ -130,6 +131,7 @@ set(llui_HEADER_FILES llcallbackmap.h llchatentry.h llchat.h + llchatmentionhelper.h llcheckboxctrl.h llclipboard.h llcombobox.h diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp index da5afd0386..55e4beafb6 100644 --- a/indra/llui/llchatentry.cpp +++ b/indra/llui/llchatentry.cpp @@ -51,6 +51,7 @@ LLChatEntry::LLChatEntry(const Params& p) mCurrentHistoryLine = mLineHistory.begin(); mAutoIndent = false; + mShowChatMentionPicker = true; keepSelectionOnReturn(true); } @@ -249,3 +250,21 @@ void LLChatEntry::enableSingleLineMode(bool single_line_mode) mPrevLinesCount = -1; setWordWrap(!single_line_mode); } + +LLWString LLChatEntry::getConvertedText() const +{ + LLWString text = getWText(); + S32 diff = 0; + for (auto segment : mSegments) + { + if (segment && segment->getStyle() && segment->getStyle()->getDrawHighlightBg()) + { + S32 seg_length = segment->getEnd() - segment->getStart(); + std::string slurl = segment->getStyle()->getLinkHREF(); + + text.replace(segment->getStart() + diff, seg_length, utf8str_to_wstring(slurl)); + diff += (S32)slurl.size() - seg_length; + } + } + return text; +} diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index 5621ede1e7..bb5eb8024d 100644 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -68,6 +68,8 @@ public: void enableSingleLineMode(bool single_line_mode); boost::signals2::connection setTextExpandedCallback(const commit_signal_t::slot_type& cb); + LLWString getConvertedText() const; + private: /** diff --git a/indra/llui/llchatmentionhelper.cpp b/indra/llui/llchatmentionhelper.cpp new file mode 100644 index 0000000000..98d846b947 --- /dev/null +++ b/indra/llui/llchatmentionhelper.cpp @@ -0,0 +1,151 @@ +/** +* @file llchatmentionhelper.cpp +* +* $LicenseInfo:firstyear=2025&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2025, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "llchatmentionhelper.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "lluictrl.h" + +constexpr char CHAT_MENTION_HELPER_FLOATER[] = "chat_mention_picker"; + +bool LLChatMentionHelper::isActive(const LLUICtrl* ctrl) const +{ + return mHostHandle.get() == ctrl; +} + +bool LLChatMentionHelper::isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos) +{ + if (cursor_pos <= 0 || cursor_pos > static_cast(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 cb) +{ + if (mHelperHandle.isDead()) + { + LLFloater* av_picker_floater = LLFloaterReg::getInstance(CHAT_MENTION_HELPER_FLOATER); + mHelperHandle = av_picker_floater->getHandle(); + mHelperCommitConn = av_picker_floater->setCommitCallback([&](LLUICtrl* ctrl, const LLSD& param) { onCommitName(param.asString()); }); + } + setHostCtrl(host_ctrl); + mNameCommitCb = cb; + + S32 floater_x, floater_y; + if (!host_ctrl->localPointToOtherView(local_x, local_y, &floater_x, &floater_y, gFloaterView)) + { + LL_WARNS() << "Cannot show helper for non-floater controls." << LL_ENDL; + return; + } + + LLFloater* av_picker_floater = mHelperHandle.get(); + LLRect rect = av_picker_floater->getRect(); + rect.setLeftTopAndSize(floater_x, floater_y + rect.getHeight(), rect.getWidth(), rect.getHeight()); + av_picker_floater->setRect(rect); + av_picker_floater->openFloater(LLSD().with("av_name", av_name)); +} + +void LLChatMentionHelper::hideHelper(const LLUICtrl* ctrl) +{ + if ((ctrl && !isActive(ctrl))) + { + return; + } + setHostCtrl(nullptr); +} + +bool LLChatMentionHelper::handleKey(const LLUICtrl* ctrl, KEY key, MASK mask) +{ + if (mHelperHandle.isDead() || !isActive(ctrl)) + { + return false; + } + + return mHelperHandle.get()->handleKey(key, mask, true); +} + +void LLChatMentionHelper::onCommitName(std::string name_url) +{ + if (!mHostHandle.isDead() && mNameCommitCb) + { + mNameCommitCb(name_url); + } +} + +void LLChatMentionHelper::setHostCtrl(LLUICtrl* host_ctrl) +{ + const LLUICtrl* pCurHostCtrl = mHostHandle.get(); + if (pCurHostCtrl != host_ctrl) + { + mHostCtrlFocusLostConn.disconnect(); + mHostHandle.markDead(); + mNameCommitCb = {}; + + if (!mHelperHandle.isDead()) + { + mHelperHandle.get()->closeFloater(); + } + + if (host_ctrl) + { + mHostHandle = host_ctrl->getHandle(); + mHostCtrlFocusLostConn = host_ctrl->setFocusLostCallback(std::bind([&]() { hideHelper(getHostCtrl()); })); + } + } +} diff --git a/indra/llui/llchatmentionhelper.h b/indra/llui/llchatmentionhelper.h new file mode 100644 index 0000000000..4da8c8264e --- /dev/null +++ b/indra/llui/llchatmentionhelper.h @@ -0,0 +1,66 @@ +/** +* @file llchatmentionhelper.h +* @brief Header file for LLChatMentionHelper +* +* $LicenseInfo:firstyear=2025&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2025, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#pragma once + +#include "llhandle.h" +#include "llsingleton.h" + +#include + +class LLFloater; +class LLUICtrl; + +class LLChatMentionHelper : public LLSingleton +{ + 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 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 av_names) { mAvatarNames = av_names; } + +protected: + void setHostCtrl(LLUICtrl* host_ctrl); + LLUICtrl* getHostCtrl() const { return mHostHandle.get(); } + +private: + LLHandle mHostHandle; + LLHandle mHelperHandle; + boost::signals2::connection mHostCtrlFocusLostConn; + boost::signals2::connection mHelperCommitConn; + std::function mNameCommitCb; + + std::vector mAvatarNames; +}; diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index 53f39766c6..25fe9f2556 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -459,6 +459,7 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p) , mNoItemsCommentTextbox(NULL) , mIsConsecutiveSelection(false) , mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape) + , mFocusOnItemClicked(true) { mBorderThickness = getBorderWidth(); @@ -610,7 +611,10 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) return; } - setFocus(true); + if (mFocusOnItemClicked) + { + setFocus(true); + } bool select_item = !isSelected(item_pair); diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 6d75e9f282..2a06ded5cb 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -299,6 +299,8 @@ public: virtual S32 notify(const LLSD& info) ; + void setFocusOnItemClicked(bool b) { mFocusOnItemClicked = b; } + virtual ~LLFlatListView(); protected: @@ -423,6 +425,8 @@ private: bool mKeepSelectionVisibleOnReshape; + bool mFocusOnItemClicked; + /** All pairs of the list */ pairs_list_t mItemPairs; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index cb682a3625..5f62763683 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -2322,14 +2322,14 @@ static LLUIImagePtr image_from_icon_name(const std::string& icon_name) } -void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params) +void LLTextBase::appendTextImpl(const std::string& new_text, const LLStyle::Params& input_params, bool force_slurl) { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; LLStyle::Params style_params(getStyleParams()); style_params.overwriteFrom(input_params); S32 part = (S32)LLTextParser::WHOLE; - if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). + if ((mParseHTML || force_slurl) && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). { S32 start=0,end=0; LLUrlMatch match; diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index fa8d22c819..a2895e6bd6 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -676,7 +676,7 @@ protected: // avatar names are looked up. void replaceUrl(const std::string &url, const std::string &label, const std::string& icon); - void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params()); + void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params(), bool force_slurl = false); void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link = e_underline::UNDERLINE_ALWAYS); S32 normalizeUri(std::string& uri); diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index fe4cce29ab..bd726c3d49 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -60,6 +60,7 @@ #include "llurlregistry.h" #include "lltooltip.h" #include "llmenugl.h" +#include "llchatmentionhelper.h" #include #include "llcombobox.h" @@ -270,6 +271,7 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : mPrevalidator(p.prevalidator()), mShowContextMenu(p.show_context_menu), mShowEmojiHelper(p.show_emoji_helper), + mShowChatMentionPicker(false), mEnableTooltipPaste(p.enable_tooltip_paste), mPassDelete(false), mKeepSelectionOnReturn(false) @@ -714,6 +716,18 @@ void LLTextEditor::handleEmojiCommit(llwchar emoji) } } +void LLTextEditor::handleMentionCommit(std::string name_url) +{ + S32 mention_start_pos; + if (LLChatMentionHelper::instance().isCursorInNameMention(getWText(), mCursorPos, &mention_start_pos)) + { + remove(mention_start_pos, mCursorPos - mention_start_pos, true); + setCursorPos(mention_start_pos); + + appendTextImpl(name_url, LLStyle::Params(), true); + } +} + bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) { bool handled = false; @@ -1103,6 +1117,7 @@ void LLTextEditor::removeCharOrTab() } tryToShowEmojiHelper(); + tryToShowMentionHelper(); } else { @@ -1128,6 +1143,7 @@ void LLTextEditor::removeChar() setCursorPos(mCursorPos - 1); removeChar(mCursorPos); tryToShowEmojiHelper(); + tryToShowMentionHelper(); } else { @@ -1189,6 +1205,7 @@ void LLTextEditor::addChar(llwchar wc) setCursorPos(mCursorPos + addChar( mCursorPos, wc )); tryToShowEmojiHelper(); + tryToShowMentionHelper(); if (!mReadOnly && mAutoreplaceCallback != NULL) { @@ -1247,6 +1264,31 @@ void LLTextEditor::tryToShowEmojiHelper() } } +void LLTextEditor::tryToShowMentionHelper() +{ + if (mReadOnly || !mShowChatMentionPicker) + return; + + S32 mention_start_pos; + LLWString text(getWText()); + if (LLChatMentionHelper::instance().isCursorInNameMention(text, mCursorPos, &mention_start_pos)) + { + const LLRect cursor_rect(getLocalRectFromDocIndex(mention_start_pos)); + std::string name_part(wstring_to_utf8str(text.substr(mention_start_pos, mCursorPos - mention_start_pos))); + name_part.erase(0, 1); + auto cb = [this](std::string name_url) + { + handleMentionCommit(name_url); + }; + LLChatMentionHelper::instance().showHelper(this, cursor_rect.mLeft, cursor_rect.mTop, name_part, cb); + } + else + { + LLChatMentionHelper::instance().hideHelper(); + } +} + + void LLTextEditor::addLineBreakChar(bool group_together) { if( !getEnabled() ) @@ -1884,9 +1926,13 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask ) } else { - if (!mReadOnly && mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) + if (!mReadOnly) { - return true; + if ((mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) || + (mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask))) + { + return true; + } } if (mEnableTooltipPaste && diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index b2b14b01e2..e38908734f 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -95,6 +95,8 @@ public: void insertEmoji(llwchar emoji); void handleEmojiCommit(llwchar emoji); + void handleMentionCommit(std::string name_url); + // mousehandler overrides virtual bool handleMouseDown(S32 x, S32 y, MASK mask); virtual bool handleMouseUp(S32 x, S32 y, MASK mask); @@ -258,6 +260,7 @@ protected: S32 remove(S32 pos, S32 length, bool group_with_next_op); void tryToShowEmojiHelper(); + void tryToShowMentionHelper(); void focusLostHelper(); void updateAllowingLanguageInput(); bool hasPreeditString() const; @@ -295,6 +298,7 @@ protected: bool mAutoIndent; bool mParseOnTheFly; + bool mShowChatMentionPicker; void updateLinkSegments(); void keepSelectionOnReturn(bool keep) { mKeepSelectionOnReturn = keep; } diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index ce5ff0ff75..9657dc9527 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -580,7 +580,7 @@ LLUrlEntrySimpleSecondlifeURL::LLUrlEntrySimpleSecondlifeURL() // LLUrlEntryAgent::LLUrlEntryAgent() { - mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/\\w+", + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/(mention|(?!mention)\\w+)", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_agent.xml"; mIcon = "Generic_Person"; @@ -784,7 +784,7 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url) { // *NOTE: Could look up a badge here by calling getIDStringFromUrl() // and looking up the badge for the agent. - return mIcon; + return LLStringUtil::endsWith(url, "/mention") ? std::string() : mIcon; } // diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index fff1597fd9..98151e2f4d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -201,6 +201,7 @@ set(viewer_SOURCE_FILES llfloatercamera.cpp llfloatercamerapresets.cpp llfloaterchangeitemthumbnail.cpp + llfloaterchatmentionpicker.cpp llfloaterchatvoicevolume.cpp llfloaterclassified.cpp llfloatercolorpicker.cpp @@ -870,6 +871,7 @@ set(viewer_HEADER_FILES llfloaterbuyland.h llfloatercamerapresets.h llfloaterchangeitemthumbnail.h + llfloaterchatmentionpicker.h llfloatercamera.h llfloaterchatvoicevolume.h llfloaterclassified.h diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp index 8f858fe4e1..f206474e71 100644 --- a/indra/newview/llavatarlist.cpp +++ b/indra/newview/llavatarlist.cpp @@ -141,6 +141,7 @@ LLAvatarList::LLAvatarList(const Params& p) , mShowSpeakingIndicator(p.show_speaking_indicator) , mShowPermissions(p.show_permissions_granted) , mShowCompleteName(false) +, mForceCompleteName(false) { setCommitOnSelectionChange(true); @@ -177,7 +178,7 @@ void LLAvatarList::setShowIcons(std::string param_name) std::string LLAvatarList::getAvatarName(LLAvatarName av_name) { - return mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName(); + return mShowCompleteName? av_name.getCompleteName(false, mForceCompleteName) : av_name.getDisplayName(); } // virtual @@ -364,7 +365,7 @@ void LLAvatarList::updateAvatarNames() for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) { LLAvatarListItem* item = static_cast(*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(item1); diff --git a/indra/newview/llavatarlist.h b/indra/newview/llavatarlist.h index af5bfefcde..f99da93a3d 100644 --- a/indra/newview/llavatarlist.h +++ b/indra/newview/llavatarlist.h @@ -96,11 +96,13 @@ public: boost::signals2::connection setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb); + boost::signals2::connection setItemClickedCallback(const mouse_signal_t::slot_type& cb); + virtual S32 notifyParent(const LLSD& info); void handleDisplayNamesOptionChanged(); - void setShowCompleteName(bool show) { mShowCompleteName = show;}; + void setShowCompleteName(bool show, bool force = false) { mShowCompleteName = show; mForceCompleteName = force; }; protected: void refresh(); @@ -113,6 +115,7 @@ protected: void updateLastInteractionTimes(); void rebuildNames(); void onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask); + void onItemClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask); void updateAvatarNames(); private: @@ -127,6 +130,7 @@ private: bool mShowSpeakingIndicator; bool mShowPermissions; bool mShowCompleteName; + bool mForceCompleteName; LLTimer* mLITUpdateTimer; // last interaction time update timer std::string mIconParamName; @@ -138,6 +142,7 @@ private: commit_signal_t mRefreshCompleteSignal; mouse_signal_t mItemDoubleClickSignal; + mouse_signal_t mItemClickedSignal; }; /** Abstract comparator for avatar items */ diff --git a/indra/newview/llavatarlistitem.cpp b/indra/newview/llavatarlistitem.cpp index 880910d18e..6ef45ed160 100644 --- a/indra/newview/llavatarlistitem.cpp +++ b/indra/newview/llavatarlistitem.cpp @@ -78,6 +78,7 @@ LLAvatarListItem::LLAvatarListItem(bool not_from_ui_factory/* = true*/) mShowProfileBtn(true), mShowPermissions(false), mShowCompleteName(false), + mForceCompleteName(false), mHovered(false), mAvatarNameCacheConnection(), mGreyOutUsername("") @@ -324,13 +325,11 @@ void LLAvatarListItem::setShowProfileBtn(bool show) void LLAvatarListItem::showSpeakingIndicator(bool visible) { - // Already done? Then do nothing. - if (mSpeakingIndicator->getVisible() == (bool)visible) - return; -// Disabled to not contradict with SpeakingIndicatorManager functionality. EXT-3976 -// probably this method should be totally removed. -// mSpeakingIndicator->setVisible(visible); -// updateChildren(); + if (mSpeakingIndicator) + { + mSpeakingIndicator->setIsActiveChannel(visible); + mSpeakingIndicator->setShowParticipantsSpeaking(visible); + } } void LLAvatarListItem::setAvatarIconVisible(bool visible) @@ -417,8 +416,8 @@ void LLAvatarListItem::onAvatarNameCache(const LLAvatarName& av_name) mAvatarNameCacheConnection.disconnect(); mGreyOutUsername = ""; - std::string name_string = mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName(); - if(av_name.getCompleteName() != av_name.getUserName()) + std::string name_string = mShowCompleteName? av_name.getCompleteName(false, mForceCompleteName) : av_name.getDisplayName(); + if(av_name.getCompleteName(false, mForceCompleteName) != av_name.getUserName()) { mGreyOutUsername = "[ " + av_name.getUserName(true) + " ]"; LLStringUtil::toLower(mGreyOutUsername); diff --git a/indra/newview/llavatarlistitem.h b/indra/newview/llavatarlistitem.h index 2e4c597d30..2ec7a41055 100644 --- a/indra/newview/llavatarlistitem.h +++ b/indra/newview/llavatarlistitem.h @@ -106,7 +106,7 @@ public: void setShowPermissions(bool show) { mShowPermissions = show; }; void showLastInteractionTime(bool show); void setAvatarIconVisible(bool visible); - void setShowCompleteName(bool show) { mShowCompleteName = show;}; + void setShowCompleteName(bool show, bool force = false) { mShowCompleteName = show; mForceCompleteName = force;}; const LLUUID& getAvatarId() const; std::string getAvatarName() const; @@ -220,6 +220,7 @@ private: bool mHovered; bool mShowCompleteName; + bool mForceCompleteName; std::string mGreyOutUsername; void fetchAvatarName(); diff --git a/indra/newview/llfloaterchatmentionpicker.cpp b/indra/newview/llfloaterchatmentionpicker.cpp new file mode 100644 index 0000000000..dda2cd83f6 --- /dev/null +++ b/indra/newview/llfloaterchatmentionpicker.cpp @@ -0,0 +1,183 @@ +/** + * @file llfloaterchatmentionpicker.cpp + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterchatmentionpicker.h" + +#include "llavatarlist.h" +#include "llfloaterimcontainer.h" +#include "llchatmentionhelper.h" +#include "llparticipantlist.h" + +LLUUID LLFloaterChatMentionPicker::sSessionID(LLUUID::null); + +LLFloaterChatMentionPicker::LLFloaterChatMentionPicker(const LLSD& key) +: LLFloater(key), mAvatarList(NULL) +{ + // This floater should hover on top of our dependent (with the dependent having the focus) + setFocusStealsFrontmost(false); + setBackgroundVisible(false); + setAutoFocus(false); +} + +bool LLFloaterChatMentionPicker::postBuild() +{ + mAvatarList = getChild("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(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(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(*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(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 av_names; + for (auto& id : avatar_ids) + { + LLAvatarName av_name; + LLAvatarNameCache::get(id, &av_name); + av_names.push_back(utf8str_tolower(av_name.getAccountName())); + av_names.push_back(utf8str_tolower(av_name.getDisplayName())); + } + LLChatMentionHelper::instance().updateAvatarList(av_names); +} diff --git a/indra/newview/llfloaterchatmentionpicker.h b/indra/newview/llfloaterchatmentionpicker.h new file mode 100644 index 0000000000..8d221d7a89 --- /dev/null +++ b/indra/newview/llfloaterchatmentionpicker.h @@ -0,0 +1,58 @@ +/** + * @file llfloaterchatmentionpicker.h + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERCHATMENTIONPICKER_H +#define LLFLOATERCHATMENTIONPICKER_H + +#include "llfloater.h" + +class LLAvatarList; + +class LLFloaterChatMentionPicker : public LLFloater +{ +public: + LLFloaterChatMentionPicker(const LLSD& key); + + virtual bool postBuild() override; + virtual void goneFromFront() override; + + void buildAvatarList(); + + static uuid_vec_t getParticipantIds(); + static void updateSessionID(LLUUID session_id); + static void updateAvatarList(uuid_vec_t& avatar_ids); + +private: + + void onOpen(const LLSD& key) override; + void onClose(bool app_quitting) override; + virtual bool handleKey(KEY key, MASK mask, bool called_from_parent) override; + void selectResident(const LLUUID& id); + + static LLUUID sSessionID; + LLAvatarList* mAvatarList; +}; + +#endif diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index db6f9ac22a..b649514bff 100644 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -586,7 +586,7 @@ void LLFloaterIMNearbyChat::sendChat( EChatType type ) { if (mInputEditor) { - LLWString text = mInputEditor->getWText(); + LLWString text = mInputEditor->getConvertedText(); LLWStringUtil::trim(text); LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. if (!text.empty()) diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index 185274981b..84a9fad708 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -251,7 +251,7 @@ void LLFloaterIMSession::sendMsgFromInputEditor() { if (mInputEditor) { - LLWString text = mInputEditor->getWText(); + LLWString text = mInputEditor->getConvertedText(); LLWStringUtil::trim(text); LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. if(!text.empty()) diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 50e765c236..96aac8c1e6 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -35,6 +35,7 @@ #include "llavatariconctrl.h" #include "llchatentry.h" #include "llchathistory.h" +#include "llfloaterchatmentionpicker.h" #include "llchiclet.h" #include "llchicletbar.h" #include "lldraghandle.h" @@ -485,6 +486,7 @@ void LLFloaterIMSessionTab::onFocusReceived() LLIMModel::instance().sendNoUnreadMessages(mSessionID); } + LLFloaterChatMentionPicker::updateSessionID(mSessionID); super::onFocusReceived(); } diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 95c2a77ba8..4d9c2f3281 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -58,6 +58,7 @@ #include "llfloatercamera.h" #include "llfloatercamerapresets.h" #include "llfloaterchangeitemthumbnail.h" +#include "llfloaterchatmentionpicker.h" #include "llfloaterchatvoicevolume.h" #include "llfloaterclassified.h" #include "llfloaterconversationlog.h" @@ -353,6 +354,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("chat_voice", "floater_voice_chat_volume.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("change_item_thumbnail", "floater_change_item_thumbnail.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterIMNearbyChat::buildFloater); + LLFloaterReg::add("chat_mention_picker", "floater_chat_mention_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("classified", "floater_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("compile_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("conversation", "floater_conversation_log.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); 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 @@ + + + + -- cgit v1.3 From 4cccf8af43c8ebd9f947b9050a0403f65d0de7ee Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 18 Apr 2025 18:43:46 +0300 Subject: Restore missing 'override' --- indra/llui/llflatlistview.h | 24 ++++++++++++------------ indra/llui/lltextbox.h | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 3c40f91ab0..0ea3115f30 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -113,7 +113,7 @@ public: }; // disable traversal when finding widget to hand focus off to - /*virtual*/ bool canFocusChildren() const { return false; } + /*virtual*/ bool canFocusChildren() const override { return false; } /** * Connects callback to signal called when Return key is pressed. @@ -121,12 +121,12 @@ public: boost::signals2::connection setReturnCallback( const commit_signal_t::slot_type& cb ) { return mOnReturnSignal.connect(cb); } /** Overridden LLPanel's reshape, height is ignored, the list sets its height to accommodate all items */ - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + virtual void reshape(S32 width, S32 height, bool called_from_parent = true) override; /** Returns full rect of child panel */ const LLRect& getItemsRect() const; - LLRect getRequiredRect() const { return getItemsRect(); } + LLRect getRequiredRect() override { return getItemsRect(); } /** Returns distance between items */ const S32 getItemsPad() const { return mItemPad; } @@ -270,7 +270,7 @@ public: U32 size(const bool only_visible_items = true) const; /** Removes all items from the list */ - virtual void clear(); + virtual void clear() override; /** * Removes all items that can be detached from the list but doesn't destroy @@ -297,7 +297,7 @@ public: void selectFirstItem(); void selectLastItem(); - virtual S32 notify(const LLSD& info) ; + virtual S32 notify(const LLSD& info) override; virtual ~LLFlatListView(); @@ -346,8 +346,8 @@ protected: virtual bool selectNextItemPair(bool is_up_direction, bool reset_selection); - virtual bool canSelectAll() const; - virtual void selectAll(); + virtual bool canSelectAll() const override; + virtual void selectAll() override; virtual bool isSelected(item_pair_t* item_pair) const; @@ -364,15 +364,15 @@ protected: */ void notifyParentItemsRectChanged(); - virtual bool handleKeyHere(KEY key, MASK mask); + virtual bool handleKeyHere(KEY key, MASK mask) override; - virtual bool postBuild(); + virtual bool postBuild() override; - virtual void onFocusReceived(); + virtual void onFocusReceived() override; - virtual void onFocusLost(); + virtual void onFocusLost() override; - virtual void draw(); + virtual void draw() override; LLRect getLastSelectedItemRect(); diff --git a/indra/llui/lltextbox.h b/indra/llui/lltextbox.h index e60f528c72..507d8f3ee6 100644 --- a/indra/llui/lltextbox.h +++ b/indra/llui/lltextbox.h @@ -78,7 +78,7 @@ protected: bool mShowCursorHand; protected: - virtual std::string _getSearchText() const + virtual std::string _getSearchText() const override { return LLTextBase::_getSearchText() + mText.getString(); } -- cgit v1.3 From 6239647aa3623f84fa6c596638ecdb10b33e6b1b Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 23 Apr 2025 02:01:01 +0300 Subject: #3962 allow Tab work the same way as picking name with Enter --- indra/llui/lltexteditor.cpp | 2 +- indra/newview/llfloaterchatmentionpicker.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index bd726c3d49..bc73090ff7 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -1915,7 +1915,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) diff --git a/indra/newview/llfloaterchatmentionpicker.cpp b/indra/newview/llfloaterchatmentionpicker.cpp index dda2cd83f6..1cfed122a9 100644 --- a/indra/newview/llfloaterchatmentionpicker.cpp +++ b/indra/newview/llfloaterchatmentionpicker.cpp @@ -134,6 +134,7 @@ bool LLFloaterChatMentionPicker::handleKey(KEY key, MASK mask, bool called_from_ 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: -- cgit v1.3 From da2234a563c9dfca0ce80371167ceef08300de53 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 23 Apr 2025 17:36:50 +0300 Subject: #3952 insert @ name at the cursor position --- indra/llui/llchatentry.cpp | 18 ------------------ indra/llui/llchatentry.h | 2 -- indra/llui/lltexteditor.cpp | 34 ++++++++++++++++++++++++++++++++-- indra/llui/lltexteditor.h | 2 ++ 4 files changed, 34 insertions(+), 22 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp index 55e4beafb6..b01a8caf1f 100644 --- a/indra/llui/llchatentry.cpp +++ b/indra/llui/llchatentry.cpp @@ -250,21 +250,3 @@ void LLChatEntry::enableSingleLineMode(bool single_line_mode) mPrevLinesCount = -1; setWordWrap(!single_line_mode); } - -LLWString LLChatEntry::getConvertedText() const -{ - LLWString text = getWText(); - S32 diff = 0; - for (auto segment : mSegments) - { - if (segment && segment->getStyle() && segment->getStyle()->getDrawHighlightBg()) - { - S32 seg_length = segment->getEnd() - segment->getStart(); - std::string slurl = segment->getStyle()->getLinkHREF(); - - text.replace(segment->getStart() + diff, seg_length, utf8str_to_wstring(slurl)); - diff += (S32)slurl.size() - seg_length; - } - } - return text; -} diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index bb5eb8024d..5621ede1e7 100644 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -68,8 +68,6 @@ public: void enableSingleLineMode(bool single_line_mode); boost::signals2::connection setTextExpandedCallback(const commit_signal_t::slot_type& cb); - LLWString getConvertedText() const; - private: /** diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index bc73090ff7..cfe729be06 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -722,9 +722,21 @@ void LLTextEditor::handleMentionCommit(std::string name_url) if (LLChatMentionHelper::instance().isCursorInNameMention(getWText(), mCursorPos, &mention_start_pos)) { remove(mention_start_pos, mCursorPos - mention_start_pos, true); - setCursorPos(mention_start_pos); + insert(mention_start_pos, utf8str_to_wstring(name_url), false, LLTextSegmentPtr()); - appendTextImpl(name_url, LLStyle::Params(), true); + 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); + } } } @@ -3129,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 e38908734f..403ea3f4fc 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -215,6 +215,8 @@ public: void setPassDelete(bool b) { mPassDelete = b; } + LLWString getConvertedText() const; + protected: void showContextMenu(S32 x, S32 y); void drawPreeditMarker(); -- cgit v1.3 From 17561e2ad13ec5c32e49f26e70b4ee291433db04 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Wed, 23 Apr 2025 21:01:46 +0300 Subject: #3758 show mention name in bubble chat --- indra/llui/lltextbase.cpp | 2 +- indra/llui/llurlentry.cpp | 49 ++++++++++++++++++++++++++---------------- indra/llui/llurlentry.h | 14 +++++++++++- indra/llui/llurlregistry.cpp | 9 +++++++- indra/llui/llurlregistry.h | 3 ++- indra/newview/llviewerchat.cpp | 8 +++++++ 6 files changed, 62 insertions(+), 23 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 5f62763683..677dc63b25 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -2335,7 +2335,7 @@ void LLTextBase::appendTextImpl(const std::string& new_text, const LLStyle::Para 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; diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 9657dc9527..a2062d077e 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -580,7 +580,7 @@ LLUrlEntrySimpleSecondlifeURL::LLUrlEntrySimpleSecondlifeURL() // LLUrlEntryAgent::LLUrlEntryAgent() { - mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/(mention|(?!mention)\\w+)", + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/\\w+", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_agent.xml"; mIcon = "Generic_Person"; @@ -673,19 +673,9 @@ LLUrlMatch::EUnderlineLink LLUrlEntryAgent::getUnderline(const std::string& stri { return LLUrlMatch::EUnderlineLink::UNDERLINE_ON_HOVER; } - else if (LLStringUtil::endsWith(url, "/mention")) - { - return LLUrlMatch::EUnderlineLink::UNDERLINE_NEVER; - } return LLUrlMatch::EUnderlineLink::UNDERLINE_ALWAYS; } -bool LLUrlEntryAgent::getSkipProfileIcon(const std::string& string) const -{ - std::string url = getUrl(string); - return (LLStringUtil::endsWith(url, "/mention")) ? true : false; -} - std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) { if (!gCacheName) @@ -730,14 +720,7 @@ LLStyle::Params LLUrlEntryAgent::getStyle(const std::string &url) const LLStyle::Params style_params = LLUrlEntryBase::getStyle(url); style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - if (LLStringUtil::endsWith(url, "/mention")) - { - 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; } @@ -784,7 +767,35 @@ 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 LLStringUtil::endsWith(url, "/mention") ? std::string() : mIcon; + 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(); +} + +LLUrlMatch::EUnderlineLink LLUrlEntryAgentMention::getUnderline(const std::string& string) const +{ + return LLUrlMatch::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; } // diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index af5b8f5d83..df3932b2a0 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -234,7 +234,6 @@ public: /*virtual*/ LLUUID getID(const std::string &string) const; LLUrlMatch::EUnderlineLink getUnderline(const std::string& string) const; - bool getSkipProfileIcon(const std::string& string) const; protected: /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); @@ -245,6 +244,19 @@ private: avatar_name_cache_connection_map_t mAvatarNameCacheConnections; }; +/// +/// 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; + LLUrlMatch::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) diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 6e6e3be9b4..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; diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index 64cfec3960..35c6c0d84f 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -76,7 +76,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, @@ -102,6 +102,7 @@ private: LLUrlEntryBase* mUrlEntrySLLabel; LLUrlEntryBase* mUrlEntryNoLink; LLUrlEntryBase* mUrlEntryKeybinding; + LLUrlEntryBase* mUrlEntryAgentMention; }; #endif 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); -- cgit v1.3 From d9468ecc59f75363d9b57a9c1f7419cc3c42599b Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 25 Apr 2025 14:37:19 +0300 Subject: #3758 disable editing of mention segment --- indra/llui/lltextbase.cpp | 1 + indra/llui/lltextbase.h | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 677dc63b25..c1b01f420b 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -3458,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 a2895e6bd6..8eff3c51d9 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -140,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; } @@ -163,6 +163,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; @@ -171,6 +173,8 @@ protected: std::string mTooltip; boost::signals2::connection mImageLoadedConnection; + bool mCanEdit { true }; + // font rendering LLFontVertexBuffer mFontBufferPreSelection; LLFontVertexBuffer mFontBufferSelection; -- cgit v1.3 From f450b42acfd0ac118b07eaf16a1b4c0298edbeae Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 25 Apr 2025 17:07:00 +0300 Subject: post merge fix --- indra/llui/lltextbase.h | 1 - 1 file changed, 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index f3eac811e7..897c910c2f 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -146,7 +146,6 @@ public: /*virtual*/ void setStyle(LLStyleConstSP style) { mStyle = style; } /*virtual*/ void setToken( LLKeywordToken* token ) { mToken = token; } /*virtual*/ LLKeywordToken* getToken() const { return mToken; } - /*virtual*/ bool getToolTip( std::string& msg ) const; /*virtual*/ void setToolTip(const std::string& tooltip); /*virtual*/ void dump() const; -- cgit v1.3 From 1eb34b43fb88a0b7551d9083ab46ba7df2feab14 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 25 Apr 2025 20:22:33 +0300 Subject: #3758 clean up: move EUnderlineLink --- indra/llui/llchatmentionhelper.cpp | 2 +- indra/llui/llchatmentionhelper.h | 2 +- indra/llui/llstyle.h | 8 ++++++++ indra/llui/lltextbase.h | 2 +- indra/llui/llurlentry.cpp | 12 ++++++------ indra/llui/llurlentry.h | 9 ++++----- indra/llui/llurlmatch.cpp | 4 ++-- indra/llui/llurlmatch.h | 14 ++++---------- 8 files changed, 27 insertions(+), 26 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llchatmentionhelper.cpp b/indra/llui/llchatmentionhelper.cpp index 98d846b947..f7769b2cbe 100644 --- a/indra/llui/llchatmentionhelper.cpp +++ b/indra/llui/llchatmentionhelper.cpp @@ -37,7 +37,7 @@ bool LLChatMentionHelper::isActive(const LLUICtrl* ctrl) const return mHostHandle.get() == ctrl; } -bool LLChatMentionHelper::isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos) +bool LLChatMentionHelper::isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos) const { if (cursor_pos <= 0 || cursor_pos > static_cast(wtext.size())) return false; diff --git a/indra/llui/llchatmentionhelper.h b/indra/llui/llchatmentionhelper.h index 4da8c8264e..5f95d06f31 100644 --- a/indra/llui/llchatmentionhelper.h +++ b/indra/llui/llchatmentionhelper.h @@ -42,7 +42,7 @@ class LLChatMentionHelper : public LLSingleton public: bool isActive(const LLUICtrl* ctrl) const; - bool isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos = nullptr); + 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 commit_cb); void hideHelper(const LLUICtrl* ctrl = nullptr); diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h index 2c86eb6db7..71c3f88109 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -54,6 +54,14 @@ public: 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; } diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 897c910c2f..8ca653acb9 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -611,7 +611,7 @@ protected: bool operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const; }; typedef std::multiset segment_set_t; - typedef LLUrlMatch::EUnderlineLink e_underline; + typedef LLStyle::EUnderlineLink e_underline; // member functions LLTextBase(const Params &p); diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index a2062d077e..34138da34d 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -666,14 +666,14 @@ std::string LLUrlEntryAgent::getTooltip(const std::string &string) const return LLTrans::getString("TooltipAgentUrl"); } -LLUrlMatch::EUnderlineLink LLUrlEntryAgent::getUnderline(const std::string& string) const +LLStyle::EUnderlineLink LLUrlEntryAgent::getUnderline(const std::string& string) const { std::string url = getUrl(string); if (LLStringUtil::endsWith(url, "/about") || LLStringUtil::endsWith(url, "/inspect")) { - return LLUrlMatch::EUnderlineLink::UNDERLINE_ON_HOVER; + return LLStyle::EUnderlineLink::UNDERLINE_ON_HOVER; } - return LLUrlMatch::EUnderlineLink::UNDERLINE_ALWAYS; + return LLStyle::EUnderlineLink::UNDERLINE_ALWAYS; } std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) @@ -781,9 +781,9 @@ LLUrlEntryAgentMention::LLUrlEntryAgentMention() mIcon = std::string(); } -LLUrlMatch::EUnderlineLink LLUrlEntryAgentMention::getUnderline(const std::string& string) const +LLStyle::EUnderlineLink LLUrlEntryAgentMention::getUnderline(const std::string& string) const { - return LLUrlMatch::EUnderlineLink::UNDERLINE_NEVER; + return LLStyle::EUnderlineLink::UNDERLINE_NEVER; } LLStyle::Params LLUrlEntryAgentMention::getStyle(const std::string& url) const @@ -1406,7 +1406,7 @@ std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const return LLUrlEntryBase::getTooltip(string); } -LLUrlMatch::EUnderlineLink LLUrlEntrySLLabel::getUnderline(const std::string& string) const +LLStyle::EUnderlineLink LLUrlEntrySLLabel::getUnderline(const std::string& string) const { std::string url = getUrl(string); LLUrlMatch match; diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index df3932b2a0..740e99acfd 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -34,7 +34,6 @@ #include "llavatarname.h" #include "llhost.h" // for resolving parcel name by parcel id -#include "llurlmatch.h" #include #include @@ -97,7 +96,7 @@ public: /// Return the name of a SL location described by this Url, if any virtual std::string getLocation(const std::string &url) const { return ""; } - virtual LLUrlMatch::EUnderlineLink getUnderline(const std::string& string) const { return LLUrlMatch::EUnderlineLink::UNDERLINE_ALWAYS; } + virtual LLStyle::EUnderlineLink getUnderline(const std::string& string) const { return LLStyle::EUnderlineLink::UNDERLINE_ALWAYS; } virtual bool isTrusted() const { return false; } @@ -233,7 +232,7 @@ public: /*virtual*/ LLStyle::Params getStyle(const std::string &url) const; /*virtual*/ LLUUID getID(const std::string &string) const; - LLUrlMatch::EUnderlineLink getUnderline(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); @@ -253,7 +252,7 @@ public: LLUrlEntryAgentMention(); LLStyle::Params getStyle(const std::string& url) const; - LLUrlMatch::EUnderlineLink getUnderline(const std::string& string) const; + LLStyle::EUnderlineLink getUnderline(const std::string& string) const; bool getSkipProfileIcon(const std::string& string) const { return true; }; }; @@ -505,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; - LLUrlMatch::EUnderlineLink getUnderline(const std::string& string) const; + LLStyle::EUnderlineLink getUnderline(const std::string& string) const; }; /// diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index 3e61abe118..f093934ca9 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -37,7 +37,7 @@ LLUrlMatch::LLUrlMatch() : mIcon(""), mMenuName(""), mLocation(""), - mUnderline(UNDERLINE_ALWAYS), + mUnderline(e_underline::UNDERLINE_ALWAYS), mTrusted(false), mSkipProfileIcon(false) { @@ -47,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, EUnderlineLink underline, bool trusted, bool skip_icon) + const LLUUID& id, e_underline underline, bool trusted, bool skip_icon) { mStart = start; mEnd = end; diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index 0c4fe4a782..418a21f963 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -46,13 +46,6 @@ class LLUrlMatch public: LLUrlMatch(); - enum EUnderlineLink - { - UNDERLINE_ALWAYS = 0, - UNDERLINE_ON_HOVER, - UNDERLINE_NEVER - }; - /// return true if this object does not contain a valid Url match yet bool empty() const { return mUrl.empty(); } @@ -86,7 +79,8 @@ public: /// return the SL location that this Url describes, or "" if none. std::string getLocation() const { return mLocation; } - EUnderlineLink getUnderline() const { return mUnderline; } + typedef LLStyle::EUnderlineLink e_underline; + e_underline getUnderline() const { return mUnderline; } /// Return true if Url is trusted. bool isTrusted() const { return mTrusted; } @@ -98,7 +92,7 @@ public: 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, - EUnderlineLink underline = UNDERLINE_ALWAYS, bool trusted = false, bool skip_icon = false); + e_underline underline = e_underline::UNDERLINE_ALWAYS, bool trusted = false, bool skip_icon = false); const LLUUID& getID() const { return mID; } private: @@ -113,7 +107,7 @@ private: std::string mLocation; LLUUID mID; LLStyle::Params mStyle; - EUnderlineLink mUnderline; + e_underline mUnderline; bool mTrusted; bool mSkipProfileIcon; }; -- cgit v1.3 From 929c9b2dd0e4ff0b1b08c29267be60493010ab29 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 5 May 2025 15:28:51 +0300 Subject: #4009 Add color settings for chat mentions to Preferences --- indra/llui/llurlentry.cpp | 2 + indra/newview/skins/default/colors.xml | 5 +- .../default/xui/en/panel_preferences_colors.xml | 101 +++++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 34138da34d..7218211a44 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -789,6 +789,8 @@ LLStyle::EUnderlineLink LLUrlEntryAgentMention::getUnderline(const std::string& LLStyle::Params LLUrlEntryAgentMention::getStyle(const std::string& url) const { LLStyle::Params style_params = LLUrlEntryAgent::getStyle(url); + style_params.color = LLUIColorTable::instance().getColor("ChatMentionFont"); + style_params.readonly_color = LLUIColorTable::instance().getColor("ChatMentionFont"); style_params.font.style = "NORMAL"; style_params.draw_highlight_bg = true; diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml index cb190d3d34..f0ada22d66 100644 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -1000,10 +1000,13 @@ + + value="1 1 0 1" /> diff --git a/indra/newview/skins/default/xui/en/panel_preferences_colors.xml b/indra/newview/skins/default/xui/en/panel_preferences_colors.xml index 44df5354aa..8a09e15396 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_colors.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_colors.xml @@ -299,6 +299,107 @@ width="95"> URLs + + + + + + Mentions + + + Chat mentions highlight colors: + + + + + + + Me + + + + + + + Others + Date: Wed, 7 May 2025 18:59:00 +0300 Subject: #4011 Add conversation list highlight for chat mention --- indra/llui/llfolderviewitem.h | 2 +- indra/llui/llurlentry.cpp | 5 +++++ indra/llui/llurlentry.h | 3 +++ indra/llui/llurlregistry.cpp | 27 +++++++++++++++++++++++++++ indra/llui/llurlregistry.h | 2 ++ indra/newview/llconversationview.cpp | 9 ++++++--- indra/newview/llconversationview.h | 3 ++- indra/newview/llfloaterimcontainer.cpp | 4 ++-- indra/newview/llfloaterimcontainer.h | 2 +- indra/newview/llimview.cpp | 8 +++++--- indra/newview/llviewermessage.cpp | 2 ++ indra/newview/skins/default/colors.xml | 3 +++ 12 files changed, 59 insertions(+), 11 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index 234d0dc7f9..2ee018a90a 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -154,7 +154,7 @@ protected: virtual bool isHighlightActive(); virtual bool isFadeItem(); virtual bool isFlashing() { return false; } - virtual void setFlashState(bool) { } + virtual void setFlashState(bool, bool) { } static LLFontGL* getLabelFontForStyle(U8 style); const LLFontGL* getLabelFont(); diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 7218211a44..bcd13b7f0b 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -630,6 +630,11 @@ LLUUID LLUrlEntryAgent::getID(const std::string &string) const return LLUUID(getIDStringFromUrl(string)); } +bool LLUrlEntryAgent::isAgentID(const std::string& url) const +{ + return sAgentID == getID(url); +} + std::string LLUrlEntryAgent::getTooltip(const std::string &string) const { // return a tooltip corresponding to the URL type instead of the generic one diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 740e99acfd..6e7d2fc80f 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -103,6 +103,7 @@ public: virtual bool getSkipProfileIcon(const std::string& string) const { return false; } virtual LLUUID getID(const std::string &string) const { return LLUUID::null; } + virtual bool isAgentID(const std::string& url) const { return false; } bool isLinkDisabled() const; @@ -232,6 +233,8 @@ public: /*virtual*/ LLStyle::Params getStyle(const std::string &url) const; /*virtual*/ LLUUID getID(const std::string &string) const; + bool isAgentID(const std::string& url) const; + LLStyle::EUnderlineLink getUnderline(const std::string& string) const; protected: diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 02d88c83fb..cb101d325d 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -327,3 +327,30 @@ void LLUrlRegistry::setKeybindingHandler(LLKeyBindingToStringHandler* handler) LLUrlEntryKeybinding *entry = (LLUrlEntryKeybinding*)mUrlEntryKeybinding; entry->setHandler(handler); } + +bool LLUrlRegistry::containsAgentMention(const std::string& text) +{ + // avoid costly regexes if there is clearly no URL in the text + if (!stringHasUrl(text)) + { + return false; + } + + try + { + boost::sregex_iterator it(text.begin(), text.end(), mUrlEntryAgentMention->getPattern()); + boost::sregex_iterator end; + for (; it != end; ++it) + { + if (mUrlEntryAgentMention->isAgentID(it->str())) + { + return true; + } + } + } + catch (boost::regex_error&) + { + LL_INFOS() << "Regex error for: " << text << LL_ENDL; + } + return false; +} diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index b9502f4592..592e422487 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -92,6 +92,8 @@ public: // Set handler for url registry to be capable of parsing and populating keybindings void setKeybindingHandler(LLKeyBindingToStringHandler* handler); + bool containsAgentMention(const std::string& text); + private: std::vector mUrlEntry; LLUrlEntryBase* mUrlEntryTrusted; diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index a1f627c8cc..0e0ab236d6 100644 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -86,7 +86,8 @@ LLConversationViewSession::LLConversationViewSession(const LLConversationViewSes mHasArrow(true), mIsInActiveVoiceChannel(false), mFlashStateOn(false), - mFlashStarted(false) + mFlashStarted(false), + mIsAltFlashColor(false) { mFlashTimer = new LLFlashTimer(); mAreChildrenInited = true; // inventory only @@ -157,7 +158,7 @@ void LLConversationViewSession::destroyView() LLFolderViewFolder::destroyView(); } -void LLConversationViewSession::setFlashState(bool flash_state) +void LLConversationViewSession::setFlashState(bool flash_state, bool alternate_color) { if (flash_state && !mFlashStateOn) { @@ -170,6 +171,7 @@ void LLConversationViewSession::setFlashState(bool flash_state) mFlashStateOn = flash_state; mFlashStarted = false; + mIsAltFlashColor = mFlashStateOn && (alternate_color || mIsAltFlashColor); mFlashTimer->stopFlashing(); } @@ -288,7 +290,8 @@ void LLConversationViewSession::draw() startFlashing(); // draw highlight for selected items - drawHighlight(show_context, true, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); + static LLUIColor alt_color = LLUIColorTable::instance().getColor("MentionFlashBgColor", DEFAULT_WHITE); + drawHighlight(show_context, true, sHighlightBgColor, mIsAltFlashColor ? alt_color : sFlashBgColor, sFocusOutlineColor, sMouseOverColor); // Draw children if root folder, or any other folder that is open. Do not draw children when animating to closed state or you get rendering overlap. bool draw_children = getRoot() == static_cast(this) || isOpen(); diff --git a/indra/newview/llconversationview.h b/indra/newview/llconversationview.h index 8eb6392121..a6d240ed84 100644 --- a/indra/newview/llconversationview.h +++ b/indra/newview/llconversationview.h @@ -90,7 +90,7 @@ public: virtual void refresh(); - /*virtual*/ void setFlashState(bool flash_state); + /*virtual*/ void setFlashState(bool flash_state, bool alternate_color = false); void setHighlightState(bool hihglight_state); LLFloater* getSessionFloater(); @@ -111,6 +111,7 @@ private: LLFlashTimer* mFlashTimer; bool mFlashStateOn; bool mFlashStarted; + bool mIsAltFlashColor; bool mCollapsedMode; bool mHasArrow; diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index e4b14d8df6..72d4d30dcf 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -2302,14 +2302,14 @@ bool LLFloaterIMContainer::isConversationLoggingAllowed() return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; } -void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, bool is_flashes) +void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, bool is_flashes, bool alternate_color) { //Finds the conversation line item to flash using the session_id LLConversationViewSession * widget = dynamic_cast(get_ptr_in_map(mConversationsWidgets,session_id)); if (widget) { - widget->setFlashState(is_flashes); + widget->setFlashState(is_flashes, alternate_color); } } diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h index e5486e67da..30eed8be36 100644 --- a/indra/newview/llfloaterimcontainer.h +++ b/indra/newview/llfloaterimcontainer.h @@ -208,7 +208,7 @@ public: void reSelectConversation(); void updateSpeakBtnState(); static bool isConversationLoggingAllowed(); - void flashConversationItemWidget(const LLUUID& session_id, bool is_flashes); + void flashConversationItemWidget(const LLUUID& session_id, bool is_flashes, bool alternate_color = false); void highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted); bool isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget); boost::signals2::connection mMicroChangedSignal; diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 474b7b66d7..23bba99ed6 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -71,6 +71,7 @@ #include "llviewerregion.h" #include "llcorehttputil.h" #include "lluiusage.h" +#include "llurlregistry.h" #include @@ -197,6 +198,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id); bool store_dnd_message = false; // flag storage of a dnd message bool is_session_focused = session_floater->isTornOff() && session_floater->hasFocus(); + bool contains_mention = LLUrlRegistry::getInstance()->containsAgentMention(msg["message"].asString()); if (!LLFloater::isVisible(im_box) || im_box->isMinimized()) { conversations_floater_status = CLOSED; @@ -323,7 +325,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) if ("openconversations" == user_preferences || ON_TOP == conversations_floater_status || ("toast" == user_preferences && ON_TOP != conversations_floater_status) - || ("flash" == user_preferences && (CLOSED == conversations_floater_status + || (("flash" == user_preferences || contains_mention) && (CLOSED == conversations_floater_status || NOT_ON_TOP == conversations_floater_status)) || is_dnd_msg) { @@ -343,7 +345,7 @@ void notify_of_message(const LLSD& msg, bool is_dnd_msg) } else { - im_box->flashConversationItemWidget(session_id, true); + im_box->flashConversationItemWidget(session_id, true, contains_mention); } } } @@ -3269,7 +3271,7 @@ void LLIMMgr::addMessage( { LLFloaterReg::showInstance("im_container"); LLFloaterReg::getTypedInstance("im_container")-> - flashConversationItemWidget(new_session_id, true); + flashConversationItemWidget(new_session_id, true, LLUrlRegistry::getInstance()->containsAgentMention(msg)); } } diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index bdcfec34f6..1501ba41c2 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -2583,6 +2583,8 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) msg_notify["session_id"] = LLUUID(); msg_notify["from_id"] = chat.mFromID; msg_notify["source_type"] = chat.mSourceType; + // used to check if there is agent mention in the message + msg_notify["message"] = mesg; on_new_message(msg_notify); } diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml index f0ada22d66..0c34a3a5fb 100644 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -1009,4 +1009,7 @@ + -- cgit v1.3 From 9668d2f0eff761f1de71cea718a46e807f9c00a8 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Fri, 9 May 2025 18:46:56 +0300 Subject: #3758 do not reopen avatar picker floater --- indra/llui/llchatmentionhelper.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llchatmentionhelper.cpp b/indra/llui/llchatmentionhelper.cpp index f7769b2cbe..5745389a58 100644 --- a/indra/llui/llchatmentionhelper.cpp +++ b/indra/llui/llchatmentionhelper.cpp @@ -98,7 +98,14 @@ void LLChatMentionHelper::showHelper(LLUICtrl* host_ctrl, S32 local_x, S32 local 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)); + if (av_picker_floater->isShown()) + { + av_picker_floater->onOpen(LLSD().with("av_name", av_name)); + } + else + { + av_picker_floater->openFloater(LLSD().with("av_name", av_name)); + } } void LLChatMentionHelper::hideHelper(const LLUICtrl* ctrl) -- cgit v1.3 From 0500c78b1bccda71a8623785c9765a7adaec0071 Mon Sep 17 00:00:00 2001 From: Erik Kundiman Date: Mon, 12 May 2025 20:14:23 +0800 Subject: Have unencapsulated Expat headers in any condition The only condition where Expat headers would be encapsulated is when using LL's Autobuild-based prebuilt libraries, and we're never using any of LL's prebuilt binary for Expat on desktop, since Expat is practically available on any supported desktop platform. The system Expat headers are never encapsulated in any of those platforms. This is the beginning of not relying on the LL_USESYSTEMLIBS macro any more (eventually not relying on the custom USESYSTEMLIBS CMake setting either). Keeping the build system still flexible to have the option to use LL's prebuilt libraries *fully* (we still use some of them in cases where the platform may not have the related system library or for convenience, so the term USESYSTEMLIBS may not always be consistent), is getting harder to maintain. The way it's done is using #if 1, in order to minimise difference from upstream. --- indra/llcommon/llsdserialize_xml.cpp | 2 +- indra/llui/llxuiparser.cpp | 2 +- indra/llxml/llxmlnode.h | 2 +- indra/llxml/llxmlparser.h | 2 +- indra/newview/llvoicevivox.cpp | 2 +- indra/newview/llvoicevivox.h | 2 +- indra/newview/llvoicewebrtc.cpp | 2 +- indra/newview/llvoicewebrtc.h | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) (limited to 'indra/llui') diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp index 6396caf8d5..ce416baa04 100644 --- a/indra/llcommon/llsdserialize_xml.cpp +++ b/indra/llcommon/llsdserialize_xml.cpp @@ -35,7 +35,7 @@ extern "C" { -#ifdef LL_USESYSTEMLIBS +#if 1 # include #else # include "expat/expat.h" diff --git a/indra/llui/llxuiparser.cpp b/indra/llui/llxuiparser.cpp index 8fd85a89a1..8cd11b86b3 100644 --- a/indra/llui/llxuiparser.cpp +++ b/indra/llui/llxuiparser.cpp @@ -30,7 +30,7 @@ #include "llxmlnode.h" #include "llfasttimer.h" -#ifdef LL_USESYSTEMLIBS +#if 1 #include #else #include "expat/expat.h" diff --git a/indra/llxml/llxmlnode.h b/indra/llxml/llxmlnode.h index 09c7c4fdad..769cf97ba2 100644 --- a/indra/llxml/llxmlnode.h +++ b/indra/llxml/llxmlnode.h @@ -30,7 +30,7 @@ #ifndef XML_STATIC #define XML_STATIC #endif -#ifdef LL_USESYSTEMLIBS +#if 1 #include #else #include "expat/expat.h" diff --git a/indra/llxml/llxmlparser.h b/indra/llxml/llxmlparser.h index 0f64def6df..9c25727e54 100644 --- a/indra/llxml/llxmlparser.h +++ b/indra/llxml/llxmlparser.h @@ -30,7 +30,7 @@ #ifndef XML_STATIC #define XML_STATIC #endif -#ifdef LL_USESYSTEMLIBS +#if 1 #include #else #include "expat/expat.h" diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 7faef8cc41..14f0b52174 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -36,7 +36,7 @@ #include "llbufferstream.h" #include "llfile.h" #include "llmenugl.h" -#ifdef LL_USESYSTEMLIBS +#if 1 # include "expat.h" #else # include "expat/expat.h" diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h index 3e0a237905..ca1258e05e 100644 --- a/indra/newview/llvoicevivox.h +++ b/indra/newview/llvoicevivox.h @@ -43,7 +43,7 @@ class LLVivoxProtocolParser; #include "llmutelist.h" #include -#ifdef LL_USESYSTEMLIBS +#if 1 # include "expat.h" #else # include "expat/expat.h" diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 08fcec86ac..c2b74eb1dd 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -34,7 +34,7 @@ #include "llbufferstream.h" #include "llfile.h" #include "llmenugl.h" -#ifdef LL_USESYSTEMLIBS +#if 1 # include "expat.h" #else # include "expat/expat.h" diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h index ff82d2739d..946a80e874 100644 --- a/indra/newview/llvoicewebrtc.h +++ b/indra/newview/llvoicewebrtc.h @@ -43,7 +43,7 @@ class LLWebRTCProtocolParser; #include #include "boost/json.hpp" -#ifdef LL_USESYSTEMLIBS +#if 1 # include "expat.h" #else # include "expat/expat.h" -- cgit v1.3 From 5b12428e65a5edf56409cf3fdc858827da7460f0 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 22 May 2025 19:53:53 +0300 Subject: #4132 fix name mentions sometimes not being highlighted --- indra/llui/lltextbase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index c1b01f420b..778b253c3c 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -483,7 +483,7 @@ std::vector> LLTextBase::getHighlightedBgRects() F32 left_precise = (F32)line_iter->mRect.mLeft; F32 right_precise = (F32)line_iter->mRect.mLeft; - for (; segment_iter != mSegments.end(); ++segment_iter) + for (; segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0) { LLTextSegmentPtr segmentp = *segment_iter; -- cgit v1.3