PageRenderTime 37ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/indra/newview/llimpanel.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 976 lines | 733 code | 134 blank | 109 comment | 116 complexity | bf32339e59bf32cc372cb9346cc8a5db MD5 | raw file
Possible License(s): LGPL-2.1
  1. /**
  2. * @file llimpanel.cpp
  3. * @brief LLIMPanel class definition
  4. *
  5. * $LicenseInfo:firstyear=2001&license=viewerlgpl$
  6. * Second Life Viewer Source Code
  7. * Copyright (C) 2010, Linden Research, Inc.
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU Lesser General Public
  11. * License as published by the Free Software Foundation;
  12. * version 2.1 of the License only.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. * Lesser General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public
  20. * License along with this library; if not, write to the Free Software
  21. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  22. *
  23. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  24. * $/LicenseInfo$
  25. */
  26. #include "llviewerprecompiledheaders.h"
  27. #include "llimpanel.h"
  28. #include "indra_constants.h"
  29. #include "llfloaterreg.h"
  30. #include "llfocusmgr.h"
  31. #include "llfontgl.h"
  32. #include "llrect.h"
  33. #include "llerror.h"
  34. #include "llmultifloater.h"
  35. #include "llstring.h"
  36. #include "message.h"
  37. #include "lltextbox.h"
  38. #include "llagent.h"
  39. #include "llbutton.h"
  40. #include "llcallingcard.h"
  41. #include "llchannelmanager.h"
  42. #include "llchat.h"
  43. #include "llchiclet.h"
  44. #include "llconsole.h"
  45. #include "llgroupactions.h"
  46. #include "llfloater.h"
  47. #include "llfloateractivespeakers.h"
  48. #include "llavataractions.h"
  49. #include "llinventory.h"
  50. #include "llinventorymodel.h"
  51. #include "llfloaterinventory.h"
  52. #include "lliconctrl.h"
  53. #include "llkeyboard.h"
  54. #include "lllineeditor.h"
  55. #include "llpanelimcontrolpanel.h"
  56. #include "llrecentpeople.h"
  57. #include "llresmgr.h"
  58. #include "lltooldraganddrop.h"
  59. #include "lltrans.h"
  60. #include "lltabcontainer.h"
  61. #include "llviewertexteditor.h"
  62. #include "llviewermessage.h"
  63. #include "llviewerstats.h"
  64. #include "llviewercontrol.h"
  65. #include "lluictrlfactory.h"
  66. #include "llviewerwindow.h"
  67. #include "llvoicechannel.h"
  68. #include "lllogchat.h"
  69. #include "llweb.h"
  70. #include "llhttpclient.h"
  71. #include "llmutelist.h"
  72. #include "llstylemap.h"
  73. #include "llappviewer.h"
  74. //
  75. // Constants
  76. //
  77. const S32 LINE_HEIGHT = 16;
  78. const S32 MIN_WIDTH = 200;
  79. const S32 MIN_HEIGHT = 130;
  80. //
  81. // Statics
  82. //
  83. //
  84. static std::string sTitleString = "Instant Message with [NAME]";
  85. static std::string sTypingStartString = "[NAME]: ...";
  86. static std::string sSessionStartString = "Starting session with [NAME] please wait.";
  87. //
  88. // LLFloaterIMPanel
  89. //
  90. LLFloaterIMPanel::LLFloaterIMPanel(const std::string& session_label,
  91. const LLUUID& session_id,
  92. const LLUUID& other_participant_id,
  93. const std::vector<LLUUID>& ids,
  94. EInstantMessage dialog)
  95. : LLFloater(session_id),
  96. mInputEditor(NULL),
  97. mHistoryEditor(NULL),
  98. mSessionUUID(session_id),
  99. mSessionLabel(session_label),
  100. mSessionInitialized(FALSE),
  101. mSessionStartMsgPos(0),
  102. mOtherParticipantUUID(other_participant_id),
  103. mDialog(dialog),
  104. mSessionInitialTargetIDs(ids),
  105. mTyping(FALSE),
  106. mOtherTyping(FALSE),
  107. mTypingLineStartIndex(0),
  108. mSentTypingState(TRUE),
  109. mNumUnreadMessages(0),
  110. mShowSpeakersOnConnect(TRUE),
  111. mTextIMPossible(TRUE),
  112. mProfileButtonEnabled(TRUE),
  113. mCallBackEnabled(TRUE),
  114. mSpeakerPanel(NULL),
  115. mFirstKeystrokeTimer(),
  116. mLastKeystrokeTimer()
  117. {
  118. std::string xml_filename;
  119. switch(mDialog)
  120. {
  121. case IM_SESSION_GROUP_START:
  122. mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, this);
  123. xml_filename = "floater_instant_message_group.xml";
  124. break;
  125. case IM_SESSION_INVITE:
  126. mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, this);
  127. if (gAgent.isInGroup(mSessionUUID))
  128. {
  129. xml_filename = "floater_instant_message_group.xml";
  130. }
  131. else // must be invite to ad hoc IM
  132. {
  133. xml_filename = "floater_instant_message_ad_hoc.xml";
  134. }
  135. break;
  136. case IM_SESSION_P2P_INVITE:
  137. xml_filename = "floater_instant_message.xml";
  138. break;
  139. case IM_SESSION_CONFERENCE_START:
  140. mFactoryMap["active_speakers_panel"] = LLCallbackMap(createSpeakersPanel, this);
  141. xml_filename = "floater_instant_message_ad_hoc.xml";
  142. break;
  143. // just received text from another user
  144. case IM_NOTHING_SPECIAL:
  145. xml_filename = "floater_instant_message.xml";
  146. mTextIMPossible = LLVoiceClient::getInstance()->isSessionTextIMPossible(mSessionUUID);
  147. mProfileButtonEnabled = LLVoiceClient::getInstance()->isParticipantAvatar(mSessionUUID);
  148. mCallBackEnabled = LLVoiceClient::getInstance()->isSessionCallBackPossible(mSessionUUID);
  149. break;
  150. default:
  151. llwarns << "Unknown session type" << llendl;
  152. xml_filename = "floater_instant_message.xml";
  153. break;
  154. }
  155. LLUICtrlFactory::getInstance()->buildFloater(this, xml_filename, NULL);
  156. setTitle(mSessionLabel);
  157. mInputEditor->setMaxTextLength(DB_IM_MSG_STR_LEN);
  158. // enable line history support for instant message bar
  159. mInputEditor->setEnableLineHistory(TRUE);
  160. //*TODO we probably need the same "awaiting message" thing in LLIMFloater
  161. LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(mSessionUUID);
  162. if (!im_session)
  163. {
  164. llerror("im session with id " + mSessionUUID.asString() + " does not exist!", 0);
  165. return;
  166. }
  167. mSessionInitialized = im_session->mSessionInitialized;
  168. if (!mSessionInitialized)
  169. {
  170. //locally echo a little "starting session" message
  171. LLUIString session_start = sSessionStartString;
  172. session_start.setArg("[NAME]", getTitle());
  173. mSessionStartMsgPos =
  174. mHistoryEditor->getWText().length();
  175. addHistoryLine(
  176. session_start,
  177. LLUIColorTable::instance().getColor("SystemChatColor"),
  178. false);
  179. }
  180. }
  181. LLFloaterIMPanel::~LLFloaterIMPanel()
  182. {
  183. //delete focus lost callback
  184. mFocusCallbackConnection.disconnect();
  185. }
  186. BOOL LLFloaterIMPanel::postBuild()
  187. {
  188. setVisibleCallback(boost::bind(&LLFloaterIMPanel::onVisibilityChange, this, _2));
  189. mInputEditor = getChild<LLLineEditor>("chat_editor");
  190. mInputEditor->setFocusReceivedCallback( boost::bind(onInputEditorFocusReceived, _1, this) );
  191. mFocusCallbackConnection = mInputEditor->setFocusLostCallback( boost::bind(onInputEditorFocusLost, _1, this));
  192. mInputEditor->setKeystrokeCallback( onInputEditorKeystroke, this );
  193. mInputEditor->setCommitCallback( onCommitChat, this );
  194. mInputEditor->setCommitOnFocusLost( FALSE );
  195. mInputEditor->setRevertOnEsc( FALSE );
  196. mInputEditor->setReplaceNewlinesWithSpaces( FALSE );
  197. childSetAction("profile_callee_btn", onClickProfile, this);
  198. childSetAction("group_info_btn", onClickGroupInfo, this);
  199. childSetAction("start_call_btn", onClickStartCall, this);
  200. childSetAction("end_call_btn", onClickEndCall, this);
  201. childSetAction("send_btn", onClickSend, this);
  202. childSetAction("toggle_active_speakers_btn", onClickToggleActiveSpeakers, this);
  203. childSetAction("moderator_kick_speaker", onKickSpeaker, this);
  204. //LLButton* close_btn = getChild<LLButton>("close_btn");
  205. //close_btn->setClickedCallback(&LLFloaterIMPanel::onClickClose, this);
  206. mHistoryEditor = getChild<LLViewerTextEditor>("im_history");
  207. if ( IM_SESSION_GROUP_START == mDialog )
  208. {
  209. childSetEnabled("profile_btn", FALSE);
  210. }
  211. if(!mProfileButtonEnabled)
  212. {
  213. childSetEnabled("profile_callee_btn", FALSE);
  214. }
  215. sTitleString = getString("title_string");
  216. sTypingStartString = getString("typing_start_string");
  217. sSessionStartString = getString("session_start_string");
  218. if (mSpeakerPanel)
  219. {
  220. mSpeakerPanel->refreshSpeakers();
  221. }
  222. if (mDialog == IM_NOTHING_SPECIAL)
  223. {
  224. childSetAction("mute_btn", onClickMuteVoice, this);
  225. childSetCommitCallback("speaker_volume", onVolumeChange, this);
  226. }
  227. setDefaultBtn("send_btn");
  228. return TRUE;
  229. }
  230. void* LLFloaterIMPanel::createSpeakersPanel(void* data)
  231. {
  232. LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)data;
  233. LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(floaterp->mSessionUUID);
  234. floaterp->mSpeakerPanel = new LLPanelActiveSpeakers(speaker_mgr, TRUE);
  235. return floaterp->mSpeakerPanel;
  236. }
  237. //static
  238. void LLFloaterIMPanel::onClickMuteVoice(void* user_data)
  239. {
  240. LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)user_data;
  241. if (floaterp)
  242. {
  243. BOOL is_muted = LLMuteList::getInstance()->isMuted(floaterp->mOtherParticipantUUID, LLMute::flagVoiceChat);
  244. LLMute mute(floaterp->mOtherParticipantUUID, floaterp->getTitle(), LLMute::AGENT);
  245. if (!is_muted)
  246. {
  247. LLMuteList::getInstance()->add(mute, LLMute::flagVoiceChat);
  248. }
  249. else
  250. {
  251. LLMuteList::getInstance()->remove(mute, LLMute::flagVoiceChat);
  252. }
  253. }
  254. }
  255. //static
  256. void LLFloaterIMPanel::onVolumeChange(LLUICtrl* source, void* user_data)
  257. {
  258. LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)user_data;
  259. if (floaterp)
  260. {
  261. LLVoiceClient::getInstance()->setUserVolume(floaterp->mOtherParticipantUUID, (F32)source->getValue().asReal());
  262. }
  263. }
  264. // virtual
  265. void LLFloaterIMPanel::draw()
  266. {
  267. LLViewerRegion* region = gAgent.getRegion();
  268. BOOL enable_connect = (region && region->getCapability("ChatSessionRequest") != "")
  269. && mSessionInitialized
  270. && LLVoiceClient::getInstance()->voiceEnabled()
  271. && mCallBackEnabled;
  272. // hide/show start call and end call buttons
  273. LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionUUID);
  274. if (!voice_channel)
  275. return;
  276. childSetVisible("end_call_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED);
  277. childSetVisible("start_call_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->getState() < LLVoiceChannel::STATE_CALL_STARTED);
  278. childSetEnabled("start_call_btn", enable_connect);
  279. childSetEnabled("send_btn", !childGetValue("chat_editor").asString().empty());
  280. LLPointer<LLSpeaker> self_speaker;
  281. LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionUUID);
  282. if (speaker_mgr)
  283. {
  284. self_speaker = speaker_mgr->findSpeaker(gAgent.getID());
  285. }
  286. if(!mTextIMPossible)
  287. {
  288. mInputEditor->setEnabled(FALSE);
  289. mInputEditor->setLabel(getString("unavailable_text_label"));
  290. }
  291. else if (self_speaker.notNull() && self_speaker->mModeratorMutedText)
  292. {
  293. mInputEditor->setEnabled(FALSE);
  294. mInputEditor->setLabel(getString("muted_text_label"));
  295. }
  296. else
  297. {
  298. mInputEditor->setEnabled(TRUE);
  299. mInputEditor->setLabel(getString("default_text_label"));
  300. }
  301. // show speakers window when voice first connects
  302. if (mShowSpeakersOnConnect && voice_channel->isActive())
  303. {
  304. childSetVisible("active_speakers_panel", TRUE);
  305. mShowSpeakersOnConnect = FALSE;
  306. }
  307. childSetValue("toggle_active_speakers_btn", childIsVisible("active_speakers_panel"));
  308. if (mTyping)
  309. {
  310. // Time out if user hasn't typed for a while.
  311. if (mLastKeystrokeTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS)
  312. {
  313. setTyping(FALSE);
  314. }
  315. // If we are typing, and it's been a little while, send the
  316. // typing indicator
  317. if (!mSentTypingState
  318. && mFirstKeystrokeTimer.getElapsedTimeF32() > 1.f)
  319. {
  320. sendTypingState(TRUE);
  321. mSentTypingState = TRUE;
  322. }
  323. }
  324. // use embedded panel if available
  325. if (mSpeakerPanel)
  326. {
  327. if (mSpeakerPanel->getVisible())
  328. {
  329. mSpeakerPanel->refreshSpeakers();
  330. }
  331. }
  332. else
  333. {
  334. // refresh volume and mute checkbox
  335. childSetVisible("speaker_volume", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->isActive());
  336. childSetValue("speaker_volume", LLVoiceClient::getInstance()->getUserVolume(mOtherParticipantUUID));
  337. childSetValue("mute_btn", LLMuteList::getInstance()->isMuted(mOtherParticipantUUID, LLMute::flagVoiceChat));
  338. childSetVisible("mute_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->isActive());
  339. }
  340. LLFloater::draw();
  341. }
  342. class LLSessionInviteResponder : public LLHTTPClient::Responder
  343. {
  344. public:
  345. LLSessionInviteResponder(const LLUUID& session_id)
  346. {
  347. mSessionID = session_id;
  348. }
  349. void error(U32 statusNum, const std::string& reason)
  350. {
  351. llinfos << "Error inviting all agents to session" << llendl;
  352. //throw something back to the viewer here?
  353. }
  354. private:
  355. LLUUID mSessionID;
  356. };
  357. BOOL LLFloaterIMPanel::inviteToSession(const std::vector<LLUUID>& ids)
  358. {
  359. LLViewerRegion* region = gAgent.getRegion();
  360. if (!region)
  361. {
  362. return FALSE;
  363. }
  364. S32 count = ids.size();
  365. if( isInviteAllowed() && (count > 0) )
  366. {
  367. llinfos << "LLFloaterIMPanel::inviteToSession() - inviting participants" << llendl;
  368. std::string url = region->getCapability("ChatSessionRequest");
  369. LLSD data;
  370. data["params"] = LLSD::emptyArray();
  371. for (int i = 0; i < count; i++)
  372. {
  373. data["params"].append(ids[i]);
  374. }
  375. data["method"] = "invite";
  376. data["session-id"] = mSessionUUID;
  377. LLHTTPClient::post(
  378. url,
  379. data,
  380. new LLSessionInviteResponder(
  381. mSessionUUID));
  382. }
  383. else
  384. {
  385. llinfos << "LLFloaterIMPanel::inviteToSession -"
  386. << " no need to invite agents for "
  387. << mDialog << llendl;
  388. // successful add, because everyone that needed to get added
  389. // was added.
  390. }
  391. return TRUE;
  392. }
  393. void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, const LLColor4& color, bool log_to_file, const LLUUID& source, const std::string& name)
  394. {
  395. // start tab flashing when receiving im for background session from user
  396. if (source != LLUUID::null)
  397. {
  398. LLMultiFloater* hostp = getHost();
  399. if( !isInVisibleChain()
  400. && hostp
  401. && source != gAgent.getID())
  402. {
  403. hostp->setFloaterFlashing(this, TRUE);
  404. }
  405. }
  406. // Now we're adding the actual line of text, so erase the
  407. // "Foo is typing..." text segment, and the optional timestamp
  408. // if it was present. JC
  409. removeTypingIndicator(NULL);
  410. // Actually add the line
  411. std::string timestring;
  412. bool prepend_newline = true;
  413. if (gSavedSettings.getBOOL("IMShowTimestamps"))
  414. {
  415. timestring = mHistoryEditor->appendTime(prepend_newline);
  416. prepend_newline = false;
  417. }
  418. std::string separator_string(": ");
  419. // 'name' is a sender name that we want to hotlink so that clicking on it opens a profile.
  420. if (!name.empty()) // If name exists, then add it to the front of the message.
  421. {
  422. // Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text.
  423. if (name == SYSTEM_FROM)
  424. {
  425. mHistoryEditor->appendText(name + separator_string, prepend_newline, LLStyle::Params().color(color));
  426. }
  427. else
  428. {
  429. // Convert the name to a hotlink and add to message.
  430. mHistoryEditor->appendText(name + separator_string, prepend_newline, LLStyleMap::instance().lookupAgent(source));
  431. }
  432. prepend_newline = false;
  433. }
  434. mHistoryEditor->appendText(utf8msg, prepend_newline, LLStyle::Params().color(color));
  435. mHistoryEditor->blockUndo();
  436. if (!isInVisibleChain())
  437. {
  438. mNumUnreadMessages++;
  439. }
  440. }
  441. void LLFloaterIMPanel::setInputFocus( BOOL b )
  442. {
  443. mInputEditor->setFocus( b );
  444. }
  445. void LLFloaterIMPanel::selectAll()
  446. {
  447. mInputEditor->selectAll();
  448. }
  449. void LLFloaterIMPanel::selectNone()
  450. {
  451. mInputEditor->deselect();
  452. }
  453. BOOL LLFloaterIMPanel::handleKeyHere( KEY key, MASK mask )
  454. {
  455. BOOL handled = FALSE;
  456. if( KEY_RETURN == key && mask == MASK_NONE)
  457. {
  458. sendMsg();
  459. handled = TRUE;
  460. }
  461. else if ( KEY_ESCAPE == key )
  462. {
  463. handled = TRUE;
  464. gFocusMgr.setKeyboardFocus(NULL);
  465. }
  466. // May need to call base class LLPanel::handleKeyHere if not handled
  467. // in order to tab between buttons. JNC 1.2.2002
  468. return handled;
  469. }
  470. BOOL LLFloaterIMPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
  471. EDragAndDropType cargo_type,
  472. void* cargo_data,
  473. EAcceptance* accept,
  474. std::string& tooltip_msg)
  475. {
  476. if (mDialog == IM_NOTHING_SPECIAL)
  477. {
  478. LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionUUID, drop,
  479. cargo_type, cargo_data, accept);
  480. }
  481. // handle case for dropping calling cards (and folders of calling cards) onto invitation panel for invites
  482. else if (isInviteAllowed())
  483. {
  484. *accept = ACCEPT_NO;
  485. if (cargo_type == DAD_CALLINGCARD)
  486. {
  487. if (dropCallingCard((LLInventoryItem*)cargo_data, drop))
  488. {
  489. *accept = ACCEPT_YES_MULTI;
  490. }
  491. }
  492. else if (cargo_type == DAD_CATEGORY)
  493. {
  494. if (dropCategory((LLInventoryCategory*)cargo_data, drop))
  495. {
  496. *accept = ACCEPT_YES_MULTI;
  497. }
  498. }
  499. }
  500. return TRUE;
  501. }
  502. BOOL LLFloaterIMPanel::dropCallingCard(LLInventoryItem* item, BOOL drop)
  503. {
  504. BOOL rv = isInviteAllowed();
  505. if(rv && item && item->getCreatorUUID().notNull())
  506. {
  507. if(drop)
  508. {
  509. std::vector<LLUUID> ids;
  510. ids.push_back(item->getCreatorUUID());
  511. inviteToSession(ids);
  512. }
  513. }
  514. else
  515. {
  516. // set to false if creator uuid is null.
  517. rv = FALSE;
  518. }
  519. return rv;
  520. }
  521. BOOL LLFloaterIMPanel::dropCategory(LLInventoryCategory* category, BOOL drop)
  522. {
  523. BOOL rv = isInviteAllowed();
  524. if(rv && category)
  525. {
  526. LLInventoryModel::cat_array_t cats;
  527. LLInventoryModel::item_array_t items;
  528. LLUniqueBuddyCollector buddies;
  529. gInventory.collectDescendentsIf(category->getUUID(),
  530. cats,
  531. items,
  532. LLInventoryModel::EXCLUDE_TRASH,
  533. buddies);
  534. S32 count = items.count();
  535. if(count == 0)
  536. {
  537. rv = FALSE;
  538. }
  539. else if(drop)
  540. {
  541. std::vector<LLUUID> ids;
  542. ids.reserve(count);
  543. for(S32 i = 0; i < count; ++i)
  544. {
  545. ids.push_back(items.get(i)->getCreatorUUID());
  546. }
  547. inviteToSession(ids);
  548. }
  549. }
  550. return rv;
  551. }
  552. BOOL LLFloaterIMPanel::isInviteAllowed() const
  553. {
  554. return ( (IM_SESSION_CONFERENCE_START == mDialog)
  555. || (IM_SESSION_INVITE == mDialog) );
  556. }
  557. // static
  558. void LLFloaterIMPanel::onTabClick(void* userdata)
  559. {
  560. LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
  561. self->setInputFocus(TRUE);
  562. }
  563. // static
  564. void LLFloaterIMPanel::onClickProfile( void* userdata )
  565. {
  566. // Bring up the Profile window
  567. LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
  568. if (self->getOtherParticipantID().notNull())
  569. {
  570. LLAvatarActions::showProfile(self->getOtherParticipantID());
  571. }
  572. }
  573. // static
  574. void LLFloaterIMPanel::onClickGroupInfo( void* userdata )
  575. {
  576. // Bring up the Profile window
  577. LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
  578. LLGroupActions::show(self->mSessionUUID);
  579. }
  580. // static
  581. void LLFloaterIMPanel::onClickClose( void* userdata )
  582. {
  583. LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
  584. if(self)
  585. {
  586. self->closeFloater();
  587. }
  588. }
  589. // static
  590. void LLFloaterIMPanel::onClickStartCall(void* userdata)
  591. {
  592. LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
  593. gIMMgr->startCall(self->mSessionUUID);
  594. }
  595. // static
  596. void LLFloaterIMPanel::onClickEndCall(void* userdata)
  597. {
  598. LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
  599. gIMMgr->endCall(self->mSessionUUID);
  600. }
  601. // static
  602. void LLFloaterIMPanel::onClickSend(void* userdata)
  603. {
  604. LLFloaterIMPanel* self = (LLFloaterIMPanel*)userdata;
  605. self->sendMsg();
  606. }
  607. // static
  608. void LLFloaterIMPanel::onClickToggleActiveSpeakers(void* userdata)
  609. {
  610. LLFloaterIMPanel* self = (LLFloaterIMPanel*)userdata;
  611. self->childSetVisible("active_speakers_panel", !self->childIsVisible("active_speakers_panel"));
  612. }
  613. // static
  614. void LLFloaterIMPanel::onCommitChat(LLUICtrl* caller, void* userdata)
  615. {
  616. LLFloaterIMPanel* self= (LLFloaterIMPanel*) userdata;
  617. self->sendMsg();
  618. }
  619. // static
  620. void LLFloaterIMPanel::onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata )
  621. {
  622. LLFloaterIMPanel* self= (LLFloaterIMPanel*) userdata;
  623. self->mHistoryEditor->setCursorAndScrollToEnd();
  624. }
  625. // static
  626. void LLFloaterIMPanel::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata)
  627. {
  628. LLFloaterIMPanel* self = (LLFloaterIMPanel*) userdata;
  629. self->setTyping(FALSE);
  630. }
  631. // static
  632. void LLFloaterIMPanel::onInputEditorKeystroke(LLLineEditor* caller, void* userdata)
  633. {
  634. LLFloaterIMPanel* self = (LLFloaterIMPanel*)userdata;
  635. std::string text = self->mInputEditor->getText();
  636. if (!text.empty())
  637. {
  638. self->setTyping(TRUE);
  639. }
  640. else
  641. {
  642. // Deleting all text counts as stopping typing.
  643. self->setTyping(FALSE);
  644. }
  645. }
  646. // virtual
  647. void LLFloaterIMPanel::onClose(bool app_quitting)
  648. {
  649. setTyping(FALSE);
  650. gIMMgr->leaveSession(mSessionUUID);
  651. // *HACK hide the voice floater
  652. LLFloaterReg::hideInstance("voice_call", mSessionUUID);
  653. }
  654. void LLFloaterIMPanel::onVisibilityChange(const LLSD& new_visibility)
  655. {
  656. if (new_visibility.asBoolean())
  657. {
  658. mNumUnreadMessages = 0;
  659. }
  660. LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionUUID);
  661. if (voice_channel && voice_channel->getState() == LLVoiceChannel::STATE_CONNECTED)
  662. {
  663. if (new_visibility.asBoolean())
  664. LLFloaterReg::showInstance("voice_call", mSessionUUID);
  665. else
  666. LLFloaterReg::hideInstance("voice_call", mSessionUUID);
  667. }
  668. }
  669. void LLFloaterIMPanel::sendMsg()
  670. {
  671. if (!gAgent.isGodlike()
  672. && (mDialog == IM_NOTHING_SPECIAL)
  673. && mOtherParticipantUUID.isNull())
  674. {
  675. llinfos << "Cannot send IM to everyone unless you're a god." << llendl;
  676. return;
  677. }
  678. if (mInputEditor)
  679. {
  680. LLWString text = mInputEditor->getConvertedText();
  681. if(!text.empty())
  682. {
  683. // store sent line in history, duplicates will get filtered
  684. if (mInputEditor) mInputEditor->updateHistory();
  685. // Truncate and convert to UTF8 for transport
  686. std::string utf8_text = wstring_to_utf8str(text);
  687. utf8_text = utf8str_truncate(utf8_text, MAX_MSG_BUF_SIZE - 1);
  688. if ( mSessionInitialized )
  689. {
  690. LLIMModel::sendMessage(utf8_text,
  691. mSessionUUID,
  692. mOtherParticipantUUID,
  693. mDialog);
  694. }
  695. else
  696. {
  697. //queue up the message to send once the session is
  698. //initialized
  699. mQueuedMsgsForInit.append(utf8_text);
  700. }
  701. }
  702. LLViewerStats::getInstance()->incStat(LLViewerStats::ST_IM_COUNT);
  703. mInputEditor->setText(LLStringUtil::null);
  704. }
  705. // Don't need to actually send the typing stop message, the other
  706. // client will infer it from receiving the message.
  707. mTyping = FALSE;
  708. mSentTypingState = TRUE;
  709. }
  710. void LLFloaterIMPanel::processSessionUpdate(const LLSD& session_update)
  711. {
  712. if (
  713. session_update.has("moderated_mode") &&
  714. session_update["moderated_mode"].has("voice") )
  715. {
  716. BOOL voice_moderated = session_update["moderated_mode"]["voice"];
  717. if (voice_moderated)
  718. {
  719. setTitle(mSessionLabel + std::string(" ") + getString("moderated_chat_label"));
  720. }
  721. else
  722. {
  723. setTitle(mSessionLabel);
  724. }
  725. //update the speakers dropdown too, if it's available
  726. if (mSpeakerPanel)
  727. {
  728. mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated);
  729. }
  730. }
  731. }
  732. void LLFloaterIMPanel::sessionInitReplyReceived(const LLUUID& session_id)
  733. {
  734. mSessionUUID = session_id;
  735. mSessionInitialized = TRUE;
  736. //we assume the history editor hasn't moved at all since
  737. //we added the starting session message
  738. //so, we count how many characters to remove
  739. S32 chars_to_remove = mHistoryEditor->getWText().length() -
  740. mSessionStartMsgPos;
  741. mHistoryEditor->removeTextFromEnd(chars_to_remove);
  742. //and now, send the queued msg
  743. LLSD::array_iterator iter;
  744. for ( iter = mQueuedMsgsForInit.beginArray();
  745. iter != mQueuedMsgsForInit.endArray();
  746. ++iter)
  747. {
  748. LLIMModel::sendMessage(
  749. iter->asString(),
  750. mSessionUUID,
  751. mOtherParticipantUUID,
  752. mDialog);
  753. }
  754. }
  755. void LLFloaterIMPanel::setTyping(BOOL typing)
  756. {
  757. LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionUUID);
  758. if (typing)
  759. {
  760. // Every time you type something, reset this timer
  761. mLastKeystrokeTimer.reset();
  762. if (!mTyping)
  763. {
  764. // You just started typing.
  765. mFirstKeystrokeTimer.reset();
  766. // Will send typing state after a short delay.
  767. mSentTypingState = FALSE;
  768. }
  769. if (speaker_mgr)
  770. speaker_mgr->setSpeakerTyping(gAgent.getID(), TRUE);
  771. }
  772. else
  773. {
  774. if (mTyping)
  775. {
  776. // you just stopped typing, send state immediately
  777. sendTypingState(FALSE);
  778. mSentTypingState = TRUE;
  779. }
  780. if (speaker_mgr)
  781. speaker_mgr->setSpeakerTyping(gAgent.getID(), FALSE);
  782. }
  783. mTyping = typing;
  784. }
  785. void LLFloaterIMPanel::sendTypingState(BOOL typing)
  786. {
  787. // Don't want to send typing indicators to multiple people, potentially too
  788. // much network traffic. Only send in person-to-person IMs.
  789. if (mDialog != IM_NOTHING_SPECIAL) return;
  790. LLIMModel::instance().sendTypingState(mSessionUUID, mOtherParticipantUUID, typing);
  791. }
  792. void LLFloaterIMPanel::processIMTyping(const LLIMInfo* im_info, BOOL typing)
  793. {
  794. if (typing)
  795. {
  796. // other user started typing
  797. addTypingIndicator(im_info->mName);
  798. }
  799. else
  800. {
  801. // other user stopped typing
  802. removeTypingIndicator(im_info);
  803. }
  804. }
  805. void LLFloaterIMPanel::addTypingIndicator(const std::string &name)
  806. {
  807. // we may have lost a "stop-typing" packet, don't add it twice
  808. if (!mOtherTyping)
  809. {
  810. mTypingLineStartIndex = mHistoryEditor->getWText().length();
  811. LLUIString typing_start = sTypingStartString;
  812. typing_start.setArg("[NAME]", name);
  813. addHistoryLine(typing_start, LLUIColorTable::instance().getColor("SystemChatColor"), false);
  814. mOtherTypingName = name;
  815. mOtherTyping = TRUE;
  816. }
  817. // MBW -- XXX -- merge from release broke this (argument to this function changed from an LLIMInfo to a name)
  818. // Richard will fix.
  819. // mSpeakers->setSpeakerTyping(im_info->mFromID, TRUE);
  820. }
  821. void LLFloaterIMPanel::removeTypingIndicator(const LLIMInfo* im_info)
  822. {
  823. if (mOtherTyping)
  824. {
  825. // Must do this first, otherwise addHistoryLine calls us again.
  826. mOtherTyping = FALSE;
  827. S32 chars_to_remove = mHistoryEditor->getWText().length() - mTypingLineStartIndex;
  828. mHistoryEditor->removeTextFromEnd(chars_to_remove);
  829. if (im_info)
  830. {
  831. LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionUUID);
  832. if (speaker_mgr)
  833. {
  834. speaker_mgr->setSpeakerTyping(im_info->mFromID, FALSE);
  835. }
  836. }
  837. }
  838. }
  839. //static
  840. void LLFloaterIMPanel::onKickSpeaker(void* user_data)
  841. {
  842. }