PageRenderTime 51ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/indra/newview/llimfloater.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1193 lines | 898 code | 175 blank | 120 comment | 176 complexity | 08da5b8084c23d439ef6b7195b5c7be5 MD5 | raw file
Possible License(s): LGPL-2.1
  1. /**
  2. * @file llimfloater.cpp
  3. * @brief LLIMFloater class definition
  4. *
  5. * $LicenseInfo:firstyear=2009&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 "llimfloater.h"
  28. #include "llnotificationsutil.h"
  29. #include "llagent.h"
  30. #include "llappviewer.h"
  31. #include "llavatarnamecache.h"
  32. #include "llbutton.h"
  33. #include "llchannelmanager.h"
  34. #include "llchiclet.h"
  35. #include "llchicletbar.h"
  36. #include "llfloaterreg.h"
  37. #include "llimfloatercontainer.h" // to replace separate IM Floaters with multifloater container
  38. #include "llinventoryfunctions.h"
  39. #include "lllayoutstack.h"
  40. #include "lllineeditor.h"
  41. #include "lllogchat.h"
  42. #include "llpanelimcontrolpanel.h"
  43. #include "llscreenchannel.h"
  44. #include "llsyswellwindow.h"
  45. #include "lltrans.h"
  46. #include "llchathistory.h"
  47. #include "llnotifications.h"
  48. #include "llviewerwindow.h"
  49. #include "llvoicechannel.h"
  50. #include "lltransientfloatermgr.h"
  51. #include "llinventorymodel.h"
  52. #include "llrootview.h"
  53. #include "llspeakers.h"
  54. #include "llviewerchat.h"
  55. LLIMFloater::LLIMFloater(const LLUUID& session_id)
  56. : LLTransientDockableFloater(NULL, true, session_id),
  57. mControlPanel(NULL),
  58. mSessionID(session_id),
  59. mLastMessageIndex(-1),
  60. mDialog(IM_NOTHING_SPECIAL),
  61. mChatHistory(NULL),
  62. mInputEditor(NULL),
  63. mSavedTitle(),
  64. mTypingStart(),
  65. mShouldSendTypingState(false),
  66. mMeTyping(false),
  67. mOtherTyping(false),
  68. mTypingTimer(),
  69. mTypingTimeoutTimer(),
  70. mPositioned(false),
  71. mSessionInitialized(false)
  72. {
  73. LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(mSessionID);
  74. if (im_session)
  75. {
  76. mSessionInitialized = im_session->mSessionInitialized;
  77. mDialog = im_session->mType;
  78. switch(mDialog){
  79. case IM_NOTHING_SPECIAL:
  80. case IM_SESSION_P2P_INVITE:
  81. mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelIMControl, this);
  82. break;
  83. case IM_SESSION_CONFERENCE_START:
  84. mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelAdHocControl, this);
  85. break;
  86. case IM_SESSION_GROUP_START:
  87. mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelGroupControl, this);
  88. break;
  89. case IM_SESSION_INVITE:
  90. if (gAgent.isInGroup(mSessionID))
  91. {
  92. mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelGroupControl, this);
  93. }
  94. else
  95. {
  96. mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelAdHocControl, this);
  97. }
  98. break;
  99. default: break;
  100. }
  101. }
  102. setOverlapsScreenChannel(true);
  103. LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this);
  104. setDocked(true);
  105. }
  106. void LLIMFloater::onFocusLost()
  107. {
  108. LLIMModel::getInstance()->resetActiveSessionID();
  109. LLChicletBar::getInstance()->getChicletPanel()->setChicletToggleState(mSessionID, false);
  110. }
  111. void LLIMFloater::onFocusReceived()
  112. {
  113. LLIMModel::getInstance()->setActiveSessionID(mSessionID);
  114. LLChicletBar::getInstance()->getChicletPanel()->setChicletToggleState(mSessionID, true);
  115. if (getVisible())
  116. {
  117. LLIMModel::instance().sendNoUnreadMessages(mSessionID);
  118. }
  119. }
  120. // virtual
  121. void LLIMFloater::onClose(bool app_quitting)
  122. {
  123. setTyping(false);
  124. // The source of much argument and design thrashing
  125. // Should the window hide or the session close when the X is clicked?
  126. //
  127. // Last change:
  128. // EXT-3516 X Button should end IM session, _ button should hide
  129. gIMMgr->leaveSession(mSessionID);
  130. }
  131. /* static */
  132. void LLIMFloater::newIMCallback(const LLSD& data){
  133. if (data["num_unread"].asInteger() > 0 || data["from_id"].asUUID().isNull())
  134. {
  135. LLUUID session_id = data["session_id"].asUUID();
  136. LLIMFloater* floater = LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id);
  137. if (floater == NULL) return;
  138. // update if visible, otherwise will be updated when opened
  139. if (floater->getVisible())
  140. {
  141. floater->updateMessages();
  142. }
  143. }
  144. }
  145. void LLIMFloater::onVisibilityChange(const LLSD& new_visibility)
  146. {
  147. bool visible = new_visibility.asBoolean();
  148. LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID);
  149. if (visible && voice_channel &&
  150. voice_channel->getState() == LLVoiceChannel::STATE_CONNECTED)
  151. {
  152. LLFloaterReg::showInstance("voice_call", mSessionID);
  153. }
  154. else
  155. {
  156. LLFloaterReg::hideInstance("voice_call", mSessionID);
  157. }
  158. }
  159. void LLIMFloater::onSendMsg( LLUICtrl* ctrl, void* userdata )
  160. {
  161. LLIMFloater* self = (LLIMFloater*) userdata;
  162. self->sendMsg();
  163. self->setTyping(false);
  164. }
  165. void LLIMFloater::sendMsg()
  166. {
  167. if (!gAgent.isGodlike()
  168. && (mDialog == IM_NOTHING_SPECIAL)
  169. && mOtherParticipantUUID.isNull())
  170. {
  171. llinfos << "Cannot send IM to everyone unless you're a god." << llendl;
  172. return;
  173. }
  174. if (mInputEditor)
  175. {
  176. LLWString text = mInputEditor->getConvertedText();
  177. if(!text.empty())
  178. {
  179. // Truncate and convert to UTF8 for transport
  180. std::string utf8_text = wstring_to_utf8str(text);
  181. utf8_text = utf8str_truncate(utf8_text, MAX_MSG_BUF_SIZE - 1);
  182. if (mSessionInitialized)
  183. {
  184. LLIMModel::sendMessage(utf8_text, mSessionID,
  185. mOtherParticipantUUID,mDialog);
  186. }
  187. else
  188. {
  189. //queue up the message to send once the session is initialized
  190. mQueuedMsgsForInit.append(utf8_text);
  191. }
  192. mInputEditor->setText(LLStringUtil::null);
  193. updateMessages();
  194. }
  195. }
  196. }
  197. LLIMFloater::~LLIMFloater()
  198. {
  199. LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this);
  200. }
  201. //virtual
  202. BOOL LLIMFloater::postBuild()
  203. {
  204. const LLUUID& other_party_id = LLIMModel::getInstance()->getOtherParticipantID(mSessionID);
  205. if (other_party_id.notNull())
  206. {
  207. mOtherParticipantUUID = other_party_id;
  208. }
  209. mControlPanel->setSessionId(mSessionID);
  210. mControlPanel->getParent()->setVisible(gSavedSettings.getBOOL("IMShowControlPanel"));
  211. LLButton* slide_left = getChild<LLButton>("slide_left_btn");
  212. slide_left->setVisible(mControlPanel->getParent()->getVisible());
  213. slide_left->setClickedCallback(boost::bind(&LLIMFloater::onSlide, this));
  214. LLButton* slide_right = getChild<LLButton>("slide_right_btn");
  215. slide_right->setVisible(!mControlPanel->getParent()->getVisible());
  216. slide_right->setClickedCallback(boost::bind(&LLIMFloater::onSlide, this));
  217. mInputEditor = getChild<LLLineEditor>("chat_editor");
  218. mInputEditor->setMaxTextLength(1023);
  219. // enable line history support for instant message bar
  220. mInputEditor->setEnableLineHistory(TRUE);
  221. LLFontGL* font = LLViewerChat::getChatFont();
  222. mInputEditor->setFont(font);
  223. mInputEditor->setFocusReceivedCallback( boost::bind(onInputEditorFocusReceived, _1, this) );
  224. mInputEditor->setFocusLostCallback( boost::bind(onInputEditorFocusLost, _1, this) );
  225. mInputEditor->setKeystrokeCallback( onInputEditorKeystroke, this );
  226. mInputEditor->setCommitOnFocusLost( FALSE );
  227. mInputEditor->setRevertOnEsc( FALSE );
  228. mInputEditor->setReplaceNewlinesWithSpaces( FALSE );
  229. mInputEditor->setPassDelete( TRUE );
  230. childSetCommitCallback("chat_editor", onSendMsg, this);
  231. mChatHistory = getChild<LLChatHistory>("chat_history");
  232. setDocked(true);
  233. mTypingStart = LLTrans::getString("IM_typing_start_string");
  234. // Disable input editor if session cannot accept text
  235. LLIMModel::LLIMSession* im_session =
  236. LLIMModel::instance().findIMSession(mSessionID);
  237. if( im_session && !im_session->mTextIMPossible )
  238. {
  239. mInputEditor->setEnabled(FALSE);
  240. mInputEditor->setLabel(LLTrans::getString("IM_unavailable_text_label"));
  241. }
  242. if ( im_session && im_session->isP2PSessionType())
  243. {
  244. // look up display name for window title
  245. LLAvatarNameCache::get(im_session->mOtherParticipantID,
  246. boost::bind(&LLIMFloater::onAvatarNameCache,
  247. this, _1, _2));
  248. }
  249. else
  250. {
  251. std::string session_name(LLIMModel::instance().getName(mSessionID));
  252. updateSessionName(session_name, session_name);
  253. }
  254. //*TODO if session is not initialized yet, add some sort of a warning message like "starting session...blablabla"
  255. //see LLFloaterIMPanel for how it is done (IB)
  256. if(isChatMultiTab())
  257. {
  258. return LLFloater::postBuild();
  259. }
  260. else
  261. {
  262. return LLDockableFloater::postBuild();
  263. }
  264. }
  265. void LLIMFloater::updateSessionName(const std::string& ui_title,
  266. const std::string& ui_label)
  267. {
  268. mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + ui_label);
  269. setTitle(ui_title);
  270. }
  271. void LLIMFloater::onAvatarNameCache(const LLUUID& agent_id,
  272. const LLAvatarName& av_name)
  273. {
  274. // Use display name only for labels, as the extended name will be in the
  275. // floater title
  276. std::string ui_title = av_name.getCompleteName();
  277. updateSessionName(ui_title, av_name.mDisplayName);
  278. mTypingStart.setArg("[NAME]", ui_title);
  279. }
  280. // virtual
  281. void LLIMFloater::draw()
  282. {
  283. if ( mMeTyping )
  284. {
  285. // Time out if user hasn't typed for a while.
  286. if ( mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS )
  287. {
  288. setTyping(false);
  289. }
  290. }
  291. LLTransientDockableFloater::draw();
  292. }
  293. // static
  294. void* LLIMFloater::createPanelIMControl(void* userdata)
  295. {
  296. LLIMFloater *self = (LLIMFloater*)userdata;
  297. self->mControlPanel = new LLPanelIMControlPanel();
  298. self->mControlPanel->setXMLFilename("panel_im_control_panel.xml");
  299. return self->mControlPanel;
  300. }
  301. // static
  302. void* LLIMFloater::createPanelGroupControl(void* userdata)
  303. {
  304. LLIMFloater *self = (LLIMFloater*)userdata;
  305. self->mControlPanel = new LLPanelGroupControlPanel(self->mSessionID);
  306. self->mControlPanel->setXMLFilename("panel_group_control_panel.xml");
  307. return self->mControlPanel;
  308. }
  309. // static
  310. void* LLIMFloater::createPanelAdHocControl(void* userdata)
  311. {
  312. LLIMFloater *self = (LLIMFloater*)userdata;
  313. self->mControlPanel = new LLPanelAdHocControlPanel(self->mSessionID);
  314. self->mControlPanel->setXMLFilename("panel_adhoc_control_panel.xml");
  315. return self->mControlPanel;
  316. }
  317. void LLIMFloater::onSlide()
  318. {
  319. mControlPanel->getParent()->setVisible(!mControlPanel->getParent()->getVisible());
  320. gSavedSettings.setBOOL("IMShowControlPanel", mControlPanel->getParent()->getVisible());
  321. getChild<LLButton>("slide_left_btn")->setVisible(mControlPanel->getParent()->getVisible());
  322. getChild<LLButton>("slide_right_btn")->setVisible(!mControlPanel->getParent()->getVisible());
  323. }
  324. //static
  325. LLIMFloater* LLIMFloater::show(const LLUUID& session_id)
  326. {
  327. closeHiddenIMToasts();
  328. if (!gIMMgr->hasSession(session_id)) return NULL;
  329. if(!isChatMultiTab())
  330. {
  331. //hide all
  332. LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel");
  333. for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin();
  334. iter != inst_list.end(); ++iter)
  335. {
  336. LLIMFloater* floater = dynamic_cast<LLIMFloater*>(*iter);
  337. if (floater && floater->isDocked())
  338. {
  339. floater->setVisible(false);
  340. }
  341. }
  342. }
  343. bool exist = findInstance(session_id);
  344. LLIMFloater* floater = getInstance(session_id);
  345. if (!floater) return NULL;
  346. if(isChatMultiTab())
  347. {
  348. LLIMFloaterContainer* floater_container = LLIMFloaterContainer::getInstance();
  349. // do not add existed floaters to avoid adding torn off instances
  350. if (!exist)
  351. {
  352. // LLTabContainer::eInsertionPoint i_pt = user_initiated ? LLTabContainer::RIGHT_OF_CURRENT : LLTabContainer::END;
  353. // TODO: mantipov: use LLTabContainer::RIGHT_OF_CURRENT if it exists
  354. LLTabContainer::eInsertionPoint i_pt = LLTabContainer::END;
  355. if (floater_container)
  356. {
  357. floater_container->addFloater(floater, TRUE, i_pt);
  358. }
  359. }
  360. floater->openFloater(floater->getKey());
  361. }
  362. else
  363. {
  364. // Docking may move chat window, hide it before moving, or user will see how window "jumps"
  365. floater->setVisible(false);
  366. if (floater->getDockControl() == NULL)
  367. {
  368. LLChiclet* chiclet =
  369. LLChicletBar::getInstance()->getChicletPanel()->findChiclet<LLChiclet>(
  370. session_id);
  371. if (chiclet == NULL)
  372. {
  373. llerror("Dock chiclet for LLIMFloater doesn't exists", 0);
  374. }
  375. else
  376. {
  377. LLChicletBar::getInstance()->getChicletPanel()->scrollToChiclet(chiclet);
  378. }
  379. floater->setDockControl(new LLDockControl(chiclet, floater, floater->getDockTongue(),
  380. LLDockControl::BOTTOM));
  381. }
  382. // window is positioned, now we can show it.
  383. }
  384. floater->setVisible(TRUE);
  385. return floater;
  386. }
  387. void LLIMFloater::setDocked(bool docked, bool pop_on_undock)
  388. {
  389. // update notification channel state
  390. LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*>
  391. (LLNotificationsUI::LLChannelManager::getInstance()->
  392. findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID"))));
  393. if(!isChatMultiTab())
  394. {
  395. LLTransientDockableFloater::setDocked(docked, pop_on_undock);
  396. }
  397. // update notification channel state
  398. if(channel)
  399. {
  400. channel->updateShowToastsState();
  401. channel->redrawToasts();
  402. }
  403. }
  404. void LLIMFloater::setVisible(BOOL visible)
  405. {
  406. LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*>
  407. (LLNotificationsUI::LLChannelManager::getInstance()->
  408. findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID"))));
  409. LLTransientDockableFloater::setVisible(visible);
  410. // update notification channel state
  411. if(channel)
  412. {
  413. channel->updateShowToastsState();
  414. channel->redrawToasts();
  415. }
  416. BOOL is_minimized = visible && isChatMultiTab()
  417. ? LLIMFloaterContainer::getInstance()->isMinimized()
  418. : !visible;
  419. if (!is_minimized && mChatHistory && mInputEditor)
  420. {
  421. //only if floater was construced and initialized from xml
  422. updateMessages();
  423. //prevent stealing focus when opening a background IM tab (EXT-5387, checking focus for EXT-6781)
  424. if (!isChatMultiTab() || hasFocus())
  425. {
  426. mInputEditor->setFocus(TRUE);
  427. }
  428. }
  429. if(!visible)
  430. {
  431. LLIMChiclet* chiclet = LLChicletBar::getInstance()->getChicletPanel()->findChiclet<LLIMChiclet>(mSessionID);
  432. if(chiclet)
  433. {
  434. chiclet->setToggleState(false);
  435. }
  436. }
  437. }
  438. BOOL LLIMFloater::getVisible()
  439. {
  440. if(isChatMultiTab())
  441. {
  442. LLIMFloaterContainer* im_container = LLIMFloaterContainer::getInstance();
  443. // Treat inactive floater as invisible.
  444. bool is_active = im_container->getActiveFloater() == this;
  445. //torn off floater is always inactive
  446. if (!is_active && getHost() != im_container)
  447. {
  448. return LLTransientDockableFloater::getVisible();
  449. }
  450. // getVisible() returns TRUE when Tabbed IM window is minimized.
  451. return is_active && !im_container->isMinimized() && im_container->getVisible();
  452. }
  453. else
  454. {
  455. return LLTransientDockableFloater::getVisible();
  456. }
  457. }
  458. //static
  459. bool LLIMFloater::toggle(const LLUUID& session_id)
  460. {
  461. if(!isChatMultiTab())
  462. {
  463. LLIMFloater* floater = LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id);
  464. if (floater && floater->getVisible() && floater->hasFocus())
  465. {
  466. // clicking on chiclet to close floater just hides it to maintain existing
  467. // scroll/text entry state
  468. floater->setVisible(false);
  469. return false;
  470. }
  471. else if(floater && (!floater->isDocked() || floater->getVisible() && !floater->hasFocus()))
  472. {
  473. floater->setVisible(TRUE);
  474. floater->setFocus(TRUE);
  475. return true;
  476. }
  477. }
  478. // ensure the list of messages is updated when floater is made visible
  479. show(session_id);
  480. return true;
  481. }
  482. //static
  483. LLIMFloater* LLIMFloater::findInstance(const LLUUID& session_id)
  484. {
  485. return LLFloaterReg::findTypedInstance<LLIMFloater>("impanel", session_id);
  486. }
  487. LLIMFloater* LLIMFloater::getInstance(const LLUUID& session_id)
  488. {
  489. return LLFloaterReg::getTypedInstance<LLIMFloater>("impanel", session_id);
  490. }
  491. void LLIMFloater::sessionInitReplyReceived(const LLUUID& im_session_id)
  492. {
  493. mSessionInitialized = true;
  494. //will be different only for an ad-hoc im session
  495. if (mSessionID != im_session_id)
  496. {
  497. mSessionID = im_session_id;
  498. setKey(im_session_id);
  499. mControlPanel->setSessionId(im_session_id);
  500. }
  501. // updating "Call" button from group control panel here to enable it without placing into draw() (EXT-4796)
  502. if(gAgent.isInGroup(im_session_id))
  503. {
  504. mControlPanel->updateCallButton();
  505. }
  506. //*TODO here we should remove "starting session..." warning message if we added it in postBuild() (IB)
  507. //need to send delayed messaged collected while waiting for session initialization
  508. if (!mQueuedMsgsForInit.size()) return;
  509. LLSD::array_iterator iter;
  510. for ( iter = mQueuedMsgsForInit.beginArray();
  511. iter != mQueuedMsgsForInit.endArray();
  512. ++iter)
  513. {
  514. LLIMModel::sendMessage(iter->asString(), mSessionID,
  515. mOtherParticipantUUID, mDialog);
  516. }
  517. }
  518. void LLIMFloater::updateMessages()
  519. {
  520. bool use_plain_text_chat_history = gSavedSettings.getBOOL("PlainTextChatHistory");
  521. std::list<LLSD> messages;
  522. // we shouldn't reset unread message counters if IM floater doesn't have focus
  523. if (hasFocus())
  524. {
  525. LLIMModel::instance().getMessages(mSessionID, messages, mLastMessageIndex+1);
  526. }
  527. else
  528. {
  529. LLIMModel::instance().getMessagesSilently(mSessionID, messages, mLastMessageIndex+1);
  530. }
  531. if (messages.size())
  532. {
  533. LLSD chat_args;
  534. chat_args["use_plain_text_chat_history"] = use_plain_text_chat_history;
  535. std::ostringstream message;
  536. std::list<LLSD>::const_reverse_iterator iter = messages.rbegin();
  537. std::list<LLSD>::const_reverse_iterator iter_end = messages.rend();
  538. for (; iter != iter_end; ++iter)
  539. {
  540. LLSD msg = *iter;
  541. std::string time = msg["time"].asString();
  542. LLUUID from_id = msg["from_id"].asUUID();
  543. std::string from = msg["from"].asString();
  544. std::string message = msg["message"].asString();
  545. bool is_history = msg["is_history"].asBoolean();
  546. LLChat chat;
  547. chat.mFromID = from_id;
  548. chat.mSessionID = mSessionID;
  549. chat.mFromName = from;
  550. chat.mTimeStr = time;
  551. chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle;
  552. // process offer notification
  553. if (msg.has("notification_id"))
  554. {
  555. chat.mNotifId = msg["notification_id"].asUUID();
  556. // if notification exists - embed it
  557. if (LLNotificationsUtil::find(chat.mNotifId) != NULL)
  558. {
  559. // remove embedded notification from channel
  560. LLNotificationsUI::LLScreenChannel* channel = static_cast<LLNotificationsUI::LLScreenChannel*>
  561. (LLNotificationsUI::LLChannelManager::getInstance()->
  562. findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID"))));
  563. if (getVisible())
  564. {
  565. // toast will be automatically closed since it is not storable toast
  566. channel->hideToast(chat.mNotifId);
  567. }
  568. }
  569. // if notification doesn't exist - try to use next message which should be log entry
  570. else
  571. {
  572. continue;
  573. }
  574. }
  575. //process text message
  576. else
  577. {
  578. chat.mText = message;
  579. }
  580. mChatHistory->appendMessage(chat, chat_args);
  581. mLastMessageIndex = msg["index"].asInteger();
  582. // if it is a notification - next message is a notification history log, so skip it
  583. if (chat.mNotifId.notNull() && LLNotificationsUtil::find(chat.mNotifId) != NULL)
  584. {
  585. if (++iter == iter_end)
  586. {
  587. break;
  588. }
  589. else
  590. {
  591. mLastMessageIndex++;
  592. }
  593. }
  594. }
  595. }
  596. }
  597. void LLIMFloater::reloadMessages()
  598. {
  599. mChatHistory->clear();
  600. mLastMessageIndex = -1;
  601. updateMessages();
  602. }
  603. // static
  604. void LLIMFloater::onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata )
  605. {
  606. LLIMFloater* self= (LLIMFloater*) userdata;
  607. // Allow enabling the LLIMFloater input editor only if session can accept text
  608. LLIMModel::LLIMSession* im_session =
  609. LLIMModel::instance().findIMSession(self->mSessionID);
  610. //TODO: While disabled lllineeditor can receive focus we need to check if it is enabled (EK)
  611. if( im_session && im_session->mTextIMPossible && self->mInputEditor->getEnabled())
  612. {
  613. //in disconnected state IM input editor should be disabled
  614. self->mInputEditor->setEnabled(!gDisconnected);
  615. }
  616. }
  617. // static
  618. void LLIMFloater::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata)
  619. {
  620. LLIMFloater* self = (LLIMFloater*) userdata;
  621. self->setTyping(false);
  622. }
  623. // static
  624. void LLIMFloater::onInputEditorKeystroke(LLLineEditor* caller, void* userdata)
  625. {
  626. LLIMFloater* self = (LLIMFloater*)userdata;
  627. std::string text = self->mInputEditor->getText();
  628. if (!text.empty())
  629. {
  630. self->setTyping(true);
  631. }
  632. else
  633. {
  634. // Deleting all text counts as stopping typing.
  635. self->setTyping(false);
  636. }
  637. }
  638. void LLIMFloater::setTyping(bool typing)
  639. {
  640. if ( typing )
  641. {
  642. // Started or proceeded typing, reset the typing timeout timer
  643. mTypingTimeoutTimer.reset();
  644. }
  645. if ( mMeTyping != typing )
  646. {
  647. // Typing state is changed
  648. mMeTyping = typing;
  649. // So, should send current state
  650. mShouldSendTypingState = true;
  651. // In case typing is started, send state after some delay
  652. mTypingTimer.reset();
  653. }
  654. // Don't want to send typing indicators to multiple people, potentially too
  655. // much network traffic. Only send in person-to-person IMs.
  656. if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL )
  657. {
  658. if ( mMeTyping )
  659. {
  660. if ( mTypingTimer.getElapsedTimeF32() > 1.f )
  661. {
  662. // Still typing, send 'start typing' notification
  663. LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, TRUE);
  664. mShouldSendTypingState = false;
  665. }
  666. }
  667. else
  668. {
  669. // Send 'stop typing' notification immediately
  670. LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, FALSE);
  671. mShouldSendTypingState = false;
  672. }
  673. }
  674. LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
  675. if (speaker_mgr)
  676. speaker_mgr->setSpeakerTyping(gAgent.getID(), FALSE);
  677. }
  678. void LLIMFloater::processIMTyping(const LLIMInfo* im_info, BOOL typing)
  679. {
  680. if ( typing )
  681. {
  682. // other user started typing
  683. addTypingIndicator(im_info);
  684. }
  685. else
  686. {
  687. // other user stopped typing
  688. removeTypingIndicator(im_info);
  689. }
  690. }
  691. void LLIMFloater::processAgentListUpdates(const LLSD& body)
  692. {
  693. if ( !body.isMap() ) return;
  694. if ( body.has("agent_updates") && body["agent_updates"].isMap() )
  695. {
  696. LLSD agent_data = body["agent_updates"].get(gAgentID.asString());
  697. if (agent_data.isMap() && agent_data.has("info"))
  698. {
  699. LLSD agent_info = agent_data["info"];
  700. if (agent_info.has("mutes"))
  701. {
  702. BOOL moderator_muted_text = agent_info["mutes"]["text"].asBoolean();
  703. mInputEditor->setEnabled(!moderator_muted_text);
  704. std::string label;
  705. if (moderator_muted_text)
  706. label = LLTrans::getString("IM_muted_text_label");
  707. else
  708. label = LLTrans::getString("IM_to_label") + " " + LLIMModel::instance().getName(mSessionID);
  709. mInputEditor->setLabel(label);
  710. if (moderator_muted_text)
  711. LLNotificationsUtil::add("TextChatIsMutedByModerator");
  712. }
  713. }
  714. }
  715. }
  716. void LLIMFloater::updateChatHistoryStyle()
  717. {
  718. mChatHistory->clear();
  719. mLastMessageIndex = -1;
  720. updateMessages();
  721. }
  722. void LLIMFloater::processChatHistoryStyleUpdate(const LLSD& newvalue)
  723. {
  724. LLFontGL* font = LLViewerChat::getChatFont();
  725. LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel");
  726. for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin();
  727. iter != inst_list.end(); ++iter)
  728. {
  729. LLIMFloater* floater = dynamic_cast<LLIMFloater*>(*iter);
  730. if (floater)
  731. {
  732. floater->updateChatHistoryStyle();
  733. floater->mInputEditor->setFont(font);
  734. }
  735. }
  736. }
  737. void LLIMFloater::processSessionUpdate(const LLSD& session_update)
  738. {
  739. // *TODO : verify following code when moderated mode will be implemented
  740. if ( false && session_update.has("moderated_mode") &&
  741. session_update["moderated_mode"].has("voice") )
  742. {
  743. BOOL voice_moderated = session_update["moderated_mode"]["voice"];
  744. const std::string session_label = LLIMModel::instance().getName(mSessionID);
  745. if (voice_moderated)
  746. {
  747. setTitle(session_label + std::string(" ") + LLTrans::getString("IM_moderated_chat_label"));
  748. }
  749. else
  750. {
  751. setTitle(session_label);
  752. }
  753. // *TODO : uncomment this when/if LLPanelActiveSpeakers panel will be added
  754. //update the speakers dropdown too
  755. //mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated);
  756. }
  757. }
  758. BOOL LLIMFloater::handleDragAndDrop(S32 x, S32 y, MASK mask,
  759. BOOL drop, EDragAndDropType cargo_type,
  760. void *cargo_data, EAcceptance *accept,
  761. std::string& tooltip_msg)
  762. {
  763. if (mDialog == IM_NOTHING_SPECIAL)
  764. {
  765. LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionID, drop,
  766. cargo_type, cargo_data, accept);
  767. }
  768. // handle case for dropping calling cards (and folders of calling cards) onto invitation panel for invites
  769. else if (isInviteAllowed())
  770. {
  771. *accept = ACCEPT_NO;
  772. if (cargo_type == DAD_CALLINGCARD)
  773. {
  774. if (dropCallingCard((LLInventoryItem*)cargo_data, drop))
  775. {
  776. *accept = ACCEPT_YES_MULTI;
  777. }
  778. }
  779. else if (cargo_type == DAD_CATEGORY)
  780. {
  781. if (dropCategory((LLInventoryCategory*)cargo_data, drop))
  782. {
  783. *accept = ACCEPT_YES_MULTI;
  784. }
  785. }
  786. }
  787. return TRUE;
  788. }
  789. BOOL LLIMFloater::dropCallingCard(LLInventoryItem* item, BOOL drop)
  790. {
  791. BOOL rv = isInviteAllowed();
  792. if(rv && item && item->getCreatorUUID().notNull())
  793. {
  794. if(drop)
  795. {
  796. uuid_vec_t ids;
  797. ids.push_back(item->getCreatorUUID());
  798. inviteToSession(ids);
  799. }
  800. }
  801. else
  802. {
  803. // set to false if creator uuid is null.
  804. rv = FALSE;
  805. }
  806. return rv;
  807. }
  808. BOOL LLIMFloater::dropCategory(LLInventoryCategory* category, BOOL drop)
  809. {
  810. BOOL rv = isInviteAllowed();
  811. if(rv && category)
  812. {
  813. LLInventoryModel::cat_array_t cats;
  814. LLInventoryModel::item_array_t items;
  815. LLUniqueBuddyCollector buddies;
  816. gInventory.collectDescendentsIf(category->getUUID(),
  817. cats,
  818. items,
  819. LLInventoryModel::EXCLUDE_TRASH,
  820. buddies);
  821. S32 count = items.count();
  822. if(count == 0)
  823. {
  824. rv = FALSE;
  825. }
  826. else if(drop)
  827. {
  828. uuid_vec_t ids;
  829. ids.reserve(count);
  830. for(S32 i = 0; i < count; ++i)
  831. {
  832. ids.push_back(items.get(i)->getCreatorUUID());
  833. }
  834. inviteToSession(ids);
  835. }
  836. }
  837. return rv;
  838. }
  839. BOOL LLIMFloater::isInviteAllowed() const
  840. {
  841. return ( (IM_SESSION_CONFERENCE_START == mDialog)
  842. || (IM_SESSION_INVITE == mDialog) );
  843. }
  844. class LLSessionInviteResponder : public LLHTTPClient::Responder
  845. {
  846. public:
  847. LLSessionInviteResponder(const LLUUID& session_id)
  848. {
  849. mSessionID = session_id;
  850. }
  851. void error(U32 statusNum, const std::string& reason)
  852. {
  853. llinfos << "Error inviting all agents to session" << llendl;
  854. //throw something back to the viewer here?
  855. }
  856. private:
  857. LLUUID mSessionID;
  858. };
  859. BOOL LLIMFloater::inviteToSession(const uuid_vec_t& ids)
  860. {
  861. LLViewerRegion* region = gAgent.getRegion();
  862. if (!region)
  863. {
  864. return FALSE;
  865. }
  866. S32 count = ids.size();
  867. if( isInviteAllowed() && (count > 0) )
  868. {
  869. llinfos << "LLIMFloater::inviteToSession() - inviting participants" << llendl;
  870. std::string url = region->getCapability("ChatSessionRequest");
  871. LLSD data;
  872. data["params"] = LLSD::emptyArray();
  873. for (int i = 0; i < count; i++)
  874. {
  875. data["params"].append(ids[i]);
  876. }
  877. data["method"] = "invite";
  878. data["session-id"] = mSessionID;
  879. LLHTTPClient::post(
  880. url,
  881. data,
  882. new LLSessionInviteResponder(
  883. mSessionID));
  884. }
  885. else
  886. {
  887. llinfos << "LLIMFloater::inviteToSession -"
  888. << " no need to invite agents for "
  889. << mDialog << llendl;
  890. // successful add, because everyone that needed to get added
  891. // was added.
  892. }
  893. return TRUE;
  894. }
  895. void LLIMFloater::addTypingIndicator(const LLIMInfo* im_info)
  896. {
  897. // We may have lost a "stop-typing" packet, don't add it twice
  898. if ( im_info && !mOtherTyping )
  899. {
  900. mOtherTyping = true;
  901. // Save and set new title
  902. mSavedTitle = getTitle();
  903. setTitle (mTypingStart);
  904. // Update speaker
  905. LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
  906. if ( speaker_mgr )
  907. {
  908. speaker_mgr->setSpeakerTyping(im_info->mFromID, TRUE);
  909. }
  910. }
  911. }
  912. void LLIMFloater::removeTypingIndicator(const LLIMInfo* im_info)
  913. {
  914. if ( mOtherTyping )
  915. {
  916. mOtherTyping = false;
  917. // Revert the title to saved one
  918. setTitle(mSavedTitle);
  919. if ( im_info )
  920. {
  921. // Update speaker
  922. LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
  923. if ( speaker_mgr )
  924. {
  925. speaker_mgr->setSpeakerTyping(im_info->mFromID, FALSE);
  926. }
  927. }
  928. }
  929. }
  930. // static
  931. void LLIMFloater::closeHiddenIMToasts()
  932. {
  933. class IMToastMatcher: public LLNotificationsUI::LLScreenChannel::Matcher
  934. {
  935. public:
  936. bool matches(const LLNotificationPtr notification) const
  937. {
  938. // "notifytoast" type of notifications is reserved for IM notifications
  939. return "notifytoast" == notification->getType();
  940. }
  941. };
  942. LLNotificationsUI::LLScreenChannel* channel = LLNotificationsUI::LLChannelManager::getNotificationScreenChannel();
  943. if (channel != NULL)
  944. {
  945. channel->closeHiddenToasts(IMToastMatcher());
  946. }
  947. }
  948. // static
  949. void LLIMFloater::confirmLeaveCallCallback(const LLSD& notification, const LLSD& response)
  950. {
  951. S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
  952. const LLSD& payload = notification["payload"];
  953. LLUUID session_id = payload["session_id"];
  954. LLFloater* im_floater = LLFloaterReg::findInstance("impanel", session_id);
  955. if (option == 0 && im_floater != NULL)
  956. {
  957. im_floater->closeFloater();
  958. }
  959. return;
  960. }
  961. // static
  962. bool LLIMFloater::isChatMultiTab()
  963. {
  964. // Restart is required in order to change chat window type.
  965. static bool is_single_window = gSavedSettings.getS32("ChatWindow") == 1;
  966. return is_single_window;
  967. }
  968. // static
  969. void LLIMFloater::initIMFloater()
  970. {
  971. // This is called on viewer start up
  972. // init chat window type before user changed it in preferences
  973. isChatMultiTab();
  974. }
  975. //static
  976. void LLIMFloater::sRemoveTypingIndicator(const LLSD& data)
  977. {
  978. LLUUID session_id = data["session_id"];
  979. if (session_id.isNull()) return;
  980. LLUUID from_id = data["from_id"];
  981. if (gAgentID == from_id || LLUUID::null == from_id) return;
  982. LLIMFloater* floater = LLIMFloater::findInstance(session_id);
  983. if (!floater) return;
  984. if (IM_NOTHING_SPECIAL != floater->mDialog) return;
  985. floater->removeTypingIndicator();
  986. }
  987. void LLIMFloater::onIMChicletCreated( const LLUUID& session_id )
  988. {
  989. if (isChatMultiTab())
  990. {
  991. LLIMFloaterContainer* im_box = LLIMFloaterContainer::getInstance();
  992. if (!im_box) return;
  993. if (LLIMFloater::findInstance(session_id)) return;
  994. LLIMFloater* new_tab = LLIMFloater::getInstance(session_id);
  995. im_box->addFloater(new_tab, FALSE, LLTabContainer::END);
  996. }
  997. }
  998. void LLIMFloater::onClickCloseBtn()
  999. {
  1000. LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(
  1001. mSessionID);
  1002. if (session == NULL)
  1003. {
  1004. llwarns << "Empty session." << llendl;
  1005. return;
  1006. }
  1007. bool is_call_with_chat = session->isGroupSessionType()
  1008. || session->isAdHocSessionType() || session->isP2PSessionType();
  1009. LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID);
  1010. if (is_call_with_chat && voice_channel != NULL && voice_channel->isActive())
  1011. {
  1012. LLSD payload;
  1013. payload["session_id"] = mSessionID;
  1014. LLNotificationsUtil::add("ConfirmLeaveCall", LLSD(), payload, confirmLeaveCallCallback);
  1015. return;
  1016. }
  1017. LLFloater::onClickCloseBtn();
  1018. }