/indra/newview/llnearbychatbar.cpp

https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 672 lines · 474 code · 108 blank · 90 comment · 81 complexity · ac98df0e205fcf5ed55d4eec63a405e9 MD5 · raw file

  1. /**
  2. * @file llnearbychatbar.cpp
  3. * @brief LLNearbyChatBar class implementation
  4. *
  5. * $LicenseInfo:firstyear=2002&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 "message.h"
  28. #include "llappviewer.h"
  29. #include "llfloaterreg.h"
  30. #include "lltrans.h"
  31. #include "llfirstuse.h"
  32. #include "llnearbychatbar.h"
  33. #include "llagent.h"
  34. #include "llgesturemgr.h"
  35. #include "llmultigesture.h"
  36. #include "llkeyboard.h"
  37. #include "llanimationstates.h"
  38. #include "llviewerstats.h"
  39. #include "llcommandhandler.h"
  40. #include "llviewercontrol.h"
  41. #include "llnavigationbar.h"
  42. #include "llwindow.h"
  43. #include "llviewerwindow.h"
  44. #include "llrootview.h"
  45. #include "llviewerchat.h"
  46. #include "llnearbychat.h"
  47. #include "lltranslate.h"
  48. #include "llresizehandle.h"
  49. S32 LLNearbyChatBar::sLastSpecialChatChannel = 0;
  50. const S32 EXPANDED_HEIGHT = 300;
  51. const S32 COLLAPSED_HEIGHT = 60;
  52. const S32 EXPANDED_MIN_HEIGHT = 150;
  53. // legacy callback glue
  54. void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel);
  55. struct LLChatTypeTrigger {
  56. std::string name;
  57. EChatType type;
  58. };
  59. static LLChatTypeTrigger sChatTypeTriggers[] = {
  60. { "/whisper" , CHAT_TYPE_WHISPER},
  61. { "/shout" , CHAT_TYPE_SHOUT}
  62. };
  63. LLNearbyChatBar::LLNearbyChatBar(const LLSD& key)
  64. : LLFloater(key),
  65. mChatBox(NULL),
  66. mNearbyChat(NULL),
  67. mOutputMonitor(NULL),
  68. mSpeakerMgr(NULL),
  69. mExpandedHeight(COLLAPSED_HEIGHT + EXPANDED_HEIGHT)
  70. {
  71. mSpeakerMgr = LLLocalSpeakerMgr::getInstance();
  72. }
  73. //virtual
  74. BOOL LLNearbyChatBar::postBuild()
  75. {
  76. mChatBox = getChild<LLLineEditor>("chat_box");
  77. mChatBox->setCommitCallback(boost::bind(&LLNearbyChatBar::onChatBoxCommit, this));
  78. mChatBox->setKeystrokeCallback(&onChatBoxKeystroke, this);
  79. mChatBox->setFocusLostCallback(boost::bind(&onChatBoxFocusLost, _1, this));
  80. mChatBox->setFocusReceivedCallback(boost::bind(&LLNearbyChatBar::onChatBoxFocusReceived, this));
  81. mChatBox->setIgnoreArrowKeys( FALSE );
  82. mChatBox->setCommitOnFocusLost( FALSE );
  83. mChatBox->setRevertOnEsc( FALSE );
  84. mChatBox->setIgnoreTab(TRUE);
  85. mChatBox->setPassDelete(TRUE);
  86. mChatBox->setReplaceNewlinesWithSpaces(FALSE);
  87. mChatBox->setEnableLineHistory(TRUE);
  88. mChatBox->setFont(LLViewerChat::getChatFont());
  89. mNearbyChat = getChildView("nearby_chat");
  90. LLUICtrl* show_btn = getChild<LLUICtrl>("show_nearby_chat");
  91. show_btn->setCommitCallback(boost::bind(&LLNearbyChatBar::onToggleNearbyChatPanel, this));
  92. mOutputMonitor = getChild<LLOutputMonitorCtrl>("chat_zone_indicator");
  93. mOutputMonitor->setVisible(FALSE);
  94. gSavedSettings.declareBOOL("nearbychat_history_visibility", mNearbyChat->getVisible(), "Visibility state of nearby chat history", TRUE);
  95. mNearbyChat->setVisible(gSavedSettings.getBOOL("nearbychat_history_visibility"));
  96. // Register for font change notifications
  97. LLViewerChat::setFontChangedCallback(boost::bind(&LLNearbyChatBar::onChatFontChange, this, _1));
  98. enableResizeCtrls(true, true, false);
  99. return TRUE;
  100. }
  101. // virtual
  102. void LLNearbyChatBar::onOpen(const LLSD& key)
  103. {
  104. enableTranslationCheckbox(LLTranslate::isTranslationConfigured());
  105. }
  106. bool LLNearbyChatBar::applyRectControl()
  107. {
  108. bool rect_controlled = LLFloater::applyRectControl();
  109. if (!mNearbyChat->getVisible())
  110. {
  111. reshape(getRect().getWidth(), getMinHeight());
  112. enableResizeCtrls(true, true, false);
  113. }
  114. else
  115. {
  116. enableResizeCtrls(true);
  117. setResizeLimits(getMinWidth(), EXPANDED_MIN_HEIGHT);
  118. }
  119. return rect_controlled;
  120. }
  121. void LLNearbyChatBar::onChatFontChange(LLFontGL* fontp)
  122. {
  123. // Update things with the new font whohoo
  124. if (mChatBox)
  125. {
  126. mChatBox->setFont(fontp);
  127. }
  128. }
  129. //static
  130. LLNearbyChatBar* LLNearbyChatBar::getInstance()
  131. {
  132. return LLFloaterReg::getTypedInstance<LLNearbyChatBar>("chat_bar");
  133. }
  134. void LLNearbyChatBar::showHistory()
  135. {
  136. openFloater();
  137. if (!getChildView("nearby_chat")->getVisible())
  138. {
  139. onToggleNearbyChatPanel();
  140. }
  141. }
  142. void LLNearbyChatBar::enableTranslationCheckbox(BOOL enable)
  143. {
  144. getChild<LLUICtrl>("translate_chat_checkbox")->setEnabled(enable);
  145. }
  146. void LLNearbyChatBar::draw()
  147. {
  148. displaySpeakingIndicator();
  149. LLFloater::draw();
  150. }
  151. std::string LLNearbyChatBar::getCurrentChat()
  152. {
  153. return mChatBox ? mChatBox->getText() : LLStringUtil::null;
  154. }
  155. // virtual
  156. BOOL LLNearbyChatBar::handleKeyHere( KEY key, MASK mask )
  157. {
  158. BOOL handled = FALSE;
  159. if( KEY_RETURN == key && mask == MASK_CONTROL)
  160. {
  161. // shout
  162. sendChat(CHAT_TYPE_SHOUT);
  163. handled = TRUE;
  164. }
  165. return handled;
  166. }
  167. BOOL LLNearbyChatBar::matchChatTypeTrigger(const std::string& in_str, std::string* out_str)
  168. {
  169. U32 in_len = in_str.length();
  170. S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers);
  171. for (S32 n = 0; n < cnt; n++)
  172. {
  173. if (in_len > sChatTypeTriggers[n].name.length())
  174. continue;
  175. std::string trigger_trunc = sChatTypeTriggers[n].name;
  176. LLStringUtil::truncate(trigger_trunc, in_len);
  177. if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc))
  178. {
  179. *out_str = sChatTypeTriggers[n].name;
  180. return TRUE;
  181. }
  182. }
  183. return FALSE;
  184. }
  185. void LLNearbyChatBar::onChatBoxKeystroke(LLLineEditor* caller, void* userdata)
  186. {
  187. LLFirstUse::otherAvatarChatFirst(false);
  188. LLNearbyChatBar* self = (LLNearbyChatBar *)userdata;
  189. LLWString raw_text = self->mChatBox->getWText();
  190. // Can't trim the end, because that will cause autocompletion
  191. // to eat trailing spaces that might be part of a gesture.
  192. LLWStringUtil::trimHead(raw_text);
  193. S32 length = raw_text.length();
  194. if( (length > 0) && (raw_text[0] != '/') ) // forward slash is used for escape (eg. emote) sequences
  195. {
  196. gAgent.startTyping();
  197. }
  198. else
  199. {
  200. gAgent.stopTyping();
  201. }
  202. /* Doesn't work -- can't tell the difference between a backspace
  203. that killed the selection vs. backspace at the end of line.
  204. if (length > 1
  205. && text[0] == '/'
  206. && key == KEY_BACKSPACE)
  207. {
  208. // the selection will already be deleted, but we need to trim
  209. // off the character before
  210. std::string new_text = raw_text.substr(0, length-1);
  211. self->mInputEditor->setText( new_text );
  212. self->mInputEditor->setCursorToEnd();
  213. length = length - 1;
  214. }
  215. */
  216. KEY key = gKeyboard->currentKey();
  217. // Ignore "special" keys, like backspace, arrows, etc.
  218. if (length > 1
  219. && raw_text[0] == '/'
  220. && key < KEY_SPECIAL)
  221. {
  222. // we're starting a gesture, attempt to autocomplete
  223. std::string utf8_trigger = wstring_to_utf8str(raw_text);
  224. std::string utf8_out_str(utf8_trigger);
  225. if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str))
  226. {
  227. std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size());
  228. self->mChatBox->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part
  229. S32 outlength = self->mChatBox->getLength(); // in characters
  230. // Select to end of line, starting from the character
  231. // after the last one the user typed.
  232. self->mChatBox->setSelection(length, outlength);
  233. }
  234. else if (matchChatTypeTrigger(utf8_trigger, &utf8_out_str))
  235. {
  236. std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size());
  237. self->mChatBox->setText(utf8_trigger + rest_of_match + " "); // keep original capitalization for user-entered part
  238. self->mChatBox->setCursorToEnd();
  239. }
  240. //llinfos << "GESTUREDEBUG " << trigger
  241. // << " len " << length
  242. // << " outlen " << out_str.getLength()
  243. // << llendl;
  244. }
  245. }
  246. // static
  247. void LLNearbyChatBar::onChatBoxFocusLost(LLFocusableElement* caller, void* userdata)
  248. {
  249. // stop typing animation
  250. gAgent.stopTyping();
  251. }
  252. void LLNearbyChatBar::onChatBoxFocusReceived()
  253. {
  254. mChatBox->setEnabled(!gDisconnected);
  255. }
  256. EChatType LLNearbyChatBar::processChatTypeTriggers(EChatType type, std::string &str)
  257. {
  258. U32 length = str.length();
  259. S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers);
  260. for (S32 n = 0; n < cnt; n++)
  261. {
  262. if (length >= sChatTypeTriggers[n].name.length())
  263. {
  264. std::string trigger = str.substr(0, sChatTypeTriggers[n].name.length());
  265. if (!LLStringUtil::compareInsensitive(trigger, sChatTypeTriggers[n].name))
  266. {
  267. U32 trigger_length = sChatTypeTriggers[n].name.length();
  268. // It's to remove space after trigger name
  269. if (length > trigger_length && str[trigger_length] == ' ')
  270. trigger_length++;
  271. str = str.substr(trigger_length, length);
  272. if (CHAT_TYPE_NORMAL == type)
  273. return sChatTypeTriggers[n].type;
  274. else
  275. break;
  276. }
  277. }
  278. }
  279. return type;
  280. }
  281. void LLNearbyChatBar::sendChat( EChatType type )
  282. {
  283. if (mChatBox)
  284. {
  285. LLWString text = mChatBox->getConvertedText();
  286. if (!text.empty())
  287. {
  288. // store sent line in history, duplicates will get filtered
  289. mChatBox->updateHistory();
  290. // Check if this is destined for another channel
  291. S32 channel = 0;
  292. stripChannelNumber(text, &channel);
  293. std::string utf8text = wstring_to_utf8str(text);
  294. // Try to trigger a gesture, if not chat to a script.
  295. std::string utf8_revised_text;
  296. if (0 == channel)
  297. {
  298. // discard returned "found" boolean
  299. LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text);
  300. }
  301. else
  302. {
  303. utf8_revised_text = utf8text;
  304. }
  305. utf8_revised_text = utf8str_trim(utf8_revised_text);
  306. type = processChatTypeTriggers(type, utf8_revised_text);
  307. if (!utf8_revised_text.empty())
  308. {
  309. // Chat with animation
  310. sendChatFromViewer(utf8_revised_text, type, TRUE);
  311. }
  312. }
  313. mChatBox->setText(LLStringExplicit(""));
  314. }
  315. gAgent.stopTyping();
  316. // If the user wants to stop chatting on hitting return, lose focus
  317. // and go out of chat mode.
  318. if (gSavedSettings.getBOOL("CloseChatOnReturn"))
  319. {
  320. stopChat();
  321. }
  322. }
  323. void LLNearbyChatBar::onToggleNearbyChatPanel()
  324. {
  325. LLView* nearby_chat = getChildView("nearby_chat");
  326. if (nearby_chat->getVisible())
  327. {
  328. if (!isMinimized())
  329. {
  330. mExpandedHeight = getRect().getHeight();
  331. }
  332. setResizeLimits(getMinWidth(), COLLAPSED_HEIGHT);
  333. nearby_chat->setVisible(FALSE);
  334. reshape(getRect().getWidth(), COLLAPSED_HEIGHT);
  335. enableResizeCtrls(true, true, false);
  336. storeRectControl();
  337. }
  338. else
  339. {
  340. nearby_chat->setVisible(TRUE);
  341. setResizeLimits(getMinWidth(), EXPANDED_MIN_HEIGHT);
  342. reshape(getRect().getWidth(), mExpandedHeight);
  343. enableResizeCtrls(true);
  344. storeRectControl();
  345. }
  346. gSavedSettings.setBOOL("nearbychat_history_visibility", mNearbyChat->getVisible());
  347. }
  348. void LLNearbyChatBar::setMinimized(BOOL b)
  349. {
  350. LLNearbyChat* nearby_chat = getChild<LLNearbyChat>("nearby_chat");
  351. // when unminimizing with nearby chat visible, go ahead and kill off screen chats
  352. if (!b && nearby_chat->getVisible())
  353. {
  354. nearby_chat->removeScreenChat();
  355. }
  356. LLFloater::setMinimized(b);
  357. }
  358. void LLNearbyChatBar::onChatBoxCommit()
  359. {
  360. if (mChatBox->getText().length() > 0)
  361. {
  362. sendChat(CHAT_TYPE_NORMAL);
  363. }
  364. gAgent.stopTyping();
  365. }
  366. void LLNearbyChatBar::displaySpeakingIndicator()
  367. {
  368. LLSpeakerMgr::speaker_list_t speaker_list;
  369. LLUUID id;
  370. id.setNull();
  371. mSpeakerMgr->update(TRUE);
  372. mSpeakerMgr->getSpeakerList(&speaker_list, FALSE);
  373. for (LLSpeakerMgr::speaker_list_t::iterator i = speaker_list.begin(); i != speaker_list.end(); ++i)
  374. {
  375. LLPointer<LLSpeaker> s = *i;
  376. if (s->mSpeechVolume > 0 || s->mStatus == LLSpeaker::STATUS_SPEAKING)
  377. {
  378. id = s->mID;
  379. break;
  380. }
  381. }
  382. if (!id.isNull())
  383. {
  384. mOutputMonitor->setVisible(TRUE);
  385. mOutputMonitor->setSpeakerId(id);
  386. }
  387. else
  388. {
  389. mOutputMonitor->setVisible(FALSE);
  390. }
  391. }
  392. void LLNearbyChatBar::sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate)
  393. {
  394. sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate);
  395. }
  396. void LLNearbyChatBar::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate)
  397. {
  398. // Look for "/20 foo" channel chats.
  399. S32 channel = 0;
  400. LLWString out_text = stripChannelNumber(wtext, &channel);
  401. std::string utf8_out_text = wstring_to_utf8str(out_text);
  402. std::string utf8_text = wstring_to_utf8str(wtext);
  403. utf8_text = utf8str_trim(utf8_text);
  404. if (!utf8_text.empty())
  405. {
  406. utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1);
  407. }
  408. // Don't animate for chats people can't hear (chat to scripts)
  409. if (animate && (channel == 0))
  410. {
  411. if (type == CHAT_TYPE_WHISPER)
  412. {
  413. lldebugs << "You whisper " << utf8_text << llendl;
  414. gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START);
  415. }
  416. else if (type == CHAT_TYPE_NORMAL)
  417. {
  418. lldebugs << "You say " << utf8_text << llendl;
  419. gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START);
  420. }
  421. else if (type == CHAT_TYPE_SHOUT)
  422. {
  423. lldebugs << "You shout " << utf8_text << llendl;
  424. gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START);
  425. }
  426. else
  427. {
  428. llinfos << "send_chat_from_viewer() - invalid volume" << llendl;
  429. return;
  430. }
  431. }
  432. else
  433. {
  434. if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP)
  435. {
  436. lldebugs << "Channel chat: " << utf8_text << llendl;
  437. }
  438. }
  439. send_chat_from_viewer(utf8_out_text, type, channel);
  440. }
  441. // static
  442. void LLNearbyChatBar::startChat(const char* line)
  443. {
  444. LLNearbyChatBar* cb = LLNearbyChatBar::getInstance();
  445. if (!cb )
  446. return;
  447. cb->setVisible(TRUE);
  448. cb->setFocus(TRUE);
  449. cb->mChatBox->setFocus(TRUE);
  450. if (line)
  451. {
  452. std::string line_string(line);
  453. cb->mChatBox->setText(line_string);
  454. }
  455. cb->mChatBox->setCursorToEnd();
  456. }
  457. // Exit "chat mode" and do the appropriate focus changes
  458. // static
  459. void LLNearbyChatBar::stopChat()
  460. {
  461. LLNearbyChatBar* cb = LLNearbyChatBar::getInstance();
  462. if (!cb)
  463. return;
  464. cb->mChatBox->setFocus(FALSE);
  465. // stop typing animation
  466. gAgent.stopTyping();
  467. }
  468. // If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20.
  469. // Otherwise returns input and channel 0.
  470. LLWString LLNearbyChatBar::stripChannelNumber(const LLWString &mesg, S32* channel)
  471. {
  472. if (mesg[0] == '/'
  473. && mesg[1] == '/')
  474. {
  475. // This is a "repeat channel send"
  476. *channel = sLastSpecialChatChannel;
  477. return mesg.substr(2, mesg.length() - 2);
  478. }
  479. else if (mesg[0] == '/'
  480. && mesg[1]
  481. && LLStringOps::isDigit(mesg[1]))
  482. {
  483. // This a special "/20" speak on a channel
  484. S32 pos = 0;
  485. // Copy the channel number into a string
  486. LLWString channel_string;
  487. llwchar c;
  488. do
  489. {
  490. c = mesg[pos+1];
  491. channel_string.push_back(c);
  492. pos++;
  493. }
  494. while(c && pos < 64 && LLStringOps::isDigit(c));
  495. // Move the pointer forward to the first non-whitespace char
  496. // Check isspace before looping, so we can handle "/33foo"
  497. // as well as "/33 foo"
  498. while(c && iswspace(c))
  499. {
  500. c = mesg[pos+1];
  501. pos++;
  502. }
  503. sLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10);
  504. *channel = sLastSpecialChatChannel;
  505. return mesg.substr(pos, mesg.length() - pos);
  506. }
  507. else
  508. {
  509. // This is normal chat.
  510. *channel = 0;
  511. return mesg;
  512. }
  513. }
  514. void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel)
  515. {
  516. LLMessageSystem* msg = gMessageSystem;
  517. msg->newMessageFast(_PREHASH_ChatFromViewer);
  518. msg->nextBlockFast(_PREHASH_AgentData);
  519. msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
  520. msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
  521. msg->nextBlockFast(_PREHASH_ChatData);
  522. msg->addStringFast(_PREHASH_Message, utf8_out_text);
  523. msg->addU8Fast(_PREHASH_Type, type);
  524. msg->addS32("Channel", channel);
  525. gAgent.sendReliableMessage();
  526. LLViewerStats::getInstance()->incStat(LLViewerStats::ST_CHAT_COUNT);
  527. }
  528. class LLChatCommandHandler : public LLCommandHandler
  529. {
  530. public:
  531. // not allowed from outside the app
  532. LLChatCommandHandler() : LLCommandHandler("chat", UNTRUSTED_BLOCK) { }
  533. // Your code here
  534. bool handle(const LLSD& tokens, const LLSD& query_map,
  535. LLMediaCtrl* web)
  536. {
  537. bool retval = false;
  538. // Need at least 2 tokens to have a valid message.
  539. if (tokens.size() < 2)
  540. {
  541. retval = false;
  542. }
  543. else
  544. {
  545. S32 channel = tokens[0].asInteger();
  546. // VWR-19499 Restrict function to chat channels greater than 0.
  547. if ((channel > 0) && (channel < CHAT_CHANNEL_DEBUG))
  548. {
  549. retval = true;
  550. // Send unescaped message, see EXT-6353.
  551. std::string unescaped_mesg (LLURI::unescape(tokens[1].asString()));
  552. send_chat_from_viewer(unescaped_mesg, CHAT_TYPE_NORMAL, channel);
  553. }
  554. else
  555. {
  556. retval = false;
  557. // Tell us this is an unsupported SLurl.
  558. }
  559. }
  560. return retval;
  561. }
  562. };
  563. // Creating the object registers with the dispatcher.
  564. LLChatCommandHandler gChatHandler;