From 0ee0a5eff41a3763b1fc3fc45a36f87fc6eedac5 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Fri, 27 Jul 2012 22:25:17 +0300 Subject: CHUI-151 FIXED (Implement conversation log) A brief explanation of what have been implemented. More information can be found in comments. 1. Created conversation history viewer (llfloaterconversationpreview) 2. Created LLConversation and LLConversationLog classes which represent and hold data of conversations (llconversationlog) 3. Created LLConversationLogList and LLConversationLogListItem which are the visual representation of LLConversationLog and LLConversation respectively 4. Created LLFloaterConversationLog - which holds and displays LLConversationLogList --- indra/newview/llfloaterconversationpreview.cpp | 112 +++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 indra/newview/llfloaterconversationpreview.cpp (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp new file mode 100644 index 0000000000..e8554bb066 --- /dev/null +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -0,0 +1,112 @@ +/** + * @file llfloaterconversationpreview.cpp + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llconversationlog.h" +#include "llfloaterconversationpreview.h" +#include "llimview.h" +#include "lllineeditor.h" + +LLFloaterConversationPreview::LLFloaterConversationPreview(const LLSD& session_id) +: LLFloater(session_id), + mChatHistory(NULL), + mSessionID(session_id.asUUID()) +{} + +BOOL LLFloaterConversationPreview::postBuild() +{ + mChatHistory = getChild("chat_history"); + + const LLConversation* conv = LLConversationLog::instance().getConversation(mSessionID); + if (conv) + { + std::string name = conv->getConversationName(); + LLStringUtil::format_map_t args; + args["[NAME]"] = name; + std::string title = getString("Title", args); + setTitle(title); + + getChild("description")->setValue(name); + } + + return LLFloater::postBuild(); +} + +void LLFloaterConversationPreview::draw() +{ + LLFloater::draw(); +} + +void LLFloaterConversationPreview::onOpen(const LLSD& session_id) +{ + const LLConversation* conv = LLConversationLog::instance().getConversation(session_id); + if (!conv) + { + return; + } + std::list messages; + std::string file = conv->getHistoryFileName(); + LLLogChat::loadAllHistory(file, messages); + + if (messages.size()) + { + std::ostringstream message; + std::list::const_iterator iter = messages.begin(); + for (; iter != messages.end(); ++iter) + { + LLSD msg = *iter; + + std::string time = msg["time"].asString(); + LLUUID from_id = msg["from_id"].asUUID(); + std::string from = msg["from"].asString(); + std::string message = msg["message"].asString(); + bool is_history = msg["is_history"].asBoolean(); + + LLChat chat; + chat.mFromID = from_id; + chat.mSessionID = session_id; + chat.mFromName = from; + chat.mTimeStr = time; + chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; + chat.mText = message; + + appendMessage(chat); + } + } +} + +void LLFloaterConversationPreview::appendMessage(const LLChat& chat) +{ + if (!chat.mMuted) + { + LLSD args; + args["use_plain_text_chat_history"] = true; + args["show_time"] = true; + args["show_names_for_p2p_conv"] = true; + + mChatHistory->appendMessage(chat); + } +} -- cgit v1.3 From d4ee17e533d652e90989e60bcbc097c81e73d081 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Tue, 28 Aug 2012 14:48:32 +0300 Subject: CHUI-275 FIXED (Chat history viewer does not show entire user.txt IM log file) - Renamed LLLogChat::loadAllHistory to LLLogChat::loadChatHistory because it doesn't actually loads all history. Also added parameter to the function which is a flag whether to load all file's content or not. - Implemented displaying history by pages (as was decided on meeting page): Added showHistory() method to the LLFloaterConversationPreview which shows the chat history page by page starting from the last conversation (or may say starting from the last page). One page contains 100 entries. Added "More history..." button to display next page of history. --- indra/newview/app_settings/settings.xml | 11 +++ indra/newview/llfloaterconversationpreview.cpp | 90 +++++++++++++--------- indra/newview/llfloaterconversationpreview.h | 7 +- indra/newview/llimview.cpp | 2 +- indra/newview/lllogchat.cpp | 6 +- indra/newview/lllogchat.h | 4 +- indra/newview/llnearbychat.cpp | 2 +- .../xui/en/floater_conversation_preview.xml | 12 ++- 8 files changed, 88 insertions(+), 46 deletions(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 61bc58b1df..ab1ea6bdbc 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -1639,6 +1639,17 @@ Value + ConversationHistoryPageSize + + Comment + Chat history of conversation opened from call log is displayed by pages. So this is number of entries per page. + Persist + 1 + Type + S32 + Value + 100 + NearbyChatIsNotTornOff Comment diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index e8554bb066..2c34029c5c 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -33,12 +33,15 @@ LLFloaterConversationPreview::LLFloaterConversationPreview(const LLSD& session_id) : LLFloater(session_id), mChatHistory(NULL), - mSessionID(session_id.asUUID()) + mSessionID(session_id.asUUID()), + mCurrentPage(0), + mPageSize(gSavedSettings.getS32("ConversationHistoryPageSize")) {} BOOL LLFloaterConversationPreview::postBuild() { mChatHistory = getChild("chat_history"); + getChild("more_history")->setCommitCallback(boost::bind(&LLFloaterConversationPreview::onMoreHistoryBtnClick, this)); const LLConversation* conv = LLConversationLog::instance().getConversation(mSessionID); if (conv) @@ -52,6 +55,11 @@ BOOL LLFloaterConversationPreview::postBuild() getChild("description")->setValue(name); } + std::string file = conv->getHistoryFileName(); + LLLogChat::loadChatHistory(file, mMessages, true); + + mCurrentPage = mMessages.size() / mPageSize; + return LLFloater::postBuild(); } @@ -62,51 +70,59 @@ void LLFloaterConversationPreview::draw() void LLFloaterConversationPreview::onOpen(const LLSD& session_id) { - const LLConversation* conv = LLConversationLog::instance().getConversation(session_id); - if (!conv) + showHistory(); +} + +void LLFloaterConversationPreview::showHistory() +{ + if (!mMessages.size()) { return; } - std::list messages; - std::string file = conv->getHistoryFileName(); - LLLogChat::loadAllHistory(file, messages); - if (messages.size()) + mChatHistory->clear(); + + std::ostringstream message; + std::list::const_iterator iter = mMessages.begin(); + + int delta = 0; + if (mCurrentPage) { - std::ostringstream message; - std::list::const_iterator iter = messages.begin(); - for (; iter != messages.end(); ++iter) - { - LLSD msg = *iter; - - std::string time = msg["time"].asString(); - LLUUID from_id = msg["from_id"].asUUID(); - std::string from = msg["from"].asString(); - std::string message = msg["message"].asString(); - bool is_history = msg["is_history"].asBoolean(); - - LLChat chat; - chat.mFromID = from_id; - chat.mSessionID = session_id; - chat.mFromName = from; - chat.mTimeStr = time; - chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; - chat.mText = message; - - appendMessage(chat); - } + double num_of_pages = (double)mMessages.size() / mPageSize; + delta = (ceil(num_of_pages) - num_of_pages) * mPageSize; } -} -void LLFloaterConversationPreview::appendMessage(const LLChat& chat) -{ - if (!chat.mMuted) + std::advance(iter, (mCurrentPage * mPageSize) - delta); + + for (int msg_num = 0; (iter != mMessages.end() && msg_num < mPageSize); ++iter, ++msg_num) { - LLSD args; - args["use_plain_text_chat_history"] = true; - args["show_time"] = true; - args["show_names_for_p2p_conv"] = true; + LLSD msg = *iter; + + std::string time = msg["time"].asString(); + LLUUID from_id = msg["from_id"].asUUID(); + std::string from = msg["from"].asString(); + std::string message = msg["message"].asString(); + bool is_history = msg["is_history"].asBoolean(); + + LLChat chat; + chat.mFromID = from_id; + chat.mSessionID = mSessionID; + chat.mFromName = from; + chat.mTimeStr = time; + chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; + chat.mText = message; mChatHistory->appendMessage(chat); } + +} + +void LLFloaterConversationPreview::onMoreHistoryBtnClick() +{ + if (--mCurrentPage < 0) + { + return; + } + + showHistory(); } diff --git a/indra/newview/llfloaterconversationpreview.h b/indra/newview/llfloaterconversationpreview.h index cfc7c34485..5105ef3702 100644 --- a/indra/newview/llfloaterconversationpreview.h +++ b/indra/newview/llfloaterconversationpreview.h @@ -42,10 +42,15 @@ public: virtual void onOpen(const LLSD& session_id); private: - void appendMessage(const LLChat& chat); + void onMoreHistoryBtnClick(); + void showHistory(); LLChatHistory* mChatHistory; LLUUID mSessionID; + int mCurrentPage; + int mPageSize; + + std::list mMessages; }; #endif /* LLFLOATERCONVERSATIONPREVIEW_H_ */ diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 216db15c94..effcc9a826 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -263,7 +263,7 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& std::list chat_history; //involves parsing of a chat history - LLLogChat::loadAllHistory(mHistoryFileName, chat_history); + LLLogChat::loadChatHistory(mHistoryFileName, chat_history); addMessagesFromHistory(chat_history); } diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index ebb5912ace..073f5f00c5 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -387,7 +387,7 @@ void append_to_last_message(std::list& messages, const std::string& line) } // static -void LLLogChat::loadAllHistory(const std::string& file_name, std::list& messages) +void LLLogChat::loadChatHistory(const std::string& file_name, std::list& messages, bool load_all_history/*= false*/) { if (file_name.empty()) { @@ -412,8 +412,8 @@ void LLLogChat::loadAllHistory(const std::string& file_name, std::list& me S32 len; bool firstline = TRUE; - if (fseek(fptr, (LOG_RECALL_SIZE - 1) * -1 , SEEK_END)) - { //File is smaller than recall size. Get it all. + if (load_all_history || fseek(fptr, (LOG_RECALL_SIZE - 1) * -1 , SEEK_END)) + { //We need to load the whole historyFile or it's smaller than recall size, so get it all. firstline = FALSE; if (fseek(fptr, 0, SEEK_SET)) { diff --git a/indra/newview/lllogchat.h b/indra/newview/lllogchat.h index 27752452c9..95f83e64e5 100644 --- a/indra/newview/lllogchat.h +++ b/indra/newview/lllogchat.h @@ -50,12 +50,12 @@ public: const LLUUID& from_id, const std::string& line); - /** @deprecated @see loadAllHistory() */ + /** @deprecated @see loadChatHistory() */ static void loadHistory(const std::string& filename, void (*callback)(ELogLineType, const LLSD&, void*), void* userdata); - static void loadAllHistory(const std::string& file_name, std::list& messages); + static void loadChatHistory(const std::string& file_name, std::list& messages, bool load_all_history = false); private: static std::string cleanFileName(std::string filename); }; diff --git a/indra/newview/llnearbychat.cpp b/indra/newview/llnearbychat.cpp index a723748094..f1518fe825 100644 --- a/indra/newview/llnearbychat.cpp +++ b/indra/newview/llnearbychat.cpp @@ -262,7 +262,7 @@ void LLNearbyChat::loadHistory() do_not_log["do_not_log"] = true; std::list history; - LLLogChat::loadAllHistory("chat", history); + LLLogChat::loadChatHistory("chat", history); std::list::const_iterator it = history.begin(); while (it != history.end()) diff --git a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml index 27b744aefb..c837a0ee57 100644 --- a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml @@ -3,7 +3,7 @@ legacy_header_height="18" can_resize="true" default_tab_group="1" - height="361" + height="391" layout="topleft" min_height="243" min_width="234" @@ -50,4 +50,14 @@ left="5" width="390"> + -- cgit v1.3 From 11e9f66e6217c22373f5d47518f6d1f9885f1793 Mon Sep 17 00:00:00 2001 From: William Todd Stinson Date: Tue, 28 Aug 2012 12:41:57 -0700 Subject: BUILDFIX: Correcting a linux build error. --- indra/newview/llfloaterconversationpreview.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index 2c34029c5c..ae6f1441eb 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -88,8 +88,13 @@ void LLFloaterConversationPreview::showHistory() int delta = 0; if (mCurrentPage) { - double num_of_pages = (double)mMessages.size() / mPageSize; - delta = (ceil(num_of_pages) - num_of_pages) * mPageSize; + // stinson 08/28/2012 : This operation could be simplified using integer math with the mod (%) operator. + // e.g. The following code should give the same output. + // int remainder = mMessages.size() % mPageSize; + // delta = (remainder == 0) ? 0 : (mPageSize - remainder); + // Though without examining further, the remainder might be a more appropriate value. + double num_of_pages = static_cast(mMessages.size()) / static_cast(mPageSize); + delta = static_cast((ceil(num_of_pages) - num_of_pages) * static_cast(mPageSize)); } std::advance(iter, (mCurrentPage * mPageSize) - delta); -- cgit v1.3 From dab6788c42260135d298b619ff92a71838bba2b8 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Fri, 31 Aug 2012 11:57:36 +0300 Subject: CHUI-157 FIXED (Implement menu bar for conversation floater) - Added View Nearby chat history option - Also replaced menu item "Add Friend / Remove" with two separate menus: Add Friend and Remove Friend. So if user is a Friend the Remove Friend option would be shown there. If the user is not a friend, the Add Friend option would be shown. --- indra/newview/llconversationloglist.cpp | 17 ++++++--- indra/newview/llfloaterconversationlog.cpp | 5 +++ indra/newview/llfloaterconversationpreview.cpp | 40 ++++++++++++---------- indra/newview/llfloaterconversationpreview.h | 2 +- .../default/xui/en/menu_conversation_log_gear.xml | 28 +++++++++------ .../default/xui/en/menu_conversation_log_view.xml | 8 +++++ 6 files changed, 66 insertions(+), 34 deletions(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llconversationloglist.cpp b/indra/newview/llconversationloglist.cpp index 257ec082a5..94be9055bd 100644 --- a/indra/newview/llconversationloglist.cpp +++ b/indra/newview/llconversationloglist.cpp @@ -241,15 +241,18 @@ void LLConversationLogList::onCustomAction(const LLSD& userdata) { LLAvatarActions::offerTeleport(selected_id); } - else if("add_rem_friend" == command_name) + else if("add_friend" == command_name) { - if (LLAvatarActions::isFriend(selected_id)) + if (!LLAvatarActions::isFriend(selected_id)) { - LLAvatarActions::removeFriendDialog(selected_id); + LLAvatarActions::requestFriendshipDialog(selected_id); } - else + } + else if("remove_friend" == command_name) + { + if (LLAvatarActions::isFriend(selected_id)) { - LLAvatarActions::requestFriendshipDialog(selected_id); + LLAvatarActions::removeFriendDialog(selected_id); } } else if ("invite_to_group" == command_name) @@ -336,6 +339,10 @@ bool LLConversationLogList::isActionChecked(const LLSD& userdata) { return is_p2p && LLAvatarActions::isFriend(selected_id); } + else if ("is_not_friend" == command_name) + { + return is_p2p && !LLAvatarActions::isFriend(selected_id); + } return false; } diff --git a/indra/newview/llfloaterconversationlog.cpp b/indra/newview/llfloaterconversationlog.cpp index c77a9e74bb..4375ce5726 100644 --- a/indra/newview/llfloaterconversationlog.cpp +++ b/indra/newview/llfloaterconversationlog.cpp @@ -28,6 +28,7 @@ #include "llconversationloglist.h" #include "llfiltereditor.h" #include "llfloaterconversationlog.h" +#include "llfloaterreg.h" #include "llmenubutton.h" LLFloaterConversationLog::LLFloaterConversationLog(const LLSD& key) @@ -97,6 +98,10 @@ void LLFloaterConversationLog::onCustomAction (const LLSD& userdata) { mConversationLogList->toggleSortFriendsOnTop(); } + else if ("view_nearby_chat_history" == command_name) + { + LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true); + } } bool LLFloaterConversationLog::isActionEnabled(const LLSD& userdata) diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index ae6f1441eb..7083fb987d 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -29,6 +29,7 @@ #include "llfloaterconversationpreview.h" #include "llimview.h" #include "lllineeditor.h" +#include "lltrans.h" LLFloaterConversationPreview::LLFloaterConversationPreview(const LLSD& session_id) : LLFloater(session_id), @@ -44,20 +45,28 @@ BOOL LLFloaterConversationPreview::postBuild() getChild("more_history")->setCommitCallback(boost::bind(&LLFloaterConversationPreview::onMoreHistoryBtnClick, this)); const LLConversation* conv = LLConversationLog::instance().getConversation(mSessionID); - if (conv) - { - std::string name = conv->getConversationName(); - LLStringUtil::format_map_t args; - args["[NAME]"] = name; - std::string title = getString("Title", args); - setTitle(title); + std::string name; + std::string file; - getChild("description")->setValue(name); + if (mSessionID != LLUUID::null && conv) + { + name = conv->getConversationName(); + file = conv->getHistoryFileName(); + } + else + { + name = LLTrans::getString("NearbyChatTitle"); + file = "chat"; } - std::string file = conv->getHistoryFileName(); - LLLogChat::loadChatHistory(file, mMessages, true); + LLStringUtil::format_map_t args; + args["[NAME]"] = name; + std::string title = getString("Title", args); + setTitle(title); + + getChild("description")->setValue(name); + LLLogChat::loadChatHistory(file, mMessages, true); mCurrentPage = mMessages.size() / mPageSize; return LLFloater::postBuild(); @@ -68,7 +77,7 @@ void LLFloaterConversationPreview::draw() LLFloater::draw(); } -void LLFloaterConversationPreview::onOpen(const LLSD& session_id) +void LLFloaterConversationPreview::onOpen(const LLSD& key) { showHistory(); } @@ -88,13 +97,8 @@ void LLFloaterConversationPreview::showHistory() int delta = 0; if (mCurrentPage) { - // stinson 08/28/2012 : This operation could be simplified using integer math with the mod (%) operator. - // e.g. The following code should give the same output. - // int remainder = mMessages.size() % mPageSize; - // delta = (remainder == 0) ? 0 : (mPageSize - remainder); - // Though without examining further, the remainder might be a more appropriate value. - double num_of_pages = static_cast(mMessages.size()) / static_cast(mPageSize); - delta = static_cast((ceil(num_of_pages) - num_of_pages) * static_cast(mPageSize)); + int remainder = mMessages.size() % mPageSize; + delta = (remainder == 0) ? 0 : (mPageSize - remainder); } std::advance(iter, (mCurrentPage * mPageSize) - delta); diff --git a/indra/newview/llfloaterconversationpreview.h b/indra/newview/llfloaterconversationpreview.h index 5105ef3702..2246a44761 100644 --- a/indra/newview/llfloaterconversationpreview.h +++ b/indra/newview/llfloaterconversationpreview.h @@ -39,7 +39,7 @@ public: virtual BOOL postBuild(); virtual void draw(); - virtual void onOpen(const LLSD& session_id); + virtual void onOpen(const LLSD& key); private: void onMoreHistoryBtnClick(); diff --git a/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml b/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml index b8d0eef956..8796b87955 100644 --- a/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml +++ b/indra/newview/skins/default/xui/en/menu_conversation_log_gear.xml @@ -57,20 +57,28 @@ parameter="can_offer_teleport"/> - - + - + + + + + - - + + + + + -- cgit v1.3 From 62eb7ec0301c0313cedc2fcb63df8779b22a6d26 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Thu, 6 Sep 2012 23:34:47 +0300 Subject: CHUI-318 FIXED (User cannot navigate forward in chat history viewer once the More History option is selected.) - Added spinner so that user could select desired history page. Also displaying total count of pages. --- indra/newview/llfloaterconversationpreview.cpp | 11 +++++++ indra/newview/llfloaterconversationpreview.h | 3 ++ .../xui/en/floater_conversation_preview.xml | 36 +++++++++++++++++----- 3 files changed, 43 insertions(+), 7 deletions(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index 7083fb987d..c9d9d7aa3b 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -29,6 +29,7 @@ #include "llfloaterconversationpreview.h" #include "llimview.h" #include "lllineeditor.h" +#include "llspinctrl.h" #include "lltrans.h" LLFloaterConversationPreview::LLFloaterConversationPreview(const LLSD& session_id) @@ -69,6 +70,15 @@ BOOL LLFloaterConversationPreview::postBuild() LLLogChat::loadChatHistory(file, mMessages, true); mCurrentPage = mMessages.size() / mPageSize; + mPageSpinner = getChild("history_page_spin"); + mPageSpinner->setCommitCallback(boost::bind(&LLFloaterConversationPreview::onMoreHistoryBtnClick, this)); + mPageSpinner->setMinValue(1); + mPageSpinner->setMaxValue(mCurrentPage + 1); + mPageSpinner->set(mCurrentPage + 1); + + std::string total_page_num = llformat("/ %d", mCurrentPage + 1); + getChild("page_num_label")->setValue(total_page_num); + return LLFloater::postBuild(); } @@ -128,6 +138,7 @@ void LLFloaterConversationPreview::showHistory() void LLFloaterConversationPreview::onMoreHistoryBtnClick() { + mCurrentPage = mPageSpinner->getValueF32(); if (--mCurrentPage < 0) { return; diff --git a/indra/newview/llfloaterconversationpreview.h b/indra/newview/llfloaterconversationpreview.h index 2246a44761..0341e5d2a0 100644 --- a/indra/newview/llfloaterconversationpreview.h +++ b/indra/newview/llfloaterconversationpreview.h @@ -29,6 +29,8 @@ #include "llchathistory.h" #include "llfloater.h" +class LLSpinCtrl; + class LLFloaterConversationPreview : public LLFloater { public: @@ -45,6 +47,7 @@ private: void onMoreHistoryBtnClick(); void showHistory(); + LLSpinCtrl* mPageSpinner; LLChatHistory* mChatHistory; LLUUID mSessionID; int mCurrentPage; diff --git a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml index 28ba03ebe9..d74c2c252d 100644 --- a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml @@ -51,14 +51,36 @@ left="5" width="390"> - + name="page_label" + right="-110" + top_pad="7" + value="Page" + width="35"> + + + + -- cgit v1.3 From 20b95f6ac0a63ce36cb1a7f51505bbf9ef8015c2 Mon Sep 17 00:00:00 2001 From: Merov Linden Date: Mon, 10 Sep 2012 11:02:13 -0700 Subject: Attempt to fix Linux build --- indra/newview/llfloaterconversationpreview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index c9d9d7aa3b..9ea35fcbad 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -138,7 +138,7 @@ void LLFloaterConversationPreview::showHistory() void LLFloaterConversationPreview::onMoreHistoryBtnClick() { - mCurrentPage = mPageSpinner->getValueF32(); + mCurrentPage = (int)(mPageSpinner->getValueF32()); if (--mCurrentPage < 0) { return; -- cgit v1.3 From cb8ce1141837cc450756764e03a4564f8ab0c9eb Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Tue, 11 Sep 2012 18:37:05 +0300 Subject: CHUI-336 FIXED (Remove Description label and field from Chat history viewer) - Removed lineeditor and text box from code and xml --- indra/newview/llfloaterconversationpreview.cpp | 2 -- .../xui/en/floater_conversation_preview.xml | 28 ++-------------------- 2 files changed, 2 insertions(+), 28 deletions(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index 9ea35fcbad..dbcf154ef2 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -65,8 +65,6 @@ BOOL LLFloaterConversationPreview::postBuild() std::string title = getString("Title", args); setTitle(title); - getChild("description")->setValue(name); - LLLogChat::loadChatHistory(file, mMessages, true); mCurrentPage = mMessages.size() / mPageSize; diff --git a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml index d74c2c252d..825a88e4e1 100644 --- a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml @@ -14,41 +14,17 @@ name="Title"> CONVERSATION: [NAME] - - Description: - - Date: Thu, 13 Sep 2012 18:49:57 +0300 Subject: CHUI-334 FIXED (Date not shown in the chat log for current date entries) Before the "%Y/%m/%d" was always cutted from timestamp string for today's date. Now I added flag whether we should cut off timestamp or not. --- indra/newview/llfloaterconversationpreview.cpp | 6 +++++- indra/newview/lllogchat.cpp | 17 +++++++++++++---- indra/newview/lllogchat.h | 4 ++-- 3 files changed, 20 insertions(+), 7 deletions(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index dbcf154ef2..88efc39764 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -65,7 +65,11 @@ BOOL LLFloaterConversationPreview::postBuild() std::string title = getString("Title", args); setTitle(title); - LLLogChat::loadChatHistory(file, mMessages, true); + LLSD load_params; + load_params["load_all_history"] = true; + load_params["cut_off_todays_date"] = false; + + LLLogChat::loadChatHistory(file, mMessages, load_params); mCurrentPage = mMessages.size() / mPageSize; mPageSpinner = getChild("history_page_spin"); diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index 073f5f00c5..3692658e9e 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -387,13 +387,16 @@ void append_to_last_message(std::list& messages, const std::string& line) } // static -void LLLogChat::loadChatHistory(const std::string& file_name, std::list& messages, bool load_all_history/*= false*/) +void LLLogChat::loadChatHistory(const std::string& file_name, std::list& messages, const LLSD& load_params) { if (file_name.empty()) { llwarns << "Session name is Empty!" << llendl; return ; } + + bool load_all_history = load_params.has("load_all_history") ? load_params["load_all_history"].asBoolean() : false; + //LL_INFOS("") << "Loading:" << file_name << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ //LL_INFOS("") << "Current:" << makeLogFileName(file_name) << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ LLFILE* fptr = LLFile::fopen(makeLogFileName(file_name), "r");/*Flawfinder: ignore*/ @@ -449,7 +452,7 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list& m else { LLSD item; - if (!LLChatLogParser::parse(line, item)) + if (!LLChatLogParser::parse(line, item, load_params)) { item[IM_TEXT] = line; } @@ -500,10 +503,11 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const } } -bool LLChatLogParser::parse(std::string& raw, LLSD& im) +bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params) { if (!raw.length()) return false; + bool cut_off_todays_date = parse_params.has("cut_off_todays_date") ? parse_params["cut_off_todays_date"].asBoolean() : true; im = LLSD::emptyMap(); //matching a timestamp @@ -518,7 +522,12 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im) boost::trim(timestamp); timestamp.erase(0, 1); timestamp.erase(timestamp.length()-1, 1); - LLLogChatTimeScanner::instance().checkAndCutOffDate(timestamp); + + if (cut_off_todays_date) + { + LLLogChatTimeScanner::instance().checkAndCutOffDate(timestamp); + } + im[IM_TIME] = timestamp; } else diff --git a/indra/newview/lllogchat.h b/indra/newview/lllogchat.h index 95f83e64e5..d3e9adcc37 100644 --- a/indra/newview/lllogchat.h +++ b/indra/newview/lllogchat.h @@ -55,7 +55,7 @@ public: void (*callback)(ELogLineType, const LLSD&, void*), void* userdata); - static void loadChatHistory(const std::string& file_name, std::list& messages, bool load_all_history = false); + static void loadChatHistory(const std::string& file_name, std::list& messages, const LLSD& load_params = LLSD()); private: static std::string cleanFileName(std::string filename); }; @@ -105,7 +105,7 @@ public: * * @return false if failed to parse mandatory data - message text */ - static bool parse(std::string& raw, LLSD& im); + static bool parse(std::string& raw, LLSD& im, const LLSD& parse_params = LLSD()); protected: LLChatLogParser(); -- cgit v1.3 From fcb010e835d9b894ba6d1012ac8e3a85c5ab3400 Mon Sep 17 00:00:00 2001 From: Paul ProductEngine Date: Wed, 19 Sep 2012 19:20:21 +0300 Subject: CHUI-338 FIXED (LLAvatarNameResponder warning shown in debug console when using spinner to page through chat history viewer) - Trying to restore avatarID by its name before appending message to chat history. - Also prevented requesting avatar name by null LLUUID in LLAvatarIconCtrl::setValue --- indra/newview/llavatariconctrl.cpp | 7 ++-- indra/newview/llfloaterconversationpreview.cpp | 27 ++++++++++++--- indra/newview/llnearbychat.cpp | 40 +++++++++++----------- indra/newview/llnearbychat.h | 2 ++ .../xui/en/floater_conversation_preview.xml | 2 +- 5 files changed, 50 insertions(+), 28 deletions(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llavatariconctrl.cpp b/indra/newview/llavatariconctrl.cpp index b539ac38ed..62c6c6763b 100755 --- a/indra/newview/llavatariconctrl.cpp +++ b/indra/newview/llavatariconctrl.cpp @@ -245,9 +245,10 @@ void LLAvatarIconCtrl::setValue(const LLSD& value) LLIconCtrl::setValue(value); } - LLAvatarNameCache::get(mAvatarId, - boost::bind(&LLAvatarIconCtrl::onAvatarNameCache, - this, _1, _2)); + if (mAvatarId != LLUUID::null) + { + LLAvatarNameCache::get(mAvatarId, boost::bind(&LLAvatarIconCtrl::onAvatarNameCache, this, _1, _2)); + } } bool LLAvatarIconCtrl::updateFromCache() diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index 88efc39764..a3825eafc8 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -29,6 +29,7 @@ #include "llfloaterconversationpreview.h" #include "llimview.h" #include "lllineeditor.h" +#include "llnearbychat.h" #include "llspinctrl.h" #include "lltrans.h" @@ -43,7 +44,6 @@ LLFloaterConversationPreview::LLFloaterConversationPreview(const LLSD& session_i BOOL LLFloaterConversationPreview::postBuild() { mChatHistory = getChild("chat_history"); - getChild("more_history")->setCommitCallback(boost::bind(&LLFloaterConversationPreview::onMoreHistoryBtnClick, this)); const LLConversation* conv = LLConversationLog::instance().getConversation(mSessionID); std::string name; @@ -119,20 +119,39 @@ void LLFloaterConversationPreview::showHistory() { LLSD msg = *iter; + LLUUID from_id = LLUUID::null; std::string time = msg["time"].asString(); - LLUUID from_id = msg["from_id"].asUUID(); std::string from = msg["from"].asString(); std::string message = msg["message"].asString(); - bool is_history = msg["is_history"].asBoolean(); + + if (msg["from_id"].isDefined()) + { + from_id = msg["from_id"].asUUID(); + } + else + { + std::string legacy_name = gCacheName->buildLegacyName(from); + gCacheName->getUUID(legacy_name, from_id); + } LLChat chat; chat.mFromID = from_id; chat.mSessionID = mSessionID; chat.mFromName = from; chat.mTimeStr = time; - chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; + chat.mChatStyle = CHAT_STYLE_HISTORY; chat.mText = message; + if (from_id.isNull() && SYSTEM_FROM == from) + { + chat.mSourceType = CHAT_SOURCE_SYSTEM; + + } + else if (from_id.isNull()) + { + chat.mSourceType = LLNearbyChat::isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; + } + mChatHistory->appendMessage(chat); } diff --git a/indra/newview/llnearbychat.cpp b/indra/newview/llnearbychat.cpp index a803b35aa8..76626bd5a6 100644 --- a/indra/newview/llnearbychat.cpp +++ b/indra/newview/llnearbychat.cpp @@ -69,26 +69,6 @@ S32 LLNearbyChat::sLastSpecialChatChannel = 0; -// --- function in the global namespace :( --- -bool isWordsName(const std::string& name) -{ - // checking to see if it's display name plus username in parentheses - S32 open_paren = name.find(" (", 0); - S32 close_paren = name.find(')', 0); - - if (open_paren != std::string::npos && - close_paren == name.length()-1) - { - return true; - } - else - { - //checking for a single space - S32 pos = name.find(' ', 0); - return std::string::npos != pos && name.rfind(' ', name.length()) == pos && 0 != pos && name.length()-1 != pos; - } -} - const S32 EXPANDED_HEIGHT = 266; const S32 COLLAPSED_HEIGHT = 60; const S32 EXPANDED_MIN_HEIGHT = 150; @@ -717,6 +697,26 @@ void LLNearbyChat::sendChatFromViewer(const LLWString &wtext, EChatType type, BO send_chat_from_viewer(utf8_out_text, type, channel); } +// static +bool LLNearbyChat::isWordsName(const std::string& name) +{ + // checking to see if it's display name plus username in parentheses + S32 open_paren = name.find(" (", 0); + S32 close_paren = name.find(')', 0); + + if (open_paren != std::string::npos && + close_paren == name.length()-1) + { + return true; + } + else + { + //checking for a single space + S32 pos = name.find(' ', 0); + return std::string::npos != pos && name.rfind(' ', name.length()) == pos && 0 != pos && name.length()-1 != pos; + } +} + // static void LLNearbyChat::startChat(const char* line) { diff --git a/indra/newview/llnearbychat.h b/indra/newview/llnearbychat.h index 2cbafbfa62..648098113a 100644 --- a/indra/newview/llnearbychat.h +++ b/indra/newview/llnearbychat.h @@ -79,6 +79,8 @@ public: static void sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate); static void sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate); + static bool isWordsName(const std::string& name); + void showHistory(); protected: diff --git a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml index 0e5af67f68..6f1ddaaf4f 100644 --- a/indra/newview/skins/default/xui/en/floater_conversation_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_conversation_preview.xml @@ -52,7 +52,7 @@ width="50"/> Date: Fri, 9 Nov 2012 12:45:36 +0200 Subject: CHUI-337 FIXED: To avoid confusion with a classes "...conversation..." and in accordance with the naming convention in the project, some classes and corresponding files should be renamed: LLIMConversation -> LLFloaterIMSessionTab LLIMFloater -> LLFloaterIMSession LLNearbyChat -> LLFloaterIMNearbyChat LLIMFloaterContainer -> LLFloaterIMContainer LLNearbyChatBarListener -> LLFloaterIMNearbyChatListener LLNearbyChatHandler -> LLFloaterIMNearbyChatHandler --- indra/newview/CMakeLists.txt | 24 +- indra/newview/llagent.cpp | 6 +- indra/newview/llappviewer.cpp | 4 +- indra/newview/llavataractions.cpp | 6 +- indra/newview/llavatariconctrl.cpp | 2 +- indra/newview/llchatbar.cpp | 2 +- indra/newview/llchatitemscontainerctrl.cpp | 38 +- indra/newview/llchatitemscontainerctrl.h | 8 +- indra/newview/llchiclet.cpp | 16 +- indra/newview/llchiclet.h | 2 +- indra/newview/llchicletbar.cpp | 10 +- indra/newview/llconversationlog.cpp | 6 +- indra/newview/llconversationlog.h | 4 +- indra/newview/llconversationloglistitem.cpp | 6 +- indra/newview/llconversationloglistitem.h | 2 +- indra/newview/llconversationview.cpp | 14 +- indra/newview/llconversationview.h | 8 +- indra/newview/llfloaterconversationpreview.cpp | 4 +- indra/newview/llfloaterimcontainer.cpp | 1557 +++++++++++++++++++++++ indra/newview/llfloaterimcontainer.h | 178 +++ indra/newview/llfloaterimnearbychat.cpp | 867 +++++++++++++ indra/newview/llfloaterimnearbychat.h | 125 ++ indra/newview/llfloaterimnearbychathandler.cpp | 630 +++++++++ indra/newview/llfloaterimnearbychathandler.h | 54 + indra/newview/llfloaterimnearbychatlistener.cpp | 100 ++ indra/newview/llfloaterimnearbychatlistener.h | 50 + indra/newview/llfloaterimsession.cpp | 1202 +++++++++++++++++ indra/newview/llfloaterimsession.h | 196 +++ indra/newview/llfloaterimsessiontab.cpp | 743 +++++++++++ indra/newview/llfloaterimsessiontab.h | 176 +++ indra/newview/llfloaterpreference.cpp | 6 +- indra/newview/llfloatertranslationsettings.cpp | 4 +- indra/newview/llgesturemgr.cpp | 4 +- indra/newview/llgroupactions.cpp | 4 +- indra/newview/llgroupiconctrl.cpp | 2 +- indra/newview/llimconversation.cpp | 743 ----------- indra/newview/llimconversation.h | 176 --- indra/newview/llimfloater.cpp | 1202 ----------------- indra/newview/llimfloater.h | 196 --- indra/newview/llimfloatercontainer.cpp | 1557 ----------------------- indra/newview/llimfloatercontainer.h | 178 --- indra/newview/llimpanel.cpp | 2 +- indra/newview/llimview.cpp | 36 +- indra/newview/llinventorybridge.cpp | 4 +- indra/newview/llinventorypanel.cpp | 4 +- indra/newview/llnearbychat.cpp | 867 ------------- indra/newview/llnearbychat.h | 125 -- indra/newview/llnearbychatbarlistener.cpp | 100 -- indra/newview/llnearbychatbarlistener.h | 50 - indra/newview/llnearbychathandler.cpp | 630 --------- indra/newview/llnearbychathandler.h | 54 - indra/newview/llnotificationhandler.h | 2 +- indra/newview/llnotificationhandlerutil.cpp | 10 +- indra/newview/llnotificationmanager.cpp | 4 +- indra/newview/llnotificationmanager.h | 2 +- indra/newview/llnotificationtiphandler.cpp | 6 +- indra/newview/llparticipantlist.cpp | 6 +- indra/newview/llscreenchannel.cpp | 2 +- indra/newview/llscriptfloater.cpp | 2 +- indra/newview/llstartup.cpp | 8 +- indra/newview/lltoastnotifypanel.cpp | 2 +- indra/newview/llviewerfloaterreg.cpp | 12 +- indra/newview/llviewergesture.cpp | 4 +- indra/newview/llviewerkeyboard.cpp | 10 +- indra/newview/llviewermessage.cpp | 6 +- indra/newview/llviewerwindow.cpp | 6 +- 66 files changed, 6033 insertions(+), 6033 deletions(-) create mode 100644 indra/newview/llfloaterimcontainer.cpp create mode 100644 indra/newview/llfloaterimcontainer.h create mode 100644 indra/newview/llfloaterimnearbychat.cpp create mode 100644 indra/newview/llfloaterimnearbychat.h create mode 100644 indra/newview/llfloaterimnearbychathandler.cpp create mode 100644 indra/newview/llfloaterimnearbychathandler.h create mode 100644 indra/newview/llfloaterimnearbychatlistener.cpp create mode 100644 indra/newview/llfloaterimnearbychatlistener.h create mode 100644 indra/newview/llfloaterimsession.cpp create mode 100644 indra/newview/llfloaterimsession.h create mode 100644 indra/newview/llfloaterimsessiontab.cpp create mode 100644 indra/newview/llfloaterimsessiontab.h delete mode 100644 indra/newview/llimconversation.cpp delete mode 100644 indra/newview/llimconversation.h delete mode 100644 indra/newview/llimfloater.cpp delete mode 100644 indra/newview/llimfloater.h delete mode 100644 indra/newview/llimfloatercontainer.cpp delete mode 100644 indra/newview/llimfloatercontainer.h delete mode 100644 indra/newview/llnearbychat.cpp delete mode 100644 indra/newview/llnearbychat.h delete mode 100644 indra/newview/llnearbychatbarlistener.cpp delete mode 100644 indra/newview/llnearbychatbarlistener.h delete mode 100644 indra/newview/llnearbychathandler.cpp delete mode 100644 indra/newview/llnearbychathandler.h (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 2c7e96f1e4..45c719f28d 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -299,9 +299,9 @@ set(viewer_SOURCE_FILES llhudrender.cpp llhudtext.cpp llhudview.cpp - llimconversation.cpp - llimfloater.cpp - llimfloatercontainer.cpp + llfloaterimsessiontab.cpp + llfloaterimsession.cpp + llfloaterimcontainer.cpp llimhandler.cpp llimview.cpp llinspect.cpp @@ -353,9 +353,9 @@ set(viewer_SOURCE_FILES llnameeditor.cpp llnamelistctrl.cpp llnavigationbar.cpp - llnearbychat.cpp - llnearbychathandler.cpp - llnearbychatbarlistener.cpp + llfloaterimnearbychat.cpp + llfloaterimnearbychathandler.cpp + llfloaterimnearbychatlistener.cpp llnetmap.cpp llnotificationalerthandler.cpp llnotificationgrouphandler.cpp @@ -882,9 +882,9 @@ set(viewer_HEADER_FILES llhudrender.h llhudtext.h llhudview.h - llimconversation.h - llimfloater.h - llimfloatercontainer.h + llfloaterimsessiontab.h + llfloaterimsession.h + llfloaterimcontainer.h llimview.h llinspect.h llinspectavatar.h @@ -936,9 +936,9 @@ set(viewer_HEADER_FILES llnameeditor.h llnamelistctrl.h llnavigationbar.h - llnearbychat.h - llnearbychathandler.h - llnearbychatbarlistener.h + llfloaterimnearbychat.h + llfloaterimnearbychathandler.h + llfloaterimnearbychatlistener.h llnetmap.h llnotificationhandler.h llnotificationmanager.h diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index cefd5c72e8..9c3a7ac45b 100755 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -54,7 +54,7 @@ #include "llmorphview.h" #include "llmoveview.h" #include "llnavigationbar.h" // to show/hide navigation bar when changing mouse look state -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llnotificationsutil.h" #include "llpanelpathfindingrebakenavmesh.h" #include "llpaneltopinfobar.h" @@ -1899,7 +1899,7 @@ void LLAgent::startTyping() { sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_START); } - (LLFloaterReg::getTypedInstance("nearby_chat"))-> + (LLFloaterReg::getTypedInstance("nearby_chat"))-> sendChatFromViewer("", CHAT_TYPE_START, FALSE); } @@ -1912,7 +1912,7 @@ void LLAgent::stopTyping() { clearRenderState(AGENT_STATE_TYPING); sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_STOP); - (LLFloaterReg::getTypedInstance("nearby_chat"))-> + (LLFloaterReg::getTypedInstance("nearby_chat"))-> sendChatFromViewer("", CHAT_TYPE_STOP, FALSE); } } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index b23e5866dc..8abe9bcfc1 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -42,7 +42,7 @@ #include "llagentcamera.h" #include "llagentlanguage.h" #include "llagentwearables.h" -#include "llimfloatercontainer.h" +#include "llfloaterimcontainer.h" #include "llwindow.h" #include "llviewerstats.h" #include "llviewerstatsrecorder.h" @@ -1204,7 +1204,7 @@ bool LLAppViewer::mainLoop() LLVoiceChannel::initClass(); LLVoiceClient::getInstance()->init(gServicePump); - LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLIMFloaterContainer::onCurrentChannelChanged, _1), true); + LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLFloaterIMContainer::onCurrentChannelChanged, _1), true); LLTimer frameTimer,idleTimer; LLTimer debugTime; LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index 130428f3c0..4f57498506 100755 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -56,7 +56,7 @@ #include "llinventorybridge.h" #include "llinventorymodel.h" // for gInventory.findCategoryUUIDForType #include "llinventorypanel.h" -#include "llimfloatercontainer.h" +#include "llfloaterimcontainer.h" #include "llimview.h" // for gIMMgr #include "llmutelist.h" #include "llnotificationsutil.h" // for LLNotificationsUtil @@ -184,7 +184,7 @@ static void on_avatar_name_cache_start_im(const LLUUID& agent_id, LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id); if (session_id != LLUUID::null) { - LLIMFloaterContainer::getInstance()->showConversation(session_id); + LLFloaterIMContainer::getInstance()->showConversation(session_id); } make_ui_sound("UISndStartIM"); } @@ -302,7 +302,7 @@ void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& float return; } - LLIMFloaterContainer::getInstance()->showConversation(session_id); + LLFloaterIMContainer::getInstance()->showConversation(session_id); make_ui_sound("UISndStartIM"); } diff --git a/indra/newview/llavatariconctrl.cpp b/indra/newview/llavatariconctrl.cpp index 6355f0db56..b7278d4a3a 100755 --- a/indra/newview/llavatariconctrl.cpp +++ b/indra/newview/llavatariconctrl.cpp @@ -38,7 +38,7 @@ #include "llmenugl.h" #include "lluictrlfactory.h" #include "llagentdata.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" // library includes #include "llavatarnamecache.h" diff --git a/indra/newview/llchatbar.cpp b/indra/newview/llchatbar.cpp index d6095cce07..27138e6c06 100644 --- a/indra/newview/llchatbar.cpp +++ b/indra/newview/llchatbar.cpp @@ -672,7 +672,7 @@ void LLChatBar::onCommitGesture(LLUICtrl* ctrl) /* Cruft - global gChatHandler declared below has been commented out, - so this class is never used. See similar code in llnearbychatbar.cpp + so this class is never used. See similar code in llfloaterimnearbychatbar.cpp class LLChatHandler : public LLCommandHandler { public: diff --git a/indra/newview/llchatitemscontainerctrl.cpp b/indra/newview/llchatitemscontainerctrl.cpp index f1b5c42ef3..a1a9463d43 100644 --- a/indra/newview/llchatitemscontainerctrl.cpp +++ b/indra/newview/llchatitemscontainerctrl.cpp @@ -35,7 +35,7 @@ #include "llfloaterreg.h" #include "lllocalcliprect.h" #include "lltrans.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llviewercontrol.h" #include "llagentdata.h" @@ -81,18 +81,18 @@ public: LLObjectHandler gObjectHandler; //******************************************************************************************************************* -//LLNearbyChatToastPanel +//LLFloaterIMNearbyChatToastPanel //******************************************************************************************************************* -LLNearbyChatToastPanel* LLNearbyChatToastPanel::createInstance() +LLFloaterIMNearbyChatToastPanel* LLFloaterIMNearbyChatToastPanel::createInstance() { - LLNearbyChatToastPanel* item = new LLNearbyChatToastPanel(); + LLFloaterIMNearbyChatToastPanel* item = new LLFloaterIMNearbyChatToastPanel(); item->buildFromFile("panel_chat_item.xml"); item->setFollows(FOLLOWS_NONE); return item; } -void LLNearbyChatToastPanel::reshape (S32 width, S32 height, BOOL called_from_parent ) +void LLFloaterIMNearbyChatToastPanel::reshape (S32 width, S32 height, BOOL called_from_parent ) { LLPanel::reshape(width, height,called_from_parent); @@ -121,12 +121,12 @@ void LLNearbyChatToastPanel::reshape (S32 width, S32 height, BOOL called_from_p msg_text->setRect(msg_text_rect); } -BOOL LLNearbyChatToastPanel::postBuild() +BOOL LLFloaterIMNearbyChatToastPanel::postBuild() { return LLPanel::postBuild(); } -void LLNearbyChatToastPanel::addMessage(LLSD& notification) +void LLFloaterIMNearbyChatToastPanel::addMessage(LLSD& notification) { std::string messageText = notification["message"].asString(); // UTF-8 line of text @@ -178,7 +178,7 @@ void LLNearbyChatToastPanel::addMessage(LLSD& notification) } -void LLNearbyChatToastPanel::init(LLSD& notification) +void LLFloaterIMNearbyChatToastPanel::init(LLSD& notification) { std::string messageText = notification["message"].asString(); // UTF-8 line of text std::string fromName = notification["from"].asString(); // agent or object name @@ -273,7 +273,7 @@ void LLNearbyChatToastPanel::init(LLSD& notification) mIsDirty = true;//will set Avatar Icon in draw } -void LLNearbyChatToastPanel::snapToMessageHeight () +void LLFloaterIMNearbyChatToastPanel::snapToMessageHeight () { LLChatMsgBox* text_box = getChild("msg_text", false); S32 new_height = llmax (text_box->getTextPixelHeight() + 2*text_box->getVPad() + 2*msg_height_pad, 25); @@ -288,22 +288,22 @@ void LLNearbyChatToastPanel::snapToMessageHeight () } -void LLNearbyChatToastPanel::onMouseLeave (S32 x, S32 y, MASK mask) +void LLFloaterIMNearbyChatToastPanel::onMouseLeave (S32 x, S32 y, MASK mask) { } -void LLNearbyChatToastPanel::onMouseEnter (S32 x, S32 y, MASK mask) +void LLFloaterIMNearbyChatToastPanel::onMouseEnter (S32 x, S32 y, MASK mask) { if(mSourceType != CHAT_SOURCE_AGENT) return; } -BOOL LLNearbyChatToastPanel::handleMouseDown (S32 x, S32 y, MASK mask) +BOOL LLFloaterIMNearbyChatToastPanel::handleMouseDown (S32 x, S32 y, MASK mask) { return LLPanel::handleMouseDown(x,y,mask); } -BOOL LLNearbyChatToastPanel::handleMouseUp (S32 x, S32 y, MASK mask) +BOOL LLFloaterIMNearbyChatToastPanel::handleMouseUp (S32 x, S32 y, MASK mask) { /* fix for request EXT-4780 @@ -323,16 +323,16 @@ BOOL LLNearbyChatToastPanel::handleMouseUp (S32 x, S32 y, MASK mask) return TRUE; else { - (LLFloaterReg::getTypedInstance("nearby_chat"))->showHistory(); + (LLFloaterReg::getTypedInstance("nearby_chat"))->showHistory(); return FALSE; } } - (LLFloaterReg::getTypedInstance("nearby_chat"))->showHistory(); + (LLFloaterReg::getTypedInstance("nearby_chat"))->showHistory(); return LLPanel::handleMouseUp(x,y,mask); } -void LLNearbyChatToastPanel::setHeaderVisibility(EShowItemHeader e) +void LLFloaterIMNearbyChatToastPanel::setHeaderVisibility(EShowItemHeader e) { LLUICtrl* icon = getChild("avatar_icon", false); if(icon) @@ -340,7 +340,7 @@ void LLNearbyChatToastPanel::setHeaderVisibility(EShowItemHeader e) } -bool LLNearbyChatToastPanel::canAddText () +bool LLFloaterIMNearbyChatToastPanel::canAddText () { LLChatMsgBox* msg_text = findChild("msg_text"); if(!msg_text) @@ -348,7 +348,7 @@ bool LLNearbyChatToastPanel::canAddText () return msg_text->getLineCount()<10; } -BOOL LLNearbyChatToastPanel::handleRightMouseDown(S32 x, S32 y, MASK mask) +BOOL LLFloaterIMNearbyChatToastPanel::handleRightMouseDown(S32 x, S32 y, MASK mask) { LLUICtrl* avatar_icon = getChild("avatar_icon", false); @@ -360,7 +360,7 @@ BOOL LLNearbyChatToastPanel::handleRightMouseDown(S32 x, S32 y, MASK mask) return TRUE; return LLPanel::handleRightMouseDown(x,y,mask); } -void LLNearbyChatToastPanel::draw() +void LLFloaterIMNearbyChatToastPanel::draw() { LLPanel::draw(); diff --git a/indra/newview/llchatitemscontainerctrl.h b/indra/newview/llchatitemscontainerctrl.h index 89b0c4f37a..54b6499d52 100644 --- a/indra/newview/llchatitemscontainerctrl.h +++ b/indra/newview/llchatitemscontainerctrl.h @@ -40,18 +40,18 @@ typedef enum e_show_item_header CHATITEMHEADER_SHOW_BOTH } EShowItemHeader; -class LLNearbyChatToastPanel : public LLPanel +class LLFloaterIMNearbyChatToastPanel : public LLPanel { protected: - LLNearbyChatToastPanel() + LLFloaterIMNearbyChatToastPanel() : mIsDirty(false), mSourceType(CHAT_SOURCE_OBJECT) {}; public: - ~LLNearbyChatToastPanel(){} + ~LLFloaterIMNearbyChatToastPanel(){} - static LLNearbyChatToastPanel* createInstance(); + static LLFloaterIMNearbyChatToastPanel* createInstance(); const LLUUID& getFromID() const { return mFromID;} const std::string& getFromName() const { return mFromName; } diff --git a/indra/newview/llchiclet.cpp b/indra/newview/llchiclet.cpp index e328186fd6..64d8a68a99 100644 --- a/indra/newview/llchiclet.cpp +++ b/indra/newview/llchiclet.cpp @@ -33,8 +33,8 @@ #include "lleventtimer.h" #include "llgroupactions.h" #include "lliconctrl.h" -#include "llimfloater.h" -#include "llimfloatercontainer.h" +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" #include "llimview.h" #include "llfloaterreg.h" #include "lllocalcliprect.h" @@ -605,7 +605,7 @@ bool LLIMChiclet::getShowNewMessagesIcon() void LLIMChiclet::onMouseDown() { - LLIMFloater::toggle(getSessionId()); + LLFloaterIMSession::toggle(getSessionId()); setCounter(0); } @@ -754,7 +754,7 @@ void LLIMP2PChiclet::updateMenuItems() if(getSessionId().isNull()) return; - LLIMFloater* open_im_floater = LLIMFloater::findInstance(getSessionId()); + LLFloaterIMSession* open_im_floater = LLFloaterIMSession::findInstance(getSessionId()); bool open_window_exists = open_im_floater && open_im_floater->getVisible(); mPopupMenu->getChild("Send IM")->setEnabled(!open_window_exists); @@ -1030,7 +1030,7 @@ void LLIMGroupChiclet::updateMenuItems() if(getSessionId().isNull()) return; - LLIMFloater* open_im_floater = LLIMFloater::findInstance(getSessionId()); + LLFloaterIMSession* open_im_floater = LLFloaterIMSession::findInstance(getSessionId()); bool open_window_exists = open_im_floater && open_im_floater->getVisible(); mPopupMenu->getChild("Chat")->setEnabled(!open_window_exists); } @@ -1116,7 +1116,7 @@ void LLChicletPanel::onMessageCountChanged(const LLSD& data) LLUUID session_id = data["session_id"].asUUID(); S32 unread = data["participant_unread"].asInteger(); - LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); if (im_floater && im_floater->getVisible() && im_floater->hasFocus()) { unread = 0; @@ -1197,7 +1197,7 @@ void LLChicletPanel::onCurrentVoiceChannelChanged(const LLUUID& session_id) chiclet->setShowSpeaker(true); if (gSavedSettings.getBOOL("OpenIMOnVoice")) { - LLIMFloaterContainer::getInstance()->showConversation(session_id); + LLFloaterIMContainer::getInstance()->showConversation(session_id); } } } @@ -1688,7 +1688,7 @@ bool LLChicletPanel::isAnyIMFloaterDoked() for (chiclet_list_t::iterator it = mChicletList.begin(); it != mChicletList.end(); it++) { - LLIMFloater* im_floater = LLFloaterReg::findTypedInstance( + LLFloaterIMSession* im_floater = LLFloaterReg::findTypedInstance( "impanel", (*it)->getSessionId()); if (im_floater != NULL && im_floater->getVisible() && !im_floater->isMinimized() && im_floater->isDocked()) diff --git a/indra/newview/llchiclet.h b/indra/newview/llchiclet.h index 6395f5b694..3c8389e20d 100644 --- a/indra/newview/llchiclet.h +++ b/indra/newview/llchiclet.h @@ -37,7 +37,7 @@ #include "llnotifications.h" class LLMenuGL; -class LLIMFloater; +class LLFloaterIMSession; /** * Class for displaying amount of messages/notifications(unread). diff --git a/indra/newview/llchicletbar.cpp b/indra/newview/llchicletbar.cpp index 3ebb83b336..ad7890b47a 100644 --- a/indra/newview/llchicletbar.cpp +++ b/indra/newview/llchicletbar.cpp @@ -34,7 +34,7 @@ // newview includes #include "llchiclet.h" -#include "llimfloater.h" // for LLIMFloater +#include "llfloaterimsession.h" // for LLFloaterIMSession #include "llpaneltopinfobar.h" #include "llsyswellwindow.h" @@ -95,9 +95,9 @@ void LLChicletBar::sessionAdded(const LLUUID& session_id, const std::string& nam if (session->isP2P() && session->isOtherParticipantAvaline()) return; // Do not spawn chiclet when using the new multitab conversation UI - if (LLIMConversation::isChatMultiTab()) + if (LLFloaterIMSessionTab::isChatMultiTab()) { - LLIMConversation::addToHost(session_id); + LLFloaterIMSessionTab::addToHost(session_id); return; } @@ -109,7 +109,7 @@ void LLChicletBar::sessionAdded(const LLUUID& session_id, const std::string& nam chiclet->setIMSessionName(name); chiclet->setOtherParticipantId(other_participant_id); - LLIMFloater::onIMChicletCreated(session_id); + LLFloaterIMSession::onIMChicletCreated(session_id); } else @@ -124,7 +124,7 @@ void LLChicletBar::sessionRemoved(const LLUUID& session_id) if(getChicletPanel()) { // IM floater should be closed when session removed and associated chiclet closed - LLIMFloater* im_floater = LLFloaterReg::findTypedInstance("impanel", session_id); + LLFloaterIMSession* im_floater = LLFloaterReg::findTypedInstance("impanel", session_id); if (im_floater != NULL && !im_floater->getStartConferenceInSameFloater()) { // Close the IM floater only if we are not planning to close the P2P chat diff --git a/indra/newview/llconversationlog.cpp b/indra/newview/llconversationlog.cpp index 7a5a476efb..3d2b6a5c00 100644 --- a/indra/newview/llconversationlog.cpp +++ b/indra/newview/llconversationlog.cpp @@ -140,15 +140,15 @@ bool LLConversation::isOlderThan(U32 days) const void LLConversation::setListenIMFloaterOpened() { - LLIMFloater* floater = LLIMFloater::findInstance(mSessionID); + LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mSessionID); - bool offline_ims_visible = LLIMFloater::isVisible(floater) && floater->hasFocus(); + bool offline_ims_visible = LLFloaterIMSession::isVisible(floater) && floater->hasFocus(); // we don't need to listen for im floater with this conversation is opened // if floater is already opened or this conversation doesn't have unread offline messages if (mHasOfflineIMs && !offline_ims_visible) { - mIMFloaterShowedConnection = LLIMFloater::setIMFloaterShowedCallback(boost::bind(&LLConversation::onIMFloaterShown, this, _1)); + mIMFloaterShowedConnection = LLFloaterIMSession::setIMFloaterShowedCallback(boost::bind(&LLConversation::onIMFloaterShown, this, _1)); } else { diff --git a/indra/newview/llconversationlog.h b/indra/newview/llconversationlog.h index b92cf0f5e2..7d0b9113c6 100644 --- a/indra/newview/llconversationlog.h +++ b/indra/newview/llconversationlog.h @@ -27,7 +27,7 @@ #define LLCONVERSATIONLOG_H_ #include "llcallingcard.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" #include "llimview.h" class LLConversationLogObserver; @@ -80,7 +80,7 @@ public: private: /* - * If conversation has unread offline messages sets callback for opening LLIMFloater + * If conversation has unread offline messages sets callback for opening LLFloaterIMSession * with this conversation. */ void setListenIMFloaterOpened(); diff --git a/indra/newview/llconversationloglistitem.cpp b/indra/newview/llconversationloglistitem.cpp index b4ae5f19da..9fad0e603e 100644 --- a/indra/newview/llconversationloglistitem.cpp +++ b/indra/newview/llconversationloglistitem.cpp @@ -47,13 +47,13 @@ LLConversationLogListItem::LLConversationLogListItem(const LLConversation* conve { buildFromFile("panel_conversation_log_list_item.xml"); - LLIMFloater* floater = LLIMFloater::findInstance(mConversation->getSessionID()); + LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mConversation->getSessionID()); - bool ims_are_read = LLIMFloater::isVisible(floater) && floater->hasFocus(); + bool ims_are_read = LLFloaterIMSession::isVisible(floater) && floater->hasFocus(); if (mConversation->hasOfflineMessages() && !ims_are_read) { - mIMFloaterShowedConnection = LLIMFloater::setIMFloaterShowedCallback(boost::bind(&LLConversationLogListItem::onIMFloaterShown, this, _1)); + mIMFloaterShowedConnection = LLFloaterIMSession::setIMFloaterShowedCallback(boost::bind(&LLConversationLogListItem::onIMFloaterShown, this, _1)); } } diff --git a/indra/newview/llconversationloglistitem.h b/indra/newview/llconversationloglistitem.h index 1bf7a0ed93..57f72db382 100644 --- a/indra/newview/llconversationloglistitem.h +++ b/indra/newview/llconversationloglistitem.h @@ -26,7 +26,7 @@ #ifndef LLCONVERSATIONLOGLISTITEM_H_ #define LLCONVERSATIONLOGLISTITEM_H_ -#include "llimfloater.h" +#include "llfloaterimsession.h" #include "llpanel.h" class LLTextBox; diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index ac5b2ad6ac..3495d74191 100755 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -32,10 +32,10 @@ #include #include "llagentdata.h" #include "llconversationmodel.h" -#include "llimfloater.h" -#include "llnearbychat.h" -#include "llimconversation.h" -#include "llimfloatercontainer.h" +#include "llfloaterimsession.h" +#include "llfloaterimnearbychat.h" +#include "llfloaterimsessiontab.h" +#include "llfloaterimcontainer.h" #include "llfloaterreg.h" #include "llgroupiconctrl.h" #include "lluictrlfactory.h" @@ -208,7 +208,7 @@ BOOL LLConversationViewSession::handleMouseDown( S32 x, S32 y, MASK mask ) LLConversationItem* item = dynamic_cast(getViewModelItem()); LLUUID session_id = item? item->getUUID() : LLUUID(); - (LLFloaterReg::getTypedInstance("im_container"))-> + (LLFloaterReg::getTypedInstance("im_container"))-> selectConversationPair(session_id, false); return LLFolderViewFolder::handleMouseDown(x, y, mask); @@ -262,7 +262,7 @@ void LLConversationViewSession::setVisibleIfDetached(BOOL visible) // Note: minimized dockable floaters are brought to front hence unminimized when made visible and we don't want that here LLFolderViewModelItem* item = mViewModelItem; LLUUID session_uuid = dynamic_cast(item)->getUUID(); - LLFloater* session_floater = LLIMConversation::getConversation(session_uuid); + LLFloater* session_floater = LLFloaterIMSessionTab::getConversation(session_uuid); if (session_floater && !session_floater->getHost() && !session_floater->isMinimized()) { @@ -505,7 +505,7 @@ BOOL LLConversationViewParticipant::handleMouseDown( S32 x, S32 y, MASK mask ) } LLUUID session_id = item? item->getUUID() : LLUUID(); - (LLFloaterReg::getTypedInstance("im_container"))-> + (LLFloaterReg::getTypedInstance("im_container"))-> selectConversationPair(session_id, false); return LLFolderViewItem::handleMouseDown(x, y, mask); diff --git a/indra/newview/llconversationview.h b/indra/newview/llconversationview.h index 43547d155b..cc6995c207 100755 --- a/indra/newview/llconversationview.h +++ b/indra/newview/llconversationview.h @@ -34,7 +34,7 @@ #include "lloutputmonitorctrl.h" class LLTextBox; -class LLIMFloaterContainer; +class LLFloaterIMContainer; class LLConversationViewSession; class LLConversationViewParticipant; @@ -47,7 +47,7 @@ class LLConversationViewSession : public LLFolderViewFolder public: struct Params : public LLInitParam::Block { - Optional container; + Optional container; Params(); }; @@ -56,7 +56,7 @@ protected: friend class LLUICtrlFactory; LLConversationViewSession( const Params& p ); - LLIMFloaterContainer* mContainer; + LLFloaterIMContainer* mContainer; public: virtual ~LLConversationViewSession(); @@ -107,7 +107,7 @@ public: struct Params : public LLInitParam::Block { - Optional container; + Optional container; Optional participant_id; Optional avatar_icon; Optional info_button; diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index a3825eafc8..c93181c0a1 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -29,7 +29,7 @@ #include "llfloaterconversationpreview.h" #include "llimview.h" #include "lllineeditor.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llspinctrl.h" #include "lltrans.h" @@ -149,7 +149,7 @@ void LLFloaterConversationPreview::showHistory() } else if (from_id.isNull()) { - chat.mSourceType = LLNearbyChat::isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; + chat.mSourceType = LLFloaterIMNearbyChat::isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; } mChatHistory->appendMessage(chat); diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp new file mode 100644 index 0000000000..b20d19d0fd --- /dev/null +++ b/indra/newview/llfloaterimcontainer.cpp @@ -0,0 +1,1557 @@ +/** + * @file llfloaterimcontainer.cpp + * @brief Multifloater containing active IM sessions in separate tab container tabs + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" + +#include "llfloaterreg.h" +#include "lllayoutstack.h" +#include "llfloaterimnearbychat.h" + +#include "llagent.h" +#include "llavataractions.h" +#include "llavatariconctrl.h" +#include "llavatarnamecache.h" +#include "llcallbacklist.h" +#include "llgroupactions.h" +#include "llgroupiconctrl.h" +#include "llfloateravatarpicker.h" +#include "llfloaterpreference.h" +#include "llimview.h" +#include "llnotificationsutil.h" +#include "lltransientfloatermgr.h" +#include "llviewercontrol.h" +#include "llconversationview.h" +#include "llcallbacklist.h" +#include "llworld.h" + +#include "llsdserialize.h" +// +// LLFloaterIMContainer +// +LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed) +: LLMultiFloater(seed), + mExpandCollapseBtn(NULL), + mConversationsRoot(NULL), + mConversationsEventStream("ConversationsEvents"), + mInitialized(false) +{ + mEnableCallbackRegistrar.add("IMFloaterContainer.Check", boost::bind(&LLFloaterIMContainer::isActionChecked, this, _2)); + mCommitCallbackRegistrar.add("IMFloaterContainer.Action", boost::bind(&LLFloaterIMContainer::onCustomAction, this, _2)); + + mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMContainer::checkContextMenuItem, this, _2)); + mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMContainer::enableContextMenuItem, this, _2)); + mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMContainer::doToSelected, this, _2)); + + mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&LLFloaterIMContainer::doToSelectedGroup, this, _2)); + + // Firstly add our self to IMSession observers, so we catch session events + LLIMMgr::getInstance()->addSessionObserver(this); + + mAutoResize = FALSE; + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); +} + +LLFloaterIMContainer::~LLFloaterIMContainer() +{ + mConversationsEventStream.stopListening("ConversationsRefresh"); + + gIdleCallbacks.deleteFunction(idle, this); + + mNewMessageConnection.disconnect(); + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); + + gSavedPerAccountSettings.setBOOL("ConversationsListPaneCollapsed", mConversationsPane->isCollapsed()); + gSavedPerAccountSettings.setBOOL("ConversationsMessagePaneCollapsed", mMessagesPane->isCollapsed()); + + if (!LLSingleton::destroyed()) + { + LLIMMgr::getInstance()->removeSessionObserver(this); + } +} + +void LLFloaterIMContainer::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) +{ + addConversationListItem(session_id); + LLFloaterIMSessionTab::addToHost(session_id); +} + +void LLFloaterIMContainer::sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) +{ + selectConversation(session_id); +} + +void LLFloaterIMContainer::sessionVoiceOrIMStarted(const LLUUID& session_id) +{ + addConversationListItem(session_id); + LLFloaterIMSessionTab::addToHost(session_id); +} + +void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) +{ + // *TODO: We should do this *without* delete and recreate + addConversationListItem(new_session_id, removeConversationListItem(old_session_id)); +} + +void LLFloaterIMContainer::sessionRemoved(const LLUUID& session_id) +{ + removeConversationListItem(session_id); +} + +// static +void LLFloaterIMContainer::onCurrentChannelChanged(const LLUUID& session_id) +{ + if (session_id != LLUUID::null) + { + LLFloaterIMContainer::getInstance()->showConversation(session_id); + } +} + + +BOOL LLFloaterIMContainer::postBuild() +{ + mNewMessageConnection = LLIMModel::instance().mNewMsgSignal.connect(boost::bind(&LLFloaterIMContainer::onNewMessageReceived, this, _1)); + // Do not call base postBuild to not connect to mCloseSignal to not close all floaters via Close button + // mTabContainer will be initialized in LLMultiFloater::addChild() + + setTabContainer(getChild("im_box_tab_container")); + + mConversationsStack = getChild("conversations_stack"); + mConversationsPane = getChild("conversations_layout_panel"); + mMessagesPane = getChild("messages_layout_panel"); + + mConversationsListPanel = getChild("conversations_list_panel"); + + // Open IM session with selected participant on double click event + mConversationsListPanel->setDoubleClickCallback(boost::bind(&LLFloaterIMContainer::doToSelected, this, LLSD("im"))); + + // Create the root model and view for all conversation sessions + LLConversationItem* base_item = new LLConversationItem(getRootViewModel()); + + LLFolderView::Params p(LLUICtrlFactory::getDefaultParams()); + p.name = getName(); + p.title = getLabel(); + p.rect = LLRect(0, 0, getRect().getWidth(), 0); + p.parent_panel = mConversationsListPanel; + p.tool_tip = p.name; + p.listener = base_item; + p.view_model = &mConversationViewModel; + p.root = NULL; + p.use_ellipses = true; + p.options_menu = "menu_conversation.xml"; + mConversationsRoot = LLUICtrlFactory::create(p); + mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); + + // Add listener to conversation model events + mConversationsEventStream.listen("ConversationsRefresh", boost::bind(&LLFloaterIMContainer::onConversationModelEvent, this, _1)); + + // a scroller for folder view + LLRect scroller_view_rect = mConversationsListPanel->getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams()); + scroller_params.rect(scroller_view_rect); + + LLScrollContainer* scroller = LLUICtrlFactory::create(scroller_params); + scroller->setFollowsAll(); + mConversationsListPanel->addChild(scroller); + scroller->addChild(mConversationsRoot); + mConversationsRoot->setScrollContainer(scroller); + mConversationsRoot->setFollowsAll(); + mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox); + + addConversationListItem(LLUUID()); // manually add nearby chat + + mExpandCollapseBtn = getChild("expand_collapse_btn"); + mExpandCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onExpandCollapseButtonClicked, this)); + + childSetAction("add_btn", boost::bind(&LLFloaterIMContainer::onAddButtonClicked, this)); + + collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); + collapseConversationsPane(gSavedPerAccountSettings.getBOOL("ConversationsListPaneCollapsed")); + LLAvatarNameCache::addUseDisplayNamesCallback(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate)); + + if (! mMessagesPane->isCollapsed()) + { + S32 list_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth"); + LLRect list_size = mConversationsPane->getRect(); + S32 left_pad = mConversationsListPanel->getRect().mLeft; + list_size.mRight = list_size.mLeft + list_width - left_pad; + + mConversationsPane->handleReshape(list_size, TRUE); + } + + // Init the sort order now that the root had been created + setSortOrder(LLConversationSort(gSavedSettings.getU32("ConversationSortOrder"))); + + mInitialized = true; + + // Add callbacks: + // We'll take care of view updates on idle + gIdleCallbacks.addFunction(idle, this); + // When display name option change, we need to reload all participant names + LLAvatarNameCache::addUseDisplayNamesCallback(boost::bind(&LLFloaterIMContainer::processParticipantsStyleUpdate, this)); + + return TRUE; +} + +void LLFloaterIMContainer::onOpen(const LLSD& key) +{ + LLMultiFloater::onOpen(key); + openNearbyChat(); +} + +// virtual +void LLFloaterIMContainer::addFloater(LLFloater* floaterp, + BOOL select_added_floater, + LLTabContainer::eInsertionPoint insertion_point) +{ + if(!floaterp) return; + + // already here + if (floaterp->getHost() == this) + { + openFloater(floaterp->getKey()); + return; + } + + // Make sure the message panel is open when adding a floater or it stays mysteriously hidden + collapseMessagesPane(false); + + // Add the floater + LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point); + + LLUUID session_id = floaterp->getKey(); + + LLIconCtrl* icon = 0; + + if(gAgent.isInGroup(session_id, TRUE)) + { + LLGroupIconCtrl::Params icon_params; + icon_params.group_id = session_id; + icon = LLUICtrlFactory::instance().create(icon_params); + + mSessions[session_id] = floaterp; + floaterp->mCloseSignal.connect(boost::bind(&LLFloaterIMContainer::onCloseFloater, this, session_id)); + } + else + { LLUUID avatar_id = session_id.notNull()? + LLIMModel::getInstance()->getOtherParticipantID(session_id) : LLUUID(); + + LLAvatarIconCtrl::Params icon_params; + icon_params.avatar_id = avatar_id; + icon = LLUICtrlFactory::instance().create(icon_params); + + mSessions[session_id] = floaterp; + floaterp->mCloseSignal.connect(boost::bind(&LLFloaterIMContainer::onCloseFloater, this, session_id)); + } + + // forced resize of the floater + LLRect wrapper_rect = this->mTabContainer->getLocalRect(); + floaterp->setRect(wrapper_rect); + + mTabContainer->setTabImage(floaterp, icon); +} + + +void LLFloaterIMContainer::onCloseFloater(LLUUID& id) +{ + mSessions.erase(id); + setFocus(TRUE); +} + +// virtual +void LLFloaterIMContainer::computeResizeLimits(S32& new_min_width, S32& new_min_height) +{ + // possibly increase floater's minimum height according to children's minimums + for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) + { + LLFloater* floaterp = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); + if (floaterp) + { + new_min_height = llmax(new_min_height, floaterp->getMinHeight()); + } + } + + S32 conversations_pane_min_dim = mConversationsPane->getRelevantMinDim(); + S32 messages_pane_min_dim = mMessagesPane->getRelevantMinDim(); + + // set floater's minimum width according to relevant minimal children's dimensionals + new_min_width = conversations_pane_min_dim + messages_pane_min_dim + LLPANEL_BORDER_WIDTH*2; +} + +void LLFloaterIMContainer::onNewMessageReceived(const LLSD& data) +{ + LLUUID session_id = data["session_id"].asUUID(); + LLFloater* floaterp = get_ptr_in_map(mSessions, session_id); + LLFloater* current_floater = LLMultiFloater::getActiveFloater(); + + if(floaterp && current_floater && floaterp != current_floater) + { + if(LLMultiFloater::isFloaterFlashing(floaterp)) + LLMultiFloater::setFloaterFlashing(floaterp, FALSE); + LLMultiFloater::setFloaterFlashing(floaterp, TRUE); + } +} + +void LLFloaterIMContainer::onExpandCollapseButtonClicked() +{ + if (mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed() + && gSavedPerAccountSettings.getBOOL("ConversationsExpandMessagePaneFirst")) + { + // Expand the messages pane from ultra minimized state + // if it was collapsed last in order. + collapseMessagesPane(false); + } + else + { + collapseConversationsPane(!mConversationsPane->isCollapsed()); + } + selectConversation(mSelectedSession); +} + +LLFloaterIMContainer* LLFloaterIMContainer::findInstance() +{ + return LLFloaterReg::findTypedInstance("im_container"); +} + +LLFloaterIMContainer* LLFloaterIMContainer::getInstance() +{ + return LLFloaterReg::getTypedInstance("im_container"); +} + +// Update all participants in the conversation lists +void LLFloaterIMContainer::processParticipantsStyleUpdate() +{ + // On each session in mConversationsItems + for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) + { + // Get the current session descriptors + LLConversationItem* session_model = it_session->second; + // Iterate through each model participant child + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = session_model->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = session_model->getChildrenEnd(); + while (current_participant_model != end_participant_model) + { + LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); + // Get the avatar name for this participant id from the cache and update the model + participant_model->fetchAvatarName(); + // Next participant + current_participant_model++; + } + } +} + +// static +void LLFloaterIMContainer::idle(void* user_data) +{ + LLFloaterIMContainer* self = static_cast(user_data); + + // Update the distance to agent in the nearby chat session if required + // Note: it makes no sense of course to update the distance in other session + if (self->mConversationViewModel.getSorter().getSortOrderParticipants() == LLConversationFilter::SO_DISTANCE) + { + self->setNearbyDistances(); + } + self->mConversationsRoot->update(); +} + +bool LLFloaterIMContainer::onConversationModelEvent(const LLSD& event) +{ + // For debug only + //std::ostringstream llsd_value; + //llsd_value << LLSDOStreamer(event) << std::endl; + //llinfos << "LLFloaterIMContainer::onConversationModelEvent, event = " << llsd_value.str() << llendl; + // end debug + + // Note: In conversations, the model is not responsible for creating the view, which is a good thing. This means that + // the model could change substantially and the view could echo only a portion of this model (though currently the + // conversation view does echo the conversation model 1 to 1). + // Consequently, the participant views need to be created either by the session view or by the container panel. + // For the moment, we create them here, at the container level, to conform to the pattern implemented in llinventorypanel.cpp + // (see LLInventoryPanel::buildNewViews()). + + std::string type = event.get("type").asString(); + LLUUID session_id = event.get("session_uuid").asUUID(); + LLUUID participant_id = event.get("participant_uuid").asUUID(); + + LLConversationViewSession* session_view = dynamic_cast(get_ptr_in_map(mConversationsWidgets,session_id)); + if (!session_view) + { + // We skip events that are not associated with a session + return false; + } + LLConversationViewParticipant* participant_view = session_view->findParticipant(participant_id); + LLFloaterIMSessionTab *conversation_floater = (session_id.isNull() ? (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(session_id))); + + if (type == "remove_participant") + { + // Remove a participant view from the hierarchical conversation list + if (participant_view) + { + session_view->extractItem(participant_view); + delete participant_view; + session_view->refresh(); + mConversationsRoot->arrangeAll(); + } + // Remove a participant view from the conversation floater + if (conversation_floater) + { + conversation_floater->removeConversationViewParticipant(participant_id); + } + } + else if (type == "add_participant") + { + LLConversationItemSession* session_model = dynamic_cast(mConversationsItems[session_id]); + LLConversationItemParticipant* participant_model = (session_model ? session_model->findParticipant(participant_id) : NULL); + if (!participant_view && session_model && participant_model) + { + LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(session_id); + if (session_id.isNull() || (im_sessionp && !im_sessionp->isP2PSessionType())) + { + participant_view = createConversationViewParticipant(participant_model); + participant_view->addToFolder(session_view); + participant_view->setVisible(TRUE); + } + } + // Add a participant view to the conversation floater + if (conversation_floater && participant_model) + { + conversation_floater->addConversationViewParticipant(participant_model); + } + } + else if (type == "update_participant") + { + // Update the participant view in the hierarchical conversation list + if (participant_view) + { + participant_view->refresh(); + } + // Update the participant view in the conversation floater + if (conversation_floater) + { + conversation_floater->updateConversationViewParticipant(participant_id); + } + } + else if (type == "update_session") + { + session_view->refresh(); + if (conversation_floater) + { + conversation_floater->refreshConversation(); + } + } + + mConversationViewModel.requestSortAll(); + mConversationsRoot->arrangeAll(); + + return false; +} + +void LLFloaterIMContainer::draw() +{ + if (mTabContainer->getTabCount() == 0) + { + // Do not close the container when every conversation is torn off because the user + // still needs the conversation list. Simply collapse the message pane in that case. + collapseMessagesPane(true); + } + LLFloater::draw(); +} + +void LLFloaterIMContainer::tabClose() +{ + if (mTabContainer->getTabCount() == 0) + { + // Do not close the container when every conversation is torn off because the user + // still needs the conversation list. Simply collapse the message pane in that case. + collapseMessagesPane(true); + } +} + +void LLFloaterIMContainer::setVisible(BOOL visible) +{ LLFloaterIMNearbyChat* nearby_chat; + if (visible) + { + // Make sure we have the Nearby Chat present when showing the conversation container + nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if (nearby_chat == NULL) + { + // If not found, force the creation of the nearby chat conversation panel + // *TODO: find a way to move this to XML as a default panel or something like that + LLSD name("nearby_chat"); + LLFloaterReg::toggleInstanceOrBringToFront(name); + } + openNearbyChat(); + } + + nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if (nearby_chat) + { + LLFloaterIMSessionTab::addToHost(LLUUID()); + } + + // We need to show/hide all the associated conversations that have been torn off + // (and therefore, are not longer managed by the multifloater), + // so that they show/hide with the conversations manager. + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + for (;widget_it != mConversationsWidgets.end(); ++widget_it) + { + LLConversationViewSession* widget = dynamic_cast(widget_it->second); + if (widget) + { + widget->setVisibleIfDetached(visible); + } + } + + // Now, do the normal multifloater show/hide + LLMultiFloater::setVisible(visible); + +} + +void LLFloaterIMContainer::collapseMessagesPane(bool collapse) +{ + if (mMessagesPane->isCollapsed() == collapse) + { + return; + } + + if (collapse) + { + // Save the messages pane width before collapsing it. + gSavedPerAccountSettings.setS32("ConversationsMessagePaneWidth", mMessagesPane->getRect().getWidth()); + + // Save the order in which the panels are closed to reverse user's last action. + gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", mConversationsPane->isCollapsed()); + } + + // Save left pane rectangle before collapsing/expanding right pane. + LLRect prevRect = mConversationsPane->getRect(); + + // Show/hide the messages pane. + mConversationsStack->collapsePanel(mMessagesPane, collapse); + + if (!collapse) + { + // Make sure layout is updated before resizing conversation pane. + mConversationsStack->updateLayout(); + } + + updateState(collapse, gSavedPerAccountSettings.getS32("ConversationsMessagePaneWidth")); + if (!collapse) + { + // Restore conversation's pane previous width after expanding messages pane. + mConversationsPane->setTargetDim(prevRect.getWidth()); + } +} +void LLFloaterIMContainer::collapseConversationsPane(bool collapse) +{ + if (mConversationsPane->isCollapsed() == collapse) + { + return; + } + + LLView* button_panel = getChild("conversations_pane_buttons_expanded"); + button_panel->setVisible(!collapse); + mExpandCollapseBtn->setImageOverlay(getString(collapse ? "expand_icon" : "collapse_icon")); + + if (collapse) + { + // Save the conversations pane width before collapsing it. + gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", mConversationsPane->getRect().getWidth()); + + // Save the order in which the panels are closed to reverse user's last action. + gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", !mMessagesPane->isCollapsed()); + } + + mConversationsStack->collapsePanel(mConversationsPane, collapse); + + S32 collapsed_width = mConversationsPane->getMinDim(); + updateState(collapse, gSavedPerAccountSettings.getS32("ConversationsListPaneWidth") - collapsed_width); + + for (conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + widget_it != mConversationsWidgets.end(); ++widget_it) + { + LLConversationViewSession* widget = dynamic_cast(widget_it->second); + if (widget) + { + widget->toggleMinimizedMode(collapse); + + // force closing all open conversations when collapsing to minimized state + if (collapse) + { + widget->setOpen(false); + } +} + } +} + +void LLFloaterIMContainer::updateState(bool collapse, S32 delta_width) +{ + LLRect floater_rect = getRect(); + floater_rect.mRight += ((collapse ? -1 : 1) * delta_width); + + // Set by_user = true so that reshaped rect is saved in user_settings. + setShape(floater_rect, true); + + updateResizeLimits(); + + bool is_left_pane_expanded = !mConversationsPane->isCollapsed(); + bool is_right_pane_expanded = !mMessagesPane->isCollapsed(); + + setCanResize(is_left_pane_expanded || is_right_pane_expanded); + setCanMinimize(is_left_pane_expanded || is_right_pane_expanded); + + // force set correct size for the title after show/hide minimize button + LLRect cur_rect = getRect(); + LLRect force_rect = cur_rect; + force_rect.mRight = cur_rect.mRight + 1; + setRect(force_rect); + setRect(cur_rect); + + // restore floater's resize limits (prevent collapse when left panel is expanded) + if (is_left_pane_expanded && !is_right_pane_expanded) + { + S32 expanded_min_size = mConversationsPane->getExpandedMinDim(); + setResizeLimits(expanded_min_size, expanded_min_size); + } + +} + +void LLFloaterIMContainer::onAddButtonClicked() +{ + LLView * button = findChild("conversations_pane_buttons_expanded")->findChild("add_btn"); + LLFloater* root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterIMContainer::onAvatarPicked, this, _1), TRUE, TRUE, TRUE, root_floater->getName(), button); + + if (picker && root_floater) + { + root_floater->addDependentFloater(picker); + } +} + +void LLFloaterIMContainer::onAvatarPicked(const uuid_vec_t& ids) +{ + if (ids.size() == 1) + { + LLAvatarActions::startIM(ids.back()); + } + else + { + LLAvatarActions::startConference(ids); + } +} + +void LLFloaterIMContainer::onCustomAction(const LLSD& userdata) +{ + std::string command = userdata.asString(); + + if ("sort_sessions_by_type" == command) + { + setSortOrderSessions(LLConversationFilter::SO_SESSION_TYPE); + } + if ("sort_sessions_by_name" == command) + { + setSortOrderSessions(LLConversationFilter::SO_NAME); + } + if ("sort_sessions_by_recent" == command) + { + setSortOrderSessions(LLConversationFilter::SO_DATE); + } + if ("sort_participants_by_name" == command) + { + setSortOrderParticipants(LLConversationFilter::SO_NAME); + } + if ("sort_participants_by_recent" == command) + { + setSortOrderParticipants(LLConversationFilter::SO_DATE); + } + if ("sort_participants_by_distance" == command) + { + setSortOrderParticipants(LLConversationFilter::SO_DISTANCE); + } + if ("chat_preferences" == command) + { + LLFloaterPreference* floater_prefs = LLFloaterReg::showTypedInstance("preferences"); + if (floater_prefs) + { + LLTabContainer* tab_container = floater_prefs->getChild("pref core"); + LLPanel* chat_panel = tab_container->getPanelByName("chat"); + if (tab_container && chat_panel) + { + tab_container->selectTabPanel(chat_panel); + } + } + } +} + +BOOL LLFloaterIMContainer::isActionChecked(const LLSD& userdata) +{ + LLConversationSort order = mConversationViewModel.getSorter(); + std::string command = userdata.asString(); + if ("sort_sessions_by_type" == command) + { + return (order.getSortOrderSessions() == LLConversationFilter::SO_SESSION_TYPE); + } + if ("sort_sessions_by_name" == command) + { + return (order.getSortOrderSessions() == LLConversationFilter::SO_NAME); + } + if ("sort_sessions_by_recent" == command) + { + return (order.getSortOrderSessions() == LLConversationFilter::SO_DATE); + } + if ("sort_participants_by_name" == command) + { + return (order.getSortOrderParticipants() == LLConversationFilter::SO_NAME); + } + if ("sort_participants_by_recent" == command) + { + return (order.getSortOrderParticipants() == LLConversationFilter::SO_DATE); + } + if ("sort_participants_by_distance" == command) + { + return (order.getSortOrderParticipants() == LLConversationFilter::SO_DISTANCE); + } + + return FALSE; +} + +void LLFloaterIMContainer::setSortOrderSessions(const LLConversationFilter::ESortOrderType order) +{ + LLConversationSort old_order = mConversationViewModel.getSorter(); + if (order != old_order.getSortOrderSessions()) + { + old_order.setSortOrderSessions(order); + setSortOrder(old_order); + } +} + +void LLFloaterIMContainer::setSortOrderParticipants(const LLConversationFilter::ESortOrderType order) +{ + LLConversationSort old_order = mConversationViewModel.getSorter(); + if (order != old_order.getSortOrderParticipants()) + { + old_order.setSortOrderParticipants(order); + setSortOrder(old_order); + } +} + +void LLFloaterIMContainer::setSortOrder(const LLConversationSort& order) +{ + mConversationViewModel.setSorter(order); + mConversationsRoot->arrangeAll(); + // try to keep selection onscreen, even if it wasn't to start with + mConversationsRoot->scrollToShowSelection(); + + // Notify all conversation (torn off or not) of the change to the sort order + // Note: For the moment, the sort order is *unique* across all conversations. That might change in the future. + for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) + { + LLUUID session_id = it_session->first; + LLFloaterIMSessionTab *conversation_floater = (session_id.isNull() ? (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(session_id))); + if (conversation_floater) + { + conversation_floater->setSortOrder(order); + } + } + + gSavedSettings.setU32("ConversationSortOrder", (U32)order); +} + +void LLFloaterIMContainer::getSelectedUUIDs(uuid_vec_t& selected_uuids) +{ + const std::set selectedItems = mConversationsRoot->getSelectionList(); + + std::set::const_iterator it = selectedItems.begin(); + const std::set::const_iterator it_end = selectedItems.end(); + LLConversationItem * conversationItem; + + for (; it != it_end; ++it) + { + conversationItem = static_cast((*it)->getViewModelItem()); + selected_uuids.push_back(conversationItem->getUUID()); + } +} + +const LLConversationItem * LLFloaterIMContainer::getCurSelectedViewModelItem() +{ + LLConversationItem * conversationItem = NULL; + + if(mConversationsRoot && + mConversationsRoot->getCurSelectedItem() && + mConversationsRoot->getCurSelectedItem()->getViewModelItem()) + { + conversationItem = static_cast(mConversationsRoot->getCurSelectedItem()->getViewModelItem()); + } + + return conversationItem; +} + +void LLFloaterIMContainer::getParticipantUUIDs(uuid_vec_t& selected_uuids) +{ + //Find the conversation floater associated with the selected id + const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); + + if(conversationItem->getType() == LLConversationItem::CONV_PARTICIPANT) + { + getSelectedUUIDs(selected_uuids); + } + //When a one-on-one conversation exists, retrieve the participant id from the conversation floater + else if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1) + { + LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(conversationItem->getUUID()); + LLUUID participantID = conversationFloater->getOtherParticipantUUID(); + selected_uuids.push_back(participantID); + } +} + +void LLFloaterIMContainer::doToParticipants(const std::string& command, uuid_vec_t& selectedIDS) +{ + if(selectedIDS.size() > 0) + { + const LLUUID& userID = selectedIDS.front(); + if(gAgent.getID() != userID) + { + if ("view_profile" == command) + { + LLAvatarActions::showProfile(userID); + } + else if("im" == command) + { + LLAvatarActions::startIM(userID); + } + else if("offer_teleport" == command) + { + LLAvatarActions::offerTeleport(selectedIDS); + } + else if("voice_call" == command) + { + LLAvatarActions::startCall(userID); + } + else if("chat_history" == command) + { + LLAvatarActions::viewChatHistory(userID); + } + else if("add_friend" == command) + { + LLAvatarActions::requestFriendshipDialog(userID); + } + else if("remove_friend" == command) + { + LLAvatarActions::removeFriendDialog(userID); + } + else if("invite_to_group" == command) + { + LLAvatarActions::inviteToGroup(userID); + } + else if("map" == command) + { + LLAvatarActions::showOnMap(userID); + } + else if("share" == command) + { + LLAvatarActions::share(userID); + } + else if("pay" == command) + { + LLAvatarActions::pay(userID); + } + else if("block_unblock" == command) + { + LLAvatarActions::toggleBlock(userID); + } + else if("selected" == command || "mute_all" == command || "unmute_all" == command) + { + moderateVoice(command, userID); + } + else if ("toggle_allow_text_chat" == command) + { + toggleAllowTextChat(userID); + } + } + } +} + +void LLFloaterIMContainer::doToSelectedConversation(const std::string& command, uuid_vec_t& selectedIDS) +{ + //Find the conversation floater associated with the selected id + const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); + LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(conversationItem->getUUID()); + + if(conversationFloater) + { + //Close the selected conversation + if("close_conversation" == command) + { + LLFloater::onClickClose(conversationFloater); + } + else if("open_voice_conversation" == command) + { + gIMMgr->startCall(conversationItem->getUUID()); + } + else if("disconnect_from_voice" == command) + { + gIMMgr->endCall(conversationItem->getUUID()); + } + else if("chat_history" == command) + { + const LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(conversationItem->getUUID()); + + if (NULL != session) + { + const LLUUID session_id = session->isOutgoingAdHoc() ? session->generateOutgouigAdHocHash() : session->mSessionID; + LLFloaterReg::showInstance("preview_conversation", session_id, true); + } + } + else + { + doToParticipants(command, selectedIDS); + } + } +} + +void LLFloaterIMContainer::doToSelected(const LLSD& userdata) +{ + std::string command = userdata.asString(); + const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); + uuid_vec_t selected_uuids; + + if(conversationItem != NULL) + { + getParticipantUUIDs(selected_uuids); + + if(conversationItem->getType() == LLConversationItem::CONV_PARTICIPANT) + { + doToParticipants(command, selected_uuids); + } + else + { + doToSelectedConversation(command, selected_uuids); + } + } +} + +void LLFloaterIMContainer::doToSelectedGroup(const LLSD& userdata) +{ + std::string action = userdata.asString(); + LLUUID selected_group = getCurSelectedViewModelItem()->getUUID(); + + if (action == "group_profile") + { + LLGroupActions::show(selected_group); + } + else if (action == "activate_group") + { + LLGroupActions::activate(selected_group); + } + else if (action == "leave_group") + { + LLGroupActions::leave(selected_group); + } +} + +bool LLFloaterIMContainer::enableContextMenuItem(const LLSD& userdata) +{ + std::string item = userdata.asString(); + uuid_vec_t uuids; + getParticipantUUIDs(uuids); + + if(item == std::string("can_activate_group")) + { + LLUUID selected_group_id = getCurSelectedViewModelItem()->getUUID(); + return gAgent.getGroupID() != selected_group_id; + } + + if(uuids.size() <= 0) + { + return false; + } + + // Note: can_block and can_delete is used only for one person selected menu + // so we don't need to go over all uuids. + + if (item == std::string("can_block")) + { + const LLUUID& id = uuids.front(); + return LLAvatarActions::canBlock(id); + } + else if (item == std::string("can_add")) + { + // We can add friends if: + // - there are selected people + // - and there are no friends among selection yet. + + //EXT-7389 - disable for more than 1 + if(uuids.size() > 1) + { + return false; + } + + bool result = true; + + uuid_vec_t::const_iterator + id = uuids.begin(), + uuids_end = uuids.end(); + + for (;id != uuids_end; ++id) + { + if ( LLAvatarActions::isFriend(*id) ) + { + result = false; + break; + } + } + + return result; + } + else if (item == std::string("can_delete")) + { + // We can remove friends if: + // - there are selected people + // - and there are only friends among selection. + + bool result = (uuids.size() > 0); + + uuid_vec_t::const_iterator + id = uuids.begin(), + uuids_end = uuids.end(); + + for (;id != uuids_end; ++id) + { + if ( !LLAvatarActions::isFriend(*id) ) + { + result = false; + break; + } + } + + return result; + } + else if (item == std::string("can_call")) + { + return LLAvatarActions::canCall(); + } + else if (item == std::string("can_show_on_map")) + { + const LLUUID& id = uuids.front(); + + return (LLAvatarTracker::instance().isBuddyOnline(id) && is_agent_mappable(id)) + || gAgent.isGodlike(); + } + else if(item == std::string("can_offer_teleport")) + { + return LLAvatarActions::canOfferTeleport(uuids); + } + else if("can_moderate_voice" == item || "can_allow_text_chat" == item || "can_mute" == item || "can_unmute" == item) + { + return enableModerateContextMenuItem(item); + } + + return false; +} + +bool LLFloaterIMContainer::checkContextMenuItem(const LLSD& userdata) +{ + std::string item = userdata.asString(); + uuid_vec_t mUUIDs; + getParticipantUUIDs(mUUIDs); + + if(mUUIDs.size() > 0 ) + { + if ("is_blocked" == item) + { + return LLAvatarActions::isBlocked(mUUIDs.front()); + } + else if ("is_allowed_text_chat" == item) + { + const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); + + if (NULL != speakerp) + { + return !speakerp->mModeratorMutedText; + } + } + } + + return false; +} + +void LLFloaterIMContainer::showConversation(const LLUUID& session_id) +{ + setVisibleAndFrontmost(false); + selectConversation(session_id); +} + +// Will select only the conversation item +void LLFloaterIMContainer::selectConversation(const LLUUID& session_id) +{ + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,session_id); + if (widget) + { + (widget->getRoot())->setSelection(widget, FALSE, FALSE); + } +} + +// Synchronous select the conversation item and the conversation floater +BOOL LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool select_widget) +{ + BOOL handled = TRUE; + + /* widget processing */ + if (select_widget) + { + LLFolderViewItem* widget = mConversationsWidgets[session_id]; + if (widget && widget->getParentFolder()) + { + widget->getParentFolder()->setSelection(widget, FALSE, FALSE); + } + } + + /* floater processing */ + + if (session_id != getSelectedSession()) + { + // Store the active session + setSelectedSession(session_id); + + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id); + + if (session_floater->getHost()) + { + // Always expand the message pane if the panel is hosted by the container + collapseMessagesPane(false); + // Switch to the conversation floater that is being selected + selectFloater(session_floater); + } + + // Set the focus on the selected floater + if (!session_floater->hasFocus()) + { + session_floater->setFocus(TRUE); + } + } + + return handled; +} + +void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& participant_id) +{ + LLConversationItemSession* item = dynamic_cast(get_ptr_in_map(mConversationsItems,session_id)); + if (item) + { + item->setTimeNow(participant_id); + mConversationViewModel.requestSortAll(); + mConversationsRoot->arrangeAll(); + } +} + +void LLFloaterIMContainer::setNearbyDistances() +{ + // Get the nearby chat session: that's the one with uuid nul + LLConversationItemSession* item = dynamic_cast(get_ptr_in_map(mConversationsItems,LLUUID())); + if (item) + { + // Get the positions of the nearby avatars and their ids + std::vector positions; + uuid_vec_t avatar_ids; + LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); + // Get the position of the agent + const LLVector3d& me_pos = gAgent.getPositionGlobal(); + // For each nearby avatar, compute and update the distance + int avatar_count = positions.size(); + for (int i = 0; i < avatar_count; i++) + { + F64 dist = dist_vec_squared(positions[i], me_pos); + item->setDistance(avatar_ids[i],dist); + } + // Also does it for the agent itself + item->setDistance(gAgent.getID(),0.0f); + // Request resort + mConversationViewModel.requestSortAll(); + mConversationsRoot->arrangeAll(); + } +} + +LLConversationItem* LLFloaterIMContainer::addConversationListItem(const LLUUID& uuid, bool isWidgetSelected /*= false*/) +{ + bool is_nearby_chat = uuid.isNull(); + + // Stores the display name for the conversation line item + std::string display_name = is_nearby_chat ? LLTrans::getString("NearbyChatLabel") : LLIMModel::instance().getName(uuid); + + // Check if the item is not already in the list, exit (nothing to do) + // Note: this happens often, when reattaching a torn off conversation for instance + conversations_items_map::iterator item_it = mConversationsItems.find(uuid); + if (item_it != mConversationsItems.end()) + { + return item_it->second; + } + + // Remove the conversation item that might exist already: it'll be recreated anew further down anyway + // and nothing wrong will happen removing it if it doesn't exist + removeConversationListItem(uuid,false); + + // Create a conversation session model + LLConversationItemSession* item = NULL; + LLSpeakerMgr* speaker_manager = (is_nearby_chat ? (LLSpeakerMgr*)(LLLocalSpeakerMgr::getInstance()) : LLIMModel::getInstance()->getSpeakerManager(uuid)); + if (speaker_manager) + { + item = new LLParticipantList(speaker_manager, getRootViewModel()); + } + if (!item) + { + llwarns << "Couldn't create conversation session item : " << display_name << llendl; + return NULL; + } + item->renameItem(display_name); + item->updateParticipantName(NULL); + + mConversationsItems[uuid] = item; + + // Create a widget from it + LLConversationViewSession* widget = createConversationItemWidget(item); + mConversationsWidgets[uuid] = widget; + + // Add a new conversation widget to the root folder of the folder view + widget->addToFolder(mConversationsRoot); + widget->requestArrange(); + + LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(uuid); + + // Create the participants widgets now + // Note: usually, we do not get an updated avatar list at that point + if (uuid.isNull() || im_sessionp && !im_sessionp->isP2PSessionType()) + { + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); + while (current_participant_model != end_participant_model) + { + LLConversationItem* participant_model = dynamic_cast(*current_participant_model); + LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); + participant_view->addToFolder(widget); + current_participant_model++; + } + } + // Do that too for the conversation dialog + LLFloaterIMSessionTab *conversation_floater = (uuid.isNull() ? (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(uuid))); + if (conversation_floater) + { + conversation_floater->buildConversationViewParticipant(); + } + + // set the widget to minimized mode if conversations pane is collapsed + widget->toggleMinimizedMode(mConversationsPane->isCollapsed()); + + if (isWidgetSelected) + { + selectConversation(uuid); + // scroll to newly added item + mConversationsRoot->scrollToShowSelection(); + } + + return item; +} + +bool LLFloaterIMContainer::removeConversationListItem(const LLUUID& uuid, bool change_focus) +{ + // Delete the widget and the associated conversation item + // Note : since the mConversationsItems is also the listener to the widget, deleting + // the widget will also delete its listener + bool isWidgetSelected = false; + LLFolderViewItem* new_selection = NULL; + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); + if (widget) + { + isWidgetSelected = widget->isSelected(); + new_selection = mConversationsRoot->getNextFromChild(widget); + if(new_selection == NULL) + { + new_selection = mConversationsRoot->getPreviousFromChild(widget); + } + widget->destroyView(); + } + + // Suppress the conversation items and widgets from their respective maps + mConversationsItems.erase(uuid); + mConversationsWidgets.erase(uuid); + + // Don't let the focus fall IW, select and refocus on the first conversation in the list + if (change_focus) + { + setFocus(TRUE); + if(new_selection != NULL) + { + LLConversationItem* vmi = dynamic_cast(new_selection->getViewModelItem()); + if(vmi != NULL) + { + selectConversation(vmi->getUUID()); + } + } + } + return isWidgetSelected; +} + +LLConversationViewSession* LLFloaterIMContainer::createConversationItemWidget(LLConversationItem* item) +{ + LLConversationViewSession::Params params; + + params.name = item->getDisplayName(); + params.root = mConversationsRoot; + params.listener = item; + params.tool_tip = params.name; + params.container = this; + + return LLUICtrlFactory::create(params); +} + +LLConversationViewParticipant* LLFloaterIMContainer::createConversationViewParticipant(LLConversationItem* item) +{ + LLConversationViewParticipant::Params params; + LLRect panel_rect = mConversationsListPanel->getRect(); + + params.name = item->getDisplayName(); + params.root = mConversationsRoot; + params.listener = item; + + //24 is the the current hight of an item (itemHeight) loaded from conversation_view_participant.xml. + params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); + params.tool_tip = params.name; + params.participant_id = item->getUUID(); + params.folder_indentation = 42; + + return LLUICtrlFactory::create(params); +} + +bool LLFloaterIMContainer::enableModerateContextMenuItem(const std::string& userdata) +{ + // only group moderators can perform actions related to this "enable callback" + if (!isGroupModerator()) + { + return false; + } + + LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); + if (NULL == speakerp) + { + return false; + } + + bool voice_channel = speakerp->isInVoiceChannel(); + + if ("can_moderate_voice" == userdata) + { + return voice_channel; + } + else if ("can_mute" == userdata) + { + return voice_channel && !isMuted(getCurSelectedViewModelItem()->getUUID()); + } + else if ("can_unmute" == userdata) + { + return voice_channel && isMuted(getCurSelectedViewModelItem()->getUUID()); + } + + // The last invoke is used to check whether the "can_allow_text_chat" will enabled + return LLVoiceClient::getInstance()->isParticipantAvatar(getCurSelectedViewModelItem()->getUUID()); +} + +bool LLFloaterIMContainer::isGroupModerator() +{ + LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); + if (NULL == speaker_manager) + { + llwarns << "Speaker manager is missing" << llendl; + return false; + } + + // Is session a group call/chat? + if(gAgent.isInGroup(speaker_manager->getSessionID())) + { + LLSpeaker * speaker = speaker_manager->findSpeaker(gAgentID).get(); + + // Is agent a moderator? + return speaker && speaker->mIsModerator; + } + + return false; +} + +void LLFloaterIMContainer::moderateVoice(const std::string& command, const LLUUID& userID) +{ + if (!gAgent.getRegion()) return; + + if (command.compare("selected")) + { + moderateVoiceAllParticipants(command.compare("mute_all")); + } + else + { + moderateVoiceParticipant(userID, isMuted(userID)); + } +} + +bool LLFloaterIMContainer::isMuted(const LLUUID& avatar_id) +{ + const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); + return NULL == speakerp ? true : speakerp->mStatus == LLSpeaker::STATUS_MUTED; +} + +void LLFloaterIMContainer::moderateVoiceAllParticipants(bool unmute) +{ + LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); + + if (NULL != speaker_managerp) + { + if (!unmute) + { + LLSD payload; + payload["session_id"] = speaker_managerp->getSessionID(); + LLNotificationsUtil::add("ConfirmMuteAll", LLSD(), payload, confirmMuteAllCallback); + return; + } + + speaker_managerp->moderateVoiceAllParticipants(unmute); + } +} + +// static +void LLFloaterIMContainer::confirmMuteAllCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + // if Cancel pressed + if (option == 1) + { + return; + } + + const LLSD& payload = notification["payload"]; + const LLUUID& session_id = payload["session_id"]; + + LLIMSpeakerMgr * speaker_manager = dynamic_cast ( + LLIMModel::getInstance()->getSpeakerManager(session_id)); + if (speaker_manager) + { + speaker_manager->moderateVoiceAllParticipants(false); + } + + return; +} + +void LLFloaterIMContainer::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute) +{ + LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); + + if (NULL != speaker_managerp) + { + speaker_managerp->moderateVoiceParticipant(avatar_id, unmute); + } +} + +LLSpeakerMgr * LLFloaterIMContainer::getSpeakerMgrForSelectedParticipant() +{ + LLFolderViewItem * selected_folder_itemp = mConversationsRoot->getCurSelectedItem(); + if (NULL == selected_folder_itemp) + { + llwarns << "Current selected item is null" << llendl; + return NULL; + } + + LLFolderViewFolder * conversation_itemp = selected_folder_itemp->getParentFolder(); + + conversations_widgets_map::const_iterator iter = mConversationsWidgets.begin(); + conversations_widgets_map::const_iterator end = mConversationsWidgets.end(); + const LLUUID * conversation_uuidp = NULL; + while(iter != end) + { + if (iter->second == conversation_itemp) + { + conversation_uuidp = &iter->first; + break; + } + ++iter; + } + if (NULL == conversation_uuidp) + { + llwarns << "Cannot find conversation item widget" << llendl; + return NULL; + } + + return conversation_uuidp->isNull() ? (LLSpeakerMgr *)LLLocalSpeakerMgr::getInstance() + : LLIMModel::getInstance()->getSpeakerManager(*conversation_uuidp); +} + +LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp) +{ + if (NULL == speaker_managerp) + { + llwarns << "Speaker manager is missing" << llendl; + return NULL; + } + + const LLConversationItem * participant_itemp = getCurSelectedViewModelItem(); + if (NULL == participant_itemp) + { + llwarns << "Cannot evaluate current selected view model item" << llendl; + return NULL; + } + + return speaker_managerp->findSpeaker(participant_itemp->getUUID()); +} + +void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid) +{ + LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); + if (NULL != speaker_managerp) + { + speaker_managerp->toggleAllowTextChat(participant_uuid); + } +} + +void LLFloaterIMContainer::openNearbyChat() +{ + // If there's only one conversation in the container and that conversation is the nearby chat + //(which it should be...), open it so to make the list of participants visible. This happens to be the most common case when opening the Chat floater. + if(mConversationsItems.size() == 1) + { + LLConversationViewSession* nearby_chat = dynamic_cast(mConversationsWidgets[LLUUID()]); + if (nearby_chat) + { + nearby_chat->setOpen(TRUE); + } + } +} + +void LLFloaterIMContainer::onNearbyChatClosed() +{ + // If nearby chat is the only remaining conversation and it is closed, close whole conversation floater as well + if (mConversationsItems.size() == 1) + closeFloater(); +} + +// EOF diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h new file mode 100644 index 0000000000..f65e946dad --- /dev/null +++ b/indra/newview/llfloaterimcontainer.h @@ -0,0 +1,178 @@ +/** + * @file llfloaterimcontainer.h + * @brief Multifloater containing active IM sessions in separate tab container tabs + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERIMCONTAINER_H +#define LL_LLFLOATERIMCONTAINER_H + +#include +#include + +#include "llimview.h" +#include "llevents.h" +#include "llfloater.h" +#include "llmultifloater.h" +#include "llavatarpropertiesprocessor.h" +#include "llgroupmgr.h" +#include "lltrans.h" +#include "llconversationmodel.h" +#include "llconversationview.h" + +class LLButton; +class LLLayoutPanel; +class LLLayoutStack; +class LLTabContainer; +class LLFloaterIMContainer; +class LLSpeaker; +class LLSpeakerMgr; + +class LLFloaterIMContainer + : public LLMultiFloater + , public LLIMSessionObserver +{ +public: + LLFloaterIMContainer(const LLSD& seed); + virtual ~LLFloaterIMContainer(); + + /*virtual*/ BOOL postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void draw(); + /*virtual*/ void setVisible(BOOL visible); + void onCloseFloater(LLUUID& id); + + /*virtual*/ void addFloater(LLFloater* floaterp, + BOOL select_added_floater, + LLTabContainer::eInsertionPoint insertion_point = LLTabContainer::END); + + void showConversation(const LLUUID& session_id); + void selectConversation(const LLUUID& session_id); + BOOL selectConversationPair(const LLUUID& session_id, bool select_widget); + + /*virtual*/ void tabClose(); + + static LLFloater* getCurrentVoiceFloater(); + static LLFloaterIMContainer* findInstance(); + static LLFloaterIMContainer* getInstance(); + + static void onCurrentChannelChanged(const LLUUID& session_id); + + void collapseMessagesPane(bool collapse); + + // Callbacks + static void idle(void* user_data); + + // LLIMSessionObserver observe triggers + /*virtual*/ void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id); + /*virtual*/ void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id); + /*virtual*/ void sessionVoiceOrIMStarted(const LLUUID& session_id); + /*virtual*/ void sessionRemoved(const LLUUID& session_id); + /*virtual*/ void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id); + + LLConversationViewModel& getRootViewModel() { return mConversationViewModel; } + LLUUID getSelectedSession() { return mSelectedSession; } + void setSelectedSession(LLUUID sessionID) { mSelectedSession = sessionID; } + LLConversationItem* getSessionModel(const LLUUID& session_id) { return get_ptr_in_map(mConversationsItems,session_id); } + LLConversationSort& getSortOrder() { return mConversationViewModel.getSorter(); } + + void onNearbyChatClosed(); + +private: + typedef std::map avatarID_panel_map_t; + avatarID_panel_map_t mSessions; + boost::signals2::connection mNewMessageConnection; + + /*virtual*/ void computeResizeLimits(S32& new_min_width, S32& new_min_height); + + void onNewMessageReceived(const LLSD& data); + + void onExpandCollapseButtonClicked(); + void processParticipantsStyleUpdate(); + + void collapseConversationsPane(bool collapse); + + void updateState(bool collapse, S32 delta_width); + + void onAddButtonClicked(); + void onAvatarPicked(const uuid_vec_t& ids); + + BOOL isActionChecked(const LLSD& userdata); + void onCustomAction (const LLSD& userdata); + void setSortOrderSessions(const LLConversationFilter::ESortOrderType order); + void setSortOrderParticipants(const LLConversationFilter::ESortOrderType order); + void setSortOrder(const LLConversationSort& order); + + void getSelectedUUIDs(uuid_vec_t& selected_uuids); + const LLConversationItem * getCurSelectedViewModelItem(); + void getParticipantUUIDs(uuid_vec_t& selected_uuids); + void doToSelected(const LLSD& userdata); + void doToSelectedConversation(const std::string& command, uuid_vec_t& selectedIDS); + void doToParticipants(const std::string& item, uuid_vec_t& selectedIDS); + void doToSelectedGroup(const LLSD& userdata); + bool checkContextMenuItem(const LLSD& userdata); + bool enableContextMenuItem(const LLSD& userdata); + + static void confirmMuteAllCallback(const LLSD& notification, const LLSD& response); + bool enableModerateContextMenuItem(const std::string& userdata); + LLSpeaker * getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp); + LLSpeakerMgr * getSpeakerMgrForSelectedParticipant(); + bool isGroupModerator(); + bool isMuted(const LLUUID& avatar_id); + void moderateVoice(const std::string& command, const LLUUID& userID); + void moderateVoiceAllParticipants(bool unmute); + void moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute); + void toggleAllowTextChat(const LLUUID& participant_uuid); + void openNearbyChat(); + + LLButton* mExpandCollapseBtn; + LLLayoutPanel* mMessagesPane; + LLLayoutPanel* mConversationsPane; + LLLayoutStack* mConversationsStack; + + bool mInitialized; + + LLUUID mSelectedSession; + + // Conversation list implementation +public: + bool removeConversationListItem(const LLUUID& uuid, bool change_focus = true); + LLConversationItem* addConversationListItem(const LLUUID& uuid, bool isWidgetSelected = false); + void setTimeNow(const LLUUID& session_id, const LLUUID& participant_id); + void setNearbyDistances(); + +private: + LLConversationViewSession* createConversationItemWidget(LLConversationItem* item); + LLConversationViewParticipant* createConversationViewParticipant(LLConversationItem* item); + bool onConversationModelEvent(const LLSD& event); + + // Conversation list data + LLPanel* mConversationsListPanel; // This is the main widget we add conversation widget to + conversations_items_map mConversationsItems; + conversations_widgets_map mConversationsWidgets; + LLConversationViewModel mConversationViewModel; + LLFolderView* mConversationsRoot; + LLEventStream mConversationsEventStream; +}; + +#endif // LL_LLFLOATERIMCONTAINER_H diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp new file mode 100644 index 0000000000..5867eb3e84 --- /dev/null +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -0,0 +1,867 @@ +/** + * @file LLFloaterIMNearbyChat.cpp + * @brief LLFloaterIMNearbyChat class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "message.h" + +#include "lliconctrl.h" +#include "llappviewer.h" +#include "llchatentry.h" +#include "llfloaterreg.h" +#include "lltrans.h" +#include "llfloaterimcontainer.h" +#include "llfloatersidepanelcontainer.h" +#include "llfocusmgr.h" +#include "lllogchat.h" +#include "llresizebar.h" +#include "llresizehandle.h" +#include "lldraghandle.h" +#include "llmenugl.h" +#include "llviewermenu.h" // for gMenuHolder +#include "llfloaterimnearbychathandler.h" +#include "llchannelmanager.h" +#include "llchathistory.h" +#include "llstylemap.h" +#include "llavatarnamecache.h" +#include "llfloaterreg.h" +#include "lltrans.h" + +#include "llfirstuse.h" +#include "llfloaterimnearbychat.h" +#include "llagent.h" // gAgent +#include "llgesturemgr.h" +#include "llmultigesture.h" +#include "llkeyboard.h" +#include "llanimationstates.h" +#include "llviewerstats.h" +#include "llcommandhandler.h" +#include "llviewercontrol.h" +#include "llnavigationbar.h" +#include "llwindow.h" +#include "llviewerwindow.h" +#include "llrootview.h" +#include "llviewerchat.h" +#include "lltranslate.h" + +S32 LLFloaterIMNearbyChat::sLastSpecialChatChannel = 0; + +const S32 EXPANDED_HEIGHT = 266; +const S32 COLLAPSED_HEIGHT = 60; +const S32 EXPANDED_MIN_HEIGHT = 150; + +// legacy callback glue +void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel); + +struct LLChatTypeTrigger { + std::string name; + EChatType type; +}; + +static LLChatTypeTrigger sChatTypeTriggers[] = { + { "/whisper" , CHAT_TYPE_WHISPER}, + { "/shout" , CHAT_TYPE_SHOUT} +}; + + +LLFloaterIMNearbyChat::LLFloaterIMNearbyChat(const LLSD& llsd) +: LLFloaterIMSessionTab(llsd), + //mOutputMonitor(NULL), + mSpeakerMgr(NULL), + mExpandedHeight(COLLAPSED_HEIGHT + EXPANDED_HEIGHT) +{ + mIsP2PChat = false; + mIsNearbyChat = true; + setIsChrome(TRUE); + mSpeakerMgr = LLLocalSpeakerMgr::getInstance(); + mSessionID = LLUUID(); +} + +//static +LLFloaterIMNearbyChat* LLFloaterIMNearbyChat::buildFloater(const LLSD& key) +{ + LLFloaterReg::getInstance("im_container"); + return new LLFloaterIMNearbyChat(key); +} + +//virtual +BOOL LLFloaterIMNearbyChat::postBuild() +{ + setIsSingleInstance(TRUE); + BOOL result = LLFloaterIMSessionTab::postBuild(); + mInputEditor->setCommitCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxCommit, this)); + mInputEditor->setKeystrokeCallback(boost::bind(&onChatBoxKeystroke, _1, this)); + mInputEditor->setFocusLostCallback(boost::bind(&onChatBoxFocusLost, _1, this)); + mInputEditor->setFocusReceivedCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxFocusReceived, this)); + mInputEditor->setLabel(LLTrans::getString("NearbyChatTitle")); + +// mOutputMonitor = getChild("chat_zone_indicator"); +// mOutputMonitor->setVisible(FALSE); + + // Register for font change notifications + LLViewerChat::setFontChangedCallback(boost::bind(&LLFloaterIMNearbyChat::onChatFontChange, this, _1)); + + // title must be defined BEFORE call addConversationListItem() because + // it is used for show the item's name in the conversations list + setTitle(LLTrans::getString("NearbyChatTitle")); + + //for menu + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + enable_registrar.add("NearbyChat.Check", boost::bind(&LLFloaterIMNearbyChat::onNearbyChatCheckContextMenuItem, this, _2)); + registrar.add("NearbyChat.Action", boost::bind(&LLFloaterIMNearbyChat::onNearbyChatContextMenuItemClicked, this, _2)); + + LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_nearby_chat.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if(menu) + { + mPopupMenuHandle = menu->getHandle(); + } + + // obsolete, but may be needed for backward compatibility? + gSavedSettings.declareS32("nearbychat_showicons_and_names", 2, "NearByChat header settings", true); + + if (gSavedPerAccountSettings.getBOOL("LogShowHistory")) + { + loadHistory(); + } + + return result; +} + +// virtual +void LLFloaterIMNearbyChat::refresh() +{ + displaySpeakingIndicator(); + updateCallBtnState(LLVoiceClient::getInstance()->getUserPTTState()); + + // *HACK: Update transparency type depending on whether our children have focus. + // This is needed because this floater is chrome and thus cannot accept focus, so + // the transparency type setting code from LLFloater::setFocus() isn't reached. + if (getTransparencyType() != TT_DEFAULT) + { + setTransparencyType(hasFocus() ? TT_ACTIVE : TT_INACTIVE); + } +} + +void LLFloaterIMNearbyChat::onNearbySpeakers() +{ + LLSD param; + param["people_panel_tab_name"] = "nearby_panel"; + LLFloaterSidePanelContainer::showPanel("people", "panel_people", param); +} + +void LLFloaterIMNearbyChat::onNearbyChatContextMenuItemClicked(const LLSD& userdata) +{ +} + +bool LLFloaterIMNearbyChat::onNearbyChatCheckContextMenuItem(const LLSD& userdata) +{ + std::string str = userdata.asString(); + if(str == "nearby_people") + onNearbySpeakers(); + return false; +} + + +BOOL LLFloaterIMNearbyChat::handleMouseDown(S32 x, S32 y, MASK mask) +{ + //fix for EXT-6625 + //highlight NearbyChat history whenever mouseclick happen in NearbyChat + //setting focus to eidtor will force onFocusLost() call that in its turn will change + //background opaque. This all happenn since NearByChat is "chrome" and didn't process focus change. + + if(mChatHistory) + { + mChatHistory->setFocus(TRUE); + } + + BOOL handled = LLPanel::handleMouseDown(x, y, mask); + setFocus(handled); + return handled; +} + +void LLFloaterIMNearbyChat::reloadMessages() +{ + mChatHistory->clear(); + + LLSD do_not_log; + do_not_log["do_not_log"] = true; + for(std::vector::iterator it = mMessageArchive.begin();it!=mMessageArchive.end();++it) + { + // Update the messages without re-writing them to a log file. + addMessage(*it,false, do_not_log); + } +} + +void LLFloaterIMNearbyChat::loadHistory() +{ + LLSD do_not_log; + do_not_log["do_not_log"] = true; + + std::list history; + LLLogChat::loadChatHistory("chat", history); + + std::list::const_iterator it = history.begin(); + while (it != history.end()) + { + const LLSD& msg = *it; + + std::string from = msg[IM_FROM]; + LLUUID from_id; + if (msg[IM_FROM_ID].isDefined()) + { + from_id = msg[IM_FROM_ID].asUUID(); + } + else + { + std::string legacy_name = gCacheName->buildLegacyName(from); + gCacheName->getUUID(legacy_name, from_id); + } + + LLChat chat; + chat.mFromName = from; + chat.mFromID = from_id; + chat.mText = msg[IM_TEXT].asString(); + chat.mTimeStr = msg[IM_TIME].asString(); + chat.mChatStyle = CHAT_STYLE_HISTORY; + + chat.mSourceType = CHAT_SOURCE_AGENT; + if (from_id.isNull() && SYSTEM_FROM == from) + { + chat.mSourceType = CHAT_SOURCE_SYSTEM; + + } + else if (from_id.isNull()) + { + chat.mSourceType = isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; + } + + addMessage(chat, true, do_not_log); + + it++; + } +} + +void LLFloaterIMNearbyChat::removeScreenChat() +{ + LLNotificationsUI::LLScreenChannelBase* chat_channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID(LLUUID(gSavedSettings.getString("NearByChatChannelUUID"))); + if(chat_channel) + { + chat_channel->removeToastsFromChannel(); + } +} + + +void LLFloaterIMNearbyChat::setVisible(BOOL visible) +{ + LLFloaterIMSessionTab::setVisible(visible); + + if(visible) + { + removeScreenChat(); + } + setFocus(visible); +} + +// virtual +void LLFloaterIMNearbyChat::onTearOffClicked() +{ + LLFloaterIMSessionTab::onTearOffClicked(); + + // see CHUI-170: Save torn-off state of the nearby chat between sessions + BOOL in_the_multifloater = !isTornOff(); + gSavedSettings.setBOOL("NearbyChatIsNotTornOff", in_the_multifloater); +} + + +// virtual +void LLFloaterIMNearbyChat::onOpen(const LLSD& key) +{ + LLFloaterIMSessionTab::onOpen(key); + showTranslationCheckbox(LLTranslate::isTranslationConfigured()); +} + +// virtual +void LLFloaterIMNearbyChat::onClose(bool app_quitting) +{ + // Override LLFloaterIMSessionTab::onClose() so that Nearby Chat is not removed from the conversation floater +} + +// virtual +void LLFloaterIMNearbyChat::onClickCloseBtn() +{ + if (!isTornOff()) + return; + onTearOffClicked(); + + LLFloaterIMContainer *im_box = LLFloaterIMContainer::findInstance(); + if (im_box) + { + im_box->onNearbyChatClosed(); + } +} + +void LLFloaterIMNearbyChat::onChatFontChange(LLFontGL* fontp) +{ + // Update things with the new font whohoo + if (mInputEditor) + { + mInputEditor->setFont(fontp); + } +} + + +void LLFloaterIMNearbyChat::show() +{ + if (isChatMultiTab()) + { + openFloater(getKey()); + } +} + +bool LLFloaterIMNearbyChat::isChatVisible() const +{ + bool isVisible = false; + LLFloaterIMContainer* im_box = LLFloaterIMContainer::getInstance(); + // Is the IM floater container ever null? + llassert(im_box != NULL); + if (im_box != NULL) + { + isVisible = + isChatMultiTab() && gSavedSettings.getBOOL("NearbyChatIsNotTornOff")? + im_box->getVisible() && !im_box->isMinimized() : + getVisible() && !isMinimized(); + } + + return isVisible; +} + +void LLFloaterIMNearbyChat::showHistory() +{ + openFloater(); + setResizeLimits(getMinWidth(), EXPANDED_MIN_HEIGHT); +} + +std::string LLFloaterIMNearbyChat::getCurrentChat() +{ + return mInputEditor ? mInputEditor->getText() : LLStringUtil::null; +} + +// virtual +BOOL LLFloaterIMNearbyChat::handleKeyHere( KEY key, MASK mask ) +{ + BOOL handled = FALSE; + + if( KEY_RETURN == key && mask == MASK_CONTROL) + { + // shout + sendChat(CHAT_TYPE_SHOUT); + handled = TRUE; + } + + return handled; +} + +BOOL LLFloaterIMNearbyChat::matchChatTypeTrigger(const std::string& in_str, std::string* out_str) +{ + U32 in_len = in_str.length(); + S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers); + + bool string_was_found = false; + + for (S32 n = 0; n < cnt && !string_was_found; n++) + { + if (in_len <= sChatTypeTriggers[n].name.length()) + { + std::string trigger_trunc = sChatTypeTriggers[n].name; + LLStringUtil::truncate(trigger_trunc, in_len); + + if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc)) + { + *out_str = sChatTypeTriggers[n].name; + string_was_found = true; + } + } + } + + return string_was_found; +} + +void LLFloaterIMNearbyChat::onChatBoxKeystroke(LLTextEditor* caller, void* userdata) +{ + LLFirstUse::otherAvatarChatFirst(false); + + LLFloaterIMNearbyChat* self = (LLFloaterIMNearbyChat *)userdata; + + LLWString raw_text = self->mInputEditor->getWText(); + + // Can't trim the end, because that will cause autocompletion + // to eat trailing spaces that might be part of a gesture. + LLWStringUtil::trimHead(raw_text); + + S32 length = raw_text.length(); + + if( (length > 0) && (raw_text[0] != '/') ) // forward slash is used for escape (eg. emote) sequences + { + gAgent.startTyping(); + } + else + { + gAgent.stopTyping(); + } + + /* Doesn't work -- can't tell the difference between a backspace + that killed the selection vs. backspace at the end of line. + if (length > 1 + && text[0] == '/' + && key == KEY_BACKSPACE) + { + // the selection will already be deleted, but we need to trim + // off the character before + std::string new_text = raw_text.substr(0, length-1); + self->mInputEditor->setText( new_text ); + self->mInputEditor->setCursorToEnd(); + length = length - 1; + } + */ + + KEY key = gKeyboard->currentKey(); + + // Ignore "special" keys, like backspace, arrows, etc. + if (length > 1 + && raw_text[0] == '/' + && key < KEY_SPECIAL) + { + // we're starting a gesture, attempt to autocomplete + + std::string utf8_trigger = wstring_to_utf8str(raw_text); + std::string utf8_out_str(utf8_trigger); + + if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str)) + { + std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); + self->mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part + + // Select to end of line, starting from the character + // after the last one the user typed. + self->mInputEditor->selectNext(rest_of_match, false); + } + else if (matchChatTypeTrigger(utf8_trigger, &utf8_out_str)) + { + std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); + self->mInputEditor->setText(utf8_trigger + rest_of_match + " "); // keep original capitalization for user-entered part + self->mInputEditor->endOfDoc(); + } + + //llinfos << "GESTUREDEBUG " << trigger + // << " len " << length + // << " outlen " << out_str.getLength() + // << llendl; + } +} + +// static +void LLFloaterIMNearbyChat::onChatBoxFocusLost(LLFocusableElement* caller, void* userdata) +{ + // stop typing animation + gAgent.stopTyping(); +} + +void LLFloaterIMNearbyChat::onChatBoxFocusReceived() +{ + mInputEditor->setEnabled(!gDisconnected); +} + +EChatType LLFloaterIMNearbyChat::processChatTypeTriggers(EChatType type, std::string &str) +{ + U32 length = str.length(); + S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers); + + for (S32 n = 0; n < cnt; n++) + { + if (length >= sChatTypeTriggers[n].name.length()) + { + std::string trigger = str.substr(0, sChatTypeTriggers[n].name.length()); + + if (!LLStringUtil::compareInsensitive(trigger, sChatTypeTriggers[n].name)) + { + U32 trigger_length = sChatTypeTriggers[n].name.length(); + + // It's to remove space after trigger name + if (length > trigger_length && str[trigger_length] == ' ') + trigger_length++; + + str = str.substr(trigger_length, length); + + if (CHAT_TYPE_NORMAL == type) + return sChatTypeTriggers[n].type; + else + break; + } + } + } + + return type; +} + +void LLFloaterIMNearbyChat::sendChat( EChatType type ) +{ + if (mInputEditor) + { + LLWString text = mInputEditor->getWText(); + LLWStringUtil::trim(text); + LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. + if (!text.empty()) + { + // Check if this is destined for another channel + S32 channel = 0; + stripChannelNumber(text, &channel); + + std::string utf8text = wstring_to_utf8str(text); + // Try to trigger a gesture, if not chat to a script. + std::string utf8_revised_text; + if (0 == channel) + { + // discard returned "found" boolean + LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text); + } + else + { + utf8_revised_text = utf8text; + } + + utf8_revised_text = utf8str_trim(utf8_revised_text); + + type = processChatTypeTriggers(type, utf8_revised_text); + + if (!utf8_revised_text.empty()) + { + // Chat with animation + sendChatFromViewer(utf8_revised_text, type, TRUE); + } + } + + mInputEditor->setText(LLStringExplicit("")); + } + + gAgent.stopTyping(); + + // If the user wants to stop chatting on hitting return, lose focus + // and go out of chat mode. + if (gSavedSettings.getBOOL("CloseChatOnReturn")) + { + stopChat(); + } +} + +void LLFloaterIMNearbyChat::addMessage(const LLChat& chat,bool archive,const LLSD &args) +{ + appendMessage(chat, args); + + if(archive) + { + mMessageArchive.push_back(chat); + if(mMessageArchive.size()>200) + mMessageArchive.erase(mMessageArchive.begin()); + } + + // logging + if (!args["do_not_log"].asBoolean() + && gSavedPerAccountSettings.getBOOL("LogNearbyChat")) + { + std::string from_name = chat.mFromName; + + if (chat.mSourceType == CHAT_SOURCE_AGENT) + { + // if the chat is coming from an agent, log the complete name + LLAvatarName av_name; + LLAvatarNameCache::get(chat.mFromID, &av_name); + + if (!av_name.mIsDisplayNameDefault) + { + from_name = av_name.getCompleteName(); + } + } + + LLLogChat::saveHistory("chat", from_name, chat.mFromID, chat.mText); + } +} + + +void LLFloaterIMNearbyChat::onChatBoxCommit() +{ + if (mInputEditor->getText().length() > 0) + { + sendChat(CHAT_TYPE_NORMAL); + } + + gAgent.stopTyping(); +} + +void LLFloaterIMNearbyChat::displaySpeakingIndicator() +{ + LLSpeakerMgr::speaker_list_t speaker_list; + LLUUID id; + + id.setNull(); + mSpeakerMgr->update(TRUE); + mSpeakerMgr->getSpeakerList(&speaker_list, FALSE); + + for (LLSpeakerMgr::speaker_list_t::iterator i = speaker_list.begin(); i != speaker_list.end(); ++i) + { + LLPointer s = *i; + if (s->mSpeechVolume > 0 || s->mStatus == LLSpeaker::STATUS_SPEAKING) + { + id = s->mID; + break; + } + } + + if (!id.isNull()) + { + //mOutputMonitor->setVisible(TRUE); + //mOutputMonitor->setSpeakerId(id); + } + else + { + //mOutputMonitor->setVisible(FALSE); + } +} + +void LLFloaterIMNearbyChat::sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate) +{ + sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate); +} + +void LLFloaterIMNearbyChat::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate) +{ + // Look for "/20 foo" channel chats. + S32 channel = 0; + LLWString out_text = stripChannelNumber(wtext, &channel); + std::string utf8_out_text = wstring_to_utf8str(out_text); + std::string utf8_text = wstring_to_utf8str(wtext); + + utf8_text = utf8str_trim(utf8_text); + if (!utf8_text.empty()) + { + utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1); + } + + // Don't animate for chats people can't hear (chat to scripts) + if (animate && (channel == 0)) + { + if (type == CHAT_TYPE_WHISPER) + { + lldebugs << "You whisper " << utf8_text << llendl; + gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START); + } + else if (type == CHAT_TYPE_NORMAL) + { + lldebugs << "You say " << utf8_text << llendl; + gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START); + } + else if (type == CHAT_TYPE_SHOUT) + { + lldebugs << "You shout " << utf8_text << llendl; + gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START); + } + else + { + llinfos << "send_chat_from_viewer() - invalid volume" << llendl; + return; + } + } + else + { + if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP) + { + lldebugs << "Channel chat: " << utf8_text << llendl; + } + } + + send_chat_from_viewer(utf8_out_text, type, channel); +} + +// static +bool LLFloaterIMNearbyChat::isWordsName(const std::string& name) +{ + // checking to see if it's display name plus username in parentheses + S32 open_paren = name.find(" (", 0); + S32 close_paren = name.find(')', 0); + + if (open_paren != std::string::npos && + close_paren == name.length()-1) + { + return true; + } + else + { + //checking for a single space + S32 pos = name.find(' ', 0); + return std::string::npos != pos && name.rfind(' ', name.length()) == pos && 0 != pos && name.length()-1 != pos; + } +} + +// static +void LLFloaterIMNearbyChat::startChat(const char* line) +{ + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + if (nearby_chat) + { + nearby_chat->show(); + nearby_chat->setVisible(TRUE); + nearby_chat->setFocus(TRUE); + nearby_chat->mInputEditor->setFocus(TRUE); + + if (line) + { + std::string line_string(line); + nearby_chat->mInputEditor->setText(line_string); + } + + nearby_chat->mInputEditor->endOfDoc(); + } +} + +// Exit "chat mode" and do the appropriate focus changes +// static +void LLFloaterIMNearbyChat::stopChat() +{ + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + if (nearby_chat) + { + nearby_chat->mInputEditor->setFocus(FALSE); + gAgent.stopTyping(); + } +} + +// If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20. +// Otherwise returns input and channel 0. +LLWString LLFloaterIMNearbyChat::stripChannelNumber(const LLWString &mesg, S32* channel) +{ + if (mesg[0] == '/' + && mesg[1] == '/') + { + // This is a "repeat channel send" + *channel = sLastSpecialChatChannel; + return mesg.substr(2, mesg.length() - 2); + } + else if (mesg[0] == '/' + && mesg[1] + && LLStringOps::isDigit(mesg[1])) + { + // This a special "/20" speak on a channel + S32 pos = 0; + + // Copy the channel number into a string + LLWString channel_string; + llwchar c; + do + { + c = mesg[pos+1]; + channel_string.push_back(c); + pos++; + } + while(c && pos < 64 && LLStringOps::isDigit(c)); + + // Move the pointer forward to the first non-whitespace char + // Check isspace before looping, so we can handle "/33foo" + // as well as "/33 foo" + while(c && iswspace(c)) + { + c = mesg[pos+1]; + pos++; + } + + sLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10); + *channel = sLastSpecialChatChannel; + return mesg.substr(pos, mesg.length() - pos); + } + else + { + // This is normal chat. + *channel = 0; + return mesg; + } +} + +void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel) +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ChatFromViewer); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ChatData); + msg->addStringFast(_PREHASH_Message, utf8_out_text); + msg->addU8Fast(_PREHASH_Type, type); + msg->addS32("Channel", channel); + + gAgent.sendReliableMessage(); + + LLViewerStats::getInstance()->incStat(LLViewerStats::ST_CHAT_COUNT); +} + +class LLChatCommandHandler : public LLCommandHandler +{ +public: + // not allowed from outside the app + LLChatCommandHandler() : LLCommandHandler("chat", UNTRUSTED_BLOCK) { } + + // Your code here + bool handle(const LLSD& tokens, const LLSD& query_map, + LLMediaCtrl* web) + { + bool retval = false; + // Need at least 2 tokens to have a valid message. + if (tokens.size() < 2) + { + retval = false; + } + else + { + S32 channel = tokens[0].asInteger(); + // VWR-19499 Restrict function to chat channels greater than 0. + if ((channel > 0) && (channel < CHAT_CHANNEL_DEBUG)) + { + retval = true; + // Send unescaped message, see EXT-6353. + std::string unescaped_mesg (LLURI::unescape(tokens[1].asString())); + send_chat_from_viewer(unescaped_mesg, CHAT_TYPE_NORMAL, channel); + } + else + { + retval = false; + // Tell us this is an unsupported SLurl. + } + } + return retval; + } +}; + +// Creating the object registers with the dispatcher. +LLChatCommandHandler gChatHandler; diff --git a/indra/newview/llfloaterimnearbychat.h b/indra/newview/llfloaterimnearbychat.h new file mode 100644 index 0000000000..1479746fbd --- /dev/null +++ b/indra/newview/llfloaterimnearbychat.h @@ -0,0 +1,125 @@ +/** + * @file llfloaterimnearbychat.h + * @brief LLFloaterIMNearbyChat class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERIMNEARBYCHAT_H +#define LL_LLFLOATERIMNEARBYCHAT_H + +#include "llfloaterimsessiontab.h" +#include "llcombobox.h" +#include "llgesturemgr.h" +#include "llchat.h" +#include "llvoiceclient.h" +#include "lloutputmonitorctrl.h" +#include "llspeakers.h" +#include "llscrollbar.h" +#include "llviewerchat.h" +#include "llpanel.h" + +class LLResizeBar; + +class LLFloaterIMNearbyChat + : public LLFloaterIMSessionTab +{ +public: + // constructor for inline chat-bars (e.g. hosted in chat history window) + LLFloaterIMNearbyChat(const LLSD& key = LLSD(LLUUID())); + ~LLFloaterIMNearbyChat() {} + + static LLFloaterIMNearbyChat* buildFloater(const LLSD& key); + + /*virtual*/ BOOL postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void setVisible(BOOL visible); + + void loadHistory(); + void reloadMessages(); + void removeScreenChat(); + + void addToHost(); + void show(); + bool isChatVisible() const; + + /** @param archive true - to save a message to the chat history log */ + void addMessage (const LLChat& message,bool archive = true, const LLSD &args = LLSD()); + void onNearbyChatContextMenuItemClicked(const LLSD& userdata); + bool onNearbyChatCheckContextMenuItem(const LLSD& userdata); + + LLChatEntry* getChatBox() { return mInputEditor; } + + std::string getCurrentChat(); + + virtual BOOL handleKeyHere( KEY key, MASK mask ); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + + static void startChat(const char* line); + static void stopChat(); + + static void sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate); + static void sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate); + + static bool isWordsName(const std::string& name); + + void showHistory(); + +protected: + static BOOL matchChatTypeTrigger(const std::string& in_str, std::string* out_str); + static void onChatBoxKeystroke(LLTextEditor* caller, void* userdata); + static void onChatBoxFocusLost(LLFocusableElement* caller, void* userdata); + void onChatBoxFocusReceived(); + + void sendChat( EChatType type ); + void onChatBoxCommit(); + void onChatFontChange(LLFontGL* fontp); + + /*virtual*/ void onTearOffClicked(); + /*virtual*/ void onClickCloseBtn(); + + static LLWString stripChannelNumber(const LLWString &mesg, S32* channel); + EChatType processChatTypeTriggers(EChatType type, std::string &str); + + void displaySpeakingIndicator(); + + // Which non-zero channel did we last chat on? + static S32 sLastSpecialChatChannel; + + LLOutputMonitorCtrl* mOutputMonitor; + LLLocalSpeakerMgr* mSpeakerMgr; + + S32 mExpandedHeight; + +private: + + void onNearbySpeakers (); + + /*virtual*/ void refresh(); + + LLHandle mPopupMenuHandle; + std::vector mMessageArchive; + +}; + +#endif // LL_LLFLOATERIMNEARBYCHAT_H diff --git a/indra/newview/llfloaterimnearbychathandler.cpp b/indra/newview/llfloaterimnearbychathandler.cpp new file mode 100644 index 0000000000..0dfaa9174b --- /dev/null +++ b/indra/newview/llfloaterimnearbychathandler.cpp @@ -0,0 +1,630 @@ +/** + * @file LLFloaterIMNearbyChatHandler.cpp + * @brief Nearby chat chat managment + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagentdata.h" // for gAgentID +#include "llfloaterimnearbychathandler.h" + +#include "llchatitemscontainerctrl.h" +#include "llfirstuse.h" +#include "llfloaterscriptdebug.h" +#include "llhints.h" +#include "llfloaterimnearbychat.h" +#include "llrecentpeople.h" + +#include "llviewercontrol.h" + +#include "llfloaterreg.h"//for LLFloaterReg::getTypedInstance +#include "llviewerwindow.h"//for screen channel position +#include "llfloaterimnearbychat.h" +#include "llrootview.h" +#include "lllayoutstack.h" + +//add LLFloaterIMNearbyChatHandler to LLNotificationsUI namespace +using namespace LLNotificationsUI; + +static LLFloaterIMNearbyChatToastPanel* createToastPanel() +{ + LLFloaterIMNearbyChatToastPanel* item = LLFloaterIMNearbyChatToastPanel::createInstance(); + return item; +} + + +//----------------------------------------------------------------------------------------------- +//LLFloaterIMNearbyChatScreenChannel +//----------------------------------------------------------------------------------------------- + +class LLFloaterIMNearbyChatScreenChannel: public LLScreenChannelBase +{ + LOG_CLASS(LLFloaterIMNearbyChatScreenChannel); +public: + typedef std::vector > toast_vec_t; + typedef std::list > toast_list_t; + + LLFloaterIMNearbyChatScreenChannel(const Params& p) + : LLScreenChannelBase(p) + { + mStopProcessing = false; + + LLControlVariable* ctrl = gSavedSettings.getControl("NearbyToastLifeTime").get(); + if (ctrl) + { + ctrl->getSignal()->connect(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastsLifetime, this)); + } + + ctrl = gSavedSettings.getControl("NearbyToastFadingTime").get(); + if (ctrl) + { + ctrl->getSignal()->connect(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastFadingTime, this)); + } + } + + void addChat (LLSD& chat); + void arrangeToasts (); + + typedef boost::function create_toast_panel_callback_t; + void setCreatePanelCallback(create_toast_panel_callback_t value) { m_create_toast_panel_callback_t = value;} + + void onToastDestroyed (LLToast* toast, bool app_quitting); + void onToastFade (LLToast* toast); + + void redrawToasts() + { + arrangeToasts(); + } + + // hide all toasts from screen, but not remove them from a channel + // removes all toasts from a channel + virtual void removeToastsFromChannel() + { + for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it) + { + addToToastPool(it->get()); + } + m_active_toasts.clear(); + }; + + virtual void deleteAllChildren() + { + LL_DEBUGS("NearbyChat") << "Clearing toast pool" << llendl; + m_toast_pool.clear(); + m_active_toasts.clear(); + LLScreenChannelBase::deleteAllChildren(); + } + +protected: + void deactivateToast(LLToast* toast); + void addToToastPool(LLToast* toast) + { + if (!toast) return; + LL_DEBUGS("NearbyChat") << "Pooling toast" << llendl; + toast->setVisible(FALSE); + toast->stopTimer(); + toast->setIsHidden(true); + + // Nearby chat toasts that are hidden, not destroyed. They are collected to the toast pool, so that + // they can be used next time, this is done for performance. But if the toast lifetime was changed + // (from preferences floater (STORY-36)) while it was shown (at this moment toast isn't in the pool yet) + // changes don't take affect. + // So toast's lifetime should be updated each time it's added to the pool. Otherwise viewer would have + // to be restarted so that changes take effect. + toast->setLifetime(gSavedSettings.getS32("NearbyToastLifeTime")); + toast->setFadingTime(gSavedSettings.getS32("NearbyToastFadingTime")); + m_toast_pool.push_back(toast->getHandle()); + } + + void createOverflowToast(S32 bottom, F32 timer); + + void updateToastsLifetime(); + + void updateToastFadingTime(); + + create_toast_panel_callback_t m_create_toast_panel_callback_t; + + bool createPoolToast(); + + toast_vec_t m_active_toasts; + toast_list_t m_toast_pool; + + bool mStopProcessing; + bool mChannelRect; +}; + + + +//----------------------------------------------------------------------------------------------- +// LLFloaterIMNearbyChatToast +//----------------------------------------------------------------------------------------------- + +// We're deriving from LLToast to be able to override onClose() +// in order to handle closing nearby chat toasts properly. +class LLFloaterIMNearbyChatToast : public LLToast +{ + LOG_CLASS(LLFloaterIMNearbyChatToast); +public: + LLFloaterIMNearbyChatToast(const LLToast::Params& p, LLFloaterIMNearbyChatScreenChannel* nc_channelp) + : LLToast(p), + mNearbyChatScreenChannelp(nc_channelp) + { + } + + /*virtual*/ void onClose(bool app_quitting); + +private: + LLFloaterIMNearbyChatScreenChannel* mNearbyChatScreenChannelp; +}; + +//----------------------------------------------------------------------------------------------- +// LLFloaterIMNearbyChatScreenChannel +//----------------------------------------------------------------------------------------------- + +void LLFloaterIMNearbyChatScreenChannel::deactivateToast(LLToast* toast) +{ + toast_vec_t::iterator pos = std::find(m_active_toasts.begin(), m_active_toasts.end(), toast->getHandle()); + + if (pos == m_active_toasts.end()) + { + llassert(pos == m_active_toasts.end()); + return; + } + + LL_DEBUGS("NearbyChat") << "Deactivating toast" << llendl; + m_active_toasts.erase(pos); +} + +void LLFloaterIMNearbyChatScreenChannel::createOverflowToast(S32 bottom, F32 timer) +{ + //we don't need overflow toast in nearby chat +} + +void LLFloaterIMNearbyChatScreenChannel::onToastDestroyed(LLToast* toast, bool app_quitting) +{ + LL_DEBUGS("NearbyChat") << "Toast destroyed (app_quitting=" << app_quitting << ")" << llendl; + + if (app_quitting) + { + // Viewer is quitting. + // Immediately stop processing chat messages (EXT-1419). + mStopProcessing = true; +} + else + { + // The toast is being closed by user (STORM-192). + // Remove it from the list of active toasts to prevent + // further references to the invalid pointer. + deactivateToast(toast); + } +} + +void LLFloaterIMNearbyChatScreenChannel::onToastFade(LLToast* toast) +{ + LL_DEBUGS("NearbyChat") << "Toast fading" << llendl; + + //fade mean we put toast to toast pool + if(!toast) + return; + + deactivateToast(toast); + + addToToastPool(toast); + + arrangeToasts(); +} + +void LLFloaterIMNearbyChatScreenChannel::updateToastsLifetime() +{ + S32 seconds = gSavedSettings.getS32("NearbyToastLifeTime"); + toast_list_t::iterator it; + + for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it) + { + (*it).get()->setLifetime(seconds); + } +} + +void LLFloaterIMNearbyChatScreenChannel::updateToastFadingTime() +{ + S32 seconds = gSavedSettings.getS32("NearbyToastFadingTime"); + toast_list_t::iterator it; + + for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it) + { + (*it).get()->setFadingTime(seconds); + } +} + +bool LLFloaterIMNearbyChatScreenChannel::createPoolToast() +{ + LLFloaterIMNearbyChatToastPanel* panel= m_create_toast_panel_callback_t(); + if(!panel) + return false; + + LLToast::Params p; + p.panel = panel; + p.lifetime_secs = gSavedSettings.getS32("NearbyToastLifeTime"); + p.fading_time_secs = gSavedSettings.getS32("NearbyToastFadingTime"); + + LLToast* toast = new LLFloaterIMNearbyChatToast(p, this); + + + toast->setOnFadeCallback(boost::bind(&LLFloaterIMNearbyChatScreenChannel::onToastFade, this, _1)); + + // If the toast gets somehow prematurely destroyed, deactivate it to prevent crash (STORM-1352). + toast->setOnToastDestroyedCallback(boost::bind(&LLFloaterIMNearbyChatScreenChannel::onToastDestroyed, this, _1, false)); + + LL_DEBUGS("NearbyChat") << "Creating and pooling toast" << llendl; + m_toast_pool.push_back(toast->getHandle()); + return true; +} + +void LLFloaterIMNearbyChatScreenChannel::addChat(LLSD& chat) +{ + //look in pool. if there is any message + if(mStopProcessing) + return; + + /* + find last toast and check ID + */ + + if(m_active_toasts.size()) + { + LLUUID fromID = chat["from_id"].asUUID(); // agent id or object id + std::string from = chat["from"].asString(); + LLToast* toast = m_active_toasts[0].get(); + if (toast) + { + LLFloaterIMNearbyChatToastPanel* panel = dynamic_cast(toast->getPanel()); + + if(panel && panel->messageID() == fromID && panel->getFromName() == from && panel->canAddText()) + { + panel->addMessage(chat); + toast->reshapeToPanel(); + toast->startTimer(); + + arrangeToasts(); + return; + } + } + } + + + + if(m_toast_pool.empty()) + { + //"pool" is empty - create one more panel + LL_DEBUGS("NearbyChat") << "Empty pool" << llendl; + if(!createPoolToast())//created toast will go to pool. so next call will find it + return; + addChat(chat); + return; + } + + int chat_type = chat["chat_type"].asInteger(); + + if( ((EChatType)chat_type == CHAT_TYPE_DEBUG_MSG)) + { + if(gSavedSettings.getBOOL("ShowScriptErrors") == FALSE) + return; + if(gSavedSettings.getS32("ShowScriptErrorsLocation")== 1) + return; + } + + + //take 1st element from pool, (re)initialize it, put it in active toasts + + LL_DEBUGS("NearbyChat") << "Getting toast from pool" << llendl; + LLToast* toast = m_toast_pool.back().get(); + + m_toast_pool.pop_back(); + + + LLFloaterIMNearbyChatToastPanel* panel = dynamic_cast(toast->getPanel()); + if(!panel) + return; + panel->init(chat); + + toast->reshapeToPanel(); + toast->startTimer(); + + m_active_toasts.push_back(toast->getHandle()); + + arrangeToasts(); +} + +static bool sort_toasts_predicate(LLHandle first, LLHandle second) +{ + if (!first.get() || !second.get()) return false; // STORM-1352 + + F32 v1 = first.get()->getTimeLeftToLive(); + F32 v2 = second.get()->getTimeLeftToLive(); + return v1 > v2; +} + +void LLFloaterIMNearbyChatScreenChannel::arrangeToasts() +{ + if(mStopProcessing || isHovering()) + return; + + if (mFloaterSnapRegion == NULL) + { + mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); + } + + if (!getParent()) + { + // connect to floater snap region just to get resize events, we don't care about being a proper widget + mFloaterSnapRegion->addChild(this); + setFollows(FOLLOWS_ALL); + } + + LLRect toast_rect; + updateRect(); + + LLRect channel_rect; + mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &channel_rect, gFloaterView); + channel_rect.mLeft += 10; + channel_rect.mRight = channel_rect.mLeft + 300; + + S32 channel_bottom = channel_rect.mBottom; + + S32 bottom = channel_bottom + 80; + S32 margin = gSavedSettings.getS32("ToastGap"); + + //sort active toasts + std::sort(m_active_toasts.begin(),m_active_toasts.end(),sort_toasts_predicate); + + //calc max visible item and hide other toasts. + + for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it) + { + LLToast* toast = it->get(); + if (!toast) + { + llwarns << "NULL found in the active chat toasts list!" << llendl; + continue; + } + + S32 toast_top = bottom + toast->getRect().getHeight() + margin; + + if(toast_top > channel_rect.getHeight()) + { + while(it!=m_active_toasts.end()) + { + addToToastPool(it->get()); + it=m_active_toasts.erase(it); + } + break; + } + + toast_rect = toast->getRect(); + toast_rect.setLeftTopAndSize(channel_rect.mLeft , bottom + toast_rect.getHeight(), toast_rect.getWidth() ,toast_rect.getHeight()); + + toast->setRect(toast_rect); + bottom += toast_rect.getHeight() - toast->getTopPad() + margin; + } + + // use reverse order to provide correct z-order and avoid toast blinking + + for(toast_vec_t::reverse_iterator it = m_active_toasts.rbegin(); it != m_active_toasts.rend(); ++it) + { + LLToast* toast = it->get(); + if (toast) + { + toast->setIsHidden(false); + toast->setVisible(TRUE); + } + } + +} + + + +//----------------------------------------------------------------------------------------------- +//LLFloaterIMNearbyChatHandler +//----------------------------------------------------------------------------------------------- +boost::scoped_ptr LLFloaterIMNearbyChatHandler::sChatWatcher(new LLEventStream("LLChat")); + +LLFloaterIMNearbyChatHandler::LLFloaterIMNearbyChatHandler() +{ + // Getting a Channel for our notifications + LLFloaterIMNearbyChatScreenChannel::Params p; + p.id = LLUUID(gSavedSettings.getString("NearByChatChannelUUID")); + LLFloaterIMNearbyChatScreenChannel* channel = new LLFloaterIMNearbyChatScreenChannel(p); + + LLFloaterIMNearbyChatScreenChannel::create_toast_panel_callback_t callback = createToastPanel; + + channel->setCreatePanelCallback(callback); + + LLChannelManager::getInstance()->addChannel(channel); + + mChannel = channel->getHandle(); +} + +LLFloaterIMNearbyChatHandler::~LLFloaterIMNearbyChatHandler() +{ +} + + +void LLFloaterIMNearbyChatHandler::initChannel() +{ + //LLRect snap_rect = gFloaterView->getSnapRect(); + //mChannel->init(snap_rect.mLeft, snap_rect.mLeft + 200); +} + + + +void LLFloaterIMNearbyChatHandler::processChat(const LLChat& chat_msg, + const LLSD &args) +{ + if(chat_msg.mMuted == TRUE) + return; + + if(chat_msg.mText.empty()) + return;//don't process empty messages + + LLFloaterReg::getInstance("im_container"); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + + // Build notification data + LLSD chat; + chat["message"] = chat_msg.mText; + chat["from"] = chat_msg.mFromName; + chat["from_id"] = chat_msg.mFromID; + chat["time"] = chat_msg.mTime; + chat["source"] = (S32)chat_msg.mSourceType; + chat["chat_type"] = (S32)chat_msg.mChatType; + chat["chat_style"] = (S32)chat_msg.mChatStyle; + // Pass sender info so that it can be rendered properly (STORM-1021). + chat["sender_slurl"] = LLViewerChat::getSenderSLURL(chat_msg, args); + + if (chat_msg.mChatType == CHAT_TYPE_DIRECT && + chat_msg.mText.length() > 0 && + chat_msg.mText[0] == '@') + { + // Send event on to LLEventStream and exit + sChatWatcher->post(chat); + return; + } + + // don't show toast and add message to chat history on receive debug message + // with disabled setting showing script errors or enabled setting to show script + // errors in separate window. + if (chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG) + { + if(gSavedSettings.getBOOL("ShowScriptErrors") == FALSE) + return; + + // don't process debug messages from not owned objects, see EXT-7762 + if (gAgentID != chat_msg.mOwnerID) + { + return; + } + + if (gSavedSettings.getS32("ShowScriptErrorsLocation")== 1)// show error in window //("ScriptErrorsAsChat")) + { + + LLColor4 txt_color; + + LLViewerChat::getChatColor(chat_msg,txt_color); + + LLFloaterScriptDebug::addScriptLine(chat_msg.mText, + chat_msg.mFromName, + txt_color, + chat_msg.mFromID); + return; + } + } + + nearby_chat->addMessage(chat_msg, true, args); + + if(chat_msg.mSourceType == CHAT_SOURCE_AGENT + && chat_msg.mFromID.notNull() + && chat_msg.mFromID != gAgentID) + { + LLFirstUse::otherAvatarChatFirst(); + + // Add sender to the recent people list. + LLRecentPeople::instance().add(chat_msg.mFromID); + + } + + // Send event on to LLEventStream + sChatWatcher->post(chat); + + if( nearby_chat->isInVisibleChain() + || ( chat_msg.mSourceType == CHAT_SOURCE_AGENT + && gSavedSettings.getBOOL("UseChatBubbles") ) + || mChannel.isDead() + || !mChannel.get()->getShowToasts() ) // to prevent toasts in Do Not Disturb mode + return;//no need in toast if chat is visible or if bubble chat is enabled + + // arrange a channel on a screen + if(!mChannel.get()->getVisible()) + { + initChannel(); + } + + /* + //comment all this due to EXT-4432 + ..may clean up after some time... + + //only messages from AGENTS + if(CHAT_SOURCE_OBJECT == chat_msg.mSourceType) + { + if(chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG) + return;//ok for now we don't skip messeges from object, so skip only debug messages + } + */ + + LLFloaterIMNearbyChatScreenChannel* channel = dynamic_cast(mChannel.get()); + + if(channel) + { + // Handle IRC styled messages. + std::string toast_msg; + if (chat_msg.mChatStyle == CHAT_STYLE_IRC) + { + if (!chat_msg.mFromName.empty()) + { + toast_msg += chat_msg.mFromName; + } + toast_msg += chat_msg.mText.substr(3); + } + else + { + toast_msg = chat_msg.mText; + } + + // Add a nearby chat toast. + LLUUID id; + id.generate(); + chat["id"] = id; + std::string r_color_name = "White"; + F32 r_color_alpha = 1.0f; + LLViewerChat::getChatColor( chat_msg, r_color_name, r_color_alpha); + + chat["text_color"] = r_color_name; + chat["color_alpha"] = r_color_alpha; + chat["font_size"] = (S32)LLViewerChat::getChatFontSize() ; + chat["message"] = toast_msg; + channel->addChat(chat); + } +} + + +//----------------------------------------------------------------------------------------------- +// LLFloaterIMNearbyChatToast +//----------------------------------------------------------------------------------------------- + +// virtual +void LLFloaterIMNearbyChatToast::onClose(bool app_quitting) +{ + mNearbyChatScreenChannelp->onToastDestroyed(this, app_quitting); +} + +// EOF diff --git a/indra/newview/llfloaterimnearbychathandler.h b/indra/newview/llfloaterimnearbychathandler.h new file mode 100644 index 0000000000..5e6f8cde30 --- /dev/null +++ b/indra/newview/llfloaterimnearbychathandler.h @@ -0,0 +1,54 @@ +/** + * @file llfloaterimnearbychathandler.h + * @brief nearby chat notify + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERIMNEARBYCHATHANDLER_H +#define LL_LLFLOATERIMNEARBYCHATHANDLER_H + +#include "llnotificationhandler.h" + +class LLEventPump; + +//add LLFloaterIMNearbyChatHandler to LLNotificationsUI namespace +namespace LLNotificationsUI{ + +class LLFloaterIMNearbyChatHandler : public LLChatHandler +{ +public: + LLFloaterIMNearbyChatHandler(); + virtual ~LLFloaterIMNearbyChatHandler(); + + + virtual void processChat(const LLChat& chat_msg, const LLSD &args); + +protected: + virtual void initChannel(); + + static boost::scoped_ptr sChatWatcher; +}; + +} + +#endif /* LL_LLFLOATERIMNEARBYCHATHANDLER_H */ diff --git a/indra/newview/llfloaterimnearbychatlistener.cpp b/indra/newview/llfloaterimnearbychatlistener.cpp new file mode 100644 index 0000000000..14a22bcd84 --- /dev/null +++ b/indra/newview/llfloaterimnearbychatlistener.cpp @@ -0,0 +1,100 @@ +/** + * @file llfloaterimnearbychatlistener.cpp + * @author Dave Simmons + * @date 2011-03-15 + * @brief Implementation for LLFloaterIMNearbyChatListener. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimnearbychatlistener.h" +#include "llfloaterimnearbychat.h" + +#include "llagent.h" +#include "llchat.h" + + + +LLFloaterIMNearbyChatListener::LLFloaterIMNearbyChatListener(LLFloaterIMNearbyChat & chatbar) + : LLEventAPI("LLChatBar", + "LLChatBar listener to (e.g.) sendChat, etc."), + mChatbar(chatbar) +{ + add("sendChat", + "Send chat to the simulator:\n" + "[\"message\"] chat message text [required]\n" + "[\"channel\"] chat channel number [default = 0]\n" + "[\"type\"] chat type \"whisper\", \"normal\", \"shout\" [default = \"normal\"]", + &LLFloaterIMNearbyChatListener::sendChat); +} + + +// "sendChat" command +void LLFloaterIMNearbyChatListener::sendChat(LLSD const & chat_data) const +{ + // Extract the data + std::string chat_text = chat_data["message"].asString(); + + S32 channel = 0; + if (chat_data.has("channel")) + { + channel = chat_data["channel"].asInteger(); + if (channel < 0 || channel >= CHAT_CHANNEL_DEBUG) + { // Use 0 up to (but not including) CHAT_CHANNEL_DEBUG + channel = 0; + } + } + + EChatType type_o_chat = CHAT_TYPE_NORMAL; + if (chat_data.has("type")) + { + std::string type_string = chat_data["type"].asString(); + if (type_string == "whisper") + { + type_o_chat = CHAT_TYPE_WHISPER; + } + else if (type_string == "shout") + { + type_o_chat = CHAT_TYPE_SHOUT; + } + } + + // Have to prepend /42 style channel numbers + std::string chat_to_send; + if (channel == 0) + { + chat_to_send = chat_text; + } + else + { + chat_to_send += "/"; + chat_to_send += chat_data["channel"].asString(); + chat_to_send += " "; + chat_to_send += chat_text; + } + + // Send it as if it was typed in + mChatbar.sendChatFromViewer(chat_to_send, type_o_chat, (BOOL)(channel == 0)); +} + diff --git a/indra/newview/llfloaterimnearbychatlistener.h b/indra/newview/llfloaterimnearbychatlistener.h new file mode 100644 index 0000000000..1470a6dc1e --- /dev/null +++ b/indra/newview/llfloaterimnearbychatlistener.h @@ -0,0 +1,50 @@ +/** + * @file llfloaterimnearbychatlistener.h + * @author Dave Simmons + * @date 2011-03-15 + * @brief Class definition for LLFloaterIMNearbyChatListener. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#ifndef LL_LLFLOATERIMNEARBYCHATLISTENER_H +#define LL_LLFLOATERIMNEARBYCHATLISTENER_H + +#include "lleventapi.h" + +class LLSD; +class LLFloaterIMNearbyChat; + +class LLFloaterIMNearbyChatListener : public LLEventAPI +{ +public: + LLFloaterIMNearbyChatListener(LLFloaterIMNearbyChat & chatbar); + +private: + void sendChat(LLSD const & chat_data) const; + + LLFloaterIMNearbyChat & mChatbar; +}; + +#endif // LL_LLFLOATERIMNEARBYCHATLISTENER_H + diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp new file mode 100644 index 0000000000..0c622e07c4 --- /dev/null +++ b/indra/newview/llfloaterimsession.cpp @@ -0,0 +1,1202 @@ +/** + * @file llfloaterimsession.cpp + * @brief LLFloaterIMSession class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimsession.h" + +#include "lldraghandle.h" +#include "llnotificationsutil.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llavataractions.h" +#include "llavatarnamecache.h" +#include "llbutton.h" +#include "llchannelmanager.h" +#include "llchiclet.h" +#include "llchicletbar.h" +#include "llfloaterreg.h" +#include "llfloateravatarpicker.h" +#include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container +#include "llinventoryfunctions.h" +//#include "lllayoutstack.h" +#include "llchatentry.h" +#include "lllogchat.h" +#include "llscreenchannel.h" +#include "llsyswellwindow.h" +#include "lltrans.h" +#include "llchathistory.h" +#include "llnotifications.h" +#include "llviewerwindow.h" +#include "lltransientfloatermgr.h" +#include "llinventorymodel.h" +#include "llrootview.h" +#include "llspeakers.h" +#include "llviewerchat.h" +#include "llnotificationmanager.h" +#include "llautoreplace.h" + +floater_showed_signal_t LLFloaterIMSession::sIMFloaterShowedSignal; + +LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id) + : LLFloaterIMSessionTab(session_id), + mLastMessageIndex(-1), + mDialog(IM_NOTHING_SPECIAL), + mSavedTitle(), + mTypingStart(), + mShouldSendTypingState(false), + mMeTyping(false), + mOtherTyping(false), + mTypingTimer(), + mTypingTimeoutTimer(), + mPositioned(false), + mSessionInitialized(false), + mStartConferenceInSameFloater(false) +{ + mIsNearbyChat = false; + + initIMSession(session_id); + + setOverlapsScreenChannel(true); + + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); + + setDocked(true); +} + + +// virtual +void LLFloaterIMSession::refresh() +{ + if (mMeTyping) +{ + // Time out if user hasn't typed for a while. + if (mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS) + { + setTyping(false); + } + } +} + +// virtual +void LLFloaterIMSession::onClickCloseBtn() +{ + LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(mSessionID); + + if (session != NULL) + { + bool is_call_with_chat = session->isGroupSessionType() + || session->isAdHocSessionType() || session->isP2PSessionType(); + + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + + if (is_call_with_chat && voice_channel != NULL + && voice_channel->isActive()) + { + LLSD payload; + payload["session_id"] = mSessionID; + LLNotificationsUtil::add("ConfirmLeaveCall", LLSD(), payload, confirmLeaveCallCallback); + return; + } + } + else + { + llwarns << "Empty session with id: " << (mSessionID.asString()) << llendl; + return; + } + + LLFloaterIMSessionTab::onClickCloseBtn(); +} + +/* static */ +void LLFloaterIMSession::newIMCallback(const LLSD& data) +{ + if (data["num_unread"].asInteger() > 0 || data["from_id"].asUUID().isNull()) + { + LLUUID session_id = data["session_id"].asUUID(); + + LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance("impanel", session_id); + + // update if visible, otherwise will be updated when opened + if (floater && floater->getVisible()) + { + floater->updateMessages(); + } + } +} + +void LLFloaterIMSession::onVisibilityChange(const LLSD& new_visibility) +{ + bool visible = new_visibility.asBoolean(); + + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + + if (visible && voice_channel && + voice_channel->getState() == LLVoiceChannel::STATE_CONNECTED) + { + LLFloaterReg::showInstance("voice_call", mSessionID); + } + else + { + LLFloaterReg::hideInstance("voice_call", mSessionID); + } +} + +void LLFloaterIMSession::onSendMsg( LLUICtrl* ctrl, void* userdata ) +{ + LLFloaterIMSession* self = (LLFloaterIMSession*) userdata; + self->sendMsgFromInputEditor(); + self->setTyping(false); +} + +void LLFloaterIMSession::sendMsgFromInputEditor() +{ + if (gAgent.isGodlike() + || (mDialog != IM_NOTHING_SPECIAL) + || !mOtherParticipantUUID.isNull()) + { + if (mInputEditor) + { + LLWString text = mInputEditor->getWText(); + LLWStringUtil::trim(text); + LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. + if(!text.empty()) + { + // Truncate and convert to UTF8 for transport + std::string utf8_text = wstring_to_utf8str(text); + + sendMsg(utf8_text); + + mInputEditor->setText(LLStringUtil::null); + } + } + } + else + { + llinfos << "Cannot send IM to everyone unless you're a god." << llendl; + } +} + +void LLFloaterIMSession::sendMsg(const std::string& msg) +{ + const std::string utf8_text = utf8str_truncate(msg, MAX_MSG_BUF_SIZE - 1); + + if (mSessionInitialized) + { + LLIMModel::sendMessage(utf8_text, mSessionID, mOtherParticipantUUID, mDialog); + } + else + { + //queue up the message to send once the session is initialized + mQueuedMsgsForInit.append(utf8_text); + } + + updateMessages(); +} + +LLFloaterIMSession::~LLFloaterIMSession() +{ + mVoiceChannelStateChangeConnection.disconnect(); + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(this); + } + + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); +} + + +void LLFloaterIMSession::initIMSession(const LLUUID& session_id) +{ + // Change the floater key to bind it to a new session. + setKey(session_id); + + mSessionID = session_id; + mSession = LLIMModel::getInstance()->findIMSession(mSessionID); + + if (mSession) + { + mIsP2PChat = mSession->isP2PSessionType(); + mSessionInitialized = mSession->mSessionInitialized; + mDialog = mSession->mType; + } +} + +void LLFloaterIMSession::initIMFloater() +{ + const LLUUID& other_party_id = + LLIMModel::getInstance()->getOtherParticipantID(mSessionID); + if (other_party_id.notNull()) + { + mOtherParticipantUUID = other_party_id; + } + + boundVoiceChannel(); + + mTypingStart = LLTrans::getString("IM_typing_start_string"); + + // Show control panel in torn off floaters only. + mParticipantListPanel->setVisible(!getHost() && gSavedSettings.getBOOL("IMShowControlPanel")); + + // Disable input editor if session cannot accept text + if ( mSession && !mSession->mTextIMPossible ) + { + mInputEditor->setEnabled(FALSE); + mInputEditor->setLabel(LLTrans::getString("IM_unavailable_text_label")); + } + + if (!mIsP2PChat) + { + std::string session_name(LLIMModel::instance().getName(mSessionID)); + updateSessionName(session_name); + } +} + +//virtual +BOOL LLFloaterIMSession::postBuild() +{ + BOOL result = LLFloaterIMSessionTab::postBuild(); + + mInputEditor->setMaxTextLength(1023); + // enable line history support for instant message bar + // XXX stinson TODO : resolve merge by adding autoreplace to text editors +#if 0 + // *TODO Establish LineEditor with autoreplace callback + mInputEditor->setAutoreplaceCallback(boost::bind(&LLAutoReplace::autoreplaceCallback, LLAutoReplace::getInstance(), _1, _2)); +#endif + + mInputEditor->setFocusReceivedCallback( boost::bind(onInputEditorFocusReceived, _1, this) ); + mInputEditor->setFocusLostCallback( boost::bind(onInputEditorFocusLost, _1, this) ); + mInputEditor->setKeystrokeCallback( boost::bind(onInputEditorKeystroke, _1, this) ); + mInputEditor->setCommitCallback(boost::bind(onSendMsg, _1, this)); + + setDocked(true); + + LLButton* add_btn = getChild("add_btn"); + + // Allow to add chat participants depending on the session type + add_btn->setEnabled(isInviteAllowed()); + add_btn->setClickedCallback(boost::bind(&LLFloaterIMSession::onAddButtonClicked, this)); + + childSetAction("voice_call_btn", boost::bind(&LLFloaterIMSession::onCallButtonClicked, this)); + + LLVoiceClient::getInstance()->addObserver(this); + + //*TODO if session is not initialized yet, add some sort of a warning message like "starting session...blablabla" + //see LLFloaterIMPanel for how it is done (IB) + + initIMFloater(); + + return result; +} + +void LLFloaterIMSession::onAddButtonClicked() +{ + LLView * button = findChild("toolbar_panel")->findChild("add_btn"); + LLFloater* root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterIMSession::addSessionParticipants, this, _1), TRUE, TRUE, FALSE, root_floater->getName(), button); + if (!picker) + { + return; + } + + // Need to disable 'ok' button when selected users are already in conversation. + picker->setOkBtnEnableCb(boost::bind(&LLFloaterIMSession::canAddSelectedToChat, this, _1)); + + if (root_floater) + { + root_floater->addDependentFloater(picker); + } +} + +bool LLFloaterIMSession::canAddSelectedToChat(const uuid_vec_t& uuids) +{ + if (!mSession + || mDialog == IM_SESSION_GROUP_START + || mDialog == IM_SESSION_INVITE && gAgent.isInGroup(mSessionID)) + { + return false; + } + + if (mIsP2PChat) + { + // For a P2P session just check if we are not adding the other participant. + + for (uuid_vec_t::const_iterator id = uuids.begin(); + id != uuids.end(); ++id) + { + if (*id == mOtherParticipantUUID) + { + return false; + } + } + } + else + { + // For a conference session we need to check against the list from LLSpeakerMgr, + // because this list may change when participants join or leave the session. + + LLSpeakerMgr::speaker_list_t speaker_list; + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + speaker_mgr->getSpeakerList(&speaker_list, true); + } + + for (uuid_vec_t::const_iterator id = uuids.begin(); + id != uuids.end(); ++id) + { + for (LLSpeakerMgr::speaker_list_t::const_iterator it = speaker_list.begin(); + it != speaker_list.end(); ++it) + { + const LLPointer& speaker = *it; + if (*id == speaker->mID) + { + return false; + } + } + } + } + + return true; +} + +void LLFloaterIMSession::addSessionParticipants(const uuid_vec_t& uuids) +{ + if (mIsP2PChat) + { + LLSD payload; + LLSD args; + + LLNotificationsUtil::add("ConfirmAddingChatParticipants", args, payload, + boost::bind(&LLFloaterIMSession::addP2PSessionParticipants, this, _1, _2, uuids)); + } + else + { + // remember whom we have invited, to notify others later, when the invited ones actually join + mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); + + inviteToSession(uuids); + } +} + +void LLFloaterIMSession::addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) + { + return; + } + + mStartConferenceInSameFloater = true; + + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + + // first check whether this is a voice session + bool is_voice_call = voice_channel != NULL && voice_channel->isActive(); + + uuid_vec_t temp_ids; + + // Add the initial participant of a P2P session + temp_ids.push_back(mOtherParticipantUUID); + temp_ids.insert(temp_ids.end(), uuids.begin(), uuids.end()); + + // then we can close the current session + onClose(false); + + // we start a new session so reset the initialization flag + mSessionInitialized = false; + + // remember whom we have invited, to notify others later, when the invited ones actually join + mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); + + // Start a new ad hoc voice call if we invite new participants to a P2P call, + // or start a text chat otherwise. + if (is_voice_call) + { + LLAvatarActions::startAdhocCall(temp_ids, mSessionID); + } + else + { + LLAvatarActions::startConference(temp_ids, mSessionID); + } +} + +void LLFloaterIMSession::sendParticipantsAddedNotification(const uuid_vec_t& uuids) +{ + std::string names_string; + LLAvatarActions::buildResidentsString(uuids, names_string); + LLStringUtil::format_map_t args; + args["[NAME]"] = names_string; + + sendMsg(getString(uuids.size() > 1 ? "multiple_participants_added" : "participant_added", args)); +} + +void LLFloaterIMSession::boundVoiceChannel() +{ + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + if(voice_channel) + { + mVoiceChannelStateChangeConnection = voice_channel->setStateChangedCallback( + boost::bind(&LLFloaterIMSession::onVoiceChannelStateChanged, this, _1, _2)); + + //call (either p2p, group or ad-hoc) can be already in started state + bool callIsActive = voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED; + updateCallBtnState(callIsActive); + } +} + +void LLFloaterIMSession::onCallButtonClicked() +{ + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + if (voice_channel) + { + bool is_call_active = voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED; + if (is_call_active) + { + gIMMgr->endCall(mSessionID); + } + else + { + gIMMgr->startCall(mSessionID); + } + } +} + +void LLFloaterIMSession::onChange(EStatusType status, const std::string &channelURI, bool proximal) +{ + if(status != STATUS_JOINING && status != STATUS_LEFT_CHANNEL) + { + enableDisableCallBtn(); + } +} + +void LLFloaterIMSession::onVoiceChannelStateChanged( + const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state) +{ + bool callIsActive = new_state >= LLVoiceChannel::STATE_CALL_STARTED; + updateCallBtnState(callIsActive); +} + +void LLFloaterIMSession::updateSessionName(const std::string& name) +{ + LLFloaterIMSessionTab::updateSessionName(name); + setTitle(name); + mTypingStart.setArg("[NAME]", name); +} + +//static +LLFloaterIMSession* LLFloaterIMSession::show(const LLUUID& session_id) +{ + closeHiddenIMToasts(); + + if (!gIMMgr->hasSession(session_id)) + return NULL; + + // Test the existence of the floater before we try to create it + bool exist = findInstance(session_id); + + // Get the floater: this will create the instance if it didn't exist + LLFloaterIMSession* floater = getInstance(session_id); + if (!floater) + return NULL; + + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + + // Do not add again existing floaters + if (!exist) + { + // LLTabContainer::eInsertionPoint i_pt = user_initiated ? LLTabContainer::RIGHT_OF_CURRENT : LLTabContainer::END; + // TODO: mantipov: use LLTabContainer::RIGHT_OF_CURRENT if it exists + LLTabContainer::eInsertionPoint i_pt = LLTabContainer::END; + if (floater_container) + { + floater_container->addFloater(floater, TRUE, i_pt); + } + } + + floater->openFloater(floater->getKey()); + + floater->setVisible(TRUE); + + return floater; +} +//static +LLFloaterIMSession* LLFloaterIMSession::findInstance(const LLUUID& session_id) +{ + LLFloaterIMSession* conversation = + LLFloaterReg::findTypedInstance("impanel", session_id); + + return conversation; +} + +LLFloaterIMSession* LLFloaterIMSession::getInstance(const LLUUID& session_id) +{ + LLFloaterIMSession* conversation = + LLFloaterReg::getTypedInstance("impanel", session_id); + + return conversation; +} + +void LLFloaterIMSession::onClose(bool app_quitting) +{ + setTyping(false); + + // The source of much argument and design thrashing + // Should the window hide or the session close when the X is clicked? + // + // Last change: + // EXT-3516 X Button should end IM session, _ button should hide + gIMMgr->leaveSession(mSessionID); + + // Clean up the conversation *after* the session has been ended + LLFloaterIMSessionTab::onClose(app_quitting); +} + +void LLFloaterIMSession::setDocked(bool docked, bool pop_on_undock) +{ + // update notification channel state + LLNotificationsUI::LLScreenChannel* channel = static_cast + (LLNotificationsUI::LLChannelManager::getInstance()-> + findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); + + if(!isChatMultiTab()) + { + LLTransientDockableFloater::setDocked(docked, pop_on_undock); + } + + // update notification channel state + if(channel) + { + channel->updateShowToastsState(); + channel->redrawToasts(); + } +} + +void LLFloaterIMSession::setVisible(BOOL visible) +{ + LLNotificationsUI::LLScreenChannel* channel = static_cast + (LLNotificationsUI::LLChannelManager::getInstance()-> + findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); + + LLFloaterIMSessionTab::setVisible(visible); + + // update notification channel state + if(channel) + { + channel->updateShowToastsState(); + channel->redrawToasts(); + } + + if(!visible) + { + LLIMChiclet* chiclet = LLChicletBar::getInstance()->getChicletPanel()->findChiclet(mSessionID); + if(chiclet) + { + chiclet->setToggleState(false); + } + } + + if (visible && isInVisibleChain()) + { + sIMFloaterShowedSignal(mSessionID); + + } + + setFocus(visible); +} + +BOOL LLFloaterIMSession::getVisible() +{ + bool visible; + + if(isChatMultiTab()) + { + LLFloaterIMContainer* im_container = + LLFloaterIMContainer::getInstance(); + + // Treat inactive floater as invisible. + bool is_active = im_container->getActiveFloater() == this; + + //torn off floater is always inactive + if (!is_active && getHost() != im_container) + { + visible = LLTransientDockableFloater::getVisible(); + } + else + { + // getVisible() returns TRUE when Tabbed IM window is minimized. + visible = is_active && !im_container->isMinimized() + && im_container->getVisible(); + } + } + else + { + visible = LLTransientDockableFloater::getVisible(); + } + + return visible; +} + +//static +bool LLFloaterIMSession::toggle(const LLUUID& session_id) +{ + if(!isChatMultiTab()) + { + LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance( + "impanel", session_id); + if (floater && floater->getVisible() && floater->hasFocus()) + { + // clicking on chiclet to close floater just hides it to maintain existing + // scroll/text entry state + floater->setVisible(false); + return false; + } + else if(floater && (!floater->isDocked() || floater->getVisible() && !floater->hasFocus())) + { + floater->setVisible(TRUE); + floater->setFocus(TRUE); + return true; + } + } + + // ensure the list of messages is updated when floater is made visible + show(session_id); + return true; +} + +void LLFloaterIMSession::sessionInitReplyReceived(const LLUUID& im_session_id) +{ + mSessionInitialized = true; + + //will be different only for an ad-hoc im session + if (mSessionID != im_session_id) + { + initIMSession(im_session_id); + buildConversationViewParticipant(); + } + + initIMFloater(); + + //*TODO here we should remove "starting session..." warning message if we added it in postBuild() (IB) + + //need to send delayed messages collected while waiting for session initialization + if (mQueuedMsgsForInit.size()) + { + LLSD::array_iterator iter; + for ( iter = mQueuedMsgsForInit.beginArray(); + iter != mQueuedMsgsForInit.endArray(); ++iter) + { + LLIMModel::sendMessage(iter->asString(), mSessionID, + mOtherParticipantUUID, mDialog); + } + + mQueuedMsgsForInit.clear(); + } +} + +void LLFloaterIMSession::updateMessages() +{ + std::list messages; + + // we shouldn't reset unread message counters if IM floater doesn't have focus + LLIMModel::instance().getMessages( + mSessionID, messages, mLastMessageIndex + 1, hasFocus()); + + if (messages.size()) + { + std::ostringstream message; + std::list::const_reverse_iterator iter = messages.rbegin(); + std::list::const_reverse_iterator iter_end = messages.rend(); + for (; iter != iter_end; ++iter) + { + LLSD msg = *iter; + + std::string time = msg["time"].asString(); + LLUUID from_id = msg["from_id"].asUUID(); + std::string from = msg["from"].asString(); + std::string message = msg["message"].asString(); + bool is_history = msg["is_history"].asBoolean(); + + LLChat chat; + chat.mFromID = from_id; + chat.mSessionID = mSessionID; + chat.mFromName = from; + chat.mTimeStr = time; + chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; + + // process offer notification + if (msg.has("notification_id")) + { + chat.mNotifId = msg["notification_id"].asUUID(); + // if notification exists - embed it + if (LLNotificationsUtil::find(chat.mNotifId) != NULL) + { + // remove embedded notification from channel + LLNotificationsUI::LLScreenChannel* channel = static_cast + (LLNotificationsUI::LLChannelManager::getInstance()-> + findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); + if (getVisible()) + { + // toast will be automatically closed since it is not storable toast + channel->hideToast(chat.mNotifId); + } + } + // if notification doesn't exist - try to use next message which should be log entry + else + { + continue; + } + } + //process text message + else + { + chat.mText = message; + } + + // Add the message to the chat log + appendMessage(chat); + mLastMessageIndex = msg["index"].asInteger(); + + // if it is a notification - next message is a notification history log, so skip it + if (chat.mNotifId.notNull() && LLNotificationsUtil::find(chat.mNotifId) != NULL) + { + if (++iter == iter_end) + { + break; + } + else + { + mLastMessageIndex++; + } + } + } + } +} + +void LLFloaterIMSession::reloadMessages() +{ + mChatHistory->clear(); + mLastMessageIndex = -1; + updateMessages(); + mInputEditor->setFont(LLViewerChat::getChatFont()); +} + +// static +void LLFloaterIMSession::onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata ) +{ + LLFloaterIMSession* self= (LLFloaterIMSession*) userdata; + + // Allow enabling the LLFloaterIMSession input editor only if session can accept text + LLIMModel::LLIMSession* im_session = + LLIMModel::instance().findIMSession(self->mSessionID); + //TODO: While disabled lllineeditor can receive focus we need to check if it is enabled (EK) + if( im_session && im_session->mTextIMPossible && self->mInputEditor->getEnabled()) + { + //in disconnected state IM input editor should be disabled + self->mInputEditor->setEnabled(!gDisconnected); + } +} + +// static +void LLFloaterIMSession::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata) +{ + LLFloaterIMSession* self = (LLFloaterIMSession*) userdata; + self->setTyping(false); +} + +// static +void LLFloaterIMSession::onInputEditorKeystroke(LLTextEditor* caller, void* userdata) +{ + LLFloaterIMSession* self = (LLFloaterIMSession*)userdata; + std::string text = self->mInputEditor->getText(); + + // Deleting all text counts as stopping typing. + self->setTyping(!text.empty()); +} + +void LLFloaterIMSession::setTyping(bool typing) +{ + if ( typing ) + { + // Started or proceeded typing, reset the typing timeout timer + mTypingTimeoutTimer.reset(); + } + + if ( mMeTyping != typing ) + { + // Typing state is changed + mMeTyping = typing; + // So, should send current state + mShouldSendTypingState = true; + // In case typing is started, send state after some delay + mTypingTimer.reset(); + } + + // Don't want to send typing indicators to multiple people, potentially too + // much network traffic. Only send in person-to-person IMs. + if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL ) + { + // Still typing, send 'start typing' notification or + // send 'stop typing' notification immediately + if (!mMeTyping || mTypingTimer.getElapsedTimeF32() > 1.f) + { + LLIMModel::instance().sendTypingState(mSessionID, + mOtherParticipantUUID, mMeTyping); + mShouldSendTypingState = false; + } + } + + if (!mIsNearbyChat) + { + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + speaker_mgr->setSpeakerTyping(gAgent.getID(), FALSE); + } + } +} + +void LLFloaterIMSession::processIMTyping(const LLIMInfo* im_info, BOOL typing) +{ + if ( typing ) + { + // other user started typing + addTypingIndicator(im_info); + } + else + { + // other user stopped typing + removeTypingIndicator(im_info); + } +} + +void LLFloaterIMSession::processAgentListUpdates(const LLSD& body) +{ + uuid_vec_t joined_uuids; + + if (body.isMap() && body.has("agent_updates") && body["agent_updates"].isMap()) + { + LLSD::map_const_iterator update_it; + for(update_it = body["agent_updates"].beginMap(); + update_it != body["agent_updates"].endMap(); + ++update_it) + { + LLUUID agent_id(update_it->first); + LLSD agent_data = update_it->second; + + if (agent_data.isMap()) + { + // store the new participants in joined_uuids + if (agent_data.has("transition") && agent_data["transition"].asString() == "ENTER") + { + joined_uuids.push_back(agent_id); + } + + // process the moderator mutes + if (agent_id == gAgentID && agent_data.has("info") && agent_data["info"].has("mutes")) + { + BOOL moderator_muted_text = agent_data["info"]["mutes"]["text"].asBoolean(); + mInputEditor->setEnabled(!moderator_muted_text); + std::string label; + if (moderator_muted_text) + label = LLTrans::getString("IM_muted_text_label"); + else + label = LLTrans::getString("IM_to_label") + " " + LLIMModel::instance().getName(mSessionID); + mInputEditor->setLabel(label); + + if (moderator_muted_text) + LLNotificationsUtil::add("TextChatIsMutedByModerator"); + } + } + } + } + + // the vectors need to be sorted for computing the intersection and difference + std::sort(mInvitedParticipants.begin(), mInvitedParticipants.end()); + std::sort(joined_uuids.begin(), joined_uuids.end()); + + uuid_vec_t intersection; // uuids of invited residents who have joined the conversation + std::set_intersection(mInvitedParticipants.begin(), mInvitedParticipants.end(), + joined_uuids.begin(), joined_uuids.end(), + std::back_inserter(intersection)); + + if (intersection.size() > 0) + { + sendParticipantsAddedNotification(intersection); + } + + // Remove all joined participants from invited array. + // The difference between the two vectors (the elements in mInvitedParticipants which are not in joined_uuids) + // is placed at the beginning of mInvitedParticipants, then all other elements are erased. + mInvitedParticipants.erase(std::set_difference(mInvitedParticipants.begin(), mInvitedParticipants.end(), + joined_uuids.begin(), joined_uuids.end(), + mInvitedParticipants.begin()), + mInvitedParticipants.end()); +} + +void LLFloaterIMSession::processSessionUpdate(const LLSD& session_update) +{ + // *TODO : verify following code when moderated mode will be implemented + if ( false && session_update.has("moderated_mode") && + session_update["moderated_mode"].has("voice") ) + { + BOOL voice_moderated = session_update["moderated_mode"]["voice"]; + const std::string session_label = LLIMModel::instance().getName(mSessionID); + + if (voice_moderated) + { + setTitle(session_label + std::string(" ") + + LLTrans::getString("IM_moderated_chat_label")); + } + else + { + setTitle(session_label); + } + + // *TODO : uncomment this when/if LLPanelActiveSpeakers panel will be added + //update the speakers dropdown too + //mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated); + } +} + +// virtual +BOOL LLFloaterIMSession::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + if (cargo_type == DAD_PERSON) + { + if (dropPerson(static_cast(cargo_data), drop)) + { + *accept = ACCEPT_YES_MULTI; + } + else + { + *accept = ACCEPT_NO; + } + } + else if (mDialog == IM_NOTHING_SPECIAL) + { + LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionID, drop, + cargo_type, cargo_data, accept); + } + + return TRUE; +} + +bool LLFloaterIMSession::dropPerson(LLUUID* person_id, bool drop) +{ + bool res = person_id && person_id->notNull(); + if(res) + { + uuid_vec_t ids; + ids.push_back(*person_id); + + res = canAddSelectedToChat(ids); + if(res && drop) + { + addSessionParticipants(ids); + } + } + + return res; +} + +BOOL LLFloaterIMSession::isInviteAllowed() const +{ + return ( (IM_SESSION_CONFERENCE_START == mDialog) + || (IM_SESSION_INVITE == mDialog && !gAgent.isInGroup(mSessionID)) + || mIsP2PChat); +} + +class LLSessionInviteResponder : public LLHTTPClient::Responder +{ +public: + LLSessionInviteResponder(const LLUUID& session_id) + { + mSessionID = session_id; + } + + void error(U32 statusNum, const std::string& reason) + { + llinfos << "Error inviting all agents to session" << llendl; + //throw something back to the viewer here? + } + +private: + LLUUID mSessionID; +}; + +BOOL LLFloaterIMSession::inviteToSession(const uuid_vec_t& ids) +{ + LLViewerRegion* region = gAgent.getRegion(); + bool is_region_exist = region != NULL; + + if (is_region_exist) + { + S32 count = ids.size(); + + if( isInviteAllowed() && (count > 0) ) + { + llinfos << "LLFloaterIMSession::inviteToSession() - inviting participants" << llendl; + + std::string url = region->getCapability("ChatSessionRequest"); + + LLSD data; + data["params"] = LLSD::emptyArray(); + for (int i = 0; i < count; i++) + { + data["params"].append(ids[i]); + } + data["method"] = "invite"; + data["session-id"] = mSessionID; + LLHTTPClient::post(url, data,new LLSessionInviteResponder(mSessionID)); + } + else + { + llinfos << "LLFloaterIMSession::inviteToSession -" + << " no need to invite agents for " + << mDialog << llendl; + // successful add, because everyone that needed to get added + // was added. + } + } + + return is_region_exist; +} + +void LLFloaterIMSession::addTypingIndicator(const LLIMInfo* im_info) +{ + // We may have lost a "stop-typing" packet, don't add it twice + if ( im_info && !mOtherTyping ) + { + mOtherTyping = true; + + // Save and set new title + mSavedTitle = getTitle(); + setTitle (mTypingStart); + + // Update speaker + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if ( speaker_mgr ) + { + speaker_mgr->setSpeakerTyping(im_info->mFromID, TRUE); + } + } +} + +void LLFloaterIMSession::removeTypingIndicator(const LLIMInfo* im_info) +{ + if ( mOtherTyping ) + { + mOtherTyping = false; + + // Revert the title to saved one + setTitle(mSavedTitle); + + if ( im_info ) + { + // Update speaker + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if ( speaker_mgr ) + { + speaker_mgr->setSpeakerTyping(im_info->mFromID, FALSE); + } + } + } +} + +// static +void LLFloaterIMSession::closeHiddenIMToasts() +{ + class IMToastMatcher: public LLNotificationsUI::LLScreenChannel::Matcher + { + public: + bool matches(const LLNotificationPtr notification) const + { + // "notifytoast" type of notifications is reserved for IM notifications + return "notifytoast" == notification->getType(); + } + }; + + LLNotificationsUI::LLScreenChannel* channel = + LLNotificationsUI::LLChannelManager::getNotificationScreenChannel(); + if (channel != NULL) + { + channel->closeHiddenToasts(IMToastMatcher()); + } +} +// static +void LLFloaterIMSession::confirmLeaveCallCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + const LLSD& payload = notification["payload"]; + LLUUID session_id = payload["session_id"]; + + LLFloater* im_floater = findInstance(session_id); + if (option == 0 && im_floater != NULL) + { + im_floater->closeFloater(); + } + + return; +} + +// static +void LLFloaterIMSession::sRemoveTypingIndicator(const LLSD& data) +{ + LLUUID session_id = data["session_id"]; + if (session_id.isNull()) + return; + + LLUUID from_id = data["from_id"]; + if (gAgentID == from_id || LLUUID::null == from_id) + return; + + LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(session_id); + if (!floater) + return; + + if (IM_NOTHING_SPECIAL != floater->mDialog) + return; + + floater->removeTypingIndicator(); +} + +// static +void LLFloaterIMSession::onIMChicletCreated( const LLUUID& session_id ) +{ + LLFloaterIMSession::addToHost(session_id); +} + +boost::signals2::connection LLFloaterIMSession::setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb) +{ + return LLFloaterIMSession::sIMFloaterShowedSignal.connect(cb); +} diff --git a/indra/newview/llfloaterimsession.h b/indra/newview/llfloaterimsession.h new file mode 100644 index 0000000000..f4ec2d457d --- /dev/null +++ b/indra/newview/llfloaterimsession.h @@ -0,0 +1,196 @@ +/** + * @file llfloaterimsession.h + * @brief LLFloaterIMSession class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATERIMSESSION_H +#define LL_FLOATERIMSESSION_H + +#include "llimview.h" +#include "llfloaterimsessiontab.h" +#include "llinstantmessage.h" +#include "lllogchat.h" +#include "lltooldraganddrop.h" +#include "llvoicechannel.h" +#include "llvoiceclient.h" + +class LLAvatarName; +class LLButton; +class LLChatEntry; +class LLTextEditor; +class LLPanelChatControlPanel; +class LLChatHistory; +class LLInventoryItem; +class LLInventoryCategory; + +typedef boost::signals2::signal floater_showed_signal_t; + +/** + * Individual IM window that appears at the bottom of the screen, + * optionally "docked" to the bottom tray. + */ +class LLFloaterIMSession + : public LLVoiceClientStatusObserver + , public LLFloaterIMSessionTab +{ + LOG_CLASS(LLFloaterIMSession); +public: + LLFloaterIMSession(const LLUUID& session_id); + + virtual ~LLFloaterIMSession(); + + void initIMSession(const LLUUID& session_id); + void initIMFloater(); + + // LLView overrides + /*virtual*/ BOOL postBuild(); + /*virtual*/ void setVisible(BOOL visible); + /*virtual*/ BOOL getVisible(); + // Check typing timeout timer. + + static LLFloaterIMSession* findInstance(const LLUUID& session_id); + static LLFloaterIMSession* getInstance(const LLUUID& session_id); + + // LLFloater overrides + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); + // Make IM conversion visible and update the message history + static LLFloaterIMSession* show(const LLUUID& session_id); + + // Toggle panel specified by session_id + // Returns true iff panel became visible + static bool toggle(const LLUUID& session_id); + + void sessionInitReplyReceived(const LLUUID& im_session_id); + + // get new messages from LLIMModel + /*virtual*/ void updateMessages(); + void reloadMessages(); + static void onSendMsg(LLUICtrl*, void*); + void sendMsgFromInputEditor(); + void sendMsg(const std::string& msg); + + // callback for LLIMModel on new messages + // route to specific floater if it is visible + static void newIMCallback(const LLSD& data); + + // called when docked floater's position has been set by chiclet + void setPositioned(bool b) { mPositioned = b; }; + + void onVisibilityChange(const LLSD& new_visibility); + + // Implements LLVoiceClientStatusObserver::onChange() to enable the call + // button when voice is available + void onChange(EStatusType status, const std::string &channelURI, + bool proximal); + + virtual LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; } + virtual void onVoiceChannelStateChanged( + const LLVoiceChannel::EState& old_state, + const LLVoiceChannel::EState& new_state); + + void processIMTyping(const LLIMInfo* im_info, BOOL typing); + void processAgentListUpdates(const LLSD& body); + void processSessionUpdate(const LLSD& session_update); + + /*virtual*/ BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + + //used as a callback on receiving new IM message + static void sRemoveTypingIndicator(const LLSD& data); + static void onIMChicletCreated(const LLUUID& session_id); + + bool getStartConferenceInSameFloater() const { return mStartConferenceInSameFloater; } + const LLUUID& getOtherParticipantUUID() {return mOtherParticipantUUID;} + + static boost::signals2::connection setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb); + static floater_showed_signal_t sIMFloaterShowedSignal; + +private: + + /*virtual*/ void refresh(); + + /*virtual*/ void onClickCloseBtn(); + + // Update the window title and input field help text + /*virtual*/ void updateSessionName(const std::string& name); + + bool dropPerson(LLUUID* person_id, bool drop); + + BOOL isInviteAllowed() const; + BOOL inviteToSession(const uuid_vec_t& agent_ids); + static void onInputEditorFocusReceived( LLFocusableElement* caller,void* userdata ); + static void onInputEditorFocusLost(LLFocusableElement* caller, void* userdata); + static void onInputEditorKeystroke(LLTextEditor* caller, void* userdata); + void setTyping(bool typing); + void onAddButtonClicked(); + void addSessionParticipants(const uuid_vec_t& uuids); + void addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids); + void sendParticipantsAddedNotification(const uuid_vec_t& uuids); + bool canAddSelectedToChat(const uuid_vec_t& uuids); + + void onCallButtonClicked(); + + void boundVoiceChannel(); + + // Add the "User is typing..." indicator. + void addTypingIndicator(const LLIMInfo* im_info); + + // Remove the "User is typing..." indicator. + void removeTypingIndicator(const LLIMInfo* im_info = NULL); + + static void closeHiddenIMToasts(); + + static void confirmLeaveCallCallback(const LLSD& notification, const LLSD& response); + + S32 mLastMessageIndex; + + EInstantMessage mDialog; + LLUUID mOtherParticipantUUID; + bool mPositioned; + + std::string mSavedTitle; + LLUIString mTypingStart; + bool mMeTyping; + bool mOtherTyping; + bool mShouldSendTypingState; + LLFrameTimer mTypingTimer; + LLFrameTimer mTypingTimeoutTimer; + + bool mSessionInitialized; + LLSD mQueuedMsgsForInit; + + bool mStartConferenceInSameFloater; + + uuid_vec_t mInvitedParticipants; + + // connection to voice channel state change signal + boost::signals2::connection mVoiceChannelStateChangeConnection; +}; + +#endif // LL_FLOATERIMSESSION_H diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp new file mode 100644 index 0000000000..a47c9177a1 --- /dev/null +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -0,0 +1,743 @@ +/** + * @file llfloaterimsessiontab.cpp + * @brief LLFloaterIMSessionTab class implements the common behavior of LNearbyChatBar + * @brief and LLFloaterIMSession for hosting both in LLIMContainer + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimsessiontab.h" + +#include "llagent.h" +#include "llavataractions.h" +#include "llchatentry.h" +#include "llchathistory.h" +#include "llchiclet.h" +#include "llchicletbar.h" +#include "lldraghandle.h" +#include "llfloaterreg.h" +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container +#include "lllayoutstack.h" +#include "llfloaterimnearbychat.h" + +const F32 REFRESH_INTERVAL = 0.2f; + +LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) + : LLTransientDockableFloater(NULL, true, session_id) + , mIsP2PChat(false) + , mExpandCollapseBtn(NULL) + , mTearOffBtn(NULL) + , mCloseBtn(NULL) + , mSessionID(session_id.asUUID()) + , mConversationsRoot(NULL) + , mChatHistory(NULL) + , mInputEditor(NULL) + , mInputEditorTopPad(0) + , mRefreshTimer(new LLTimer()) + , mIsHostAttached(false) +{ + mSession = LLIMModel::getInstance()->findIMSession(mSessionID); + + mCommitCallbackRegistrar.add("IMSession.Menu.Action", + boost::bind(&LLFloaterIMSessionTab::onIMSessionMenuItemClicked, this, _2)); + mEnableCallbackRegistrar.add("IMSession.Menu.CompactExpandedModes.CheckItem", + boost::bind(&LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck, this, _2)); + mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.CheckItem", + boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemCheck, this, _2)); + mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.Enable", + boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemEnable, this, _2)); + + // Zero expiry time is set only once to allow initial update. + mRefreshTimer->setTimerExpirySec(0); + mRefreshTimer->start(); +} + +LLFloaterIMSessionTab::~LLFloaterIMSessionTab() +{ + delete mRefreshTimer; +} + +//static +LLFloaterIMSessionTab* LLFloaterIMSessionTab::findConversation(const LLUUID& uuid) +{ + LLFloaterIMSessionTab* conv; + + if (uuid.isNull()) + { + conv = LLFloaterReg::findTypedInstance("nearby_chat"); + } + else + { + conv = LLFloaterReg::findTypedInstance("impanel", LLSD(uuid)); + } + + return conv; +}; + +//static +LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid) +{ + LLFloaterIMSessionTab* conv; + + if (uuid.isNull()) + { + conv = LLFloaterReg::getTypedInstance("nearby_chat"); + } + else + { + conv = LLFloaterReg::getTypedInstance("impanel", LLSD(uuid)); + } + + return conv; +}; + +void LLFloaterIMSessionTab::setVisible(BOOL visible) +{ + LLTransientDockableFloater::setVisible(visible); + + if(visible) + { + LLFloaterIMSessionTab::addToHost(mSessionID); + } + setFocus(visible); +} + +/*virtual*/ +void LLFloaterIMSessionTab::setFocus(BOOL focus) +{ + LLTransientDockableFloater::setFocus(focus); + + //Redirect focus to input editor + if (focus) + { + updateMessages(); + + if (mInputEditor) + { + mInputEditor->setFocus(TRUE); + } + } +} + + +void LLFloaterIMSessionTab::addToHost(const LLUUID& session_id) +{ + if ((session_id.notNull() && !gIMMgr->hasSession(session_id)) + || !LLFloaterIMSessionTab::isChatMultiTab()) + { + return; + } + + // Get the floater: this will create the instance if it didn't exist + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(session_id); + if (conversp) + { + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + + // Do not add again existing floaters + if (floater_container && !conversp->isHostAttached()) + { + conversp->setHostAttached(true); + + if (!conversp->isNearbyChat() + || gSavedSettings.getBOOL("NearbyChatIsNotTornOff")) + { + floater_container->addFloater(conversp, TRUE, LLTabContainer::END); + } + else + { + // setting of the "potential" host for Nearby Chat: this sequence sets + // LLFloater::mHostHandle = NULL (a current host), but + // LLFloater::mLastHostHandle = floater_container (a "future" host) + conversp->setHost(floater_container); + conversp->setHost(NULL); + } + // Added floaters share some state (like sort order) with their host + conversp->setSortOrder(floater_container->getSortOrder()); + } + } +} + +BOOL LLFloaterIMSessionTab::postBuild() +{ + BOOL result; + + mCloseBtn = getChild("close_btn"); + mCloseBtn->setCommitCallback(boost::bind(&LLFloater::onClickClose, this)); + + mExpandCollapseBtn = getChild("expand_collapse_btn"); + mExpandCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMSessionTab::onSlide, this)); + + mTearOffBtn = getChild("tear_off_btn"); + mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this)); + + mParticipantListPanel = getChild("speakers_list_panel"); + + // Create a root view folder for all participants + LLConversationItem* base_item = new LLConversationItem(mSessionID, mConversationViewModel); + LLFolderView::Params p(LLUICtrlFactory::getDefaultParams()); + p.rect = LLRect(0, 0, getRect().getWidth(), 0); + p.parent_panel = mParticipantListPanel; + p.listener = base_item; + p.view_model = &mConversationViewModel; + p.root = NULL; + p.use_ellipses = true; + mConversationsRoot = LLUICtrlFactory::create(p); + mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); + + // Add a scroller for the folder (participant) view + LLRect scroller_view_rect = mParticipantListPanel->getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams()); + scroller_params.rect(scroller_view_rect); + LLScrollContainer* scroller = LLUICtrlFactory::create(scroller_params); + scroller->setFollowsAll(); + + // Insert that scroller into the panel widgets hierarchy and folder view + mParticipantListPanel->addChild(scroller); + scroller->addChild(mConversationsRoot); + mConversationsRoot->setScrollContainer(scroller); + mConversationsRoot->setFollowsAll(); + mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox); + + + mChatHistory = getChild("chat_history"); + + mInputEditor = getChild("chat_editor"); + mInputEditor->setTextExpandedCallback(boost::bind(&LLFloaterIMSessionTab::reshapeChatHistory, this)); + mInputEditor->setCommitOnFocusLost( FALSE ); + mInputEditor->setPassDelete(TRUE); + mInputEditor->setFont(LLViewerChat::getChatFont()); + + mInputEditorTopPad = mChatHistory->getRect().mBottom - mInputEditor->getRect().mTop; + + setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); + + buildConversationViewParticipant(); + + mSaveRect = isTornOff(); + initRectControl(); + + if (isChatMultiTab()) + { + result = LLFloater::postBuild(); + } + else + { + result = LLDockableFloater::postBuild(); + } + + refreshConversation(); + + return result; +} + +LLParticipantList* LLFloaterIMSessionTab::getParticipantList() +{ + return dynamic_cast(LLFloaterIMContainer::getInstance()->getSessionModel(mSessionID)); +} + +void LLFloaterIMSessionTab::draw() +{ + if (mRefreshTimer->hasExpired()) + { + if (getParticipantList()) + { + getParticipantList()->update(); + } + + refreshConversation(); + + // Restart the refresh timer + mRefreshTimer->setTimerExpirySec(REFRESH_INTERVAL); + } + + LLTransientDockableFloater::draw(); +} + +void LLFloaterIMSessionTab::enableDisableCallBtn() +{ + getChildView("voice_call_btn")->setEnabled( + mSessionID.notNull() + && mSession + && mSession->mSessionInitialized + && LLVoiceClient::getInstance()->voiceEnabled() + && LLVoiceClient::getInstance()->isVoiceWorking() + && mSession->mCallBackEnabled); +} + +void LLFloaterIMSessionTab::onFocusReceived() +{ + setBackgroundOpaque(true); + + if (mSessionID.notNull() && isInVisibleChain()) + { + LLIMModel::instance().sendNoUnreadMessages(mSessionID); + } + + LLTransientDockableFloater::onFocusReceived(); + + LLFloaterIMContainer* container = LLFloaterReg::getTypedInstance("im_container"); + if (container) + { + container->selectConversationPair(mSessionID, true); + } +} + +void LLFloaterIMSessionTab::onFocusLost() +{ + setBackgroundOpaque(false); + LLTransientDockableFloater::onFocusLost(); +} + +std::string LLFloaterIMSessionTab::appendTime() +{ + time_t utc_time; + utc_time = time_corrected(); + std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:[" + +LLTrans::getString("TimeMin")+"]"; + + LLSD substitution; + + substitution["datetime"] = (S32) utc_time; + LLStringUtil::format (timeStr, substitution); + + return timeStr; +} + +void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD &args) +{ + // Update the participant activity time + LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); + if (im_box) + { + im_box->setTimeNow(mSessionID,chat.mFromID); + } + + + LLChat& tmp_chat = const_cast(chat); + + if(tmp_chat.mTimeStr.empty()) + tmp_chat.mTimeStr = appendTime(); + + if (!chat.mMuted) + { + tmp_chat.mFromName = chat.mFromName; + LLSD chat_args; + if (args) chat_args = args; + chat_args["use_plain_text_chat_history"] = + gSavedSettings.getBOOL("PlainTextChatHistory"); + chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); + chat_args["show_names_for_p2p_conv"] = + !mIsP2PChat || gSavedSettings.getBOOL("IMShowNamesForP2PConv"); + + if (mChatHistory) + { + mChatHistory->appendMessage(chat, chat_args); + } + } +} + + +void LLFloaterIMSessionTab::buildConversationViewParticipant() +{ + // Clear the widget list since we are rebuilding afresh from the model + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + while (widget_it != mConversationsWidgets.end()) + { + removeConversationViewParticipant(widget_it->first); + // Iterators are invalidated by erase so we need to pick begin again + widget_it = mConversationsWidgets.begin(); + } + + // Get the model list + LLParticipantList* item = getParticipantList(); + if (!item) + { + // Nothing to do if the model list is empty + return; + } + + // Create the participants widgets now + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); + while (current_participant_model != end_participant_model) + { + LLConversationItem* participant_model = dynamic_cast(*current_participant_model); + addConversationViewParticipant(participant_model); + current_participant_model++; + } +} + +void LLFloaterIMSessionTab::addConversationViewParticipant(LLConversationItem* participant_model) +{ + // Check if the model already has an associated view + LLUUID uuid = participant_model->getUUID(); + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); + + // If not already present, create the participant view and attach it to the root, otherwise, just refresh it + if (widget) + { + updateConversationViewParticipant(uuid); // overkill? + } + else + { + LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); + mConversationsWidgets[uuid] = participant_view; + participant_view->addToFolder(mConversationsRoot); + participant_view->setVisible(TRUE); + refreshConversation(); + } +} + +void LLFloaterIMSessionTab::removeConversationViewParticipant(const LLUUID& participant_id) +{ + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); + if (widget) + { + mConversationsRoot->extractItem(widget); + delete widget; + mConversationsWidgets.erase(participant_id); + refreshConversation(); + } +} + +void LLFloaterIMSessionTab::updateConversationViewParticipant(const LLUUID& participant_id) +{ + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); + if (widget) + { + widget->refresh(); + } + refreshConversation(); +} + +void LLFloaterIMSessionTab::refreshConversation() +{ + // Note: We collect participants names to change the session name only in the case of ad-hoc conversations + bool is_ad_hoc = (mSession ? mSession->isAdHocSessionType() : false); + uuid_vec_t participants_uuids; // uuids vector for building the added participants name string + // For P2P chat, we still need to update the session name who may have changed (switch display name for instance) + if (mIsP2PChat && mSession) + { + participants_uuids.push_back(mSession->mOtherParticipantID); + } + + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + while (widget_it != mConversationsWidgets.end()) + { + // Add the participant to the list except if it's the agent itself (redundant) + if (is_ad_hoc && (widget_it->first != gAgentID)) + { + participants_uuids.push_back(widget_it->first); + } + widget_it->second->refresh(); + widget_it->second->setVisible(TRUE); + ++widget_it; + } + if (is_ad_hoc || mIsP2PChat) + { + // Build the session name and update it + std::string session_name; + if (participants_uuids.size() != 0) + { + LLAvatarActions::buildResidentsString(participants_uuids, session_name); + } + else + { + session_name = LLIMModel::instance().getName(mSessionID); + } + updateSessionName(session_name); + } + mConversationViewModel.requestSortAll(); + mConversationsRoot->arrangeAll(); + mConversationsRoot->update(); + updateHeaderAndToolbar(); + refresh(); +} + +// Copied from LLFloaterIMContainer::createConversationViewParticipant(). Refactor opportunity! +LLConversationViewParticipant* LLFloaterIMSessionTab::createConversationViewParticipant(LLConversationItem* item) +{ + LLRect panel_rect = mParticipantListPanel->getRect(); + + LLConversationViewParticipant::Params params; + params.name = item->getDisplayName(); + params.root = mConversationsRoot; + params.listener = item; + params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); // *TODO: use conversation_view_participant.xml itemHeight value in lieu of 24 + params.tool_tip = params.name; + params.participant_id = item->getUUID(); + + return LLUICtrlFactory::create(params); +} + +void LLFloaterIMSessionTab::setSortOrder(const LLConversationSort& order) +{ + mConversationViewModel.setSorter(order); + mConversationsRoot->arrangeAll(); + refreshConversation(); +} + +void LLFloaterIMSessionTab::onIMSessionMenuItemClicked(const LLSD& userdata) +{ + std::string item = userdata.asString(); + + if (item == "compact_view" || item == "expanded_view") + { + gSavedSettings.setBOOL("PlainTextChatHistory", item == "compact_view"); + } + else + { + bool prev_value = gSavedSettings.getBOOL(item); + gSavedSettings.setBOOL(item, !prev_value); + } + + LLFloaterIMSessionTab::processChatHistoryStyleUpdate(); +} + + +bool LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + bool is_plain_text_mode = gSavedSettings.getBOOL("PlainTextChatHistory"); + + return is_plain_text_mode? item == "compact_view" : item == "expanded_view"; +} + + +bool LLFloaterIMSessionTab::onIMShowModesMenuItemCheck(const LLSD& userdata) +{ + return gSavedSettings.getBOOL(userdata.asString()); +} + +// enable/disable states for the "show time" and "show names" items of the show-modes menu +bool LLFloaterIMSessionTab::onIMShowModesMenuItemEnable(const LLSD& userdata) +{ + std::string item = userdata.asString(); + bool plain_text = gSavedSettings.getBOOL("PlainTextChatHistory"); + bool is_not_names = (item != "IMShowNamesForP2PConv"); + return (plain_text && (is_not_names || mIsP2PChat)); +} + +void LLFloaterIMSessionTab::hideOrShowTitle() +{ + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + LLView* floater_contents = getChild("contents_view"); + + LLRect floater_rect = getLocalRect(); + S32 top_border_of_contents = floater_rect.mTop - (isTornOff()? floater_header_size : 0); + LLRect handle_rect (0, floater_rect.mTop, floater_rect.mRight, top_border_of_contents); + LLRect contents_rect (0, top_border_of_contents, floater_rect.mRight, floater_rect.mBottom); + mDragHandle->setShape(handle_rect); + mDragHandle->setVisible(isTornOff()); + floater_contents->setShape(contents_rect); +} + +void LLFloaterIMSessionTab::updateSessionName(const std::string& name) +{ + mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + name); +} + +void LLFloaterIMSessionTab::hideAllStandardButtons() +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + if (mButtons[i]) + { + // Hide the standard header buttons in a docked IM floater. + mButtons[i]->setVisible(false); + } + } +} + +void LLFloaterIMSessionTab::updateHeaderAndToolbar() +{ + // prevent start conversation before its container + LLFloaterIMContainer::getInstance(); + + bool is_torn_off = checkIfTornOff(); + if (!is_torn_off) + { + hideAllStandardButtons(); + } + + hideOrShowTitle(); + + // Participant list should be visible only in torn off floaters. + bool is_participant_list_visible = + is_torn_off + && gSavedSettings.getBOOL("IMShowControlPanel") + && !mIsP2PChat; + + mParticipantListPanel->setVisible(is_participant_list_visible); + + // Display collapse image (<<) if the floater is hosted + // or if it is torn off but has an open control panel. + bool is_expanded = !is_torn_off || is_participant_list_visible; + mExpandCollapseBtn->setImageOverlay(getString(is_expanded ? "collapse_icon" : "expand_icon")); + + // toggle floater's drag handle and title visibility + if (mDragHandle) + { + mDragHandle->setTitleVisible(is_torn_off); + } + + // The button (>>) should be disabled for torn off P2P conversations. + mExpandCollapseBtn->setEnabled(!is_torn_off || !mIsP2PChat); + + mTearOffBtn->setImageOverlay(getString(is_torn_off? "return_icon" : "tear_off_icon")); + mTearOffBtn->setToolTip(getString(!is_torn_off? "tooltip_to_separate_window" : "tooltip_to_main_window")); + + mCloseBtn->setVisible(!is_torn_off && !mIsNearbyChat); + + enableDisableCallBtn(); + + showTranslationCheckbox(); +} + +void LLFloaterIMSessionTab::reshapeChatHistory() +{ + LLRect chat_rect = mChatHistory->getRect(); + LLRect input_rect = mInputEditor->getRect(); + + int delta_height = chat_rect.mBottom - (input_rect.mTop + mInputEditorTopPad); + + chat_rect.setLeftTopAndSize(chat_rect.mLeft, chat_rect.mTop, chat_rect.getWidth(), chat_rect.getHeight() + delta_height); + mChatHistory->setShape(chat_rect); +} + +void LLFloaterIMSessionTab::showTranslationCheckbox(BOOL show) +{ + getChild("translate_chat_checkbox_lp")->setVisible(mIsNearbyChat? show : FALSE); +} + +// static +void LLFloaterIMSessionTab::processChatHistoryStyleUpdate() +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); + iter != inst_list.end(); ++iter) + { + LLFloaterIMSession* floater = dynamic_cast(*iter); + if (floater) + { + floater->reloadMessages(); + } + } + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if (nearby_chat) + { + nearby_chat->reloadMessages(); + } +} + +void LLFloaterIMSessionTab::updateCallBtnState(bool callIsActive) +{ + getChild("voice_call_btn")->setImageOverlay( + callIsActive? getString("call_btn_stop") : getString("call_btn_start")); + enableDisableCallBtn(); + +} + +void LLFloaterIMSessionTab::onSlide(LLFloaterIMSessionTab* self) +{ + LLFloaterIMContainer* host_floater = dynamic_cast(self->getHost()); + if (host_floater) + { + // Hide the messages pane if a floater is hosted in the Conversations + host_floater->collapseMessagesPane(true); + } + else ///< floater is torn off + { + if (!self->mIsP2PChat) + { + bool expand = !self->mParticipantListPanel->getVisible(); + + // Expand/collapse the IM control panel + self->mParticipantListPanel->setVisible(expand); + + gSavedSettings.setBOOL("IMShowControlPanel", expand); + + self->mExpandCollapseBtn->setImageOverlay(self->getString(expand ? "collapse_icon" : "expand_icon")); + } + } +} + +/*virtual*/ +void LLFloaterIMSessionTab::onOpen(const LLSD& key) +{ + if (!checkIfTornOff()) + { + LLFloaterIMContainer* host_floater = dynamic_cast(getHost()); + // Show the messages pane when opening a floater hosted in the Conversations + host_floater->collapseMessagesPane(false); + } +} + +// virtual +void LLFloaterIMSessionTab::onClose(bool app_quitting) +{ + // Always suppress the IM from the conversations list on close if present for any reason + if (LLFloaterIMSessionTab::isChatMultiTab()) + { + LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); + if (im_box) + { + im_box->removeConversationListItem(mKey); + } + } +} + +void LLFloaterIMSessionTab::onTearOffClicked() +{ + setFollows(isTornOff()? FOLLOWS_ALL : FOLLOWS_NONE); + mSaveRect = isTornOff(); + initRectControl(); + LLFloater::onClickTearOff(this); + refreshConversation(); +} + +// static +bool LLFloaterIMSessionTab::isChatMultiTab() +{ + // Restart is required in order to change chat window type. + return true; +} + +bool LLFloaterIMSessionTab::checkIfTornOff() +{ + bool isTorn = !getHost(); + + if (isTorn != isTornOff()) + { + setTornOff(isTorn); + refreshConversation(); + } + + return isTorn; +} diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h new file mode 100644 index 0000000000..94854ee9ee --- /dev/null +++ b/indra/newview/llfloaterimsessiontab.h @@ -0,0 +1,176 @@ +/** + * @file llfloaterimsessiontab.h + * @brief LLFloaterIMSessionTab class implements the common behavior of LNearbyChatBar + * @brief and LLFloaterIMSession for hosting both in LLIMContainer + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATERIMSESSIONTAB_H +#define LL_FLOATERIMSESSIONTAB_H + +#include "lllayoutstack.h" +#include "llparticipantlist.h" +#include "lltransientdockablefloater.h" +#include "llviewercontrol.h" +#include "lleventtimer.h" +#include "llimview.h" +#include "llconversationmodel.h" +#include "llconversationview.h" +#include "lltexteditor.h" + +class LLPanelChatControlPanel; +class LLChatEntry; +class LLChatHistory; + +class LLFloaterIMSessionTab + : public LLTransientDockableFloater +{ + +public: + LOG_CLASS(LLFloaterIMSessionTab); + + LLFloaterIMSessionTab(const LLSD& session_id); + ~LLFloaterIMSessionTab(); + + // reload all message with new settings of visual modes + static void processChatHistoryStyleUpdate(); + + /** + * Returns true if chat is displayed in multi tabbed floater + * false if chat is displayed in multiple windows + */ + static bool isChatMultiTab(); + + // add conversation to container + static void addToHost(const LLUUID& session_id); + + bool isHostAttached() {return mIsHostAttached;} + void setHostAttached(bool is_attached) {mIsHostAttached = is_attached;} + + static LLFloaterIMSessionTab* findConversation(const LLUUID& uuid); + static LLFloaterIMSessionTab* getConversation(const LLUUID& uuid); + + // show/hide the translation check box + void showTranslationCheckbox(const BOOL visible = FALSE); + + bool isNearbyChat() {return mIsNearbyChat;} + + // LLFloater overrides + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ BOOL postBuild(); + /*virtual*/ void draw(); + /*virtual*/ void setVisible(BOOL visible); + /*virtual*/ void setFocus(BOOL focus); + + // Handle the left hand participant list widgets + void addConversationViewParticipant(LLConversationItem* item); + void removeConversationViewParticipant(const LLUUID& participant_id); + void updateConversationViewParticipant(const LLUUID& participant_id); + void refreshConversation(); + void buildConversationViewParticipant(); + + void setSortOrder(const LLConversationSort& order); + + virtual void updateMessages() {} + +protected: + + // callback for click on any items of the visual states menu + void onIMSessionMenuItemClicked(const LLSD& userdata); + + // callback for check/uncheck of the expanded/collapse mode's switcher + bool onIMCompactExpandedMenuItemCheck(const LLSD& userdata); + + // + bool onIMShowModesMenuItemCheck(const LLSD& userdata); + bool onIMShowModesMenuItemEnable(const LLSD& userdata); + static void onSlide(LLFloaterIMSessionTab *self); + virtual void onTearOffClicked(); + + // refresh a visual state of the Call button + void updateCallBtnState(bool callIsActive); + + void hideOrShowTitle(); // toggle the floater's drag handle + void hideAllStandardButtons(); + + /// Update floater header and toolbar buttons when hosted/torn off state is toggled. + void updateHeaderAndToolbar(); + + // Update the input field help text and other places that need the session name + virtual void updateSessionName(const std::string& name); + + // set the enable/disable state for the Call button + virtual void enableDisableCallBtn(); + + // process focus events to set a currently active session + /* virtual */ void onFocusLost(); + /* virtual */ void onFocusReceived(); + + // prepare chat's params and out one message to chatHistory + void appendMessage(const LLChat& chat, const LLSD &args = 0); + + std::string appendTime(); + + bool mIsNearbyChat; + bool mIsP2PChat; + + LLIMModel::LLIMSession* mSession; + + // Participants list: model and view + LLConversationViewParticipant* createConversationViewParticipant(LLConversationItem* item); + + LLUUID mSessionID; + LLLayoutPanel* mParticipantListPanel; // add the widgets to that see mConversationsListPanel + LLParticipantList* getParticipantList(); + conversations_widgets_map mConversationsWidgets; + LLConversationViewModel mConversationViewModel; + LLFolderView* mConversationsRoot; + + LLChatHistory* mChatHistory; + LLChatEntry* mInputEditor; + int mInputEditorTopPad; // padding between input field and chat history + + LLButton* mExpandCollapseBtn; + LLButton* mTearOffBtn; + LLButton* mCloseBtn; + +private: + /// Refreshes the floater at a constant rate. + virtual void refresh() = 0; + + /** + * Adjusts chat history height to fit vertically with input chat field + * and avoid overlapping, since input chat field can be vertically expanded. + * Implementation: chat history bottom "follows" top+top_pad of input chat field + */ + void reshapeChatHistory(); + + bool checkIfTornOff(); + bool mIsHostAttached; + + LLTimer* mRefreshTimer; ///< Defines the rate at which refresh() is called. +}; + + +#endif /* LL_FLOATERIMSESSIONTAB_H */ diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index b60af1a635..7c5e0776a7 100755 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -51,11 +51,11 @@ #include "llfloaterabout.h" #include "llfloaterhardwaresettings.h" #include "llfloatersidepanelcontainer.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" #include "llkeyboard.h" #include "llmodaldialog.h" #include "llnavigationbar.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llnotifications.h" #include "llnotificationsutil.h" #include "llnotificationtemplate.h" @@ -425,7 +425,7 @@ void LLFloaterPreference::saveAvatarProperties( void ) BOOL LLFloaterPreference::postBuild() { - gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLIMConversation::processChatHistoryStyleUpdate)); + gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate)); gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLViewerChat::signalChatFontChanged)); diff --git a/indra/newview/llfloatertranslationsettings.cpp b/indra/newview/llfloatertranslationsettings.cpp index 29d7732a68..6a9236ce0c 100644 --- a/indra/newview/llfloatertranslationsettings.cpp +++ b/indra/newview/llfloatertranslationsettings.cpp @@ -29,7 +29,7 @@ #include "llfloatertranslationsettings.h" // Viewer includes -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "lltranslate.h" #include "llviewercontrol.h" // for gSavedSettings @@ -293,7 +293,7 @@ void LLFloaterTranslationSettings::onBtnOK() gSavedSettings.setString("TranslationService", getSelectedService()); gSavedSettings.setString("BingTranslateAPIKey", getEnteredBingKey()); gSavedSettings.setString("GoogleTranslateAPIKey", getEnteredGoogleKey()); - (LLFloaterReg::getTypedInstance("nearby_chat"))-> + (LLFloaterReg::getTypedInstance("nearby_chat"))-> showTranslationCheckbox(LLTranslate::isTranslationConfigured()); closeFloater(false); } diff --git a/indra/newview/llgesturemgr.cpp b/indra/newview/llgesturemgr.cpp index 0996af6125..f307505ff8 100644 --- a/indra/newview/llgesturemgr.cpp +++ b/indra/newview/llgesturemgr.cpp @@ -52,7 +52,7 @@ #include "llviewermessage.h" #include "llvoavatarself.h" #include "llviewerstats.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llappearancemgr.h" #include "llgesturelistener.h" @@ -998,7 +998,7 @@ void LLGestureMgr::runStep(LLMultiGesture* gesture, LLGestureStep* step) const BOOL animate = FALSE; - (LLFloaterReg::getTypedInstance("nearby_chat"))-> + (LLFloaterReg::getTypedInstance("nearby_chat"))-> sendChatFromViewer(chat_text, CHAT_TYPE_NORMAL, animate); gesture->mCurrentStep++; diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 15eca39bce..a0f2918bd7 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -36,7 +36,7 @@ #include "llfloaterreg.h" #include "llfloatersidepanelcontainer.h" #include "llgroupmgr.h" -#include "llimfloatercontainer.h" +#include "llfloaterimcontainer.h" #include "llimview.h" // for gIMMgr #include "llnotificationsutil.h" #include "llstatusbar.h" // can_afford_transaction() @@ -335,7 +335,7 @@ LLUUID LLGroupActions::startIM(const LLUUID& group_id) group_id); if (session_id != LLUUID::null) { - LLIMFloaterContainer::getInstance()->showConversation(session_id); + LLFloaterIMContainer::getInstance()->showConversation(session_id); } make_ui_sound("UISndStartIM"); return session_id; diff --git a/indra/newview/llgroupiconctrl.cpp b/indra/newview/llgroupiconctrl.cpp index 2f9810775b..188c4bcf25 100644 --- a/indra/newview/llgroupiconctrl.cpp +++ b/indra/newview/llgroupiconctrl.cpp @@ -38,7 +38,7 @@ #include "llcachename.h" #include "llagentdata.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" */ static LLDefaultChildRegistry::Register g_i("group_icon"); diff --git a/indra/newview/llimconversation.cpp b/indra/newview/llimconversation.cpp deleted file mode 100644 index a321b3545a..0000000000 --- a/indra/newview/llimconversation.cpp +++ /dev/null @@ -1,743 +0,0 @@ -/** - * @file llimconversation.cpp - * @brief LLIMConversation class implements the common behavior of LNearbyChatBar - * @brief and LLIMFloater for hosting both in LLIMContainer - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llimconversation.h" - -#include "llagent.h" -#include "llavataractions.h" -#include "llchatentry.h" -#include "llchathistory.h" -#include "llchiclet.h" -#include "llchicletbar.h" -#include "lldraghandle.h" -#include "llfloaterreg.h" -#include "llimfloater.h" -#include "llimfloatercontainer.h" // to replace separate IM Floaters with multifloater container -#include "lllayoutstack.h" -#include "llnearbychat.h" - -const F32 REFRESH_INTERVAL = 0.2f; - -LLIMConversation::LLIMConversation(const LLSD& session_id) - : LLTransientDockableFloater(NULL, true, session_id) - , mIsP2PChat(false) - , mExpandCollapseBtn(NULL) - , mTearOffBtn(NULL) - , mCloseBtn(NULL) - , mSessionID(session_id.asUUID()) - , mConversationsRoot(NULL) - , mChatHistory(NULL) - , mInputEditor(NULL) - , mInputEditorTopPad(0) - , mRefreshTimer(new LLTimer()) - , mIsHostAttached(false) -{ - mSession = LLIMModel::getInstance()->findIMSession(mSessionID); - - mCommitCallbackRegistrar.add("IMSession.Menu.Action", - boost::bind(&LLIMConversation::onIMSessionMenuItemClicked, this, _2)); - mEnableCallbackRegistrar.add("IMSession.Menu.CompactExpandedModes.CheckItem", - boost::bind(&LLIMConversation::onIMCompactExpandedMenuItemCheck, this, _2)); - mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.CheckItem", - boost::bind(&LLIMConversation::onIMShowModesMenuItemCheck, this, _2)); - mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.Enable", - boost::bind(&LLIMConversation::onIMShowModesMenuItemEnable, this, _2)); - - // Zero expiry time is set only once to allow initial update. - mRefreshTimer->setTimerExpirySec(0); - mRefreshTimer->start(); -} - -LLIMConversation::~LLIMConversation() -{ - delete mRefreshTimer; -} - -//static -LLIMConversation* LLIMConversation::findConversation(const LLUUID& uuid) -{ - LLIMConversation* conv; - - if (uuid.isNull()) - { - conv = LLFloaterReg::findTypedInstance("nearby_chat"); - } - else - { - conv = LLFloaterReg::findTypedInstance("impanel", LLSD(uuid)); - } - - return conv; -}; - -//static -LLIMConversation* LLIMConversation::getConversation(const LLUUID& uuid) -{ - LLIMConversation* conv; - - if (uuid.isNull()) - { - conv = LLFloaterReg::getTypedInstance("nearby_chat"); - } - else - { - conv = LLFloaterReg::getTypedInstance("impanel", LLSD(uuid)); - } - - return conv; -}; - -void LLIMConversation::setVisible(BOOL visible) -{ - LLTransientDockableFloater::setVisible(visible); - - if(visible) - { - LLIMConversation::addToHost(mSessionID); - } - setFocus(visible); -} - -/*virtual*/ -void LLIMConversation::setFocus(BOOL focus) -{ - LLTransientDockableFloater::setFocus(focus); - - //Redirect focus to input editor - if (focus) - { - updateMessages(); - - if (mInputEditor) - { - mInputEditor->setFocus(TRUE); - } - } -} - - -void LLIMConversation::addToHost(const LLUUID& session_id) -{ - if ((session_id.notNull() && !gIMMgr->hasSession(session_id)) - || !LLIMConversation::isChatMultiTab()) - { - return; - } - - // Get the floater: this will create the instance if it didn't exist - LLIMConversation* conversp = LLIMConversation::getConversation(session_id); - if (conversp) - { - LLIMFloaterContainer* floater_container = LLIMFloaterContainer::getInstance(); - - // Do not add again existing floaters - if (floater_container && !conversp->isHostAttached()) - { - conversp->setHostAttached(true); - - if (!conversp->isNearbyChat() - || gSavedSettings.getBOOL("NearbyChatIsNotTornOff")) - { - floater_container->addFloater(conversp, TRUE, LLTabContainer::END); - } - else - { - // setting of the "potential" host for Nearby Chat: this sequence sets - // LLFloater::mHostHandle = NULL (a current host), but - // LLFloater::mLastHostHandle = floater_container (a "future" host) - conversp->setHost(floater_container); - conversp->setHost(NULL); - } - // Added floaters share some state (like sort order) with their host - conversp->setSortOrder(floater_container->getSortOrder()); - } - } -} - -BOOL LLIMConversation::postBuild() -{ - BOOL result; - - mCloseBtn = getChild("close_btn"); - mCloseBtn->setCommitCallback(boost::bind(&LLFloater::onClickClose, this)); - - mExpandCollapseBtn = getChild("expand_collapse_btn"); - mExpandCollapseBtn->setClickedCallback(boost::bind(&LLIMConversation::onSlide, this)); - - mTearOffBtn = getChild("tear_off_btn"); - mTearOffBtn->setCommitCallback(boost::bind(&LLIMConversation::onTearOffClicked, this)); - - mParticipantListPanel = getChild("speakers_list_panel"); - - // Create a root view folder for all participants - LLConversationItem* base_item = new LLConversationItem(mSessionID, mConversationViewModel); - LLFolderView::Params p(LLUICtrlFactory::getDefaultParams()); - p.rect = LLRect(0, 0, getRect().getWidth(), 0); - p.parent_panel = mParticipantListPanel; - p.listener = base_item; - p.view_model = &mConversationViewModel; - p.root = NULL; - p.use_ellipses = true; - mConversationsRoot = LLUICtrlFactory::create(p); - mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); - - // Add a scroller for the folder (participant) view - LLRect scroller_view_rect = mParticipantListPanel->getRect(); - scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); - LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams()); - scroller_params.rect(scroller_view_rect); - LLScrollContainer* scroller = LLUICtrlFactory::create(scroller_params); - scroller->setFollowsAll(); - - // Insert that scroller into the panel widgets hierarchy and folder view - mParticipantListPanel->addChild(scroller); - scroller->addChild(mConversationsRoot); - mConversationsRoot->setScrollContainer(scroller); - mConversationsRoot->setFollowsAll(); - mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox); - - - mChatHistory = getChild("chat_history"); - - mInputEditor = getChild("chat_editor"); - mInputEditor->setTextExpandedCallback(boost::bind(&LLIMConversation::reshapeChatHistory, this)); - mInputEditor->setCommitOnFocusLost( FALSE ); - mInputEditor->setPassDelete(TRUE); - mInputEditor->setFont(LLViewerChat::getChatFont()); - - mInputEditorTopPad = mChatHistory->getRect().mBottom - mInputEditor->getRect().mTop; - - setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); - - buildConversationViewParticipant(); - - mSaveRect = isTornOff(); - initRectControl(); - - if (isChatMultiTab()) - { - result = LLFloater::postBuild(); - } - else - { - result = LLDockableFloater::postBuild(); - } - - refreshConversation(); - - return result; -} - -LLParticipantList* LLIMConversation::getParticipantList() -{ - return dynamic_cast(LLIMFloaterContainer::getInstance()->getSessionModel(mSessionID)); -} - -void LLIMConversation::draw() -{ - if (mRefreshTimer->hasExpired()) - { - if (getParticipantList()) - { - getParticipantList()->update(); - } - - refreshConversation(); - - // Restart the refresh timer - mRefreshTimer->setTimerExpirySec(REFRESH_INTERVAL); - } - - LLTransientDockableFloater::draw(); -} - -void LLIMConversation::enableDisableCallBtn() -{ - getChildView("voice_call_btn")->setEnabled( - mSessionID.notNull() - && mSession - && mSession->mSessionInitialized - && LLVoiceClient::getInstance()->voiceEnabled() - && LLVoiceClient::getInstance()->isVoiceWorking() - && mSession->mCallBackEnabled); -} - -void LLIMConversation::onFocusReceived() -{ - setBackgroundOpaque(true); - - if (mSessionID.notNull() && isInVisibleChain()) - { - LLIMModel::instance().sendNoUnreadMessages(mSessionID); - } - - LLTransientDockableFloater::onFocusReceived(); - - LLIMFloaterContainer* container = LLFloaterReg::getTypedInstance("im_container"); - if (container) - { - container->selectConversationPair(mSessionID, true); - } -} - -void LLIMConversation::onFocusLost() -{ - setBackgroundOpaque(false); - LLTransientDockableFloater::onFocusLost(); -} - -std::string LLIMConversation::appendTime() -{ - time_t utc_time; - utc_time = time_corrected(); - std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:[" - +LLTrans::getString("TimeMin")+"]"; - - LLSD substitution; - - substitution["datetime"] = (S32) utc_time; - LLStringUtil::format (timeStr, substitution); - - return timeStr; -} - -void LLIMConversation::appendMessage(const LLChat& chat, const LLSD &args) -{ - // Update the participant activity time - LLIMFloaterContainer* im_box = LLIMFloaterContainer::findInstance(); - if (im_box) - { - im_box->setTimeNow(mSessionID,chat.mFromID); - } - - - LLChat& tmp_chat = const_cast(chat); - - if(tmp_chat.mTimeStr.empty()) - tmp_chat.mTimeStr = appendTime(); - - if (!chat.mMuted) - { - tmp_chat.mFromName = chat.mFromName; - LLSD chat_args; - if (args) chat_args = args; - chat_args["use_plain_text_chat_history"] = - gSavedSettings.getBOOL("PlainTextChatHistory"); - chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); - chat_args["show_names_for_p2p_conv"] = - !mIsP2PChat || gSavedSettings.getBOOL("IMShowNamesForP2PConv"); - - if (mChatHistory) - { - mChatHistory->appendMessage(chat, chat_args); - } - } -} - - -void LLIMConversation::buildConversationViewParticipant() -{ - // Clear the widget list since we are rebuilding afresh from the model - conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); - while (widget_it != mConversationsWidgets.end()) - { - removeConversationViewParticipant(widget_it->first); - // Iterators are invalidated by erase so we need to pick begin again - widget_it = mConversationsWidgets.begin(); - } - - // Get the model list - LLParticipantList* item = getParticipantList(); - if (!item) - { - // Nothing to do if the model list is empty - return; - } - - // Create the participants widgets now - LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); - LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); - while (current_participant_model != end_participant_model) - { - LLConversationItem* participant_model = dynamic_cast(*current_participant_model); - addConversationViewParticipant(participant_model); - current_participant_model++; - } -} - -void LLIMConversation::addConversationViewParticipant(LLConversationItem* participant_model) -{ - // Check if the model already has an associated view - LLUUID uuid = participant_model->getUUID(); - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); - - // If not already present, create the participant view and attach it to the root, otherwise, just refresh it - if (widget) - { - updateConversationViewParticipant(uuid); // overkill? - } - else - { - LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); - mConversationsWidgets[uuid] = participant_view; - participant_view->addToFolder(mConversationsRoot); - participant_view->setVisible(TRUE); - refreshConversation(); - } -} - -void LLIMConversation::removeConversationViewParticipant(const LLUUID& participant_id) -{ - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); - if (widget) - { - mConversationsRoot->extractItem(widget); - delete widget; - mConversationsWidgets.erase(participant_id); - refreshConversation(); - } -} - -void LLIMConversation::updateConversationViewParticipant(const LLUUID& participant_id) -{ - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); - if (widget) - { - widget->refresh(); - } - refreshConversation(); -} - -void LLIMConversation::refreshConversation() -{ - // Note: We collect participants names to change the session name only in the case of ad-hoc conversations - bool is_ad_hoc = (mSession ? mSession->isAdHocSessionType() : false); - uuid_vec_t participants_uuids; // uuids vector for building the added participants name string - // For P2P chat, we still need to update the session name who may have changed (switch display name for instance) - if (mIsP2PChat && mSession) - { - participants_uuids.push_back(mSession->mOtherParticipantID); - } - - conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); - while (widget_it != mConversationsWidgets.end()) - { - // Add the participant to the list except if it's the agent itself (redundant) - if (is_ad_hoc && (widget_it->first != gAgentID)) - { - participants_uuids.push_back(widget_it->first); - } - widget_it->second->refresh(); - widget_it->second->setVisible(TRUE); - ++widget_it; - } - if (is_ad_hoc || mIsP2PChat) - { - // Build the session name and update it - std::string session_name; - if (participants_uuids.size() != 0) - { - LLAvatarActions::buildResidentsString(participants_uuids, session_name); - } - else - { - session_name = LLIMModel::instance().getName(mSessionID); - } - updateSessionName(session_name); - } - mConversationViewModel.requestSortAll(); - mConversationsRoot->arrangeAll(); - mConversationsRoot->update(); - updateHeaderAndToolbar(); - refresh(); -} - -// Copied from LLIMFloaterContainer::createConversationViewParticipant(). Refactor opportunity! -LLConversationViewParticipant* LLIMConversation::createConversationViewParticipant(LLConversationItem* item) -{ - LLRect panel_rect = mParticipantListPanel->getRect(); - - LLConversationViewParticipant::Params params; - params.name = item->getDisplayName(); - params.root = mConversationsRoot; - params.listener = item; - params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); // *TODO: use conversation_view_participant.xml itemHeight value in lieu of 24 - params.tool_tip = params.name; - params.participant_id = item->getUUID(); - - return LLUICtrlFactory::create(params); -} - -void LLIMConversation::setSortOrder(const LLConversationSort& order) -{ - mConversationViewModel.setSorter(order); - mConversationsRoot->arrangeAll(); - refreshConversation(); -} - -void LLIMConversation::onIMSessionMenuItemClicked(const LLSD& userdata) -{ - std::string item = userdata.asString(); - - if (item == "compact_view" || item == "expanded_view") - { - gSavedSettings.setBOOL("PlainTextChatHistory", item == "compact_view"); - } - else - { - bool prev_value = gSavedSettings.getBOOL(item); - gSavedSettings.setBOOL(item, !prev_value); - } - - LLIMConversation::processChatHistoryStyleUpdate(); -} - - -bool LLIMConversation::onIMCompactExpandedMenuItemCheck(const LLSD& userdata) -{ - std::string item = userdata.asString(); - bool is_plain_text_mode = gSavedSettings.getBOOL("PlainTextChatHistory"); - - return is_plain_text_mode? item == "compact_view" : item == "expanded_view"; -} - - -bool LLIMConversation::onIMShowModesMenuItemCheck(const LLSD& userdata) -{ - return gSavedSettings.getBOOL(userdata.asString()); -} - -// enable/disable states for the "show time" and "show names" items of the show-modes menu -bool LLIMConversation::onIMShowModesMenuItemEnable(const LLSD& userdata) -{ - std::string item = userdata.asString(); - bool plain_text = gSavedSettings.getBOOL("PlainTextChatHistory"); - bool is_not_names = (item != "IMShowNamesForP2PConv"); - return (plain_text && (is_not_names || mIsP2PChat)); -} - -void LLIMConversation::hideOrShowTitle() -{ - const LLFloater::Params& default_params = LLFloater::getDefaultParams(); - S32 floater_header_size = default_params.header_height; - LLView* floater_contents = getChild("contents_view"); - - LLRect floater_rect = getLocalRect(); - S32 top_border_of_contents = floater_rect.mTop - (isTornOff()? floater_header_size : 0); - LLRect handle_rect (0, floater_rect.mTop, floater_rect.mRight, top_border_of_contents); - LLRect contents_rect (0, top_border_of_contents, floater_rect.mRight, floater_rect.mBottom); - mDragHandle->setShape(handle_rect); - mDragHandle->setVisible(isTornOff()); - floater_contents->setShape(contents_rect); -} - -void LLIMConversation::updateSessionName(const std::string& name) -{ - mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + name); -} - -void LLIMConversation::hideAllStandardButtons() -{ - for (S32 i = 0; i < BUTTON_COUNT; i++) - { - if (mButtons[i]) - { - // Hide the standard header buttons in a docked IM floater. - mButtons[i]->setVisible(false); - } - } -} - -void LLIMConversation::updateHeaderAndToolbar() -{ - // prevent start conversation before its container - LLIMFloaterContainer::getInstance(); - - bool is_torn_off = checkIfTornOff(); - if (!is_torn_off) - { - hideAllStandardButtons(); - } - - hideOrShowTitle(); - - // Participant list should be visible only in torn off floaters. - bool is_participant_list_visible = - is_torn_off - && gSavedSettings.getBOOL("IMShowControlPanel") - && !mIsP2PChat; - - mParticipantListPanel->setVisible(is_participant_list_visible); - - // Display collapse image (<<) if the floater is hosted - // or if it is torn off but has an open control panel. - bool is_expanded = !is_torn_off || is_participant_list_visible; - mExpandCollapseBtn->setImageOverlay(getString(is_expanded ? "collapse_icon" : "expand_icon")); - - // toggle floater's drag handle and title visibility - if (mDragHandle) - { - mDragHandle->setTitleVisible(is_torn_off); - } - - // The button (>>) should be disabled for torn off P2P conversations. - mExpandCollapseBtn->setEnabled(!is_torn_off || !mIsP2PChat); - - mTearOffBtn->setImageOverlay(getString(is_torn_off? "return_icon" : "tear_off_icon")); - mTearOffBtn->setToolTip(getString(!is_torn_off? "tooltip_to_separate_window" : "tooltip_to_main_window")); - - mCloseBtn->setVisible(!is_torn_off && !mIsNearbyChat); - - enableDisableCallBtn(); - - showTranslationCheckbox(); -} - -void LLIMConversation::reshapeChatHistory() -{ - LLRect chat_rect = mChatHistory->getRect(); - LLRect input_rect = mInputEditor->getRect(); - - int delta_height = chat_rect.mBottom - (input_rect.mTop + mInputEditorTopPad); - - chat_rect.setLeftTopAndSize(chat_rect.mLeft, chat_rect.mTop, chat_rect.getWidth(), chat_rect.getHeight() + delta_height); - mChatHistory->setShape(chat_rect); -} - -void LLIMConversation::showTranslationCheckbox(BOOL show) -{ - getChild("translate_chat_checkbox_lp")->setVisible(mIsNearbyChat? show : FALSE); -} - -// static -void LLIMConversation::processChatHistoryStyleUpdate() -{ - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); - iter != inst_list.end(); ++iter) - { - LLIMFloater* floater = dynamic_cast(*iter); - if (floater) - { - floater->reloadMessages(); - } - } - - LLNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if (nearby_chat) - { - nearby_chat->reloadMessages(); - } -} - -void LLIMConversation::updateCallBtnState(bool callIsActive) -{ - getChild("voice_call_btn")->setImageOverlay( - callIsActive? getString("call_btn_stop") : getString("call_btn_start")); - enableDisableCallBtn(); - -} - -void LLIMConversation::onSlide(LLIMConversation* self) -{ - LLIMFloaterContainer* host_floater = dynamic_cast(self->getHost()); - if (host_floater) - { - // Hide the messages pane if a floater is hosted in the Conversations - host_floater->collapseMessagesPane(true); - } - else ///< floater is torn off - { - if (!self->mIsP2PChat) - { - bool expand = !self->mParticipantListPanel->getVisible(); - - // Expand/collapse the IM control panel - self->mParticipantListPanel->setVisible(expand); - - gSavedSettings.setBOOL("IMShowControlPanel", expand); - - self->mExpandCollapseBtn->setImageOverlay(self->getString(expand ? "collapse_icon" : "expand_icon")); - } - } -} - -/*virtual*/ -void LLIMConversation::onOpen(const LLSD& key) -{ - if (!checkIfTornOff()) - { - LLIMFloaterContainer* host_floater = dynamic_cast(getHost()); - // Show the messages pane when opening a floater hosted in the Conversations - host_floater->collapseMessagesPane(false); - } -} - -// virtual -void LLIMConversation::onClose(bool app_quitting) -{ - // Always suppress the IM from the conversations list on close if present for any reason - if (LLIMConversation::isChatMultiTab()) - { - LLIMFloaterContainer* im_box = LLIMFloaterContainer::findInstance(); - if (im_box) - { - im_box->removeConversationListItem(mKey); - } - } -} - -void LLIMConversation::onTearOffClicked() -{ - setFollows(isTornOff()? FOLLOWS_ALL : FOLLOWS_NONE); - mSaveRect = isTornOff(); - initRectControl(); - LLFloater::onClickTearOff(this); - refreshConversation(); -} - -// static -bool LLIMConversation::isChatMultiTab() -{ - // Restart is required in order to change chat window type. - return true; -} - -bool LLIMConversation::checkIfTornOff() -{ - bool isTorn = !getHost(); - - if (isTorn != isTornOff()) - { - setTornOff(isTorn); - refreshConversation(); - } - - return isTorn; -} diff --git a/indra/newview/llimconversation.h b/indra/newview/llimconversation.h deleted file mode 100644 index 93a1ab847e..0000000000 --- a/indra/newview/llimconversation.h +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @file llimconversation.h - * @brief LLIMConversation class implements the common behavior of LNearbyChatBar - * @brief and LLIMFloater for hosting both in LLIMContainer - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_IMCONVERSATION_H -#define LL_IMCONVERSATION_H - -#include "lllayoutstack.h" -#include "llparticipantlist.h" -#include "lltransientdockablefloater.h" -#include "llviewercontrol.h" -#include "lleventtimer.h" -#include "llimview.h" -#include "llconversationmodel.h" -#include "llconversationview.h" -#include "lltexteditor.h" - -class LLPanelChatControlPanel; -class LLChatEntry; -class LLChatHistory; - -class LLIMConversation - : public LLTransientDockableFloater -{ - -public: - LOG_CLASS(LLIMConversation); - - LLIMConversation(const LLSD& session_id); - ~LLIMConversation(); - - // reload all message with new settings of visual modes - static void processChatHistoryStyleUpdate(); - - /** - * Returns true if chat is displayed in multi tabbed floater - * false if chat is displayed in multiple windows - */ - static bool isChatMultiTab(); - - // add conversation to container - static void addToHost(const LLUUID& session_id); - - bool isHostAttached() {return mIsHostAttached;} - void setHostAttached(bool is_attached) {mIsHostAttached = is_attached;} - - static LLIMConversation* findConversation(const LLUUID& uuid); - static LLIMConversation* getConversation(const LLUUID& uuid); - - // show/hide the translation check box - void showTranslationCheckbox(const BOOL visible = FALSE); - - bool isNearbyChat() {return mIsNearbyChat;} - - // LLFloater overrides - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ BOOL postBuild(); - /*virtual*/ void draw(); - /*virtual*/ void setVisible(BOOL visible); - /*virtual*/ void setFocus(BOOL focus); - - // Handle the left hand participant list widgets - void addConversationViewParticipant(LLConversationItem* item); - void removeConversationViewParticipant(const LLUUID& participant_id); - void updateConversationViewParticipant(const LLUUID& participant_id); - void refreshConversation(); - void buildConversationViewParticipant(); - - void setSortOrder(const LLConversationSort& order); - - virtual void updateMessages() {} - -protected: - - // callback for click on any items of the visual states menu - void onIMSessionMenuItemClicked(const LLSD& userdata); - - // callback for check/uncheck of the expanded/collapse mode's switcher - bool onIMCompactExpandedMenuItemCheck(const LLSD& userdata); - - // - bool onIMShowModesMenuItemCheck(const LLSD& userdata); - bool onIMShowModesMenuItemEnable(const LLSD& userdata); - static void onSlide(LLIMConversation *self); - virtual void onTearOffClicked(); - - // refresh a visual state of the Call button - void updateCallBtnState(bool callIsActive); - - void hideOrShowTitle(); // toggle the floater's drag handle - void hideAllStandardButtons(); - - /// Update floater header and toolbar buttons when hosted/torn off state is toggled. - void updateHeaderAndToolbar(); - - // Update the input field help text and other places that need the session name - virtual void updateSessionName(const std::string& name); - - // set the enable/disable state for the Call button - virtual void enableDisableCallBtn(); - - // process focus events to set a currently active session - /* virtual */ void onFocusLost(); - /* virtual */ void onFocusReceived(); - - // prepare chat's params and out one message to chatHistory - void appendMessage(const LLChat& chat, const LLSD &args = 0); - - std::string appendTime(); - - bool mIsNearbyChat; - bool mIsP2PChat; - - LLIMModel::LLIMSession* mSession; - - // Participants list: model and view - LLConversationViewParticipant* createConversationViewParticipant(LLConversationItem* item); - - LLUUID mSessionID; - LLLayoutPanel* mParticipantListPanel; // add the widgets to that see mConversationsListPanel - LLParticipantList* getParticipantList(); - conversations_widgets_map mConversationsWidgets; - LLConversationViewModel mConversationViewModel; - LLFolderView* mConversationsRoot; - - LLChatHistory* mChatHistory; - LLChatEntry* mInputEditor; - int mInputEditorTopPad; // padding between input field and chat history - - LLButton* mExpandCollapseBtn; - LLButton* mTearOffBtn; - LLButton* mCloseBtn; - -private: - /// Refreshes the floater at a constant rate. - virtual void refresh() = 0; - - /** - * Adjusts chat history height to fit vertically with input chat field - * and avoid overlapping, since input chat field can be vertically expanded. - * Implementation: chat history bottom "follows" top+top_pad of input chat field - */ - void reshapeChatHistory(); - - bool checkIfTornOff(); - bool mIsHostAttached; - - LLTimer* mRefreshTimer; ///< Defines the rate at which refresh() is called. -}; - - -#endif // LL_IMCONVERSATION_H diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp deleted file mode 100644 index 73c7be37eb..0000000000 --- a/indra/newview/llimfloater.cpp +++ /dev/null @@ -1,1202 +0,0 @@ -/** - * @file llimfloater.cpp - * @brief LLIMFloater class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llimfloater.h" - -#include "lldraghandle.h" -#include "llnotificationsutil.h" - -#include "llagent.h" -#include "llappviewer.h" -#include "llavataractions.h" -#include "llavatarnamecache.h" -#include "llbutton.h" -#include "llchannelmanager.h" -#include "llchiclet.h" -#include "llchicletbar.h" -#include "llfloaterreg.h" -#include "llfloateravatarpicker.h" -#include "llimfloatercontainer.h" // to replace separate IM Floaters with multifloater container -#include "llinventoryfunctions.h" -//#include "lllayoutstack.h" -#include "llchatentry.h" -#include "lllogchat.h" -#include "llscreenchannel.h" -#include "llsyswellwindow.h" -#include "lltrans.h" -#include "llchathistory.h" -#include "llnotifications.h" -#include "llviewerwindow.h" -#include "lltransientfloatermgr.h" -#include "llinventorymodel.h" -#include "llrootview.h" -#include "llspeakers.h" -#include "llviewerchat.h" -#include "llnotificationmanager.h" -#include "llautoreplace.h" - -floater_showed_signal_t LLIMFloater::sIMFloaterShowedSignal; - -LLIMFloater::LLIMFloater(const LLUUID& session_id) - : LLIMConversation(session_id), - mLastMessageIndex(-1), - mDialog(IM_NOTHING_SPECIAL), - mSavedTitle(), - mTypingStart(), - mShouldSendTypingState(false), - mMeTyping(false), - mOtherTyping(false), - mTypingTimer(), - mTypingTimeoutTimer(), - mPositioned(false), - mSessionInitialized(false), - mStartConferenceInSameFloater(false) -{ - mIsNearbyChat = false; - - initIMSession(session_id); - - setOverlapsScreenChannel(true); - - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); - - setDocked(true); -} - - -// virtual -void LLIMFloater::refresh() -{ - if (mMeTyping) -{ - // Time out if user hasn't typed for a while. - if (mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS) - { - setTyping(false); - } - } -} - -// virtual -void LLIMFloater::onClickCloseBtn() -{ - LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(mSessionID); - - if (session != NULL) - { - bool is_call_with_chat = session->isGroupSessionType() - || session->isAdHocSessionType() || session->isP2PSessionType(); - - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - - if (is_call_with_chat && voice_channel != NULL - && voice_channel->isActive()) - { - LLSD payload; - payload["session_id"] = mSessionID; - LLNotificationsUtil::add("ConfirmLeaveCall", LLSD(), payload, confirmLeaveCallCallback); - return; - } - } - else - { - llwarns << "Empty session with id: " << (mSessionID.asString()) << llendl; - return; - } - - LLIMConversation::onClickCloseBtn(); -} - -/* static */ -void LLIMFloater::newIMCallback(const LLSD& data) -{ - if (data["num_unread"].asInteger() > 0 || data["from_id"].asUUID().isNull()) - { - LLUUID session_id = data["session_id"].asUUID(); - - LLIMFloater* floater = LLFloaterReg::findTypedInstance("impanel", session_id); - - // update if visible, otherwise will be updated when opened - if (floater && floater->getVisible()) - { - floater->updateMessages(); - } - } -} - -void LLIMFloater::onVisibilityChange(const LLSD& new_visibility) -{ - bool visible = new_visibility.asBoolean(); - - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - - if (visible && voice_channel && - voice_channel->getState() == LLVoiceChannel::STATE_CONNECTED) - { - LLFloaterReg::showInstance("voice_call", mSessionID); - } - else - { - LLFloaterReg::hideInstance("voice_call", mSessionID); - } -} - -void LLIMFloater::onSendMsg( LLUICtrl* ctrl, void* userdata ) -{ - LLIMFloater* self = (LLIMFloater*) userdata; - self->sendMsgFromInputEditor(); - self->setTyping(false); -} - -void LLIMFloater::sendMsgFromInputEditor() -{ - if (gAgent.isGodlike() - || (mDialog != IM_NOTHING_SPECIAL) - || !mOtherParticipantUUID.isNull()) - { - if (mInputEditor) - { - LLWString text = mInputEditor->getWText(); - LLWStringUtil::trim(text); - LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. - if(!text.empty()) - { - // Truncate and convert to UTF8 for transport - std::string utf8_text = wstring_to_utf8str(text); - - sendMsg(utf8_text); - - mInputEditor->setText(LLStringUtil::null); - } - } - } - else - { - llinfos << "Cannot send IM to everyone unless you're a god." << llendl; - } -} - -void LLIMFloater::sendMsg(const std::string& msg) -{ - const std::string utf8_text = utf8str_truncate(msg, MAX_MSG_BUF_SIZE - 1); - - if (mSessionInitialized) - { - LLIMModel::sendMessage(utf8_text, mSessionID, mOtherParticipantUUID, mDialog); - } - else - { - //queue up the message to send once the session is initialized - mQueuedMsgsForInit.append(utf8_text); - } - - updateMessages(); -} - -LLIMFloater::~LLIMFloater() -{ - mVoiceChannelStateChangeConnection.disconnect(); - if(LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->removeObserver(this); - } - - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); -} - - -void LLIMFloater::initIMSession(const LLUUID& session_id) -{ - // Change the floater key to bind it to a new session. - setKey(session_id); - - mSessionID = session_id; - mSession = LLIMModel::getInstance()->findIMSession(mSessionID); - - if (mSession) - { - mIsP2PChat = mSession->isP2PSessionType(); - mSessionInitialized = mSession->mSessionInitialized; - mDialog = mSession->mType; - } -} - -void LLIMFloater::initIMFloater() -{ - const LLUUID& other_party_id = - LLIMModel::getInstance()->getOtherParticipantID(mSessionID); - if (other_party_id.notNull()) - { - mOtherParticipantUUID = other_party_id; - } - - boundVoiceChannel(); - - mTypingStart = LLTrans::getString("IM_typing_start_string"); - - // Show control panel in torn off floaters only. - mParticipantListPanel->setVisible(!getHost() && gSavedSettings.getBOOL("IMShowControlPanel")); - - // Disable input editor if session cannot accept text - if ( mSession && !mSession->mTextIMPossible ) - { - mInputEditor->setEnabled(FALSE); - mInputEditor->setLabel(LLTrans::getString("IM_unavailable_text_label")); - } - - if (!mIsP2PChat) - { - std::string session_name(LLIMModel::instance().getName(mSessionID)); - updateSessionName(session_name); - } -} - -//virtual -BOOL LLIMFloater::postBuild() -{ - BOOL result = LLIMConversation::postBuild(); - - mInputEditor->setMaxTextLength(1023); - // enable line history support for instant message bar - // XXX stinson TODO : resolve merge by adding autoreplace to text editors -#if 0 - // *TODO Establish LineEditor with autoreplace callback - mInputEditor->setAutoreplaceCallback(boost::bind(&LLAutoReplace::autoreplaceCallback, LLAutoReplace::getInstance(), _1, _2)); -#endif - - mInputEditor->setFocusReceivedCallback( boost::bind(onInputEditorFocusReceived, _1, this) ); - mInputEditor->setFocusLostCallback( boost::bind(onInputEditorFocusLost, _1, this) ); - mInputEditor->setKeystrokeCallback( boost::bind(onInputEditorKeystroke, _1, this) ); - mInputEditor->setCommitCallback(boost::bind(onSendMsg, _1, this)); - - setDocked(true); - - LLButton* add_btn = getChild("add_btn"); - - // Allow to add chat participants depending on the session type - add_btn->setEnabled(isInviteAllowed()); - add_btn->setClickedCallback(boost::bind(&LLIMFloater::onAddButtonClicked, this)); - - childSetAction("voice_call_btn", boost::bind(&LLIMFloater::onCallButtonClicked, this)); - - LLVoiceClient::getInstance()->addObserver(this); - - //*TODO if session is not initialized yet, add some sort of a warning message like "starting session...blablabla" - //see LLFloaterIMPanel for how it is done (IB) - - initIMFloater(); - - return result; -} - -void LLIMFloater::onAddButtonClicked() -{ - LLView * button = findChild("toolbar_panel")->findChild("add_btn"); - LLFloater* root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLIMFloater::addSessionParticipants, this, _1), TRUE, TRUE, FALSE, root_floater->getName(), button); - if (!picker) - { - return; - } - - // Need to disable 'ok' button when selected users are already in conversation. - picker->setOkBtnEnableCb(boost::bind(&LLIMFloater::canAddSelectedToChat, this, _1)); - - if (root_floater) - { - root_floater->addDependentFloater(picker); - } -} - -bool LLIMFloater::canAddSelectedToChat(const uuid_vec_t& uuids) -{ - if (!mSession - || mDialog == IM_SESSION_GROUP_START - || mDialog == IM_SESSION_INVITE && gAgent.isInGroup(mSessionID)) - { - return false; - } - - if (mIsP2PChat) - { - // For a P2P session just check if we are not adding the other participant. - - for (uuid_vec_t::const_iterator id = uuids.begin(); - id != uuids.end(); ++id) - { - if (*id == mOtherParticipantUUID) - { - return false; - } - } - } - else - { - // For a conference session we need to check against the list from LLSpeakerMgr, - // because this list may change when participants join or leave the session. - - LLSpeakerMgr::speaker_list_t speaker_list; - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - speaker_mgr->getSpeakerList(&speaker_list, true); - } - - for (uuid_vec_t::const_iterator id = uuids.begin(); - id != uuids.end(); ++id) - { - for (LLSpeakerMgr::speaker_list_t::const_iterator it = speaker_list.begin(); - it != speaker_list.end(); ++it) - { - const LLPointer& speaker = *it; - if (*id == speaker->mID) - { - return false; - } - } - } - } - - return true; -} - -void LLIMFloater::addSessionParticipants(const uuid_vec_t& uuids) -{ - if (mIsP2PChat) - { - LLSD payload; - LLSD args; - - LLNotificationsUtil::add("ConfirmAddingChatParticipants", args, payload, - boost::bind(&LLIMFloater::addP2PSessionParticipants, this, _1, _2, uuids)); - } - else - { - // remember whom we have invited, to notify others later, when the invited ones actually join - mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); - - inviteToSession(uuids); - } -} - -void LLIMFloater::addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) - { - return; - } - - mStartConferenceInSameFloater = true; - - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - - // first check whether this is a voice session - bool is_voice_call = voice_channel != NULL && voice_channel->isActive(); - - uuid_vec_t temp_ids; - - // Add the initial participant of a P2P session - temp_ids.push_back(mOtherParticipantUUID); - temp_ids.insert(temp_ids.end(), uuids.begin(), uuids.end()); - - // then we can close the current session - onClose(false); - - // we start a new session so reset the initialization flag - mSessionInitialized = false; - - // remember whom we have invited, to notify others later, when the invited ones actually join - mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); - - // Start a new ad hoc voice call if we invite new participants to a P2P call, - // or start a text chat otherwise. - if (is_voice_call) - { - LLAvatarActions::startAdhocCall(temp_ids, mSessionID); - } - else - { - LLAvatarActions::startConference(temp_ids, mSessionID); - } -} - -void LLIMFloater::sendParticipantsAddedNotification(const uuid_vec_t& uuids) -{ - std::string names_string; - LLAvatarActions::buildResidentsString(uuids, names_string); - LLStringUtil::format_map_t args; - args["[NAME]"] = names_string; - - sendMsg(getString(uuids.size() > 1 ? "multiple_participants_added" : "participant_added", args)); -} - -void LLIMFloater::boundVoiceChannel() -{ - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - if(voice_channel) - { - mVoiceChannelStateChangeConnection = voice_channel->setStateChangedCallback( - boost::bind(&LLIMFloater::onVoiceChannelStateChanged, this, _1, _2)); - - //call (either p2p, group or ad-hoc) can be already in started state - bool callIsActive = voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED; - updateCallBtnState(callIsActive); - } -} - -void LLIMFloater::onCallButtonClicked() -{ - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - if (voice_channel) - { - bool is_call_active = voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED; - if (is_call_active) - { - gIMMgr->endCall(mSessionID); - } - else - { - gIMMgr->startCall(mSessionID); - } - } -} - -void LLIMFloater::onChange(EStatusType status, const std::string &channelURI, bool proximal) -{ - if(status != STATUS_JOINING && status != STATUS_LEFT_CHANNEL) - { - enableDisableCallBtn(); - } -} - -void LLIMFloater::onVoiceChannelStateChanged( - const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state) -{ - bool callIsActive = new_state >= LLVoiceChannel::STATE_CALL_STARTED; - updateCallBtnState(callIsActive); -} - -void LLIMFloater::updateSessionName(const std::string& name) -{ - LLIMConversation::updateSessionName(name); - setTitle(name); - mTypingStart.setArg("[NAME]", name); -} - -//static -LLIMFloater* LLIMFloater::show(const LLUUID& session_id) -{ - closeHiddenIMToasts(); - - if (!gIMMgr->hasSession(session_id)) - return NULL; - - // Test the existence of the floater before we try to create it - bool exist = findInstance(session_id); - - // Get the floater: this will create the instance if it didn't exist - LLIMFloater* floater = getInstance(session_id); - if (!floater) - return NULL; - - LLIMFloaterContainer* floater_container = LLIMFloaterContainer::getInstance(); - - // Do not add again existing floaters - if (!exist) - { - // LLTabContainer::eInsertionPoint i_pt = user_initiated ? LLTabContainer::RIGHT_OF_CURRENT : LLTabContainer::END; - // TODO: mantipov: use LLTabContainer::RIGHT_OF_CURRENT if it exists - LLTabContainer::eInsertionPoint i_pt = LLTabContainer::END; - if (floater_container) - { - floater_container->addFloater(floater, TRUE, i_pt); - } - } - - floater->openFloater(floater->getKey()); - - floater->setVisible(TRUE); - - return floater; -} -//static -LLIMFloater* LLIMFloater::findInstance(const LLUUID& session_id) -{ - LLIMFloater* conversation = - LLFloaterReg::findTypedInstance("impanel", session_id); - - return conversation; -} - -LLIMFloater* LLIMFloater::getInstance(const LLUUID& session_id) -{ - LLIMFloater* conversation = - LLFloaterReg::getTypedInstance("impanel", session_id); - - return conversation; -} - -void LLIMFloater::onClose(bool app_quitting) -{ - setTyping(false); - - // The source of much argument and design thrashing - // Should the window hide or the session close when the X is clicked? - // - // Last change: - // EXT-3516 X Button should end IM session, _ button should hide - gIMMgr->leaveSession(mSessionID); - - // Clean up the conversation *after* the session has been ended - LLIMConversation::onClose(app_quitting); -} - -void LLIMFloater::setDocked(bool docked, bool pop_on_undock) -{ - // update notification channel state - LLNotificationsUI::LLScreenChannel* channel = static_cast - (LLNotificationsUI::LLChannelManager::getInstance()-> - findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); - - if(!isChatMultiTab()) - { - LLTransientDockableFloater::setDocked(docked, pop_on_undock); - } - - // update notification channel state - if(channel) - { - channel->updateShowToastsState(); - channel->redrawToasts(); - } -} - -void LLIMFloater::setVisible(BOOL visible) -{ - LLNotificationsUI::LLScreenChannel* channel = static_cast - (LLNotificationsUI::LLChannelManager::getInstance()-> - findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); - - LLIMConversation::setVisible(visible); - - // update notification channel state - if(channel) - { - channel->updateShowToastsState(); - channel->redrawToasts(); - } - - if(!visible) - { - LLIMChiclet* chiclet = LLChicletBar::getInstance()->getChicletPanel()->findChiclet(mSessionID); - if(chiclet) - { - chiclet->setToggleState(false); - } - } - - if (visible && isInVisibleChain()) - { - sIMFloaterShowedSignal(mSessionID); - - } - - setFocus(visible); -} - -BOOL LLIMFloater::getVisible() -{ - bool visible; - - if(isChatMultiTab()) - { - LLIMFloaterContainer* im_container = - LLIMFloaterContainer::getInstance(); - - // Treat inactive floater as invisible. - bool is_active = im_container->getActiveFloater() == this; - - //torn off floater is always inactive - if (!is_active && getHost() != im_container) - { - visible = LLTransientDockableFloater::getVisible(); - } - else - { - // getVisible() returns TRUE when Tabbed IM window is minimized. - visible = is_active && !im_container->isMinimized() - && im_container->getVisible(); - } - } - else - { - visible = LLTransientDockableFloater::getVisible(); - } - - return visible; -} - -//static -bool LLIMFloater::toggle(const LLUUID& session_id) -{ - if(!isChatMultiTab()) - { - LLIMFloater* floater = LLFloaterReg::findTypedInstance( - "impanel", session_id); - if (floater && floater->getVisible() && floater->hasFocus()) - { - // clicking on chiclet to close floater just hides it to maintain existing - // scroll/text entry state - floater->setVisible(false); - return false; - } - else if(floater && (!floater->isDocked() || floater->getVisible() && !floater->hasFocus())) - { - floater->setVisible(TRUE); - floater->setFocus(TRUE); - return true; - } - } - - // ensure the list of messages is updated when floater is made visible - show(session_id); - return true; -} - -void LLIMFloater::sessionInitReplyReceived(const LLUUID& im_session_id) -{ - mSessionInitialized = true; - - //will be different only for an ad-hoc im session - if (mSessionID != im_session_id) - { - initIMSession(im_session_id); - buildConversationViewParticipant(); - } - - initIMFloater(); - - //*TODO here we should remove "starting session..." warning message if we added it in postBuild() (IB) - - //need to send delayed messages collected while waiting for session initialization - if (mQueuedMsgsForInit.size()) - { - LLSD::array_iterator iter; - for ( iter = mQueuedMsgsForInit.beginArray(); - iter != mQueuedMsgsForInit.endArray(); ++iter) - { - LLIMModel::sendMessage(iter->asString(), mSessionID, - mOtherParticipantUUID, mDialog); - } - - mQueuedMsgsForInit.clear(); - } -} - -void LLIMFloater::updateMessages() -{ - std::list messages; - - // we shouldn't reset unread message counters if IM floater doesn't have focus - LLIMModel::instance().getMessages( - mSessionID, messages, mLastMessageIndex + 1, hasFocus()); - - if (messages.size()) - { - std::ostringstream message; - std::list::const_reverse_iterator iter = messages.rbegin(); - std::list::const_reverse_iterator iter_end = messages.rend(); - for (; iter != iter_end; ++iter) - { - LLSD msg = *iter; - - std::string time = msg["time"].asString(); - LLUUID from_id = msg["from_id"].asUUID(); - std::string from = msg["from"].asString(); - std::string message = msg["message"].asString(); - bool is_history = msg["is_history"].asBoolean(); - - LLChat chat; - chat.mFromID = from_id; - chat.mSessionID = mSessionID; - chat.mFromName = from; - chat.mTimeStr = time; - chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; - - // process offer notification - if (msg.has("notification_id")) - { - chat.mNotifId = msg["notification_id"].asUUID(); - // if notification exists - embed it - if (LLNotificationsUtil::find(chat.mNotifId) != NULL) - { - // remove embedded notification from channel - LLNotificationsUI::LLScreenChannel* channel = static_cast - (LLNotificationsUI::LLChannelManager::getInstance()-> - findChannelByID(LLUUID(gSavedSettings.getString("NotificationChannelUUID")))); - if (getVisible()) - { - // toast will be automatically closed since it is not storable toast - channel->hideToast(chat.mNotifId); - } - } - // if notification doesn't exist - try to use next message which should be log entry - else - { - continue; - } - } - //process text message - else - { - chat.mText = message; - } - - // Add the message to the chat log - appendMessage(chat); - mLastMessageIndex = msg["index"].asInteger(); - - // if it is a notification - next message is a notification history log, so skip it - if (chat.mNotifId.notNull() && LLNotificationsUtil::find(chat.mNotifId) != NULL) - { - if (++iter == iter_end) - { - break; - } - else - { - mLastMessageIndex++; - } - } - } - } -} - -void LLIMFloater::reloadMessages() -{ - mChatHistory->clear(); - mLastMessageIndex = -1; - updateMessages(); - mInputEditor->setFont(LLViewerChat::getChatFont()); -} - -// static -void LLIMFloater::onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata ) -{ - LLIMFloater* self= (LLIMFloater*) userdata; - - // Allow enabling the LLIMFloater input editor only if session can accept text - LLIMModel::LLIMSession* im_session = - LLIMModel::instance().findIMSession(self->mSessionID); - //TODO: While disabled lllineeditor can receive focus we need to check if it is enabled (EK) - if( im_session && im_session->mTextIMPossible && self->mInputEditor->getEnabled()) - { - //in disconnected state IM input editor should be disabled - self->mInputEditor->setEnabled(!gDisconnected); - } -} - -// static -void LLIMFloater::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata) -{ - LLIMFloater* self = (LLIMFloater*) userdata; - self->setTyping(false); -} - -// static -void LLIMFloater::onInputEditorKeystroke(LLTextEditor* caller, void* userdata) -{ - LLIMFloater* self = (LLIMFloater*)userdata; - std::string text = self->mInputEditor->getText(); - - // Deleting all text counts as stopping typing. - self->setTyping(!text.empty()); -} - -void LLIMFloater::setTyping(bool typing) -{ - if ( typing ) - { - // Started or proceeded typing, reset the typing timeout timer - mTypingTimeoutTimer.reset(); - } - - if ( mMeTyping != typing ) - { - // Typing state is changed - mMeTyping = typing; - // So, should send current state - mShouldSendTypingState = true; - // In case typing is started, send state after some delay - mTypingTimer.reset(); - } - - // Don't want to send typing indicators to multiple people, potentially too - // much network traffic. Only send in person-to-person IMs. - if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL ) - { - // Still typing, send 'start typing' notification or - // send 'stop typing' notification immediately - if (!mMeTyping || mTypingTimer.getElapsedTimeF32() > 1.f) - { - LLIMModel::instance().sendTypingState(mSessionID, - mOtherParticipantUUID, mMeTyping); - mShouldSendTypingState = false; - } - } - - if (!mIsNearbyChat) - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - speaker_mgr->setSpeakerTyping(gAgent.getID(), FALSE); - } - } -} - -void LLIMFloater::processIMTyping(const LLIMInfo* im_info, BOOL typing) -{ - if ( typing ) - { - // other user started typing - addTypingIndicator(im_info); - } - else - { - // other user stopped typing - removeTypingIndicator(im_info); - } -} - -void LLIMFloater::processAgentListUpdates(const LLSD& body) -{ - uuid_vec_t joined_uuids; - - if (body.isMap() && body.has("agent_updates") && body["agent_updates"].isMap()) - { - LLSD::map_const_iterator update_it; - for(update_it = body["agent_updates"].beginMap(); - update_it != body["agent_updates"].endMap(); - ++update_it) - { - LLUUID agent_id(update_it->first); - LLSD agent_data = update_it->second; - - if (agent_data.isMap()) - { - // store the new participants in joined_uuids - if (agent_data.has("transition") && agent_data["transition"].asString() == "ENTER") - { - joined_uuids.push_back(agent_id); - } - - // process the moderator mutes - if (agent_id == gAgentID && agent_data.has("info") && agent_data["info"].has("mutes")) - { - BOOL moderator_muted_text = agent_data["info"]["mutes"]["text"].asBoolean(); - mInputEditor->setEnabled(!moderator_muted_text); - std::string label; - if (moderator_muted_text) - label = LLTrans::getString("IM_muted_text_label"); - else - label = LLTrans::getString("IM_to_label") + " " + LLIMModel::instance().getName(mSessionID); - mInputEditor->setLabel(label); - - if (moderator_muted_text) - LLNotificationsUtil::add("TextChatIsMutedByModerator"); - } - } - } - } - - // the vectors need to be sorted for computing the intersection and difference - std::sort(mInvitedParticipants.begin(), mInvitedParticipants.end()); - std::sort(joined_uuids.begin(), joined_uuids.end()); - - uuid_vec_t intersection; // uuids of invited residents who have joined the conversation - std::set_intersection(mInvitedParticipants.begin(), mInvitedParticipants.end(), - joined_uuids.begin(), joined_uuids.end(), - std::back_inserter(intersection)); - - if (intersection.size() > 0) - { - sendParticipantsAddedNotification(intersection); - } - - // Remove all joined participants from invited array. - // The difference between the two vectors (the elements in mInvitedParticipants which are not in joined_uuids) - // is placed at the beginning of mInvitedParticipants, then all other elements are erased. - mInvitedParticipants.erase(std::set_difference(mInvitedParticipants.begin(), mInvitedParticipants.end(), - joined_uuids.begin(), joined_uuids.end(), - mInvitedParticipants.begin()), - mInvitedParticipants.end()); -} - -void LLIMFloater::processSessionUpdate(const LLSD& session_update) -{ - // *TODO : verify following code when moderated mode will be implemented - if ( false && session_update.has("moderated_mode") && - session_update["moderated_mode"].has("voice") ) - { - BOOL voice_moderated = session_update["moderated_mode"]["voice"]; - const std::string session_label = LLIMModel::instance().getName(mSessionID); - - if (voice_moderated) - { - setTitle(session_label + std::string(" ") - + LLTrans::getString("IM_moderated_chat_label")); - } - else - { - setTitle(session_label); - } - - // *TODO : uncomment this when/if LLPanelActiveSpeakers panel will be added - //update the speakers dropdown too - //mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated); - } -} - -// virtual -BOOL LLIMFloater::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - if (cargo_type == DAD_PERSON) - { - if (dropPerson(static_cast(cargo_data), drop)) - { - *accept = ACCEPT_YES_MULTI; - } - else - { - *accept = ACCEPT_NO; - } - } - else if (mDialog == IM_NOTHING_SPECIAL) - { - LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionID, drop, - cargo_type, cargo_data, accept); - } - - return TRUE; -} - -bool LLIMFloater::dropPerson(LLUUID* person_id, bool drop) -{ - bool res = person_id && person_id->notNull(); - if(res) - { - uuid_vec_t ids; - ids.push_back(*person_id); - - res = canAddSelectedToChat(ids); - if(res && drop) - { - addSessionParticipants(ids); - } - } - - return res; -} - -BOOL LLIMFloater::isInviteAllowed() const -{ - return ( (IM_SESSION_CONFERENCE_START == mDialog) - || (IM_SESSION_INVITE == mDialog && !gAgent.isInGroup(mSessionID)) - || mIsP2PChat); -} - -class LLSessionInviteResponder : public LLHTTPClient::Responder -{ -public: - LLSessionInviteResponder(const LLUUID& session_id) - { - mSessionID = session_id; - } - - void error(U32 statusNum, const std::string& reason) - { - llinfos << "Error inviting all agents to session" << llendl; - //throw something back to the viewer here? - } - -private: - LLUUID mSessionID; -}; - -BOOL LLIMFloater::inviteToSession(const uuid_vec_t& ids) -{ - LLViewerRegion* region = gAgent.getRegion(); - bool is_region_exist = region != NULL; - - if (is_region_exist) - { - S32 count = ids.size(); - - if( isInviteAllowed() && (count > 0) ) - { - llinfos << "LLIMFloater::inviteToSession() - inviting participants" << llendl; - - std::string url = region->getCapability("ChatSessionRequest"); - - LLSD data; - data["params"] = LLSD::emptyArray(); - for (int i = 0; i < count; i++) - { - data["params"].append(ids[i]); - } - data["method"] = "invite"; - data["session-id"] = mSessionID; - LLHTTPClient::post(url, data,new LLSessionInviteResponder(mSessionID)); - } - else - { - llinfos << "LLIMFloater::inviteToSession -" - << " no need to invite agents for " - << mDialog << llendl; - // successful add, because everyone that needed to get added - // was added. - } - } - - return is_region_exist; -} - -void LLIMFloater::addTypingIndicator(const LLIMInfo* im_info) -{ - // We may have lost a "stop-typing" packet, don't add it twice - if ( im_info && !mOtherTyping ) - { - mOtherTyping = true; - - // Save and set new title - mSavedTitle = getTitle(); - setTitle (mTypingStart); - - // Update speaker - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if ( speaker_mgr ) - { - speaker_mgr->setSpeakerTyping(im_info->mFromID, TRUE); - } - } -} - -void LLIMFloater::removeTypingIndicator(const LLIMInfo* im_info) -{ - if ( mOtherTyping ) - { - mOtherTyping = false; - - // Revert the title to saved one - setTitle(mSavedTitle); - - if ( im_info ) - { - // Update speaker - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if ( speaker_mgr ) - { - speaker_mgr->setSpeakerTyping(im_info->mFromID, FALSE); - } - } - } -} - -// static -void LLIMFloater::closeHiddenIMToasts() -{ - class IMToastMatcher: public LLNotificationsUI::LLScreenChannel::Matcher - { - public: - bool matches(const LLNotificationPtr notification) const - { - // "notifytoast" type of notifications is reserved for IM notifications - return "notifytoast" == notification->getType(); - } - }; - - LLNotificationsUI::LLScreenChannel* channel = - LLNotificationsUI::LLChannelManager::getNotificationScreenChannel(); - if (channel != NULL) - { - channel->closeHiddenToasts(IMToastMatcher()); - } -} -// static -void LLIMFloater::confirmLeaveCallCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - const LLSD& payload = notification["payload"]; - LLUUID session_id = payload["session_id"]; - - LLFloater* im_floater = findInstance(session_id); - if (option == 0 && im_floater != NULL) - { - im_floater->closeFloater(); - } - - return; -} - -// static -void LLIMFloater::sRemoveTypingIndicator(const LLSD& data) -{ - LLUUID session_id = data["session_id"]; - if (session_id.isNull()) - return; - - LLUUID from_id = data["from_id"]; - if (gAgentID == from_id || LLUUID::null == from_id) - return; - - LLIMFloater* floater = LLIMFloater::findInstance(session_id); - if (!floater) - return; - - if (IM_NOTHING_SPECIAL != floater->mDialog) - return; - - floater->removeTypingIndicator(); -} - -// static -void LLIMFloater::onIMChicletCreated( const LLUUID& session_id ) -{ - LLIMFloater::addToHost(session_id); -} - -boost::signals2::connection LLIMFloater::setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb) -{ - return LLIMFloater::sIMFloaterShowedSignal.connect(cb); -} diff --git a/indra/newview/llimfloater.h b/indra/newview/llimfloater.h deleted file mode 100644 index 6ba31657dc..0000000000 --- a/indra/newview/llimfloater.h +++ /dev/null @@ -1,196 +0,0 @@ -/** - * @file llimfloater.h - * @brief LLIMFloater class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_IMFLOATER_H -#define LL_IMFLOATER_H - -#include "llimview.h" -#include "llimconversation.h" -#include "llinstantmessage.h" -#include "lllogchat.h" -#include "lltooldraganddrop.h" -#include "llvoicechannel.h" -#include "llvoiceclient.h" - -class LLAvatarName; -class LLButton; -class LLChatEntry; -class LLTextEditor; -class LLPanelChatControlPanel; -class LLChatHistory; -class LLInventoryItem; -class LLInventoryCategory; - -typedef boost::signals2::signal floater_showed_signal_t; - -/** - * Individual IM window that appears at the bottom of the screen, - * optionally "docked" to the bottom tray. - */ -class LLIMFloater - : public LLVoiceClientStatusObserver - , public LLIMConversation -{ - LOG_CLASS(LLIMFloater); -public: - LLIMFloater(const LLUUID& session_id); - - virtual ~LLIMFloater(); - - void initIMSession(const LLUUID& session_id); - void initIMFloater(); - - // LLView overrides - /*virtual*/ BOOL postBuild(); - /*virtual*/ void setVisible(BOOL visible); - /*virtual*/ BOOL getVisible(); - // Check typing timeout timer. - - static LLIMFloater* findInstance(const LLUUID& session_id); - static LLIMFloater* getInstance(const LLUUID& session_id); - - // LLFloater overrides - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); - // Make IM conversion visible and update the message history - static LLIMFloater* show(const LLUUID& session_id); - - // Toggle panel specified by session_id - // Returns true iff panel became visible - static bool toggle(const LLUUID& session_id); - - void sessionInitReplyReceived(const LLUUID& im_session_id); - - // get new messages from LLIMModel - /*virtual*/ void updateMessages(); - void reloadMessages(); - static void onSendMsg(LLUICtrl*, void*); - void sendMsgFromInputEditor(); - void sendMsg(const std::string& msg); - - // callback for LLIMModel on new messages - // route to specific floater if it is visible - static void newIMCallback(const LLSD& data); - - // called when docked floater's position has been set by chiclet - void setPositioned(bool b) { mPositioned = b; }; - - void onVisibilityChange(const LLSD& new_visibility); - - // Implements LLVoiceClientStatusObserver::onChange() to enable the call - // button when voice is available - void onChange(EStatusType status, const std::string &channelURI, - bool proximal); - - virtual LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; } - virtual void onVoiceChannelStateChanged( - const LLVoiceChannel::EState& old_state, - const LLVoiceChannel::EState& new_state); - - void processIMTyping(const LLIMInfo* im_info, BOOL typing); - void processAgentListUpdates(const LLSD& body); - void processSessionUpdate(const LLSD& session_update); - - /*virtual*/ BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - - - //used as a callback on receiving new IM message - static void sRemoveTypingIndicator(const LLSD& data); - static void onIMChicletCreated(const LLUUID& session_id); - - bool getStartConferenceInSameFloater() const { return mStartConferenceInSameFloater; } - const LLUUID& getOtherParticipantUUID() {return mOtherParticipantUUID;} - - static boost::signals2::connection setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb); - static floater_showed_signal_t sIMFloaterShowedSignal; - -private: - - /*virtual*/ void refresh(); - - /*virtual*/ void onClickCloseBtn(); - - // Update the window title and input field help text - /*virtual*/ void updateSessionName(const std::string& name); - - bool dropPerson(LLUUID* person_id, bool drop); - - BOOL isInviteAllowed() const; - BOOL inviteToSession(const uuid_vec_t& agent_ids); - static void onInputEditorFocusReceived( LLFocusableElement* caller,void* userdata ); - static void onInputEditorFocusLost(LLFocusableElement* caller, void* userdata); - static void onInputEditorKeystroke(LLTextEditor* caller, void* userdata); - void setTyping(bool typing); - void onAddButtonClicked(); - void addSessionParticipants(const uuid_vec_t& uuids); - void addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids); - void sendParticipantsAddedNotification(const uuid_vec_t& uuids); - bool canAddSelectedToChat(const uuid_vec_t& uuids); - - void onCallButtonClicked(); - - void boundVoiceChannel(); - - // Add the "User is typing..." indicator. - void addTypingIndicator(const LLIMInfo* im_info); - - // Remove the "User is typing..." indicator. - void removeTypingIndicator(const LLIMInfo* im_info = NULL); - - static void closeHiddenIMToasts(); - - static void confirmLeaveCallCallback(const LLSD& notification, const LLSD& response); - - S32 mLastMessageIndex; - - EInstantMessage mDialog; - LLUUID mOtherParticipantUUID; - bool mPositioned; - - std::string mSavedTitle; - LLUIString mTypingStart; - bool mMeTyping; - bool mOtherTyping; - bool mShouldSendTypingState; - LLFrameTimer mTypingTimer; - LLFrameTimer mTypingTimeoutTimer; - - bool mSessionInitialized; - LLSD mQueuedMsgsForInit; - - bool mStartConferenceInSameFloater; - - uuid_vec_t mInvitedParticipants; - - // connection to voice channel state change signal - boost::signals2::connection mVoiceChannelStateChangeConnection; -}; - -#endif // LL_IMFLOATER_H diff --git a/indra/newview/llimfloatercontainer.cpp b/indra/newview/llimfloatercontainer.cpp deleted file mode 100644 index 9c1f5d7593..0000000000 --- a/indra/newview/llimfloatercontainer.cpp +++ /dev/null @@ -1,1557 +0,0 @@ -/** - * @file llimfloatercontainer.cpp - * @brief Multifloater containing active IM sessions in separate tab container tabs - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include "llimfloater.h" -#include "llimfloatercontainer.h" - -#include "llfloaterreg.h" -#include "lllayoutstack.h" -#include "llnearbychat.h" - -#include "llagent.h" -#include "llavataractions.h" -#include "llavatariconctrl.h" -#include "llavatarnamecache.h" -#include "llcallbacklist.h" -#include "llgroupactions.h" -#include "llgroupiconctrl.h" -#include "llfloateravatarpicker.h" -#include "llfloaterpreference.h" -#include "llimview.h" -#include "llnotificationsutil.h" -#include "lltransientfloatermgr.h" -#include "llviewercontrol.h" -#include "llconversationview.h" -#include "llcallbacklist.h" -#include "llworld.h" - -#include "llsdserialize.h" -// -// LLIMFloaterContainer -// -LLIMFloaterContainer::LLIMFloaterContainer(const LLSD& seed) -: LLMultiFloater(seed), - mExpandCollapseBtn(NULL), - mConversationsRoot(NULL), - mConversationsEventStream("ConversationsEvents"), - mInitialized(false) -{ - mEnableCallbackRegistrar.add("IMFloaterContainer.Check", boost::bind(&LLIMFloaterContainer::isActionChecked, this, _2)); - mCommitCallbackRegistrar.add("IMFloaterContainer.Action", boost::bind(&LLIMFloaterContainer::onCustomAction, this, _2)); - - mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLIMFloaterContainer::checkContextMenuItem, this, _2)); - mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLIMFloaterContainer::enableContextMenuItem, this, _2)); - mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLIMFloaterContainer::doToSelected, this, _2)); - - mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&LLIMFloaterContainer::doToSelectedGroup, this, _2)); - - // Firstly add our self to IMSession observers, so we catch session events - LLIMMgr::getInstance()->addSessionObserver(this); - - mAutoResize = FALSE; - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); -} - -LLIMFloaterContainer::~LLIMFloaterContainer() -{ - mConversationsEventStream.stopListening("ConversationsRefresh"); - - gIdleCallbacks.deleteFunction(idle, this); - - mNewMessageConnection.disconnect(); - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); - - gSavedPerAccountSettings.setBOOL("ConversationsListPaneCollapsed", mConversationsPane->isCollapsed()); - gSavedPerAccountSettings.setBOOL("ConversationsMessagePaneCollapsed", mMessagesPane->isCollapsed()); - - if (!LLSingleton::destroyed()) - { - LLIMMgr::getInstance()->removeSessionObserver(this); - } -} - -void LLIMFloaterContainer::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) -{ - addConversationListItem(session_id); - LLIMConversation::addToHost(session_id); -} - -void LLIMFloaterContainer::sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) -{ - selectConversation(session_id); -} - -void LLIMFloaterContainer::sessionVoiceOrIMStarted(const LLUUID& session_id) -{ - addConversationListItem(session_id); - LLIMConversation::addToHost(session_id); -} - -void LLIMFloaterContainer::sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) -{ - // *TODO: We should do this *without* delete and recreate - addConversationListItem(new_session_id, removeConversationListItem(old_session_id)); -} - -void LLIMFloaterContainer::sessionRemoved(const LLUUID& session_id) -{ - removeConversationListItem(session_id); -} - -// static -void LLIMFloaterContainer::onCurrentChannelChanged(const LLUUID& session_id) -{ - if (session_id != LLUUID::null) - { - LLIMFloaterContainer::getInstance()->showConversation(session_id); - } -} - - -BOOL LLIMFloaterContainer::postBuild() -{ - mNewMessageConnection = LLIMModel::instance().mNewMsgSignal.connect(boost::bind(&LLIMFloaterContainer::onNewMessageReceived, this, _1)); - // Do not call base postBuild to not connect to mCloseSignal to not close all floaters via Close button - // mTabContainer will be initialized in LLMultiFloater::addChild() - - setTabContainer(getChild("im_box_tab_container")); - - mConversationsStack = getChild("conversations_stack"); - mConversationsPane = getChild("conversations_layout_panel"); - mMessagesPane = getChild("messages_layout_panel"); - - mConversationsListPanel = getChild("conversations_list_panel"); - - // Open IM session with selected participant on double click event - mConversationsListPanel->setDoubleClickCallback(boost::bind(&LLIMFloaterContainer::doToSelected, this, LLSD("im"))); - - // Create the root model and view for all conversation sessions - LLConversationItem* base_item = new LLConversationItem(getRootViewModel()); - - LLFolderView::Params p(LLUICtrlFactory::getDefaultParams()); - p.name = getName(); - p.title = getLabel(); - p.rect = LLRect(0, 0, getRect().getWidth(), 0); - p.parent_panel = mConversationsListPanel; - p.tool_tip = p.name; - p.listener = base_item; - p.view_model = &mConversationViewModel; - p.root = NULL; - p.use_ellipses = true; - p.options_menu = "menu_conversation.xml"; - mConversationsRoot = LLUICtrlFactory::create(p); - mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); - - // Add listener to conversation model events - mConversationsEventStream.listen("ConversationsRefresh", boost::bind(&LLIMFloaterContainer::onConversationModelEvent, this, _1)); - - // a scroller for folder view - LLRect scroller_view_rect = mConversationsListPanel->getRect(); - scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); - LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams()); - scroller_params.rect(scroller_view_rect); - - LLScrollContainer* scroller = LLUICtrlFactory::create(scroller_params); - scroller->setFollowsAll(); - mConversationsListPanel->addChild(scroller); - scroller->addChild(mConversationsRoot); - mConversationsRoot->setScrollContainer(scroller); - mConversationsRoot->setFollowsAll(); - mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox); - - addConversationListItem(LLUUID()); // manually add nearby chat - - mExpandCollapseBtn = getChild("expand_collapse_btn"); - mExpandCollapseBtn->setClickedCallback(boost::bind(&LLIMFloaterContainer::onExpandCollapseButtonClicked, this)); - - childSetAction("add_btn", boost::bind(&LLIMFloaterContainer::onAddButtonClicked, this)); - - collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); - collapseConversationsPane(gSavedPerAccountSettings.getBOOL("ConversationsListPaneCollapsed")); - LLAvatarNameCache::addUseDisplayNamesCallback(boost::bind(&LLIMConversation::processChatHistoryStyleUpdate)); - - if (! mMessagesPane->isCollapsed()) - { - S32 list_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth"); - LLRect list_size = mConversationsPane->getRect(); - S32 left_pad = mConversationsListPanel->getRect().mLeft; - list_size.mRight = list_size.mLeft + list_width - left_pad; - - mConversationsPane->handleReshape(list_size, TRUE); - } - - // Init the sort order now that the root had been created - setSortOrder(LLConversationSort(gSavedSettings.getU32("ConversationSortOrder"))); - - mInitialized = true; - - // Add callbacks: - // We'll take care of view updates on idle - gIdleCallbacks.addFunction(idle, this); - // When display name option change, we need to reload all participant names - LLAvatarNameCache::addUseDisplayNamesCallback(boost::bind(&LLIMFloaterContainer::processParticipantsStyleUpdate, this)); - - return TRUE; -} - -void LLIMFloaterContainer::onOpen(const LLSD& key) -{ - LLMultiFloater::onOpen(key); - openNearbyChat(); -} - -// virtual -void LLIMFloaterContainer::addFloater(LLFloater* floaterp, - BOOL select_added_floater, - LLTabContainer::eInsertionPoint insertion_point) -{ - if(!floaterp) return; - - // already here - if (floaterp->getHost() == this) - { - openFloater(floaterp->getKey()); - return; - } - - // Make sure the message panel is open when adding a floater or it stays mysteriously hidden - collapseMessagesPane(false); - - // Add the floater - LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point); - - LLUUID session_id = floaterp->getKey(); - - LLIconCtrl* icon = 0; - - if(gAgent.isInGroup(session_id, TRUE)) - { - LLGroupIconCtrl::Params icon_params; - icon_params.group_id = session_id; - icon = LLUICtrlFactory::instance().create(icon_params); - - mSessions[session_id] = floaterp; - floaterp->mCloseSignal.connect(boost::bind(&LLIMFloaterContainer::onCloseFloater, this, session_id)); - } - else - { LLUUID avatar_id = session_id.notNull()? - LLIMModel::getInstance()->getOtherParticipantID(session_id) : LLUUID(); - - LLAvatarIconCtrl::Params icon_params; - icon_params.avatar_id = avatar_id; - icon = LLUICtrlFactory::instance().create(icon_params); - - mSessions[session_id] = floaterp; - floaterp->mCloseSignal.connect(boost::bind(&LLIMFloaterContainer::onCloseFloater, this, session_id)); - } - - // forced resize of the floater - LLRect wrapper_rect = this->mTabContainer->getLocalRect(); - floaterp->setRect(wrapper_rect); - - mTabContainer->setTabImage(floaterp, icon); -} - - -void LLIMFloaterContainer::onCloseFloater(LLUUID& id) -{ - mSessions.erase(id); - setFocus(TRUE); -} - -// virtual -void LLIMFloaterContainer::computeResizeLimits(S32& new_min_width, S32& new_min_height) -{ - // possibly increase floater's minimum height according to children's minimums - for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) - { - LLFloater* floaterp = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); - if (floaterp) - { - new_min_height = llmax(new_min_height, floaterp->getMinHeight()); - } - } - - S32 conversations_pane_min_dim = mConversationsPane->getRelevantMinDim(); - S32 messages_pane_min_dim = mMessagesPane->getRelevantMinDim(); - - // set floater's minimum width according to relevant minimal children's dimensionals - new_min_width = conversations_pane_min_dim + messages_pane_min_dim + LLPANEL_BORDER_WIDTH*2; -} - -void LLIMFloaterContainer::onNewMessageReceived(const LLSD& data) -{ - LLUUID session_id = data["session_id"].asUUID(); - LLFloater* floaterp = get_ptr_in_map(mSessions, session_id); - LLFloater* current_floater = LLMultiFloater::getActiveFloater(); - - if(floaterp && current_floater && floaterp != current_floater) - { - if(LLMultiFloater::isFloaterFlashing(floaterp)) - LLMultiFloater::setFloaterFlashing(floaterp, FALSE); - LLMultiFloater::setFloaterFlashing(floaterp, TRUE); - } -} - -void LLIMFloaterContainer::onExpandCollapseButtonClicked() -{ - if (mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed() - && gSavedPerAccountSettings.getBOOL("ConversationsExpandMessagePaneFirst")) - { - // Expand the messages pane from ultra minimized state - // if it was collapsed last in order. - collapseMessagesPane(false); - } - else - { - collapseConversationsPane(!mConversationsPane->isCollapsed()); - } - selectConversation(mSelectedSession); -} - -LLIMFloaterContainer* LLIMFloaterContainer::findInstance() -{ - return LLFloaterReg::findTypedInstance("im_container"); -} - -LLIMFloaterContainer* LLIMFloaterContainer::getInstance() -{ - return LLFloaterReg::getTypedInstance("im_container"); -} - -// Update all participants in the conversation lists -void LLIMFloaterContainer::processParticipantsStyleUpdate() -{ - // On each session in mConversationsItems - for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) - { - // Get the current session descriptors - LLConversationItem* session_model = it_session->second; - // Iterate through each model participant child - LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = session_model->getChildrenBegin(); - LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = session_model->getChildrenEnd(); - while (current_participant_model != end_participant_model) - { - LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); - // Get the avatar name for this participant id from the cache and update the model - participant_model->fetchAvatarName(); - // Next participant - current_participant_model++; - } - } -} - -// static -void LLIMFloaterContainer::idle(void* user_data) -{ - LLIMFloaterContainer* self = static_cast(user_data); - - // Update the distance to agent in the nearby chat session if required - // Note: it makes no sense of course to update the distance in other session - if (self->mConversationViewModel.getSorter().getSortOrderParticipants() == LLConversationFilter::SO_DISTANCE) - { - self->setNearbyDistances(); - } - self->mConversationsRoot->update(); -} - -bool LLIMFloaterContainer::onConversationModelEvent(const LLSD& event) -{ - // For debug only - //std::ostringstream llsd_value; - //llsd_value << LLSDOStreamer(event) << std::endl; - //llinfos << "LLIMFloaterContainer::onConversationModelEvent, event = " << llsd_value.str() << llendl; - // end debug - - // Note: In conversations, the model is not responsible for creating the view, which is a good thing. This means that - // the model could change substantially and the view could echo only a portion of this model (though currently the - // conversation view does echo the conversation model 1 to 1). - // Consequently, the participant views need to be created either by the session view or by the container panel. - // For the moment, we create them here, at the container level, to conform to the pattern implemented in llinventorypanel.cpp - // (see LLInventoryPanel::buildNewViews()). - - std::string type = event.get("type").asString(); - LLUUID session_id = event.get("session_uuid").asUUID(); - LLUUID participant_id = event.get("participant_uuid").asUUID(); - - LLConversationViewSession* session_view = dynamic_cast(get_ptr_in_map(mConversationsWidgets,session_id)); - if (!session_view) - { - // We skip events that are not associated with a session - return false; - } - LLConversationViewParticipant* participant_view = session_view->findParticipant(participant_id); - LLIMConversation *conversation_floater = (session_id.isNull() ? (LLIMConversation*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLIMConversation*)(LLIMFloater::findInstance(session_id))); - - if (type == "remove_participant") - { - // Remove a participant view from the hierarchical conversation list - if (participant_view) - { - session_view->extractItem(participant_view); - delete participant_view; - session_view->refresh(); - mConversationsRoot->arrangeAll(); - } - // Remove a participant view from the conversation floater - if (conversation_floater) - { - conversation_floater->removeConversationViewParticipant(participant_id); - } - } - else if (type == "add_participant") - { - LLConversationItemSession* session_model = dynamic_cast(mConversationsItems[session_id]); - LLConversationItemParticipant* participant_model = (session_model ? session_model->findParticipant(participant_id) : NULL); - if (!participant_view && session_model && participant_model) - { - LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(session_id); - if (session_id.isNull() || (im_sessionp && !im_sessionp->isP2PSessionType())) - { - participant_view = createConversationViewParticipant(participant_model); - participant_view->addToFolder(session_view); - participant_view->setVisible(TRUE); - } - } - // Add a participant view to the conversation floater - if (conversation_floater && participant_model) - { - conversation_floater->addConversationViewParticipant(participant_model); - } - } - else if (type == "update_participant") - { - // Update the participant view in the hierarchical conversation list - if (participant_view) - { - participant_view->refresh(); - } - // Update the participant view in the conversation floater - if (conversation_floater) - { - conversation_floater->updateConversationViewParticipant(participant_id); - } - } - else if (type == "update_session") - { - session_view->refresh(); - if (conversation_floater) - { - conversation_floater->refreshConversation(); - } - } - - mConversationViewModel.requestSortAll(); - mConversationsRoot->arrangeAll(); - - return false; -} - -void LLIMFloaterContainer::draw() -{ - if (mTabContainer->getTabCount() == 0) - { - // Do not close the container when every conversation is torn off because the user - // still needs the conversation list. Simply collapse the message pane in that case. - collapseMessagesPane(true); - } - LLFloater::draw(); -} - -void LLIMFloaterContainer::tabClose() -{ - if (mTabContainer->getTabCount() == 0) - { - // Do not close the container when every conversation is torn off because the user - // still needs the conversation list. Simply collapse the message pane in that case. - collapseMessagesPane(true); - } -} - -void LLIMFloaterContainer::setVisible(BOOL visible) -{ LLNearbyChat* nearby_chat; - if (visible) - { - // Make sure we have the Nearby Chat present when showing the conversation container - nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if (nearby_chat == NULL) - { - // If not found, force the creation of the nearby chat conversation panel - // *TODO: find a way to move this to XML as a default panel or something like that - LLSD name("nearby_chat"); - LLFloaterReg::toggleInstanceOrBringToFront(name); - } - openNearbyChat(); - } - - nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if (nearby_chat) - { - LLIMConversation::addToHost(LLUUID()); - } - - // We need to show/hide all the associated conversations that have been torn off - // (and therefore, are not longer managed by the multifloater), - // so that they show/hide with the conversations manager. - conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); - for (;widget_it != mConversationsWidgets.end(); ++widget_it) - { - LLConversationViewSession* widget = dynamic_cast(widget_it->second); - if (widget) - { - widget->setVisibleIfDetached(visible); - } - } - - // Now, do the normal multifloater show/hide - LLMultiFloater::setVisible(visible); - -} - -void LLIMFloaterContainer::collapseMessagesPane(bool collapse) -{ - if (mMessagesPane->isCollapsed() == collapse) - { - return; - } - - if (collapse) - { - // Save the messages pane width before collapsing it. - gSavedPerAccountSettings.setS32("ConversationsMessagePaneWidth", mMessagesPane->getRect().getWidth()); - - // Save the order in which the panels are closed to reverse user's last action. - gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", mConversationsPane->isCollapsed()); - } - - // Save left pane rectangle before collapsing/expanding right pane. - LLRect prevRect = mConversationsPane->getRect(); - - // Show/hide the messages pane. - mConversationsStack->collapsePanel(mMessagesPane, collapse); - - if (!collapse) - { - // Make sure layout is updated before resizing conversation pane. - mConversationsStack->updateLayout(); - } - - updateState(collapse, gSavedPerAccountSettings.getS32("ConversationsMessagePaneWidth")); - if (!collapse) - { - // Restore conversation's pane previous width after expanding messages pane. - mConversationsPane->setTargetDim(prevRect.getWidth()); - } -} -void LLIMFloaterContainer::collapseConversationsPane(bool collapse) -{ - if (mConversationsPane->isCollapsed() == collapse) - { - return; - } - - LLView* button_panel = getChild("conversations_pane_buttons_expanded"); - button_panel->setVisible(!collapse); - mExpandCollapseBtn->setImageOverlay(getString(collapse ? "expand_icon" : "collapse_icon")); - - if (collapse) - { - // Save the conversations pane width before collapsing it. - gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", mConversationsPane->getRect().getWidth()); - - // Save the order in which the panels are closed to reverse user's last action. - gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", !mMessagesPane->isCollapsed()); - } - - mConversationsStack->collapsePanel(mConversationsPane, collapse); - - S32 collapsed_width = mConversationsPane->getMinDim(); - updateState(collapse, gSavedPerAccountSettings.getS32("ConversationsListPaneWidth") - collapsed_width); - - for (conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); - widget_it != mConversationsWidgets.end(); ++widget_it) - { - LLConversationViewSession* widget = dynamic_cast(widget_it->second); - if (widget) - { - widget->toggleMinimizedMode(collapse); - - // force closing all open conversations when collapsing to minimized state - if (collapse) - { - widget->setOpen(false); - } -} - } -} - -void LLIMFloaterContainer::updateState(bool collapse, S32 delta_width) -{ - LLRect floater_rect = getRect(); - floater_rect.mRight += ((collapse ? -1 : 1) * delta_width); - - // Set by_user = true so that reshaped rect is saved in user_settings. - setShape(floater_rect, true); - - updateResizeLimits(); - - bool is_left_pane_expanded = !mConversationsPane->isCollapsed(); - bool is_right_pane_expanded = !mMessagesPane->isCollapsed(); - - setCanResize(is_left_pane_expanded || is_right_pane_expanded); - setCanMinimize(is_left_pane_expanded || is_right_pane_expanded); - - // force set correct size for the title after show/hide minimize button - LLRect cur_rect = getRect(); - LLRect force_rect = cur_rect; - force_rect.mRight = cur_rect.mRight + 1; - setRect(force_rect); - setRect(cur_rect); - - // restore floater's resize limits (prevent collapse when left panel is expanded) - if (is_left_pane_expanded && !is_right_pane_expanded) - { - S32 expanded_min_size = mConversationsPane->getExpandedMinDim(); - setResizeLimits(expanded_min_size, expanded_min_size); - } - -} - -void LLIMFloaterContainer::onAddButtonClicked() -{ - LLView * button = findChild("conversations_pane_buttons_expanded")->findChild("add_btn"); - LLFloater* root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLIMFloaterContainer::onAvatarPicked, this, _1), TRUE, TRUE, TRUE, root_floater->getName(), button); - - if (picker && root_floater) - { - root_floater->addDependentFloater(picker); - } -} - -void LLIMFloaterContainer::onAvatarPicked(const uuid_vec_t& ids) -{ - if (ids.size() == 1) - { - LLAvatarActions::startIM(ids.back()); - } - else - { - LLAvatarActions::startConference(ids); - } -} - -void LLIMFloaterContainer::onCustomAction(const LLSD& userdata) -{ - std::string command = userdata.asString(); - - if ("sort_sessions_by_type" == command) - { - setSortOrderSessions(LLConversationFilter::SO_SESSION_TYPE); - } - if ("sort_sessions_by_name" == command) - { - setSortOrderSessions(LLConversationFilter::SO_NAME); - } - if ("sort_sessions_by_recent" == command) - { - setSortOrderSessions(LLConversationFilter::SO_DATE); - } - if ("sort_participants_by_name" == command) - { - setSortOrderParticipants(LLConversationFilter::SO_NAME); - } - if ("sort_participants_by_recent" == command) - { - setSortOrderParticipants(LLConversationFilter::SO_DATE); - } - if ("sort_participants_by_distance" == command) - { - setSortOrderParticipants(LLConversationFilter::SO_DISTANCE); - } - if ("chat_preferences" == command) - { - LLFloaterPreference* floater_prefs = LLFloaterReg::showTypedInstance("preferences"); - if (floater_prefs) - { - LLTabContainer* tab_container = floater_prefs->getChild("pref core"); - LLPanel* chat_panel = tab_container->getPanelByName("chat"); - if (tab_container && chat_panel) - { - tab_container->selectTabPanel(chat_panel); - } - } - } -} - -BOOL LLIMFloaterContainer::isActionChecked(const LLSD& userdata) -{ - LLConversationSort order = mConversationViewModel.getSorter(); - std::string command = userdata.asString(); - if ("sort_sessions_by_type" == command) - { - return (order.getSortOrderSessions() == LLConversationFilter::SO_SESSION_TYPE); - } - if ("sort_sessions_by_name" == command) - { - return (order.getSortOrderSessions() == LLConversationFilter::SO_NAME); - } - if ("sort_sessions_by_recent" == command) - { - return (order.getSortOrderSessions() == LLConversationFilter::SO_DATE); - } - if ("sort_participants_by_name" == command) - { - return (order.getSortOrderParticipants() == LLConversationFilter::SO_NAME); - } - if ("sort_participants_by_recent" == command) - { - return (order.getSortOrderParticipants() == LLConversationFilter::SO_DATE); - } - if ("sort_participants_by_distance" == command) - { - return (order.getSortOrderParticipants() == LLConversationFilter::SO_DISTANCE); - } - - return FALSE; -} - -void LLIMFloaterContainer::setSortOrderSessions(const LLConversationFilter::ESortOrderType order) -{ - LLConversationSort old_order = mConversationViewModel.getSorter(); - if (order != old_order.getSortOrderSessions()) - { - old_order.setSortOrderSessions(order); - setSortOrder(old_order); - } -} - -void LLIMFloaterContainer::setSortOrderParticipants(const LLConversationFilter::ESortOrderType order) -{ - LLConversationSort old_order = mConversationViewModel.getSorter(); - if (order != old_order.getSortOrderParticipants()) - { - old_order.setSortOrderParticipants(order); - setSortOrder(old_order); - } -} - -void LLIMFloaterContainer::setSortOrder(const LLConversationSort& order) -{ - mConversationViewModel.setSorter(order); - mConversationsRoot->arrangeAll(); - // try to keep selection onscreen, even if it wasn't to start with - mConversationsRoot->scrollToShowSelection(); - - // Notify all conversation (torn off or not) of the change to the sort order - // Note: For the moment, the sort order is *unique* across all conversations. That might change in the future. - for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) - { - LLUUID session_id = it_session->first; - LLIMConversation *conversation_floater = (session_id.isNull() ? (LLIMConversation*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLIMConversation*)(LLIMFloater::findInstance(session_id))); - if (conversation_floater) - { - conversation_floater->setSortOrder(order); - } - } - - gSavedSettings.setU32("ConversationSortOrder", (U32)order); -} - -void LLIMFloaterContainer::getSelectedUUIDs(uuid_vec_t& selected_uuids) -{ - const std::set selectedItems = mConversationsRoot->getSelectionList(); - - std::set::const_iterator it = selectedItems.begin(); - const std::set::const_iterator it_end = selectedItems.end(); - LLConversationItem * conversationItem; - - for (; it != it_end; ++it) - { - conversationItem = static_cast((*it)->getViewModelItem()); - selected_uuids.push_back(conversationItem->getUUID()); - } -} - -const LLConversationItem * LLIMFloaterContainer::getCurSelectedViewModelItem() -{ - LLConversationItem * conversationItem = NULL; - - if(mConversationsRoot && - mConversationsRoot->getCurSelectedItem() && - mConversationsRoot->getCurSelectedItem()->getViewModelItem()) - { - conversationItem = static_cast(mConversationsRoot->getCurSelectedItem()->getViewModelItem()); - } - - return conversationItem; -} - -void LLIMFloaterContainer::getParticipantUUIDs(uuid_vec_t& selected_uuids) -{ - //Find the conversation floater associated with the selected id - const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); - - if(conversationItem->getType() == LLConversationItem::CONV_PARTICIPANT) - { - getSelectedUUIDs(selected_uuids); - } - //When a one-on-one conversation exists, retrieve the participant id from the conversation floater - else if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1) - { - LLIMFloater *conversationFloater = LLIMFloater::findInstance(conversationItem->getUUID()); - LLUUID participantID = conversationFloater->getOtherParticipantUUID(); - selected_uuids.push_back(participantID); - } -} - -void LLIMFloaterContainer::doToParticipants(const std::string& command, uuid_vec_t& selectedIDS) -{ - if(selectedIDS.size() > 0) - { - const LLUUID& userID = selectedIDS.front(); - if(gAgent.getID() != userID) - { - if ("view_profile" == command) - { - LLAvatarActions::showProfile(userID); - } - else if("im" == command) - { - LLAvatarActions::startIM(userID); - } - else if("offer_teleport" == command) - { - LLAvatarActions::offerTeleport(selectedIDS); - } - else if("voice_call" == command) - { - LLAvatarActions::startCall(userID); - } - else if("chat_history" == command) - { - LLAvatarActions::viewChatHistory(userID); - } - else if("add_friend" == command) - { - LLAvatarActions::requestFriendshipDialog(userID); - } - else if("remove_friend" == command) - { - LLAvatarActions::removeFriendDialog(userID); - } - else if("invite_to_group" == command) - { - LLAvatarActions::inviteToGroup(userID); - } - else if("map" == command) - { - LLAvatarActions::showOnMap(userID); - } - else if("share" == command) - { - LLAvatarActions::share(userID); - } - else if("pay" == command) - { - LLAvatarActions::pay(userID); - } - else if("block_unblock" == command) - { - LLAvatarActions::toggleBlock(userID); - } - else if("selected" == command || "mute_all" == command || "unmute_all" == command) - { - moderateVoice(command, userID); - } - else if ("toggle_allow_text_chat" == command) - { - toggleAllowTextChat(userID); - } - } - } -} - -void LLIMFloaterContainer::doToSelectedConversation(const std::string& command, uuid_vec_t& selectedIDS) -{ - //Find the conversation floater associated with the selected id - const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); - LLIMFloater *conversationFloater = LLIMFloater::findInstance(conversationItem->getUUID()); - - if(conversationFloater) - { - //Close the selected conversation - if("close_conversation" == command) - { - LLFloater::onClickClose(conversationFloater); - } - else if("open_voice_conversation" == command) - { - gIMMgr->startCall(conversationItem->getUUID()); - } - else if("disconnect_from_voice" == command) - { - gIMMgr->endCall(conversationItem->getUUID()); - } - else if("chat_history" == command) - { - const LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(conversationItem->getUUID()); - - if (NULL != session) - { - const LLUUID session_id = session->isOutgoingAdHoc() ? session->generateOutgouigAdHocHash() : session->mSessionID; - LLFloaterReg::showInstance("preview_conversation", session_id, true); - } - } - else - { - doToParticipants(command, selectedIDS); - } - } -} - -void LLIMFloaterContainer::doToSelected(const LLSD& userdata) -{ - std::string command = userdata.asString(); - const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); - uuid_vec_t selected_uuids; - - if(conversationItem != NULL) - { - getParticipantUUIDs(selected_uuids); - - if(conversationItem->getType() == LLConversationItem::CONV_PARTICIPANT) - { - doToParticipants(command, selected_uuids); - } - else - { - doToSelectedConversation(command, selected_uuids); - } - } -} - -void LLIMFloaterContainer::doToSelectedGroup(const LLSD& userdata) -{ - std::string action = userdata.asString(); - LLUUID selected_group = getCurSelectedViewModelItem()->getUUID(); - - if (action == "group_profile") - { - LLGroupActions::show(selected_group); - } - else if (action == "activate_group") - { - LLGroupActions::activate(selected_group); - } - else if (action == "leave_group") - { - LLGroupActions::leave(selected_group); - } -} - -bool LLIMFloaterContainer::enableContextMenuItem(const LLSD& userdata) -{ - std::string item = userdata.asString(); - uuid_vec_t uuids; - getParticipantUUIDs(uuids); - - if(item == std::string("can_activate_group")) - { - LLUUID selected_group_id = getCurSelectedViewModelItem()->getUUID(); - return gAgent.getGroupID() != selected_group_id; - } - - if(uuids.size() <= 0) - { - return false; - } - - // Note: can_block and can_delete is used only for one person selected menu - // so we don't need to go over all uuids. - - if (item == std::string("can_block")) - { - const LLUUID& id = uuids.front(); - return LLAvatarActions::canBlock(id); - } - else if (item == std::string("can_add")) - { - // We can add friends if: - // - there are selected people - // - and there are no friends among selection yet. - - //EXT-7389 - disable for more than 1 - if(uuids.size() > 1) - { - return false; - } - - bool result = true; - - uuid_vec_t::const_iterator - id = uuids.begin(), - uuids_end = uuids.end(); - - for (;id != uuids_end; ++id) - { - if ( LLAvatarActions::isFriend(*id) ) - { - result = false; - break; - } - } - - return result; - } - else if (item == std::string("can_delete")) - { - // We can remove friends if: - // - there are selected people - // - and there are only friends among selection. - - bool result = (uuids.size() > 0); - - uuid_vec_t::const_iterator - id = uuids.begin(), - uuids_end = uuids.end(); - - for (;id != uuids_end; ++id) - { - if ( !LLAvatarActions::isFriend(*id) ) - { - result = false; - break; - } - } - - return result; - } - else if (item == std::string("can_call")) - { - return LLAvatarActions::canCall(); - } - else if (item == std::string("can_show_on_map")) - { - const LLUUID& id = uuids.front(); - - return (LLAvatarTracker::instance().isBuddyOnline(id) && is_agent_mappable(id)) - || gAgent.isGodlike(); - } - else if(item == std::string("can_offer_teleport")) - { - return LLAvatarActions::canOfferTeleport(uuids); - } - else if("can_moderate_voice" == item || "can_allow_text_chat" == item || "can_mute" == item || "can_unmute" == item) - { - return enableModerateContextMenuItem(item); - } - - return false; -} - -bool LLIMFloaterContainer::checkContextMenuItem(const LLSD& userdata) -{ - std::string item = userdata.asString(); - uuid_vec_t mUUIDs; - getParticipantUUIDs(mUUIDs); - - if(mUUIDs.size() > 0 ) - { - if ("is_blocked" == item) - { - return LLAvatarActions::isBlocked(mUUIDs.front()); - } - else if ("is_allowed_text_chat" == item) - { - const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); - - if (NULL != speakerp) - { - return !speakerp->mModeratorMutedText; - } - } - } - - return false; -} - -void LLIMFloaterContainer::showConversation(const LLUUID& session_id) -{ - setVisibleAndFrontmost(false); - selectConversation(session_id); -} - -// Will select only the conversation item -void LLIMFloaterContainer::selectConversation(const LLUUID& session_id) -{ - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,session_id); - if (widget) - { - (widget->getRoot())->setSelection(widget, FALSE, FALSE); - } -} - -// Synchronous select the conversation item and the conversation floater -BOOL LLIMFloaterContainer::selectConversationPair(const LLUUID& session_id, bool select_widget) -{ - BOOL handled = TRUE; - - /* widget processing */ - if (select_widget) - { - LLFolderViewItem* widget = mConversationsWidgets[session_id]; - if (widget && widget->getParentFolder()) - { - widget->getParentFolder()->setSelection(widget, FALSE, FALSE); - } - } - - /* floater processing */ - - if (session_id != getSelectedSession()) - { - // Store the active session - setSelectedSession(session_id); - - LLIMConversation* session_floater = LLIMConversation::getConversation(session_id); - - if (session_floater->getHost()) - { - // Always expand the message pane if the panel is hosted by the container - collapseMessagesPane(false); - // Switch to the conversation floater that is being selected - selectFloater(session_floater); - } - - // Set the focus on the selected floater - if (!session_floater->hasFocus()) - { - session_floater->setFocus(TRUE); - } - } - - return handled; -} - -void LLIMFloaterContainer::setTimeNow(const LLUUID& session_id, const LLUUID& participant_id) -{ - LLConversationItemSession* item = dynamic_cast(get_ptr_in_map(mConversationsItems,session_id)); - if (item) - { - item->setTimeNow(participant_id); - mConversationViewModel.requestSortAll(); - mConversationsRoot->arrangeAll(); - } -} - -void LLIMFloaterContainer::setNearbyDistances() -{ - // Get the nearby chat session: that's the one with uuid nul - LLConversationItemSession* item = dynamic_cast(get_ptr_in_map(mConversationsItems,LLUUID())); - if (item) - { - // Get the positions of the nearby avatars and their ids - std::vector positions; - uuid_vec_t avatar_ids; - LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); - // Get the position of the agent - const LLVector3d& me_pos = gAgent.getPositionGlobal(); - // For each nearby avatar, compute and update the distance - int avatar_count = positions.size(); - for (int i = 0; i < avatar_count; i++) - { - F64 dist = dist_vec_squared(positions[i], me_pos); - item->setDistance(avatar_ids[i],dist); - } - // Also does it for the agent itself - item->setDistance(gAgent.getID(),0.0f); - // Request resort - mConversationViewModel.requestSortAll(); - mConversationsRoot->arrangeAll(); - } -} - -LLConversationItem* LLIMFloaterContainer::addConversationListItem(const LLUUID& uuid, bool isWidgetSelected /*= false*/) -{ - bool is_nearby_chat = uuid.isNull(); - - // Stores the display name for the conversation line item - std::string display_name = is_nearby_chat ? LLTrans::getString("NearbyChatLabel") : LLIMModel::instance().getName(uuid); - - // Check if the item is not already in the list, exit (nothing to do) - // Note: this happens often, when reattaching a torn off conversation for instance - conversations_items_map::iterator item_it = mConversationsItems.find(uuid); - if (item_it != mConversationsItems.end()) - { - return item_it->second; - } - - // Remove the conversation item that might exist already: it'll be recreated anew further down anyway - // and nothing wrong will happen removing it if it doesn't exist - removeConversationListItem(uuid,false); - - // Create a conversation session model - LLConversationItemSession* item = NULL; - LLSpeakerMgr* speaker_manager = (is_nearby_chat ? (LLSpeakerMgr*)(LLLocalSpeakerMgr::getInstance()) : LLIMModel::getInstance()->getSpeakerManager(uuid)); - if (speaker_manager) - { - item = new LLParticipantList(speaker_manager, getRootViewModel()); - } - if (!item) - { - llwarns << "Couldn't create conversation session item : " << display_name << llendl; - return NULL; - } - item->renameItem(display_name); - item->updateParticipantName(NULL); - - mConversationsItems[uuid] = item; - - // Create a widget from it - LLConversationViewSession* widget = createConversationItemWidget(item); - mConversationsWidgets[uuid] = widget; - - // Add a new conversation widget to the root folder of the folder view - widget->addToFolder(mConversationsRoot); - widget->requestArrange(); - - LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(uuid); - - // Create the participants widgets now - // Note: usually, we do not get an updated avatar list at that point - if (uuid.isNull() || im_sessionp && !im_sessionp->isP2PSessionType()) - { - LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); - LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); - while (current_participant_model != end_participant_model) - { - LLConversationItem* participant_model = dynamic_cast(*current_participant_model); - LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); - participant_view->addToFolder(widget); - current_participant_model++; - } - } - // Do that too for the conversation dialog - LLIMConversation *conversation_floater = (uuid.isNull() ? (LLIMConversation*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLIMConversation*)(LLIMFloater::findInstance(uuid))); - if (conversation_floater) - { - conversation_floater->buildConversationViewParticipant(); - } - - // set the widget to minimized mode if conversations pane is collapsed - widget->toggleMinimizedMode(mConversationsPane->isCollapsed()); - - if (isWidgetSelected) - { - selectConversation(uuid); - // scroll to newly added item - mConversationsRoot->scrollToShowSelection(); - } - - return item; -} - -bool LLIMFloaterContainer::removeConversationListItem(const LLUUID& uuid, bool change_focus) -{ - // Delete the widget and the associated conversation item - // Note : since the mConversationsItems is also the listener to the widget, deleting - // the widget will also delete its listener - bool isWidgetSelected = false; - LLFolderViewItem* new_selection = NULL; - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); - if (widget) - { - isWidgetSelected = widget->isSelected(); - new_selection = mConversationsRoot->getNextFromChild(widget); - if(new_selection == NULL) - { - new_selection = mConversationsRoot->getPreviousFromChild(widget); - } - widget->destroyView(); - } - - // Suppress the conversation items and widgets from their respective maps - mConversationsItems.erase(uuid); - mConversationsWidgets.erase(uuid); - - // Don't let the focus fall IW, select and refocus on the first conversation in the list - if (change_focus) - { - setFocus(TRUE); - if(new_selection != NULL) - { - LLConversationItem* vmi = dynamic_cast(new_selection->getViewModelItem()); - if(vmi != NULL) - { - selectConversation(vmi->getUUID()); - } - } - } - return isWidgetSelected; -} - -LLConversationViewSession* LLIMFloaterContainer::createConversationItemWidget(LLConversationItem* item) -{ - LLConversationViewSession::Params params; - - params.name = item->getDisplayName(); - params.root = mConversationsRoot; - params.listener = item; - params.tool_tip = params.name; - params.container = this; - - return LLUICtrlFactory::create(params); -} - -LLConversationViewParticipant* LLIMFloaterContainer::createConversationViewParticipant(LLConversationItem* item) -{ - LLConversationViewParticipant::Params params; - LLRect panel_rect = mConversationsListPanel->getRect(); - - params.name = item->getDisplayName(); - params.root = mConversationsRoot; - params.listener = item; - - //24 is the the current hight of an item (itemHeight) loaded from conversation_view_participant.xml. - params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); - params.tool_tip = params.name; - params.participant_id = item->getUUID(); - params.folder_indentation = 42; - - return LLUICtrlFactory::create(params); -} - -bool LLIMFloaterContainer::enableModerateContextMenuItem(const std::string& userdata) -{ - // only group moderators can perform actions related to this "enable callback" - if (!isGroupModerator()) - { - return false; - } - - LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); - if (NULL == speakerp) - { - return false; - } - - bool voice_channel = speakerp->isInVoiceChannel(); - - if ("can_moderate_voice" == userdata) - { - return voice_channel; - } - else if ("can_mute" == userdata) - { - return voice_channel && !isMuted(getCurSelectedViewModelItem()->getUUID()); - } - else if ("can_unmute" == userdata) - { - return voice_channel && isMuted(getCurSelectedViewModelItem()->getUUID()); - } - - // The last invoke is used to check whether the "can_allow_text_chat" will enabled - return LLVoiceClient::getInstance()->isParticipantAvatar(getCurSelectedViewModelItem()->getUUID()); -} - -bool LLIMFloaterContainer::isGroupModerator() -{ - LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); - if (NULL == speaker_manager) - { - llwarns << "Speaker manager is missing" << llendl; - return false; - } - - // Is session a group call/chat? - if(gAgent.isInGroup(speaker_manager->getSessionID())) - { - LLSpeaker * speaker = speaker_manager->findSpeaker(gAgentID).get(); - - // Is agent a moderator? - return speaker && speaker->mIsModerator; - } - - return false; -} - -void LLIMFloaterContainer::moderateVoice(const std::string& command, const LLUUID& userID) -{ - if (!gAgent.getRegion()) return; - - if (command.compare("selected")) - { - moderateVoiceAllParticipants(command.compare("mute_all")); - } - else - { - moderateVoiceParticipant(userID, isMuted(userID)); - } -} - -bool LLIMFloaterContainer::isMuted(const LLUUID& avatar_id) -{ - const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); - return NULL == speakerp ? true : speakerp->mStatus == LLSpeaker::STATUS_MUTED; -} - -void LLIMFloaterContainer::moderateVoiceAllParticipants(bool unmute) -{ - LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); - - if (NULL != speaker_managerp) - { - if (!unmute) - { - LLSD payload; - payload["session_id"] = speaker_managerp->getSessionID(); - LLNotificationsUtil::add("ConfirmMuteAll", LLSD(), payload, confirmMuteAllCallback); - return; - } - - speaker_managerp->moderateVoiceAllParticipants(unmute); - } -} - -// static -void LLIMFloaterContainer::confirmMuteAllCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - // if Cancel pressed - if (option == 1) - { - return; - } - - const LLSD& payload = notification["payload"]; - const LLUUID& session_id = payload["session_id"]; - - LLIMSpeakerMgr * speaker_manager = dynamic_cast ( - LLIMModel::getInstance()->getSpeakerManager(session_id)); - if (speaker_manager) - { - speaker_manager->moderateVoiceAllParticipants(false); - } - - return; -} - -void LLIMFloaterContainer::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute) -{ - LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); - - if (NULL != speaker_managerp) - { - speaker_managerp->moderateVoiceParticipant(avatar_id, unmute); - } -} - -LLSpeakerMgr * LLIMFloaterContainer::getSpeakerMgrForSelectedParticipant() -{ - LLFolderViewItem * selected_folder_itemp = mConversationsRoot->getCurSelectedItem(); - if (NULL == selected_folder_itemp) - { - llwarns << "Current selected item is null" << llendl; - return NULL; - } - - LLFolderViewFolder * conversation_itemp = selected_folder_itemp->getParentFolder(); - - conversations_widgets_map::const_iterator iter = mConversationsWidgets.begin(); - conversations_widgets_map::const_iterator end = mConversationsWidgets.end(); - const LLUUID * conversation_uuidp = NULL; - while(iter != end) - { - if (iter->second == conversation_itemp) - { - conversation_uuidp = &iter->first; - break; - } - ++iter; - } - if (NULL == conversation_uuidp) - { - llwarns << "Cannot find conversation item widget" << llendl; - return NULL; - } - - return conversation_uuidp->isNull() ? (LLSpeakerMgr *)LLLocalSpeakerMgr::getInstance() - : LLIMModel::getInstance()->getSpeakerManager(*conversation_uuidp); -} - -LLSpeaker * LLIMFloaterContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp) -{ - if (NULL == speaker_managerp) - { - llwarns << "Speaker manager is missing" << llendl; - return NULL; - } - - const LLConversationItem * participant_itemp = getCurSelectedViewModelItem(); - if (NULL == participant_itemp) - { - llwarns << "Cannot evaluate current selected view model item" << llendl; - return NULL; - } - - return speaker_managerp->findSpeaker(participant_itemp->getUUID()); -} - -void LLIMFloaterContainer::toggleAllowTextChat(const LLUUID& participant_uuid) -{ - LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); - if (NULL != speaker_managerp) - { - speaker_managerp->toggleAllowTextChat(participant_uuid); - } -} - -void LLIMFloaterContainer::openNearbyChat() -{ - // If there's only one conversation in the container and that conversation is the nearby chat - //(which it should be...), open it so to make the list of participants visible. This happens to be the most common case when opening the Chat floater. - if(mConversationsItems.size() == 1) - { - LLConversationViewSession* nearby_chat = dynamic_cast(mConversationsWidgets[LLUUID()]); - if (nearby_chat) - { - nearby_chat->setOpen(TRUE); - } - } -} - -void LLIMFloaterContainer::onNearbyChatClosed() -{ - // If nearby chat is the only remaining conversation and it is closed, close whole conversation floater as well - if (mConversationsItems.size() == 1) - closeFloater(); -} - -// EOF diff --git a/indra/newview/llimfloatercontainer.h b/indra/newview/llimfloatercontainer.h deleted file mode 100644 index e42ed053cb..0000000000 --- a/indra/newview/llimfloatercontainer.h +++ /dev/null @@ -1,178 +0,0 @@ -/** - * @file llimfloatercontainer.h - * @brief Multifloater containing active IM sessions in separate tab container tabs - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLIMFLOATERCONTAINER_H -#define LL_LLIMFLOATERCONTAINER_H - -#include -#include - -#include "llimview.h" -#include "llevents.h" -#include "llfloater.h" -#include "llmultifloater.h" -#include "llavatarpropertiesprocessor.h" -#include "llgroupmgr.h" -#include "lltrans.h" -#include "llconversationmodel.h" -#include "llconversationview.h" - -class LLButton; -class LLLayoutPanel; -class LLLayoutStack; -class LLTabContainer; -class LLIMFloaterContainer; -class LLSpeaker; -class LLSpeakerMgr; - -class LLIMFloaterContainer - : public LLMultiFloater - , public LLIMSessionObserver -{ -public: - LLIMFloaterContainer(const LLSD& seed); - virtual ~LLIMFloaterContainer(); - - /*virtual*/ BOOL postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void draw(); - /*virtual*/ void setVisible(BOOL visible); - void onCloseFloater(LLUUID& id); - - /*virtual*/ void addFloater(LLFloater* floaterp, - BOOL select_added_floater, - LLTabContainer::eInsertionPoint insertion_point = LLTabContainer::END); - - void showConversation(const LLUUID& session_id); - void selectConversation(const LLUUID& session_id); - BOOL selectConversationPair(const LLUUID& session_id, bool select_widget); - - /*virtual*/ void tabClose(); - - static LLFloater* getCurrentVoiceFloater(); - static LLIMFloaterContainer* findInstance(); - static LLIMFloaterContainer* getInstance(); - - static void onCurrentChannelChanged(const LLUUID& session_id); - - void collapseMessagesPane(bool collapse); - - // Callbacks - static void idle(void* user_data); - - // LLIMSessionObserver observe triggers - /*virtual*/ void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id); - /*virtual*/ void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id); - /*virtual*/ void sessionVoiceOrIMStarted(const LLUUID& session_id); - /*virtual*/ void sessionRemoved(const LLUUID& session_id); - /*virtual*/ void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id); - - LLConversationViewModel& getRootViewModel() { return mConversationViewModel; } - LLUUID getSelectedSession() { return mSelectedSession; } - void setSelectedSession(LLUUID sessionID) { mSelectedSession = sessionID; } - LLConversationItem* getSessionModel(const LLUUID& session_id) { return get_ptr_in_map(mConversationsItems,session_id); } - LLConversationSort& getSortOrder() { return mConversationViewModel.getSorter(); } - - void onNearbyChatClosed(); - -private: - typedef std::map avatarID_panel_map_t; - avatarID_panel_map_t mSessions; - boost::signals2::connection mNewMessageConnection; - - /*virtual*/ void computeResizeLimits(S32& new_min_width, S32& new_min_height); - - void onNewMessageReceived(const LLSD& data); - - void onExpandCollapseButtonClicked(); - void processParticipantsStyleUpdate(); - - void collapseConversationsPane(bool collapse); - - void updateState(bool collapse, S32 delta_width); - - void onAddButtonClicked(); - void onAvatarPicked(const uuid_vec_t& ids); - - BOOL isActionChecked(const LLSD& userdata); - void onCustomAction (const LLSD& userdata); - void setSortOrderSessions(const LLConversationFilter::ESortOrderType order); - void setSortOrderParticipants(const LLConversationFilter::ESortOrderType order); - void setSortOrder(const LLConversationSort& order); - - void getSelectedUUIDs(uuid_vec_t& selected_uuids); - const LLConversationItem * getCurSelectedViewModelItem(); - void getParticipantUUIDs(uuid_vec_t& selected_uuids); - void doToSelected(const LLSD& userdata); - void doToSelectedConversation(const std::string& command, uuid_vec_t& selectedIDS); - void doToParticipants(const std::string& item, uuid_vec_t& selectedIDS); - void doToSelectedGroup(const LLSD& userdata); - bool checkContextMenuItem(const LLSD& userdata); - bool enableContextMenuItem(const LLSD& userdata); - - static void confirmMuteAllCallback(const LLSD& notification, const LLSD& response); - bool enableModerateContextMenuItem(const std::string& userdata); - LLSpeaker * getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp); - LLSpeakerMgr * getSpeakerMgrForSelectedParticipant(); - bool isGroupModerator(); - bool isMuted(const LLUUID& avatar_id); - void moderateVoice(const std::string& command, const LLUUID& userID); - void moderateVoiceAllParticipants(bool unmute); - void moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute); - void toggleAllowTextChat(const LLUUID& participant_uuid); - void openNearbyChat(); - - LLButton* mExpandCollapseBtn; - LLLayoutPanel* mMessagesPane; - LLLayoutPanel* mConversationsPane; - LLLayoutStack* mConversationsStack; - - bool mInitialized; - - LLUUID mSelectedSession; - - // Conversation list implementation -public: - bool removeConversationListItem(const LLUUID& uuid, bool change_focus = true); - LLConversationItem* addConversationListItem(const LLUUID& uuid, bool isWidgetSelected = false); - void setTimeNow(const LLUUID& session_id, const LLUUID& participant_id); - void setNearbyDistances(); - -private: - LLConversationViewSession* createConversationItemWidget(LLConversationItem* item); - LLConversationViewParticipant* createConversationViewParticipant(LLConversationItem* item); - bool onConversationModelEvent(const LLSD& event); - - // Conversation list data - LLPanel* mConversationsListPanel; // This is the main widget we add conversation widget to - conversations_items_map mConversationsItems; - conversations_widgets_map mConversationsWidgets; - LLConversationViewModel mConversationViewModel; - LLFolderView* mConversationsRoot; - LLEventStream mConversationsEventStream; -}; - -#endif // LL_LLIMFLOATERCONTAINER_H diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index 0250af6a0e..c64ecdc47a 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -171,7 +171,7 @@ LLFloaterIMPanel::LLFloaterIMPanel(const std::string& session_label, // enable line history support for instant message bar mInputEditor->setEnableLineHistory(TRUE); - //*TODO we probably need the same "awaiting message" thing in LLIMFloater + //*TODO we probably need the same "awaiting message" thing in LLFloaterIMSession LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(mSessionUUID); if (!im_session) { diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 9f24a5372f..6712127750 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -41,15 +41,15 @@ #include "lltextutil.h" #include "lltrans.h" #include "lluictrlfactory.h" -#include "llimconversation.h" +#include "llfloaterimsessiontab.h" #include "llagent.h" #include "llagentui.h" #include "llappviewer.h" #include "llavatariconctrl.h" #include "llcallingcard.h" #include "llchat.h" -#include "llimfloater.h" -#include "llimfloatercontainer.h" +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" #include "llgroupiconctrl.h" #include "llmd5.h" #include "llmutelist.h" @@ -58,7 +58,7 @@ #include "llviewerwindow.h" #include "llnotifications.h" #include "llnotificationsutil.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llspeakers.h" //for LLIMSpeakerMgr #include "lltextbox.h" #include "lltoolbarview.h" @@ -109,7 +109,7 @@ static void on_avatar_name_cache_toast(const LLUUID& agent_id, args["FROM"] = av_name.getCompleteName(); args["FROM_ID"] = msg["from_id"]; args["SESSION_ID"] = msg["session_id"]; - LLNotificationsUtil::add("IMToast", args, LLSD(), boost::bind(&LLIMFloaterContainer::showConversation, LLIMFloaterContainer::getInstance(), msg["session_id"].asUUID())); + LLNotificationsUtil::add("IMToast", args, LLSD(), boost::bind(&LLFloaterIMContainer::showConversation, LLFloaterIMContainer::getInstance(), msg["session_id"].asUUID())); } void toast_callback(const LLSD& msg){ @@ -120,7 +120,7 @@ void toast_callback(const LLSD& msg){ } // Skip toasting if we have open window of IM with this session id - LLIMFloater* open_im_floater = LLIMFloater::findInstance(msg["session_id"]); + LLFloaterIMSession* open_im_floater = LLFloaterIMSession::findInstance(msg["session_id"]); if ( open_im_floater && open_im_floater->isInVisibleChain() @@ -160,7 +160,7 @@ void toast_callback(const LLSD& msg){ LLIMModel::LLIMModel() { - addNewMsgCallback(boost::bind(&LLIMFloater::newIMCallback, _1)); + addNewMsgCallback(boost::bind(&LLFloaterIMSession::newIMCallback, _1)); addNewMsgCallback(boost::bind(&toast_callback, _1)); } @@ -638,7 +638,7 @@ void LLIMModel::processSessionInitializedReply(const LLUUID& old_session_id, con mId2SessionMap[new_session_id] = session; } - LLIMFloater* im_floater = LLIMFloater::findInstance(old_session_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(old_session_id); if (im_floater) { im_floater->sessionInitReplyReceived(new_session_id); @@ -1387,7 +1387,7 @@ public: && LLIMModel::getInstance()->findIMSession(mSessionID)) { // TODO remove in 2010, for voice calls we do not open an IM window - //LLIMFloater::show(mSessionID); + //LLFloaterIMSession::show(mSessionID); } gIMMgr->clearPendingAgentListUpdates(mSessionID); @@ -1531,7 +1531,7 @@ LLIMMgr::onConfirmForceCloseError( //only 1 option really LLUUID session_id = notification["payload"]["session_id"]; - LLFloater* floater = LLIMFloater::findInstance(session_id); + LLFloater* floater = LLFloaterIMSession::findInstance(session_id); if ( floater ) { floater->closeFloater(FALSE); @@ -2397,7 +2397,7 @@ LLIMMgr::LLIMMgr() mPendingInvitations = LLSD::emptyMap(); mPendingAgentListUpdates = LLSD::emptyMap(); - LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLIMFloater::sRemoveTypingIndicator, _1)); + LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1)); } // Add a message to a session. @@ -2492,7 +2492,7 @@ void LLIMMgr::addSystemMessage(const LLUUID& session_id, const std::string& mess LLChat chat(message); chat.mSourceType = CHAT_SOURCE_SYSTEM; - LLNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); if (nearby_chat) { nearby_chat->addMessage(chat); @@ -2618,12 +2618,12 @@ LLUUID LLIMMgr::addSession( if (floater_id.notNull()) { - LLIMFloater* im_floater = LLIMFloater::findInstance(floater_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(floater_id); if (im_floater && im_floater->getStartConferenceInSameFloater()) { // The IM floater should be initialized with a new session_id - // so that it is found by that id when creating a chiclet in LLIMFloater::onIMChicletCreated, + // so that it is found by that id when creating a chiclet in LLFloaterIMSession::onIMChicletCreated, // and a new floater is not created. im_floater->initIMSession(session_id); } @@ -2841,7 +2841,7 @@ void LLIMMgr::clearPendingInvitation(const LLUUID& session_id) void LLIMMgr::processAgentListUpdates(const LLUUID& session_id, const LLSD& body) { - LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); if ( im_floater ) { im_floater->processAgentListUpdates(body); @@ -3115,7 +3115,7 @@ void LLIMMgr::processIMTypingStop(const LLIMInfo* im_info) void LLIMMgr::processIMTypingCore(const LLIMInfo* im_info, BOOL typing) { LLUUID session_id = computeSessionID(im_info->mIMType, im_info->mFromID); - LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); if ( im_floater ) { im_floater->processIMTyping(im_info, typing); @@ -3160,7 +3160,7 @@ public: speaker_mgr->updateSpeakers(gIMMgr->getPendingAgentListUpdates(session_id)); } - LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); if ( im_floater ) { if ( body.has("session_info") ) @@ -3254,7 +3254,7 @@ public: const LLSD& input) const { LLUUID session_id = input["body"]["session_id"].asUUID(); - LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); if ( im_floater ) { im_floater->processSessionUpdate(input["body"]["info"]); diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 8b04af71c7..ffac67557a 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -46,7 +46,7 @@ #include "llfriendcard.h" #include "llgesturemgr.h" #include "llgiveinventory.h" -#include "llimfloatercontainer.h" +#include "llfloaterimcontainer.h" #include "llimview.h" #include "llclipboard.h" #include "llinventorydefines.h" @@ -4683,7 +4683,7 @@ void LLCallingCardBridge::performAction(LLInventoryModel* model, std::string act LLUUID session_id = gIMMgr->addSession(callingcard_name, IM_NOTHING_SPECIAL, item->getCreatorUUID()); if (session_id != LLUUID::null) { - LLIMFloaterContainer::getInstance()->showConversation(session_id); + LLFloaterIMContainer::getInstance()->showConversation(session_id); } } } diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index dafc71b59c..7c717af840 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -39,7 +39,7 @@ #include "llfloatersidepanelcontainer.h" #include "llfolderview.h" #include "llfolderviewitem.h" -#include "llimfloatercontainer.h" +#include "llfloaterimcontainer.h" #include "llimview.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" @@ -1087,7 +1087,7 @@ bool LLInventoryPanel::beginIMSession() LLUUID session_id = gIMMgr->addSession(name, type, members[0], members); if (session_id != LLUUID::null) { - LLIMFloaterContainer::getInstance()->showConversation(session_id); + LLFloaterIMContainer::getInstance()->showConversation(session_id); } return true; diff --git a/indra/newview/llnearbychat.cpp b/indra/newview/llnearbychat.cpp deleted file mode 100644 index dbdf460785..0000000000 --- a/indra/newview/llnearbychat.cpp +++ /dev/null @@ -1,867 +0,0 @@ -/** - * @file LLNearbyChat.cpp - * @brief LLNearbyChat class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "message.h" - -#include "lliconctrl.h" -#include "llappviewer.h" -#include "llchatentry.h" -#include "llfloaterreg.h" -#include "lltrans.h" -#include "llimfloatercontainer.h" -#include "llfloatersidepanelcontainer.h" -#include "llfocusmgr.h" -#include "lllogchat.h" -#include "llresizebar.h" -#include "llresizehandle.h" -#include "lldraghandle.h" -#include "llmenugl.h" -#include "llviewermenu.h" // for gMenuHolder -#include "llnearbychathandler.h" -#include "llchannelmanager.h" -#include "llchathistory.h" -#include "llstylemap.h" -#include "llavatarnamecache.h" -#include "llfloaterreg.h" -#include "lltrans.h" - -#include "llfirstuse.h" -#include "llnearbychat.h" -#include "llagent.h" // gAgent -#include "llgesturemgr.h" -#include "llmultigesture.h" -#include "llkeyboard.h" -#include "llanimationstates.h" -#include "llviewerstats.h" -#include "llcommandhandler.h" -#include "llviewercontrol.h" -#include "llnavigationbar.h" -#include "llwindow.h" -#include "llviewerwindow.h" -#include "llrootview.h" -#include "llviewerchat.h" -#include "lltranslate.h" - -S32 LLNearbyChat::sLastSpecialChatChannel = 0; - -const S32 EXPANDED_HEIGHT = 266; -const S32 COLLAPSED_HEIGHT = 60; -const S32 EXPANDED_MIN_HEIGHT = 150; - -// legacy callback glue -void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel); - -struct LLChatTypeTrigger { - std::string name; - EChatType type; -}; - -static LLChatTypeTrigger sChatTypeTriggers[] = { - { "/whisper" , CHAT_TYPE_WHISPER}, - { "/shout" , CHAT_TYPE_SHOUT} -}; - - -LLNearbyChat::LLNearbyChat(const LLSD& llsd) -: LLIMConversation(llsd), - //mOutputMonitor(NULL), - mSpeakerMgr(NULL), - mExpandedHeight(COLLAPSED_HEIGHT + EXPANDED_HEIGHT) -{ - mIsP2PChat = false; - mIsNearbyChat = true; - setIsChrome(TRUE); - mSpeakerMgr = LLLocalSpeakerMgr::getInstance(); - mSessionID = LLUUID(); -} - -//static -LLNearbyChat* LLNearbyChat::buildFloater(const LLSD& key) -{ - LLFloaterReg::getInstance("im_container"); - return new LLNearbyChat(key); -} - -//virtual -BOOL LLNearbyChat::postBuild() -{ - setIsSingleInstance(TRUE); - BOOL result = LLIMConversation::postBuild(); - mInputEditor->setCommitCallback(boost::bind(&LLNearbyChat::onChatBoxCommit, this)); - mInputEditor->setKeystrokeCallback(boost::bind(&onChatBoxKeystroke, _1, this)); - mInputEditor->setFocusLostCallback(boost::bind(&onChatBoxFocusLost, _1, this)); - mInputEditor->setFocusReceivedCallback(boost::bind(&LLNearbyChat::onChatBoxFocusReceived, this)); - mInputEditor->setLabel(LLTrans::getString("NearbyChatTitle")); - -// mOutputMonitor = getChild("chat_zone_indicator"); -// mOutputMonitor->setVisible(FALSE); - - // Register for font change notifications - LLViewerChat::setFontChangedCallback(boost::bind(&LLNearbyChat::onChatFontChange, this, _1)); - - // title must be defined BEFORE call addConversationListItem() because - // it is used for show the item's name in the conversations list - setTitle(LLTrans::getString("NearbyChatTitle")); - - //for menu - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - - enable_registrar.add("NearbyChat.Check", boost::bind(&LLNearbyChat::onNearbyChatCheckContextMenuItem, this, _2)); - registrar.add("NearbyChat.Action", boost::bind(&LLNearbyChat::onNearbyChatContextMenuItemClicked, this, _2)); - - LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_nearby_chat.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if(menu) - { - mPopupMenuHandle = menu->getHandle(); - } - - // obsolete, but may be needed for backward compatibility? - gSavedSettings.declareS32("nearbychat_showicons_and_names", 2, "NearByChat header settings", true); - - if (gSavedPerAccountSettings.getBOOL("LogShowHistory")) - { - loadHistory(); - } - - return result; -} - -// virtual -void LLNearbyChat::refresh() -{ - displaySpeakingIndicator(); - updateCallBtnState(LLVoiceClient::getInstance()->getUserPTTState()); - - // *HACK: Update transparency type depending on whether our children have focus. - // This is needed because this floater is chrome and thus cannot accept focus, so - // the transparency type setting code from LLFloater::setFocus() isn't reached. - if (getTransparencyType() != TT_DEFAULT) - { - setTransparencyType(hasFocus() ? TT_ACTIVE : TT_INACTIVE); - } -} - -void LLNearbyChat::onNearbySpeakers() -{ - LLSD param; - param["people_panel_tab_name"] = "nearby_panel"; - LLFloaterSidePanelContainer::showPanel("people", "panel_people", param); -} - -void LLNearbyChat::onNearbyChatContextMenuItemClicked(const LLSD& userdata) -{ -} - -bool LLNearbyChat::onNearbyChatCheckContextMenuItem(const LLSD& userdata) -{ - std::string str = userdata.asString(); - if(str == "nearby_people") - onNearbySpeakers(); - return false; -} - - -BOOL LLNearbyChat::handleMouseDown(S32 x, S32 y, MASK mask) -{ - //fix for EXT-6625 - //highlight NearbyChat history whenever mouseclick happen in NearbyChat - //setting focus to eidtor will force onFocusLost() call that in its turn will change - //background opaque. This all happenn since NearByChat is "chrome" and didn't process focus change. - - if(mChatHistory) - { - mChatHistory->setFocus(TRUE); - } - - BOOL handled = LLPanel::handleMouseDown(x, y, mask); - setFocus(handled); - return handled; -} - -void LLNearbyChat::reloadMessages() -{ - mChatHistory->clear(); - - LLSD do_not_log; - do_not_log["do_not_log"] = true; - for(std::vector::iterator it = mMessageArchive.begin();it!=mMessageArchive.end();++it) - { - // Update the messages without re-writing them to a log file. - addMessage(*it,false, do_not_log); - } -} - -void LLNearbyChat::loadHistory() -{ - LLSD do_not_log; - do_not_log["do_not_log"] = true; - - std::list history; - LLLogChat::loadChatHistory("chat", history); - - std::list::const_iterator it = history.begin(); - while (it != history.end()) - { - const LLSD& msg = *it; - - std::string from = msg[IM_FROM]; - LLUUID from_id; - if (msg[IM_FROM_ID].isDefined()) - { - from_id = msg[IM_FROM_ID].asUUID(); - } - else - { - std::string legacy_name = gCacheName->buildLegacyName(from); - gCacheName->getUUID(legacy_name, from_id); - } - - LLChat chat; - chat.mFromName = from; - chat.mFromID = from_id; - chat.mText = msg[IM_TEXT].asString(); - chat.mTimeStr = msg[IM_TIME].asString(); - chat.mChatStyle = CHAT_STYLE_HISTORY; - - chat.mSourceType = CHAT_SOURCE_AGENT; - if (from_id.isNull() && SYSTEM_FROM == from) - { - chat.mSourceType = CHAT_SOURCE_SYSTEM; - - } - else if (from_id.isNull()) - { - chat.mSourceType = isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; - } - - addMessage(chat, true, do_not_log); - - it++; - } -} - -void LLNearbyChat::removeScreenChat() -{ - LLNotificationsUI::LLScreenChannelBase* chat_channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID(LLUUID(gSavedSettings.getString("NearByChatChannelUUID"))); - if(chat_channel) - { - chat_channel->removeToastsFromChannel(); - } -} - - -void LLNearbyChat::setVisible(BOOL visible) -{ - LLIMConversation::setVisible(visible); - - if(visible) - { - removeScreenChat(); - } - setFocus(visible); -} - -// virtual -void LLNearbyChat::onTearOffClicked() -{ - LLIMConversation::onTearOffClicked(); - - // see CHUI-170: Save torn-off state of the nearby chat between sessions - BOOL in_the_multifloater = !isTornOff(); - gSavedSettings.setBOOL("NearbyChatIsNotTornOff", in_the_multifloater); -} - - -// virtual -void LLNearbyChat::onOpen(const LLSD& key) -{ - LLIMConversation::onOpen(key); - showTranslationCheckbox(LLTranslate::isTranslationConfigured()); -} - -// virtual -void LLNearbyChat::onClose(bool app_quitting) -{ - // Override LLIMConversation::onClose() so that Nearby Chat is not removed from the conversation floater -} - -// virtual -void LLNearbyChat::onClickCloseBtn() -{ - if (!isTornOff()) - return; - onTearOffClicked(); - - LLIMFloaterContainer *im_box = LLIMFloaterContainer::findInstance(); - if (im_box) - { - im_box->onNearbyChatClosed(); - } -} - -void LLNearbyChat::onChatFontChange(LLFontGL* fontp) -{ - // Update things with the new font whohoo - if (mInputEditor) - { - mInputEditor->setFont(fontp); - } -} - - -void LLNearbyChat::show() -{ - if (isChatMultiTab()) - { - openFloater(getKey()); - } -} - -bool LLNearbyChat::isChatVisible() const -{ - bool isVisible = false; - LLIMFloaterContainer* im_box = LLIMFloaterContainer::getInstance(); - // Is the IM floater container ever null? - llassert(im_box != NULL); - if (im_box != NULL) - { - isVisible = - isChatMultiTab() && gSavedSettings.getBOOL("NearbyChatIsNotTornOff")? - im_box->getVisible() && !im_box->isMinimized() : - getVisible() && !isMinimized(); - } - - return isVisible; -} - -void LLNearbyChat::showHistory() -{ - openFloater(); - setResizeLimits(getMinWidth(), EXPANDED_MIN_HEIGHT); -} - -std::string LLNearbyChat::getCurrentChat() -{ - return mInputEditor ? mInputEditor->getText() : LLStringUtil::null; -} - -// virtual -BOOL LLNearbyChat::handleKeyHere( KEY key, MASK mask ) -{ - BOOL handled = FALSE; - - if( KEY_RETURN == key && mask == MASK_CONTROL) - { - // shout - sendChat(CHAT_TYPE_SHOUT); - handled = TRUE; - } - - return handled; -} - -BOOL LLNearbyChat::matchChatTypeTrigger(const std::string& in_str, std::string* out_str) -{ - U32 in_len = in_str.length(); - S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers); - - bool string_was_found = false; - - for (S32 n = 0; n < cnt && !string_was_found; n++) - { - if (in_len <= sChatTypeTriggers[n].name.length()) - { - std::string trigger_trunc = sChatTypeTriggers[n].name; - LLStringUtil::truncate(trigger_trunc, in_len); - - if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc)) - { - *out_str = sChatTypeTriggers[n].name; - string_was_found = true; - } - } - } - - return string_was_found; -} - -void LLNearbyChat::onChatBoxKeystroke(LLTextEditor* caller, void* userdata) -{ - LLFirstUse::otherAvatarChatFirst(false); - - LLNearbyChat* self = (LLNearbyChat *)userdata; - - LLWString raw_text = self->mInputEditor->getWText(); - - // Can't trim the end, because that will cause autocompletion - // to eat trailing spaces that might be part of a gesture. - LLWStringUtil::trimHead(raw_text); - - S32 length = raw_text.length(); - - if( (length > 0) && (raw_text[0] != '/') ) // forward slash is used for escape (eg. emote) sequences - { - gAgent.startTyping(); - } - else - { - gAgent.stopTyping(); - } - - /* Doesn't work -- can't tell the difference between a backspace - that killed the selection vs. backspace at the end of line. - if (length > 1 - && text[0] == '/' - && key == KEY_BACKSPACE) - { - // the selection will already be deleted, but we need to trim - // off the character before - std::string new_text = raw_text.substr(0, length-1); - self->mInputEditor->setText( new_text ); - self->mInputEditor->setCursorToEnd(); - length = length - 1; - } - */ - - KEY key = gKeyboard->currentKey(); - - // Ignore "special" keys, like backspace, arrows, etc. - if (length > 1 - && raw_text[0] == '/' - && key < KEY_SPECIAL) - { - // we're starting a gesture, attempt to autocomplete - - std::string utf8_trigger = wstring_to_utf8str(raw_text); - std::string utf8_out_str(utf8_trigger); - - if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str)) - { - std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); - self->mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part - - // Select to end of line, starting from the character - // after the last one the user typed. - self->mInputEditor->selectNext(rest_of_match, false); - } - else if (matchChatTypeTrigger(utf8_trigger, &utf8_out_str)) - { - std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); - self->mInputEditor->setText(utf8_trigger + rest_of_match + " "); // keep original capitalization for user-entered part - self->mInputEditor->endOfDoc(); - } - - //llinfos << "GESTUREDEBUG " << trigger - // << " len " << length - // << " outlen " << out_str.getLength() - // << llendl; - } -} - -// static -void LLNearbyChat::onChatBoxFocusLost(LLFocusableElement* caller, void* userdata) -{ - // stop typing animation - gAgent.stopTyping(); -} - -void LLNearbyChat::onChatBoxFocusReceived() -{ - mInputEditor->setEnabled(!gDisconnected); -} - -EChatType LLNearbyChat::processChatTypeTriggers(EChatType type, std::string &str) -{ - U32 length = str.length(); - S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers); - - for (S32 n = 0; n < cnt; n++) - { - if (length >= sChatTypeTriggers[n].name.length()) - { - std::string trigger = str.substr(0, sChatTypeTriggers[n].name.length()); - - if (!LLStringUtil::compareInsensitive(trigger, sChatTypeTriggers[n].name)) - { - U32 trigger_length = sChatTypeTriggers[n].name.length(); - - // It's to remove space after trigger name - if (length > trigger_length && str[trigger_length] == ' ') - trigger_length++; - - str = str.substr(trigger_length, length); - - if (CHAT_TYPE_NORMAL == type) - return sChatTypeTriggers[n].type; - else - break; - } - } - } - - return type; -} - -void LLNearbyChat::sendChat( EChatType type ) -{ - if (mInputEditor) - { - LLWString text = mInputEditor->getWText(); - LLWStringUtil::trim(text); - LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. - if (!text.empty()) - { - // Check if this is destined for another channel - S32 channel = 0; - stripChannelNumber(text, &channel); - - std::string utf8text = wstring_to_utf8str(text); - // Try to trigger a gesture, if not chat to a script. - std::string utf8_revised_text; - if (0 == channel) - { - // discard returned "found" boolean - LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text); - } - else - { - utf8_revised_text = utf8text; - } - - utf8_revised_text = utf8str_trim(utf8_revised_text); - - type = processChatTypeTriggers(type, utf8_revised_text); - - if (!utf8_revised_text.empty()) - { - // Chat with animation - sendChatFromViewer(utf8_revised_text, type, TRUE); - } - } - - mInputEditor->setText(LLStringExplicit("")); - } - - gAgent.stopTyping(); - - // If the user wants to stop chatting on hitting return, lose focus - // and go out of chat mode. - if (gSavedSettings.getBOOL("CloseChatOnReturn")) - { - stopChat(); - } -} - -void LLNearbyChat::addMessage(const LLChat& chat,bool archive,const LLSD &args) -{ - appendMessage(chat, args); - - if(archive) - { - mMessageArchive.push_back(chat); - if(mMessageArchive.size()>200) - mMessageArchive.erase(mMessageArchive.begin()); - } - - // logging - if (!args["do_not_log"].asBoolean() - && gSavedPerAccountSettings.getBOOL("LogNearbyChat")) - { - std::string from_name = chat.mFromName; - - if (chat.mSourceType == CHAT_SOURCE_AGENT) - { - // if the chat is coming from an agent, log the complete name - LLAvatarName av_name; - LLAvatarNameCache::get(chat.mFromID, &av_name); - - if (!av_name.mIsDisplayNameDefault) - { - from_name = av_name.getCompleteName(); - } - } - - LLLogChat::saveHistory("chat", from_name, chat.mFromID, chat.mText); - } -} - - -void LLNearbyChat::onChatBoxCommit() -{ - if (mInputEditor->getText().length() > 0) - { - sendChat(CHAT_TYPE_NORMAL); - } - - gAgent.stopTyping(); -} - -void LLNearbyChat::displaySpeakingIndicator() -{ - LLSpeakerMgr::speaker_list_t speaker_list; - LLUUID id; - - id.setNull(); - mSpeakerMgr->update(TRUE); - mSpeakerMgr->getSpeakerList(&speaker_list, FALSE); - - for (LLSpeakerMgr::speaker_list_t::iterator i = speaker_list.begin(); i != speaker_list.end(); ++i) - { - LLPointer s = *i; - if (s->mSpeechVolume > 0 || s->mStatus == LLSpeaker::STATUS_SPEAKING) - { - id = s->mID; - break; - } - } - - if (!id.isNull()) - { - //mOutputMonitor->setVisible(TRUE); - //mOutputMonitor->setSpeakerId(id); - } - else - { - //mOutputMonitor->setVisible(FALSE); - } -} - -void LLNearbyChat::sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate) -{ - sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate); -} - -void LLNearbyChat::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate) -{ - // Look for "/20 foo" channel chats. - S32 channel = 0; - LLWString out_text = stripChannelNumber(wtext, &channel); - std::string utf8_out_text = wstring_to_utf8str(out_text); - std::string utf8_text = wstring_to_utf8str(wtext); - - utf8_text = utf8str_trim(utf8_text); - if (!utf8_text.empty()) - { - utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1); - } - - // Don't animate for chats people can't hear (chat to scripts) - if (animate && (channel == 0)) - { - if (type == CHAT_TYPE_WHISPER) - { - lldebugs << "You whisper " << utf8_text << llendl; - gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START); - } - else if (type == CHAT_TYPE_NORMAL) - { - lldebugs << "You say " << utf8_text << llendl; - gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START); - } - else if (type == CHAT_TYPE_SHOUT) - { - lldebugs << "You shout " << utf8_text << llendl; - gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START); - } - else - { - llinfos << "send_chat_from_viewer() - invalid volume" << llendl; - return; - } - } - else - { - if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP) - { - lldebugs << "Channel chat: " << utf8_text << llendl; - } - } - - send_chat_from_viewer(utf8_out_text, type, channel); -} - -// static -bool LLNearbyChat::isWordsName(const std::string& name) -{ - // checking to see if it's display name plus username in parentheses - S32 open_paren = name.find(" (", 0); - S32 close_paren = name.find(')', 0); - - if (open_paren != std::string::npos && - close_paren == name.length()-1) - { - return true; - } - else - { - //checking for a single space - S32 pos = name.find(' ', 0); - return std::string::npos != pos && name.rfind(' ', name.length()) == pos && 0 != pos && name.length()-1 != pos; - } -} - -// static -void LLNearbyChat::startChat(const char* line) -{ - LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); - if (nearby_chat) - { - nearby_chat->show(); - nearby_chat->setVisible(TRUE); - nearby_chat->setFocus(TRUE); - nearby_chat->mInputEditor->setFocus(TRUE); - - if (line) - { - std::string line_string(line); - nearby_chat->mInputEditor->setText(line_string); - } - - nearby_chat->mInputEditor->endOfDoc(); - } -} - -// Exit "chat mode" and do the appropriate focus changes -// static -void LLNearbyChat::stopChat() -{ - LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); - if (nearby_chat) - { - nearby_chat->mInputEditor->setFocus(FALSE); - gAgent.stopTyping(); - } -} - -// If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20. -// Otherwise returns input and channel 0. -LLWString LLNearbyChat::stripChannelNumber(const LLWString &mesg, S32* channel) -{ - if (mesg[0] == '/' - && mesg[1] == '/') - { - // This is a "repeat channel send" - *channel = sLastSpecialChatChannel; - return mesg.substr(2, mesg.length() - 2); - } - else if (mesg[0] == '/' - && mesg[1] - && LLStringOps::isDigit(mesg[1])) - { - // This a special "/20" speak on a channel - S32 pos = 0; - - // Copy the channel number into a string - LLWString channel_string; - llwchar c; - do - { - c = mesg[pos+1]; - channel_string.push_back(c); - pos++; - } - while(c && pos < 64 && LLStringOps::isDigit(c)); - - // Move the pointer forward to the first non-whitespace char - // Check isspace before looping, so we can handle "/33foo" - // as well as "/33 foo" - while(c && iswspace(c)) - { - c = mesg[pos+1]; - pos++; - } - - sLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10); - *channel = sLastSpecialChatChannel; - return mesg.substr(pos, mesg.length() - pos); - } - else - { - // This is normal chat. - *channel = 0; - return mesg; - } -} - -void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel) -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ChatFromViewer); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ChatData); - msg->addStringFast(_PREHASH_Message, utf8_out_text); - msg->addU8Fast(_PREHASH_Type, type); - msg->addS32("Channel", channel); - - gAgent.sendReliableMessage(); - - LLViewerStats::getInstance()->incStat(LLViewerStats::ST_CHAT_COUNT); -} - -class LLChatCommandHandler : public LLCommandHandler -{ -public: - // not allowed from outside the app - LLChatCommandHandler() : LLCommandHandler("chat", UNTRUSTED_BLOCK) { } - - // Your code here - bool handle(const LLSD& tokens, const LLSD& query_map, - LLMediaCtrl* web) - { - bool retval = false; - // Need at least 2 tokens to have a valid message. - if (tokens.size() < 2) - { - retval = false; - } - else - { - S32 channel = tokens[0].asInteger(); - // VWR-19499 Restrict function to chat channels greater than 0. - if ((channel > 0) && (channel < CHAT_CHANNEL_DEBUG)) - { - retval = true; - // Send unescaped message, see EXT-6353. - std::string unescaped_mesg (LLURI::unescape(tokens[1].asString())); - send_chat_from_viewer(unescaped_mesg, CHAT_TYPE_NORMAL, channel); - } - else - { - retval = false; - // Tell us this is an unsupported SLurl. - } - } - return retval; - } -}; - -// Creating the object registers with the dispatcher. -LLChatCommandHandler gChatHandler; diff --git a/indra/newview/llnearbychat.h b/indra/newview/llnearbychat.h deleted file mode 100644 index c6a2637e8f..0000000000 --- a/indra/newview/llnearbychat.h +++ /dev/null @@ -1,125 +0,0 @@ -/** - * @file llnearbychat.h - * @brief LLNearbyChat class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLNEARBYCHAT_H -#define LL_LLNEARBYCHAT_H - -#include "llimconversation.h" -#include "llcombobox.h" -#include "llgesturemgr.h" -#include "llchat.h" -#include "llvoiceclient.h" -#include "lloutputmonitorctrl.h" -#include "llspeakers.h" -#include "llscrollbar.h" -#include "llviewerchat.h" -#include "llpanel.h" - -class LLResizeBar; - -class LLNearbyChat - : public LLIMConversation -{ -public: - // constructor for inline chat-bars (e.g. hosted in chat history window) - LLNearbyChat(const LLSD& key = LLSD(LLUUID())); - ~LLNearbyChat() {} - - static LLNearbyChat* buildFloater(const LLSD& key); - - /*virtual*/ BOOL postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void setVisible(BOOL visible); - - void loadHistory(); - void reloadMessages(); - void removeScreenChat(); - - void addToHost(); - void show(); - bool isChatVisible() const; - - /** @param archive true - to save a message to the chat history log */ - void addMessage (const LLChat& message,bool archive = true, const LLSD &args = LLSD()); - void onNearbyChatContextMenuItemClicked(const LLSD& userdata); - bool onNearbyChatCheckContextMenuItem(const LLSD& userdata); - - LLChatEntry* getChatBox() { return mInputEditor; } - - std::string getCurrentChat(); - - virtual BOOL handleKeyHere( KEY key, MASK mask ); - virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); - - static void startChat(const char* line); - static void stopChat(); - - static void sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate); - static void sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate); - - static bool isWordsName(const std::string& name); - - void showHistory(); - -protected: - static BOOL matchChatTypeTrigger(const std::string& in_str, std::string* out_str); - static void onChatBoxKeystroke(LLTextEditor* caller, void* userdata); - static void onChatBoxFocusLost(LLFocusableElement* caller, void* userdata); - void onChatBoxFocusReceived(); - - void sendChat( EChatType type ); - void onChatBoxCommit(); - void onChatFontChange(LLFontGL* fontp); - - /*virtual*/ void onTearOffClicked(); - /*virtual*/ void onClickCloseBtn(); - - static LLWString stripChannelNumber(const LLWString &mesg, S32* channel); - EChatType processChatTypeTriggers(EChatType type, std::string &str); - - void displaySpeakingIndicator(); - - // Which non-zero channel did we last chat on? - static S32 sLastSpecialChatChannel; - - LLOutputMonitorCtrl* mOutputMonitor; - LLLocalSpeakerMgr* mSpeakerMgr; - - S32 mExpandedHeight; - -private: - - void onNearbySpeakers (); - - /*virtual*/ void refresh(); - - LLHandle mPopupMenuHandle; - std::vector mMessageArchive; - -}; - -#endif diff --git a/indra/newview/llnearbychatbarlistener.cpp b/indra/newview/llnearbychatbarlistener.cpp deleted file mode 100644 index 61815d1864..0000000000 --- a/indra/newview/llnearbychatbarlistener.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @file llnearbychatbarlistener.cpp - * @author Dave Simmons - * @date 2011-03-15 - * @brief Implementation for LLNearbyChatBarListener. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llnearbychatbarlistener.h" -#include "llnearbychat.h" - -#include "llagent.h" -#include "llchat.h" - - - -LLNearbyChatBarListener::LLNearbyChatBarListener(LLNearbyChat & chatbar) - : LLEventAPI("LLChatBar", - "LLChatBar listener to (e.g.) sendChat, etc."), - mChatbar(chatbar) -{ - add("sendChat", - "Send chat to the simulator:\n" - "[\"message\"] chat message text [required]\n" - "[\"channel\"] chat channel number [default = 0]\n" - "[\"type\"] chat type \"whisper\", \"normal\", \"shout\" [default = \"normal\"]", - &LLNearbyChatBarListener::sendChat); -} - - -// "sendChat" command -void LLNearbyChatBarListener::sendChat(LLSD const & chat_data) const -{ - // Extract the data - std::string chat_text = chat_data["message"].asString(); - - S32 channel = 0; - if (chat_data.has("channel")) - { - channel = chat_data["channel"].asInteger(); - if (channel < 0 || channel >= CHAT_CHANNEL_DEBUG) - { // Use 0 up to (but not including) CHAT_CHANNEL_DEBUG - channel = 0; - } - } - - EChatType type_o_chat = CHAT_TYPE_NORMAL; - if (chat_data.has("type")) - { - std::string type_string = chat_data["type"].asString(); - if (type_string == "whisper") - { - type_o_chat = CHAT_TYPE_WHISPER; - } - else if (type_string == "shout") - { - type_o_chat = CHAT_TYPE_SHOUT; - } - } - - // Have to prepend /42 style channel numbers - std::string chat_to_send; - if (channel == 0) - { - chat_to_send = chat_text; - } - else - { - chat_to_send += "/"; - chat_to_send += chat_data["channel"].asString(); - chat_to_send += " "; - chat_to_send += chat_text; - } - - // Send it as if it was typed in - mChatbar.sendChatFromViewer(chat_to_send, type_o_chat, (BOOL)(channel == 0)); -} - diff --git a/indra/newview/llnearbychatbarlistener.h b/indra/newview/llnearbychatbarlistener.h deleted file mode 100644 index 0537275424..0000000000 --- a/indra/newview/llnearbychatbarlistener.h +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @file llnearbychatbarlistener.h - * @author Dave Simmons - * @date 2011-03-15 - * @brief Class definition for LLNearbyChatBarListener. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#ifndef LL_LLNEARBYCHATBARLISTENER_H -#define LL_LLNEARBYCHATBARLISTENER_H - -#include "lleventapi.h" - -class LLSD; -class LLNearbyChat; - -class LLNearbyChatBarListener : public LLEventAPI -{ -public: - LLNearbyChatBarListener(LLNearbyChat & chatbar); - -private: - void sendChat(LLSD const & chat_data) const; - - LLNearbyChat & mChatbar; -}; - -#endif // LL_LLNEARBYCHATBARLISTENER_H - diff --git a/indra/newview/llnearbychathandler.cpp b/indra/newview/llnearbychathandler.cpp deleted file mode 100644 index 1494d9d6ee..0000000000 --- a/indra/newview/llnearbychathandler.cpp +++ /dev/null @@ -1,630 +0,0 @@ -/** - * @file LLNearbyChatHandler.cpp - * @brief Nearby chat chat managment - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llagentdata.h" // for gAgentID -#include "llnearbychathandler.h" - -#include "llchatitemscontainerctrl.h" -#include "llfirstuse.h" -#include "llfloaterscriptdebug.h" -#include "llhints.h" -#include "llnearbychat.h" -#include "llrecentpeople.h" - -#include "llviewercontrol.h" - -#include "llfloaterreg.h"//for LLFloaterReg::getTypedInstance -#include "llviewerwindow.h"//for screen channel position -#include "llnearbychat.h" -#include "llrootview.h" -#include "lllayoutstack.h" - -//add LLNearbyChatHandler to LLNotificationsUI namespace -using namespace LLNotificationsUI; - -static LLNearbyChatToastPanel* createToastPanel() -{ - LLNearbyChatToastPanel* item = LLNearbyChatToastPanel::createInstance(); - return item; -} - - -//----------------------------------------------------------------------------------------------- -//LLNearbyChatScreenChannel -//----------------------------------------------------------------------------------------------- - -class LLNearbyChatScreenChannel: public LLScreenChannelBase -{ - LOG_CLASS(LLNearbyChatScreenChannel); -public: - typedef std::vector > toast_vec_t; - typedef std::list > toast_list_t; - - LLNearbyChatScreenChannel(const Params& p) - : LLScreenChannelBase(p) - { - mStopProcessing = false; - - LLControlVariable* ctrl = gSavedSettings.getControl("NearbyToastLifeTime").get(); - if (ctrl) - { - ctrl->getSignal()->connect(boost::bind(&LLNearbyChatScreenChannel::updateToastsLifetime, this)); - } - - ctrl = gSavedSettings.getControl("NearbyToastFadingTime").get(); - if (ctrl) - { - ctrl->getSignal()->connect(boost::bind(&LLNearbyChatScreenChannel::updateToastFadingTime, this)); - } - } - - void addChat (LLSD& chat); - void arrangeToasts (); - - typedef boost::function create_toast_panel_callback_t; - void setCreatePanelCallback(create_toast_panel_callback_t value) { m_create_toast_panel_callback_t = value;} - - void onToastDestroyed (LLToast* toast, bool app_quitting); - void onToastFade (LLToast* toast); - - void redrawToasts() - { - arrangeToasts(); - } - - // hide all toasts from screen, but not remove them from a channel - // removes all toasts from a channel - virtual void removeToastsFromChannel() - { - for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it) - { - addToToastPool(it->get()); - } - m_active_toasts.clear(); - }; - - virtual void deleteAllChildren() - { - LL_DEBUGS("NearbyChat") << "Clearing toast pool" << llendl; - m_toast_pool.clear(); - m_active_toasts.clear(); - LLScreenChannelBase::deleteAllChildren(); - } - -protected: - void deactivateToast(LLToast* toast); - void addToToastPool(LLToast* toast) - { - if (!toast) return; - LL_DEBUGS("NearbyChat") << "Pooling toast" << llendl; - toast->setVisible(FALSE); - toast->stopTimer(); - toast->setIsHidden(true); - - // Nearby chat toasts that are hidden, not destroyed. They are collected to the toast pool, so that - // they can be used next time, this is done for performance. But if the toast lifetime was changed - // (from preferences floater (STORY-36)) while it was shown (at this moment toast isn't in the pool yet) - // changes don't take affect. - // So toast's lifetime should be updated each time it's added to the pool. Otherwise viewer would have - // to be restarted so that changes take effect. - toast->setLifetime(gSavedSettings.getS32("NearbyToastLifeTime")); - toast->setFadingTime(gSavedSettings.getS32("NearbyToastFadingTime")); - m_toast_pool.push_back(toast->getHandle()); - } - - void createOverflowToast(S32 bottom, F32 timer); - - void updateToastsLifetime(); - - void updateToastFadingTime(); - - create_toast_panel_callback_t m_create_toast_panel_callback_t; - - bool createPoolToast(); - - toast_vec_t m_active_toasts; - toast_list_t m_toast_pool; - - bool mStopProcessing; - bool mChannelRect; -}; - - - -//----------------------------------------------------------------------------------------------- -// LLNearbyChatToast -//----------------------------------------------------------------------------------------------- - -// We're deriving from LLToast to be able to override onClose() -// in order to handle closing nearby chat toasts properly. -class LLNearbyChatToast : public LLToast -{ - LOG_CLASS(LLNearbyChatToast); -public: - LLNearbyChatToast(const LLToast::Params& p, LLNearbyChatScreenChannel* nc_channelp) - : LLToast(p), - mNearbyChatScreenChannelp(nc_channelp) - { - } - - /*virtual*/ void onClose(bool app_quitting); - -private: - LLNearbyChatScreenChannel* mNearbyChatScreenChannelp; -}; - -//----------------------------------------------------------------------------------------------- -// LLNearbyChatScreenChannel -//----------------------------------------------------------------------------------------------- - -void LLNearbyChatScreenChannel::deactivateToast(LLToast* toast) -{ - toast_vec_t::iterator pos = std::find(m_active_toasts.begin(), m_active_toasts.end(), toast->getHandle()); - - if (pos == m_active_toasts.end()) - { - llassert(pos == m_active_toasts.end()); - return; - } - - LL_DEBUGS("NearbyChat") << "Deactivating toast" << llendl; - m_active_toasts.erase(pos); -} - -void LLNearbyChatScreenChannel::createOverflowToast(S32 bottom, F32 timer) -{ - //we don't need overflow toast in nearby chat -} - -void LLNearbyChatScreenChannel::onToastDestroyed(LLToast* toast, bool app_quitting) -{ - LL_DEBUGS("NearbyChat") << "Toast destroyed (app_quitting=" << app_quitting << ")" << llendl; - - if (app_quitting) - { - // Viewer is quitting. - // Immediately stop processing chat messages (EXT-1419). - mStopProcessing = true; -} - else - { - // The toast is being closed by user (STORM-192). - // Remove it from the list of active toasts to prevent - // further references to the invalid pointer. - deactivateToast(toast); - } -} - -void LLNearbyChatScreenChannel::onToastFade(LLToast* toast) -{ - LL_DEBUGS("NearbyChat") << "Toast fading" << llendl; - - //fade mean we put toast to toast pool - if(!toast) - return; - - deactivateToast(toast); - - addToToastPool(toast); - - arrangeToasts(); -} - -void LLNearbyChatScreenChannel::updateToastsLifetime() -{ - S32 seconds = gSavedSettings.getS32("NearbyToastLifeTime"); - toast_list_t::iterator it; - - for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it) - { - (*it).get()->setLifetime(seconds); - } -} - -void LLNearbyChatScreenChannel::updateToastFadingTime() -{ - S32 seconds = gSavedSettings.getS32("NearbyToastFadingTime"); - toast_list_t::iterator it; - - for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it) - { - (*it).get()->setFadingTime(seconds); - } -} - -bool LLNearbyChatScreenChannel::createPoolToast() -{ - LLNearbyChatToastPanel* panel= m_create_toast_panel_callback_t(); - if(!panel) - return false; - - LLToast::Params p; - p.panel = panel; - p.lifetime_secs = gSavedSettings.getS32("NearbyToastLifeTime"); - p.fading_time_secs = gSavedSettings.getS32("NearbyToastFadingTime"); - - LLToast* toast = new LLNearbyChatToast(p, this); - - - toast->setOnFadeCallback(boost::bind(&LLNearbyChatScreenChannel::onToastFade, this, _1)); - - // If the toast gets somehow prematurely destroyed, deactivate it to prevent crash (STORM-1352). - toast->setOnToastDestroyedCallback(boost::bind(&LLNearbyChatScreenChannel::onToastDestroyed, this, _1, false)); - - LL_DEBUGS("NearbyChat") << "Creating and pooling toast" << llendl; - m_toast_pool.push_back(toast->getHandle()); - return true; -} - -void LLNearbyChatScreenChannel::addChat(LLSD& chat) -{ - //look in pool. if there is any message - if(mStopProcessing) - return; - - /* - find last toast and check ID - */ - - if(m_active_toasts.size()) - { - LLUUID fromID = chat["from_id"].asUUID(); // agent id or object id - std::string from = chat["from"].asString(); - LLToast* toast = m_active_toasts[0].get(); - if (toast) - { - LLNearbyChatToastPanel* panel = dynamic_cast(toast->getPanel()); - - if(panel && panel->messageID() == fromID && panel->getFromName() == from && panel->canAddText()) - { - panel->addMessage(chat); - toast->reshapeToPanel(); - toast->startTimer(); - - arrangeToasts(); - return; - } - } - } - - - - if(m_toast_pool.empty()) - { - //"pool" is empty - create one more panel - LL_DEBUGS("NearbyChat") << "Empty pool" << llendl; - if(!createPoolToast())//created toast will go to pool. so next call will find it - return; - addChat(chat); - return; - } - - int chat_type = chat["chat_type"].asInteger(); - - if( ((EChatType)chat_type == CHAT_TYPE_DEBUG_MSG)) - { - if(gSavedSettings.getBOOL("ShowScriptErrors") == FALSE) - return; - if(gSavedSettings.getS32("ShowScriptErrorsLocation")== 1) - return; - } - - - //take 1st element from pool, (re)initialize it, put it in active toasts - - LL_DEBUGS("NearbyChat") << "Getting toast from pool" << llendl; - LLToast* toast = m_toast_pool.back().get(); - - m_toast_pool.pop_back(); - - - LLNearbyChatToastPanel* panel = dynamic_cast(toast->getPanel()); - if(!panel) - return; - panel->init(chat); - - toast->reshapeToPanel(); - toast->startTimer(); - - m_active_toasts.push_back(toast->getHandle()); - - arrangeToasts(); -} - -static bool sort_toasts_predicate(LLHandle first, LLHandle second) -{ - if (!first.get() || !second.get()) return false; // STORM-1352 - - F32 v1 = first.get()->getTimeLeftToLive(); - F32 v2 = second.get()->getTimeLeftToLive(); - return v1 > v2; -} - -void LLNearbyChatScreenChannel::arrangeToasts() -{ - if(mStopProcessing || isHovering()) - return; - - if (mFloaterSnapRegion == NULL) - { - mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); - } - - if (!getParent()) - { - // connect to floater snap region just to get resize events, we don't care about being a proper widget - mFloaterSnapRegion->addChild(this); - setFollows(FOLLOWS_ALL); - } - - LLRect toast_rect; - updateRect(); - - LLRect channel_rect; - mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &channel_rect, gFloaterView); - channel_rect.mLeft += 10; - channel_rect.mRight = channel_rect.mLeft + 300; - - S32 channel_bottom = channel_rect.mBottom; - - S32 bottom = channel_bottom + 80; - S32 margin = gSavedSettings.getS32("ToastGap"); - - //sort active toasts - std::sort(m_active_toasts.begin(),m_active_toasts.end(),sort_toasts_predicate); - - //calc max visible item and hide other toasts. - - for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it) - { - LLToast* toast = it->get(); - if (!toast) - { - llwarns << "NULL found in the active chat toasts list!" << llendl; - continue; - } - - S32 toast_top = bottom + toast->getRect().getHeight() + margin; - - if(toast_top > channel_rect.getHeight()) - { - while(it!=m_active_toasts.end()) - { - addToToastPool(it->get()); - it=m_active_toasts.erase(it); - } - break; - } - - toast_rect = toast->getRect(); - toast_rect.setLeftTopAndSize(channel_rect.mLeft , bottom + toast_rect.getHeight(), toast_rect.getWidth() ,toast_rect.getHeight()); - - toast->setRect(toast_rect); - bottom += toast_rect.getHeight() - toast->getTopPad() + margin; - } - - // use reverse order to provide correct z-order and avoid toast blinking - - for(toast_vec_t::reverse_iterator it = m_active_toasts.rbegin(); it != m_active_toasts.rend(); ++it) - { - LLToast* toast = it->get(); - if (toast) - { - toast->setIsHidden(false); - toast->setVisible(TRUE); - } - } - -} - - - -//----------------------------------------------------------------------------------------------- -//LLNearbyChatHandler -//----------------------------------------------------------------------------------------------- -boost::scoped_ptr LLNearbyChatHandler::sChatWatcher(new LLEventStream("LLChat")); - -LLNearbyChatHandler::LLNearbyChatHandler() -{ - // Getting a Channel for our notifications - LLNearbyChatScreenChannel::Params p; - p.id = LLUUID(gSavedSettings.getString("NearByChatChannelUUID")); - LLNearbyChatScreenChannel* channel = new LLNearbyChatScreenChannel(p); - - LLNearbyChatScreenChannel::create_toast_panel_callback_t callback = createToastPanel; - - channel->setCreatePanelCallback(callback); - - LLChannelManager::getInstance()->addChannel(channel); - - mChannel = channel->getHandle(); -} - -LLNearbyChatHandler::~LLNearbyChatHandler() -{ -} - - -void LLNearbyChatHandler::initChannel() -{ - //LLRect snap_rect = gFloaterView->getSnapRect(); - //mChannel->init(snap_rect.mLeft, snap_rect.mLeft + 200); -} - - - -void LLNearbyChatHandler::processChat(const LLChat& chat_msg, - const LLSD &args) -{ - if(chat_msg.mMuted == TRUE) - return; - - if(chat_msg.mText.empty()) - return;//don't process empty messages - - LLFloaterReg::getInstance("im_container"); - LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); - - // Build notification data - LLSD chat; - chat["message"] = chat_msg.mText; - chat["from"] = chat_msg.mFromName; - chat["from_id"] = chat_msg.mFromID; - chat["time"] = chat_msg.mTime; - chat["source"] = (S32)chat_msg.mSourceType; - chat["chat_type"] = (S32)chat_msg.mChatType; - chat["chat_style"] = (S32)chat_msg.mChatStyle; - // Pass sender info so that it can be rendered properly (STORM-1021). - chat["sender_slurl"] = LLViewerChat::getSenderSLURL(chat_msg, args); - - if (chat_msg.mChatType == CHAT_TYPE_DIRECT && - chat_msg.mText.length() > 0 && - chat_msg.mText[0] == '@') - { - // Send event on to LLEventStream and exit - sChatWatcher->post(chat); - return; - } - - // don't show toast and add message to chat history on receive debug message - // with disabled setting showing script errors or enabled setting to show script - // errors in separate window. - if (chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG) - { - if(gSavedSettings.getBOOL("ShowScriptErrors") == FALSE) - return; - - // don't process debug messages from not owned objects, see EXT-7762 - if (gAgentID != chat_msg.mOwnerID) - { - return; - } - - if (gSavedSettings.getS32("ShowScriptErrorsLocation")== 1)// show error in window //("ScriptErrorsAsChat")) - { - - LLColor4 txt_color; - - LLViewerChat::getChatColor(chat_msg,txt_color); - - LLFloaterScriptDebug::addScriptLine(chat_msg.mText, - chat_msg.mFromName, - txt_color, - chat_msg.mFromID); - return; - } - } - - nearby_chat->addMessage(chat_msg, true, args); - - if(chat_msg.mSourceType == CHAT_SOURCE_AGENT - && chat_msg.mFromID.notNull() - && chat_msg.mFromID != gAgentID) - { - LLFirstUse::otherAvatarChatFirst(); - - // Add sender to the recent people list. - LLRecentPeople::instance().add(chat_msg.mFromID); - - } - - // Send event on to LLEventStream - sChatWatcher->post(chat); - - if( nearby_chat->isInVisibleChain() - || ( chat_msg.mSourceType == CHAT_SOURCE_AGENT - && gSavedSettings.getBOOL("UseChatBubbles") ) - || mChannel.isDead() - || !mChannel.get()->getShowToasts() ) // to prevent toasts in Do Not Disturb mode - return;//no need in toast if chat is visible or if bubble chat is enabled - - // arrange a channel on a screen - if(!mChannel.get()->getVisible()) - { - initChannel(); - } - - /* - //comment all this due to EXT-4432 - ..may clean up after some time... - - //only messages from AGENTS - if(CHAT_SOURCE_OBJECT == chat_msg.mSourceType) - { - if(chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG) - return;//ok for now we don't skip messeges from object, so skip only debug messages - } - */ - - LLNearbyChatScreenChannel* channel = dynamic_cast(mChannel.get()); - - if(channel) - { - // Handle IRC styled messages. - std::string toast_msg; - if (chat_msg.mChatStyle == CHAT_STYLE_IRC) - { - if (!chat_msg.mFromName.empty()) - { - toast_msg += chat_msg.mFromName; - } - toast_msg += chat_msg.mText.substr(3); - } - else - { - toast_msg = chat_msg.mText; - } - - // Add a nearby chat toast. - LLUUID id; - id.generate(); - chat["id"] = id; - std::string r_color_name = "White"; - F32 r_color_alpha = 1.0f; - LLViewerChat::getChatColor( chat_msg, r_color_name, r_color_alpha); - - chat["text_color"] = r_color_name; - chat["color_alpha"] = r_color_alpha; - chat["font_size"] = (S32)LLViewerChat::getChatFontSize() ; - chat["message"] = toast_msg; - channel->addChat(chat); - } -} - - -//----------------------------------------------------------------------------------------------- -// LLNearbyChatToast -//----------------------------------------------------------------------------------------------- - -// virtual -void LLNearbyChatToast::onClose(bool app_quitting) -{ - mNearbyChatScreenChannelp->onToastDestroyed(this, app_quitting); -} - -// EOF diff --git a/indra/newview/llnearbychathandler.h b/indra/newview/llnearbychathandler.h deleted file mode 100644 index a5034ac1cb..0000000000 --- a/indra/newview/llnearbychathandler.h +++ /dev/null @@ -1,54 +0,0 @@ -/** - * @file llnearbychathandler.h - * @brief nearby chat notify - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLNEARBYCHATHANDLER_H -#define LL_LLNEARBYCHATHANDLER_H - -#include "llnotificationhandler.h" - -class LLEventPump; - -//add LLNearbyChatHandler to LLNotificationsUI namespace -namespace LLNotificationsUI{ - -class LLNearbyChatHandler : public LLChatHandler -{ -public: - LLNearbyChatHandler(); - virtual ~LLNearbyChatHandler(); - - - virtual void processChat(const LLChat& chat_msg, const LLSD &args); - -protected: - virtual void initChannel(); - - static boost::scoped_ptr sChatWatcher; -}; - -} - -#endif /* LL_LLNEARBYCHATHANDLER_H */ diff --git a/indra/newview/llnotificationhandler.h b/indra/newview/llnotificationhandler.h index 0899625242..4bded6ab30 100644 --- a/indra/newview/llnotificationhandler.h +++ b/indra/newview/llnotificationhandler.h @@ -36,7 +36,7 @@ #include "llinstantmessage.h" #include "llnotificationptr.h" -class LLIMFloater; +class LLFloaterIMSession; namespace LLNotificationsUI { diff --git a/indra/newview/llnotificationhandlerutil.cpp b/indra/newview/llnotificationhandlerutil.cpp index b4e8927879..7f1216ff40 100644 --- a/indra/newview/llnotificationhandlerutil.cpp +++ b/indra/newview/llnotificationhandlerutil.cpp @@ -34,9 +34,9 @@ #include "llurlaction.h" #include "llagent.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" #include "llimview.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llnotificationhandler.h" using namespace LLNotificationsUI; @@ -52,7 +52,7 @@ bool LLHandlerUtil::isIMFloaterOpened(const LLNotificationPtr& notification) LLUUID from_id = notification->getPayload()["from_id"]; LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, from_id); - LLIMFloater* im_floater = LLFloaterReg::findTypedInstance("impanel", session_id); + LLFloaterIMSession* im_floater = LLFloaterReg::findTypedInstance("impanel", session_id); if (im_floater != NULL) { @@ -164,7 +164,7 @@ void LLHandlerUtil::logGroupNoticeToIMGroup( // static void LLHandlerUtil::logToNearbyChat(const LLNotificationPtr& notification, EChatSourceType type) { - LLNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); if (nearby_chat) { LLChat chat_msg(notification->getMessage()); @@ -244,7 +244,7 @@ void LLHandlerUtil::addNotifPanelToIM(const LLNotificationPtr& notification) // static void LLHandlerUtil::updateIMFLoaterMesages(const LLUUID& session_id) { - LLIMFloater* im_floater = LLIMFloater::findInstance(session_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); if (im_floater != NULL && im_floater->getVisible()) { im_floater->updateMessages(); diff --git a/indra/newview/llnotificationmanager.cpp b/indra/newview/llnotificationmanager.cpp index 2862ad6962..56f13802e3 100644 --- a/indra/newview/llnotificationmanager.cpp +++ b/indra/newview/llnotificationmanager.cpp @@ -31,7 +31,7 @@ #include "llnotificationmanager.h" -#include "llnearbychathandler.h" +#include "llfloaterimnearbychathandler.h" #include "llnotifications.h" #include @@ -64,7 +64,7 @@ void LLNotificationManager::init() mChannels.push_back(new LLOutboxNotification()); mChannels.push_back(new LLIMHandler()); - mChatHandler = boost::shared_ptr(new LLNearbyChatHandler()); + mChatHandler = boost::shared_ptr(new LLFloaterIMNearbyChatHandler()); } //-------------------------------------------------------------------------- diff --git a/indra/newview/llnotificationmanager.h b/indra/newview/llnotificationmanager.h index c8afdf9e46..f37c6b833c 100644 --- a/indra/newview/llnotificationmanager.h +++ b/indra/newview/llnotificationmanager.h @@ -60,7 +60,7 @@ public: void onChat(const LLChat& msg, const LLSD &args); private: - boost::shared_ptr mChatHandler; + boost::shared_ptr mChatHandler; std::vector mChannels; }; diff --git a/indra/newview/llnotificationtiphandler.cpp b/indra/newview/llnotificationtiphandler.cpp index a293e6acb6..faa67b5ea4 100644 --- a/indra/newview/llnotificationtiphandler.cpp +++ b/indra/newview/llnotificationtiphandler.cpp @@ -28,8 +28,8 @@ #include "llviewerprecompiledheaders.h" // must be first include #include "llfloaterreg.h" -#include "llnearbychat.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llnotificationhandler.h" #include "llnotifications.h" #include "lltoastnotifypanel.h" @@ -85,7 +85,7 @@ bool LLTipHandler::processNotification(const LLNotificationPtr& notification) LLHandlerUtil::logToNearbyChat(notification, CHAT_SOURCE_SYSTEM); // don't show toast if Nearby Chat is opened - LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); if (nearby_chat->isChatVisible()) { return false; diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp index 9a4d1166db..9f89b5f809 100644 --- a/indra/newview/llparticipantlist.cpp +++ b/indra/newview/llparticipantlist.cpp @@ -28,7 +28,7 @@ #include "llavatarnamecache.h" #include "llimview.h" -#include "llimfloatercontainer.h" +#include "llfloaterimcontainer.h" #include "llparticipantlist.h" #include "llspeakers.h" @@ -316,7 +316,7 @@ bool LLParticipantList::onSpeakerUpdateEvent(LLPointer eve if ( evt_data.has("id") ) { LLUUID participant_id = evt_data["id"]; - LLIMFloaterContainer* im_box = LLIMFloaterContainer::findInstance(); + LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); if (im_box) { im_box->setTimeNow(mUUID,participant_id); @@ -345,7 +345,7 @@ bool LLParticipantList::onModeratorUpdateEvent(LLPointer e mModeratorList.erase(id); } } - // *TODO : do we have to fire an event so that LLIMConversation::refreshConversation() gets called + // *TODO : do we have to fire an event so that LLFloaterIMSessionTab::refreshConversation() gets called } } return true; diff --git a/indra/newview/llscreenchannel.cpp b/indra/newview/llscreenchannel.cpp index a4a0198305..3bcf36ffde 100644 --- a/indra/newview/llscreenchannel.cpp +++ b/indra/newview/llscreenchannel.cpp @@ -39,7 +39,7 @@ #include "lldockablefloater.h" #include "llsyswellwindow.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" #include "llscriptfloater.h" #include "llrootview.h" diff --git a/indra/newview/llscriptfloater.cpp b/indra/newview/llscriptfloater.cpp index 6f98be1cb8..dc12192697 100644 --- a/indra/newview/llscriptfloater.cpp +++ b/indra/newview/llscriptfloater.cpp @@ -41,7 +41,7 @@ #include "lltoastscripttextbox.h" #include "lltrans.h" #include "llviewerwindow.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index c827b39d0e..8932d12e20 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -54,7 +54,7 @@ #include "llfloaterreg.h" #include "llfocusmgr.h" #include "llhttpsender.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" #include "lllocationhistory.h" #include "llimageworker.h" @@ -63,8 +63,8 @@ #include "llmemorystream.h" #include "llmessageconfig.h" #include "llmoveview.h" -#include "llimfloatercontainer.h" -#include "llnearbychat.h" +#include "llfloaterimcontainer.h" +#include "llfloaterimnearbychat.h" #include "llnotifications.h" #include "llnotificationsutil.h" #include "llteleporthistory.h" @@ -1384,7 +1384,7 @@ bool idle_startup() // create a container's instance for start a controlling conversation windows // by the voice's events - LLIMFloaterContainer::getInstance(); + LLFloaterIMContainer::getInstance(); // *Note: this is where gWorldMap used to be initialized. diff --git a/indra/newview/lltoastnotifypanel.cpp b/indra/newview/lltoastnotifypanel.cpp index 4a49922656..65b4a3a44c 100644 --- a/indra/newview/lltoastnotifypanel.cpp +++ b/indra/newview/lltoastnotifypanel.cpp @@ -40,7 +40,7 @@ #include "lltrans.h" #include "llnotificationsutil.h" #include "llviewermessage.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" const S32 BOTTOM_PAD = VPAD * 3; const S32 IGNORE_BTN_TOP_DELTA = 3*VPAD;//additional ignore_btn padding diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index b99d04abae..c6b28b9e5e 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -71,7 +71,7 @@ #include "llfloatermediasettings.h" #include "llfloaterhud.h" #include "llfloaterimagepreview.h" -#include "llimfloater.h" +#include "llfloaterimsession.h" #include "llfloaterinspect.h" #include "llfloaterinventory.h" #include "llfloaterjoystick.h" @@ -120,14 +120,14 @@ #include "llfloaterwhitelistentry.h" #include "llfloaterwindowsize.h" #include "llfloaterworldmap.h" -#include "llimfloatercontainer.h" +#include "llfloaterimcontainer.h" #include "llinspectavatar.h" #include "llinspectgroup.h" #include "llinspectobject.h" #include "llinspectremoteobject.h" #include "llinspecttoast.h" #include "llmoveview.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llpanelblockedlist.h" #include "llpanelclassified.h" #include "llpreviewanim.h" @@ -193,7 +193,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("camera", "floater_camera.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("chat_voice", "floater_voice_chat_volume.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLNearbyChat::buildFloater); + LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterIMNearbyChat::buildFloater); LLFloaterReg::add("compile_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("conversation", "floater_conversation_log.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); @@ -217,8 +217,8 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("help_browser", "floater_help_browser.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("hud", "floater_hud.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("impanel", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("im_container", "floater_im_container.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("impanel", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("im_container", "floater_im_container.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("im_well_window", "floater_sys_well.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("incoming_call", "floater_incoming_call.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("inventory", "floater_my_inventory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/llviewergesture.cpp b/indra/newview/llviewergesture.cpp index 71608b5280..3f35a5001d 100644 --- a/indra/newview/llviewergesture.cpp +++ b/indra/newview/llviewergesture.cpp @@ -41,7 +41,7 @@ #include "llviewermessage.h" // send_guid_sound_trigger #include "llviewernetwork.h" #include "llagent.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" // Globals LLViewerGestureList gGestureList; @@ -131,7 +131,7 @@ void LLViewerGesture::doTrigger( BOOL send_chat ) { // Don't play nodding animation, since that might not blend // with the gesture animation. - (LLFloaterReg::getTypedInstance("nearby_chat"))-> + (LLFloaterReg::getTypedInstance("nearby_chat"))-> sendChatFromViewer(mOutputString, CHAT_TYPE_NORMAL, FALSE); } } diff --git a/indra/newview/llviewerkeyboard.cpp b/indra/newview/llviewerkeyboard.cpp index f8e988bc0c..4ecdc31e21 100644 --- a/indra/newview/llviewerkeyboard.cpp +++ b/indra/newview/llviewerkeyboard.cpp @@ -32,7 +32,7 @@ #include "llmath.h" #include "llagent.h" #include "llagentcamera.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llviewercontrol.h" #include "llfocusmgr.h" #include "llmorphview.h" @@ -535,7 +535,7 @@ void stop_moving( EKeystate s ) void start_chat( EKeystate s ) { // start chat - LLNearbyChat::startChat(NULL); + LLFloaterIMNearbyChat::startChat(NULL); } void start_gesture( EKeystate s ) @@ -544,15 +544,15 @@ void start_gesture( EKeystate s ) if (KEYSTATE_UP == s && ! (focus_ctrlp && focus_ctrlp->acceptsTextInput())) { - if ((LLFloaterReg::getTypedInstance("nearby_chat"))->getCurrentChat().empty()) + if ((LLFloaterReg::getTypedInstance("nearby_chat"))->getCurrentChat().empty()) { // No existing chat in chat editor, insert '/' - LLNearbyChat::startChat("/"); + LLFloaterIMNearbyChat::startChat("/"); } else { // Don't overwrite existing text in chat editor - LLNearbyChat::startChat(NULL); + LLFloaterIMNearbyChat::startChat(NULL); } } } diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 1ddfc51f27..47249fad70 100755 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -68,7 +68,7 @@ #include "llinventoryfunctions.h" #include "llinventoryobserver.h" #include "llinventorypanel.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llnotifications.h" #include "llnotificationsutil.h" #include "llpanelgrouplandmoney.h" @@ -2295,7 +2295,7 @@ void god_message_name_cb(const LLAvatarName& av_name, LLChat chat, std::string m // Treat like a system message and put in chat history. chat.mText = av_name.getCompleteName() + ": " + message; - LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); if (nearby_chat) { nearby_chat->addMessage(chat); @@ -2877,7 +2877,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) // Note: lie to Nearby Chat, pretending that this is NOT an IM, because // IMs from obejcts don't open IM sessions. - LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); if(!chat_from_system && nearby_chat) { chat.mOwnerID = from_id; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index ee838b19b7..afc3e3965c 100755 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -188,7 +188,7 @@ #include "llviewerjoystick.h" #include "llviewernetwork.h" #include "llpostprocess.h" -#include "llnearbychat.h" +#include "llfloaterimnearbychat.h" #include "llagentui.h" #include "llwearablelist.h" @@ -2496,7 +2496,7 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) return TRUE; } - LLNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); // Traverses up the hierarchy if( keyboard_focus ) @@ -2574,7 +2574,7 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) LLFloaterReg::toggleInstanceOrBringToFront(name); } - LLChatEntry* chat_editor = LLFloaterReg::findTypedInstance("nearby_chat")->getChatBox(); + LLChatEntry* chat_editor = LLFloaterReg::findTypedInstance("nearby_chat")->getChatBox(); if (chat_editor) { // passing NULL here, character will be added later when it is handled by character handler. -- cgit v1.3 From e6000d5930dd646bb15951336670bab4950515d8 Mon Sep 17 00:00:00 2001 From: "maxim@mnikolenko" Date: Wed, 23 Jan 2013 14:32:18 +0200 Subject: CHUI-424 FIXED Args for appendMessage() are added to respect Compact/Expanded mode --- indra/newview/llfloaterconversationpreview.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index c93181c0a1..48e0caa0ce 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -152,7 +152,13 @@ void LLFloaterConversationPreview::showHistory() chat.mSourceType = LLFloaterIMNearbyChat::isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; } - mChatHistory->appendMessage(chat); + LLSD chat_args; + chat_args["use_plain_text_chat_history"] = + gSavedSettings.getBOOL("PlainTextChatHistory"); + chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); + chat_args["show_names_for_p2p_conv"] = gSavedSettings.getBOOL("IMShowNamesForP2PConv"); + + mChatHistory->appendMessage(chat,chat_args); } } -- cgit v1.3 From 4b6e1c26f05d89aa130a899803553940069457c0 Mon Sep 17 00:00:00 2001 From: maksymsproductengine Date: Fri, 22 Feb 2013 17:08:33 +0200 Subject: CHUI-633 FIXED User is not able to open chat history by 'View chat history' menu item from pop-up menu of People floater --- indra/newview/llavataractions.cpp | 14 +++++++++++++- indra/newview/llfloaterconversationpreview.cpp | 17 ++++++++++++++--- indra/newview/llfloaterconversationpreview.h | 5 +++++ indra/newview/lllogchat.cpp | 26 ++++++++++++++++++++++++++ indra/newview/lllogchat.h | 1 + indra/newview/llpanelpeoplemenus.cpp | 7 ++++++- 6 files changed, 65 insertions(+), 5 deletions(-) (limited to 'indra/newview/llfloaterconversationpreview.cpp') diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index d6e457887b..ce063a9887 100755 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -44,6 +44,7 @@ #include "llcallingcard.h" // for LLAvatarTracker #include "llconversationlog.h" #include "llfloateravatarpicker.h" // for LLFloaterAvatarPicker +#include "llfloaterconversationpreview.h" #include "llfloatergroupinvite.h" #include "llfloatergroups.h" #include "llfloaterreg.h" @@ -926,9 +927,20 @@ void LLAvatarActions::viewChatHistory(const LLUUID& id) if (iter->getParticipantID() == id) { LLFloaterReg::showInstance("preview_conversation", iter->getSessionID(), true); - break; + return; } } + + if (LLLogChat::isTranscriptExist(id)) + { + LLAvatarName avatar_name; + LLSD extended_id(id); + + LLAvatarNameCache::get(id, &avatar_name); + extended_id[LL_FCP_COMPLETE_NAME] = avatar_name.getCompleteName(); + extended_id[LL_FCP_ACCOUNT_NAME] = avatar_name.getAccountName(); + LLFloaterReg::showInstance("preview_conversation", extended_id, true); + } } //== private methods ======================================================================================== diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index 48e0caa0ce..a3d715530d 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -33,13 +33,19 @@ #include "llspinctrl.h" #include "lltrans.h" +const std::string LL_FCP_COMPLETE_NAME("complete_name"); +const std::string LL_FCP_ACCOUNT_NAME("user_name"); + LLFloaterConversationPreview::LLFloaterConversationPreview(const LLSD& session_id) : LLFloater(session_id), mChatHistory(NULL), mSessionID(session_id.asUUID()), mCurrentPage(0), - mPageSize(gSavedSettings.getS32("ConversationHistoryPageSize")) -{} + mPageSize(gSavedSettings.getS32("ConversationHistoryPageSize")), + mAccountName(session_id[LL_FCP_ACCOUNT_NAME]), + mCompleteName(session_id[LL_FCP_COMPLETE_NAME]) +{ +} BOOL LLFloaterConversationPreview::postBuild() { @@ -49,7 +55,12 @@ BOOL LLFloaterConversationPreview::postBuild() std::string name; std::string file; - if (mSessionID != LLUUID::null && conv) + if (mAccountName != "") + { + name = mCompleteName; + file = mAccountName; + } + else if (mSessionID != LLUUID::null && conv) { name = conv->getConversationName(); file = conv->getHistoryFileName(); diff --git a/indra/newview/llfloaterconversationpreview.h b/indra/newview/llfloaterconversationpreview.h index 0341e5d2a0..b17ae84b63 100644 --- a/indra/newview/llfloaterconversationpreview.h +++ b/indra/newview/llfloaterconversationpreview.h @@ -29,6 +29,9 @@ #include "llchathistory.h" #include "llfloater.h" +extern const std::string LL_FCP_COMPLETE_NAME; //"complete_name" +extern const std::string LL_FCP_ACCOUNT_NAME; //"user_name" + class LLSpinCtrl; class LLFloaterConversationPreview : public LLFloater @@ -54,6 +57,8 @@ private: int mPageSize; std::list mMessages; + std::string mAccountName; + std::string mCompleteName; }; #endif /* LLFLOATERCONVERSATIONPREVIEW_H_ */ diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index 17b72c5023..09f816a4e6 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -28,6 +28,7 @@ #include "llagent.h" #include "llagentui.h" +#include "llavatarnamecache.h" #include "lllogchat.h" #include "lltrans.h" #include "llviewercontrol.h" @@ -540,6 +541,31 @@ void LLLogChat::deleteTranscripts() LLFloaterIMSessionTab::processChatHistoryStyleUpdate(true); } +// static +bool LLLogChat::isTranscriptExist(const LLUUID& avatar_id) +{ + std::vector list_of_transcriptions; + LLLogChat::getListOfTranscriptFiles(list_of_transcriptions); + + if (list_of_transcriptions.size() > 0) + { + LLAvatarName avatar_name; + LLAvatarNameCache::get(avatar_id, &avatar_name); + std::string avatar_user_name = avatar_name.getAccountName(); + std::replace(avatar_user_name.begin(), avatar_user_name.end(), '.', '_'); + + BOOST_FOREACH(std::string& transcript_file_name, list_of_transcriptions) + { + if (std::string::npos != transcript_file_name.find(avatar_user_name)) + { + return true; + } + } + } + + return false; +} + //*TODO mark object's names in a special way so that they will be distinguishable form avatar name //which are more strict by its nature (only firstname and secondname) //Example, an object's name can be written like "Object " diff --git a/indra/newview/lllogchat.h b/indra/newview/lllogchat.h index 5fbb4ade96..b981d9ce04 100644 --- a/indra/newview/lllogchat.h +++ b/indra/newview/lllogchat.h @@ -57,6 +57,7 @@ public: static boost::signals2::connection setSaveHistorySignal(const save_history_signal_t::slot_type& cb); static void deleteTranscripts(); + static bool isTranscriptExist(const LLUUID& avatar_id); private: static std::string cleanFileName(std::string filename); diff --git a/indra/newview/llpanelpeoplemenus.cpp b/indra/newview/llpanelpeoplemenus.cpp index 61e9468ce5..47d6b49a50 100644 --- a/indra/newview/llpanelpeoplemenus.cpp +++ b/indra/newview/llpanelpeoplemenus.cpp @@ -37,6 +37,7 @@ #include "llagentdata.h" // for gAgentID #include "llavataractions.h" #include "llcallingcard.h" // for LLAvatarTracker +#include "lllogchat.h" #include "llviewermenu.h" // for gMenuHolder namespace LLPanelPeopleMenus @@ -180,7 +181,11 @@ bool NearbyMenu::enableContextMenuItem(const LLSD& userdata) { return LLAvatarActions::canOfferTeleport(mUUIDs); } - else if (item == std::string("can_im") || item == std::string("can_callog") || item == std::string("can_invite") || + else if (item == std::string("can_callog")) + { + return LLLogChat::isTranscriptExist(mUUIDs.front()); + } + else if (item == std::string("can_im") || item == std::string("can_invite") || item == std::string("can_share") || item == std::string("can_pay")) { return true; -- cgit v1.3