/**
* @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
{
// Enable auto-completion of @ mentions
static LLUICachedControl useMentionAutoComplete("FSUseChatMentionAutoComplete", true);
if (!useMentionAutoComplete)
{
return false;
}
//
if (cursor_pos <= 0 || cursor_pos > static_cast(wtext.size()))
return false;
// Find the beginning of the current word
S32 start = cursor_pos - 1;
while (start > 0 && wtext[start - 1] != U32(' ') && wtext[start - 1] != U32('\n'))
{
--start;
}
if (wtext[start] != U32('@'))
return false;
if (mention_start_pos)
*mention_start_pos = start;
S32 word_length = cursor_pos - start;
if (word_length == 1)
{
return true;
}
// Get the name after '@'
std::string name = wstring_to_utf8str(wtext.substr(start + 1, word_length - 1));
LLStringUtil::toLower(name);
for (const auto& av_name : mAvatarNames)
{
if (av_name == name || av_name.find(name) == 0)
{
return true;
}
}
return false;
}
void LLChatMentionHelper::showHelper(LLUICtrl* host_ctrl, S32 local_x, S32 local_y, const std::string& av_name, std::function cb)
{
if (mHelperHandle.isDead())
{
LLFloater* av_picker_floater = LLFloaterReg::getInstance(CHAT_MENTION_HELPER_FLOATER);
mHelperHandle = av_picker_floater->getHandle();
mHelperCommitConn = av_picker_floater->setCommitCallback([&](LLUICtrl* ctrl, const LLSD& param) { onCommitName(param.asString()); });
}
setHostCtrl(host_ctrl);
mNameCommitCb = cb;
S32 floater_x, floater_y;
if (!host_ctrl->localPointToOtherView(local_x, local_y, &floater_x, &floater_y, gFloaterView))
{
LL_WARNS() << "Cannot show helper for non-floater controls." << LL_ENDL;
return;
}
LLFloater* av_picker_floater = mHelperHandle.get();
LLRect rect = av_picker_floater->getRect();
rect.setLeftTopAndSize(floater_x, floater_y + rect.getHeight(), rect.getWidth(), rect.getHeight());
av_picker_floater->setRect(rect);
if (av_picker_floater->isShown())
{
av_picker_floater->onOpen(LLSD().with("av_name", av_name));
}
else
{
av_picker_floater->openFloater(LLSD().with("av_name", av_name));
}
}
void LLChatMentionHelper::hideHelper(const LLUICtrl* ctrl)
{
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()); }));
}
}
}