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.2.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.2.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.2.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 ++-- 9 files changed, 238 insertions(+), 54 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; -- cgit v1.2.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 +- 12 files changed, 306 insertions(+), 8 deletions(-) create mode 100644 indra/llui/llchatmentionhelper.cpp create mode 100644 indra/llui/llchatmentionhelper.h (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; } // -- cgit v1.2.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.2.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 +- 1 file changed, 1 insertion(+), 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) -- cgit v1.2.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.2.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 ++- 5 files changed, 54 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 -- cgit v1.2.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.2.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.2.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.2.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 ++ 1 file changed, 2 insertions(+) (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; -- cgit v1.2.3 From 50108bf0b7953e1f44a13b0790fe8dd8e9f7d2ca Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko 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 ++ 5 files changed, 38 insertions(+), 1 deletion(-) (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; -- cgit v1.2.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.2.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.2.3