/** * @file llfloaterchat.cpp * @brief LLFloaterChat class implementation * * $LicenseInfo:firstyear=2002&license=viewergpl$ * * Copyright (c) 2002-2007, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ /** * Actually the "Chat History" floater. * Should be llfloaterchathistory, not llfloaterchat. */ #include "llviewerprecompiledheaders.h" #include #include "llfloaterchat.h" #include "llfloateractivespeakers.h" #include "llfloaterscriptdebug.h" #include "llchat.h" #include "llfontgl.h" #include "llrect.h" #include "llerror.h" #include "llstring.h" #include "message.h" // project include #include "llagent.h" #include "llbutton.h" #include "llcheckboxctrl.h" #include "llcombobox.h" #include "llconsole.h" #include "llfloaterchatterbox.h" #include "llfloatermute.h" #include "llkeyboard.h" //#include "lllineeditor.h" #include "llmutelist.h" #include "llregionnamecache.h" //#include "llresizehandle.h" #include "llchatbar.h" #include "lllineeditor.h" #include "llstatusbar.h" #include "lltextbox.h" #include "llviewertexteditor.h" #include "llviewergesture.h" // for triggering gestures #include "llviewermessage.h" #include "llviewerwindow.h" #include "llviewercontrol.h" #include "llvieweruictrlfactory.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llchatbar.h" #include "lllogchat.h" #include "lltexteditor.h" #include "llfloaterhtml.h" #include "llweb.h" #include // For find // Used for LCD display extern void AddNewIMToLCD(const LLString &newLine); extern void AddNewChatToLCD(const LLString &newLine); // // Constants // const F32 INSTANT_MSG_SIZE = 8.0f; const F32 CHAT_MSG_SIZE = 8.0f; const LLColor4 INSTANT_MSG_COLOR(1, 1, 1, 1); const LLColor4 MUTED_MSG_COLOR(0.5f, 0.5f, 0.5f, 1.f); const S32 MAX_CHATTER_COUNT = 16; // // Global statics // LLColor4 get_text_color(const LLChat& chat); // // Member Functions // LLFloaterChat::LLFloaterChat(const LLSD& seed) : LLFloater("chat floater", "FloaterChatRect", "", RESIZE_YES, 440, 100, DRAG_ON_TOP, MINIMIZE_NO, CLOSE_YES), mPanel(NULL) { mFactoryMap["chat_panel"] = LLCallbackMap(createChatPanel, NULL); mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, NULL); // do not automatically open singleton floaters (as result of getInstance()) BOOL no_open = FALSE; gUICtrlFactory->buildFloater(this,"floater_chat_history.xml",&getFactoryMap(),no_open); childSetCommitCallback("show mutes",onClickToggleShowMute,this); //show mutes childSetVisible("Chat History Editor with mute",FALSE); childSetAction("toggle_active_speakers_btn", onClickToggleActiveSpeakers, this); setDefaultBtn("Chat"); } LLFloaterChat::~LLFloaterChat() { // Children all cleaned up by default view destructor. } void LLFloaterChat::setVisible(BOOL visible) { LLFloater::setVisible( visible ); gSavedSettings.setBOOL("ShowChatHistory", visible); } void LLFloaterChat::draw() { // enable say and shout only when text available childSetValue("toggle_active_speakers_btn", childIsVisible("active_speakers_panel")); LLChatBar* chat_barp = (LLChatBar*)getChildByName("chat_panel", TRUE); if (chat_barp) { chat_barp->refresh(); } mPanel->refreshSpeakers(); LLFloater::draw(); } BOOL LLFloaterChat::postBuild() { mPanel = (LLPanelActiveSpeakers*)LLUICtrlFactory::getPanelByName(this, "active_speakers_panel"); LLChatBar* chat_barp = (LLChatBar*)getChildByName("chat_panel", TRUE); if (chat_barp) { chat_barp->setGestureCombo(LLUICtrlFactory::getComboBoxByName(this, "Gesture")); } return TRUE; } // public virtual void LLFloaterChat::onClose(bool app_quitting) { if (!app_quitting) { gSavedSettings.setBOOL("ShowChatHistory", FALSE); } setVisible(FALSE); } void LLFloaterChat::onVisibilityChange(BOOL new_visibility) { // Hide the chat overlay when our history is visible. gConsole->setVisible( !new_visibility ); LLFloater::onVisibilityChange(new_visibility); } void add_timestamped_line(LLViewerTextEditor* edit, const LLString& line, const LLColor4& color) { bool prepend_newline = true; if (gSavedSettings.getBOOL("ChatShowTimestamps")) { edit->appendTime(prepend_newline); prepend_newline = false; } edit->appendColoredText(line, false, prepend_newline, color); } void log_chat_text(const LLChat& chat) { LLString histstr; if ( chat.mFromIM ) histstr = "IM: "; if (gSavedPerAccountSettings.getBOOL("LogChatTimestamp")) histstr += LLLogChat::timestamp(gSavedPerAccountSettings.getBOOL("LogTimestampDate")) + chat.mText; else histstr += chat.mText; LLLogChat::saveHistory("chat",histstr); } LLString LLFloaterChat::getChatAvatar(const LLChat& chat) { if ( chat.mFromIM || chat.mSourceType == CHAT_SOURCE_AGENT ) { // Chat is from an IM, so the speaker is the actual avatar return getAvatarName(chat.mFromID, chat.mFromName); } else { // this is llSay or similar, the speaker is the object LLUUID chatter_id = mObjectOwners[chat.mFromID].mOwnerID; mObjectOwners[chat.mFromID].mObjectName = chat.mFromName; if ( chatter_id.isNull() ) { // Owner is unknown, request requestObjectOwner(chat.mFromID, chat.mSourceHost); return ""; } else { return getAvatarName(chatter_id, chat.mFromName); } } } LLString LLFloaterChat::getAvatarName(const LLUUID &speaker_id, const LLString &speaker_name) { LLString ret = ""; if ( !speaker_id.isNull() ) { // Get actual name of the speaker. This will differ if it's an object talking char first_name[DB_FIRST_NAME_BUF_SIZE]; /* Flawfinder: ignore */ char last_name[DB_FIRST_NAME_BUF_SIZE]; /* Flawfinder: ignore */ first_name[0] = '\0'; last_name[0] = '\0'; if ( gCacheName->getName(speaker_id, first_name, last_name) ) { // Name was in cache, good ret = first_name; ret += " "; ret += last_name; if ( ret == speaker_name ) { // Don't add the info if it matches what would appear as the speaker's name ret = ""; } } else { // Name not in the cache, but we don't want to lose it, so we queue it up. LLNameLookupEntry ent(speaker_id, speaker_name); std::deque::iterator pos = std::find(mLookupQueue.begin(), mLookupQueue.end(), ent); S32 dist = 0; if ( pos == mLookupQueue.end() ) { mLookupQueue.push_back(ent); dist = mLookupQueue.size(); // request the name, write to window later. gCacheName->get(speaker_id, FALSE, cache_name_callback, this); } else { dist = std::distance(mLookupQueue.begin(), pos); } std::ostringstream o; o << dist+1; ret = ""; } } return ret; } //static void LLFloaterChat::requestObjectOwner(const LLUUID &object_id, const LLHost &host) { if ( object_id.isNull() ) return; llinfos << "Requesting object data for " << object_id << llendl; LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RequestObjectPropertiesFamily); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->nextBlockFast(_PREHASH_ObjectData); msg->addU32Fast(_PREHASH_RequestFlags, 0x0 ); msg->addUUIDFast(_PREHASH_ObjectID, object_id ); // LLViewerObject *chatter = gObjectList.findObject(object_id); // LLViewerRegion *regionp; // if ( chatter ) // { // regionp = chatter->getRegion(); // } // else // { // llwarns << "Couldn't find speaking object " << object_id << ", don't know which region to use for the message. Trying with agent's" << llendl; // regionp = gAgent.getRegion(); // } //msg->sendReliable( regionp->getHost() ); msg->sendReliable( host ); } //static void LLFloaterChat::processObjectPropertiesFamily(LLMessageSystem *msg, void **user_data) { LLUUID id; LLUUID owner_id; U32 request_flags; msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_RequestFlags, request_flags ); msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, id ); msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, owner_id ); LLFloaterChat* chat_floater = LLFloaterChat::getInstance(LLSD()); llinfos << "Processing properties for " << id << ". Owner is " << owner_id << llendl; if ( chat_floater->mObjectOwners.count( id ) > 0 ) { // We've got an entry for this object, so we were looking for information on it LLOwnerCacheEntry *ent = &chat_floater->mObjectOwners[id]; if ( ent->mOwnerID == LLUUID::null ) { // We were looking for info, and it just arrived. llinfos << "First arrival of requested data for object " << id << llendl; LLString name = chat_floater->getAvatarName(owner_id, ent->mObjectName); if ( !name.empty() ) { LLChat chat(ent->mObjectName + " is owned by " + name); chat.mFromName = "Name Lookup"; addChatHistory(chat); } else { llinfos << "Got owner, but name lookup failed for " << owner_id << ", queueing up" << llendl; } } ent->mOwnerID = owner_id; } } //static void LLFloaterChat::cache_name_callback(const LLUUID &id, const char *first, const char *last, BOOL is_group, void *data) { LLFloaterChat *floater = (LLFloaterChat*)data; std::deque::iterator pos = std::find(floater->mLookupQueue.begin(), floater->mLookupQueue.end(), LLNameLookupEntry(id)); if ( pos == floater->mLookupQueue.end() ) { llwarns << "Strange, callback called, but we weren't looking for UUID " << id << llendl; return; } LLString name = first; name += " "; name += last; LLChat chat(pos->mObject + " is owned by " + name); chat.mFromName = "Name Lookup"; floater->mLookupQueue.erase(pos); addChatHistory(chat); } // static LLString LLFloaterChat::getChatPosition(const LLChat &chat) { if ( chat.mRegionID.isNull() ) return ""; return llformat("secondlife://%s/%.0f/%.0f/%.0f", gRegionNameCache->get( chat.mRegionID ).c_str(), chat.mPosAgent.mV[VX], chat.mPosAgent.mV[VY], chat.mPosAgent.mV[VZ]); } // static void LLFloaterChat::addChatHistory(const LLChat& chat, bool log_to_file) { if ( gSavedPerAccountSettings.getBOOL("LogChat") && log_to_file) { log_chat_text(chat); } LLColor4 color = get_text_color(chat); if (!log_to_file) color = LLColor4::grey; //Recap from log file. if (chat.mChatType == CHAT_TYPE_DEBUG_MSG) { LLFloaterScriptDebug::addScriptLine(chat.mText, chat.mFromName, color, chat.mFromID); if (!gSavedSettings.getBOOL("ScriptErrorsAsChat")) { return; } } // could flash the chat button in the status bar here. JC LLFloaterChat* chat_floater = LLFloaterChat::getInstance(LLSD()); LLViewerTextEditor* history_editor = (LLViewerTextEditor*)chat_floater->getChildByName("Chat History Editor", TRUE); LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)chat_floater->getChildByName("Chat History Editor with mute", TRUE); history_editor->setParseHTML(TRUE); history_editor_with_mute->setParseHTML(TRUE); LLString owner_coords_line; if ( LLUICtrlFactory::getCheckBoxByName(chat_floater,"log owner coords")->get() ) { // Add owner and position info, if needed if ( !chat.mFromID.isNull() && chat.mSourceType == CHAT_SOURCE_OBJECT ) { owner_coords_line = chat_floater->getChatAvatar(chat); if ( owner_coords_line.empty() ) { owner_coords_line = "(requesting)"; } } LLString pos = getChatPosition(chat); if ( !pos.empty() && !owner_coords_line.empty() ) { owner_coords_line = "Owner: " + owner_coords_line; owner_coords_line += " "; } if ( ! pos.empty() ) { owner_coords_line += "at "; owner_coords_line += pos; } if ( ! owner_coords_line.empty() ) { owner_coords_line = " [" + owner_coords_line + "]"; //owner_coords_line = " " + owner_coords_line; } } LLString chat_text(chat.mText + owner_coords_line); if ( chat.mFromIM ) { chat_text = "IM: " + chat_text; } if (!chat.mMuted) { add_timestamped_line(history_editor, chat_text, color); add_timestamped_line(history_editor_with_mute, chat_text, color); } else { // desaturate muted chat LLColor4 muted_color = lerp(color, LLColor4::grey, 0.5f); add_timestamped_line(history_editor_with_mute, chat_text, color); } // add objects as transient speakers that can be muted if (chat.mSourceType == CHAT_SOURCE_OBJECT) { chat_floater->mPanel->setSpeaker(chat.mFromID, chat.mFromName, LLSpeaker::STATUS_NOT_IN_CHANNEL, LLSpeaker::SPEAKER_OBJECT); } } // static void LLFloaterChat::setHistoryCursorAndScrollToEnd() { LLViewerTextEditor* history_editor = (LLViewerTextEditor*)LLFloaterChat::getInstance(LLSD())->getChildByName("Chat History Editor", TRUE); LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)LLFloaterChat::getInstance(LLSD())->getChildByName("Chat History Editor with mute", TRUE); history_editor->setCursorAndScrollToEnd(); history_editor_with_mute->setCursorAndScrollToEnd(); } //static void LLFloaterChat::onClickMute(void *data) { LLFloaterChat* self = (LLFloaterChat*)data; LLComboBox* chatter_combo = LLUICtrlFactory::getComboBoxByName(self,"chatter combobox"); const LLString& name = chatter_combo->getSimple(); LLUUID id = chatter_combo->getCurrentID(); if (name.empty()) return; LLMute mute(id); mute.setFromDisplayName(name); gMuteListp->add(mute); if (gFloaterMute) { gFloaterMute->show(); } } //static void LLFloaterChat::onClickToggleShowMute(LLUICtrl* caller, void *data) { LLFloaterChat* floater = (LLFloaterChat*)data; //LLCheckBoxCtrl* BOOL show_mute = LLUICtrlFactory::getCheckBoxByName(floater,"show mutes")->get(); LLViewerTextEditor* history_editor = (LLViewerTextEditor*)floater->getChildByName("Chat History Editor", TRUE); LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)floater->getChildByName("Chat History Editor with mute", TRUE); if (!history_editor || !history_editor_with_mute) return; //BOOL show_mute = floater->mShowMuteCheckBox->get(); if (show_mute) { history_editor->setVisible(FALSE); history_editor_with_mute->setVisible(TRUE); history_editor_with_mute->setCursorAndScrollToEnd(); } else { history_editor->setVisible(TRUE); history_editor_with_mute->setVisible(FALSE); history_editor->setCursorAndScrollToEnd(); } } // Put a line of chat in all the right places void LLFloaterChat::addChat(const LLChat& chat, BOOL local_agent) { LLColor4 text_color = get_text_color(chat); BOOL invisible_script_debug_chat = chat.mChatType == CHAT_TYPE_DEBUG_MSG && !gSavedSettings.getBOOL("ScriptErrorsAsChat"); #if LL_WINDOWS && LL_LCD_COMPILE // add into LCD displays if (!invisible_script_debug_chat) { if (!from_instant_message) { AddNewChatToLCD(chat.mText); } else { AddNewIMToLCD(chat.mText); } } #endif if (!invisible_script_debug_chat && !chat.mMuted && gConsole && !local_agent) { F32 size = CHAT_MSG_SIZE; LLString prefix; if(chat.mFromIM) { text_color = INSTANT_MSG_COLOR; size = INSTANT_MSG_SIZE; prefix = "IM: "; } gConsole->addLine(prefix + chat.mText, size, text_color); } if(chat.mFromIM && gSavedPerAccountSettings.getBOOL("LogChatIM")) log_chat_text(chat); if(chat.mFromIM && (gSavedSettings.getBOOL("IMInChatHistory") || chat.mSourceType != CHAT_SOURCE_AGENT) ) addChatHistory(chat,false); if(!chat.mFromIM) addChatHistory(chat); } LLColor4 get_text_color(const LLChat& chat) { LLColor4 text_color; if(chat.mMuted) { text_color.setVec(0.8f, 0.8f, 0.8f, 1.f); } else { switch(chat.mSourceType) { case CHAT_SOURCE_SYSTEM: text_color = gSavedSettings.getColor4("SystemChatColor"); break; case CHAT_SOURCE_AGENT: if (chat.mFromID.isNull()) { text_color = gSavedSettings.getColor4("SystemChatColor"); } else { text_color = gSavedSettings.getColor4("AgentChatColor"); } break; case CHAT_SOURCE_OBJECT: if (chat.mChatType == CHAT_TYPE_DEBUG_MSG) { text_color = gSavedSettings.getColor4("ScriptErrorColor"); } else if ( chat.mChatType == CHAT_TYPE_OWNER ) { text_color = gSavedSettings.getColor4("llOwnerSayChatColor"); } else { text_color = gSavedSettings.getColor4("ObjectChatColor"); } break; default: text_color.setToWhite(); } if (!chat.mPosAgent.isExactlyZero()) { LLVector3 pos_agent = gAgent.getPositionAgent(); F32 distance = dist_vec(pos_agent, chat.mPosAgent); if (distance > gAgent.getNearChatRadius()) { // diminish far-off chat text_color.mV[VALPHA] = 0.8f; } } } return text_color; } //static void LLFloaterChat::loadHistory() { LLLogChat::loadHistory("chat", &chatFromLogFile, (void *)LLFloaterChat::getInstance(LLSD())); } //static void LLFloaterChat::chatFromLogFile(LLString line, void* userdata) { LLChat chat; chat.mText = line; addChatHistory(chat, FALSE); } //static void* LLFloaterChat::createSpeakersPanel(void* data) { return new LLPanelActiveSpeakers(gLocalSpeakerMgr, TRUE); } //static void* LLFloaterChat::createChatPanel(void* data) { LLChatBar* chatp = new LLChatBar("floating_chat_bar"); return chatp; } //static void LLFloaterChat::hideInstance(const LLSD& id) { LLFloaterChat* floaterp = LLFloaterChat::getInstance(LLSD()); // don't do anything when hosted in the chatterbox if(floaterp->getHost()) { LLFloaterChatterBox::hideInstance(LLSD()); } else { LLUISingleton::hideInstance(id); } } // static void LLFloaterChat::onClickToggleActiveSpeakers(void* userdata) { LLFloaterChat* self = (LLFloaterChat*)userdata; self->childSetVisible("active_speakers_panel", !self->childIsVisible("active_speakers_panel")); }