diff options
| author | Erik Kundiman <erik@megapahit.org> | 2025-04-30 09:40:14 +0800 | 
|---|---|---|
| committer | Erik Kundiman <erik@megapahit.org> | 2025-04-30 09:40:14 +0800 | 
| commit | c0a2498fa1ff074fa29b31bbd63b2ac6f69b38bb (patch) | |
| tree | 0909d51200a3de225f20ebe7d0aa7cb95fb380e1 /indra | |
| parent | 39cc9a1706340d6f84b152593a4d8aaeeaa88b56 (diff) | |
| parent | d9e55c44152064133796bfb08f1da524387c1300 (diff) | |
Merge tag 'Second_Life_Release#d9e55c44-Second_Life_Release#d9e55c4-2025.04' into 2025.04
Diffstat (limited to 'indra')
54 files changed, 1525 insertions, 442 deletions
| diff --git a/indra/llmessage/llpacketring.cpp b/indra/llmessage/llpacketring.cpp index eb6650c6c5..b8284334ea 100644 --- a/indra/llmessage/llpacketring.cpp +++ b/indra/llmessage/llpacketring.cpp @@ -209,8 +209,14 @@ S32 LLPacketRing::receiveOrDropBufferedPacket(char *datap, bool drop)      if (!drop)      { -        assert(packet_size > 0); -        memcpy(datap, packet->getData(), packet_size); +        if (packet_size > 0) +        { +            memcpy(datap, packet->getData(), packet_size); +        } +        else +        { +            assert(false); +        }      }      else      { diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index e0c2db058a..aac5e3abc8 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -1114,8 +1114,8 @@ void LLGLSLShader::bind()  void LLGLSLShader::bind(U8 variant)  { -    llassert(mGLTFVariants.size() == LLGLSLShader::NUM_GLTF_VARIANTS); -    llassert(variant < LLGLSLShader::NUM_GLTF_VARIANTS); +    llassert_always(mGLTFVariants.size() == LLGLSLShader::NUM_GLTF_VARIANTS); +    llassert_always(variant < LLGLSLShader::NUM_GLTF_VARIANTS);      mGLTFVariants[variant].bind();  } @@ -1123,7 +1123,7 @@ void LLGLSLShader::bind(bool rigged)  {      if (rigged)      { -        llassert(mRiggedVariant); +        llassert_always(mRiggedVariant);          mRiggedVariant->bind();      }      else diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 5991a5b35e..83b3a220a0 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -18,6 +18,7 @@ set(llui_SOURCE_FILES      llbadgeowner.cpp      llbutton.cpp      llchatentry.cpp +    llchatmentionhelper.cpp      llcheckboxctrl.cpp      llclipboard.cpp      llcombobox.cpp @@ -130,6 +131,7 @@ set(llui_HEADER_FILES      llcallbackmap.h      llchatentry.h      llchat.h +    llchatmentionhelper.h      llcheckboxctrl.h      llclipboard.h      llcombobox.h diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp index e8d942b8af..7506cd99c0 100644 --- a/indra/llui/llchatentry.cpp +++ b/indra/llui/llchatentry.cpp @@ -52,6 +52,7 @@ LLChatEntry::LLChatEntry(const Params& p)      mCurrentHistoryLine = mLineHistory.begin();      mAutoIndent = false; +    mShowChatMentionPicker = true;      keepSelectionOnReturn(true);  } diff --git a/indra/llui/llchatmentionhelper.cpp b/indra/llui/llchatmentionhelper.cpp new file mode 100644 index 0000000000..f7769b2cbe --- /dev/null +++ b/indra/llui/llchatmentionhelper.cpp @@ -0,0 +1,151 @@ +/** +* @file llchatmentionhelper.cpp +* +* $LicenseInfo:firstyear=2025&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2025, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "llchatmentionhelper.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "lluictrl.h" + +constexpr char CHAT_MENTION_HELPER_FLOATER[] = "chat_mention_picker"; + +bool LLChatMentionHelper::isActive(const LLUICtrl* ctrl) const +{ +    return mHostHandle.get() == ctrl; +} + +bool LLChatMentionHelper::isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos) const +{ +    if (cursor_pos <= 0 || cursor_pos > static_cast<S32>(wtext.size())) +        return false; + +    // Find the beginning of the current word +    S32 start = cursor_pos - 1; +    while (start > 0 && wtext[start - 1] != U32(' ') && wtext[start - 1] != U32('\n')) +    { +        --start; +    } + +    if (wtext[start] != U32('@')) +        return false; + +    if (mention_start_pos) +        *mention_start_pos = start; + +    S32 word_length = cursor_pos - start; + +    if (word_length == 1) +    { +        return true; +    } + +    // Get the name after '@' +    std::string name = wstring_to_utf8str(wtext.substr(start + 1, word_length - 1)); +    LLStringUtil::toLower(name); +    for (const auto& av_name : mAvatarNames) +    { +        if (av_name == name || av_name.find(name) == 0) +        { +            return true; +        } +    } + +    return false; +} + +void LLChatMentionHelper::showHelper(LLUICtrl* host_ctrl, S32 local_x, S32 local_y, const std::string& av_name, std::function<void(std::string)> cb) +{ +    if (mHelperHandle.isDead()) +    { +        LLFloater* av_picker_floater = LLFloaterReg::getInstance(CHAT_MENTION_HELPER_FLOATER); +        mHelperHandle = av_picker_floater->getHandle(); +        mHelperCommitConn = av_picker_floater->setCommitCallback([&](LLUICtrl* ctrl, const LLSD& param) { onCommitName(param.asString()); }); +    } +    setHostCtrl(host_ctrl); +    mNameCommitCb = cb; + +    S32 floater_x, floater_y; +    if (!host_ctrl->localPointToOtherView(local_x, local_y, &floater_x, &floater_y, gFloaterView)) +    { +        LL_WARNS() << "Cannot show helper for non-floater controls." << LL_ENDL; +        return; +    } + +    LLFloater* av_picker_floater = mHelperHandle.get(); +    LLRect rect = av_picker_floater->getRect(); +    rect.setLeftTopAndSize(floater_x, floater_y + rect.getHeight(), rect.getWidth(), rect.getHeight()); +    av_picker_floater->setRect(rect); +    av_picker_floater->openFloater(LLSD().with("av_name", av_name)); +} + +void LLChatMentionHelper::hideHelper(const LLUICtrl* ctrl) +{ +    if ((ctrl && !isActive(ctrl))) +    { +        return; +    } +    setHostCtrl(nullptr); +} + +bool LLChatMentionHelper::handleKey(const LLUICtrl* ctrl, KEY key, MASK mask) +{ +    if (mHelperHandle.isDead() || !isActive(ctrl)) +    { +        return false; +    } + +    return mHelperHandle.get()->handleKey(key, mask, true); +} + +void LLChatMentionHelper::onCommitName(std::string name_url) +{ +    if (!mHostHandle.isDead() && mNameCommitCb) +    { +        mNameCommitCb(name_url); +    } +} + +void LLChatMentionHelper::setHostCtrl(LLUICtrl* host_ctrl) +{ +    const LLUICtrl* pCurHostCtrl = mHostHandle.get(); +    if (pCurHostCtrl != host_ctrl) +    { +        mHostCtrlFocusLostConn.disconnect(); +        mHostHandle.markDead(); +        mNameCommitCb = {}; + +        if (!mHelperHandle.isDead()) +        { +            mHelperHandle.get()->closeFloater(); +        } + +        if (host_ctrl) +        { +            mHostHandle = host_ctrl->getHandle(); +            mHostCtrlFocusLostConn = host_ctrl->setFocusLostCallback(std::bind([&]() { hideHelper(getHostCtrl()); })); +        } +    } +} diff --git a/indra/llui/llchatmentionhelper.h b/indra/llui/llchatmentionhelper.h new file mode 100644 index 0000000000..5f95d06f31 --- /dev/null +++ b/indra/llui/llchatmentionhelper.h @@ -0,0 +1,66 @@ +/** +* @file llchatmentionhelper.h +* @brief Header file for LLChatMentionHelper +* +* $LicenseInfo:firstyear=2025&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2025, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA +* $/LicenseInfo$ +*/ + +#pragma once + +#include "llhandle.h" +#include "llsingleton.h" + +#include <boost/signals2.hpp> + +class LLFloater; +class LLUICtrl; + +class LLChatMentionHelper : public LLSingleton<LLChatMentionHelper> +{ +    LLSINGLETON(LLChatMentionHelper) {} +    ~LLChatMentionHelper() override {} + +public: + +    bool isActive(const LLUICtrl* ctrl) const; +    bool isCursorInNameMention(const LLWString& wtext, S32 cursor_pos, S32* mention_start_pos = nullptr) const; +    void showHelper(LLUICtrl* host_ctrl, S32 local_x, S32 local_y, const std::string& av_name, std::function<void(std::string)> commit_cb); +    void hideHelper(const LLUICtrl* ctrl = nullptr); + +    bool handleKey(const LLUICtrl* ctrl, KEY key, MASK mask); +    void onCommitName(std::string name_url); + +    void updateAvatarList(std::vector<std::string> av_names) { mAvatarNames = av_names; } + +protected: +    void setHostCtrl(LLUICtrl* host_ctrl); +    LLUICtrl* getHostCtrl() const { return mHostHandle.get(); } + +private: +    LLHandle<LLUICtrl>  mHostHandle; +    LLHandle<LLFloater> mHelperHandle; +    boost::signals2::connection mHostCtrlFocusLostConn; +    boost::signals2::connection mHelperCommitConn; +    std::function<void(std::string)> mNameCommitCb; + +    std::vector<std::string> mAvatarNames; +}; diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index b8c833f4fd..8178bada42 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -459,6 +459,7 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p)    , mNoItemsCommentTextbox(NULL)    , mIsConsecutiveSelection(false)    , mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape) +  , mFocusOnItemClicked(true)  {      mBorderThickness = getBorderWidth(); @@ -610,7 +611,10 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)          return;      } -    setFocus(true); +    if (mFocusOnItemClicked) +    { +        setFocus(true); +    }      bool select_item = !isSelected(item_pair); diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 0ea3115f30..6271231183 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -299,6 +299,8 @@ public:      virtual S32 notify(const LLSD& info) override; +    void setFocusOnItemClicked(bool b) { mFocusOnItemClicked = b; } +      virtual ~LLFlatListView();  protected: @@ -423,6 +425,8 @@ private:      bool mKeepSelectionVisibleOnReshape; +    bool mFocusOnItemClicked; +      /** All pairs of the list */      pairs_list_t mItemPairs; diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp index 4714665e8b..7a0e620d61 100644 --- a/indra/llui/llstyle.cpp +++ b/indra/llui/llstyle.cpp @@ -38,11 +38,13 @@ LLStyle::Params::Params()      color("color", LLColor4::black),      readonly_color("readonly_color", LLColor4::black),      selected_color("selected_color", LLColor4::black), +    highlight_bg_color("highlight_bg_color", LLColor4::green),      alpha("alpha", 1.f),      font("font", LLStyle::getDefaultFont()),      image("image"),      link_href("href"), -    is_link("is_link") +    is_link("is_link"), +    draw_highlight_bg("draw_highlight_bg", false)  {} @@ -51,12 +53,14 @@ LLStyle::LLStyle(const LLStyle::Params& p)      mColor(p.color),      mReadOnlyColor(p.readonly_color),      mSelectedColor(p.selected_color), +    mHighlightBgColor(p.highlight_bg_color),      mFont(p.font()),      mLink(p.link_href),      mIsLink(p.is_link.isProvided() ? p.is_link : !p.link_href().empty()),      mDropShadow(p.drop_shadow),      mImagep(p.image()), -    mAlpha(p.alpha) +    mAlpha(p.alpha), +    mDrawHighlightBg(p.draw_highlight_bg)  {}  void LLStyle::setFont(const LLFontGL* font) diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h index 0c78fe5a9f..71c3f88109 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -43,15 +43,25 @@ public:          Optional<LLFontGL::ShadowType>  drop_shadow;          Optional<LLUIColor>             color,                                          readonly_color, -                                        selected_color; +                                        selected_color, +                                        highlight_bg_color;          Optional<F32>                   alpha;          Optional<const LLFontGL*>       font;          Optional<LLUIImage*>            image;          Optional<std::string>           link_href;          Optional<bool>                  is_link; +        Optional<bool>                  draw_highlight_bg;          Params();      };      LLStyle(const Params& p = Params()); + +    enum EUnderlineLink +    { +        UNDERLINE_ALWAYS = 0, +        UNDERLINE_ON_HOVER, +        UNDERLINE_NEVER +    }; +  public:      const LLUIColor& getColor() const { return mColor; }      void setColor(const LLUIColor &color) { mColor = color; } @@ -84,6 +94,9 @@ public:      bool isImage() const { return mImagep.notNull(); } +    bool getDrawHighlightBg() const { return mDrawHighlightBg; } +    const LLUIColor& getHighlightBgColor() const { return mHighlightBgColor; } +      bool operator==(const LLStyle &rhs) const      {          return @@ -91,11 +104,13 @@ public:              && mColor == rhs.mColor              && mReadOnlyColor == rhs.mReadOnlyColor              && mSelectedColor == rhs.mSelectedColor +            && mHighlightBgColor == rhs.mHighlightBgColor              && mFont == rhs.mFont              && mLink == rhs.mLink              && mImagep == rhs.mImagep              && mDropShadow == rhs.mDropShadow -            && mAlpha == rhs.mAlpha; +            && mAlpha == rhs.mAlpha +            && mDrawHighlightBg == rhs.mDrawHighlightBg;      }      bool operator!=(const LLStyle& rhs) const { return !(*this == rhs); } @@ -112,11 +127,13 @@ private:      LLUIColor           mColor;      LLUIColor           mReadOnlyColor;      LLUIColor           mSelectedColor; +    LLUIColor           mHighlightBgColor;      const LLFontGL*     mFont;      LLPointer<LLUIImage> mImagep;      F32                 mAlpha;      bool                mVisible;      bool                mIsLink; +    bool                mDrawHighlightBg;  };  typedef LLPointer<LLStyle> LLStyleSP; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 41e7094163..c1b01f420b 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -460,6 +460,62 @@ std::vector<LLRect> LLTextBase::getSelectionRects()      return selection_rects;  } +std::vector<std::pair<LLRect, LLUIColor>> LLTextBase::getHighlightedBgRects() +{ +    std::vector<std::pair<LLRect, LLUIColor>> highlight_rects; + +    LLRect content_display_rect = getVisibleDocumentRect(); + +    // binary search for line that starts before top of visible buffer +    line_list_t::const_iterator line_iter = +        std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom()); +    line_list_t::const_iterator end_iter = +        std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top()); + +    for (; line_iter != end_iter; ++line_iter) +    { +            segment_set_t::iterator segment_iter; +            S32 segment_offset; +            getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset); + +            // Use F32 otherwise a string of multiple segments +            // will accumulate a large error +            F32 left_precise  = (F32)line_iter->mRect.mLeft; +            F32 right_precise = (F32)line_iter->mRect.mLeft; + +            for (; segment_iter != mSegments.end(); ++segment_iter) +            { +                LLTextSegmentPtr segmentp = *segment_iter; + +                S32 segment_line_start = segmentp->getStart() + segment_offset; +                S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd); + +                if (segment_line_start > segment_line_end) +                    break; + +                F32 segment_width  = 0; +                S32 segment_height = 0; + +                S32 num_chars = segment_line_end - segment_line_start; +                segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); +                right_precise += segment_width; + +                if (segmentp->getStyle()->getDrawHighlightBg()) +                { +                    LLRect selection_rect; +                    selection_rect.mLeft = (S32)left_precise; +                    selection_rect.mRight = (S32)right_precise; +                    selection_rect.mBottom = line_iter->mRect.mBottom; +                    selection_rect.mTop = line_iter->mRect.mTop; + +                    highlight_rects.push_back(std::pair(selection_rect, segmentp->getStyle()->getHighlightBgColor())); +                } +                left_precise += segment_width; +            } +    } +    return highlight_rects; +} +  // Draws the black box behind the selected text  void LLTextBase::drawSelectionBackground()  { @@ -529,6 +585,71 @@ void LLTextBase::drawSelectionBackground()      }  } +void LLTextBase::drawHighlightedBackground() +{ +    if (!mLineInfoList.empty()) +    { +        std::vector<std::pair<LLRect, LLUIColor>> highlight_rects = getHighlightedBgRects(); + +        if (highlight_rects.empty()) +            return; + +        gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + +        LLRect content_display_rect = getVisibleDocumentRect(); + +        for (std::vector<std::pair<LLRect, LLUIColor>>::iterator rect_it = highlight_rects.begin(); +             rect_it != highlight_rects.end(); ++rect_it) +        { +            LLRect selection_rect = rect_it->first; +            const LLColor4& color = rect_it->second; +            if (mScroller) +            { +                // If scroller is On content_display_rect has correct rect and safe to use as is +                // Note: we might need to account for border +                selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); +            } +            else +            { +                // If scroller is Off content_display_rect will have rect from document, adjusted to text width, heigh and position +                // and we have to acount for offset depending on position +                S32 v_delta = 0; +                S32 h_delta = 0; +                switch (mVAlign) +                { +                case LLFontGL::TOP: +                    v_delta = mVisibleTextRect.mTop - content_display_rect.mTop - mVPad; +                    break; +                case LLFontGL::VCENTER: +                    v_delta = (llmax(mVisibleTextRect.getHeight() - content_display_rect.mTop, -content_display_rect.mBottom) + (mVisibleTextRect.mBottom - content_display_rect.mBottom)) / 2; +                    break; +                case LLFontGL::BOTTOM: +                    v_delta = mVisibleTextRect.mBottom - content_display_rect.mBottom; +                    break; +                default: +                    break; +                } +                switch (mHAlign) +                { +                case LLFontGL::LEFT: +                    h_delta = mVisibleTextRect.mLeft - content_display_rect.mLeft + mHPad; +                    break; +                case LLFontGL::HCENTER: +                    h_delta = (llmax(mVisibleTextRect.getWidth() - content_display_rect.mLeft, -content_display_rect.mRight) + (mVisibleTextRect.mRight - content_display_rect.mRight)) / 2; +                    break; +                case LLFontGL::RIGHT: +                    h_delta = mVisibleTextRect.mRight - content_display_rect.mRight; +                    break; +                default: +                    break; +                } +                selection_rect.translate(h_delta, v_delta); +            } +            gl_rect_2d(selection_rect, color); +        } +    } +} +  void LLTextBase::drawCursor()  {      F32 alpha = getDrawContext().mAlpha; @@ -1399,6 +1520,7 @@ void LLTextBase::draw()              drawChild(mDocumentView);          } +        drawHighlightedBackground();          drawSelectionBackground();          drawText();          drawCursor(); @@ -2200,20 +2322,20 @@ static LLUIImagePtr image_from_icon_name(const std::string& icon_name)  } -void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params) +void LLTextBase::appendTextImpl(const std::string& new_text, const LLStyle::Params& input_params, bool force_slurl)  {      LL_PROFILE_ZONE_SCOPED_CATEGORY_UI;      LLStyle::Params style_params(getStyleParams());      style_params.overwriteFrom(input_params);      S32 part = (S32)LLTextParser::WHOLE; -    if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). +    if ((mParseHTML || force_slurl) && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).      {          S32 start=0,end=0;          LLUrlMatch match;          std::string text = new_text;          while (LLUrlRegistry::instance().findUrl(text, match, -                boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons)) +                boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons, force_slurl))          {              start = match.getStart();              end = match.getEnd()+1; @@ -2245,7 +2367,7 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para              }              // output the styled Url -            appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly()); +            appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.getUnderline());              bool tooltip_required =  !match.getTooltip().empty();              // set the tooltip for the Url label @@ -2260,7 +2382,7 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para              {                  link_params.color = LLColor4::grey;                  link_params.readonly_color = LLColor4::grey; -                appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly()); +                appendAndHighlightTextImpl(label, part, link_params, match.getUnderline());                  // set the tooltip for the query part of url                  if (tooltip_required) @@ -2428,7 +2550,7 @@ void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const s      insertStringNoUndo(getLength(), widget_wide_text, &segments);  } -void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) +void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link)  {      // Save old state      S32 selection_start = mSelectionStart; @@ -2458,7 +2580,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig              S32 cur_length = getLength();              LLStyleConstSP sp(new LLStyle(highlight_params));              LLTextSegmentPtr segmentp; -            if (underline_on_hover_only || mSkipLinkUnderline) +            if ((underline_link == e_underline::UNDERLINE_ON_HOVER) || mSkipLinkUnderline)              {                  highlight_params.font.style("NORMAL");                  LLStyleConstSP normal_sp(new LLStyle(highlight_params)); @@ -2482,7 +2604,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig          S32 segment_start = old_length;          S32 segment_end = old_length + static_cast<S32>(wide_text.size());          LLStyleConstSP sp(new LLStyle(style_params)); -        if (underline_on_hover_only || mSkipLinkUnderline) +        if ((underline_link == e_underline::UNDERLINE_ON_HOVER) || mSkipLinkUnderline)          {              LLStyle::Params normal_style_params(style_params);              normal_style_params.font.style("NORMAL"); @@ -2516,7 +2638,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig      }  } -void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) +void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link)  {      if (new_text.empty())      { @@ -2531,7 +2653,7 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlig          if (pos != start)          {              std::string str = std::string(new_text,start,pos-start); -            appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); +            appendAndHighlightTextImpl(str, highlight_part, style_params, underline_link);          }          appendLineBreakSegment(style_params);          start = pos+1; @@ -2539,7 +2661,7 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlig      }      std::string str = std::string(new_text, start, new_text.length() - start); -    appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); +    appendAndHighlightTextImpl(str, highlight_part, style_params, underline_link);  } @@ -3336,6 +3458,7 @@ LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 e      mLastGeneration(-1)  {      mFontHeight = mStyle->getFont()->getLineHeight(); +    mCanEdit = !mStyle->getDrawHighlightBg();      LLUIImagePtr image = mStyle->getImage();      if (image.notNull()) diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index e62b56963d..8ca653acb9 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -35,6 +35,7 @@  #include "llstyle.h"  #include "llkeywords.h"  #include "llpanel.h" +#include "llurlmatch.h"  #include <string>  #include <vector> @@ -139,7 +140,7 @@ public:      /*virtual*/ S32                 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const;      /*virtual*/ void                updateLayout(const class LLTextBase& editor);      /*virtual*/ F32                 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); -    /*virtual*/ bool                canEdit() const { return true; } +    /*virtual*/ bool                canEdit() const { return mCanEdit; }      /*virtual*/ const LLUIColor&     getColor() const                    { return mStyle->getColor(); }      /*virtual*/ LLStyleConstSP      getStyle() const                    { return mStyle; }      /*virtual*/ void                setStyle(LLStyleConstSP style)  { mStyle = style; } @@ -161,6 +162,8 @@ protected:      virtual     const LLWString&    getWText()  const;      virtual     const S32           getLength() const; +    void setAllowEdit(bool can_edit) { mCanEdit = can_edit; } +  protected:      class LLTextBase&   mEditor;      LLStyleConstSP      mStyle; @@ -169,6 +172,8 @@ protected:      std::string         mTooltip;      boost::signals2::connection mImageLoadedConnection; +    bool mCanEdit { true }; +      // font rendering      LLFontVertexBuffer  mFontBufferPreSelection;      LLFontVertexBuffer  mFontBufferSelection; @@ -606,6 +611,7 @@ protected:          bool operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const;      };      typedef std::multiset<LLTextSegmentPtr, compare_segment_end> segment_set_t; +    typedef LLStyle::EUnderlineLink e_underline;      // member functions      LLTextBase(const Params &p); @@ -619,12 +625,13 @@ protected:      virtual void                    drawSelectionBackground(); // draws the black box behind the selected text      void                            drawCursor();      void                            drawText(); +    void                            drawHighlightedBackground();      // modify contents      S32                             insertStringNoUndo(S32 pos, const LLWString &wstr, segment_vec_t* segments = NULL); // returns num of chars actually inserted      S32                             removeStringNoUndo(S32 pos, S32 length);      S32                             overwriteCharNoUndo(S32 pos, llwchar wc); -    void                            appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& stylep, bool underline_on_hover_only = false); +    void                            appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& stylep, e_underline underline_link = e_underline::UNDERLINE_ALWAYS);      // manage segments @@ -672,8 +679,9 @@ protected:      // avatar names are looked up.      void replaceUrl(const std::string &url, const std::string &label, const std::string& icon); -    void                            appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params()); -    void                            appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only = false); +    void                            appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params(), bool force_slurl = false); +    void                            appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, e_underline underline_link = e_underline::UNDERLINE_ALWAYS); +    S32 normalizeUri(std::string& uri);  protected:      // virtual @@ -683,6 +691,7 @@ protected:      }      std::vector<LLRect> getSelectionRects(); +    std::vector<std::pair<LLRect, LLUIColor>> getHighlightedBgRects();  protected:      // text segmentation and flow diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index fe4cce29ab..cfe729be06 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -60,6 +60,7 @@  #include "llurlregistry.h"  #include "lltooltip.h"  #include "llmenugl.h" +#include "llchatmentionhelper.h"  #include <queue>  #include "llcombobox.h" @@ -270,6 +271,7 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) :      mPrevalidator(p.prevalidator()),      mShowContextMenu(p.show_context_menu),      mShowEmojiHelper(p.show_emoji_helper), +    mShowChatMentionPicker(false),      mEnableTooltipPaste(p.enable_tooltip_paste),      mPassDelete(false),      mKeepSelectionOnReturn(false) @@ -714,6 +716,30 @@ void LLTextEditor::handleEmojiCommit(llwchar emoji)      }  } +void LLTextEditor::handleMentionCommit(std::string name_url) +{ +    S32 mention_start_pos; +    if (LLChatMentionHelper::instance().isCursorInNameMention(getWText(), mCursorPos, &mention_start_pos)) +    { +        remove(mention_start_pos, mCursorPos - mention_start_pos, true); +        insert(mention_start_pos, utf8str_to_wstring(name_url), false, LLTextSegmentPtr()); + +        std::string new_text(wstring_to_utf8str(getConvertedText())); +        clear(); +        appendTextImpl(new_text, LLStyle::Params(), true); + +        segment_set_t::const_iterator it = getSegIterContaining(mention_start_pos); +        if (it != mSegments.end()) +        { +            setCursorPos((*it)->getEnd() + 1); +        } +        else +        { +            setCursorPos(mention_start_pos); +        } +    } +} +  bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)  {      bool    handled = false; @@ -1103,6 +1129,7 @@ void LLTextEditor::removeCharOrTab()          }          tryToShowEmojiHelper(); +        tryToShowMentionHelper();      }      else      { @@ -1128,6 +1155,7 @@ void LLTextEditor::removeChar()          setCursorPos(mCursorPos - 1);          removeChar(mCursorPos);          tryToShowEmojiHelper(); +        tryToShowMentionHelper();      }      else      { @@ -1189,6 +1217,7 @@ void LLTextEditor::addChar(llwchar wc)      setCursorPos(mCursorPos + addChar( mCursorPos, wc ));      tryToShowEmojiHelper(); +    tryToShowMentionHelper();      if (!mReadOnly && mAutoreplaceCallback != NULL)      { @@ -1247,6 +1276,31 @@ void LLTextEditor::tryToShowEmojiHelper()      }  } +void LLTextEditor::tryToShowMentionHelper() +{ +    if (mReadOnly || !mShowChatMentionPicker) +        return; + +    S32 mention_start_pos; +    LLWString text(getWText()); +    if (LLChatMentionHelper::instance().isCursorInNameMention(text, mCursorPos, &mention_start_pos)) +    { +        const LLRect cursor_rect(getLocalRectFromDocIndex(mention_start_pos)); +        std::string name_part(wstring_to_utf8str(text.substr(mention_start_pos, mCursorPos - mention_start_pos))); +        name_part.erase(0, 1); +        auto cb = [this](std::string name_url) +        { +            handleMentionCommit(name_url); +        }; +        LLChatMentionHelper::instance().showHelper(this, cursor_rect.mLeft, cursor_rect.mTop, name_part, cb); +    } +    else +    { +        LLChatMentionHelper::instance().hideHelper(); +    } +} + +  void LLTextEditor::addLineBreakChar(bool group_together)  {      if( !getEnabled() ) @@ -1873,7 +1927,7 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask )      // not handled and let the parent take care of field movement.      if (KEY_TAB == key && mTabsToNextField)      { -        return false; +        return mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask);      }      if (mReadOnly && mScroller) @@ -1884,9 +1938,13 @@ bool LLTextEditor::handleKeyHere(KEY key, MASK mask )      }      else      { -        if (!mReadOnly && mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) +        if (!mReadOnly)          { -            return true; +            if ((mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) || +                (mShowChatMentionPicker && LLChatMentionHelper::instance().handleKey(this, key, mask))) +            { +                return true; +            }          }          if (mEnableTooltipPaste && @@ -3083,3 +3141,21 @@ S32 LLTextEditor::spacesPerTab()  {      return SPACES_PER_TAB;  } + +LLWString LLTextEditor::getConvertedText() const +{ +    LLWString text = getWText(); +    S32 diff = 0; +    for (auto segment : mSegments) +    { +        if (segment && segment->getStyle() && segment->getStyle()->getDrawHighlightBg()) +        { +            S32 seg_length = segment->getEnd() - segment->getStart(); +            std::string slurl = segment->getStyle()->getLinkHREF(); + +            text.replace(segment->getStart() + diff, seg_length, utf8str_to_wstring(slurl)); +            diff += (S32)slurl.size() - seg_length; +        } +    } +    return text; +} diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 353a7b93a0..882bb145df 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -95,6 +95,8 @@ public:      void    insertEmoji(llwchar emoji);      void    handleEmojiCommit(llwchar emoji); +    void handleMentionCommit(std::string name_url); +      // mousehandler overrides      virtual bool    handleMouseDown(S32 x, S32 y, MASK mask);      virtual bool    handleMouseUp(S32 x, S32 y, MASK mask); @@ -212,6 +214,8 @@ public:      void            setPassDelete(bool b) { mPassDelete = b; } +    LLWString       getConvertedText() const; +  protected:      void            showContextMenu(S32 x, S32 y);      void            drawPreeditMarker(); @@ -254,6 +258,7 @@ protected:      S32             remove(S32 pos, S32 length, bool group_with_next_op);      void            tryToShowEmojiHelper(); +    void            tryToShowMentionHelper();      void            focusLostHelper();      void            updateAllowingLanguageInput();      bool            hasPreeditString() const; @@ -291,6 +296,7 @@ protected:      bool                mAutoIndent;      bool                mParseOnTheFly; +    bool                mShowChatMentionPicker;      void                updateLinkSegments();      void                keepSelectionOnReturn(bool keep) { mKeepSelectionOnReturn = keep; } diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 77f132e9d8..34138da34d 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -29,7 +29,6 @@  #include "llurlentry.h"  #include "lluictrl.h"  #include "lluri.h" -#include "llurlmatch.h"  #include "llurlregistry.h"  #include "lluriparser.h" @@ -48,7 +47,7 @@  // Utility functions  std::string localize_slapp_label(const std::string& url, const std::string& full_name); - +LLUUID LLUrlEntryBase::sAgentID(LLUUID::null);  LLUrlEntryBase::LLUrlEntryBase()  {  } @@ -68,7 +67,7 @@ std::string LLUrlEntryBase::getIcon(const std::string &url)      return mIcon;  } -LLStyle::Params LLUrlEntryBase::getStyle() const +LLStyle::Params LLUrlEntryBase::getStyle(const std::string &url) const  {      LLStyle::Params style_params;      style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); @@ -667,10 +666,14 @@ std::string LLUrlEntryAgent::getTooltip(const std::string &string) const      return LLTrans::getString("TooltipAgentUrl");  } -bool LLUrlEntryAgent::underlineOnHoverOnly(const std::string &string) const +LLStyle::EUnderlineLink LLUrlEntryAgent::getUnderline(const std::string& string) const  {      std::string url = getUrl(string); -    return LLStringUtil::endsWith(url, "/about") || LLStringUtil::endsWith(url, "/inspect"); +    if (LLStringUtil::endsWith(url, "/about") || LLStringUtil::endsWith(url, "/inspect")) +    { +        return LLStyle::EUnderlineLink::UNDERLINE_ON_HOVER; +    } +    return LLStyle::EUnderlineLink::UNDERLINE_ALWAYS;  }  std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) @@ -712,11 +715,12 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa      }  } -LLStyle::Params LLUrlEntryAgent::getStyle() const +LLStyle::Params LLUrlEntryAgent::getStyle(const std::string &url) const  { -    LLStyle::Params style_params = LLUrlEntryBase::getStyle(); +    LLStyle::Params style_params = LLUrlEntryBase::getStyle(url);      style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor");      style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); +      return style_params;  } @@ -751,6 +755,10 @@ std::string localize_slapp_label(const std::string& url, const std::string& full      {          return LLTrans::getString("SLappAgentRemoveFriend") + " " + full_name;      } +    if (LLStringUtil::endsWith(url, "/mention")) +    { +        return "@" + full_name; +    }      return full_name;  } @@ -762,6 +770,34 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url)      return mIcon;  } +/// +/// LLUrlEntryAgentMention Describes a chat mention Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/mention +/// +LLUrlEntryAgentMention::LLUrlEntryAgentMention() +{ +    mPattern  = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/mention", boost::regex::perl | boost::regex::icase); +    mMenuName = "menu_url_agent.xml"; +    mIcon = std::string(); +} + +LLStyle::EUnderlineLink LLUrlEntryAgentMention::getUnderline(const std::string& string) const +{ +    return LLStyle::EUnderlineLink::UNDERLINE_NEVER; +} + +LLStyle::Params LLUrlEntryAgentMention::getStyle(const std::string& url) const +{ +    LLStyle::Params style_params = LLUrlEntryAgent::getStyle(url); +    style_params.font.style = "NORMAL"; +    style_params.draw_highlight_bg = true; + +    LLUUID agent_id(getIDStringFromUrl(url)); +    style_params.highlight_bg_color = LLUIColorTable::instance().getColor((agent_id == sAgentID) ? "ChatSelfMentionHighlight" : "ChatMentionHighlight"); + +    return style_params; +} +  //  // LLUrlEntryAgentName describes a Second Life agent name Url, e.g.,  // secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) @@ -823,7 +859,7 @@ std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLab      }  } -LLStyle::Params LLUrlEntryAgentName::getStyle() const +LLStyle::Params LLUrlEntryAgentName::getStyle(const std::string &url) const  {      // don't override default colors      return LLStyle::Params().is_link(false); @@ -959,9 +995,9 @@ std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCa      }  } -LLStyle::Params LLUrlEntryGroup::getStyle() const +LLStyle::Params LLUrlEntryGroup::getStyle(const std::string &url) const  { -    LLStyle::Params style_params = LLUrlEntryBase::getStyle(); +    LLStyle::Params style_params = LLUrlEntryBase::getStyle(url);      style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor");      style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor");      return style_params; @@ -1037,7 +1073,6 @@ std::string LLUrlEntryChat::getLabel(const std::string &url, const LLUrlLabelCal  }  // LLUrlEntryParcel statics. -LLUUID  LLUrlEntryParcel::sAgentID(LLUUID::null);  LLUUID  LLUrlEntryParcel::sSessionID(LLUUID::null);  LLHost  LLUrlEntryParcel::sRegionHost;  bool    LLUrlEntryParcel::sDisconnected(false); @@ -1371,17 +1406,17 @@ std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const      return LLUrlEntryBase::getTooltip(string);  } -bool LLUrlEntrySLLabel::underlineOnHoverOnly(const std::string &string) const +LLStyle::EUnderlineLink LLUrlEntrySLLabel::getUnderline(const std::string& string) const  {      std::string url = getUrl(string); -    LLUrlMatch match; +    LLUrlMatch  match;      if (LLUrlRegistry::instance().findUrl(url, match))      { -        return match.underlineOnHoverOnly(); +        return match.getUnderline();      }      // unrecognized URL? should not happen -    return LLUrlEntryBase::underlineOnHoverOnly(string); +    return LLUrlEntryBase::getUnderline(string);  }  // @@ -1445,7 +1480,7 @@ std::string LLUrlEntryNoLink::getLabel(const std::string &url, const LLUrlLabelC      return getUrl(url);  } -LLStyle::Params LLUrlEntryNoLink::getStyle() const +LLStyle::Params LLUrlEntryNoLink::getStyle(const std::string &url) const  {      // Don't render as URL (i.e. no context menu or hand cursor).      return LLStyle::Params().is_link(false); diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index fffee88496..740e99acfd 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -85,7 +85,7 @@ public:      virtual std::string getIcon(const std::string &url);      /// Return the style to render the displayed text -    virtual LLStyle::Params getStyle() const; +    virtual LLStyle::Params getStyle(const std::string &url) const;      /// Given a matched Url, return a tooltip string for the hyperlink      virtual std::string getTooltip(const std::string &string) const { return mTooltip; } @@ -96,11 +96,12 @@ public:      /// Return the name of a SL location described by this Url, if any      virtual std::string getLocation(const std::string &url) const { return ""; } -    /// Should this link text be underlined only when mouse is hovered over it? -    virtual bool underlineOnHoverOnly(const std::string &string) const { return false; } +    virtual LLStyle::EUnderlineLink getUnderline(const std::string& string) const { return LLStyle::EUnderlineLink::UNDERLINE_ALWAYS; }      virtual bool isTrusted() const { return false; } +    virtual bool getSkipProfileIcon(const std::string& string) const { return false; } +      virtual LLUUID  getID(const std::string &string) const { return LLUUID::null; }      bool isLinkDisabled() const; @@ -109,6 +110,8 @@ public:      virtual bool isSLURLvalid(const std::string &url) const { return true; }; +    static void setAgentID(const LLUUID& id) { sAgentID = id; } +  protected:      std::string getIDStringFromUrl(const std::string &url) const;      std::string escapeUrl(const std::string &url) const; @@ -130,6 +133,8 @@ protected:      std::string                                     mMenuName;      std::string                                     mTooltip;      std::multimap<std::string, LLUrlEntryObserver>  mObservers; + +    static LLUUID sAgentID;  };  /// @@ -224,9 +229,11 @@ public:      /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);      /*virtual*/ std::string getIcon(const std::string &url);      /*virtual*/ std::string getTooltip(const std::string &string) const; -    /*virtual*/ LLStyle::Params getStyle() const; +    /*virtual*/ LLStyle::Params getStyle(const std::string &url) const;      /*virtual*/ LLUUID  getID(const std::string &string) const; -    /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const; + +    LLStyle::EUnderlineLink getUnderline(const std::string& string) const; +  protected:      /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon);  private: @@ -237,6 +244,19 @@ private:  };  /// +/// LLUrlEntryAgentMention Describes a chat mention Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/mention +class LLUrlEntryAgentMention : public LLUrlEntryAgent +{ +public: +    LLUrlEntryAgentMention(); + +    LLStyle::Params getStyle(const std::string& url) const; +    LLStyle::EUnderlineLink getUnderline(const std::string& string) const; +    bool getSkipProfileIcon(const std::string& string) const { return true; }; +}; + +///  /// LLUrlEntryAgentName Describes a Second Life agent name Url, e.g.,  /// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username)  /// that displays various forms of user name @@ -257,7 +277,7 @@ public:          mAvatarNameCacheConnections.clear();      }      /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); -    /*virtual*/ LLStyle::Params getStyle() const; +    /*virtual*/ LLStyle::Params getStyle(const std::string &url) const;  protected:      // override this to pull out relevant name fields      virtual std::string getName(const LLAvatarName& avatar_name) = 0; @@ -339,7 +359,7 @@ class LLUrlEntryGroup : public LLUrlEntryBase  public:      LLUrlEntryGroup();      /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); -    /*virtual*/ LLStyle::Params getStyle() const; +    /*virtual*/ LLStyle::Params getStyle(const std::string &url) const;      /*virtual*/ LLUUID  getID(const std::string &string) const;  private:      void onGroupNameReceived(const LLUUID& id, const std::string& name, bool is_group); @@ -411,17 +431,15 @@ public:      // Processes parcel label and triggers notifying observers.      static void processParcelInfo(const LLParcelData& parcel_data); -    // Next 4 setters are used to update agent and viewer connection information +    // Next setters are used to update agent and viewer connection information      // upon events like user login, viewer disconnect and user changing region host.      // These setters are made public to be accessible from newview and should not be      // used in other cases. -    static void setAgentID(const LLUUID& id) { sAgentID = id; }      static void setSessionID(const LLUUID& id) { sSessionID = id; }      static void setRegionHost(const LLHost& host) { sRegionHost = host; }      static void setDisconnected(bool disconnected) { sDisconnected = disconnected; }  private: -    static LLUUID                       sAgentID;      static LLUUID                       sSessionID;      static LLHost                       sRegionHost;      static bool                         sDisconnected; @@ -486,7 +504,7 @@ public:      /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);      /*virtual*/ std::string getUrl(const std::string &string) const;      /*virtual*/ std::string getTooltip(const std::string &string) const; -    /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const; +    LLStyle::EUnderlineLink getUnderline(const std::string& string) const;  };  /// @@ -510,7 +528,7 @@ public:      LLUrlEntryNoLink();      /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);      /*virtual*/ std::string getUrl(const std::string &string) const; -    /*virtual*/ LLStyle::Params getStyle() const; +    /*virtual*/ LLStyle::Params getStyle(const std::string &url) const;  };  /// diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index bfa3b167b1..f093934ca9 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -37,8 +37,9 @@ LLUrlMatch::LLUrlMatch() :      mIcon(""),      mMenuName(""),      mLocation(""), -    mUnderlineOnHoverOnly(false), -    mTrusted(false) +    mUnderline(e_underline::UNDERLINE_ALWAYS), +    mTrusted(false), +    mSkipProfileIcon(false)  {  } @@ -46,7 +47,7 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std                             const std::string& query, const std::string &tooltip,                             const std::string &icon, const LLStyle::Params& style,                             const std::string &menu, const std::string &location, -                           const LLUUID& id, bool underline_on_hover_only, bool trusted) +                           const LLUUID& id, e_underline underline, bool trusted, bool skip_icon)  {      mStart = start;      mEnd = end; @@ -60,6 +61,7 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std      mMenuName = menu;      mLocation = location;      mID = id; -    mUnderlineOnHoverOnly = underline_on_hover_only; +    mUnderline = underline;      mTrusted = trusted; +    mSkipProfileIcon = skip_icon;  } diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index 887796bb37..418a21f963 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -79,18 +79,20 @@ public:      /// return the SL location that this Url describes, or "" if none.      std::string getLocation() const { return mLocation; } -    /// Should this link text be underlined only when mouse is hovered over it? -    bool underlineOnHoverOnly() const { return mUnderlineOnHoverOnly; } +    typedef LLStyle::EUnderlineLink e_underline; +    e_underline getUnderline() const { return mUnderline; }      /// Return true if Url is trusted.      bool isTrusted() const { return mTrusted; } +    bool getSkipProfileIcon() const { return mSkipProfileIcon; } +      /// Change the contents of this match object (used by LLUrlRegistry)      void setValues(U32 start, U32 end, const std::string &url, const std::string &label,                     const std::string& query, const std::string &tooltip, const std::string &icon,                     const LLStyle::Params& style, const std::string &menu,                     const std::string &location, const LLUUID& id, -                   bool underline_on_hover_only = false, bool trusted = false); +                   e_underline underline = e_underline::UNDERLINE_ALWAYS, bool trusted = false, bool skip_icon = false);      const LLUUID& getID() const { return mID; }  private: @@ -105,8 +107,9 @@ private:      std::string mLocation;      LLUUID      mID;      LLStyle::Params mStyle; -    bool        mUnderlineOnHoverOnly; +    e_underline mUnderline;      bool        mTrusted; +    bool mSkipProfileIcon;  };  #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index cec1ddfc57..02d88c83fb 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -62,6 +62,8 @@ LLUrlRegistry::LLUrlRegistry()      registerUrl(new LLUrlEntryAgentUserName());      // LLUrlEntryAgent*Name must appear before LLUrlEntryAgent since      // LLUrlEntryAgent is a less specific (catchall for agent urls) +    mUrlEntryAgentMention = new LLUrlEntryAgentMention(); +    registerUrl(mUrlEntryAgentMention);      registerUrl(new LLUrlEntryAgent());      registerUrl(new LLUrlEntryChat());      registerUrl(new LLUrlEntryGroup()); @@ -155,7 +157,7 @@ static bool stringHasUrl(const std::string &text)              text.find("@") != std::string::npos);  } -bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted) +bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted, bool skip_non_mentions)  {      // avoid costly regexes if there is clearly no URL in the text      if (! stringHasUrl(text)) @@ -176,6 +178,11 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL              continue;          } +        if (skip_non_mentions && (mUrlEntryAgentMention != *it)) +        { +            continue; +        } +          LLUrlEntryBase *url_entry = *it;          U32 start = 0, end = 0; @@ -233,12 +240,13 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL                          match_entry->getQuery(url),                          match_entry->getTooltip(url),                          match_entry->getIcon(url), -                        match_entry->getStyle(), +                        match_entry->getStyle(url),                          match_entry->getMenuName(),                          match_entry->getLocation(url),                          match_entry->getID(url), -                        match_entry->underlineOnHoverOnly(url), -                        match_entry->isTrusted()); +                        match_entry->getUnderline(url), +                        match_entry->isTrusted(), +                        match_entry->getSkipProfileIcon(url));          return true;      } @@ -274,7 +282,9 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr                          match.getMenuName(),                          match.getLocation(),                          match.getID(), -                        match.underlineOnHoverOnly()); +                        match.getUnderline(), +                        false, +                        match.getSkipProfileIcon());          return true;      }      return false; diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index c22af0dbc4..b9502f4592 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -75,7 +75,7 @@ public:      /// your callback is invoked if the matched Url's label changes in the future      bool findUrl(const std::string &text, LLUrlMatch &match,                   const LLUrlLabelCallback &cb = &LLUrlRegistryNullCallback, -                 bool is_content_trusted = false); +                 bool is_content_trusted = false, bool skip_non_mentions = false);      /// a slightly less efficient version of findUrl for wide strings      bool findUrl(const LLWString &text, LLUrlMatch &match, @@ -101,6 +101,7 @@ private:      LLUrlEntryBase* mUrlEntrySLLabel;      LLUrlEntryBase* mUrlEntryNoLink;      LLUrlEntryBase* mUrlEntryKeybinding; +    LLUrlEntryBase* mUrlEntryAgentMention;  };  #endif diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 77d9171f3f..473ca60728 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -212,6 +212,7 @@ set(viewer_SOURCE_FILES      llfloatercamera.cpp      llfloatercamerapresets.cpp      llfloaterchangeitemthumbnail.cpp +    llfloaterchatmentionpicker.cpp      llfloaterchatvoicevolume.cpp      llfloaterclassified.cpp      llfloatercolorpicker.cpp @@ -887,6 +888,7 @@ set(viewer_HEADER_FILES      llfloaterbuyland.h      llfloatercamerapresets.h      llfloaterchangeitemthumbnail.h +    llfloaterchatmentionpicker.h      llfloatercamera.h      llfloaterchatvoicevolume.h      llfloaterclassified.h diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 991d8e5c5f..099f298456 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.1.13 +7.1.14 diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index bf3fada3bd..9a381f9ba6 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -643,6 +643,12 @@ void GLTFSceneManager::render(Asset& asset, U8 variant)          return;      } +    if (gGLTFPBRMetallicRoughnessProgram.mGLTFVariants.size() <= variant) +    { +        llassert(false); // mGLTFVariants should have been initialized +        return; +    } +      for (U32 ds = 0; ds < 2; ++ds)      {          RenderData& rd = asset.mRenderData[ds]; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 87b165b739..3cc84666ec 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -485,7 +485,7 @@ static void deferred_ui_audio_callback(const LLUUID& uuid)  bool    create_text_segment_icon_from_url_match(LLUrlMatch* match,LLTextBase* base)  { -    if(!match || !base || base->getPlainText()) +    if (!match || match->getSkipProfileIcon() || !base || base->getPlainText())          return false;      LLUUID match_id = match->getID(); diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp index 52cd86951d..5f9d03ca66 100644 --- a/indra/newview/llavatarlist.cpp +++ b/indra/newview/llavatarlist.cpp @@ -147,6 +147,7 @@ LLAvatarList::LLAvatarList(const Params& p)  , mShowSpeakingIndicator(p.show_speaking_indicator)  , mShowPermissions(p.show_permissions_granted)  , mShowCompleteName(false) +, mForceCompleteName(false)  {      setCommitOnSelectionChange(true); @@ -183,7 +184,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 @@ -381,7 +382,7 @@ void LLAvatarList::updateAvatarNames()      for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++)      {          LLAvatarListItem* item = static_cast<LLAvatarListItem*>(*it); -        item->setShowCompleteName(mShowCompleteName); +        item->setShowCompleteName(mShowCompleteName, mForceCompleteName);          item->updateAvatarName();      }      mNeedUpdateNames = false; @@ -421,6 +422,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)  { @@ -435,7 +441,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); @@ -451,6 +457,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);  } @@ -609,6 +616,11 @@ void LLAvatarList::onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask)      mItemDoubleClickSignal(ctrl, x, y, mask);  } +void LLAvatarList::onItemClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask) +{ +    mItemClickedSignal(ctrl, x, y, mask); +} +  bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const  {      const LLAvatarListItem* avatar_item1 = dynamic_cast<const LLAvatarListItem*>(item1); diff --git a/indra/newview/llavatarlist.h b/indra/newview/llavatarlist.h index 37ad578a20..97b4f05985 100644 --- a/indra/newview/llavatarlist.h +++ b/indra/newview/llavatarlist.h @@ -98,11 +98,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(); @@ -117,6 +119,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: @@ -133,6 +136,7 @@ private:      bool mShowSpeakingIndicator;      bool mShowPermissions;      bool mShowCompleteName; +    bool mForceCompleteName;      LLTimer*                mLITUpdateTimer; // last interaction time update timer      std::string             mIconParamName; @@ -144,6 +148,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 7be4f4eeb8..1db7d63309 100644 --- a/indra/newview/llavatarlistitem.cpp +++ b/indra/newview/llavatarlistitem.cpp @@ -80,6 +80,7 @@ LLAvatarListItem::LLAvatarListItem(bool not_from_ui_factory/* = true*/)      mShowProfileBtn(true),      mShowPermissions(false),      mShowCompleteName(false), +    mForceCompleteName(false),      mHovered(false),      mAvatarNameCacheConnection(),      mGreyOutUsername("") @@ -350,13 +351,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) @@ -443,8 +442,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 630a7ec751..f9381f95e3 100644 --- a/indra/newview/llavatarlistitem.h +++ b/indra/newview/llavatarlistitem.h @@ -110,7 +110,7 @@ public:      void showAvatarDistance(bool 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; @@ -228,6 +228,7 @@ private:      bool mHovered;      bool mShowCompleteName; +    bool mForceCompleteName;      std::string mGreyOutUsername;      void fetchAvatarName(); diff --git a/indra/newview/llfloaterbulkpermission.cpp b/indra/newview/llfloaterbulkpermission.cpp index c09c02d32b..74c5079268 100644 --- a/indra/newview/llfloaterbulkpermission.cpp +++ b/indra/newview/llfloaterbulkpermission.cpp @@ -89,9 +89,17 @@ bool LLFloaterBulkPermission::postBuild()      {          mBulkChangeNextOwnerTransfer = true;      } + +    mQueueOutputList = getChild<LLScrollListCtrl>("queue output");      return true;  } +void LLFloaterBulkPermission::onClose(bool app_quitting) +{ +    removeVOInventoryListener(); +    LLFloater::onClose(app_quitting); +} +  void LLFloaterBulkPermission::doApply()  {      // Inspects a stream of selected object contents and adds modifiable ones to the given array. @@ -216,7 +224,7 @@ void LLFloaterBulkPermission::onCommitCopy()  bool LLFloaterBulkPermission::start()  {      // note: number of top-level objects to modify is mObjectIDs.size(). -    getChild<LLScrollListCtrl>("queue output")->setCommentText(getString("start_text")); +    mQueueOutputList->setCommentText(getString("start_text"));      return nextObject();  } @@ -239,7 +247,7 @@ bool LLFloaterBulkPermission::nextObject()      if(isDone() && !mDone)      { -        getChild<LLScrollListCtrl>("queue output")->setCommentText(getString("done_text")); +        mQueueOutputList->setCommentText(getString("done_text"));          mDone = true;      }      return successful_start; @@ -294,8 +302,6 @@ void LLFloaterBulkPermission::doCheckUncheckAll(bool check)  void LLFloaterBulkPermission::handleInventory(LLViewerObject* viewer_obj, LLInventoryObject::object_list_t* inv)  { -    LLScrollListCtrl* list = getChild<LLScrollListCtrl>("queue output"); -      LLInventoryObject::object_list_t::const_iterator it = inv->begin();      LLInventoryObject::object_list_t::const_iterator end = inv->end();      for ( ; it != end; ++it) @@ -362,7 +368,7 @@ void LLFloaterBulkPermission::handleInventory(LLViewerObject* viewer_obj, LLInve                      status_text.setArg("[STATUS]", "");                  } -                list->setCommentText(status_text.getString()); +                mQueueOutputList->setCommentText(status_text.getString());                  //TODO if we are an object inside an object we should check a recuse flag and if set                  //open the inventory of the object and recurse - Michelle2 Zenovka diff --git a/indra/newview/llfloaterbulkpermission.h b/indra/newview/llfloaterbulkpermission.h index 23ca45b611..0b61022e0c 100644 --- a/indra/newview/llfloaterbulkpermission.h +++ b/indra/newview/llfloaterbulkpermission.h @@ -41,7 +41,8 @@ class LLFloaterBulkPermission : public LLFloater, public LLVOInventoryListener      friend class LLFloaterReg;  public: -    bool postBuild(); +    bool postBuild() override; +    void onClose(bool app_quitting) override;  private: @@ -57,7 +58,7 @@ private:      /*virtual*/ void inventoryChanged(LLViewerObject* obj,                                   LLInventoryObject::object_list_t* inv,                                   S32 serial_num, -                                 void* queue); +                                 void* queue) override;      // This is called by inventoryChanged      void handleInventory(LLViewerObject* viewer_obj, @@ -85,7 +86,7 @@ private:  private:      // UI -    LLScrollListCtrl* mMessages; +    LLScrollListCtrl* mQueueOutputList = nullptr;      LLButton* mCloseBtn;      // Object Queue diff --git a/indra/newview/llfloaterchatmentionpicker.cpp b/indra/newview/llfloaterchatmentionpicker.cpp new file mode 100644 index 0000000000..1cfed122a9 --- /dev/null +++ b/indra/newview/llfloaterchatmentionpicker.cpp @@ -0,0 +1,184 @@ +/** + * @file llfloaterchatmentionpicker.cpp + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterchatmentionpicker.h" + +#include "llavatarlist.h" +#include "llfloaterimcontainer.h" +#include "llchatmentionhelper.h" +#include "llparticipantlist.h" + +LLUUID LLFloaterChatMentionPicker::sSessionID(LLUUID::null); + +LLFloaterChatMentionPicker::LLFloaterChatMentionPicker(const LLSD& key) +: LLFloater(key), mAvatarList(NULL) +{ +    // This floater should hover on top of our dependent (with the dependent having the focus) +    setFocusStealsFrontmost(false); +    setBackgroundVisible(false); +    setAutoFocus(false); +} + +bool LLFloaterChatMentionPicker::postBuild() +{ +    mAvatarList = getChild<LLAvatarList>("avatar_list"); +    mAvatarList->setShowCompleteName(true, true); +    mAvatarList->setFocusOnItemClicked(false); +    mAvatarList->setItemClickedCallback([this](LLUICtrl* ctrl, S32 x, S32 y, MASK mask) +    { +        if (LLAvatarListItem* item = dynamic_cast<LLAvatarListItem*>(ctrl)) +        { +            selectResident(item->getAvatarId()); +        } +    }); +    mAvatarList->setRefreshCompleteCallback([this](LLUICtrl* ctrl, const LLSD& param) +    { +        if (mAvatarList->numSelected() == 0) +        { +            mAvatarList->selectFirstItem(); +        } +    }); + +    return LLFloater::postBuild(); +} + +void LLFloaterChatMentionPicker::onOpen(const LLSD& key) +{ +    buildAvatarList(); +    mAvatarList->setNameFilter(key.has("av_name") ? key["av_name"].asString() : ""); + +    gFloaterView->adjustToFitScreen(this, false); +} + +uuid_vec_t LLFloaterChatMentionPicker::getParticipantIds() +{ +    LLParticipantList* item = dynamic_cast<LLParticipantList*>(LLFloaterIMContainer::getInstance()->getSessionModel(sSessionID)); +    if (!item) +    { +        LL_WARNS() << "Participant list is missing" << LL_ENDL; +        return {}; +    } + +    uuid_vec_t avatar_ids; +    LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); +    LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); +    while (current_participant_model != end_participant_model) +    { +        LLConversationItem* participant_model = dynamic_cast<LLConversationItem*>(*current_participant_model); +        if (participant_model) +        { +            avatar_ids.push_back(participant_model->getUUID()); +        } +        current_participant_model++; +    } +    return avatar_ids; +} + +void LLFloaterChatMentionPicker::buildAvatarList() +{ +    uuid_vec_t& avatar_ids = mAvatarList->getIDs(); +    avatar_ids = getParticipantIds(); +    updateAvatarList(avatar_ids); +    mAvatarList->setDirty(); +} + +void LLFloaterChatMentionPicker::selectResident(const LLUUID& id) +{ +    if (id.isNull()) +        return; + +    setValue(stringize("secondlife:///app/agent/", id.asString(), "/mention ")); +    onCommit(); +    LLChatMentionHelper::instance().hideHelper(); +} + +void LLFloaterChatMentionPicker::onClose(bool app_quitting) +{ +    if (!app_quitting) +    { +        LLChatMentionHelper::instance().hideHelper(); +    } +} + +bool LLFloaterChatMentionPicker::handleKey(KEY key, MASK mask, bool called_from_parent) +{ +    if (mask == MASK_NONE) +    { +        switch (key) +        { +            case KEY_UP: +            case KEY_DOWN: +                return mAvatarList->handleKey(key, mask, called_from_parent); +            case KEY_RETURN: +            case KEY_TAB: +                selectResident(mAvatarList->getSelectedUUID()); +                return true; +            case KEY_ESCAPE: +                LLChatMentionHelper::instance().hideHelper(); +                return true; +            case KEY_LEFT: +            case KEY_RIGHT: +                return true; +            default: +                break; +        } +    } +    return LLFloater::handleKey(key, mask, called_from_parent); +} + +void LLFloaterChatMentionPicker::goneFromFront() +{ +    LLChatMentionHelper::instance().hideHelper(); +} + +void LLFloaterChatMentionPicker::updateSessionID(LLUUID session_id) +{ +    sSessionID = session_id; + +    LLParticipantList* item = dynamic_cast<LLParticipantList*>(LLFloaterIMContainer::getInstance()->getSessionModel(sSessionID)); +    if (!item) +    { +        LL_WARNS() << "Participant list is missing" << LL_ENDL; +        return; +    } + +    uuid_vec_t avatar_ids = getParticipantIds(); +    updateAvatarList(avatar_ids); +} + +void LLFloaterChatMentionPicker::updateAvatarList(uuid_vec_t& avatar_ids) +{ +    std::vector<std::string> av_names; +    for (auto& id : avatar_ids) +    { +        LLAvatarName av_name; +        LLAvatarNameCache::get(id, &av_name); +        av_names.push_back(utf8str_tolower(av_name.getAccountName())); +        av_names.push_back(utf8str_tolower(av_name.getDisplayName())); +    } +    LLChatMentionHelper::instance().updateAvatarList(av_names); +} diff --git a/indra/newview/llfloaterchatmentionpicker.h b/indra/newview/llfloaterchatmentionpicker.h new file mode 100644 index 0000000000..8d221d7a89 --- /dev/null +++ b/indra/newview/llfloaterchatmentionpicker.h @@ -0,0 +1,58 @@ +/** + * @file llfloaterchatmentionpicker.h + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERCHATMENTIONPICKER_H +#define LLFLOATERCHATMENTIONPICKER_H + +#include "llfloater.h" + +class LLAvatarList; + +class LLFloaterChatMentionPicker : public LLFloater +{ +public: +    LLFloaterChatMentionPicker(const LLSD& key); + +    virtual bool postBuild() override; +    virtual void goneFromFront() override; + +    void buildAvatarList(); + +    static uuid_vec_t getParticipantIds(); +    static void updateSessionID(LLUUID session_id); +    static void updateAvatarList(uuid_vec_t& avatar_ids); + +private: + +    void onOpen(const LLSD& key) override; +    void onClose(bool app_quitting) override; +    virtual bool handleKey(KEY key, MASK mask, bool called_from_parent) override; +    void selectResident(const LLUUID& id); + +    static LLUUID sSessionID; +    LLAvatarList* mAvatarList; +}; + +#endif diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index db6f9ac22a..b649514bff 100644 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -586,7 +586,7 @@ void LLFloaterIMNearbyChat::sendChat( EChatType type )  {      if (mInputEditor)      { -        LLWString text = mInputEditor->getWText(); +        LLWString text = mInputEditor->getConvertedText();          LLWStringUtil::trim(text);          LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.          if (!text.empty()) diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index 185274981b..84a9fad708 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -251,7 +251,7 @@ void LLFloaterIMSession::sendMsgFromInputEditor()      {          if (mInputEditor)          { -            LLWString text = mInputEditor->getWText(); +            LLWString text = mInputEditor->getConvertedText();              LLWStringUtil::trim(text);              LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.              if(!text.empty()) diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 553aa1ff4b..cd428c67ef 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/llfloaternewfeaturenotification.cpp b/indra/newview/llfloaternewfeaturenotification.cpp index 369727ff1e..1badcdd3d9 100644 --- a/indra/newview/llfloaternewfeaturenotification.cpp +++ b/indra/newview/llfloaternewfeaturenotification.cpp @@ -43,12 +43,28 @@ bool LLFloaterNewFeatureNotification::postBuild()      setCanDrag(false);      getChild<LLButton>("close_btn")->setCommitCallback(boost::bind(&LLFloaterNewFeatureNotification::onCloseBtn, this)); -    const std::string title_txt = "title_txt"; -    const std::string dsc_txt = "description_txt"; -    std::string feature = "_" + getKey().asString(); +    if (getKey().isString()) +    { +        const std::string title_txt = "title_txt"; +        const std::string dsc_txt = "description_txt"; -    getChild<LLUICtrl>(title_txt)->setValue(getString(title_txt + feature)); -    getChild<LLUICtrl>(dsc_txt)->setValue(getString(dsc_txt + feature)); +        std::string feature = "_" + getKey().asString(); +        if (hasString(title_txt + feature)) +        { +            getChild<LLUICtrl>(title_txt)->setValue(getString(title_txt + feature)); +            getChild<LLUICtrl>(dsc_txt)->setValue(getString(dsc_txt + feature)); +        } +        else +        { +            // Show blank +            LL_WARNS() << "Feature \"" << getKey().asString() <<  "\" not found for feature notification" << LL_ENDL; +        } +    } +    else +    { +        // Show blank +        LL_WARNS() << "Feature notification without a feature" << LL_ENDL; +    }      if (getKey().asString() == "gltf")      { diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index de1c7a7ed8..c9e9d50e19 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -2768,6 +2768,7 @@ bool LLInventoryModel::loadSkeleton(          bool is_cache_obsolete = false;          if (loadFromFile(inventory_filename, categories, items, categories_to_update, is_cache_obsolete))          { +            LL_PROFILE_ZONE_NAMED("loadFromFile");              // We were able to find a cache of files. So, use what we              // found to generate a set of categories we should add. We              // will go through each category loaded and if the version diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 48c80842b9..851107b3be 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -544,6 +544,66 @@ bool RequestStats::isDelayed() const      return mTimer.getStarted() && !mTimer.hasExpired();  } +F32 calculate_score(LLVOVolume* object) +{ +    if (!object) +    { +        return -1.f; +    } +    LLDrawable* drawable = object->mDrawable; +    if (!drawable) +    { +        return -1; +    } +    if (drawable->isState(LLDrawable::RIGGED) || object->isAttachment()) +    { +        LLVOAvatar* avatar = object->getAvatar(); +        LLDrawable* av_drawable = avatar ? avatar->mDrawable : nullptr; +        if (avatar && av_drawable) +        { +            // See LLVOVolume::calcLOD() +            F32 radius; +            if (avatar->isControlAvatar()) +            { +                const LLVector3* box = avatar->getLastAnimExtents(); +                LLVector3 diag = box[1] - box[0]; +                radius = diag.magVec() * 0.5f; +            } +            else +            { +                // Volume in a rigged mesh attached to a regular avatar. +                const LLVector3* box = avatar->getLastAnimExtents(); +                LLVector3 diag = box[1] - box[0]; +                radius = diag.magVec(); + +                if (!avatar->isSelf() && !avatar->hasFirstFullAttachmentData()) +                { +                    // slightly deprioritize avatars that are still receiving data +                    radius *= 0.9f; +                } +            } +            return radius / llmax(av_drawable->mDistanceWRTCamera, 1.f); +        } +    } +    return drawable->getRadius() / llmax(drawable->mDistanceWRTCamera, 1.f); +} + +void PendingRequestBase::updateScore() +{ +    mScore = 0; +    if (mTrackedData) +    { +        for (LLVOVolume* volume : mTrackedData->mVolumes) +        { +            F32 cur_score = calculate_score(volume); +            if (cur_score > 0) +            { +                mScore = llmax(mScore, cur_score); +            } +        } +    } +} +  LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMaterial& material)  {      LLPointer< LLViewerFetchedTexture > * ppTex = static_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData); @@ -1809,42 +1869,36 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)  //static  void LLMeshRepoThread::incActiveLODRequests()  { -    LLMutexLock lock(gMeshRepo.mThread->mMutex);      ++LLMeshRepoThread::sActiveLODRequests;  }  //static  void LLMeshRepoThread::decActiveLODRequests()  { -    LLMutexLock lock(gMeshRepo.mThread->mMutex);      --LLMeshRepoThread::sActiveLODRequests;  }  //static  void LLMeshRepoThread::incActiveHeaderRequests()  { -    LLMutexLock lock(gMeshRepo.mThread->mMutex);      ++LLMeshRepoThread::sActiveHeaderRequests;  }  //static  void LLMeshRepoThread::decActiveHeaderRequests()  { -    LLMutexLock lock(gMeshRepo.mThread->mMutex);      --LLMeshRepoThread::sActiveHeaderRequests;  }  //static  void LLMeshRepoThread::incActiveSkinRequests()  { -    LLMutexLock lock(gMeshRepo.mThread->mMutex);      ++LLMeshRepoThread::sActiveSkinRequests;  }  //static  void LLMeshRepoThread::decActiveSkinRequests()  { -    LLMutexLock lock(gMeshRepo.mThread->mMutex);      --LLMeshRepoThread::sActiveSkinRequests;  } @@ -2226,7 +2280,7 @@ EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mes                  if (gMeshRepo.mLoadingSkins.find(mesh_id) == gMeshRepo.mLoadingSkins.end())                  { -                    gMeshRepo.mLoadingSkins[mesh_id] = {}; // add an empty vector to indicate to main thread that we are loading skin info +                    gMeshRepo.mLoadingSkins[mesh_id]; // add an empty vector to indicate to main thread that we are loading skin info                  }              } @@ -4218,13 +4272,13 @@ void LLMeshRepository::unregisterMesh(LLVOVolume* vobj)      {          for (auto& param : lod)          { -            vector_replace_with_last(param.second, vobj); +            vector_replace_with_last(param.second.mVolumes, vobj);          }      }      for (auto& skin_pair : mLoadingSkins)      { -        vector_replace_with_last(skin_pair.second, vobj); +        vector_replace_with_last(skin_pair.second.mVolumes, vobj);      }  } @@ -4247,16 +4301,17 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para          mesh_load_map::iterator iter = mLoadingMeshes[new_lod].find(mesh_id);          if (iter != mLoadingMeshes[new_lod].end())          { //request pending for this mesh, append volume id to list -            auto it = std::find(iter->second.begin(), iter->second.end(), vobj); -            if (it == iter->second.end()) { -                iter->second.push_back(vobj); +            auto it = std::find(iter->second.mVolumes.begin(), iter->second.mVolumes.end(), vobj); +            if (it == iter->second.mVolumes.end()) { +                iter->second.addVolume(vobj);              }          }          else          {              //first request for this mesh -            mLoadingMeshes[new_lod][mesh_id].push_back(vobj); -            mPendingRequests.emplace_back(new PendingRequestLOD(mesh_params, new_lod)); +            std::shared_ptr<PendingRequestBase> request(new PendingRequestLOD(mesh_params, new_lod)); +            mPendingRequests.emplace_back(request); +            mLoadingMeshes[new_lod][mesh_id].initData(vobj, request);              LLMeshRepository::sLODPending++;          }      } @@ -4315,50 +4370,6 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para      return new_lod;  } -F32 calculate_score(LLVOVolume* object) -{ -    if (!object) -    { -        return -1.f; -    } -    LLDrawable* drawable = object->mDrawable; -    if (!drawable) -    { -        return -1; -    } -    if (drawable->isState(LLDrawable::RIGGED) || object->isAttachment()) -    { -        LLVOAvatar* avatar = object->getAvatar(); -        LLDrawable* av_drawable = avatar ? avatar->mDrawable : nullptr; -        if (avatar && av_drawable) -        { -            // See LLVOVolume::calcLOD() -            F32 radius; -            if (avatar->isControlAvatar()) -            { -                const LLVector3* box = avatar->getLastAnimExtents(); -                LLVector3 diag = box[1] - box[0]; -                radius = diag.magVec() * 0.5f; -            } -            else -            { -                // Volume in a rigged mesh attached to a regular avatar. -                const LLVector3* box = avatar->getLastAnimExtents(); -                LLVector3 diag = box[1] - box[0]; -                radius = diag.magVec(); - -                if (!avatar->isSelf() && !avatar->hasFirstFullAttachmentData()) -                { -                    // slightly deprioritize avatars that are still receiving data -                    radius *= 0.9f; -                } -            } -            return radius / llmax(av_drawable->mDistanceWRTCamera, 1.f); -        } -    } -    return drawable->getRadius() / llmax(drawable->mDistanceWRTCamera, 1.f); -} -  void LLMeshRepository::notifyLoadedMeshes()  { //called from main thread      LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); @@ -4495,13 +4506,20 @@ void LLMeshRepository::notifyLoadedMeshes()      {          LLMutexTrylock lock1(mMeshMutex);          LLMutexTrylock lock2(mThread->mMutex); +        LLMutexTrylock lock3(mThread->mHeaderMutex); +        LLMutexTrylock lock4(mThread->mPendingMutex);          static U32 hold_offs(0); -        if (! lock1.isLocked() || ! lock2.isLocked()) +        if (! lock1.isLocked() || ! lock2.isLocked() || ! lock3.isLocked() || ! lock4.isLocked())          {              // If we can't get the locks, skip and pick this up later. +            // Eventually thread queue will be free enough              ++hold_offs;              sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs); +            if (hold_offs > 4) +            { +                LL_WARNS_ONCE() << "High mesh thread holdoff" << LL_ENDL; +            }              return;          }          hold_offs = 0; @@ -4548,61 +4566,25 @@ void LLMeshRepository::notifyLoadedMeshes()              if (mPendingRequests.size() > push_count)              { +                LL_PROFILE_ZONE_NAMED("Mesh score update");                  // More requests than the high-water limit allows so                  // sort and forward the most important. -                //calculate "score" for pending requests - -                //create score map -                std::map<LLUUID, F32> score_map; - -                for (U32 i = 0; i < LLVolumeLODGroup::NUM_LODS; ++i) -                { -                    for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin();  iter != mLoadingMeshes[i].end(); ++iter) -                    { -                        F32 max_score = 0.f; -                        for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) -                        { -                            F32 cur_score = calculate_score(*obj_iter); -                            if (cur_score >= 0.f) -                            { -                                max_score = llmax(max_score, cur_score); -                            } -                        } - -                        score_map[iter->first] = max_score; -                    } -                } -                for (mesh_load_map::iterator iter = mLoadingSkins.begin(); iter != mLoadingSkins.end(); ++iter) -                { -                    F32 max_score = 0.f; -                    for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) -                    { -                        F32 cur_score = calculate_score(*obj_iter); -                        if (cur_score >= 0.f) -                        { -                            max_score = llmax(max_score, cur_score); -                        } -                    } - -                    score_map[iter->first] = max_score; -                } - -                //set "score" for pending requests -                for (std::unique_ptr<PendingRequestBase>& req_p : mPendingRequests) +                // update "score" for pending requests +                for (std::shared_ptr<PendingRequestBase>& req_p : mPendingRequests)                  { -                    req_p->setScore(score_map[req_p->getId()]); +                    req_p->checkScore();                  }                  //sort by "score"                  std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count,                                    mPendingRequests.end(), PendingRequestBase::CompareScoreGreater());              } -            LLMutexTrylock lock3(mThread->mHeaderMutex); -            LLMutexTrylock lock4(mThread->mPendingMutex);              while (!mPendingRequests.empty() && push_count > 0)              { -                std::unique_ptr<PendingRequestBase>& req_p = mPendingRequests.front(); +                std::shared_ptr<PendingRequestBase>& req_p = mPendingRequests.front(); +                // todo: check hasTrackedData here and erase request if none +                // since this is supposed to mean that request was removed                  switch (req_p->getRequestType())                  {                  case MESH_REQUEST_LOD: @@ -4657,7 +4639,7 @@ void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo* info)      skin_load_map::iterator iter = mLoadingSkins.find(info->mMeshID);      if (iter != mLoadingSkins.end())      { -        for (LLVOVolume* vobj : iter->second) +        for (LLVOVolume* vobj : iter->second.mVolumes)          {              if (vobj)              { @@ -4673,7 +4655,7 @@ void LLMeshRepository::notifySkinInfoUnavailable(const LLUUID& mesh_id)      skin_load_map::iterator iter = mLoadingSkins.find(mesh_id);      if (iter != mLoadingSkins.end())      { -        for (LLVOVolume* vobj : iter->second) +        for (LLVOVolume* vobj : iter->second.mVolumes)          {              if (vobj)              { @@ -4737,7 +4719,7 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol          }          //notify waiting LLVOVolume instances that their requested mesh is available -        for (LLVOVolume* vobj : obj_iter->second) +        for (LLVOVolume* vobj : obj_iter->second.mVolumes)          {              if (vobj)              { @@ -4767,7 +4749,7 @@ void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params,              LLPrimitive::getVolumeManager()->unrefVolume(sys_volume);          } -        for (LLVOVolume* vobj : obj_iter->second) +        for (LLVOVolume* vobj : obj_iter->second.mVolumes)          {              if (vobj)              { @@ -4810,16 +4792,17 @@ const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, LLVOV              skin_load_map::iterator iter = mLoadingSkins.find(mesh_id);              if (iter != mLoadingSkins.end())              { //request pending for this mesh, append volume id to list -                auto it = std::find(iter->second.begin(), iter->second.end(), requesting_obj); -                if (it == iter->second.end()) { -                    iter->second.push_back(requesting_obj); +                auto it = std::find(iter->second.mVolumes.begin(), iter->second.mVolumes.end(), requesting_obj); +                if (it == iter->second.mVolumes.end()) { +                    iter->second.addVolume(requesting_obj);                  }              }              else              {                  //first request for this mesh -                mLoadingSkins[mesh_id].push_back(requesting_obj); -                mPendingRequests.emplace_back(new PendingRequestUUID(mesh_id, MESH_REQUEST_SKIN)); +                std::shared_ptr<PendingRequestBase> request(new PendingRequestUUID(mesh_id, MESH_REQUEST_SKIN)); +                mLoadingSkins[mesh_id].initData(requesting_obj, request); +                mPendingRequests.emplace_back(request);              }          }      } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index b9acb3573f..0847c29d0d 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -168,7 +168,6 @@ public:      void submitRequest(Request* request);      static S32 llcdCallback(const char*, S32, S32); -    void cancel();      void setMeshData(LLCDMeshData& mesh, bool vertex_based);      void doDecomposition(); @@ -206,19 +205,19 @@ private:      LLFrameTimer mTimer;  }; - +class MeshLoadData;  class PendingRequestBase  {  public:      struct CompareScoreGreater      { -        bool operator()(const std::unique_ptr<PendingRequestBase>& lhs, const std::unique_ptr<PendingRequestBase>& rhs) +        bool operator()(const std::shared_ptr<PendingRequestBase>& lhs, const std::shared_ptr<PendingRequestBase>& rhs)          {              return lhs->mScore > rhs->mScore; // greatest = first          }      }; -    PendingRequestBase() : mScore(0.f) {}; +    PendingRequestBase() : mScore(0.f), mTrackedData(nullptr), mScoreDirty(true) {};      virtual ~PendingRequestBase() {}      bool operator<(const PendingRequestBase& rhs) const @@ -226,14 +225,34 @@ public:          return mId < rhs.mId;      } -    void setScore(F32 score) { mScore = score; }      F32 getScore() const { return mScore; } +    void checkScore() +    { +        constexpr F32 EXPIRE_TIME_SECS = 8.f; +        if (mScoreTimer.getElapsedTimeF32() > EXPIRE_TIME_SECS || mScoreDirty) +        { +            updateScore(); +            mScoreDirty = false; +            mScoreTimer.reset(); +        } +    }; +      LLUUID getId() const { return mId; }      virtual EMeshRequestType getRequestType() const = 0; +    void trackData(MeshLoadData* data) { mTrackedData = data; mScoreDirty = true; } +    void untrackData() { mTrackedData = nullptr; } +    bool hasTrackedData() { return mTrackedData != nullptr; } +    void setScoreDirty() { mScoreDirty = true; } +  protected: -    F32 mScore; +    void updateScore(); +      LLUUID mId; +    F32 mScore; +    bool mScoreDirty; +    LLTimer mScoreTimer; +    MeshLoadData* mTrackedData;  };  class PendingRequestLOD : public PendingRequestBase @@ -267,6 +286,37 @@ private:      EMeshRequestType mRequestType;  }; + +class MeshLoadData +{ +public: +    MeshLoadData() {} +    ~MeshLoadData() +    { +        if (std::shared_ptr<PendingRequestBase> request = mRequest.lock()) +        { +            request->untrackData(); +        } +    } +    void initData(LLVOVolume* vol, std::shared_ptr<PendingRequestBase>& request) +    { +        mVolumes.push_back(vol); +        request->trackData(this); +        mRequest = request; +    } +    void addVolume(LLVOVolume* vol) +    { +        mVolumes.push_back(vol); +        if (std::shared_ptr<PendingRequestBase> request = mRequest.lock()) +        { +            request->setScoreDirty(); +        } +    } +    std::vector<LLVOVolume*> mVolumes; +private: +    std::weak_ptr<PendingRequestBase> mRequest; +}; +  class LLMeshHeader  {  public: @@ -814,7 +864,7 @@ public:      static void metricsProgress(unsigned int count);      static void metricsUpdate(); -    typedef std::unordered_map<LLUUID, std::vector<LLVOVolume*> > mesh_load_map; +    typedef std::unordered_map<LLUUID, MeshLoadData> mesh_load_map;      mesh_load_map mLoadingMeshes[4];      typedef std::unordered_map<LLUUID, LLPointer<LLMeshSkinInfo>> skin_map; @@ -825,11 +875,11 @@ public:      LLMutex*                    mMeshMutex; -    typedef std::vector <std::unique_ptr<PendingRequestBase> > pending_requests_vec; +    typedef std::vector <std::shared_ptr<PendingRequestBase> > pending_requests_vec;      pending_requests_vec mPendingRequests;      //list of mesh ids awaiting skin info -    typedef std::unordered_map<LLUUID, std::vector<LLVOVolume*> > skin_load_map; +    typedef std::unordered_map<LLUUID, MeshLoadData > skin_load_map;      skin_load_map mLoadingSkins;      //list of mesh ids awaiting decompositions diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 3a164ec94b..5b059516cd 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -1106,6 +1106,63 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)          updateVisibility(objectp); +        bool missing_asset = false; +        { +            LLGLenum image_format = GL_RGB; +            bool identical_image_format = false; +            LLSelectedTE::getImageFormat(image_format, identical_image_format, missing_asset); + +            if (!missing_asset) +            { +                mIsAlpha = false; +                switch (image_format) +                { +                    case GL_RGBA: +                    case GL_ALPHA: +                    { +                        mIsAlpha = true; +                    } +                    break; + +                    case GL_RGB: +                        break; +                    default: +                    { +                        LL_WARNS() << "Unexpected tex format in LLPanelFace...resorting to no alpha" << LL_ENDL; +                    } +                    break; +                } +            } +            else +            { +                // Don't know image's properties, use material's mode value +                mIsAlpha = true; +            } + +            // Diffuse Alpha Mode +            // Init to the default that is appropriate for the alpha content of the asset +            // +            U8 alpha_mode = mIsAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + +            bool identical_alpha_mode = false; + +            // See if that's been overridden by a material setting for same... +            // +            LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode, mIsAlpha); + +            // it is invalid to have any alpha mode other than blend if transparency is greater than zero ... +            // Want masking? Want emissive? Tough! You get BLEND! +            alpha_mode = (transparency > 0.f) ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : alpha_mode; + +            // ... unless there is no alpha channel in the texture, in which case alpha mode MUST be none +            alpha_mode = mIsAlpha ? alpha_mode : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + +            mComboAlphaMode->getSelectionInterface()->selectNthItem(alpha_mode); +            updateAlphaControls(); + +            mExcludeWater &= (LLMaterial::DIFFUSE_ALPHA_MODE_BLEND == alpha_mode); +        } +          // Water exclusion          {              mCheckHideWater->setEnabled(editable && !has_pbr_material && !isMediaTexSelected()); @@ -1188,65 +1245,11 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)          // Texture          { -            LLGLenum image_format = GL_RGB; -            bool identical_image_format = false; -            bool missing_asset = false; -            LLSelectedTE::getImageFormat(image_format, identical_image_format, missing_asset); - -            if (!missing_asset) -            { -                mIsAlpha = false; -                switch (image_format) -                { -                case GL_RGBA: -                case GL_ALPHA: -                    { -                        mIsAlpha = true; -                    } -                    break; - -                case GL_RGB: break; -                default: -                    { -                        LL_WARNS() << "Unexpected tex format in LLPanelFace...resorting to no alpha" << LL_ENDL; -                    } -                    break; -                } -            } -            else -            { -                // Don't know image's properties, use material's mode value -                mIsAlpha = true; -            } -              if (LLViewerMedia::getInstance()->textureHasMedia(id))              {                  mBtnAlign->setEnabled(editable);              } -            // Diffuse Alpha Mode - -            // Init to the default that is appropriate for the alpha content of the asset -            // -            U8 alpha_mode = mIsAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - -            bool identical_alpha_mode = false; - -            // See if that's been overridden by a material setting for same... -            // -            LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode, mIsAlpha); - -            // it is invalid to have any alpha mode other than blend if transparency is greater than zero ... -            // Want masking? Want emissive? Tough! You get BLEND! -            alpha_mode = (transparency > 0.f) ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : alpha_mode; - -            // ... unless there is no alpha channel in the texture, in which case alpha mode MUST be none -            alpha_mode = mIsAlpha ? alpha_mode : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - -            mComboAlphaMode->getSelectionInterface()->selectNthItem(alpha_mode); - -            updateAlphaControls(); -              if (mTextureCtrl)              {                  if (identical_diffuse) @@ -4009,6 +4012,85 @@ void LLPanelFace::onPasteColor(LLViewerObject* objectp, S32 te)      }  } +void set_item_availability( +    const LLUUID& id, +    LLSD& dest, +    const std::string& modifier, +    bool is_creator, +    std::map<LLUUID, LLUUID> &asset_item_map, +    LLViewerObject* objectp) +{ +    if (id.isNull()) +    { +        return; +    } + +    LLUUID item_id; +    bool from_library = get_is_predefined_texture(id); +    bool full_perm = from_library; +    full_perm |= is_creator; + +    if (!full_perm) +    { +        std::map<LLUUID, LLUUID>::iterator iter = asset_item_map.find(id); +        if (iter != asset_item_map.end()) +        { +            item_id = iter->second; +        } +        else +        { +            // What this does is simply searches inventory for item with same asset id, +            // as result it is Hightly unreliable, leaves little control to user, borderline hack +            // but there are little options to preserve permissions - multiple inventory +            // items might reference same asset and inventory search is expensive. +            bool no_transfer = false; +            if (objectp->getInventoryItemByAsset(id)) +            { +                no_transfer = !objectp->getInventoryItemByAsset(id)->getIsFullPerm(); +            } +            item_id = get_copy_free_item_by_asset_id(id, no_transfer); +            // record value to avoid repeating inventory search when possible +            asset_item_map[id] = item_id; +        } +    } + +    if (item_id.notNull() && gInventory.isObjectDescendentOf(item_id, gInventory.getLibraryRootFolderID())) +    { +        full_perm = true; +        from_library = true; +    } + +    dest[modifier + "itemfullperm"] = full_perm; +    dest[modifier + "fromlibrary"] = from_library; + +    // If full permission object, texture is free to copy, +    // but otherwise we need to check inventory and extract permissions +    // +    // Normally we care only about restrictions for current user and objects +    // don't inherit any 'next owner' permissions from texture, so there is +    // no need to record item id if full_perm==true +    if (!full_perm && item_id.notNull()) +    { +        LLViewerInventoryItem* itemp = gInventory.getItem(item_id); +        if (itemp) +        { +            LLPermissions item_permissions = itemp->getPermissions(); +            if (item_permissions.allowOperationBy(PERM_COPY, +                gAgent.getID(), +                gAgent.getGroupID())) +            { +                dest[modifier + "itemid"] = item_id; +                dest[modifier + "itemfullperm"] = itemp->getIsFullPerm(); +                if (!itemp->isFinished()) +                { +                    // needed for dropTextureAllFaces +                    LLInventoryModelBackgroundFetch::instance().start(item_id, false); +                } +            } +        } +    } +} +  void LLPanelFace::onCopyTexture()  {      LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); @@ -4046,6 +4128,7 @@ void LLPanelFace::onCopyTexture()              if (tep)              {                  LLSD te_data; +                LLUUID pbr_id = objectp->getRenderMaterialID(te);                  // asLLSD() includes media                  te_data["te"] = tep->asLLSD(); @@ -4054,21 +4137,20 @@ void LLPanelFace::onCopyTexture()                  te_data["te"]["bumpshiny"] = tep->getBumpShiny();                  te_data["te"]["bumpfullbright"] = tep->getBumpShinyFullbright();                  te_data["te"]["texgen"] = tep->getTexGen(); -                te_data["te"]["pbr"] = objectp->getRenderMaterialID(te); +                te_data["te"]["pbr"] = pbr_id;                  if (tep->getGLTFMaterialOverride() != nullptr)                  {                      te_data["te"]["pbr_override"] = tep->getGLTFMaterialOverride()->asJSON();                  } -                if (te_data["te"].has("imageid")) +                if (te_data["te"].has("imageid") || pbr_id.notNull())                  { -                    LLUUID item_id; -                    LLUUID id = te_data["te"]["imageid"].asUUID(); -                    bool from_library = get_is_predefined_texture(id); -                    bool full_perm = from_library; +                    LLUUID img_id = te_data["te"]["imageid"].asUUID(); +                    bool pbr_from_library = false; +                    bool pbr_full_perm = false; +                    bool is_creator = false; -                    if (!full_perm -                        && objectp->permCopy() +                    if (objectp->permCopy()                          && objectp->permTransfer()                          && objectp->permModify())                      { @@ -4078,66 +4160,31 @@ void LLPanelFace::onCopyTexture()                          std::string creator_app_link;                          LLUUID creator_id;                          LLSelectMgr::getInstance()->selectGetCreator(creator_id, creator_app_link); -                        full_perm = objectp->mOwnerID == creator_id; +                        is_creator = objectp->mOwnerID == creator_id;                      } -                    if (id.notNull() && !full_perm) +                    // check permissions for blin-phong/diffuse image and for pbr asset +                    if (img_id.notNull())                      { -                        std::map<LLUUID, LLUUID>::iterator iter = asset_item_map.find(id); -                        if (iter != asset_item_map.end()) -                        { -                            item_id = iter->second; -                        } -                        else -                        { -                            // What this does is simply searches inventory for item with same asset id, -                            // as result it is Hightly unreliable, leaves little control to user, borderline hack -                            // but there are little options to preserve permissions - multiple inventory -                            // items might reference same asset and inventory search is expensive. -                            bool no_transfer = false; -                            if (objectp->getInventoryItemByAsset(id)) -                            { -                                no_transfer = !objectp->getInventoryItemByAsset(id)->getIsFullPerm(); -                            } -                            item_id = get_copy_free_item_by_asset_id(id, no_transfer); -                            // record value to avoid repeating inventory search when possible -                            asset_item_map[id] = item_id; -                        } +                        set_item_availability(img_id, te_data["te"], "img", is_creator, asset_item_map, objectp);                      } - -                    if (item_id.notNull() && gInventory.isObjectDescendentOf(item_id, gInventory.getLibraryRootFolderID())) +                    if (pbr_id.notNull())                      { -                        full_perm = true; -                        from_library = true; -                    } +                        set_item_availability(pbr_id, te_data["te"], "pbr", is_creator, asset_item_map, objectp); -                    { -                        te_data["te"]["itemfullperm"] = full_perm; -                        te_data["te"]["fromlibrary"] = from_library; - -                        // If full permission object, texture is free to copy, -                        // but otherwise we need to check inventory and extract permissions -                        // -                        // Normally we care only about restrictions for current user and objects -                        // don't inherit any 'next owner' permissions from texture, so there is -                        // no need to record item id if full_perm==true -                        if (!full_perm && !from_library && item_id.notNull()) +                        // permissions for overrides +                        // Overrides do not permit no-copy textures +                        LLGLTFMaterial* override = tep->getGLTFMaterialOverride(); +                        if (override != nullptr)                          { -                            LLViewerInventoryItem* itemp = gInventory.getItem(item_id); -                            if (itemp) +                            for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)                              { -                                LLPermissions item_permissions = itemp->getPermissions(); -                                if (item_permissions.allowOperationBy(PERM_COPY, -                                    gAgent.getID(), -                                    gAgent.getGroupID())) +                                LLUUID& texture_id = override->mTextureId[i]; +                                if (texture_id.notNull())                                  { -                                    te_data["te"]["imageitemid"] = item_id; -                                    te_data["te"]["itemfullperm"] = itemp->getIsFullPerm(); -                                    if (!itemp->isFinished()) -                                    { -                                        // needed for dropTextureAllFaces -                                        LLInventoryModelBackgroundFetch::instance().start(item_id, false); -                                    } +                                    const std::string prefix = "pbr" + std::to_string(i); +                                    te_data["te"][prefix + "imageid"] = texture_id; +                                    set_item_availability(texture_id, te_data["te"], prefix, is_creator, asset_item_map, objectp);                                  }                              }                          } @@ -4201,6 +4248,44 @@ void LLPanelFace::onCopyTexture()      }  } +bool get_full_permission(const LLSD& te, const std::string &prefix) +{ +    return te.has(prefix + "itemfullperm") && te[prefix+"itemfullperm"].asBoolean(); +} + +bool LLPanelFace::validateInventoryItem(const LLSD& te, const std::string& prefix) +{ +    if (te.has(prefix + "itemid")) +    { +        LLUUID item_id = te[prefix + "itemid"].asUUID(); +        if (item_id.notNull()) +        { +            LLViewerInventoryItem* itemp = gInventory.getItem(item_id); +            if (!itemp) +            { +                // image might be in object's inventory, but it can be not up to date +                LLSD notif_args; +                static std::string reason = getString("paste_error_inventory_not_found"); +                notif_args["REASON"] = reason; +                LLNotificationsUtil::add("FacePasteFailed", notif_args); +                return false; +            } +        } +    } +    else +    { +        // Item was not found on 'copy' stage +        // Since this happened at copy, might be better to either show this +        // at copy stage or to drop clipboard here +        LLSD notif_args; +        static std::string reason = getString("paste_error_inventory_not_found"); +        notif_args["REASON"] = reason; +        LLNotificationsUtil::add("FacePasteFailed", notif_args); +        return false; +    } +    return true; +} +  void LLPanelFace::onPasteTexture()  {      if (!mClipboardParams.has("texture")) @@ -4265,39 +4350,49 @@ void LLPanelFace::onPasteTexture()      for (; iter != end; ++iter)      {          const LLSD& te_data = *iter; -        if (te_data.has("te") && te_data["te"].has("imageid")) +        if (te_data.has("te"))          { -            bool full_perm = te_data["te"].has("itemfullperm") && te_data["te"]["itemfullperm"].asBoolean(); -            full_perm_object &= full_perm; -            if (!full_perm) +            if (te_data["te"].has("imageid"))              { -                if (te_data["te"].has("imageitemid")) +                bool full_perm = get_full_permission(te_data["te"], "img"); +                full_perm_object &= full_perm; +                if (!full_perm)                  { -                    LLUUID item_id = te_data["te"]["imageitemid"].asUUID(); -                    if (item_id.notNull()) +                    if (!validateInventoryItem(te_data["te"], "img"))                      { -                        LLViewerInventoryItem* itemp = gInventory.getItem(item_id); -                        if (!itemp) -                        { -                            // image might be in object's inventory, but it can be not up to date -                            LLSD notif_args; -                            static std::string reason = getString("paste_error_inventory_not_found"); -                            notif_args["REASON"] = reason; -                            LLNotificationsUtil::add("FacePasteFailed", notif_args); -                            return; -                        } +                        return;                      }                  } -                else +            } +            if (te_data["te"].has("pbr")) +            { +                bool full_perm = get_full_permission(te_data["te"], "pbr"); +                full_perm_object &= full_perm; +                if (!full_perm)                  { -                    // Item was not found on 'copy' stage -                    // Since this happened at copy, might be better to either show this -                    // at copy stage or to drop clipboard here -                    LLSD notif_args; -                    static std::string reason = getString("paste_error_inventory_not_found"); -                    notif_args["REASON"] = reason; -                    LLNotificationsUtil::add("FacePasteFailed", notif_args); -                    return; +                    if (!validateInventoryItem(te_data["te"], "pbr")) +                    { +                        return; +                    } +                } +                if (te_data["te"].has("pbr_override")) +                { +                    for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i) +                    { +                        const std::string prefix = "pbr" + std::to_string(i); +                        if (te_data["te"].has(prefix + "imageid")) +                        { +                            bool full_perm = get_full_permission(te_data["te"], prefix); +                            full_perm_object &= full_perm; +                            if (!full_perm) +                            { +                                if (!validateInventoryItem(te_data["te"], prefix)) +                                { +                                    return; +                                } +                            } +                        } +                    }                  }              }          } @@ -4322,6 +4417,71 @@ void LLPanelFace::onPasteTexture()      selected_objects->applyToTEs(&navigate_home_func);  } +void get_item_and_permissions(const LLUUID &id, LLViewerInventoryItem*& itemp, bool& full_perm, bool& from_library, const LLSD &data, const std::string &prefix) +{ +    full_perm = get_full_permission(data, prefix); +    from_library = data.has(prefix + "fromlibrary") && data.get(prefix + "fromlibrary").asBoolean(); +    LLViewerInventoryItem* itemp_res = NULL; + +    if (data.has(prefix + "itemid")) +    { +        LLUUID item_id = data.get(prefix + "itemid").asUUID(); +        if (item_id.notNull()) +        { +            LLViewerInventoryItem* itemp = gInventory.getItem(item_id); +            if (itemp && itemp->isFinished()) +            { +                // dropTextureAllFaces will fail if incomplete +                itemp_res = itemp; +            } +            else +            { +                // Theoretically shouldn't happend, but if it does happen, we +                // might need to add a notification to user that paste will fail +                // since inventory isn't fully loaded +                LL_WARNS() << "Item " << item_id << " is incomplete, paste might fail silently." << LL_ENDL; +            } +        } +    } + +    // for case when item got removed from inventory after we pressed 'copy' +    // or texture got pasted into previous object +    if (!itemp_res && !full_perm) +    { +        // Due to checks for imageitemid in LLPanelFace::onPasteTexture() this should no longer be reachable. +        LL_INFOS() << "Item " << data.get(prefix + "itemid").asUUID() << " no longer in inventory." << LL_ENDL; +        // Todo: fix this, we are often searching same texture multiple times (equal to number of faces) +        // Perhaps just mPanelFace->onPasteTexture(objectp, te, &asset_to_item_id_map); ? Not pretty, but will work +        LLViewerInventoryCategory::cat_array_t cats; +        LLViewerInventoryItem::item_array_t items; +        LLAssetIDMatches asset_id_matches(id); +        gInventory.collectDescendentsIf(LLUUID::null, +            cats, +            items, +            LLInventoryModel::INCLUDE_TRASH, +            asset_id_matches); + +        // Extremely unreliable and perfomance unfriendly. +        // But we need this to check permissions and it is how texture control finds items +        for (S32 i = 0; i < items.size(); i++) +        { +            LLViewerInventoryItem* itemp = items[i]; +            if (itemp && itemp->isFinished()) +            { +                // dropTextureAllFaces will fail if incomplete +                LLPermissions item_permissions = itemp->getPermissions(); +                if (item_permissions.allowOperationBy(PERM_COPY, +                    gAgent.getID(), +                    gAgent.getGroupID())) +                { +                    itemp_res = itemp; +                    break; // first match +                } +            } +        } +    } +} +  void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te)  {      LLSD te_data; @@ -4345,77 +4505,22 @@ void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te)          if (te_data.has("te"))          {              // Texture -            bool full_perm = te_data["te"].has("itemfullperm") && te_data["te"]["itemfullperm"].asBoolean(); -            bool from_library = te_data["te"].has("fromlibrary") && te_data["te"]["fromlibrary"].asBoolean();              if (te_data["te"].has("imageid"))              { +                bool img_full_perm = false; +                bool img_from_library = false;                  const LLUUID& imageid = te_data["te"]["imageid"].asUUID(); //texture or asset id -                LLViewerInventoryItem* itemp_res = NULL; +                LLViewerInventoryItem* img_itemp_res = NULL; -                if (te_data["te"].has("imageitemid")) -                { -                    LLUUID item_id = te_data["te"]["imageitemid"].asUUID(); -                    if (item_id.notNull()) -                    { -                        LLViewerInventoryItem* itemp = gInventory.getItem(item_id); -                        if (itemp && itemp->isFinished()) -                        { -                            // dropTextureAllFaces will fail if incomplete -                            itemp_res = itemp; -                        } -                        else -                        { -                            // Theoretically shouldn't happend, but if it does happen, we -                            // might need to add a notification to user that paste will fail -                            // since inventory isn't fully loaded -                            LL_WARNS() << "Item " << item_id << " is incomplete, paste might fail silently." << LL_ENDL; -                        } -                    } -                } -                // for case when item got removed from inventory after we pressed 'copy' -                // or texture got pasted into previous object -                if (!itemp_res && !full_perm) -                { -                    // Due to checks for imageitemid in LLPanelFace::onPasteTexture() this should no longer be reachable. -                    LL_INFOS() << "Item " << te_data["te"]["imageitemid"].asUUID() << " no longer in inventory." << LL_ENDL; -                    // Todo: fix this, we are often searching same texture multiple times (equal to number of faces) -                    // Perhaps just mPanelFace->onPasteTexture(objectp, te, &asset_to_item_id_map); ? Not pretty, but will work -                    LLViewerInventoryCategory::cat_array_t cats; -                    LLViewerInventoryItem::item_array_t items; -                    LLAssetIDMatches asset_id_matches(imageid); -                    gInventory.collectDescendentsIf(LLUUID::null, -                        cats, -                        items, -                        LLInventoryModel::INCLUDE_TRASH, -                        asset_id_matches); - -                    // Extremely unreliable and perfomance unfriendly. -                    // But we need this to check permissions and it is how texture control finds items -                    for (S32 i = 0; i < items.size(); i++) -                    { -                        LLViewerInventoryItem* itemp = items[i]; -                        if (itemp && itemp->isFinished()) -                        { -                            // dropTextureAllFaces will fail if incomplete -                            LLPermissions item_permissions = itemp->getPermissions(); -                            if (item_permissions.allowOperationBy(PERM_COPY, -                                gAgent.getID(), -                                gAgent.getGroupID())) -                            { -                                itemp_res = itemp; -                                break; // first match -                            } -                        } -                    } -                } +                get_item_and_permissions(imageid, img_itemp_res, img_full_perm, img_from_library, te_data["te"], "img"); -                if (itemp_res) +                if (img_itemp_res)                  {                      if (te == -1) // all faces                      {                          LLToolDragAndDrop::dropTextureAllFaces(objectp, -                            itemp_res, -                            from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT, +                            img_itemp_res, +                            img_from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT,                              LLUUID::null,                              false);                      } @@ -4423,15 +4528,15 @@ void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te)                      {                          LLToolDragAndDrop::dropTextureOneFace(objectp,                              te, -                            itemp_res, -                            from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT, +                            img_itemp_res, +                            img_from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT,                              LLUUID::null,                              false,                              0);                      }                  }                  // not an inventory item or no complete items -                else if (full_perm) +                else if (img_full_perm)                  {                      // Either library, local or existed as fullperm when user made a copy                      LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(imageid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); @@ -4459,17 +4564,65 @@ void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te)              // PBR/GLTF              if (te_data["te"].has("pbr"))              { -                objectp->setRenderMaterialID(te, te_data["te"]["pbr"].asUUID(), false /*managing our own update*/); -                tep->setGLTFRenderMaterial(nullptr); -                tep->setGLTFMaterialOverride(nullptr); +                const LLUUID pbr_id = te_data["te"]["pbr"].asUUID(); +                bool pbr_full_perm = false; +                bool pbr_from_library = false; +                LLViewerInventoryItem* pbr_itemp_res = NULL; + +                get_item_and_permissions(pbr_id, pbr_itemp_res, pbr_full_perm, pbr_from_library, te_data["te"], "pbr"); +                bool allow = true; + +                // check overrides first since they don't need t be moved to inventory                  if (te_data["te"].has("pbr_override"))                  { -                    LLGLTFMaterialList::queueApply(objectp, te, te_data["te"]["pbr"].asUUID(), te_data["te"]["pbr_override"]); +                    for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i) +                    { +                        const std::string prefix = "pbr" + std::to_string(i); +                        if (te_data["te"].has(prefix + "imageid")) +                        { +                            LLUUID tex_id = te_data["te"][prefix + "imageid"]; + +                            bool full_perm = false; +                            bool from_library = false; +                            LLViewerInventoryItem* itemp_res = NULL; +                            get_item_and_permissions(tex_id, itemp_res, full_perm, from_library, te_data["te"], prefix); +                            allow = full_perm; +                            if (!allow) break; +                        } +                    }                  } -                else + +                if (allow && pbr_itemp_res) +                { +                    if (pbr_itemp_res) +                    { +                        allow = LLToolDragAndDrop::handleDropMaterialProtections( +                            objectp, +                            pbr_itemp_res, +                            pbr_from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT, +                            pbr_id); +                    } +                    else +                    { +                        allow = pbr_full_perm; +                    } +                } + +                if (allow)                  { -                    LLGLTFMaterialList::queueApply(objectp, te, te_data["te"]["pbr"].asUUID()); +                    objectp->setRenderMaterialID(te, te_data["te"]["pbr"].asUUID(), false /*managing our own update*/); +                    tep->setGLTFRenderMaterial(nullptr); +                    tep->setGLTFMaterialOverride(nullptr); + +                    if (te_data["te"].has("pbr_override")) +                    { +                        LLGLTFMaterialList::queueApply(objectp, te, te_data["te"]["pbr"].asUUID(), te_data["te"]["pbr_override"]); +                    } +                    else +                    { +                        LLGLTFMaterialList::queueApply(objectp, te, te_data["te"]["pbr"].asUUID()); +                    }                  }              }              else diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h index 1ee9bf2cf7..ce3dd8bdea 100644 --- a/indra/newview/llpanelface.h +++ b/indra/newview/llpanelface.h @@ -264,6 +264,9 @@ public: // needs to be accessible to selection manager      void onCopyTexture();      void onPasteTexture();      void onPasteTexture(LLViewerObject* objectp, S32 te); +private: +    // for copy/paste operations +    bool validateInventoryItem(const LLSD& te, const std::string& prefix);  protected:      void menuDoToSelected(const LLSD& userdata); diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 132098ba99..1fa1c9587f 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 9a26ebc5f9..72295ffba9 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -3601,7 +3601,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/llsurfacepatch.cpp b/indra/newview/llsurfacepatch.cpp index 6f99da5957..875af76c10 100644 --- a/indra/newview/llsurfacepatch.cpp +++ b/indra/newview/llsurfacepatch.cpp @@ -210,7 +210,6 @@ void LLSurfacePatch::eval(const U32 x, const U32 y, const U32 stride, LLVector3      llassert_always(vertex && normal && tex1);      U32 surface_stride = mSurfacep->getGridsPerEdge(); -    U32 texture_stride = mSurfacep->getGridsPerEdge() - 1;      U32 point_offset = x + y*surface_stride;      *normal = getNormal(x, y); @@ -223,7 +222,7 @@ void LLSurfacePatch::eval(const U32 x, const U32 y, const U32 stride, LLVector3      // tex0 is used for ownership overlay      LLVector3 rel_pos = pos_agent - mSurfacep->getOriginAgent(); -    LLVector3 tex_pos = rel_pos * (1.f / (texture_stride * mSurfacep->getMetersPerGrid())); +    LLVector3 tex_pos = rel_pos * (1.f / (surface_stride * mSurfacep->getMetersPerGrid()));      tex0->mV[0] = tex_pos.mV[0];      tex0->mV[1] = tex_pos.mV[1]; diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index 35057a910a..20127f5f27 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -88,7 +88,8 @@ bool get_is_predefined_texture(LLUUID asset_id)          || asset_id == DEFAULT_OBJECT_NORMAL          || asset_id == BLANK_OBJECT_NORMAL          || asset_id == IMG_WHITE -        || asset_id == LLUUID(SCULPT_DEFAULT_TEXTURE)) +        || asset_id == LLUUID(SCULPT_DEFAULT_TEXTURE) +        || asset_id == BLANK_MATERIAL_ASSET_ID)      {          return true;      } diff --git a/indra/newview/llviewerchat.cpp b/indra/newview/llviewerchat.cpp index 8b01c4ef88..2ca2c5c07d 100644 --- a/indra/newview/llviewerchat.cpp +++ b/indra/newview/llviewerchat.cpp @@ -36,6 +36,7 @@  #include "llviewerregion.h"  #include "llworld.h"  #include "llinstantmessage.h" //SYSTEM_FROM +#include "llurlregistry.h"  // LLViewerChat  LLViewerChat::font_change_signal_t LLViewerChat::sChatFontChangedSignal; @@ -222,6 +223,13 @@ void LLViewerChat::formatChatMsg(const LLChat& chat, std::string& formated_msg)  {      std::string tmpmsg = chat.mText; +    // show @name instead of slurl for chat mentions +    LLUrlMatch match; +    while (LLUrlRegistry::instance().findUrl(tmpmsg, match, LLUrlRegistryNullCallback, false, true)) +    { +        tmpmsg.replace(match.getStart(), match.getEnd() - match.getStart() + 1, match.getLabel()); +    } +      if(chat.mChatStyle == CHAT_STYLE_IRC)      {          formated_msg = chat.mFromName + tmpmsg.substr(3); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 7d138dfb3b..39c5b9f218 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" @@ -356,6 +357,7 @@ void LLViewerFloaterReg::registerFloaters()      LLFloaterReg::add("chat_voice", "floater_voice_chat_volume.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChatVoiceVolume>);      LLFloaterReg::add("change_item_thumbnail", "floater_change_item_thumbnail.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChangeItemThumbnail>);      LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterIMNearbyChat::buildFloater); +    LLFloaterReg::add("chat_mention_picker", "floater_chat_mention_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterChatMentionPicker>);      LLFloaterReg::add("classified", "floater_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterClassified>);      LLFloaterReg::add("compile_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterCompileQueue>);      LLFloaterReg::add("conversation", "floater_conversation_log.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterConversationLog>); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 3a52908b8a..a6d50af025 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -6418,6 +6418,16 @@ LLJoint *LLVOAvatar::getJoint( S32 joint_num )      return pJoint;  } +void LLVOAvatar::initAllJoints() +{ +    getJointAliases(); +    for (auto& alias : mJointAliasMap) +    { +        mJointMap[alias.first] = mRoot->findJoint(alias.second); +    } +    // ignore mScreen and mRoot +} +  //-----------------------------------------------------------------------------  // getRiggedMeshID  // diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index a2232d21a2..ab27c5752d 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -204,6 +204,7 @@ public:      virtual LLJoint*        getJoint(const std::string &name);      LLJoint*                getJoint(S32 num); +    void                    initAllJoints();      //if you KNOW joint_num is a valid animated joint index, use getSkeletonJoint for efficiency      inline LLJoint* getSkeletonJoint(S32 joint_num) { return mSkeleton[joint_num]; } diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index f23af5afa4..90ff4067f2 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -225,6 +225,8 @@ void LLVOAvatarSelf::initInstance()      doPeriodically(update_avatar_rez_metrics, 5.0);      doPeriodically(boost::bind(&LLVOAvatarSelf::checkStuckAppearance, this), 30.0); +    initAllJoints(); // mesh thread uses LLVOAvatarSelf as a joint source +      mInitFlags |= 1<<2;  } diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 3fb7a3c156..6b3dccf89c 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -860,8 +860,11 @@ void LLVOVolume::updateTextureVirtualSize(bool forced)                  // animated faces get moved to a smaller partition to reduce                  // side-effects of their updates (see shrinkWrap in                  // LLVOVolume::animateTextures). -                mDrawable->getSpatialGroup()->dirtyGeom(); -                gPipeline.markRebuild(mDrawable->getSpatialGroup()); +                if (mDrawable->getSpatialGroup()) +                { +                    mDrawable->getSpatialGroup()->dirtyGeom(); +                    gPipeline.markRebuild(mDrawable->getSpatialGroup()); +                }              }          } diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml index bc9aef8974..c549a7dddb 100644 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -1004,4 +1004,10 @@    <color      name="OutfitSnapshotMacMask2"      value="0.1 0.1 0.1 1"/> +  <color +    name="ChatMentionHighlight" +    value="0.82 0.91 0.98 0.15" /> +  <color +    name="ChatSelfMentionHighlight" +    value="0.85 0.75 0.40 0.5" />  </colors> diff --git a/indra/newview/skins/default/xui/en/floater_chat_mention_picker.xml b/indra/newview/skins/default/xui/en/floater_chat_mention_picker.xml new file mode 100644 index 0000000000..bbad99f932 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_chat_mention_picker.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater +    name="chat_mention_picker" +    title="CHOOSE RESIDENT" +    single_instance="true" +    can_minimize="false" +    can_tear_off="false" +    can_resize="true" +    auto_close="true" +    layout="topleft" +    min_width="250" +    chrome="true" +    height="125" +    width="310"> +   <avatar_list +    allow_select="true" +    follows="all" +    height="120" +    width="306" +    ignore_online_status="true" +    layout="topleft" +    left="3" +    keep_one_selected="true" +    multi_select="false" +    show_info_btn="false" +    show_profile_btn="false" +    show_speaking_indicator="false" +    name="avatar_list" +    right="-1" +    top="2" /> +</floater> diff --git a/indra/newview/skins/default/xui/en/panel_tools_texture.xml b/indra/newview/skins/default/xui/en/panel_tools_texture.xml index af6a9b94d9..b7db9dec96 100644 --- a/indra/newview/skins/default/xui/en/panel_tools_texture.xml +++ b/indra/newview/skins/default/xui/en/panel_tools_texture.xml @@ -968,8 +968,8 @@               label_width="205"               layout="topleft"               left="10" -             min_val="-100" -             max_val="100" +             min_val="-10000" +             max_val="10000"               name="gltfTextureScaleU"               top_delta="34"               width="265" /> @@ -981,8 +981,8 @@               label_width="205"               layout="topleft"               left="10" -             min_val="-100" -             max_val="100" +             min_val="-10000" +             max_val="10000"               name="gltfTextureScaleV"               width="265" />              <spinner | 
