Files

369 lines
11 KiB
C++

/**
* @file fsnearbychatcontrol.cpp
* @brief Nearby chat input control implementation
*
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
* Copyright (C) 2012, Zi Ree @ Second Life
*
* 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 "fsnearbychatcontrol.h"
#include "fsnearbychathub.h"
#include "lfsimfeaturehandler.h"
#include "llagent.h" // gAgent
#include "llagentcamera.h" // gAgentCamera
#include "llautoreplace.h"
#include "llchatmentionhelper.h"
#include "llemojihelper.h"
#include "llfloaterchatmentionpicker.h"
#include "llfocusmgr.h"
#include "llscrollcontainer.h"
#include "llworld.h"
#include "rlvactions.h"
static LLDefaultChildRegistry::Register<FSNearbyChatControl> r("fs_nearby_chat_control");
FSNearbyChatControl::FSNearbyChatControl(const FSNearbyChatControl::Params& p) :
LLChatEntry(p),
mDefault(p.is_default),
mTextPadLeft(p.text_pad_left),
mTextPadRight(p.text_pad_right),
mBackgroundPad(p.background_pad),
mBgImage(p.background_image),
mBgImageDisabled(p.background_image_disabled),
mBgImageFocused(p.background_image_focused)
{
//<FS:TS> FIRE-11373: Autoreplace doesn't work in nearby chat bar
setAutoreplaceCallback(boost::bind(&LLAutoReplace::autoreplaceCallback, LLAutoReplace::getInstance(), _1, _2, _3, _4, _5));
setKeystrokeCallback([this](LLTextEditor* caller) { onKeystroke(caller); });
FSNearbyChat::instance().registerChatBar(this);
setCommitOnFocusLost(false);
setPassDelete(true);
mBGVisible = false;
setFont(LLViewerChat::getChatFont());
enableSingleLineMode(true);
setShowChatMentionPicker(!RlvActions::isRlvEnabled() || RlvActions::canShowName(RlvActions::SNC_DEFAULT));
mRlvBehaviorCallbackConnection = gRlvHandler.setBehaviourToggleCallback(
boost::bind(&FSNearbyChatControl::updateRlvRestrictions, this, _1));
setShowEmojiHelper(gSavedSettings.getBOOL("FSEnableEmojiWindowPopupWhileTyping"));
mEmojiHelperSettingConnection =
gSavedSettings.getControl("FSEnableEmojiWindowPopupWhileTyping")->getSignal()->connect(
boost::bind(&FSNearbyChatControl::updateEmojiHelperSetting, this, _2));
// Register for font change notifications
LLViewerChat::setFontChangedCallback(boost::bind(&FSNearbyChatControl::setFont, this, _1));
}
FSNearbyChatControl::~FSNearbyChatControl()
{
if (mRlvBehaviorCallbackConnection.connected())
{
mRlvBehaviorCallbackConnection.disconnect();
}
if (mEmojiHelperSettingConnection.connected())
{
mEmojiHelperSettingConnection.disconnect();
}
LLFloaterChatMentionPicker::removeParticipantSource(this);
}
void FSNearbyChatControl::onKeystroke(LLTextEditor* caller)
{
FSNearbyChat::handleChatBarKeystroke(caller);
}
void FSNearbyChatControl::paste()
{
LLChatEntry::paste();
// Keep nearby chat on one visual line by flattening paragraph markers from paste.
S32 cursor_pos = getCursorPos();
LLWString content = getWText();
LLWStringUtil::replaceChar(content, llwchar(182), llwchar('\n'));
setWText(content);
setCursorPos(cursor_pos);
}
// send our focus status to the LLNearbyChat hub
void FSNearbyChatControl::onFocusReceived()
{
FSNearbyChat::instance().setFocusedInputEditor(this, true);
LLFloaterChatMentionPicker::updateParticipantSource(this);
LLChatEntry::onFocusReceived();
}
void FSNearbyChatControl::onFocusLost()
{
FSNearbyChat::instance().setFocusedInputEditor(this, false);
LLFloaterChatMentionPicker::removeParticipantSource(this);
LLChatEntry::onFocusLost();
}
void FSNearbyChatControl::setFocus(bool focus)
{
FSNearbyChat::instance().setFocusedInputEditor(this, focus);
if (focus)
{
LLFloaterChatMentionPicker::updateParticipantSource(this);
}
else
{
LLFloaterChatMentionPicker::removeParticipantSource(this);
}
LLChatEntry::setFocus(focus);
}
void FSNearbyChatControl::draw()
{
applyTextPadding();
drawBackground();
LLChatEntry::draw();
}
void FSNearbyChatControl::setTextPadding(S32 left, S32 right)
{
mTextPadLeft = left;
mTextPadRight = right;
applyTextPadding();
}
void FSNearbyChatControl::drawBackground()
{
LLUIImage* image = nullptr;
if (getReadOnly())
{
image = mBgImageDisabled;
}
else if (hasFocus())
{
image = mBgImageFocused;
}
else
{
image = mBgImage;
}
if (!image)
{
return;
}
LLRect background_rect = getLocalRect();
background_rect.stretch(-mBackgroundPad);
if (background_rect.getWidth() <= 0 || background_rect.getHeight() <= 0)
{
return;
}
const F32 alpha = getCurrentTransparency();
if (hasFocus())
{
LLColor4 focus_color = gFocusMgr.getFocusColor();
focus_color.setAlpha(alpha);
image->drawBorder(
background_rect.mLeft,
background_rect.mBottom,
background_rect.getWidth(),
background_rect.getHeight(),
focus_color,
gFocusMgr.getFocusFlashWidth());
}
LLColor4 image_color = LLColor4::white;
image_color.setAlpha(alpha);
image->draw(background_rect, image_color);
// LLLineEditor-style subtle inner shade ring.
if (background_rect.getWidth() > 2 && background_rect.getHeight() > 2)
{
LLRect inner_border_rect = background_rect;
inner_border_rect.stretch(-1);
LLColor4 inner_shade = LLColor4::black;
const F32 inner_alpha = getReadOnly() ? 0.12f : (hasFocus() ? 0.10f : 0.16f);
inner_shade.setAlpha(alpha * inner_alpha);
image->drawBorder(
inner_border_rect.mLeft,
inner_border_rect.mBottom,
inner_border_rect.getWidth(),
inner_border_rect.getHeight(),
inner_shade,
1);
}
}
void FSNearbyChatControl::applyTextPadding()
{
LLRect base_rect = mScroller ? mScroller->getContentWindowRect() : getLocalRect();
const LLRect local_rect = getLocalRect();
if (base_rect.getHeight() < local_rect.getHeight())
{
base_rect.mTop = local_rect.mTop;
base_rect.mBottom = local_rect.mBottom;
}
base_rect.mLeft = llmin(base_rect.mRight, base_rect.mLeft + mTextPadLeft);
base_rect.mRight = llmax(base_rect.mLeft, base_rect.mRight - mTextPadRight);
if (base_rect != mVisibleTextRect)
{
mVisibleTextRect = base_rect;
needsReflow();
}
}
void FSNearbyChatControl::autohide(bool after_send)
{
if (isDefault())
{
const bool in_mouselook = gAgentCamera.cameraMouselook();
const bool show_interface_in_mouselook = gSavedSettings.getBOOL("FSShowInterfaceInMouselook");
const bool closeChatOnReturn = gSavedSettings.getBOOL("CloseChatOnReturn")
&& !(!in_mouselook && gSavedSettings.getBOOL("FSCloseChatOnReturnInMouselook"));
const bool autohideChatBar = gSavedSettings.getBOOL("AutohideChatBar");
if (closeChatOnReturn)
{
setFocus(false);
}
if ((!after_send || closeChatOnReturn) &&
((in_mouselook && !show_interface_in_mouselook) || autohideChatBar))
{
FSNearbyChat::instance().showDefaultChatBar(false);
}
}
}
// handle ESC key here
bool FSNearbyChatControl::handleKeyHere(KEY key, MASK mask)
{
bool handled = false;
EChatType type = CHAT_TYPE_NORMAL;
if (LLChatMentionHelper::instance().isActive(this) ||
LLEmojiHelper::instance().isActive(this))
{
return LLChatEntry::handleKeyHere(key, mask);
}
// autohide the chat bar if escape key was pressed and we're the default chat bar
if (key == KEY_ESCAPE && mask == MASK_NONE)
{
// we let ESC key go through to the rest of the UI code, so don't set handled = true
autohide(false);
gAgent.stopTyping();
}
else if (KEY_RETURN == key)
{
if (mask == MASK_CONTROL && gSavedSettings.getBOOL("FSUseCtrlShout"))
{
// shout
type = CHAT_TYPE_SHOUT;
handled = true;
}
else if (mask == MASK_SHIFT && gSavedSettings.getBOOL("FSUseShiftWhisper"))
{
// whisper
type = CHAT_TYPE_WHISPER;
handled = true;
}
else if (mask == MASK_ALT && gSavedSettings.getBOOL("FSUseAltOOC"))
{
// OOC
type = CHAT_TYPE_OOC;
handled = true;
}
else if (mask == (MASK_SHIFT | MASK_CONTROL))
{
// linefeed
if ((wstring_utf8_length(getWText()) + wchar_utf8_length(llwchar(182))) > getMaxTextLength())
{
LLUI::getInstance()->reportBadKeystroke();
}
else
{
LLWString line_break(1, llwchar(182));
insertText(line_break);
}
return true;
}
else
{
// say
type = CHAT_TYPE_NORMAL;
handled = true;
}
}
if (handled)
{
// save current line in the history buffer
updateHistory();
// send chat to nearby chat hub
FSNearbyChat::instance().sendChat(getConvertedText(), type);
setText(LLStringExplicit(""));
autohide(true);
return true;
}
// let the line editor handle everything we don't handle
return LLChatEntry::handleKeyHere(key, mask);
}
uuid_vec_t FSNearbyChatControl::getSessionParticipants() const
{
if (!isAgentAvatarValid() || !LLWorld::instanceExists() || !LFSimFeatureHandler::instanceExists())
{
return {};
}
uuid_vec_t avatar_ids;
LLWorld::instance().getAvatars(&avatar_ids, nullptr, gAgent.getPositionGlobal(), (F32)LFSimFeatureHandler::instance().sayRange());
return avatar_ids;
}
void FSNearbyChatControl::updateRlvRestrictions(ERlvBehaviour behavior)
{
if (behavior != RLV_BHVR_SHOWNAMES)
{
return;
}
setShowChatMentionPicker(!RlvActions::isRlvEnabled() || RlvActions::canShowName(RlvActions::SNC_DEFAULT));
}
void FSNearbyChatControl::updateEmojiHelperSetting(const LLSD& data)
{
setShowEmojiHelper(data.asBoolean());
}