Skip to content
2 changes: 2 additions & 0 deletions indra/llui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ set(llui_SOURCE_FILES
llbadgeowner.cpp
llbutton.cpp
llchatentry.cpp
llchatmentionhelper.cpp
llcheckboxctrl.cpp
llclipboard.cpp
llcombobox.cpp
Expand Down Expand Up @@ -130,6 +131,7 @@ set(llui_HEADER_FILES
llcallbackmap.h
llchatentry.h
llchat.h
llchatmentionhelper.h
llcheckboxctrl.h
llclipboard.h
llcombobox.h
Expand Down
1 change: 1 addition & 0 deletions indra/llui/llchatentry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ LLChatEntry::LLChatEntry(const Params& p)
mCurrentHistoryLine = mLineHistory.begin();

mAutoIndent = false;
mShowChatMentionPicker = true;
keepSelectionOnReturn(true);
}

Expand Down
151 changes: 151 additions & 0 deletions indra/llui/llchatmentionhelper.cpp
Original file line number Diff line number Diff line change
@@ -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()); }));
}
}
}
66 changes: 66 additions & 0 deletions indra/llui/llchatmentionhelper.h
Original file line number Diff line number Diff line change
@@ -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;
};
6 changes: 5 additions & 1 deletion indra/llui/llflatlistview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);

Expand Down
4 changes: 4 additions & 0 deletions indra/llui/llflatlistview.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler

virtual S32 notify(const LLSD& info) override;

void setFocusOnItemClicked(bool b) { mFocusOnItemClicked = b; }

virtual ~LLFlatListView();

protected:
Expand Down Expand Up @@ -423,6 +425,8 @@ class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler

bool mKeepSelectionVisibleOnReshape;

bool mFocusOnItemClicked;

/** All pairs of the list */
pairs_list_t mItemPairs;

Expand Down
8 changes: 6 additions & 2 deletions indra/llui/llstyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{}


Expand All @@ -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)
Expand Down
21 changes: 19 additions & 2 deletions indra/llui/llstyle.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,25 @@ class LLStyle : public LLRefCount
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; }
Expand Down Expand Up @@ -84,18 +94,23 @@ class LLStyle : public LLRefCount

bool isImage() const { return mImagep.notNull(); }

bool getDrawHighlightBg() const { return mDrawHighlightBg; }
const LLUIColor& getHighlightBgColor() const { return mHighlightBgColor; }

bool operator==(const LLStyle &rhs) const
{
return
mVisible == rhs.mVisible
&& 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); }
Expand All @@ -112,11 +127,13 @@ class LLStyle : public LLRefCount
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;
Expand Down
Loading