00001
00037 #include "llviewerprecompiledheaders.h"
00038 #include <iostream>
00039
00040 #include "llfloaterchat.h"
00041 #include "llfloateractivespeakers.h"
00042 #include "llfloaterscriptdebug.h"
00043
00044 #include "llchat.h"
00045 #include "llfontgl.h"
00046 #include "llrect.h"
00047 #include "llerror.h"
00048 #include "llstring.h"
00049 #include "message.h"
00050
00051
00052 #include "llagent.h"
00053 #include "llbutton.h"
00054 #include "llcheckboxctrl.h"
00055 #include "llcombobox.h"
00056 #include "llconsole.h"
00057 #include "llfloaterchatterbox.h"
00058 #include "llfloatermute.h"
00059 #include "llkeyboard.h"
00060
00061 #include "llmutelist.h"
00062 #include "llregionnamecache.h"
00063
00064 #include "llchatbar.h"
00065 #include "lllineeditor.h"
00066 #include "llstatusbar.h"
00067 #include "lltextbox.h"
00068 #include "llviewertexteditor.h"
00069 #include "llviewergesture.h"
00070 #include "llviewermessage.h"
00071 #include "llviewerwindow.h"
00072 #include "llviewercontrol.h"
00073 #include "llvieweruictrlfactory.h"
00074 #include "llviewerobjectlist.h"
00075 #include "llviewerregion.h"
00076 #include "llchatbar.h"
00077 #include "lllogchat.h"
00078 #include "lltexteditor.h"
00079 #include "llfloaterhtml.h"
00080 #include "llweb.h"
00081
00082 #include <algorithm>
00083
00084
00085 extern void AddNewIMToLCD(const LLString &newLine);
00086 extern void AddNewChatToLCD(const LLString &newLine);
00087
00088
00089
00090 const F32 INSTANT_MSG_SIZE = 8.0f;
00091 const F32 CHAT_MSG_SIZE = 8.0f;
00092 const LLColor4 INSTANT_MSG_COLOR(1, 1, 1, 1);
00093 const LLColor4 MUTED_MSG_COLOR(0.5f, 0.5f, 0.5f, 1.f);
00094 const S32 MAX_CHATTER_COUNT = 16;
00095
00096
00097
00098
00099 LLColor4 get_text_color(const LLChat& chat);
00100
00101
00102
00103
00104 LLFloaterChat::LLFloaterChat(const LLSD& seed)
00105 : LLFloater("chat floater", "FloaterChatRect", "",
00106 RESIZE_YES, 440, 100, DRAG_ON_TOP, MINIMIZE_NO, CLOSE_YES),
00107 mPanel(NULL)
00108 {
00109 mFactoryMap["chat_panel"] = LLCallbackMap(createChatPanel, NULL);
00110 mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, NULL);
00111
00112 BOOL no_open = FALSE;
00113 gUICtrlFactory->buildFloater(this,"floater_chat_history.xml",&getFactoryMap(),no_open);
00114
00115 childSetCommitCallback("show mutes",onClickToggleShowMute,this);
00116 childSetVisible("Chat History Editor with mute",FALSE);
00117 childSetAction("toggle_active_speakers_btn", onClickToggleActiveSpeakers, this);
00118 setDefaultBtn("Chat");
00119 }
00120
00121 LLFloaterChat::~LLFloaterChat()
00122 {
00123
00124 }
00125
00126 void LLFloaterChat::setVisible(BOOL visible)
00127 {
00128 LLFloater::setVisible( visible );
00129
00130 gSavedSettings.setBOOL("ShowChatHistory", visible);
00131 }
00132
00133 void LLFloaterChat::draw()
00134 {
00135
00136
00137 childSetValue("toggle_active_speakers_btn", childIsVisible("active_speakers_panel"));
00138
00139 LLChatBar* chat_barp = (LLChatBar*)getChildByName("chat_panel", TRUE);
00140 if (chat_barp)
00141 {
00142 chat_barp->refresh();
00143 }
00144
00145 mPanel->refreshSpeakers();
00146 LLFloater::draw();
00147 }
00148
00149 BOOL LLFloaterChat::postBuild()
00150 {
00151 mPanel = (LLPanelActiveSpeakers*)LLUICtrlFactory::getPanelByName(this, "active_speakers_panel");
00152
00153 LLChatBar* chat_barp = (LLChatBar*)getChildByName("chat_panel", TRUE);
00154 if (chat_barp)
00155 {
00156 chat_barp->setGestureCombo(LLUICtrlFactory::getComboBoxByName(this, "Gesture"));
00157 }
00158 return TRUE;
00159 }
00160
00161
00162 void LLFloaterChat::onClose(bool app_quitting)
00163 {
00164 if (!app_quitting)
00165 {
00166 gSavedSettings.setBOOL("ShowChatHistory", FALSE);
00167 }
00168 setVisible(FALSE);
00169 }
00170
00171 void LLFloaterChat::onVisibilityChange(BOOL new_visibility)
00172 {
00173
00174 gConsole->setVisible( !new_visibility );
00175 LLFloater::onVisibilityChange(new_visibility);
00176 }
00177
00178
00179 void add_timestamped_line(LLViewerTextEditor* edit, const LLString& line, const LLColor4& color)
00180 {
00181 bool prepend_newline = true;
00182 if (gSavedSettings.getBOOL("ChatShowTimestamps"))
00183 {
00184 edit->appendTime(prepend_newline);
00185 prepend_newline = false;
00186 }
00187 edit->appendColoredText(line, false, prepend_newline, color);
00188 }
00189
00190 void log_chat_text(const LLChat& chat)
00191 {
00192 LLString histstr;
00193 if ( chat.mFromIM )
00194 histstr = "IM: ";
00195
00196 if (gSavedPerAccountSettings.getBOOL("LogChatTimestamp"))
00197 histstr += LLLogChat::timestamp(gSavedPerAccountSettings.getBOOL("LogTimestampDate")) + chat.mText;
00198 else
00199 histstr += chat.mText;
00200
00201 LLLogChat::saveHistory("chat",histstr);
00202 }
00203
00204 LLString LLFloaterChat::getChatAvatar(const LLChat& chat)
00205 {
00206 if ( chat.mFromIM || chat.mSourceType == CHAT_SOURCE_AGENT )
00207 {
00208
00209 return getAvatarName(chat.mFromID, chat.mFromName);
00210 }
00211 else
00212 {
00213
00214 LLUUID chatter_id = mObjectOwners[chat.mFromID].mOwnerID;
00215 mObjectOwners[chat.mFromID].mObjectName = chat.mFromName;
00216
00217 if ( chatter_id.isNull() )
00218 {
00219
00220 requestObjectOwner(chat.mFromID, chat.mSourceHost);
00221 return "";
00222 }
00223 else
00224 {
00225 return getAvatarName(chatter_id, chat.mFromName);
00226 }
00227 }
00228 }
00229
00230
00231 LLString LLFloaterChat::getAvatarName(const LLUUID &speaker_id, const LLString &speaker_name)
00232 {
00233 LLString ret = "";
00234
00235 if ( !speaker_id.isNull() )
00236 {
00237
00238 char first_name[DB_FIRST_NAME_BUF_SIZE];
00239 char last_name[DB_FIRST_NAME_BUF_SIZE];
00240 first_name[0] = '\0';
00241 last_name[0] = '\0';
00242
00243 if ( gCacheName->getName(speaker_id, first_name, last_name) )
00244 {
00245
00246 ret = first_name;
00247 ret += " ";
00248 ret += last_name;
00249
00250 if ( ret == speaker_name )
00251 {
00252
00253 ret = "";
00254 }
00255 }
00256 else
00257 {
00258
00259 LLNameLookupEntry ent(speaker_id, speaker_name);
00260
00261 std::deque<LLNameLookupEntry>::iterator pos = std::find(mLookupQueue.begin(), mLookupQueue.end(), ent);
00262 S32 dist = 0;
00263
00264 if ( pos == mLookupQueue.end() )
00265 {
00266 mLookupQueue.push_back(ent);
00267 dist = mLookupQueue.size();
00268
00269
00270 gCacheName->get(speaker_id, FALSE, cache_name_callback, this);
00271 }
00272 else
00273 {
00274 dist = std::distance(mLookupQueue.begin(), pos);
00275 }
00276
00277 std::ostringstream o;
00278 o << dist+1;
00279
00280 ret = "";
00281 }
00282 }
00283
00284 return ret;
00285 }
00286
00287
00288 void LLFloaterChat::requestObjectOwner(const LLUUID &object_id, const LLHost &host)
00289 {
00290 if ( object_id.isNull() ) return;
00291
00292 llinfos << "Requesting object data for " << object_id << llendl;
00293
00294 LLMessageSystem* msg = gMessageSystem;
00295
00296 msg->newMessageFast(_PREHASH_RequestObjectPropertiesFamily);
00297 msg->nextBlockFast(_PREHASH_AgentData);
00298 msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
00299 msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
00300 msg->nextBlockFast(_PREHASH_ObjectData);
00301 msg->addU32Fast(_PREHASH_RequestFlags, 0x0 );
00302 msg->addUUIDFast(_PREHASH_ObjectID, object_id );
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318 msg->sendReliable( host );
00319 }
00320
00321
00322 void LLFloaterChat::processObjectPropertiesFamily(LLMessageSystem *msg, void **user_data)
00323 {
00324
00325 LLUUID id;
00326 LLUUID owner_id;
00327 U32 request_flags;
00328
00329 msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_RequestFlags, request_flags );
00330 msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, id );
00331 msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, owner_id );
00332
00333 LLFloaterChat* chat_floater = LLFloaterChat::getInstance(LLSD());
00334
00335 llinfos << "Processing properties for " << id << ". Owner is " << owner_id << llendl;
00336
00337 if ( chat_floater->mObjectOwners.count( id ) > 0 )
00338 {
00339
00340 LLOwnerCacheEntry *ent = &chat_floater->mObjectOwners[id];
00341
00342 if ( ent->mOwnerID == LLUUID::null )
00343 {
00344
00345 llinfos << "First arrival of requested data for object " << id << llendl;
00346 LLString name = chat_floater->getAvatarName(owner_id, ent->mObjectName);
00347 if ( !name.empty() )
00348 {
00349 LLChat chat(ent->mObjectName + " is owned by " + name);
00350 chat.mFromName = "Name Lookup";
00351 addChatHistory(chat);
00352 }
00353 else
00354 {
00355 llinfos << "Got owner, but name lookup failed for " << owner_id << ", queueing up" << llendl;
00356 }
00357 }
00358
00359 ent->mOwnerID = owner_id;
00360 }
00361 }
00362
00363 void LLFloaterChat::cache_name_callback(const LLUUID &id, const char *first, const char *last, BOOL is_group, void *data)
00364 {
00365 LLFloaterChat *floater = (LLFloaterChat*)data;
00366
00367 std::deque<LLNameLookupEntry>::iterator pos = std::find(floater->mLookupQueue.begin(), floater->mLookupQueue.end(), LLNameLookupEntry(id));
00368 if ( pos == floater->mLookupQueue.end() )
00369 {
00370 llwarns << "Strange, callback called, but we weren't looking for UUID " << id << llendl;
00371 return;
00372 }
00373
00374 LLString name = first;
00375 name += " ";
00376 name += last;
00377
00378 LLChat chat(pos->mObject + " is owned by " + name);
00379 chat.mFromName = "Name Lookup";
00380
00381 floater->mLookupQueue.erase(pos);
00382 addChatHistory(chat);
00383 }
00384
00385
00386 LLString LLFloaterChat::getChatPosition(const LLChat &chat)
00387 {
00388 if ( chat.mRegionID.isNull() ) return "";
00389 return llformat("secondlife://%s/%.0f/%.0f/%.0f", gRegionNameCache->get( chat.mRegionID ).c_str(),
00390 chat.mPosAgent.mV[VX],
00391 chat.mPosAgent.mV[VY],
00392 chat.mPosAgent.mV[VZ]);
00393 }
00394
00395
00396 void LLFloaterChat::addChatHistory(const LLChat& chat, bool log_to_file)
00397 {
00398 if ( gSavedPerAccountSettings.getBOOL("LogChat") && log_to_file)
00399 {
00400 log_chat_text(chat);
00401 }
00402
00403 LLColor4 color = get_text_color(chat);
00404
00405 if (!log_to_file) color = LLColor4::grey;
00406
00407 if (chat.mChatType == CHAT_TYPE_DEBUG_MSG)
00408 {
00409 LLFloaterScriptDebug::addScriptLine(chat.mText,
00410 chat.mFromName,
00411 color,
00412 chat.mFromID);
00413 if (!gSavedSettings.getBOOL("ScriptErrorsAsChat"))
00414 {
00415 return;
00416 }
00417 }
00418
00419
00420 LLFloaterChat* chat_floater = LLFloaterChat::getInstance(LLSD());
00421 LLViewerTextEditor* history_editor = (LLViewerTextEditor*)chat_floater->getChildByName("Chat History Editor", TRUE);
00422 LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)chat_floater->getChildByName("Chat History Editor with mute", TRUE);
00423
00424 history_editor->setParseHTML(TRUE);
00425 history_editor_with_mute->setParseHTML(TRUE);
00426
00427 LLString owner_coords_line;
00428
00429 if ( LLUICtrlFactory::getCheckBoxByName(chat_floater,"log owner coords")->get() )
00430 {
00431
00432 if ( !chat.mFromID.isNull() && chat.mSourceType == CHAT_SOURCE_OBJECT )
00433 {
00434 owner_coords_line = chat_floater->getChatAvatar(chat);
00435 if ( owner_coords_line.empty() )
00436 {
00437 owner_coords_line = "(requesting)";
00438 }
00439 }
00440
00441 LLString pos = getChatPosition(chat);
00442
00443 if ( !pos.empty() && !owner_coords_line.empty() )
00444 {
00445 owner_coords_line = "Owner: " + owner_coords_line;
00446 owner_coords_line += " ";
00447 }
00448
00449 if ( ! pos.empty() )
00450 {
00451 owner_coords_line += "at ";
00452 owner_coords_line += pos;
00453 }
00454
00455 if ( ! owner_coords_line.empty() )
00456 {
00457 owner_coords_line = " [" + owner_coords_line + "]";
00458
00459 }
00460 }
00461
00462 LLString chat_text(chat.mText + owner_coords_line);
00463 if ( chat.mFromIM )
00464 {
00465 chat_text = "IM: " + chat_text;
00466 }
00467
00468 if (!chat.mMuted)
00469 {
00470 add_timestamped_line(history_editor, chat_text, color);
00471 add_timestamped_line(history_editor_with_mute, chat_text, color);
00472 }
00473 else
00474 {
00475
00476 LLColor4 muted_color = lerp(color, LLColor4::grey, 0.5f);
00477 add_timestamped_line(history_editor_with_mute, chat_text, color);
00478 }
00479
00480
00481 if (chat.mSourceType == CHAT_SOURCE_OBJECT)
00482 {
00483 chat_floater->mPanel->setSpeaker(chat.mFromID, chat.mFromName, LLSpeaker::STATUS_NOT_IN_CHANNEL, LLSpeaker::SPEAKER_OBJECT);
00484 }
00485 }
00486
00487
00488 void LLFloaterChat::setHistoryCursorAndScrollToEnd()
00489 {
00490 LLViewerTextEditor* history_editor = (LLViewerTextEditor*)LLFloaterChat::getInstance(LLSD())->getChildByName("Chat History Editor", TRUE);
00491 LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)LLFloaterChat::getInstance(LLSD())->getChildByName("Chat History Editor with mute", TRUE);
00492
00493 history_editor->setCursorAndScrollToEnd();
00494 history_editor_with_mute->setCursorAndScrollToEnd();
00495 }
00496
00497
00498
00499 void LLFloaterChat::onClickMute(void *data)
00500 {
00501 LLFloaterChat* self = (LLFloaterChat*)data;
00502
00503 LLComboBox* chatter_combo = LLUICtrlFactory::getComboBoxByName(self,"chatter combobox");
00504
00505 const LLString& name = chatter_combo->getSimple();
00506 LLUUID id = chatter_combo->getCurrentID();
00507
00508 if (name.empty()) return;
00509
00510 LLMute mute(id);
00511 mute.setFromDisplayName(name);
00512 gMuteListp->add(mute);
00513
00514 if (gFloaterMute)
00515 {
00516 gFloaterMute->show();
00517 }
00518 }
00519
00520
00521 void LLFloaterChat::onClickToggleShowMute(LLUICtrl* caller, void *data)
00522 {
00523 LLFloaterChat* floater = (LLFloaterChat*)data;
00524
00525
00526
00527 BOOL show_mute = LLUICtrlFactory::getCheckBoxByName(floater,"show mutes")->get();
00528 LLViewerTextEditor* history_editor = (LLViewerTextEditor*)floater->getChildByName("Chat History Editor", TRUE);
00529 LLViewerTextEditor* history_editor_with_mute = (LLViewerTextEditor*)floater->getChildByName("Chat History Editor with mute", TRUE);
00530
00531 if (!history_editor || !history_editor_with_mute)
00532 return;
00533
00534
00535 if (show_mute)
00536 {
00537 history_editor->setVisible(FALSE);
00538 history_editor_with_mute->setVisible(TRUE);
00539 history_editor_with_mute->setCursorAndScrollToEnd();
00540 }
00541 else
00542 {
00543 history_editor->setVisible(TRUE);
00544 history_editor_with_mute->setVisible(FALSE);
00545 history_editor->setCursorAndScrollToEnd();
00546 }
00547 }
00548
00549
00550 void LLFloaterChat::addChat(const LLChat& chat,
00551 BOOL local_agent)
00552 {
00553 LLColor4 text_color = get_text_color(chat);
00554
00555 BOOL invisible_script_debug_chat =
00556 chat.mChatType == CHAT_TYPE_DEBUG_MSG
00557 && !gSavedSettings.getBOOL("ScriptErrorsAsChat");
00558
00559 #if LL_WINDOWS && LL_LCD_COMPILE
00560
00561 if (!invisible_script_debug_chat)
00562 {
00563 if (!from_instant_message)
00564 {
00565 AddNewChatToLCD(chat.mText);
00566 }
00567 else
00568 {
00569 AddNewIMToLCD(chat.mText);
00570 }
00571 }
00572 #endif
00573 if (!invisible_script_debug_chat
00574 && !chat.mMuted
00575 && gConsole
00576 && !local_agent)
00577 {
00578 F32 size = CHAT_MSG_SIZE;
00579 LLString prefix;
00580 if(chat.mFromIM)
00581 {
00582 text_color = INSTANT_MSG_COLOR;
00583 size = INSTANT_MSG_SIZE;
00584 prefix = "IM: ";
00585 }
00586 gConsole->addLine(prefix + chat.mText, size, text_color);
00587 }
00588
00589 if(chat.mFromIM && gSavedPerAccountSettings.getBOOL("LogChatIM"))
00590 log_chat_text(chat);
00591
00592 if(chat.mFromIM && (gSavedSettings.getBOOL("IMInChatHistory") || chat.mSourceType != CHAT_SOURCE_AGENT) )
00593 addChatHistory(chat,false);
00594
00595 if(!chat.mFromIM)
00596 addChatHistory(chat);
00597 }
00598
00599 LLColor4 get_text_color(const LLChat& chat)
00600 {
00601 LLColor4 text_color;
00602
00603 if(chat.mMuted)
00604 {
00605 text_color.setVec(0.8f, 0.8f, 0.8f, 1.f);
00606 }
00607 else
00608 {
00609 switch(chat.mSourceType)
00610 {
00611 case CHAT_SOURCE_SYSTEM:
00612 text_color = gSavedSettings.getColor4("SystemChatColor");
00613 break;
00614 case CHAT_SOURCE_AGENT:
00615 if (chat.mFromID.isNull())
00616 {
00617 text_color = gSavedSettings.getColor4("SystemChatColor");
00618 }
00619 else
00620 {
00621 text_color = gSavedSettings.getColor4("AgentChatColor");
00622 }
00623 break;
00624 case CHAT_SOURCE_OBJECT:
00625 if (chat.mChatType == CHAT_TYPE_DEBUG_MSG)
00626 {
00627 text_color = gSavedSettings.getColor4("ScriptErrorColor");
00628 }
00629 else if ( chat.mChatType == CHAT_TYPE_OWNER )
00630 {
00631 text_color = gSavedSettings.getColor4("llOwnerSayChatColor");
00632 }
00633 else
00634 {
00635 text_color = gSavedSettings.getColor4("ObjectChatColor");
00636 }
00637 break;
00638 default:
00639 text_color.setToWhite();
00640 }
00641
00642 if (!chat.mPosAgent.isExactlyZero())
00643 {
00644 LLVector3 pos_agent = gAgent.getPositionAgent();
00645 F32 distance = dist_vec(pos_agent, chat.mPosAgent);
00646 if (distance > gAgent.getNearChatRadius())
00647 {
00648
00649 text_color.mV[VALPHA] = 0.8f;
00650 }
00651 }
00652 }
00653
00654 return text_color;
00655 }
00656
00657
00658 void LLFloaterChat::loadHistory()
00659 {
00660 LLLogChat::loadHistory("chat", &chatFromLogFile, (void *)LLFloaterChat::getInstance(LLSD()));
00661 }
00662
00663
00664 void LLFloaterChat::chatFromLogFile(LLString line, void* userdata)
00665 {
00666 LLChat chat;
00667
00668 chat.mText = line;
00669 addChatHistory(chat, FALSE);
00670 }
00671
00672
00673 void* LLFloaterChat::createSpeakersPanel(void* data)
00674 {
00675 return new LLPanelActiveSpeakers(gLocalSpeakerMgr, TRUE);
00676 }
00677
00678
00679 void* LLFloaterChat::createChatPanel(void* data)
00680 {
00681 LLChatBar* chatp = new LLChatBar("floating_chat_bar");
00682 return chatp;
00683 }
00684
00685
00686 void LLFloaterChat::hideInstance(const LLSD& id)
00687 {
00688 LLFloaterChat* floaterp = LLFloaterChat::getInstance(LLSD());
00689
00690 if(floaterp->getHost())
00691 {
00692 LLFloaterChatterBox::hideInstance(LLSD());
00693 }
00694 else
00695 {
00696 LLUISingleton<LLFloaterChat>::hideInstance(id);
00697 }
00698 }
00699
00700
00701 void LLFloaterChat::onClickToggleActiveSpeakers(void* userdata)
00702 {
00703 LLFloaterChat* self = (LLFloaterChat*)userdata;
00704
00705 self->childSetVisible("active_speakers_panel", !self->childIsVisible("active_speakers_panel"));
00706 }
00707