Make the nearby chat floater use the expandable chat bars

Cinders 2013-07-26 14:12:16 -06:00
parent 8e98883f76
commit 5ae32941dd
3 changed files with 1021 additions and 236 deletions

View File

@ -33,14 +33,16 @@
#include "chatbar_as_cmdline.h"
#include "fschathistory.h"
#include "fscommon.h"
#include "fsfloaterim.h"
#include "fsfloaterimcontainer.h"
#include "fsnearbychathub.h"
#include "llagent.h" // gAgent
#include "llanimationstates.h" // ANIM_AGENT_WHISPER, ANIM_AGENT_TALK, ANIM_AGENT_SHOUT
#include "llautoreplace.h"
#include "llavatarnamecache.h"
#include "llbutton.h"
#include "llchannelmanager.h"
#include "llchatentry.h"
#include "llcommandhandler.h"
#include "llconsole.h"
#include "lldraghandle.h"
@ -51,7 +53,6 @@
#include "lliconctrl.h"
#include "llkeyboard.h"
#include "lllayoutstack.h"
#include "lllineeditor.h"
#include "lllogchat.h"
#include "llmenugl.h"
#include "llmultigesture.h"
@ -66,9 +67,26 @@
#include "llviewermenu.h"//for gMenuHolder
#include "llviewerstats.h"
#include "llviewerwindow.h"
#include "llworld.h"
#include "rlvhandler.h"
static const S32 RESIZE_BAR_THICKNESS = 3;
#define NAME_PREDICTION_MINIMUM_LENGTH 3
S32 FSFloaterNearbyChat::sLastSpecialChatChannel = 0;
// [RLVa:KB] - Checked: 2010-02-27 (RLVa-0.2.2)
void send_chat_from_nearby_floater(std::string utf8_out_text, EChatType type, S32 channel);
// [/RLVa:KB]
void really_send_chat_from_nearby_floater(std::string utf8_out_text, EChatType type, S32 channel);
struct LLChatTypeTrigger {
std::string name;
EChatType type;
};
static LLChatTypeTrigger sChatTypeTriggers[] = {
{ "/whisper" , CHAT_TYPE_WHISPER},
{ "/shout" , CHAT_TYPE_SHOUT}
};
FSFloaterNearbyChat::FSFloaterNearbyChat(const LLSD& key)
: LLDockableFloater(NULL, false, false, key)
@ -76,6 +94,9 @@ FSFloaterNearbyChat::FSFloaterNearbyChat(const LLSD& key)
,mInputEditor(NULL)
// <FS:Ansariel> Optional muted chat history
,mChatHistoryMuted(NULL)
,mChatLayoutPanel(NULL)
,mInputPanels(NULL)
,mChatLayoutPanelHeight(0)
{
}
@ -105,13 +126,14 @@ void FSFloaterNearbyChat::updateFSUseNearbyChatConsole(const LLSD &data)
BOOL FSFloaterNearbyChat::postBuild()
{
setIsSingleInstance(TRUE);
//menu
LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar;
enable_registrar.add("NearbyChat.Check", boost::bind(&FSFloaterNearbyChat::onNearbyChatCheckContextMenuItem, this, _2));
registrar.add("NearbyChat.Action", boost::bind(&FSFloaterNearbyChat::onNearbyChatContextMenuItemClicked, this, _2));
LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_nearby_chat.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance());
if(menu)
@ -119,9 +141,22 @@ BOOL FSFloaterNearbyChat::postBuild()
gSavedSettings.declareS32("nearbychat_showicons_and_names",2,"NearByChat header settings",true);
mInputEditor = getChild<LLLineEditor>("chat_box");
mInputEditor->setEnabled(TRUE);
mInputEditor = getChild<LLChatEntry>("chat_box");
if (mInputEditor)
{
mInputEditor->setAutoreplaceCallback(boost::bind(&LLAutoReplace::autoreplaceCallback, LLAutoReplace::getInstance(), _1, _2, _3, _4, _5));
mInputEditor->setCommitCallback(boost::bind(&FSFloaterNearbyChat::onChatBoxCommit, this));
mInputEditor->setKeystrokeCallback(boost::bind(&FSFloaterNearbyChat::onChatBoxKeystroke, this));
mInputEditor->setFocusLostCallback(boost::bind(&FSFloaterNearbyChat::onChatBoxFocusLost, this));
mInputEditor->setFocusReceivedCallback(boost::bind(&FSFloaterNearbyChat::onChatBoxFocusReceived, this));
mInputEditor->setTextExpandedCallback(boost::bind(&FSFloaterNearbyChat::reshapeChatLayoutPanel, this));
mInputEditor->setPassDelete(TRUE);
mInputEditor->setFont(LLViewerChat::getChatFont());
}
mChatLayoutPanel = getChild<LLLayoutPanel>("chat_layout_panel");
mInputPanels = getChild<LLLayoutStack>("input_panels");
mChatLayoutPanelHeight = mChatLayoutPanel->getRect().getHeight();
mInputEditorPad = mChatLayoutPanelHeight - mInputEditor->getRect().getHeight();
gSavedSettings.getControl("FSNearbyChatbar")->getCommitSignal()->connect(boost::bind(&FSFloaterNearbyChat::onChatBarVisibilityChanged, this));
gSavedSettings.getControl("FSShowChatChannel")->getCommitSignal()->connect(boost::bind(&FSFloaterNearbyChat::onChatChannelVisibilityChanged, this));
@ -173,7 +208,6 @@ std::string appendTime()
return timeStr;
}
void FSFloaterNearbyChat::addMessage(const LLChat& chat,bool archive,const LLSD &args)
{
LLChat& tmp_chat = const_cast<LLChat&>(chat);
@ -279,7 +313,6 @@ void FSFloaterNearbyChat::addMessage(const LLChat& chat,bool archive,const LLSD
// virtual
BOOL FSFloaterNearbyChat::focusFirstItem(BOOL prefer_text_fields, BOOL focus_flash)
{
mInputEditor->setFocus(TRUE);
onTabInto();
if (focus_flash)
{
@ -392,12 +425,6 @@ void FSFloaterNearbyChat::setVisible(BOOL visible)
// </Ansariel> Support for chat console
}
void FSFloaterNearbyChat::onFocusReceived()
{
// <FS:Ansariel> Give focus to input textbox
mInputEditor->setFocus(TRUE);
}
void FSFloaterNearbyChat::onOpen(const LLSD& key )
{
// <FS:Ansariel> [FS communication UI]
@ -498,6 +525,25 @@ bool FSFloaterNearbyChat::isWordsName(const std::string& name)
}
}
void FSFloaterNearbyChat::reloadMessages(bool clean_messages/* = false*/)
{
if (clean_messages)
{
mMessageArchive.clear();
loadHistory();
}
mChatHistory->clear();
LLSD do_not_log;
do_not_log["do_not_log"] = true;
for(std::vector<LLChat>::iterator it = mMessageArchive.begin();it!=mMessageArchive.end();++it)
{
// Update the messages without re-writing them to a log file.
addMessage(*it,false, do_not_log);
}
}
void FSFloaterNearbyChat::loadHistory()
{
LLSD do_not_log;
@ -639,3 +685,701 @@ void FSFloaterNearbyChat::enableTranslationButton(bool enabled)
{
getChildView("translate_btn")->setEnabled(enabled);
}
// virtual
BOOL FSFloaterNearbyChat::handleKeyHere( KEY key, MASK mask )
{
BOOL handled = FALSE;
if( KEY_RETURN == key && mask == MASK_CONTROL)
{
// shout
sendChat(CHAT_TYPE_SHOUT);
handled = TRUE;
}
else if (KEY_RETURN == key && mask == MASK_SHIFT)
{
// whisper
sendChat(CHAT_TYPE_WHISPER);
handled = TRUE;
}
return handled;
}
BOOL FSFloaterNearbyChat::matchChatTypeTrigger(const std::string& in_str, std::string* out_str)
{
U32 in_len = in_str.length();
S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers);
bool string_was_found = false;
for (S32 n = 0; n < cnt && !string_was_found; n++)
{
if (in_len <= sChatTypeTriggers[n].name.length())
{
std::string trigger_trunc = sChatTypeTriggers[n].name;
LLStringUtil::truncate(trigger_trunc, in_len);
if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc))
{
*out_str = sChatTypeTriggers[n].name;
string_was_found = true;
}
}
}
return string_was_found;
}
void FSFloaterNearbyChat::onChatBoxKeystroke()
{
LLWString raw_text = mInputEditor->getWText();
// Can't trim the end, because that will cause autocompletion
// to eat trailing spaces that might be part of a gesture.
LLWStringUtil::trimHead(raw_text);
S32 length = raw_text.length();
S32 channel=0;
if (gSavedSettings.getBOOL("FSNearbyChatbar") &&
gSavedSettings.getBOOL("FSShowChatChannel"))
{
// <FS:Ansariel> [FS communication UI]
//channel = (S32)(LLFloaterNearbyChat::getInstance()->getChild<LLSpinCtrl>("ChatChannel")->get());
channel = (S32)(FSFloaterNearbyChat::getInstance()->getChild<LLSpinCtrl>("ChatChannel")->get());
// </FS:Ansariel> [FS communication UI]
}
// -Zi
// if( (length > 0) && (raw_text[0] != '/') ) // forward slash is used for escape (eg. emote) sequences
// [RLVa:KB] - Checked: 2010-03-26 (RLVa-1.2.0b) | Modified: RLVa-1.0.0d
if ( (length > 0) && (raw_text[0] != '/') && (!(gSavedSettings.getBOOL("AllowMUpose") && raw_text[0] == ':')) && (!gRlvHandler.hasBehaviour(RLV_BHVR_REDIRCHAT)) )
// [/RLVa:KB]
{
// only start typing animation if we are chatting without / on channel 0 -Zi
if(channel==0)
gAgent.startTyping();
}
else
{
gAgent.stopTyping();
}
KEY key = gKeyboard->currentKey();
MASK mask = gKeyboard->currentMask(FALSE);
// Ignore "special" keys, like backspace, arrows, etc.
if (length > 1
&& raw_text[0] == '/'
&& key < KEY_SPECIAL
&& gSavedSettings.getBOOL("FSChatbarGestureAutoCompleteEnable"))
{
// we're starting a gesture, attempt to autocomplete
std::string utf8_trigger = wstring_to_utf8str(raw_text);
std::string utf8_out_str(utf8_trigger);
if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str))
{
std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size());
if (!rest_of_match.empty())
{
mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part
// Select to end of line, starting from the character
// after the last one the user typed.
mInputEditor->selectNext(rest_of_match, false);
}
}
else if (matchChatTypeTrigger(utf8_trigger, &utf8_out_str))
{
std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size());
mInputEditor->setText(utf8_trigger + rest_of_match + " "); // keep original capitalization for user-entered part
mInputEditor->endOfDoc();
}
}
// <FS:CR> FIRE-3192 - Predictive name completion, based on code by Satomi Ahn
static LLCachedControl<bool> sNameAutocomplete(gSavedSettings, "FSChatbarNamePrediction");
if (length > NAME_PREDICTION_MINIMUM_LENGTH && sNameAutocomplete && key < KEY_SPECIAL && mask != MASK_CONTROL)
{
S32 cur_pos = mInputEditor->getCursorPos();
if (cur_pos && (raw_text[cur_pos - 1] != ' '))
{
// Get a list of avatars within range
std::vector<LLUUID> avatar_ids;
std::vector<LLVector3d> positions;
LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange"));
if (avatar_ids.empty()) return; // Nobody's in range!
// Parse text for a pattern to search
std::string prefix = wstring_to_utf8str(raw_text.substr(0, cur_pos)); // Text before search string
std::string suffix = "";
if (cur_pos <= raw_text.length()) // Is there anything after the cursor?
{
suffix = wstring_to_utf8str(raw_text.substr(cur_pos)); // Text after search string
}
U32 last_space = prefix.rfind(" ");
std::string pattern = prefix.substr(last_space + 1, prefix.length() - last_space - 1); // Search pattern
prefix = prefix.substr(0, last_space + 1);
std::string match_pattern = "";
if (pattern.size() < NAME_PREDICTION_MINIMUM_LENGTH) return;
match_pattern = prefix.substr(last_space + 1, prefix.length() - last_space - 1);
prefix = prefix.substr(0, last_space + 1);
std::string match = pattern;
LLStringUtil::toLower(pattern);
std::string name;
bool found = false;
bool full_name = false;
std::vector<LLUUID>::iterator iter = avatar_ids.begin();
if (last_space != std::string::npos && !prefix.empty())
{
last_space = prefix.substr(0, prefix.length() - 2).rfind(" ");
match_pattern = prefix.substr(last_space + 1, prefix.length() - last_space - 1);
prefix = prefix.substr(0, last_space + 1);
// prepare search pattern
std::string full_pattern(match_pattern + pattern);
LLStringUtil::toLower(full_pattern);
// Look for a match
while (iter != avatar_ids.end() && !found)
{
if ((bool)gCacheName->getFullName(*iter++, name))
{
if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES))
{
name = RlvStrings::getAnonym(name);
}
LLStringUtil::toLower(name);
found = (name.find(full_pattern) == 0);
}
}
}
if (found)
{
full_name = true; // ignore OnlyFirstName in case we want to disambiguate
prefix += match_pattern;
}
else if (!pattern.empty()) // if first search did not work, try matching with last word before cursor only
{
prefix += match_pattern; // first part of the pattern wasn't a pattern, so keep it in prefix
LLStringUtil::toLower(pattern);
iter = avatar_ids.begin();
// Look for a match
while (iter != avatar_ids.end() && !found)
{
if ((bool)gCacheName->getFullName(*iter++, name))
{
if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES))
{
name = RlvStrings::getAnonym(name);
}
LLStringUtil::toLower(name);
found = (name.find(pattern) == 0);
}
}
}
// if we found something by either method, replace the pattern by the avatar name
if (found)
{
std::string first_name, last_name;
gCacheName->getFirstLastName(*(iter - 1), first_name, last_name);
std::string rest_of_match;
if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES))
{
prefix += RlvStrings::getAnonym(first_name + " " + last_name) + " ";
}
else
{
if (full_name)
{
rest_of_match = /*first_name + " " +*/ last_name.substr(pattern.size());
}
else
{
rest_of_match = first_name.substr(pattern.size());
}
prefix += match + rest_of_match + " ";
}
mInputEditor->setText(prefix + suffix);
mInputEditor->selectNext(rest_of_match, false);
}
}
}
// </FS:CR>
}
// static
void FSFloaterNearbyChat::onChatBoxFocusLost()
{
// stop typing animation
gAgent.stopTyping();
}
void FSFloaterNearbyChat::onChatBoxFocusReceived()
{
mInputEditor->setEnabled(!gDisconnected);
}
void FSFloaterNearbyChat::reshapeChatLayoutPanel()
{
mChatLayoutPanel->reshape(mChatLayoutPanel->getRect().getWidth(), mInputEditor->getRect().getHeight() + mInputEditorPad, FALSE);
}
EChatType FSFloaterNearbyChat::processChatTypeTriggers(EChatType type, std::string &str)
{
U32 length = str.length();
S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers);
for (S32 n = 0; n < cnt; n++)
{
if (length >= sChatTypeTriggers[n].name.length())
{
std::string trigger = str.substr(0, sChatTypeTriggers[n].name.length());
if (!LLStringUtil::compareInsensitive(trigger, sChatTypeTriggers[n].name))
{
U32 trigger_length = sChatTypeTriggers[n].name.length();
// It's to remove space after trigger name
if (length > trigger_length && str[trigger_length] == ' ')
trigger_length++;
str = str.substr(trigger_length, length);
if (CHAT_TYPE_NORMAL == type)
return sChatTypeTriggers[n].type;
else
break;
}
}
}
return type;
}
void FSFloaterNearbyChat::sendChat( EChatType type )
{
if (mInputEditor)
{
LLWString text = mInputEditor->getWText();
LLWStringUtil::trim(text);
LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines.
if (!text.empty())
{
if(type == CHAT_TYPE_OOC)
{
std::string tempText = wstring_to_utf8str( text );
tempText = gSavedSettings.getString("FSOOCPrefix") + " " + tempText + " " + gSavedSettings.getString("FSOOCPostfix");
text = utf8str_to_wstring(tempText);
}
// Check if this is destined for another channel
S32 channel = 0;
stripChannelNumber(text, &channel);
// If "/<number>" is not specified, see if a channel has been set in
// the spinner.
if (gSavedSettings.getBOOL("FSNearbyChatbar") &&
gSavedSettings.getBOOL("FSShowChatChannel") &&
(channel == 0))
{
channel = (S32)(FSFloaterNearbyChat::getInstance()->getChild<LLSpinCtrl>("ChatChannel")->get());
}
std::string utf8text = wstring_to_utf8str(text);
// Try to trigger a gesture, if not chat to a script.
std::string utf8_revised_text;
if (0 == channel)
{
// Convert OOC and MU* style poses
utf8text = applyAutoCloseOoc(utf8text);
utf8text = applyMuPose(utf8text);
// discard returned "found" boolean
LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text);
}
else
{
utf8_revised_text = utf8text;
}
utf8_revised_text = utf8str_trim(utf8_revised_text);
EChatType nType;
if(type == CHAT_TYPE_OOC)
nType = CHAT_TYPE_NORMAL;
else
nType = type;
type = processChatTypeTriggers(nType, utf8_revised_text);
if (!utf8_revised_text.empty() && cmd_line_chat(utf8_revised_text, type))
{
// Chat with animation
sendChatFromViewer(utf8_revised_text, type, gSavedSettings.getBOOL("FSPlayChatAnimation"));
}
}
mInputEditor->setText(LLStringExplicit(""));
}
gAgent.stopTyping();
// If the user wants to stop chatting on hitting return, lose focus
// and go out of chat mode.
if (gSavedSettings.getBOOL("CloseChatOnReturn"))
{
stopChat();
}
}
void FSFloaterNearbyChat::onChatBoxCommit()
{
if (mInputEditor->getText().length() > 0)
{
sendChat(CHAT_TYPE_NORMAL);
}
gAgent.stopTyping();
}
void FSFloaterNearbyChat::sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate)
{
sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate);
}
void FSFloaterNearbyChat::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate)
{
S32 channel = 0;
LLWString out_text = stripChannelNumber(wtext, &channel);
// If "/<number>" is not specified, see if a channel has been set in
// the spinner.
if (gSavedSettings.getBOOL("FSNearbyChatbar") &&
gSavedSettings.getBOOL("FSShowChatChannel") &&
(channel == 0))
{
// <FS:Ansariel> [FS communication UI]
//channel = (S32)(LLFloaterNearbyChat::getInstance()->getChild<LLSpinCtrl>("ChatChannel")->get());
channel = (S32)(FSFloaterNearbyChat::getInstance()->getChild<LLSpinCtrl>("ChatChannel")->get());
// </FS:Ansariel> [FS communication UI]
}
std::string utf8_out_text = wstring_to_utf8str(out_text);
std::string utf8_text = wstring_to_utf8str(wtext);
utf8_text = utf8str_trim(utf8_text);
if (!utf8_text.empty())
{
utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1);
}
// [RLVa:KB] - Checked: 2010-03-27 (RLVa-1.2.0b) | Modified: RLVa-1.2.0b
if ( (0 == channel) && (rlv_handler_t::isEnabled()) )
{
// Adjust the (public) chat "volume" on chat and gestures (also takes care of playing the proper animation)
if ( ((CHAT_TYPE_SHOUT == type) || (CHAT_TYPE_NORMAL == type)) && (gRlvHandler.hasBehaviour(RLV_BHVR_CHATNORMAL)) )
type = CHAT_TYPE_WHISPER;
else if ( (CHAT_TYPE_SHOUT == type) && (gRlvHandler.hasBehaviour(RLV_BHVR_CHATSHOUT)) )
type = CHAT_TYPE_NORMAL;
else if ( (CHAT_TYPE_WHISPER == type) && (gRlvHandler.hasBehaviour(RLV_BHVR_CHATWHISPER)) )
type = CHAT_TYPE_NORMAL;
animate &= !gRlvHandler.hasBehaviour( (!RlvUtil::isEmote(utf8_text)) ? RLV_BHVR_REDIRCHAT : RLV_BHVR_REDIREMOTE );
}
// [/RLVa:KB]
// Don't animate for chats people can't hear (chat to scripts)
if (animate && (channel == 0))
{
if (type == CHAT_TYPE_WHISPER)
{
lldebugs << "You whisper " << utf8_text << llendl;
gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START);
}
else if (type == CHAT_TYPE_NORMAL)
{
lldebugs << "You say " << utf8_text << llendl;
gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START);
}
else if (type == CHAT_TYPE_SHOUT)
{
lldebugs << "You shout " << utf8_text << llendl;
gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START);
}
else
{
llinfos << "send_chat_from_viewer() - invalid volume" << llendl;
return;
}
}
else
{
if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP)
{
lldebugs << "Channel chat: " << utf8_text << llendl;
}
}
send_chat_from_nearby_floater(utf8_out_text, type, channel);
}
// Exit "chat mode" and do the appropriate focus changes
// static
void FSFloaterNearbyChat::stopChat()
{
FSFloaterNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance<FSFloaterNearbyChat>("nearby_chat");
if (nearby_chat)
{
nearby_chat->mInputEditor->setFocus(FALSE);
gAgent.stopTyping();
}
}
// If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20.
// Otherwise returns input and channel 0.
LLWString FSFloaterNearbyChat::stripChannelNumber(const LLWString &mesg, S32* channel)
{
if (mesg[0] == '/'
&& mesg[1] == '/')
{
// This is a "repeat channel send"
*channel = sLastSpecialChatChannel;
return mesg.substr(2, mesg.length() - 2);
}
else if (mesg[0] == '/'
&& mesg[1]
&& LLStringOps::isDigit(mesg[1]))
{
// This a special "/20" speak on a channel
S32 pos = 0;
// Copy the channel number into a string
LLWString channel_string;
llwchar c;
do
{
c = mesg[pos+1];
channel_string.push_back(c);
pos++;
}
while(c && pos < 64 && LLStringOps::isDigit(c));
// Move the pointer forward to the first non-whitespace char
// Check isspace before looping, so we can handle "/33foo"
// as well as "/33 foo"
while(c && iswspace(c))
{
c = mesg[pos+1];
pos++;
}
sLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10);
*channel = sLastSpecialChatChannel;
return mesg.substr(pos, mesg.length() - pos);
}
else
{
// This is normal chat.
*channel = 0;
return mesg;
}
}
//void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel)
// [RLVa:KB] - Checked: 2010-02-27 (RLVa-1.2.0b) | Modified: RLVa-0.2.2a
void send_chat_from_nearby_floater(std::string utf8_out_text, EChatType type, S32 channel)
// [/RLVa:KB]
{
// [RLVa:KB] - Checked: 2010-02-27 (RLVa-1.2.0b) | Modified: RLVa-1.2.0a
// Only process chat messages (ie not CHAT_TYPE_START, CHAT_TYPE_STOP, etc)
if ( (rlv_handler_t::isEnabled()) && ( (CHAT_TYPE_WHISPER == type) || (CHAT_TYPE_NORMAL == type) || (CHAT_TYPE_SHOUT == type) ) )
{
if (0 == channel)
{
// (We already did this before, but LLChatHandler::handle() calls this directly)
if ( ((CHAT_TYPE_SHOUT == type) || (CHAT_TYPE_NORMAL == type)) && (gRlvHandler.hasBehaviour(RLV_BHVR_CHATNORMAL)) )
type = CHAT_TYPE_WHISPER;
else if ( (CHAT_TYPE_SHOUT == type) && (gRlvHandler.hasBehaviour(RLV_BHVR_CHATSHOUT)) )
type = CHAT_TYPE_NORMAL;
else if ( (CHAT_TYPE_WHISPER == type) && (gRlvHandler.hasBehaviour(RLV_BHVR_CHATWHISPER)) )
type = CHAT_TYPE_NORMAL;
// Redirect chat if needed
if ( ( (gRlvHandler.hasBehaviour(RLV_BHVR_REDIRCHAT) || (gRlvHandler.hasBehaviour(RLV_BHVR_REDIREMOTE)) ) &&
(gRlvHandler.redirectChatOrEmote(utf8_out_text)) ) )
{
return;
}
// Filter public chat if sendchat restricted
if (gRlvHandler.hasBehaviour(RLV_BHVR_SENDCHAT))
gRlvHandler.filterChat(utf8_out_text, true);
}
else
{
// Don't allow chat on a non-public channel if sendchannel restricted (unless the channel is an exception)
if ( (gRlvHandler.hasBehaviour(RLV_BHVR_SENDCHANNEL)) && (!gRlvHandler.isException(RLV_BHVR_SENDCHANNEL, channel)) )
return;
// Don't allow chat on debug channel if @sendchat, @redirchat or @rediremote restricted (shows as public chat on viewers)
if (CHAT_CHANNEL_DEBUG == channel)
{
bool fIsEmote = RlvUtil::isEmote(utf8_out_text);
if ( (gRlvHandler.hasBehaviour(RLV_BHVR_SENDCHAT)) ||
((!fIsEmote) && (gRlvHandler.hasBehaviour(RLV_BHVR_REDIRCHAT))) ||
((fIsEmote) && (gRlvHandler.hasBehaviour(RLV_BHVR_REDIREMOTE))) )
{
return;
}
}
}
}
// [/RLVa:KB]
//<FS:TS> FIRE-787: break up too long chat lines into multiple messages
U32 split = MAX_MSG_BUF_SIZE - 1;
U32 pos = 0;
U32 total = utf8_out_text.length();
// Don't break null messages
if(total == 0)
{
really_send_chat_from_nearby_floater(utf8_out_text, type, channel);
}
while(pos < total)
{
U32 next_split = split;
if (pos + next_split > total)
{
// just send the rest of the message
next_split = total - pos;
}
else
{
// first, try to split at a space
while((U8(utf8_out_text[pos + next_split]) != ' ')
&& (next_split > 0))
{
--next_split;
}
if (next_split == 0)
{
next_split = split;
// no space found, split somewhere not in the middle of UTF-8
while((U8(utf8_out_text[pos + next_split]) >= 0x80)
&& (U8(utf8_out_text[pos + next_split]) < 0xC0)
&& (next_split > 0))
{
--next_split;
}
}
if(next_split == 0)
{
next_split = split;
LL_WARNS("Splitting") << "utf-8 couldn't be split correctly" << LL_ENDL;
}
}
std::string send = utf8_out_text.substr(pos, next_split);
pos += next_split;
// *FIXME: Queue messages and wait for server
really_send_chat_from_nearby_floater(send, type, channel);
}
// moved here so we don't bump the count for every message segment
LLViewerStats::getInstance()->incStat(LLViewerStats::ST_CHAT_COUNT);
//</FS:TS> FIRE-787
}
//<FS:TS> FIRE-787: break up too long chat lines into multiple messages
// This function just sends the message, with no other processing. Moved out
// of send_chat_from_viewer.
void really_send_chat_from_nearby_floater(std::string utf8_out_text, EChatType type, S32 channel)
{
LLMessageSystem* msg = gMessageSystem;
// <FS:ND> gMessageSystem can be 0, not sure how it is exactly to reproduce, maybe during viewer shutdown?
if( !msg )
return;
// </FS:ND>
if(channel >= 0)
{
msg->newMessageFast(_PREHASH_ChatFromViewer);
msg->nextBlockFast(_PREHASH_AgentData);
msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
msg->nextBlockFast(_PREHASH_ChatData);
msg->addStringFast(_PREHASH_Message, utf8_out_text);
msg->addU8Fast(_PREHASH_Type, type);
msg->addS32("Channel", channel);
}
else
{
msg->newMessage("ScriptDialogReply");
msg->nextBlock("AgentData");
msg->addUUID("AgentID", gAgent.getID());
msg->addUUID("SessionID", gAgent.getSessionID());
msg->nextBlock("Data");
msg->addUUID("ObjectID", gAgent.getID());
msg->addS32("ChatChannel", channel);
msg->addS32("ButtonIndex", 0);
msg->addString("ButtonLabel", utf8_out_text);
}
gAgent.sendReliableMessage();
}
//</FS:TS> FIRE-787
class LLChatCommandHandler : public LLCommandHandler
{
public:
// not allowed from outside the app
LLChatCommandHandler() : LLCommandHandler("chat", UNTRUSTED_BLOCK) { }
// Your code here
bool handle(const LLSD& tokens, const LLSD& query_map,
LLMediaCtrl* web)
{
bool retval = false;
// Need at least 2 tokens to have a valid message.
if (tokens.size() < 2)
{
retval = false;
}
else
{
S32 channel = tokens[0].asInteger();
// VWR-19499 Restrict function to chat channels greater than 0.
if ((channel > 0) && (channel < CHAT_CHANNEL_DEBUG))
{
retval = true;
// Send unescaped message, see EXT-6353.
std::string unescaped_mesg (LLURI::unescape(tokens[1].asString()));
send_chat_from_nearby_floater(unescaped_mesg, CHAT_TYPE_NORMAL, channel);
}
else
{
retval = false;
// Tell us this is an unsupported SLurl.
}
}
return retval;
}
};

View File

@ -36,11 +36,9 @@
class LLResizeBar;
class FSChatHistory;
// <FS:Zi> Nearby chat bar class
// class LLLineEditor;
#include "lllineeditor.h"
#include "llsingleton.h"
// </FS:Zi>
#include "llchatentry.h"
//#include "llsingleton.h"
#include "lllayoutstack.h"
class FSFloaterNearbyChat: public LLDockableFloater
{
@ -58,16 +56,6 @@ public:
void onChatBarVisibilityChanged();
void onChatChannelVisibilityChanged();
// This doesn't seem to apply anymore? It makes the chat and spin box colors
// appear wrong when focused and unfocused, so disable this. -Zi
#if 0
virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask);
virtual void draw();
// focus overrides
/*virtual*/ void onFocusLost();
#endif
/*virtual*/ void onFocusReceived();
/*virtual*/ void onOpen (const LLSD& key);
/*virtual*/ void setVisible(BOOL visible);
@ -81,6 +69,7 @@ public:
static void processChatHistoryStyleUpdate(const LLSD& newvalue);
void loadHistory();
void reloadMessages(bool clean_messages = false);
static FSFloaterNearbyChat* getInstance();
@ -99,6 +88,31 @@ public:
static bool isWordsName(const std::string& name);
void enableTranslationButton(bool enabled);
LLChatEntry* getChatBox() { return mInputEditor; }
virtual BOOL handleKeyHere( KEY key, MASK mask );
static void startChat(const char* line);
static void stopChat();
static void sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate);
static void sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate);
protected:
static BOOL matchChatTypeTrigger(const std::string& in_str, std::string* out_str);
void onChatBoxKeystroke();
void onChatBoxFocusLost();
void onChatBoxFocusReceived();
void sendChat( EChatType type );
void onChatBoxCommit();
static LLWString stripChannelNumber(const LLWString &mesg, S32* channel);
EChatType processChatTypeTriggers(EChatType type, std::string &str);
void reshapeFloater(bool collapse);
void reshapeChatLayoutPanel();
static S32 sLastSpecialChatChannel;
private:
void getAllowedRect (LLRect& rect);
@ -110,9 +124,15 @@ private:
FSChatHistory* mChatHistory;
// <FS:Ansariel> Optional muted chat history
FSChatHistory* mChatHistoryMuted;
LLChatEntry* mInputEditor;
LLLayoutPanel* mChatLayoutPanel;
LLLayoutStack* mInputPanels;
S32 mInputEditorPad;
S32 mChatLayoutPanelHeight;
S32 mFloaterHeight;
std::vector<LLChat> mMessageArchive;
LLLineEditor* mInputEditor;
BOOL FSUseNearbyChatConsole;
};

View File

@ -1,213 +1,234 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<floater
legacy_header_height="18"
background_visible="true"
default_tab_group="1"
follows="all"
height="225"
layout="topleft"
left="0"
name="nearby_chat"
title="Nearby Chat"
top="0"
can_dock="false"
can_close="true"
can_minimize="false"
can_tear_off="true"
single_instance="true"
save_rect="true"
save_visibility="true"
save_dock_state="true"
save_tearoff_state="true"
visible="false"
width="394"
can_resize="true"
min_width="250"
min_height="150">
<layout_stack
border_size="0"
follows="all"
height="203"
layout="topleft"
left="0"
name="main_layout_stack"
orientation="vertical"
top="20"
width="394">
<layout_panel
follows="all"
height="200"
layout="topleft"
name="chat_history_and_buttons_panel"
right="394"
user_resize="false">
<button
height="20"
follows="left|top"
top="0"
left="2"
image_overlay="TabIcon_Open_Off"
layout="topleft"
width="25"
name="slide_left_btn" />
<button
height="20"
follows="left|top"
top_delta="0"
left="2"
image_overlay="TabIcon_Close_Off"
layout="topleft"
width="25"
name="slide_right_btn" />
<button
height="20"
follows="left|top"
top_delta="0"
left="2"
image_overlay="TabIcon_Translate_Off"
layout="topleft"
width="25"
control_name="TranslateChat"
is_toggle="true"
image_pressed="PushButton_Press"
image_pressed_selected="PushButton_Selected_Press"
image_selected="PushButton_Selected_Press"
tool_tip="Use selected translation service"
name="translate_btn"/>
<button
height="20"
follows="left|top"
left_pad="2"
image_overlay="List_View_On"
layout="topleft"
width="25"
image_pressed="PushButton_Press"
image_pressed_selected="PushButton_Selected_Press"
image_selected="PushButton_Selected_Press"
tool_tip="Open the nearby chat history."
name="chat_history_btn"/>
<button
height="20"
follows="left|top"
left_pad="2"
image_overlay="Muted_Chat"
layout="topleft"
width="25"
control_name="FSShowMutedChatHistory"
image_pressed="PushButton_Press"
image_pressed_selected="PushButton_Selected_Press"
image_selected="PushButton_Selected_Press"
tool_tip="Shows the muted chat in nearby chat history."
name="chat_history_muted_btn">
<button.commit_callback
function="ToggleControl"
parameter="FSShowMutedChatHistory"/>
</button>
<fs_chat_history
invisibility_control="FSShowMutedChatHistory"
top_pad="4"
font="SansSerifSmall"
follows="all"
height="174"
mouse_opaque="false"
name="chat_history"
parse_highlights="true"
parse_urls="true"
layout="topleft"
left="2"
right="-2" />
<fs_chat_history
visibility_control="FSShowMutedChatHistory"
top_delta="0"
font="SansSerifSmall"
follows="all"
height="174"
mouse_opaque="false"
name="chat_history_muted"
parse_highlights="true"
parse_urls="true"
layout="topleft"
left="2"
right="-2" />
</layout_panel>
<layout_panel
legacy_header_height="18"
background_visible="true"
follows="all"
height="350"
layout="topleft"
left="0"
name="nearby_chat"
title="Nearby Chat"
top="0"
can_dock="false"
can_close="true"
can_minimize="false"
can_tear_off="true"
single_instance="true"
save_rect="true"
save_visibility="true"
save_dock_state="true"
save_tearoff_state="true"
visible="false"
width="394"
can_resize="true"
min_width="250"
min_height="150">
<layout_stack
border_size="0"
animate="false"
follows="top|left"
clip="false"
height="27"
width="200"
left_pad="0"
layout="topleft"
orientation="horizontal"
name="ls_control_panel"
top="14"
left="3">
<layout_panel
auto_resize="false"
follows="all"
height="20"
layout="topleft"
name="chat_bar_visibility_panel"
right="394"
user_resize="false"
visible="true">
top="0"
layout="topleft"
height="26"
width="26"
follows="left|top">
<button
follows="left|top"
top="2"
width="24"
height="24"
image_overlay="TabIcon_Translate_Off"
control_name="TranslateChat"
is_toggle="true"
image_pressed="PushButton_Press"
image_pressed_selected="PushButton_Selected_Press"
image_selected="PushButton_Selected_Press"
tool_tip="Use selected translation service"
name="translate_btn"/>
</layout_panel>
<layout_panel
auto_resize="false"
user_resize="false"
layout="topleft"
height="26"
width="24"
follows="left|top">
<button
follows="left|top"
top="2"
width="24"
height="24"
image_overlay="List_View_On"
image_pressed="PushButton_Press"
image_pressed_selected="PushButton_Selected_Press"
image_selected="PushButton_Selected_Press"
tool_tip="Open the nearby chat history."
name="chat_history_btn"/>
</layout_panel>
<layout_panel
auto_resize="false"
user_resize="false"
top="0"
layout="topleft"
height="26"
width="26"
follows="left|top">
<button
follows="left|top"
top="2"
width="24"
height="24"
image_overlay="Muted_Chat"
control_name="FSShowMutedChatHistory"
is_toggle="true"
image_pressed="PushButton_Press"
image_pressed_selected="PushButton_Selected_Press"
image_selected="PushButton_Selected_Press"
tool_tip="Shows the muted chat in nearby chat history."
name="chat_history_muted_btn">
</button>
</layout_panel>
</layout_stack>
<layout_stack
animate="false"
follows="all"
height="308"
width="394"
layout="topleft"
orientation="horizontal"
name="im_panels"
top="40"
left="0">
<layout_panel
name="panel_im_control_lp"
left="0"
top="0"
bottom="-1"
right="0"
user_resize="true">
<layout_stack
orientation="horizontal"
border_size="0"
follows="left|right|bottom"
height="20"
layout="topleft"
name="chat_bar_channel_spinner_layout_stack"
right="-2"
top_pad="0">
<layout_panel
follows="all"
height="20"
layout="topleft"
name="chat_bar_layout_panel"
user_resize="false"
width="300">
<fs_nearby_chat_control
layout="topleft"
top="0"
left="3"
right="-1"
follows="all"
font="SansSerifSmall"
height="20"
label="To nearby chat"
name="chat_box"
spellcheck="true"
tab_group="3" />
</layout_panel>
<layout_panel
auto_resize="false"
follows="all"
height="20"
layout="topleft"
name="channel_spinner_visibility_panel"
user_resize="false"
visible="true"
width="90">
<spinner
layout="topleft"
top="0"
left="0"
width="90"
decimal_digits="0"
follows="all"
height="20"
increment="1.00"
initial_value="0"
label="Chan"
label_width="29"
max_val="2147483647"
min_val="-2147483647"
mouse_opaque="true"
name="ChatChannel"
tool_tip="Channel to send chat on. Enabled in Preferences -> Chat -> Firestorm -> Show channel selection in chat bar." />
</layout_panel>
animate="false"
follows="all"
height="308"
width="394"
layout="topleft"
orientation="vertical"
name="im_panels"
top="4"
left="0"
right="0"
bottom="-1">
<layout_panel
name="chat_holder">
<fs_chat_history
invisibility_control="FSShowMutedChatHistory"
font="SansSerifSmall"
follows="all"
name="chat_history"
parse_highlights="true"
parse_urls="true"
right="-4"
left="4"
top="0"
bottom="-1" />
<fs_chat_history
visibility_control="FSShowMutedChatHistory"
font="SansSerifSmall"
follows="all"
name="chat_history_muted"
parse_highlights="true"
parse_urls="true"
right="-4"
left="4"
top="0"
bottom="-1" />
</layout_panel>
<layout_panel
top_delta="0"
top="0"
height="26"
bottom="-1"
auto_resize="false"
name="chat_layout_panel">
<layout_stack
animate="false"
default_tab_group="2"
follows="all"
orientation="horizontal"
name="input_panels"
top="0"
bottom="-2"
left="0"
right="-1">
<layout_panel
name="input_editor_layout_panel"
auto_resize="true"
user_resize="false"
top="0"
bottom="-1">
<chat_editor
layout="topleft"
expand_lines_count="5"
follows="left|right|bottom"
font="SansSerifSmall"
height="20"
is_expandable="true"
text_tentative_color="TextFgTentativeColor"
name="chat_box"
max_length="3070"
spellcheck="true"
tab_group="3"
top="1"
bottom="-2"
left="4"
right="-4"
wrap="true" />
</layout_panel>
<layout_panel
visibility_control="FSShowChatChannel"
auto_resize="false"
user_resize="false"
name="input_button_layout_panel"
width="50"
top="0"
bottom="-1">
<spinner
layout="topleft"
top="1"
left="1"
right="-1"
height="22"
width="90"
decimal_digits="0"
follows="all"
increment="1.00"
initial_value="0"
label=""
label_width="29"
max_val="2147483647"
min_val="-2147483647"
mouse_opaque="true"
name="ChatChannel"
tool_tip="Channel to send chat on. Enabled in Preferences -> Chat -> Firestorm -> Show channel selection in chat bar." />
</layout_panel>
</layout_stack>
</layout_panel>
</layout_stack>
</layout_panel>
</layout_stack>
</layout_panel>
</layout_stack>
</floater>