llfloaterim.cpp 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857
  1. /**
  2. * @file llfloaterim.cpp
  3. * @brief LLFloaterIM and LLFloaterIMSession classes definition
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewergpl$
  6. *
  7. * Copyright (c) 2001-2009, Linden Research, Inc.
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "llviewerprecompiledheaders.h"
  33. #include "llfloaterim.h"
  34. #include "llbutton.h"
  35. #include "llcachename.h"
  36. #include "llcombobox.h" // For class LLFlyoutButton
  37. #include "llconsole.h"
  38. #include "llcorehttputil.h"
  39. #include "llkeyboard.h"
  40. #include "lllineeditor.h"
  41. #include "llmessage.h"
  42. #include "llnotifications.h"
  43. #include "llsliderctrl.h"
  44. #include "llstylemap.h"
  45. #include "lltabcontainer.h"
  46. #include "lltextbox.h"
  47. #include "lluictrlfactory.h"
  48. #include "llagent.h"
  49. #include "llavataractions.h"
  50. #include "llchat.h"
  51. #include "llfloateractivespeakers.h"
  52. #include "llfloateravatarinfo.h"
  53. #include "llfloaterchat.h"
  54. #include "llfloatergroupinfo.h"
  55. #include "llfloatermediabrowser.h"
  56. #include "hbfloatertextinput.h"
  57. #include "llimmgr.h"
  58. #include "llinventory.h"
  59. #include "llinventorymodel.h"
  60. #include "lllogchat.h"
  61. #include "llmutelist.h"
  62. //MK
  63. #include "mkrlinterface.h"
  64. //mk
  65. #include "lltooldraganddrop.h"
  66. #include "llviewertexteditor.h"
  67. #include "llviewerstats.h"
  68. #include "llviewercontrol.h"
  69. #include "llviewerwindow.h"
  70. #include "llvoicechannel.h"
  71. #include "llweb.h"
  72. // Static variables
  73. static std::string sTypingStartString;
  74. static std::string sSessionStartString;
  75. static std::string sDefaultTextString;
  76. static std::string sUnavailableTextString;
  77. static std::string sMutedTextString;
  78. LLFloaterIMSession::instances_list_t LLFloaterIMSession::sFloaterIMSessions;
  79. std::string LLFloaterIM::sOnlyUserMessage;
  80. std::string LLFloaterIM::sOfflineMessage;
  81. std::string LLFloaterIM::sMutedMessage;
  82. LLFloaterIM::strings_map_t LLFloaterIM::sMsgStringsMap;
  83. ///////////////////////////////////////////////////////////////////////////////
  84. // LLFloaterIMSession class
  85. ///////////////////////////////////////////////////////////////////////////////
  86. //static
  87. LLFloaterIMSession* LLFloaterIMSession::findInstance(const LLUUID& session_id)
  88. {
  89. for (instances_list_t::const_iterator it = sFloaterIMSessions.begin(),
  90. end = sFloaterIMSessions.end();
  91. it != end; ++it)
  92. {
  93. LLFloaterIMSession* inst = *it;
  94. if (inst && inst->mSessionUUID == session_id)
  95. {
  96. return inst;
  97. }
  98. }
  99. return NULL;
  100. }
  101. //static
  102. void LLFloaterIMSession::closeAllInstances()
  103. {
  104. instances_list_t instances_copy = sFloaterIMSessions;
  105. while (!instances_copy.empty())
  106. {
  107. LLFloaterIMSession* inst = *instances_copy.begin();
  108. if (inst)
  109. {
  110. inst->setEnabled(false);
  111. inst->close(true);
  112. instances_copy.erase(inst);
  113. }
  114. }
  115. }
  116. LLFloaterIMSession::LLFloaterIMSession(const std::string& session_label,
  117. const LLUUID& session_id,
  118. const LLUUID& other_participant_id,
  119. EInstantMessage dialog)
  120. : LLFloater(session_label, LLRect(), session_label),
  121. mSendButton(NULL),
  122. mOpenTextEditorButton(NULL),
  123. mStartCallButton(NULL),
  124. mEndCallButton(NULL),
  125. mSnoozeButton(NULL),
  126. mViewLogButton(NULL),
  127. mToggleSpeakersButton(NULL),
  128. mSpeakerVolumeSlider(NULL),
  129. mMuteButton(NULL),
  130. mSessionUUID(session_id),
  131. mVoiceChannel(NULL),
  132. mSessionInitialized(false),
  133. mOtherParticipantUUID(other_participant_id),
  134. mDialog(dialog),
  135. mIsGroupSession(false),
  136. mHasScrolledOnce(false),
  137. mTyping(false),
  138. mOtherTyping(false),
  139. mTypingLineStartIndex(0),
  140. mSentTypingState(true),
  141. mNumUnreadMessages(0),
  142. mShowSpeakersOnConnect(true),
  143. mAutoConnect(false),
  144. mProfileButtonEnabled(true),
  145. mFetchingLog(false),
  146. mSpeakers(NULL),
  147. mSpeakerPanel(NULL)
  148. {
  149. sFloaterIMSessions.insert(this);
  150. init(session_label);
  151. }
  152. LLFloaterIMSession::LLFloaterIMSession(const std::string& session_label,
  153. const LLUUID& session_id,
  154. const LLUUID& other_participant_id,
  155. const uuid_vec_t& ids,
  156. const LLSD& voice_channel_info,
  157. EInstantMessage dialog)
  158. : LLFloater(session_label, LLRect(), session_label),
  159. mSessionUUID(session_id),
  160. mOtherParticipantUUID(other_participant_id),
  161. mSendButton(NULL),
  162. mOpenTextEditorButton(NULL),
  163. mStartCallButton(NULL),
  164. mEndCallButton(NULL),
  165. mSnoozeButton(NULL),
  166. mToggleSpeakersButton(NULL),
  167. mSpeakerVolumeSlider(NULL),
  168. mMuteButton(NULL),
  169. mVoiceChannel(NULL),
  170. mVoiceChannelInfo(voice_channel_info),
  171. mSpeakers(NULL),
  172. mSpeakerPanel(NULL),
  173. mSessionInitialized(false),
  174. mSnoozeDuration(0),
  175. mDialog(dialog),
  176. mIsGroupSession(false),
  177. mHasScrolledOnce(false),
  178. mTyping(false),
  179. mOtherTyping(false),
  180. mTypingLineStartIndex(0),
  181. mSentTypingState(true),
  182. mShowSpeakersOnConnect(true),
  183. mAutoConnect(false),
  184. mProfileButtonEnabled(true)
  185. {
  186. sFloaterIMSessions.insert(this);
  187. mSessionInitialTargetIDs = ids;
  188. // On Agni, we may get an empty voice channel info for Vivox servers: reset
  189. // it to undefined in this case.
  190. if (!mVoiceChannelInfo.isMap() || !mVoiceChannelInfo.size())
  191. {
  192. mVoiceChannelInfo.clear();
  193. }
  194. init(session_label);
  195. }
  196. void LLFloaterIMSession::init(const std::string& session_label)
  197. {
  198. LL_DEBUGS("InstantMessaging") << "Initializing session '" << session_label
  199. << "' from status: "
  200. << im_status_to_string(mDialog) << LL_ENDL;
  201. mSessionLabel = mSessionLog = session_label;
  202. mProfileButtonEnabled = false;
  203. U32 server_type = 0;
  204. if (mVoiceChannelInfo.isDefined())
  205. {
  206. server_type = gVoiceClient.getVoiceServerType(mVoiceChannelInfo);
  207. }
  208. bool use_p2p = false;
  209. std::string xml_filename;
  210. switch (mDialog)
  211. {
  212. case IM_SESSION_GROUP_START:
  213. mFactoryMap["active_speakers_panel"] =
  214. LLCallbackMap(createSpeakersPanel, this);
  215. xml_filename = "floater_instant_message_group.xml";
  216. mIsGroupSession = true;
  217. mVoiceChannel = new LLVoiceChannelGroup(mSessionUUID,
  218. mSessionLabel, false);
  219. break;
  220. case IM_SESSION_INVITE:
  221. mFactoryMap["active_speakers_panel"] =
  222. LLCallbackMap(createSpeakersPanel, this);
  223. if (gAgent.isInGroup(mSessionUUID, true))
  224. {
  225. xml_filename = "floater_instant_message_group.xml";
  226. mIsGroupSession = true;
  227. }
  228. else // Must be invite to ad hoc IM
  229. {
  230. xml_filename = "floater_instant_message_ad_hoc.xml";
  231. }
  232. mVoiceChannel = new LLVoiceChannelGroup(mSessionUUID,
  233. mSessionLabel, false);
  234. break;
  235. case IM_SESSION_P2P_INVITE:
  236. xml_filename = "floater_instant_message.xml";
  237. mProfileButtonEnabled = true;
  238. if (LLAvatarName::sOmitResidentAsLastName)
  239. {
  240. mSessionLabel = LLCacheName::cleanFullName(mSessionLabel);
  241. }
  242. if (server_type)
  243. {
  244. mVoiceChannel = new LLVoiceChannelP2P(mSessionUUID,
  245. mSessionLabel,
  246. mOtherParticipantUUID,
  247. server_type);
  248. use_p2p = server_type == LLVoiceClient::WEBRTC_SERVER;
  249. }
  250. else
  251. {
  252. mVoiceChannel = new LLVoiceChannelGroup(mSessionUUID,
  253. mSessionLabel, true);
  254. }
  255. break;
  256. case IM_SESSION_CONFERENCE_START:
  257. mFactoryMap["active_speakers_panel"] =
  258. LLCallbackMap(createSpeakersPanel, this);
  259. xml_filename = "floater_instant_message_ad_hoc.xml";
  260. mVoiceChannel = new LLVoiceChannelGroup(mSessionUUID,
  261. mSessionLabel);
  262. break;
  263. case IM_NOTHING_SPECIAL: // Just received text from another user
  264. xml_filename = "floater_instant_message.xml";
  265. mProfileButtonEnabled = true;
  266. if (mProfileButtonEnabled && LLAvatarName::sOmitResidentAsLastName)
  267. {
  268. mSessionLabel = LLCacheName::cleanFullName(mSessionLabel);
  269. }
  270. if (server_type)
  271. {
  272. mVoiceChannel = new LLVoiceChannelP2P(mSessionUUID,
  273. mSessionLabel,
  274. mOtherParticipantUUID,
  275. server_type);
  276. use_p2p = server_type == LLVoiceClient::WEBRTC_SERVER;
  277. }
  278. else
  279. {
  280. mVoiceChannel = new LLVoiceChannelGroup(mSessionUUID,
  281. mSessionLabel, true);
  282. }
  283. break;
  284. default:
  285. llwarns << "Unknown session type" << llendl;
  286. xml_filename = "floater_instant_message.xml";
  287. llassert(false);
  288. }
  289. if (mVoiceChannel)
  290. {
  291. if (mVoiceChannelInfo.isDefined())
  292. {
  293. mVoiceChannel->setChannelInfo(mVoiceChannelInfo);
  294. }
  295. mSpeakers = new LLIMSpeakerMgr(mVoiceChannel);
  296. }
  297. LLUICtrlFactory::getInstance()->buildFloater(this, xml_filename,
  298. &getFactoryMap(), false);
  299. if (mProfileButtonEnabled && mSessionLog.find(' ') == std::string::npos)
  300. {
  301. // Make sure the IM log file will be unique (avoid getting both
  302. // "JohnDoe.txt" and "JohnDoe Resident.txt", depending on how the IM
  303. // session was started)
  304. mSessionLog += " Resident";
  305. }
  306. setTitle(mSessionLabel);
  307. if (mProfileButtonEnabled)
  308. {
  309. lookupName();
  310. }
  311. mInputEditor->setMaxTextLength(DB_IM_MSG_STR_LEN);
  312. // Enable line history support for instant message bar
  313. mInputEditor->setEnableLineHistory(true);
  314. if (mViewLogButton)
  315. {
  316. // This button is visible only if a log file exists
  317. mViewLogButton->setVisible(false);
  318. }
  319. if (gSavedPerAccountSettings.getBool("LogShowHistory"))
  320. {
  321. LLUUID log_session_id;
  322. if (mIsGroupSession)
  323. {
  324. log_session_id = mSessionUUID;
  325. }
  326. LLLogChat::loadHistory(mSessionLog, &chatFromLog, (void*)this,
  327. log_session_id);
  328. }
  329. if (LLIMMgr::sendStartSessionMessages(mSessionUUID, mOtherParticipantUUID,
  330. mSessionInitialTargetIDs, mDialog,
  331. use_p2p))
  332. {
  333. // We need to wait for session initialization for outgoing ad-hoc and
  334. // group chat session; correct session id for initiated ad-hoc chat
  335. // will be received from the server.
  336. mInputEditor->setEnabled(false);
  337. // Use the starting session message as the input line editor label. We
  338. // used to echo a temporary message in the text editor, but it relied
  339. // on the fact that it would stay the last printed message, so that we
  340. // could remove it later; this is no more true with the server logs
  341. // asynchronous fetching. HB
  342. LLUIString session_start = sSessionStartString;
  343. session_start.setArg("[NAME]", getTitle());
  344. mInputEditor->setLabel(session_start);
  345. }
  346. else
  347. {
  348. // We do not need to wait for any responses so we are already
  349. // initialized
  350. mSessionInitialized = true;
  351. }
  352. }
  353. void LLFloaterIMSession::lookupName()
  354. {
  355. LLAvatarNameCache::get(mOtherParticipantUUID,
  356. boost::bind(&LLFloaterIMSession::onAvatarNameLookup,
  357. _1, _2, this));
  358. }
  359. //static
  360. void LLFloaterIMSession::onAvatarNameLookup(const LLUUID& id,
  361. const LLAvatarName& avatar_name,
  362. void* user_data)
  363. {
  364. LLFloaterIMSession* self = (LLFloaterIMSession*)user_data;
  365. if (self && sFloaterIMSessions.count(self) != 0)
  366. {
  367. // Always show "Display Name [Legacy Name]" for security reasons
  368. std::string title = avatar_name.getNames();
  369. if (!title.empty())
  370. {
  371. self->setTitle(title);
  372. }
  373. }
  374. }
  375. LLFloaterIMSession::~LLFloaterIMSession()
  376. {
  377. sFloaterIMSessions.erase(this);
  378. delete mSpeakers;
  379. mSpeakers = NULL;
  380. // Kicks you out of the voice channel if it is currently active
  381. if (mVoiceChannel)
  382. {
  383. // HAVE to do this here: if it happens in the LLVoiceChannel destructor
  384. // it will call the wrong version (since the object is partially
  385. // deconstructed at that point).
  386. mVoiceChannel->deactivate();
  387. delete mVoiceChannel;
  388. mVoiceChannel = NULL;
  389. }
  390. // Delete focus lost callback
  391. mInputEditor->setFocusLostCallback(NULL);
  392. }
  393. bool LLFloaterIMSession::postBuild()
  394. {
  395. if (sDefaultTextString.empty())
  396. {
  397. sDefaultTextString = getString("default_text_label");
  398. sSessionStartString = getString("session_start_string");
  399. sTypingStartString = getString("typing_start_string");
  400. sUnavailableTextString = getString("unavailable_text_label");
  401. sMutedTextString = getString("muted_text_label");
  402. }
  403. mInputEditor = getChild<LLLineEditor>("chat_editor");
  404. mInputEditor->setFocusReceivedCallback(onInputEditorFocusReceived, this);
  405. mInputEditor->setFocusLostCallback(onInputEditorFocusLost, this);
  406. mInputEditor->setKeystrokeCallback(onInputEditorKeystroke);
  407. mInputEditor->setScrolledCallback(onInputEditorScrolled, this);
  408. mInputEditor->setCommitCallback(onCommitChat);
  409. mInputEditor->setCallbackUserData(this);
  410. mInputEditor->setCommitOnFocusLost(false);
  411. mInputEditor->setRevertOnEsc(false);
  412. mInputEditor->setReplaceNewlinesWithSpaces(false);
  413. mInputEditor->setCustomMenuType("im_input");
  414. if (getChild<LLFlyoutButton>("avatar_btn", true, false))
  415. {
  416. childSetCommitCallback("avatar_btn", onCommitAvatar, this);
  417. if (!mProfileButtonEnabled)
  418. {
  419. childSetEnabled("avatar_btn", false);
  420. }
  421. }
  422. if (getChild<LLButton>("group_info_btn", true, false))
  423. {
  424. childSetAction("group_info_btn", onClickGroupInfo, this);
  425. }
  426. mStartCallButton = getChild<LLButton>("start_call_btn", true, false);
  427. if (mStartCallButton)
  428. {
  429. mStartCallButton->setClickedCallback(onClickStartCall, this);
  430. mEndCallButton = getChild<LLButton>("end_call_btn");
  431. mEndCallButton->setClickedCallback(onClickEndCall, this);
  432. }
  433. mViewLogButton = getChild<LLButton>("view_log_btn", true, false);
  434. if (mViewLogButton)
  435. {
  436. mViewLogButton->setClickedCallback(onClickViewLog, this);
  437. }
  438. mSendButton = getChild<LLButton>("send_btn", true, false);
  439. if (mSendButton)
  440. {
  441. mSendButton->setClickedCallback(onClickSend, this);
  442. }
  443. mOpenTextEditorButton = getChild<LLButton>("open_text_editor_btn", true,
  444. false);
  445. if (mOpenTextEditorButton)
  446. {
  447. mOpenTextEditorButton->setClickedCallback(onClickOpenTextEditor, this);
  448. }
  449. mToggleSpeakersButton = getChild<LLButton>("toggle_active_speakers_btn",
  450. true, false);
  451. if (mToggleSpeakersButton)
  452. {
  453. mToggleSpeakersButton->setClickedCallback(onClickToggleActiveSpeakers,
  454. this);
  455. }
  456. #if 0
  457. LLButton* close_btn = getChild<LLButton>("close_btn");
  458. close_btn->setClickedCallback(&LLFloaterIMSession::onClickClose, this);
  459. #endif
  460. mHistoryEditor = getChild<LLViewerTextEditor>("im_history");
  461. mHistoryEditor->setParseHTML(true);
  462. mHistoryEditor->setCustomMenuType("im_history");
  463. if (mIsGroupSession)
  464. {
  465. mSnoozeButton = getChild<LLButton>("snooze_btn");
  466. mSnoozeButton->setClickedCallback(onClickSnooze, this);
  467. childSetEnabled("profile_btn", false);
  468. }
  469. if (mSpeakerPanel)
  470. {
  471. mSpeakerPanel->refreshSpeakers();
  472. }
  473. if (mDialog == IM_NOTHING_SPECIAL)
  474. {
  475. mMuteButton = getChild<LLButton>("mute_btn", true, false);
  476. if (mMuteButton)
  477. {
  478. mMuteButton->setClickedCallback(onClickMuteVoice, this);
  479. mSpeakerVolumeSlider = getChild<LLSliderCtrl>("speaker_volume");
  480. childSetCommitCallback("speaker_volume", onVolumeChange, this);
  481. }
  482. }
  483. setDefaultBtn("send_btn");
  484. return true;
  485. }
  486. bool LLFloaterIMSession::setSnoozeDuration(U32 duration)
  487. {
  488. if (mIsGroupSession)
  489. {
  490. mSnoozeDuration = duration;
  491. return true;
  492. }
  493. return false;
  494. }
  495. void* LLFloaterIMSession::createSpeakersPanel(void* data)
  496. {
  497. LLFloaterIMSession* floaterp = (LLFloaterIMSession*)data;
  498. if (floaterp && floaterp->mSpeakers)
  499. {
  500. floaterp->mSpeakerPanel =
  501. new LLPanelActiveSpeakers(floaterp->mSpeakers, true);
  502. return floaterp->mSpeakerPanel;
  503. }
  504. else
  505. {
  506. if (floaterp)
  507. {
  508. llwarns << "NULL LLIMSpeakerMgr object" << llendl;
  509. }
  510. else
  511. {
  512. llwarns << "Called with a NULL pointer" << llendl;
  513. llassert(false);
  514. }
  515. return NULL;
  516. }
  517. }
  518. //static
  519. void LLFloaterIMSession::onClickMuteVoice(void* user_data)
  520. {
  521. LLFloaterIMSession* floaterp = (LLFloaterIMSession*)user_data;
  522. if (floaterp)
  523. {
  524. bool is_muted = LLMuteList::isMuted(floaterp->mOtherParticipantUUID,
  525. LLMute::flagVoiceChat);
  526. LLMute mute(floaterp->mOtherParticipantUUID, floaterp->getTitle(),
  527. LLMute::AGENT);
  528. if (!is_muted)
  529. {
  530. LLMuteList::add(mute, LLMute::flagVoiceChat);
  531. }
  532. else
  533. {
  534. LLMuteList::remove(mute, LLMute::flagVoiceChat);
  535. }
  536. }
  537. }
  538. //static
  539. void LLFloaterIMSession::onVolumeChange(LLUICtrl* source, void* user_data)
  540. {
  541. LLFloaterIMSession* self = (LLFloaterIMSession*)user_data;
  542. if (self)
  543. {
  544. gVoiceClient.setUserVolume(self->mOtherParticipantUUID,
  545. (F32)source->getValue().asReal());
  546. }
  547. }
  548. // virtual
  549. void LLFloaterIMSession::draw()
  550. {
  551. bool voice_enabled = LLVoiceClient::voiceEnabled();
  552. bool enable_connect = voice_enabled && mSessionInitialized;
  553. if (mStartCallButton)
  554. {
  555. // Hide/show start call and end call buttons
  556. bool call_started =
  557. mVoiceChannel &&
  558. mVoiceChannel->getState() >= LLVoiceChannel::STATE_CALL_STARTED;
  559. mStartCallButton->setVisible(voice_enabled && !call_started);
  560. mStartCallButton->setEnabled(enable_connect);
  561. mEndCallButton->setVisible(voice_enabled && call_started);
  562. }
  563. if (mSnoozeButton)
  564. {
  565. static LLCachedControl<U32> snooze_duration(gSavedSettings,
  566. "GroupIMSnoozeDuration");
  567. mSnoozeButton->setVisible(snooze_duration > 0);
  568. }
  569. bool has_text_editor = HBFloaterTextInput::hasFloaterFor(mInputEditor);
  570. bool empty = mInputEditor->getText().size() == 0;
  571. if (empty && !has_text_editor)
  572. {
  573. // Reset this flag if the chat input line is empty
  574. mHasScrolledOnce = false;
  575. }
  576. if (mSendButton)
  577. {
  578. mSendButton->setEnabled(!empty && !has_text_editor);
  579. }
  580. // Test mSessionInitialized to keep "Starting session..." when not yet
  581. // ready. HB
  582. if (mSessionInitialized)
  583. {
  584. LLPointer<LLSpeaker> self_speaker;
  585. if (mSpeakers)
  586. {
  587. self_speaker = mSpeakers->findSpeaker(gAgentID);
  588. }
  589. if (self_speaker.notNull() && self_speaker->mModeratorMutedText)
  590. {
  591. mInputEditor->setEnabled(false);
  592. mInputEditor->setLabel(sMutedTextString);
  593. }
  594. else
  595. {
  596. mInputEditor->setEnabled(!has_text_editor);
  597. mInputEditor->setLabel(sDefaultTextString);
  598. }
  599. }
  600. if (mAutoConnect && enable_connect)
  601. {
  602. onClickStartCall(this);
  603. mAutoConnect = false;
  604. }
  605. // Show speakers window when voice first connects
  606. if (mShowSpeakersOnConnect && mSpeakerPanel &&
  607. mVoiceChannel && mVoiceChannel->isActive())
  608. {
  609. mSpeakerPanel->setVisible(true);
  610. mShowSpeakersOnConnect = false;
  611. }
  612. if (mToggleSpeakersButton)
  613. {
  614. mToggleSpeakersButton->setValue(mSpeakerPanel &&
  615. mSpeakerPanel->getVisible());
  616. }
  617. if (mTyping)
  618. {
  619. // Time out if user has not typed for a while.
  620. if (mLastKeystrokeTimer.getElapsedTimeF32() >
  621. LLAgent::TYPING_TIMEOUT_SECS)
  622. {
  623. setTyping(false);
  624. }
  625. // If we are typing, and it has been a little while, send the
  626. // typing indicator
  627. if (!mSentTypingState &&
  628. mFirstKeystrokeTimer.getElapsedTimeF32() > 1.f)
  629. {
  630. sendTypingState(true);
  631. mSentTypingState = true;
  632. }
  633. }
  634. // Use embedded panel if available
  635. if (mSpeakerPanel)
  636. {
  637. if (mSpeakerPanel->getVisible())
  638. {
  639. mSpeakerPanel->refreshSpeakers();
  640. }
  641. }
  642. else if (mMuteButton)
  643. {
  644. // Refresh volume and mute
  645. bool voice_active = voice_enabled && mVoiceChannel &&
  646. mVoiceChannel->isActive();
  647. mSpeakerVolumeSlider->setVisible(voice_active);
  648. mMuteButton->setVisible(voice_active);
  649. if (voice_active)
  650. {
  651. F32 volume = gVoiceClient.getUserVolume(mOtherParticipantUUID);
  652. mSpeakerVolumeSlider->setValue(volume);
  653. mMuteButton->setValue(LLMuteList::isMuted(mOtherParticipantUUID,
  654. LLMute::flagVoiceChat));
  655. }
  656. }
  657. LLFloater::draw();
  658. }
  659. bool LLFloaterIMSession::inviteToSession(const uuid_vec_t& ids)
  660. {
  661. const std::string& url = gAgent.getRegionCapability("ChatSessionRequest");
  662. if (url.empty())
  663. {
  664. return false;
  665. }
  666. S32 count = ids.size();
  667. if (isInviteAllowed() && count > 0)
  668. {
  669. llinfos << "Inviting participants" << llendl;
  670. LLSD data;
  671. data["params"] = LLSD::emptyArray();
  672. for (S32 i = 0; i < count; ++i)
  673. {
  674. data["params"].append(ids[i]);
  675. }
  676. data["method"] = "invite";
  677. data["session-id"] = mSessionUUID;
  678. LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data,
  679. "Session invite sent",
  680. "Session invite failed");
  681. }
  682. else
  683. {
  684. llinfos << "No need to invite agents for " << mDialog << llendl;
  685. // Successful add, because everyone that needed to get added was added.
  686. }
  687. return true;
  688. }
  689. void LLFloaterIMSession::addQueuedMessages()
  690. {
  691. mFetchingLog = false;
  692. for (U32 i = 0, count = mMessagesBuffer.size(); i < count; ++i)
  693. {
  694. const QueuedMessage& data = mMessagesBuffer[i];
  695. addHistoryLine(data.mText, data.mColor, data.mLog, data.mSourceId,
  696. data.mFrom);
  697. }
  698. mMessagesBuffer.clear();
  699. }
  700. void LLFloaterIMSession::addHistoryLine(const std::string& utf8msg,
  701. const LLColor4& color,
  702. bool log_to_file,
  703. const LLUUID& source,
  704. const std::string& const_name)
  705. {
  706. if (mFetchingLog)
  707. {
  708. // We must queue this message until the log is fully recovered. HB
  709. mMessagesBuffer.emplace_back(source, const_name, utf8msg, color,
  710. log_to_file);
  711. return;
  712. }
  713. std::string name = const_name;
  714. // Start tab flashing when receiving im for background session from user
  715. if (source.notNull())
  716. {
  717. LLMultiFloater* hostp = getHost();
  718. if (!isInVisibleChain() && hostp && source != gAgentID)
  719. {
  720. hostp->setFloaterFlashing(this, true);
  721. }
  722. }
  723. // Now we are adding the actual line of text, so erase the "Foo is
  724. // typing..." text segment, and the optional timestamp if it was present.
  725. // JC
  726. removeTypingIndicator();
  727. // Actually add the line
  728. std::string timestring;
  729. bool prepend_newline = true;
  730. static LLCachedControl<bool> show_time(gSavedSettings, "IMShowTimestamps");
  731. if (show_time)
  732. {
  733. timestring = mHistoryEditor->appendTime(prepend_newline);
  734. prepend_newline = false;
  735. }
  736. // 'name' is a sender name that we want to hotlink so that clicking on it
  737. // opens a profile. If name exists then add it to the front of the message.
  738. if (!name.empty())
  739. {
  740. // Do not hotlink any messages from the system (e.g. "Second Life:"),
  741. // so just add those in plain text.
  742. if (name == SYSTEM_FROM)
  743. {
  744. mHistoryEditor->appendColoredText(name, false, prepend_newline,
  745. color);
  746. }
  747. else
  748. {
  749. LLUUID av_id = source;
  750. if (av_id.isNull())
  751. {
  752. std::string self_name;
  753. gAgent.buildFullname(self_name);
  754. if (name == self_name)
  755. {
  756. av_id = gAgentID;
  757. }
  758. }
  759. else if (LLAvatarNameCache::useDisplayNames())
  760. {
  761. LLAvatarName avatar_name;
  762. if (LLAvatarNameCache::get(av_id, &avatar_name))
  763. {
  764. if (LLAvatarNameCache::useDisplayNames() == 2)
  765. {
  766. name = avatar_name.mDisplayName;
  767. }
  768. else
  769. {
  770. name = avatar_name.getNames();
  771. }
  772. }
  773. }
  774. // Convert the name to a hotlink and add to message.
  775. const LLStyleSP& source_style = gStyleMap.lookupAgent(source);
  776. mHistoryEditor->appendStyledText(name, false, prepend_newline,
  777. source_style);
  778. }
  779. prepend_newline = false;
  780. }
  781. mHistoryEditor->appendColoredText(utf8msg, false, prepend_newline, color);
  782. if (log_to_file)
  783. {
  784. logToFile(name + utf8msg);
  785. }
  786. if (!isInVisibleChain())
  787. {
  788. ++mNumUnreadMessages;
  789. }
  790. if (source.notNull())
  791. {
  792. if (mSpeakers)
  793. {
  794. mSpeakers->speakerChatted(source);
  795. mSpeakers->setSpeakerTyping(source, false);
  796. }
  797. if (mSpeakerPanel)
  798. {
  799. // Make sure this speaker is listed...
  800. mSpeakerPanel->addSpeaker(source, true);
  801. if (source != gAgentID)
  802. {
  803. // And that we are here too !
  804. mSpeakerPanel->addSpeaker(gAgentID, true);
  805. }
  806. }
  807. }
  808. }
  809. void LLFloaterIMSession::logToFile(const std::string& line,
  810. bool allow_timestamp)
  811. {
  812. static LLCachedControl<bool> log_im(gSavedPerAccountSettings,
  813. "LogInstantMessages");
  814. if (!log_im)
  815. {
  816. return;
  817. }
  818. static LLCachedControl<bool> stamp(gSavedPerAccountSettings,
  819. "IMLogTimestamp");
  820. if (allow_timestamp && stamp)
  821. {
  822. LLLogChat::saveHistory(mSessionLog, LLLogChat::timestamp() + line);
  823. }
  824. else
  825. {
  826. LLLogChat::saveHistory(mSessionLog, line);
  827. }
  828. }
  829. void LLFloaterIMSession::setVisible(bool b)
  830. {
  831. LLPanel::setVisible(b);
  832. LLMultiFloater* hostp = getHost();
  833. if (b && hostp)
  834. {
  835. hostp->setFloaterFlashing(this, false);
  836. }
  837. }
  838. void LLFloaterIMSession::setInputFocus(bool b)
  839. {
  840. mInputEditor->setFocus(b);
  841. }
  842. void LLFloaterIMSession::selectAll()
  843. {
  844. mInputEditor->selectAll();
  845. }
  846. void LLFloaterIMSession::selectNone()
  847. {
  848. mInputEditor->deselect();
  849. }
  850. bool LLFloaterIMSession::handleKeyHere(KEY key, MASK mask)
  851. {
  852. bool handled = false;
  853. if (key == KEY_RETURN)
  854. {
  855. if (HBFloaterTextInput::hasFloaterFor(mInputEditor))
  856. {
  857. HBFloaterTextInput::show(mInputEditor);
  858. return true;
  859. }
  860. if (mask == MASK_NONE || mask == MASK_CONTROL || mask == MASK_SHIFT)
  861. {
  862. sendMsg();
  863. handled = true;
  864. // Close talk panels on hitting return but not shift-return or
  865. // control-return
  866. if (gIMMgrp && !gSavedSettings.getBool("PinTalkViewOpen") &&
  867. !(mask & MASK_CONTROL) && !(mask & MASK_SHIFT))
  868. {
  869. gIMMgrp->toggle(NULL);
  870. }
  871. }
  872. else if (mask == (MASK_SHIFT | MASK_CONTROL))
  873. {
  874. S32 cursor = mInputEditor->getCursor();
  875. std::string text = mInputEditor->getText();
  876. // For some reason, the event is triggered twice: let's insert only
  877. // one newline character.
  878. if (cursor == 0 || text[cursor - 1] != '\n')
  879. {
  880. text = text.insert(cursor, "\n");
  881. mInputEditor->setText(text);
  882. mInputEditor->setCursor(cursor + 1);
  883. }
  884. handled = true;
  885. }
  886. }
  887. else if (key == KEY_ESCAPE && mask == MASK_NONE)
  888. {
  889. handled = true;
  890. gFocusMgr.setKeyboardFocus(NULL);
  891. // Close talk panel with escape
  892. if (gIMMgrp && !gSavedSettings.getBool("PinTalkViewOpen"))
  893. {
  894. gIMMgrp->toggle(NULL);
  895. }
  896. }
  897. // May need to call base class LLPanel::handleKeyHere if not handled in
  898. // order to tab between buttons. JNC 1.2.2002
  899. return handled;
  900. }
  901. bool LLFloaterIMSession::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop,
  902. EDragAndDropType cargo_type,
  903. void* cargo_data,
  904. EAcceptance* accept,
  905. std::string& tooltip_msg)
  906. {
  907. if (mDialog == IM_NOTHING_SPECIAL)
  908. {
  909. LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID,
  910. mSessionUUID, drop,
  911. cargo_type, cargo_data,
  912. accept);
  913. }
  914. else if (isInviteAllowed())
  915. {
  916. // Handle case for dropping calling cards (and folders of calling
  917. // cards) onto invitation panel for invites
  918. *accept = ACCEPT_NO;
  919. if (cargo_type == DAD_CALLINGCARD)
  920. {
  921. if (dropCallingCard((LLInventoryItem*)cargo_data, drop))
  922. {
  923. *accept = ACCEPT_YES_MULTI;
  924. }
  925. }
  926. else if (cargo_type == DAD_CATEGORY)
  927. {
  928. if (dropCategory((LLInventoryCategory*)cargo_data, drop))
  929. {
  930. *accept = ACCEPT_YES_MULTI;
  931. }
  932. }
  933. }
  934. return true;
  935. }
  936. bool LLFloaterIMSession::dropCallingCard(LLInventoryItem* item, bool drop)
  937. {
  938. bool rv = isInviteAllowed();
  939. if (rv && item && item->getCreatorUUID().notNull())
  940. {
  941. if (drop)
  942. {
  943. uuid_vec_t ids;
  944. ids.emplace_back(item->getCreatorUUID());
  945. inviteToSession(ids);
  946. }
  947. }
  948. else
  949. {
  950. // Set to false if creator uuid is null.
  951. rv = false;
  952. }
  953. return rv;
  954. }
  955. bool LLFloaterIMSession::dropCategory(LLInventoryCategory* category, bool drop)
  956. {
  957. bool rv = isInviteAllowed();
  958. if (rv && category)
  959. {
  960. LLInventoryModel::cat_array_t cats;
  961. LLInventoryModel::item_array_t items;
  962. LLUniqueBuddyCollector buddies;
  963. gInventory.collectDescendentsIf(category->getUUID(), cats, items,
  964. LLInventoryModel::EXCLUDE_TRASH,
  965. buddies);
  966. U32 count = items.size();
  967. if (!count)
  968. {
  969. return false;
  970. }
  971. if (drop)
  972. {
  973. uuid_vec_t ids;
  974. for (U32 i = 0; i < count; ++i)
  975. {
  976. ids.emplace_back(items[i]->getCreatorUUID());
  977. }
  978. inviteToSession(ids);
  979. }
  980. }
  981. return rv;
  982. }
  983. bool LLFloaterIMSession::isInviteAllowed() const
  984. {
  985. return mDialog == IM_SESSION_CONFERENCE_START ||
  986. mDialog == IM_SESSION_INVITE;
  987. }
  988. //static
  989. void LLFloaterIMSession::onTabClick(void* userdata)
  990. {
  991. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  992. if (self)
  993. {
  994. self->setInputFocus(true);
  995. }
  996. }
  997. //static
  998. void LLFloaterIMSession::onCommitAvatar(LLUICtrl* ctrl, void* userdata)
  999. {
  1000. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1001. if (!self || !ctrl) return;
  1002. LLUUID id = self->mOtherParticipantUUID;
  1003. if (id.notNull())
  1004. {
  1005. std::string valstr = ctrl->getValue().asString();
  1006. if (valstr == "offer_tp")
  1007. {
  1008. LLAvatarActions::offerTeleport(id);
  1009. }
  1010. else if (valstr == "request_tp")
  1011. {
  1012. LLAvatarActions::teleportRequest(id);
  1013. }
  1014. else
  1015. {
  1016. // Bring up the Profile window
  1017. LLFloaterAvatarInfo::showFromDirectory(id);
  1018. }
  1019. }
  1020. }
  1021. //static
  1022. void LLFloaterIMSession::onClickViewLog(void* userdata)
  1023. {
  1024. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1025. if (self && !self->mLogFileName.empty() && gViewerWindowp)
  1026. {
  1027. #if LL_WINDOWS
  1028. std::string url = "file:///";
  1029. #else
  1030. std::string url = "file://";
  1031. #endif
  1032. url += LLWeb::escapeURL(self->mLogFileName);
  1033. if (gSavedPerAccountSettings.getBool("OpenIMLogsInBuiltInBrowser"))
  1034. {
  1035. LLFloaterMediaBrowser::showInstance(url);
  1036. }
  1037. else
  1038. {
  1039. gWindowp->spawnWebBrowser(url, true);
  1040. }
  1041. }
  1042. }
  1043. //static
  1044. void LLFloaterIMSession::onClickGroupInfo(void* userdata)
  1045. {
  1046. // Bring up the Profile window
  1047. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1048. LLFloaterGroupInfo::showFromUUID(self->mSessionUUID);
  1049. }
  1050. //static
  1051. void LLFloaterIMSession::onClickClose(void* userdata)
  1052. {
  1053. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1054. if (self)
  1055. {
  1056. self->close();
  1057. }
  1058. }
  1059. //static
  1060. void LLFloaterIMSession::onClickSnooze(void* userdata)
  1061. {
  1062. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1063. if (self)
  1064. {
  1065. if (self->mIsGroupSession)
  1066. {
  1067. self->mSnoozeDuration =
  1068. gSavedSettings.getU32("GroupIMSnoozeDuration");
  1069. }
  1070. self->close();
  1071. }
  1072. }
  1073. //static
  1074. void LLFloaterIMSession::onClickStartCall(void* userdata)
  1075. {
  1076. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1077. if (self && self->mVoiceChannel)
  1078. {
  1079. self->mVoiceChannel->activate();
  1080. }
  1081. }
  1082. //static
  1083. void LLFloaterIMSession::onClickEndCall(void* userdata)
  1084. {
  1085. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1086. if (self && self->mVoiceChannel)
  1087. {
  1088. self->mVoiceChannel->deactivate();
  1089. }
  1090. }
  1091. //static
  1092. void LLFloaterIMSession::onClickSend(void* userdata)
  1093. {
  1094. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1095. if (self)
  1096. {
  1097. self->sendMsg();
  1098. }
  1099. }
  1100. //static
  1101. void LLFloaterIMSession::onClickOpenTextEditor(void* userdata)
  1102. {
  1103. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1104. if (self && !self->mSessionLabel.empty())
  1105. {
  1106. self->mHasScrolledOnce = true;
  1107. HBFloaterTextInput::show(self->mInputEditor, self->mSessionLabel,
  1108. &setIMTyping, self);
  1109. }
  1110. }
  1111. //static
  1112. void LLFloaterIMSession::onClickToggleActiveSpeakers(void* userdata)
  1113. {
  1114. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1115. if (self && self->mSpeakerPanel)
  1116. {
  1117. self->mSpeakerPanel->setVisible(!self->mSpeakerPanel->getVisible());
  1118. }
  1119. }
  1120. //static
  1121. void LLFloaterIMSession::onCommitChat(LLUICtrl* caller, void* userdata)
  1122. {
  1123. LLFloaterIMSession* self= (LLFloaterIMSession*)userdata;
  1124. self->sendMsg();
  1125. }
  1126. //static
  1127. void LLFloaterIMSession::onInputEditorFocusReceived(LLFocusableElement* caller,
  1128. void* userdata)
  1129. {
  1130. LLFloaterIMSession* self= (LLFloaterIMSession*)userdata;
  1131. self->mHistoryEditor->setCursorAndScrollToEnd();
  1132. }
  1133. //static
  1134. void LLFloaterIMSession::onInputEditorFocusLost(LLFocusableElement* caller,
  1135. void* userdata)
  1136. {
  1137. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1138. self->setTyping(false);
  1139. }
  1140. //static
  1141. void LLFloaterIMSession::onInputEditorKeystroke(LLLineEditor* caller,
  1142. void* userdata)
  1143. {
  1144. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1145. std::string text = self->mInputEditor->getText();
  1146. if (!text.empty())
  1147. {
  1148. self->setTyping(true);
  1149. }
  1150. else
  1151. {
  1152. // Deleting all text counts as stopping typing.
  1153. self->setTyping(false);
  1154. }
  1155. }
  1156. //static
  1157. void LLFloaterIMSession::onInputEditorScrolled(LLLineEditor* caller,
  1158. void* userdata)
  1159. {
  1160. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1161. if (!self || !caller) return;
  1162. if (!self->mHasScrolledOnce && !self->mSessionLabel.empty() &&
  1163. gSavedSettings.getBool("AutoOpenTextInput"))
  1164. {
  1165. self->mHasScrolledOnce = true;
  1166. HBFloaterTextInput::show(caller, self->mSessionLabel, &setIMTyping,
  1167. self);
  1168. }
  1169. }
  1170. void LLFloaterIMSession::onClose(bool app_quitting)
  1171. {
  1172. HBFloaterTextInput::abort(mInputEditor);
  1173. setTyping(false);
  1174. if (gIMMgrp)
  1175. {
  1176. gIMMgrp->removeSession(mSessionUUID, mOtherParticipantUUID,
  1177. mSnoozeDuration);
  1178. }
  1179. destroy();
  1180. }
  1181. void LLFloaterIMSession::onVisibilityChange(bool new_visibility)
  1182. {
  1183. if (new_visibility)
  1184. {
  1185. mNumUnreadMessages = 0;
  1186. }
  1187. }
  1188. void LLFloaterIMSession::sendText(LLWString text)
  1189. {
  1190. if (!gAgent.isGodlike() && mDialog == IM_NOTHING_SPECIAL &&
  1191. mOtherParticipantUUID.isNull())
  1192. {
  1193. llinfos << "Cannot send IM to everyone unless you are a god."
  1194. << llendl;
  1195. return;
  1196. }
  1197. //MK
  1198. if (gRLenabled)
  1199. {
  1200. bool allowed;
  1201. if (mIsGroupSession)
  1202. {
  1203. allowed = gRLInterface.canSendGroupIM(mSessionLabel);
  1204. }
  1205. else
  1206. {
  1207. allowed = gRLInterface.canSendIM(mOtherParticipantUUID);
  1208. }
  1209. if (!allowed)
  1210. {
  1211. // User is forbidden to send IMs and the receiver is no exception
  1212. // signal both the sender and the receiver
  1213. text = utf8str_to_wstring(RLInterface::sSendimMessage);
  1214. }
  1215. }
  1216. //mk
  1217. if (!text.empty())
  1218. {
  1219. // Store sent line in history, duplicates will get filtered
  1220. mInputEditor->updateHistory();
  1221. // Convert to UTF8 for transport
  1222. std::string utf8_text = wstring_to_utf8str(text);
  1223. if (utf8_text.length() > 3)
  1224. {
  1225. if (gSavedSettings.getBool("AutoCloseOOC"))
  1226. {
  1227. // Try to find any unclosed OOC chat (i.e. an opening double
  1228. // parenthesis without a matching closing double parenthesis.
  1229. size_t i = utf8_text.find("((");
  1230. if (i != std::string::npos)
  1231. {
  1232. size_t j = utf8_text.rfind("))");
  1233. if (j == std::string::npos || j < i)
  1234. {
  1235. if (utf8_text.back() == ')')
  1236. {
  1237. // Cosmetic: add a space first to avoid a closing
  1238. // triple parenthesis
  1239. utf8_text += ' ';
  1240. }
  1241. // Add the missing closing double parenthesis.
  1242. utf8_text += "))";
  1243. }
  1244. }
  1245. }
  1246. // Convert MU*s style poses into IRC emotes here.
  1247. if (utf8_text[0] == ':'&& gSavedSettings.getBool("AllowMUpose"))
  1248. {
  1249. if (utf8_text.compare(0, 2, ":'") == 0)
  1250. {
  1251. utf8_text.replace(0, 1, "/me");
  1252. }
  1253. // Do not prevent smileys and such.
  1254. else if (isalpha(utf8_text[1]))
  1255. {
  1256. utf8_text.replace(0, 1, "/me ");
  1257. }
  1258. }
  1259. }
  1260. // Truncate
  1261. utf8_text = utf8str_truncate(utf8_text, MAX_MSG_BUF_SIZE - 1);
  1262. if (mSessionInitialized)
  1263. {
  1264. LLIMMgr::deliverMessage(utf8_text, mSessionUUID,
  1265. mOtherParticipantUUID, mDialog);
  1266. // Local echo
  1267. if (mDialog == IM_NOTHING_SPECIAL &&
  1268. mOtherParticipantUUID.notNull())
  1269. {
  1270. std::string history_echo;
  1271. gAgent.buildFullname(history_echo);
  1272. if (LLAvatarNameCache::useDisplayNames())
  1273. {
  1274. LLAvatarName avatar_name;
  1275. if (LLAvatarNameCache::get(gAgentID, &avatar_name))
  1276. {
  1277. if (LLAvatarNameCache::useDisplayNames() == 2)
  1278. {
  1279. history_echo = avatar_name.mDisplayName;
  1280. }
  1281. else
  1282. {
  1283. history_echo = avatar_name.getNames();
  1284. }
  1285. }
  1286. }
  1287. // Look for IRC-style emotes here.
  1288. std::string prefix = utf8_text.substr(0, 4);
  1289. if (prefix == "/me " || prefix == "/me'")
  1290. {
  1291. utf8_text.replace(0, 3, "");
  1292. }
  1293. else
  1294. {
  1295. history_echo += ": ";
  1296. }
  1297. history_echo += utf8_text;
  1298. bool other_was_typing = mOtherTyping;
  1299. addHistoryLine(history_echo,
  1300. gSavedSettings.getColor("IMChatColor"), true,
  1301. gAgentID);
  1302. if (other_was_typing)
  1303. {
  1304. addTypingIndicator(mOtherParticipantUUID,
  1305. mOtherTypingName);
  1306. }
  1307. }
  1308. }
  1309. else
  1310. {
  1311. // Queue up the message to send once the session is initialized
  1312. mQueuedMsgsForInit.append(utf8_text);
  1313. }
  1314. }
  1315. gViewerStats.incStat(LLViewerStats::ST_IM_COUNT);
  1316. // We do not need to actually send the typing stop message, the other
  1317. // client will infer it from receiving the message.
  1318. mTyping = false;
  1319. mSentTypingState = true;
  1320. }
  1321. void LLFloaterIMSession::sendMsg()
  1322. {
  1323. sendText(mInputEditor->getConvertedText());
  1324. mInputEditor->setText(LLStringUtil::null);
  1325. }
  1326. void LLFloaterIMSession::updateSpeakersList(const LLSD& speaker_updates)
  1327. {
  1328. if (mSpeakers)
  1329. {
  1330. mSpeakers->updateSpeakers(speaker_updates);
  1331. }
  1332. }
  1333. void LLFloaterIMSession::processSessionUpdate(const LLSD& session_update)
  1334. {
  1335. if (session_update.has("moderated_mode") &&
  1336. session_update["moderated_mode"].has("voice"))
  1337. {
  1338. bool voice_moderated = session_update["moderated_mode"]["voice"];
  1339. if (voice_moderated)
  1340. {
  1341. setTitle(mSessionLabel + " " + getString("moderated_chat_label"));
  1342. }
  1343. else
  1344. {
  1345. setTitle(mSessionLabel);
  1346. }
  1347. // Update the speakers drop-down too
  1348. if (mSpeakerPanel)
  1349. {
  1350. mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated);
  1351. }
  1352. }
  1353. }
  1354. void LLFloaterIMSession::setSpeakers(const LLSD& speaker_list)
  1355. {
  1356. if (mSpeakers)
  1357. {
  1358. mSpeakers->setSpeakers(speaker_list);
  1359. }
  1360. }
  1361. void LLFloaterIMSession::sessionInitReplyReceived(const LLUUID& session_id)
  1362. {
  1363. mSessionUUID = session_id;
  1364. if (mVoiceChannel)
  1365. {
  1366. mVoiceChannel->updateSessionID(session_id);
  1367. }
  1368. mSessionInitialized = true;
  1369. // Reenable now that the session has started. The "Starting session..."
  1370. // label will be reset to what it should finally be in draw(). HB
  1371. mInputEditor->setEnabled(true);
  1372. // And now, send the queued message.
  1373. LLSD::array_iterator iter;
  1374. for (iter = mQueuedMsgsForInit.beginArray();
  1375. iter != mQueuedMsgsForInit.endArray(); ++iter)
  1376. {
  1377. LLIMMgr::deliverMessage(iter->asString(), mSessionUUID,
  1378. mOtherParticipantUUID, mDialog);
  1379. }
  1380. }
  1381. void LLFloaterIMSession::requestAutoConnect()
  1382. {
  1383. mAutoConnect = true;
  1384. }
  1385. void LLFloaterIMSession::setTyping(bool typing)
  1386. {
  1387. if (typing)
  1388. {
  1389. // Every time the user types something, reset this timer
  1390. mLastKeystrokeTimer.reset();
  1391. if (!mTyping)
  1392. {
  1393. // The user just started typing.
  1394. mFirstKeystrokeTimer.reset();
  1395. // Will send typing state after a short delay.
  1396. mSentTypingState = false;
  1397. }
  1398. if (mSpeakers)
  1399. {
  1400. mSpeakers->setSpeakerTyping(gAgentID, true);
  1401. }
  1402. }
  1403. else
  1404. {
  1405. if (mTyping)
  1406. {
  1407. // The user just stopped typing, send state immediately
  1408. sendTypingState(false);
  1409. mSentTypingState = true;
  1410. }
  1411. if (mSpeakers)
  1412. {
  1413. mSpeakers->setSpeakerTyping(gAgentID, false);
  1414. }
  1415. }
  1416. mTyping = typing;
  1417. }
  1418. void LLFloaterIMSession::sendTypingState(bool typing)
  1419. {
  1420. // Do not want to send typing indicators to multiple people, potentially
  1421. // too much network traffic. Only send in person-to-person IMs.
  1422. if (mDialog != IM_NOTHING_SPECIAL) return;
  1423. std::string name;
  1424. gAgent.buildFullname(name);
  1425. pack_instant_message(gAgentID, false, gAgentSessionID,
  1426. mOtherParticipantUUID, name, std::string("typing"),
  1427. IM_ONLINE, typing ? IM_TYPING_START : IM_TYPING_STOP,
  1428. mSessionUUID);
  1429. gAgent.sendReliableMessage();
  1430. }
  1431. void LLFloaterIMSession::processIMTyping(const LLUUID& from_id,
  1432. const std::string& name, bool typing)
  1433. {
  1434. if (typing)
  1435. {
  1436. // Other user started typing
  1437. addTypingIndicator(from_id, name);
  1438. }
  1439. else
  1440. {
  1441. // Other user stopped typing
  1442. removeTypingIndicator(from_id);
  1443. }
  1444. }
  1445. void LLFloaterIMSession::addTypingIndicator(const LLUUID& from_id,
  1446. const std::string& from_name)
  1447. {
  1448. // We may have lost a "stop-typing" packet, do not add it twice.
  1449. // Also, do not add any indicator while fetching the server log; for
  1450. // now, this should never happen since the server log is so far reserved
  1451. // to group IM sessions, for which the typing state is never sent. HB
  1452. if (!mOtherTyping && !mFetchingLog)
  1453. {
  1454. mTypingLineStartIndex = mHistoryEditor->getWText().length();
  1455. LLUIString typing_start = sTypingStartString;
  1456. typing_start.setArg("[NAME]", from_name);
  1457. addHistoryLine(typing_start,
  1458. gSavedSettings.getColor4("SystemChatColor"), false);
  1459. mOtherTypingName = from_name;
  1460. mOtherTyping = true;
  1461. if (from_id.notNull() && mSpeakers)
  1462. {
  1463. mSpeakers->setSpeakerTyping(from_id, true);
  1464. }
  1465. }
  1466. }
  1467. void LLFloaterIMSession::removeTypingIndicator(const LLUUID& from_id)
  1468. {
  1469. if (mOtherTyping)
  1470. {
  1471. // Must do this first, otherwise addHistoryLine calls us again.
  1472. mOtherTyping = false;
  1473. S32 chars_to_remove = mHistoryEditor->getWText().length() -
  1474. mTypingLineStartIndex;
  1475. mHistoryEditor->removeTextFromEnd(chars_to_remove);
  1476. }
  1477. if (from_id.notNull() && mSpeakers)
  1478. {
  1479. mSpeakers->setSpeakerTyping(from_id, false);
  1480. }
  1481. }
  1482. //static
  1483. void LLFloaterIMSession::setIMTyping(void* caller, bool typing)
  1484. {
  1485. LLFloaterIMSession* self = (LLFloaterIMSession*)caller;
  1486. if (self)
  1487. {
  1488. self->setTyping(typing);
  1489. }
  1490. }
  1491. //static
  1492. void LLFloaterIMSession::chatFromLog(S32 type, const LLSD& data,
  1493. void* userdata)
  1494. {
  1495. LLFloaterIMSession* self = (LLFloaterIMSession*)userdata;
  1496. std::string message;
  1497. LLColor4 color = LLColor4::grey;
  1498. switch (type)
  1499. {
  1500. case LLLogChat::LOG_FILENAME:
  1501. self->mLogFileName = data["filename"].asString();
  1502. // Nothing to print in the IM window.
  1503. return;
  1504. case LLLogChat::LOG_SERVER_FETCH:
  1505. // The server log is being fetched: at this point we must queue all
  1506. // incoming messages until the full log has been (asynchronously)
  1507. // recovered and printed into mHistoryEditor, or else we would get
  1508. // out of order messages in the latter. HB
  1509. self->mFetchingLog = true;
  1510. // Nothing to print in the IM window.
  1511. return;
  1512. case LLLogChat::LOG_SERVER:
  1513. {
  1514. // Compare this log message against queued messages; the server
  1515. // will have the first message of an opening sesssion already
  1516. // logged when that message arrives on our side, for example. HB
  1517. U32 count = self->mMessagesBuffer.size();
  1518. if (count)
  1519. {
  1520. LLUUID src_id(data["from_id"].asString());
  1521. message = data["message"].asString();
  1522. for (U32 i = 0; i < count; ++i)
  1523. {
  1524. // Here, we can compare the message originator, based on
  1525. // their UUID. HB
  1526. const QueuedMessage& msg = self->mMessagesBuffer[i];
  1527. if (src_id.notNull() && msg.mSourceId != src_id)
  1528. {
  1529. continue;
  1530. }
  1531. // Match the text. HB
  1532. size_t pos = msg.mText.find(message);
  1533. if (pos == std::string::npos)
  1534. {
  1535. continue;
  1536. }
  1537. if (pos + message.size() == msg.mText.size())
  1538. {
  1539. // This message is already queued. Skip from log. HB
  1540. LL_DEBUGS("ServerIMLog") << "Skipping log server message that we did receive: "
  1541. << message << LL_ENDL;
  1542. return;
  1543. }
  1544. }
  1545. }
  1546. // Add an IM chat log line, with a slighty lighter grey color. HB
  1547. color = LLColor4::grey2;
  1548. message = data["line"].asString();
  1549. // Add it to our log file too, if configured for logging, but do
  1550. // not attempt to add a timestamp, which was already added if the
  1551. // user does want it, in LLLogChat::fetchHistoryCoro(). HB
  1552. self->logToFile(message, false);
  1553. break;
  1554. }
  1555. case LLLogChat::LOG_LINE:
  1556. // Add an IM chat log line.
  1557. message = data["line"].asString();
  1558. break;
  1559. case LLLogChat::LOG_END:
  1560. // Add log end message
  1561. if (gSavedPerAccountSettings.getBool("LogInstantMessages"))
  1562. {
  1563. message =
  1564. LLFloaterChat::getInstance()->getString("IM_logging_string");
  1565. }
  1566. // Enable the View log button only when the file exists; it may
  1567. // have been created as the result of the server log retreival,
  1568. // thus why we waited until now to take this action. HB
  1569. if (self->mViewLogButton && !self->mLogFileName.empty() &&
  1570. LLFile::exists(self->mLogFileName))
  1571. {
  1572. self->mViewLogButton->setVisible(true);
  1573. }
  1574. break;
  1575. default:
  1576. llerrs << "Unknown callback response type: " << type << llendl;
  1577. }
  1578. if (!message.empty())
  1579. {
  1580. self->mHistoryEditor->appendColoredText(message, false, true, color);
  1581. }
  1582. // If the log is fully printed and messages got queued, add them now. HB
  1583. if (type == LLLogChat::LOG_END && self->mFetchingLog)
  1584. {
  1585. self->addQueuedMessages(); // Note: this resets mFetchingLog. HB
  1586. }
  1587. }
  1588. void LLFloaterIMSession::showSessionStartError(const std::string& error_string)
  1589. {
  1590. // The error strings etc should really be static and local to this file
  1591. // instead of in the LLFloaterIM, but they were in llimmgr.cpp first and
  1592. // unfortunately some translations into non English languages already
  1593. // occurred thus making it a tad harder to change over to a "correct"
  1594. // solution. The best solution would be to store all of the misc strings
  1595. // into their own XML file which would be read in by any LLIMPanel post
  1596. // build function instead of repeating the same info in the group, adhoc
  1597. // and normal IM xml files.
  1598. LLSD args;
  1599. args["REASON"] = LLFloaterIM::sMsgStringsMap[error_string];
  1600. std::string recipient = getTitle();
  1601. args["RECIPIENT"] = recipient.empty() ? mSessionUUID.asString()
  1602. : recipient;
  1603. LLSD payload;
  1604. payload["session_id"] = mSessionUUID;
  1605. gNotifications.add("ChatterBoxSessionStartError", args, payload,
  1606. onConfirmForceCloseError);
  1607. }
  1608. void LLFloaterIMSession::showSessionEventError(const std::string& event_string,
  1609. const std::string& error_string)
  1610. {
  1611. LLSD args;
  1612. args["REASON"] = LLFloaterIM::sMsgStringsMap[error_string];
  1613. LLUIString event_str = LLFloaterIM::sMsgStringsMap[event_string];
  1614. std::string recipient = getTitle();
  1615. event_str.setArg("[RECIPIENT]",
  1616. recipient.empty() ? mSessionUUID.asString() : recipient);
  1617. args["EVENT"] = event_str;
  1618. gNotifications.add("ChatterBoxSessionEventError", args);
  1619. }
  1620. void LLFloaterIMSession::showSessionForceClose(const std::string& reason_string)
  1621. {
  1622. LLSD args;
  1623. args["NAME"] = getTitle();
  1624. args["REASON"] = LLFloaterIM::sMsgStringsMap[reason_string];
  1625. LLSD payload;
  1626. payload["session_id"] = mSessionUUID;
  1627. gNotifications.add("ForceCloseChatterBoxSession", args, payload,
  1628. onConfirmForceCloseError);
  1629. }
  1630. bool LLFloaterIMSession::onConfirmForceCloseError(const LLSD& notification,
  1631. const LLSD& response)
  1632. {
  1633. LLUUID session_id = notification["payload"]["session_id"];
  1634. LLFloaterIMSession* floaterp = findInstance(session_id);
  1635. if (floaterp)
  1636. {
  1637. floaterp->close();
  1638. }
  1639. return false;
  1640. }
  1641. ///////////////////////////////////////////////////////////////////////////////
  1642. // LLFloaterIM class
  1643. ///////////////////////////////////////////////////////////////////////////////
  1644. LLFloaterIM::LLFloaterIM()
  1645. {
  1646. // autoresize=false is necessary to avoid resizing of the IM window
  1647. // whenever a session is opened or closed (it would otherwise resize the
  1648. // window to match the size of the im-sesssion when they were created.
  1649. // This happens in LLMultiFloater::resizeToContents() when called through
  1650. // LLMultiFloater::addFloater())
  1651. this->mAutoResize = false;
  1652. LLUICtrlFactory::getInstance()->buildFloater(this, "floater_im.xml");
  1653. }
  1654. bool LLFloaterIM::postBuild()
  1655. {
  1656. if (sOnlyUserMessage.empty())
  1657. {
  1658. sOnlyUserMessage = getString("only_user_message");
  1659. sOfflineMessage = getString("offline_message");
  1660. sMutedMessage = getString("muted_message");
  1661. sMsgStringsMap["generic"] = getString("generic_request_error");
  1662. sMsgStringsMap["unverified"] = getString("insufficient_perms_error");
  1663. sMsgStringsMap["no_ability"] = getString("no_ability_error");
  1664. sMsgStringsMap["muted"] = getString("muted_error");
  1665. sMsgStringsMap["not_a_moderator"] = getString("not_a_mod_error");
  1666. sMsgStringsMap["does not exist"] = getString("session_does_not_exist_error");
  1667. sMsgStringsMap["add"] = getString("add_session_event");
  1668. sMsgStringsMap["message"] = getString("message_session_event");
  1669. sMsgStringsMap["removed"] = getString("removed_from_group");
  1670. sMsgStringsMap["no ability"] = getString("close_on_no_ability");
  1671. }
  1672. return true;
  1673. }