diff options
Diffstat (limited to 'indra/newview/llfloaterimcontainer.cpp')
-rw-r--r-- | indra/newview/llfloaterimcontainer.cpp | 5000 |
1 files changed, 2500 insertions, 2500 deletions
diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 2f8ada98cb..2d398de61a 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -1,2500 +1,2500 @@ -/**
- * @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 "lldonotdisturbnotificationstorage.h"
-#include "llgroupactions.h"
-#include "llgroupiconctrl.h"
-#include "llflashtimer.h"
-#include "llfloateravatarpicker.h"
-#include "llfloaterpreference.h"
-#include "llfloaterreporter.h"
-#include "llimview.h"
-#include "llnotificationsutil.h"
-#include "lltoolbarview.h"
-#include "lltransientfloatermgr.h"
-#include "llviewercontrol.h"
-#include "llconversationview.h"
-#include "llcallbacklist.h"
-#include "llworld.h"
-#include "llsdserialize.h"
-#include "llviewermenu.h" // is_agent_mappable
-#include "llviewerobjectlist.h"
-
-
-const S32 EVENTS_PER_IDLE_LOOP_CURRENT_SESSION = 80;
-const S32 EVENTS_PER_IDLE_LOOP_BACKGROUND = 40;
-const F32 EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE = 0.01f; // process a minimum of 1% of total events per frame
-
-//
-// LLFloaterIMContainer
-//
-LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed, const Params& params /*= getDefaultParams()*/)
-: LLMultiFloater(seed, params),
- mExpandCollapseBtn(NULL),
- mConversationsRoot(NULL),
- mConversationsEventStream("ConversationsEvents"),
- mInitialized(false),
- mIsFirstLaunch(true),
- mConversationEventQueue()
-{
- 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));
- mEnableCallbackRegistrar.add("Avatar.VisibleItem", boost::bind(&LLFloaterIMContainer::visibleContextMenuItem, 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);
-
- if (mMicroChangedSignal.connected())
- {
- mMicroChangedSignal.disconnect();
- }
-
- gSavedPerAccountSettings.setBOOL("ConversationsListPaneCollapsed", mConversationsPane->isCollapsed());
- gSavedPerAccountSettings.setBOOL("ConversationsMessagePaneCollapsed", mMessagesPane->isCollapsed());
- gSavedPerAccountSettings.setBOOL("ConversationsParticipantListCollapsed", !isParticipantListExpanded());
-
- if (LLIMMgr::instanceExists())
- {
- LLIMMgr::getInstance()->removeSessionObserver(this);
- }
-}
-
-void LLFloaterIMContainer::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg)
-{
- addConversationListItem(session_id);
- LLFloaterIMSessionTab::addToHost(session_id);
-}
-
-void LLFloaterIMContainer::sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id)
-{
- if(!isInVisibleChain())
- {
- setVisibleAndFrontmost(false);
- }
- selectConversationPair(session_id, true);
- collapseMessagesPane(false);
-}
-
-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)
-{
- // The general strategy when a session id is modified is to delete all related objects and create them anew.
-
- // Note however that the LLFloaterIMSession has its session id updated through a call to sessionInitReplyReceived()
- // and do not need to be deleted and recreated (trying this creates loads of problems). We do need however to suppress
- // its related mSessions record as it's indexed with the wrong id.
- // Grabbing the updated LLFloaterIMSession and readding it in mSessions will eventually be done by addConversationListItem().
- mSessions.erase(old_session_id);
-
- // Delete the model and participants related to the old session
- bool change_focus = removeConversationListItem(old_session_id);
-
- // Create a new conversation with the new id
- addConversationListItem(new_session_id, change_focus);
- LLFloaterIMSessionTab::addToHost(new_session_id);
-}
-
-
-LLConversationItem* LLFloaterIMContainer::getSessionModel(const LLUUID& session_id)
-{
- conversations_items_map::iterator iter = mConversationsItems.find(session_id);
- if (iter == mConversationsItems.end())
- {
- return NULL;
- }
- else
- {
- return iter->second.get();
- }
-}
-
-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()
-{
- mOrigMinWidth = getMinWidth();
- mOrigMinHeight = getMinHeight();
-
- 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<LLTabContainer>("im_box_tab_container"));
- mStubPanel = getChild<LLPanel>("stub_panel");
- mStubTextBox = getChild<LLTextBox>("stub_textbox");
- mStubTextBox->setURLClickedCallback(boost::bind(&LLFloaterIMContainer::returnFloaterToHost, this));
-
- mConversationsStack = getChild<LLLayoutStack>("conversations_stack");
- mConversationsPane = getChild<LLLayoutPanel>("conversations_layout_panel");
- mMessagesPane = getChild<LLLayoutPanel>("messages_layout_panel");
-
- mConversationsListPanel = getChild<LLPanel>("conversations_list_panel");
-
- // Open IM session with selected participant on double click event
- mConversationsListPanel->setDoubleClickCallback(boost::bind(&LLFloaterIMContainer::doToSelected, this, LLSD("im")));
-
- // The resize limits for LLFloaterIMContainer should be updated, based on current values of width of conversation and message panels
- mConversationsPane->getResizeBar()->setResizeListener(boost::bind(&LLFloaterIMContainer::assignResizeLimits, this));
-
- // Create the root model and view for all conversation sessions
- LLConversationItem* base_item = new LLConversationItem(getRootViewModel());
-
- LLFolderView::Params p(LLUICtrlFactory::getDefaultParams<LLFolderView>());
- 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<LLFolderView>(p);
- mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar);
- mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar);
-
- // 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);
- scroller_view_rect.mBottom += getChild<LLLayoutStack>("conversations_pane_buttons_stack")->getRect().getHeight();
- LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams<LLFolderViewScrollContainer>());
- scroller_params.rect(scroller_view_rect);
-
- LLScrollContainer* scroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(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<LLButton>("expand_collapse_btn");
- mExpandCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onExpandCollapseButtonClicked, this));
- mStubCollapseBtn = getChild<LLButton>("stub_collapse_btn");
- mStubCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onStubCollapseButtonClicked, this));
- mSpeakBtn = getChild<LLButton>("speak_btn");
-
- mSpeakBtn->setMouseDownCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonPressed, this));
- mSpeakBtn->setMouseUpCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonReleased, this));
-
- childSetAction("add_btn", boost::bind(&LLFloaterIMContainer::onAddButtonClicked, this));
-
- collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed"));
- collapseConversationsPane(gSavedPerAccountSettings.getBOOL("ConversationsListPaneCollapsed"), false);
- LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate, false));
- mMicroChangedSignal = LLVoiceClient::getInstance()->MicroChangedCallback(boost::bind(&LLFloaterIMContainer::updateSpeakBtnState, this));
-
- if (! mMessagesPane->isCollapsed() && ! mConversationsPane->isCollapsed())
- {
- S32 conversations_panel_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth");
- LLRect conversations_panel_rect = mConversationsPane->getRect();
- conversations_panel_rect.mRight = conversations_panel_rect.mLeft + conversations_panel_width;
- mConversationsPane->handleReshape(conversations_panel_rect, true);
- }
-
- // Init the sort order now that the root had been created
- setSortOrder(LLConversationSort(gSavedSettings.getU32("ConversationSortOrder")));
-
- //We should expand nearby chat participants list for the new user
- if(gAgent.isFirstLogin() || !gSavedPerAccountSettings.getBOOL("ConversationsParticipantListCollapsed"))
- {
- expandConversation();
- }
- // Keep the xml set title around for when we have to overwrite it
- mGeneralTitle = getTitle();
-
- mInitialized = true;
-
- mIsFirstOpen = 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::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLFloaterIMContainer::processParticipantsStyleUpdate, this));
-
- mParticipantRefreshTimer.setTimerExpirySec(0);
- mParticipantRefreshTimer.start();
-
- return true;
-}
-
-void LLFloaterIMContainer::onOpen(const LLSD& key)
-{
- LLMultiFloater::onOpen(key);
- reSelectConversation();
- assignResizeLimits();
-
- LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession);
- session_floater->onOpen(key);
-}
-
-// 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;
- }
-
- LLUUID session_id = floaterp->getKey();
-
- // Add the floater
- LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point);
-
-
-
- LLIconCtrl* icon = 0;
- bool is_in_group = gAgent.isInGroup(session_id, true);
- LLUUID icon_id;
-
- if (is_in_group)
- {
- LLGroupIconCtrl::Params icon_params;
- icon_params.group_id = session_id;
- icon = LLUICtrlFactory::instance().create<LLGroupIconCtrl>(icon_params);
- icon_id = session_id;
-
- 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<LLAvatarIconCtrl>(icon_params);
- icon_id = avatar_id;
-
- mSessions[session_id] = floaterp;
- floaterp->mCloseSignal.connect(boost::bind(&LLFloaterIMContainer::onCloseFloater, this, session_id));
- }
-
- LLFloaterIMSessionTab* floater = LLFloaterIMSessionTab::getConversation(session_id);
- if (floater)
- {
- floater->updateChatIcon(icon_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);
-}
-
-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::onStubCollapseButtonClicked()
-{
- collapseMessagesPane(true);
-}
-
-void LLFloaterIMContainer::onSpeakButtonPressed()
-{
- LLVoiceClient::getInstance()->inputUserControlState(true);
- updateSpeakBtnState();
-}
-
-void LLFloaterIMContainer::onSpeakButtonReleased()
-{
- LLVoiceClient::getInstance()->inputUserControlState(false);
- updateSpeakBtnState();
-}
-
-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());
- }
- reSelectConversation();
-}
-
-LLFloaterIMContainer* LLFloaterIMContainer::findInstance()
-{
- return LLFloaterReg::findTypedInstance<LLFloaterIMContainer>("im_container");
-}
-
-LLFloaterIMContainer* LLFloaterIMContainer::getInstance()
-{
- return LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("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<LLConversationItemParticipant*>(*current_participant_model);
- if (participant_model)
- {
- // Get the avatar name for this participant id from the cache and update the model
- participant_model->updateName();
- }
- // Next participant
- current_participant_model++;
- }
- }
-}
-
-// static
-void LLFloaterIMContainer::idle(void* user_data)
-{
- LLFloaterIMContainer* self = static_cast<LLFloaterIMContainer*>(user_data);
-
- self->idleProcessEvents();
-
- if (!self->getVisible() || self->isMinimized())
- {
- return;
- }
- self->idleUpdate();
-}
-
-void LLFloaterIMContainer::idleUpdate()
-{
- 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);
- }
-
- U32 sort_order = mConversationViewModel.getSorter().getSortOrderParticipants();
-
- if (mParticipantRefreshTimer.hasExpired())
- {
- const LLConversationItem *current_session = getCurSelectedViewModelItem();
- if (current_session)
- {
- if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP)
- {
- // Update moderator options visibility
- LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = current_session->getChildrenBegin();
- LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = current_session->getChildrenEnd();
- bool is_moderator = isGroupModerator();
- bool can_ban = haveAbilityToBan();
- while (current_participant_model != end_participant_model)
- {
- LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(*current_participant_model);
- if (participant_model)
- {
- participant_model->setModeratorOptionsVisible(is_moderator);
- participant_model->setGroupBanVisible(can_ban && participant_model->getUUID() != gAgentID);
- }
-
- current_participant_model++;
- }
- }
-
- // Update floater's title as required by the currently selected session or use the default title
- LLFloaterIMSession * conversation_floaterp = LLFloaterIMSession::findInstance(current_session->getUUID());
- setTitle(conversation_floaterp && conversation_floaterp->needsTitleOverwrite() ? conversation_floaterp->getTitle() : mGeneralTitle);
- }
-
- mParticipantRefreshTimer.setTimerExpirySec(1.0f);
- }
-
- // 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 (sort_order == LLConversationFilter::SO_DISTANCE)
- {
- // almost real-time updates
- setNearbyDistances(); //calls arrange all
- }
- mConversationsRoot->update(); //arranges, resizes, heavy
-
- // "Manually" resize of mConversationsPane: same as temporarity cancellation of the flag "auto_resize=false" for it
- if (!mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed())
- {
- LLRect stack_rect = mConversationsStack->getRect();
- mConversationsPane->reshape(stack_rect.getWidth(), stack_rect.getHeight(), true);
- }
-}
-
-void LLFloaterIMContainer::idleProcessEvents()
-{
- LLUUID current_session_id = getSelectedSession();
- conversations_items_deque::iterator iter = mConversationEventQueue.begin();
- conversations_items_deque::iterator end = mConversationEventQueue.end();
- while (iter != end)
- {
- std::deque<LLSD> &events = iter->second;
- if (!events.empty())
- {
- S32 events_to_handle;
- S32 query_size = (S32)events.size();
- if (current_session_id == iter->first)
- {
- events_to_handle = EVENTS_PER_IDLE_LOOP_CURRENT_SESSION;
- }
- else
- {
- events_to_handle = EVENTS_PER_IDLE_LOOP_BACKGROUND;
- }
-
- if (events_to_handle <= query_size)
- {
- // Some groups can be very large and can generate huge amount of updates, scale processing up to keep up
- events_to_handle = llmax(events_to_handle, (S32)(query_size * EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE));
- }
- else
- {
- events_to_handle = query_size;
- }
-
- for (S32 i = 0; i < events_to_handle; i++)
- {
- handleConversationModelEvent(events.back());
- events.pop_back();
- }
- }
- iter++;
- }
-}
-
-bool LLFloaterIMContainer::onConversationModelEvent(const LLSD& event)
-{
- LLUUID id = event.get("session_uuid").asUUID();
- mConversationEventQueue[id].push_front(event);
- return true;
-}
-
-
-void LLFloaterIMContainer::handleConversationModelEvent(const LLSD& event)
-{
-
- // 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<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,session_id));
- if (!session_view)
- {
- // We skip events that are not associated with a session
- return;
- }
- LLConversationViewParticipant* participant_view = session_view->findParticipant(participant_id);
- LLFloaterIMSessionTab *conversation_floater = (session_id.isNull() ?
- (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("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")
- {
- LLConversationItem* item = getSessionModel(session_id);
- LLConversationItemSession* session_model = dynamic_cast<LLConversationItemSession*>(item);
- LLConversationItemParticipant* participant_model = (session_model ? session_model->findParticipant(participant_id) : NULL);
- LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(session_id);
- if (!participant_view && session_model && participant_model)
- {
- 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)
- {
- bool skip_updating = im_sessionp && im_sessionp->isGroupChat();
- conversation_floater->addConversationViewParticipant(participant_model, !skip_updating);
- }
- }
- 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();
- }
-
- mConversationViewModel.requestSortAll();
- mConversationsRoot->arrangeAll();
-}
-
-void LLFloaterIMContainer::draw()
-{
- 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);
- }
-}
-
-//Shows/hides the stub panel when a conversation floater is torn off
-void LLFloaterIMContainer::showStub(bool stub_is_visible)
-{
- S32 tabCount = 0;
- LLPanel * tabPanel = NULL;
-
- if(stub_is_visible)
- {
- tabCount = mTabContainer->getTabCount();
-
- //Hide all tabs even stub
- for(S32 i = 0; i < tabCount; ++i)
- {
- tabPanel = mTabContainer->getPanelByIndex(i);
-
- if(tabPanel)
- {
- tabPanel->setVisible(false);
- }
- }
-
- //Set the index to the stub panel since we will be showing the stub
- mTabContainer->setCurrentPanelIndex(0);
- }
-
- //Now show/hide the stub
- mStubPanel->setVisible(stub_is_visible);
-}
-
-// listener for click on mStubTextBox2
-void LLFloaterIMContainer::returnFloaterToHost()
-{
- LLUUID session_id = this->getSelectedSession();
- LLFloaterIMSessionTab* floater = LLFloaterIMSessionTab::getConversation(session_id);
- floater->onTearOffClicked();
-}
-
-void LLFloaterIMContainer::setMinimized(bool b)
-{
- bool was_minimized = isMinimized();
- LLMultiFloater::setMinimized(b);
-
- //Switching from minimized to un-minimized
- if(was_minimized && !b)
- {
- gToolBarView->flashCommand(LLCommandId("chat"), false);
- LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(mSelectedSession);
-
- if(session_floater && !session_floater->isTornOff())
- {
- //When in DND mode, remove stored IM notifications
- //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal
- if(gAgent.isDoNotDisturb() && mSelectedSession.notNull())
- {
- LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSelectedSession);
- }
- }
- }
-}
-
-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<LLFloaterIMNearbyChat>("nearby_chat");
- if ((nearby_chat == NULL) || mIsFirstOpen)
- {
- mIsFirstOpen = false;
- // 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);
- selectConversationPair(LLUUID(NULL), false, false);
- }
-
- flashConversationItemWidget(mSelectedSession,false);
-
- LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(mSelectedSession);
- if(session_floater && !session_floater->isMinimized())
- {
- //When in DND mode, remove stored IM notifications
- //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal
- if(gAgent.isDoNotDisturb() && mSelectedSession.notNull())
- {
- LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSelectedSession);
- }
- }
- }
-
- nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
- if (nearby_chat)
- {
- LLFloaterIMSessionTab::addToHost(LLUUID());
- }
-
- if (!LLFloater::isQuitRequested())
- {
- // 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<LLConversationViewSession*>(widget_it->second);
- if (widget)
- {
- LLFloater* session_floater = widget->getSessionFloater();
- if (session_floater != nearby_chat)
- {
- widget->setVisibleIfDetached(visible);
- }
- }
- }
- }
-
- // Now, do the normal multifloater show/hide
- LLMultiFloater::setVisible(visible);
-}
-
-void LLFloaterIMContainer::getDetachedConversationFloaters(floater_list_t& floaters)
-{
- LLFloaterIMNearbyChat *nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
-
- for (const auto& [key, fvi] : mConversationsWidgets)
- {
- LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(fvi);
- if (widget)
- {
- LLFloater* session_floater = widget->getSessionFloater();
-
- // Exclude nearby chat from output, as it should be handled separately
- if (session_floater && session_floater->isDetachedAndNotMinimized()
- && session_floater != nearby_chat)
- {
- floaters.push_back(session_floater);
- }
- }
- }
-}
-
-void LLFloaterIMContainer::setVisibleAndFrontmost(bool take_focus, const LLSD& key)
-{
- LLMultiFloater::setVisibleAndFrontmost(take_focus, key);
- // Do not select "Nearby Chat" conversation, since it will bring its window to front
- // Only select other sessions
- if (!getSelectedSession().isNull())
- {
- selectConversationPair(getSelectedSession(), false, take_focus);
- }
- if (mInitialized && mIsFirstLaunch)
- {
- collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed"));
- mIsFirstLaunch = false;
-}
-}
-
-void LLFloaterIMContainer::updateResizeLimits()
-{
- LLMultiFloater::updateResizeLimits();
- assignResizeLimits();
-}
-
-bool LLFloaterIMContainer::isMessagesPaneCollapsed()
-{
- return mMessagesPane->isCollapsed();
-}
-
-bool LLFloaterIMContainer::isConversationsPaneCollapsed()
-{
- return mConversationsPane->isCollapsed();
-}
-
-void LLFloaterIMContainer::collapseMessagesPane(bool collapse)
-{
- if (mMessagesPane->isCollapsed() == collapse)
- {
- return;
- }
-
- // Save current width of panels before collapsing/expanding right pane.
- S32 conv_pane_width = mConversationsPane->getRect().getWidth();
- S32 msg_pane_width = mMessagesPane->getRect().getWidth();
-
- if (collapse)
- {
- // Save the messages pane width before collapsing it.
- gSavedPerAccountSettings.setS32("ConversationsMessagePaneWidth", msg_pane_width);
-
- // Save the order in which the panels are closed to reverse user's last action.
- gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", mConversationsPane->isCollapsed());
- }
-
- mConversationsPane->setIgnoreReshape(collapse);
-
- // Show/hide the messages pane.
- mConversationsStack->collapsePanel(mMessagesPane, collapse);
-
- // Make sure layout is updated before resizing conversation pane.
- mConversationsStack->updateLayout();
-
- reshapeFloaterAndSetResizeLimits(collapse, gSavedPerAccountSettings.getS32("ConversationsMessagePaneWidth"));
-
- if (!collapse)
- {
- // Restore conversation's pane previous width after expanding messages pane.
- mConversationsPane->setTargetDim(conv_pane_width);
- }
-}
-
-void LLFloaterIMContainer::collapseConversationsPane(bool collapse, bool save_is_allowed /*=true*/)
-{
- if (mConversationsPane->isCollapsed() == collapse)
- {
- return;
- }
-
- LLView* button_panel = getChild<LLView>("conversations_pane_buttons_expanded");
- button_panel->setVisible(!collapse);
- mExpandCollapseBtn->setImageOverlay(getString(collapse ? "expand_icon" : "collapse_icon"));
-
- // Save current width of Conversation panel before collapsing/expanding right pane.
- S32 conv_pane_width = mConversationsPane->getRect().getWidth();
-
- if (collapse && save_is_allowed)
- {
- // Save the conversations pane width before collapsing it.
- gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", conv_pane_width);
-
- // Save the order in which the panels are closed to reverse user's last action.
- gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", !mMessagesPane->isCollapsed());
- }
-
- mConversationsStack->collapsePanel(mConversationsPane, collapse);
- if (!collapse)
- {
- // Make sure layout is updated before resizing conversation pane.
- mConversationsStack->updateLayout();
- // Restore conversation's pane previous width.
- mConversationsPane->setTargetDim(gSavedPerAccountSettings.getS32("ConversationsListPaneWidth"));
- }
-
- S32 delta_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth")
- - mConversationsPane->getMinDim() - mConversationsStack->getPanelSpacing() + 1;
-
- reshapeFloaterAndSetResizeLimits(collapse, delta_width);
-
- for (conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin();
- widget_it != mConversationsWidgets.end(); ++widget_it)
- {
- LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(widget_it->second);
- if (widget)
- {
- widget->toggleCollapsedMode(collapse);
-
- // force closing all open conversations when collapsing to minimized state
- if (collapse)
- {
- widget->setOpen(false);
- }
- widget->requestArrange();
- }
- }
-}
-
-void LLFloaterIMContainer::reshapeFloaterAndSetResizeLimits(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 at_least_one_panel_is_expanded =
- ! (mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed());
-
- setCanResize(at_least_one_panel_is_expanded);
- setCanMinimize(at_least_one_panel_is_expanded);
-
- assignResizeLimits();
-}
-
-void LLFloaterIMContainer::assignResizeLimits()
-{
- bool is_conv_pane_expanded = !mConversationsPane->isCollapsed();
- bool is_msg_pane_expanded = !mMessagesPane->isCollapsed();
-
- S32 summary_width_of_visible_borders = (is_msg_pane_expanded ? mConversationsStack->getPanelSpacing() : 0) + 1;
-
- S32 conv_pane_target_width = is_conv_pane_expanded
- ? ( is_msg_pane_expanded?mConversationsPane->getRect().getWidth():mConversationsPane->getExpandedMinDim() )
- : mConversationsPane->getMinDim();
-
- S32 msg_pane_min_width = is_msg_pane_expanded ? mMessagesPane->getExpandedMinDim() : 0;
- S32 new_min_width = conv_pane_target_width + msg_pane_min_width + summary_width_of_visible_borders;
-
- setResizeLimits(new_min_width, getMinHeight());
-
- mConversationsStack->updateLayout();
-}
-
-void LLFloaterIMContainer::onAddButtonClicked()
-{
- LLView * button = findChild<LLView>("conversations_pane_buttons_expanded")->findChild<LLButton>("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_prefp = LLFloaterReg::showTypedInstance<LLFloaterPreference>("preferences");
- if (floater_prefp)
- {
- floater_prefp->selectChatPanel();
- }
- }
- if ("privacy_preferences" == command)
- {
- LLFloaterPreference * floater_prefp = LLFloaterReg::showTypedInstance<LLFloaterPreference>("preferences");
- if (floater_prefp)
- {
- floater_prefp->selectPrivacyPanel();
- }
- }
- if ("Translating.Toggle" == command)
- {
- gSavedSettings.setBOOL("TranslateChat", !gSavedSettings.getBOOL("TranslateChat"));
- }
-}
-
-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);
- }
- if ("Translating.Enabled" == command)
- {
- return gSavedPerAccountSettings.getBOOL("TranslatingEnabled");
- }
- if ("Translating.On" == command)
- {
- return gSavedSettings.getBOOL("TranslateChat");
- }
- 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<LLFloaterIMNearbyChat>("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, bool participant_uuids/* = true*/)
-{
- const std::set<LLFolderViewItem*> selectedItems = mConversationsRoot->getSelectionList();
-
- std::set<LLFolderViewItem*>::const_iterator it = selectedItems.begin();
- const std::set<LLFolderViewItem*>::const_iterator it_end = selectedItems.end();
- LLConversationItem * conversationItem;
-
- for (; it != it_end; ++it)
- {
- conversationItem = static_cast<LLConversationItem *>((*it)->getViewModelItem());
-
- //When a one-on-one conversation exists, retrieve the participant id from the conversation floater
- if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1 && participant_uuids)
- {
- LLFloaterIMSession * conversation_floaterp = LLFloaterIMSession::findInstance(conversationItem->getUUID());
- LLUUID participant_id = conversation_floaterp->getOtherParticipantUUID();
- selected_uuids.push_back(participant_id);
- }
- else
- {
- selected_uuids.push_back(conversationItem->getUUID());
- }
- }
-}
-
-const LLConversationItem * LLFloaterIMContainer::getCurSelectedViewModelItem()
-{
- LLConversationItem * conversation_item = NULL;
-
- if(mConversationsRoot &&
- mConversationsRoot->getCurSelectedItem() &&
- mConversationsRoot->getCurSelectedItem()->getViewModelItem())
- {
- LLFloaterIMSessionTab *selected_session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession);
- if (selected_session_floater && !selected_session_floater->getHost() && selected_session_floater->getCurSelectedViewModelItem())
- {
- conversation_item = selected_session_floater->getCurSelectedViewModelItem();
- }
- else
- {
- conversation_item = static_cast<LLConversationItem *>(mConversationsRoot->getCurSelectedItem()->getViewModelItem());
- }
- }
-
- return conversation_item;
-}
-
-void LLFloaterIMContainer::getParticipantUUIDs(uuid_vec_t& selected_uuids)
-{
- //Find the conversation floater associated with the selected id
- const LLConversationItem * conversation_item = getCurSelectedViewModelItem();
-
- if (NULL == conversation_item)
- {
- return;
- }
-
- getSelectedUUIDs(selected_uuids);
-}
-
-void LLFloaterIMContainer::doToParticipants(const std::string& command, uuid_vec_t& selectedIDS)
-{
- if (selectedIDS.size() == 1)
- {
- const LLUUID& userID = selectedIDS.front();
- if ("view_profile" == command)
- {
- LLAvatarActions::showProfile(userID);
- }
- else if ("im" == command)
- {
- if (gAgent.getID() != userID)
- {
- LLAvatarActions::startIM(userID);
- }
- }
- else if ("offer_teleport" == command)
- {
- LLAvatarActions::offerTeleport(selectedIDS);
- }
- else if ("request_teleport" == command)
- {
- LLAvatarActions::teleportRequest(selectedIDS.front());
- }
- 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 ("zoom_in" == command)
- {
- handle_zoom_to_object(userID);
- }
- else if ("map" == command)
- {
- LLAvatarActions::showOnMap(userID);
- }
- else if ("share" == command)
- {
- LLAvatarActions::share(userID);
- }
- else if ("pay" == command)
- {
- LLAvatarActions::pay(userID);
- }
- else if ("report_abuse" == command)
- {
- LLAvatarName av_name;
- if (LLAvatarNameCache::get(userID, &av_name))
- {
- LLFloaterReporter::showFromAvatar(userID, av_name.getCompleteName());
- }
- else
- {
- LLFloaterReporter::showFromAvatar(userID, "not avaliable");
- }
- }
- else if ("block_unblock" == command)
- {
- LLAvatarActions::toggleMute(userID, LLMute::flagVoiceChat);
- }
- else if ("mute_unmute" == command)
- {
- LLAvatarActions::toggleMute(userID, LLMute::flagTextChat);
- }
- else if ("selected" == command || "mute_all" == command || "unmute_all" == command)
- {
- moderateVoice(command, userID);
- }
- else if ("toggle_allow_text_chat" == command)
- {
- toggleAllowTextChat(userID);
- }
- else if ("ban_member" == command)
- {
- banSelectedMember(userID);
- }
- }
- else if (selectedIDS.size() > 1)
- {
- if ("im" == command)
- {
- LLAvatarActions::startConference(selectedIDS);
- }
- else if ("offer_teleport" == command)
- {
- LLAvatarActions::offerTeleport(selectedIDS);
- }
- else if ("voice_call" == command)
- {
- LLAvatarActions::startAdhocCall(selectedIDS);
- }
- else if ("remove_friend" == command)
- {
- LLAvatarActions::removeFriendsDialog(selectedIDS);
- }
- }
-}
-
-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("close_selected_conversations" == command)
- {
- getSelectedUUIDs(selectedIDS,false);
- closeSelectedConversations(selectedIDS);
- }
- 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)
- {
- if (selectedIDS.size() > 0)
- {
- if(conversationItem->getType() == LLConversationItem::CONV_SESSION_GROUP)
- {
- LLFloaterReg::showInstance("preview_conversation", conversationItem->getUUID(), true);
- }
- else if(conversationItem->getType() == LLConversationItem::CONV_SESSION_AD_HOC)
- {
- LLConversation* conv = LLConversationLog::instance().findConversation(LLIMModel::getInstance()->findIMSession(conversationItem->getUUID()));
- if(conv)
- {
- LLFloaterReg::showInstance("preview_conversation", conv->getSessionID(), true);
- }
- }
- else
- {
- LLAvatarActions::viewChatHistory(selectedIDS.front());
- }
- }
- }
- else
- {
- if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1)
- {
- doToParticipants(command, selectedIDS);
- }
- }
- }
- //if there is no LLFloaterIMSession* instance for selected conversation it might be Nearby chat
- else
- {
- if(conversationItem->getType() == LLConversationItem::CONV_SESSION_NEARBY)
- {
- if("chat_history" == command)
- {
- LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true);
- }
-}
- }
-}
-
-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();
-
- if (action == "group_profile")
- {
- LLGroupActions::show(mSelectedSession);
- }
- else if (action == "activate_group")
- {
- LLGroupActions::activate(mSelectedSession);
- }
- else if (action == "leave_group")
- {
- LLGroupActions::leave(mSelectedSession);
- }
-}
-
-bool LLFloaterIMContainer::enableContextMenuItem(const LLSD& userdata)
-{
- const std::string& item = userdata.asString();
- uuid_vec_t uuids;
- getParticipantUUIDs(uuids);
-
-
- //If there is group or ad-hoc chat in multiselection, everything needs to be disabled
- if(uuids.size() > 1)
- {
- const std::set<LLFolderViewItem*> selectedItems = mConversationsRoot->getSelectionList();
- LLConversationItem * conversationItem;
- for(std::set<LLFolderViewItem*>::const_iterator it = selectedItems.begin(); it != selectedItems.end(); ++it)
- {
- conversationItem = static_cast<LLConversationItem *>((*it)->getViewModelItem());
- if((conversationItem->getType() == LLConversationItem::CONV_SESSION_GROUP) || (conversationItem->getType() == LLConversationItem::CONV_SESSION_AD_HOC))
- {
- return false;
- }
- }
- }
-
- if ("conversation_log" == item)
- {
- return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0;
- }
-
- //Enable Chat history item for ad-hoc and group conversations
- if ("can_chat_history" == item && uuids.size() > 0)
- {
- //Disable menu item if selected participant is user agent
- if(uuids.front() != gAgentID)
- {
- if (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_NEARBY)
- {
- return LLLogChat::isNearbyTranscriptExist();
- }
- else if (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_AD_HOC)
- {
- const LLConversation* conv = LLConversationLog::instance().findConversation(LLIMModel::getInstance()->findIMSession(uuids.front()));
- if(conv)
- {
- return LLLogChat::isAdHocTranscriptExist(conv->getHistoryFileName());
- }
- return false;
- }
- else
- {
- bool is_group = (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_GROUP);
- return LLLogChat::isTranscriptExist(uuids.front(),is_group);
- }
- }
- }
-
- // If nothing is selected(and selected item is not group chat), everything needs to be disabled
- if (uuids.size() <= 0)
- {
- if(getCurSelectedViewModelItem())
- {
- return getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_GROUP;
- }
- return false;
- }
-
- if("can_activate_group" == item)
- {
- LLUUID selected_group_id = getCurSelectedViewModelItem()->getUUID();
- return gAgent.getGroupID() != selected_group_id;
- }
-
- return enableContextMenuItem(item, uuids);
-}
-
-bool LLFloaterIMContainer::enableContextMenuItem(const std::string& item, uuid_vec_t& uuids)
-{
- // Extract the single select info
- bool is_single_select = (uuids.size() == 1);
- const LLUUID& single_id = uuids.front();
-
- if ("can_chat_history" == item && is_single_select)
- {
- return LLLogChat::isTranscriptExist(uuids.front(),false);
- }
-
- // Handle options that are applicable to all including the user agent
- if ("can_view_profile" == item)
- {
- return is_single_select;
- }
-
- bool is_moderator_option = ("can_moderate_voice" == item) || ("can_allow_text_chat" == item) || ("can_mute" == item) || ("can_unmute" == item);
-
- // Beyond that point, if only the user agent is selected, everything is disabled
- if (is_single_select && (single_id == gAgentID))
- {
- if (is_moderator_option)
- {
- return enableModerateContextMenuItem(item, true);
- }
- else
- {
- return false;
- }
- }
-
- // If the user agent is selected with others, everything is disabled
- for (uuid_vec_t::const_iterator id = uuids.begin(); id != uuids.end(); ++id)
- {
- if (gAgent.getID() == *id)
- {
- return false;
- }
- }
-
- // Handle all other options
- if (("can_invite" == item)
- || ("can_chat_history" == item)
- || ("can_share" == item)
- || ("can_pay" == item)
- || ("report_abuse" == item))
- {
- // Those menu items are enable only if a single avatar is selected
- return is_single_select;
- }
- else if ("can_block" == item)
- {
- return (is_single_select ? LLAvatarActions::canBlock(single_id) : false);
- }
- else if ("can_add" == item)
- {
- // We can add friends if:
- // - there is only 1 selected avatar (EXT-7389)
- // - this avatar is not already a friend
- return (is_single_select ? !LLAvatarActions::isFriend(single_id) : false);
- }
- else if ("can_delete" == item)
- {
- // We can remove friends if there are only friends among the selection
- bool result = true;
- for (uuid_vec_t::const_iterator id = uuids.begin(); id != uuids.end(); ++id)
- {
- result &= LLAvatarActions::isFriend(*id);
- }
- return result;
- }
- else if ("can_call" == item)
- {
- return LLAvatarActions::canCall();
- }
- else if ("can_open_voice_conversation" == item)
- {
- return is_single_select && LLAvatarActions::canCall();
- }
- else if ("can_open_voice_conversation" == item)
- {
- return is_single_select && LLAvatarActions::canCall();
- }
- else if ("can_zoom_in" == item)
- {
- return is_single_select && gObjectList.findObject(single_id);
- }
- else if ("can_show_on_map" == item)
- {
- return (is_single_select ? (LLAvatarTracker::instance().isBuddyOnline(single_id) && is_agent_mappable(single_id)) || gAgent.isGodlike() : false);
- }
- else if ("can_offer_teleport" == item)
- {
- return LLAvatarActions::canOfferTeleport(uuids);
- }
- else if ("can_ban_member" == item)
- {
- return canBanSelectedMember(single_id);
- }
- else if (is_moderator_option)
- {
- // *TODO : get that out of here...
- return enableModerateContextMenuItem(item);
- }
-
- // By default, options that not explicitely disabled are enabled
- return true;
-}
-
-bool LLFloaterIMContainer::checkContextMenuItem(const LLSD& userdata)
-{
- std::string item = userdata.asString();
- uuid_vec_t uuids;
- getParticipantUUIDs(uuids);
-
- return checkContextMenuItem(item, uuids);
-}
-
-bool LLFloaterIMContainer::checkContextMenuItem(const std::string& item, uuid_vec_t& uuids)
-{
- if (uuids.size() == 1)
- {
- if ("is_blocked" == item)
- {
- return LLMuteList::getInstance()->isMuted(uuids.front(), LLMute::flagVoiceChat);
- }
- else if (item == "is_muted")
- {
- return LLMuteList::getInstance()->isMuted(uuids.front(), LLMute::flagTextChat);
- }
- else if ("is_allowed_text_chat" == item)
- {
- const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant());
-
- if (NULL != speakerp)
- {
- return !speakerp->mModeratorMutedText;
- }
- }
- }
-
- return false;
-}
-
-bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata)
-{
- const LLConversationItem *conversation_item = getCurSelectedViewModelItem();
- if(!conversation_item)
- {
- return false;
- }
-
- const std::string& item = userdata.asString();
-
- if ("show_mute" == item)
- {
- return !isMuted(conversation_item->getUUID());
- }
- else if ("show_unmute" == item)
- {
- return isMuted(conversation_item->getUUID());
- }
-
- return true;
-}
-
-void LLFloaterIMContainer::showConversation(const LLUUID& session_id)
-{
- setVisibleAndFrontmost(false);
- selectConversationPair(session_id, true);
-
- LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id);
- if (session_floater)
- {
- session_floater->restoreFloater();
- }
-}
-
-void LLFloaterIMContainer::clearAllFlashStates()
-{
- conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin();
- for (;widget_it != mConversationsWidgets.end(); ++widget_it)
- {
- LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(widget_it->second);
- if (widget)
- {
- widget->setFlashState(false);
- }
- }
-}
-
-void LLFloaterIMContainer::selectConversation(const LLUUID& session_id)
-{
- selectConversationPair(session_id, true);
-}
-
-// Select the conversation *after* (or before if none after) the passed uuid conversation
-// Used to change the selection on key hits
-void LLFloaterIMContainer::selectNextConversationByID(const LLUUID& uuid)
-{
- bool new_selection = false;
- selectConversation(uuid);
- new_selection = selectNextorPreviousConversation(true);
- if (!new_selection)
- {
- selectNextorPreviousConversation(false);
- }
-}
-
-// Synchronous select the conversation item and the conversation floater
-bool LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater/*=true*/)
-{
- bool handled = true;
- LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id);
-
- /* widget processing */
- if (select_widget && mConversationsRoot->getSelectedCount() <= 1)
- {
- LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,session_id);
- if (widget && widget->getParentFolder())
- {
- widget->getParentFolder()->setSelection(widget, false, false);
- mConversationsRoot->scrollToShowSelection();
- }
- }
-
- /* floater processing */
-
- if (NULL != session_floater && !session_floater->isDead())
- {
- if (session_id != getSelectedSession())
- {
- // Store the active session
- setSelectedSession(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);
- }
- else
- {
- showStub(true);
- }
-
- //When in DND mode, remove stored IM notifications
- //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal
- if(gAgent.isDoNotDisturb() && session_id.notNull())
- {
- LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, session_id);
- }
- }
-
- // Set the focus on the selected floater
- if (!session_floater->hasFocus() && !session_floater->isMinimized())
- {
- session_floater->setFocus(focus_floater);
- }
- }
- flashConversationItemWidget(session_id,false);
- return handled;
-}
-
-void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& participant_id)
-{
- LLConversationItemSession* item = dynamic_cast<LLConversationItemSession*>(getSessionModel(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<LLConversationItemSession*>(getSessionModel(LLUUID()));
- if (item)
- {
- // Get the positions of the nearby avatars and their ids
- std::vector<LLVector3d> 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;
- }
-
- // 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)
- {
- LL_WARNS() << "Couldn't create conversation session item : " << display_name << LL_ENDL;
- return NULL;
- }
- item->renameItem(display_name);
- item->updateName(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<LLConversationItem*>(*current_participant_model);
- LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model);
- participant_view->addToFolder(widget);
- current_participant_model++;
- }
- }
-
- if (uuid.notNull() && im_sessionp->isP2PSessionType())
- {
- item->fetchAvatarName(false);
- }
-
- // Do that too for the conversation dialog
- LLFloaterIMSessionTab *conversation_floater = (uuid.isNull() ? (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat")) : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(uuid)));
- if (conversation_floater)
- {
- conversation_floater->buildConversationViewParticipant();
- }
-
- // set the widget to minimized mode if conversations pane is collapsed
- widget->toggleCollapsedMode(mConversationsPane->isCollapsed());
-
- if (isWidgetSelected || 0 == mConversationsRoot->getSelectedCount())
- {
- selectConversationPair(uuid, true);
- widget->requestArrange();
-
- // 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 is_widget_selected = false;
- LLFolderViewItem* new_selection = NULL;
- LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid);
- if (widget)
- {
- is_widget_selected = widget->isSelected();
- if (mConversationsRoot)
- {
- new_selection = mConversationsRoot->getNextFromChild(widget, false);
- if (!new_selection)
- {
- new_selection = mConversationsRoot->getPreviousFromChild(widget, false);
- }
- }
-
- // Will destroy views and delete models that are not assigned to any views
- widget->destroyView();
- }
-
- // Suppress the conversation items and widgets from their respective maps
- mConversationsItems.erase(uuid);
- mConversationsWidgets.erase(uuid);
- // Clear event query (otherwise reopening session in some way can bombard session with stale data)
- mConversationEventQueue.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)
- {
- if (mConversationsWidgets.size() == 1)
- {
- // If only one widget is left, it has to be the Nearby Chat. Select it directly.
- selectConversationPair(LLUUID(NULL), true);
- }
- else
- {
- LLConversationItem* vmi = dynamic_cast<LLConversationItem*>(new_selection->getViewModelItem());
- if (vmi)
- {
- selectConversationPair(vmi->getUUID(), true);
- }
- }
- }
- }
- return is_widget_selected;
-}
-
-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;
-
- //Indentation for aligning the p2p converstation image with the nearby chat arrow
- if(item->getType() == LLConversationItem::CONV_SESSION_1_ON_1)
- {
- params.folder_indentation = 3;
- }
-
- return LLUICtrlFactory::create<LLConversationViewSession>(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 = 27;
-
- return LLUICtrlFactory::create<LLConversationViewParticipant>(params);
-}
-
-bool LLFloaterIMContainer::enableModerateContextMenuItem(const std::string& userdata, bool is_self)
-{
- // 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) && !is_self)
- {
- 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()) && !is_self;
-}
-
-bool LLFloaterIMContainer::isGroupModerator()
-{
- LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant();
- if (NULL == speaker_manager)
- {
- LL_WARNS() << "Speaker manager is missing" << LL_ENDL;
- 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;
-}
-
-bool LLFloaterIMContainer::haveAbilityToBan()
-{
- LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant();
- if (NULL == speaker_manager)
- {
- LL_WARNS() << "Speaker manager is missing" << LL_ENDL;
- return false;
- }
- LLUUID group_uuid = speaker_manager->getSessionID();
-
- return gAgent.isInGroup(group_uuid) && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS);
-}
-
-bool LLFloaterIMContainer::canBanSelectedMember(const LLUUID& participant_uuid)
-{
- LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant();
- if (NULL == speaker_manager)
- {
- LL_WARNS() << "Speaker manager is missing" << LL_ENDL;
- return false;
- }
- LLUUID group_uuid = speaker_manager->getSessionID();
- LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid);
- if(!gdatap)
- {
- LL_WARNS("Groups") << "Unable to get group data for group " << group_uuid << LL_ENDL;
- return false;
- }
-
- if (gdatap->mPendingBanRequest)
- {
- return false;
- }
-
- if (gdatap->isRoleMemberDataComplete())
- {
- if (gdatap->mMembers.size())
- {
- LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find((participant_uuid));
- if (mi != gdatap->mMembers.end())
- {
- LLGroupMemberData* member_data = (*mi).second;
- // Is the member an owner?
- if (member_data && member_data->isInRole(gdatap->mOwnerRole))
- {
- return false;
- }
- }
- }
- }
-
- if( gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) &&
- gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS) )
- {
- return true;
- }
-
- return false;
-}
-
-void LLFloaterIMContainer::banSelectedMember(const LLUUID& participant_uuid)
-{
- LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant();
- if (NULL == speaker_manager)
- {
- LL_WARNS() << "Speaker manager is missing" << LL_ENDL;
- return;
- }
-
- LLUUID group_uuid = speaker_manager->getSessionID();
- LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid);
- if(!gdatap)
- {
- LL_WARNS("Groups") << "Unable to get group data for group " << group_uuid << LL_ENDL;
- return;
- }
-
- gdatap->banMemberById(participant_uuid);
-
-}
-
-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<LLIMSpeakerMgr*>(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<LLIMSpeakerMgr*> (
- 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<LLIMSpeakerMgr *>(getSpeakerMgrForSelectedParticipant());
-
- if (NULL != speaker_managerp)
- {
- speaker_managerp->moderateVoiceParticipant(avatar_id, unmute);
- }
-}
-
-LLSpeakerMgr * LLFloaterIMContainer::getSpeakerMgrForSelectedParticipant()
-{
- LLFolderViewItem *selectedItem = mConversationsRoot->getCurSelectedItem();
- if (NULL == selectedItem)
- {
- LL_WARNS() << "Current selected item is null" << LL_ENDL;
- return NULL;
- }
-
- 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 == selectedItem || iter->second == selectedItem->getParentFolder())
- {
- conversation_uuidp = &iter->first;
- break;
- }
- ++iter;
- }
- if (NULL == conversation_uuidp)
- {
- LL_WARNS() << "Cannot find conversation item widget" << LL_ENDL;
- return NULL;
- }
-
- return conversation_uuidp->isNull() ? (LLSpeakerMgr *)LLLocalSpeakerMgr::getInstance()
- : LLIMModel::getInstance()->getSpeakerManager(*conversation_uuidp);
-}
-
-LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp)
-{
- if (NULL == speaker_managerp)
- {
- LL_WARNS() << "Speaker manager is missing" << LL_ENDL;
- return NULL;
- }
-
- const LLConversationItem * participant_itemp = getCurSelectedViewModelItem();
- if (NULL == participant_itemp)
- {
- LL_WARNS() << "Cannot evaluate current selected view model item" << LL_ENDL;
- return NULL;
- }
-
- return speaker_managerp->findSpeaker(participant_itemp->getUUID());
-}
-
-void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid)
-{
- LLIMSpeakerMgr * speaker_managerp = dynamic_cast<LLIMSpeakerMgr*>(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)&&(!mConversationsPane->isCollapsed()))
- {
- LLConversationViewSession* nearby_chat = dynamic_cast<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,LLUUID()));
- if (nearby_chat)
- {
- reSelectConversation();
- nearby_chat->setOpen(true);
- }
- }
-}
-
-void LLFloaterIMContainer::reSelectConversation()
-{
- LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession);
- if (session_floater->getHost())
- {
- selectFloater(session_floater);
- }
-}
-
-void LLFloaterIMContainer::updateSpeakBtnState()
-{
- mSpeakBtn->setToggleState(LLVoiceClient::getInstance()->getUserPTTState());
- mSpeakBtn->setEnabled(LLAgent::isActionAllowed("speak"));
-}
-
-bool LLFloaterIMContainer::isConversationLoggingAllowed()
-{
- return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0;
-}
-
-void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, bool is_flashes)
-{
- //Finds the conversation line item to flash using the session_id
- LLConversationViewSession * widget = dynamic_cast<LLConversationViewSession *>(get_ptr_in_map(mConversationsWidgets,session_id));
-
- if (widget)
- {
- widget->setFlashState(is_flashes);
- }
-}
-
-void LLFloaterIMContainer::highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted)
-{
- //Finds the conversation line item to highlight using the session_id
- LLConversationViewSession * widget = dynamic_cast<LLConversationViewSession *>(get_ptr_in_map(mConversationsWidgets,session_id));
-
- if (widget)
- {
- widget->setHighlightState(is_highlighted);
- }
-}
-
-bool LLFloaterIMContainer::isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget)
-{
- llassert(conversation_item_widget != NULL);
-
- // make sure the widget is actually in the right spot first
- mConversationsRoot->arrange(NULL, NULL);
-
- // check whether the widget is in the visible portion of the scroll container
- LLRect widget_rect;
- conversation_item_widget->localRectToOtherView(conversation_item_widget->getLocalRect(), &widget_rect, mConversationsRoot);
- return !mConversationsRoot->getVisibleRect().overlaps(widget_rect);
-}
-
-bool LLFloaterIMContainer::handleKeyHere(KEY key, MASK mask )
-{
- bool handled = false;
-
- if(mask == MASK_ALT)
- {
- if (KEY_RETURN == key )
- {
- expandConversation();
- handled = true;
- }
-
- if ((KEY_DOWN == key ) || (KEY_RIGHT == key))
- {
- selectNextorPreviousConversation(true);
- handled = true;
- }
- if ((KEY_UP == key) || (KEY_LEFT == key))
- {
- selectNextorPreviousConversation(false);
- handled = true;
- }
- }
- return handled;
-}
-
-bool LLFloaterIMContainer::selectAdjacentConversation(bool focus_selected)
-{
- bool selectedAdjacentConversation = selectNextorPreviousConversation(true, focus_selected);
-
- if(!selectedAdjacentConversation)
- {
- selectedAdjacentConversation = selectNextorPreviousConversation(false, focus_selected);
- }
-
- return selectedAdjacentConversation;
-}
-
-bool LLFloaterIMContainer::selectNextorPreviousConversation(bool select_next, bool focus_selected)
-{
- if (mConversationsWidgets.size() > 1)
- {
- LLFolderViewItem* new_selection = NULL;
- LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,getSelectedSession());
- if (widget)
- {
- if(select_next)
- {
- new_selection = mConversationsRoot->getNextFromChild(widget, false);
- }
- else
- {
- new_selection = mConversationsRoot->getPreviousFromChild(widget, false);
- }
- if (new_selection)
- {
- LLConversationItem* vmi = dynamic_cast<LLConversationItem*>(new_selection->getViewModelItem());
- if (vmi)
- {
- selectConversationPair(vmi->getUUID(), true, focus_selected);
- return true;
- }
- }
- }
- }
- return false;
-}
-
-void LLFloaterIMContainer::expandConversation()
-{
- if(!mConversationsPane->isCollapsed())
- {
- LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,getSelectedSession()));
- if (widget)
- {
- widget->setOpen(!widget->isOpen());
- }
- }
-}
-bool LLFloaterIMContainer::isParticipantListExpanded()
-{
- bool is_expanded = false;
- if(!mConversationsPane->isCollapsed())
- {
- LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,getSelectedSession()));
- if (widget)
- {
- is_expanded = widget->isOpen();
- }
- }
- return is_expanded;
-}
-
-// By default, if torn off session is currently frontmost, LLFloater::isFrontmost() will return false, which can lead to some bugs
-// So LLFloater::isFrontmost() is overriden here to check both selected session and the IM floater itself
-// Exclude "Nearby Chat" session from the check, as "Nearby Chat" window and "Conversations" floater can be brought
-// to front independently
-/*virtual*/
-bool LLFloaterIMContainer::isFrontmost()
-{
- LLFloaterIMSessionTab* selected_session = LLFloaterIMSessionTab::getConversation(mSelectedSession);
- LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
- return (selected_session && selected_session->isFrontmost() && (selected_session != nearby_chat))
- || LLFloater::isFrontmost();
-}
-
-// For conversations, closeFloater() (linked to Ctrl-W) does not actually close the floater but the active conversation.
-// This is intentional so it doesn't confuse the user. onClickCloseBtn() closes the whole floater.
-void LLFloaterIMContainer::onClickCloseBtn(bool app_quitting/* = false*/)
-{
- gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", mConversationsPane->getRect().getWidth());
- LLMultiFloater::closeFloater(app_quitting);
-}
-
-void LLFloaterIMContainer::closeHostedFloater()
-{
- onClickCloseBtn();
-}
-
-void LLFloaterIMContainer::closeAllConversations()
-{
- std::vector<LLUUID> ids;
- for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++)
- {
- LLUUID session_id = it_session->first;
- if (session_id != LLUUID())
- {
- ids.push_back(session_id);
- }
- }
-
- for (std::vector<LLUUID>::const_iterator it = ids.begin(); it != ids.end(); ++it)
- {
- LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(*it);
- LLFloater::onClickClose(conversationFloater);
- }
-}
-
-void LLFloaterIMContainer::closeSelectedConversations(const uuid_vec_t& ids)
-{
- for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it)
- {
- //We don't need to close Nearby chat, so skip it
- if (*it != LLUUID())
- {
- LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(*it);
- if(conversationFloater)
- {
- LLFloater::onClickClose(conversationFloater);
- }
- }
- }
-}
-void LLFloaterIMContainer::closeFloater(bool app_quitting/* = false*/)
-{
- if(app_quitting)
- {
- closeAllConversations();
- onClickCloseBtn(app_quitting);
- }
- else
- {
- // Check for currently active session
- LLUUID session_id = getSelectedSession();
- // If current session is Nearby Chat or there is only one session remaining, close the floater
- if (mConversationsItems.size() == 1 || session_id == LLUUID() || app_quitting)
- {
- onClickCloseBtn();
- }
- else
- {
- // Otherwise, close current conversation
- LLFloaterIMSessionTab* active_conversation = LLFloaterIMSessionTab::getConversation(session_id);
- if (active_conversation)
- {
- active_conversation->closeFloater();
- }
- }
- }
-}
-
-void LLFloaterIMContainer::handleReshape(const LLRect& rect, bool by_user)
-{
- LLMultiFloater::handleReshape(rect, by_user);
- storeRectControl();
-}
-
-// EOF
+/** + * @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 "lldonotdisturbnotificationstorage.h" +#include "llgroupactions.h" +#include "llgroupiconctrl.h" +#include "llflashtimer.h" +#include "llfloateravatarpicker.h" +#include "llfloaterpreference.h" +#include "llfloaterreporter.h" +#include "llimview.h" +#include "llnotificationsutil.h" +#include "lltoolbarview.h" +#include "lltransientfloatermgr.h" +#include "llviewercontrol.h" +#include "llconversationview.h" +#include "llcallbacklist.h" +#include "llworld.h" +#include "llsdserialize.h" +#include "llviewermenu.h" // is_agent_mappable +#include "llviewerobjectlist.h" + + +const S32 EVENTS_PER_IDLE_LOOP_CURRENT_SESSION = 80; +const S32 EVENTS_PER_IDLE_LOOP_BACKGROUND = 40; +const F32 EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE = 0.01f; // process a minimum of 1% of total events per frame + +// +// LLFloaterIMContainer +// +LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed, const Params& params /*= getDefaultParams()*/) +: LLMultiFloater(seed, params), + mExpandCollapseBtn(NULL), + mConversationsRoot(NULL), + mConversationsEventStream("ConversationsEvents"), + mInitialized(false), + mIsFirstLaunch(true), + mConversationEventQueue() +{ + 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)); + mEnableCallbackRegistrar.add("Avatar.VisibleItem", boost::bind(&LLFloaterIMContainer::visibleContextMenuItem, 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); + + if (mMicroChangedSignal.connected()) + { + mMicroChangedSignal.disconnect(); + } + + gSavedPerAccountSettings.setBOOL("ConversationsListPaneCollapsed", mConversationsPane->isCollapsed()); + gSavedPerAccountSettings.setBOOL("ConversationsMessagePaneCollapsed", mMessagesPane->isCollapsed()); + gSavedPerAccountSettings.setBOOL("ConversationsParticipantListCollapsed", !isParticipantListExpanded()); + + if (LLIMMgr::instanceExists()) + { + LLIMMgr::getInstance()->removeSessionObserver(this); + } +} + +void LLFloaterIMContainer::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) +{ + addConversationListItem(session_id); + LLFloaterIMSessionTab::addToHost(session_id); +} + +void LLFloaterIMContainer::sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) +{ + if(!isInVisibleChain()) + { + setVisibleAndFrontmost(false); + } + selectConversationPair(session_id, true); + collapseMessagesPane(false); +} + +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) +{ + // The general strategy when a session id is modified is to delete all related objects and create them anew. + + // Note however that the LLFloaterIMSession has its session id updated through a call to sessionInitReplyReceived() + // and do not need to be deleted and recreated (trying this creates loads of problems). We do need however to suppress + // its related mSessions record as it's indexed with the wrong id. + // Grabbing the updated LLFloaterIMSession and readding it in mSessions will eventually be done by addConversationListItem(). + mSessions.erase(old_session_id); + + // Delete the model and participants related to the old session + bool change_focus = removeConversationListItem(old_session_id); + + // Create a new conversation with the new id + addConversationListItem(new_session_id, change_focus); + LLFloaterIMSessionTab::addToHost(new_session_id); +} + + +LLConversationItem* LLFloaterIMContainer::getSessionModel(const LLUUID& session_id) +{ + conversations_items_map::iterator iter = mConversationsItems.find(session_id); + if (iter == mConversationsItems.end()) + { + return NULL; + } + else + { + return iter->second.get(); + } +} + +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() +{ + mOrigMinWidth = getMinWidth(); + mOrigMinHeight = getMinHeight(); + + 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<LLTabContainer>("im_box_tab_container")); + mStubPanel = getChild<LLPanel>("stub_panel"); + mStubTextBox = getChild<LLTextBox>("stub_textbox"); + mStubTextBox->setURLClickedCallback(boost::bind(&LLFloaterIMContainer::returnFloaterToHost, this)); + + mConversationsStack = getChild<LLLayoutStack>("conversations_stack"); + mConversationsPane = getChild<LLLayoutPanel>("conversations_layout_panel"); + mMessagesPane = getChild<LLLayoutPanel>("messages_layout_panel"); + + mConversationsListPanel = getChild<LLPanel>("conversations_list_panel"); + + // Open IM session with selected participant on double click event + mConversationsListPanel->setDoubleClickCallback(boost::bind(&LLFloaterIMContainer::doToSelected, this, LLSD("im"))); + + // The resize limits for LLFloaterIMContainer should be updated, based on current values of width of conversation and message panels + mConversationsPane->getResizeBar()->setResizeListener(boost::bind(&LLFloaterIMContainer::assignResizeLimits, this)); + + // Create the root model and view for all conversation sessions + LLConversationItem* base_item = new LLConversationItem(getRootViewModel()); + + LLFolderView::Params p(LLUICtrlFactory::getDefaultParams<LLFolderView>()); + 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<LLFolderView>(p); + mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); + mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar); + + // 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); + scroller_view_rect.mBottom += getChild<LLLayoutStack>("conversations_pane_buttons_stack")->getRect().getHeight(); + LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams<LLFolderViewScrollContainer>()); + scroller_params.rect(scroller_view_rect); + + LLScrollContainer* scroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(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<LLButton>("expand_collapse_btn"); + mExpandCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onExpandCollapseButtonClicked, this)); + mStubCollapseBtn = getChild<LLButton>("stub_collapse_btn"); + mStubCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onStubCollapseButtonClicked, this)); + mSpeakBtn = getChild<LLButton>("speak_btn"); + + mSpeakBtn->setMouseDownCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonPressed, this)); + mSpeakBtn->setMouseUpCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonReleased, this)); + + childSetAction("add_btn", boost::bind(&LLFloaterIMContainer::onAddButtonClicked, this)); + + collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); + collapseConversationsPane(gSavedPerAccountSettings.getBOOL("ConversationsListPaneCollapsed"), false); + LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate, false)); + mMicroChangedSignal = LLVoiceClient::getInstance()->MicroChangedCallback(boost::bind(&LLFloaterIMContainer::updateSpeakBtnState, this)); + + if (! mMessagesPane->isCollapsed() && ! mConversationsPane->isCollapsed()) + { + S32 conversations_panel_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth"); + LLRect conversations_panel_rect = mConversationsPane->getRect(); + conversations_panel_rect.mRight = conversations_panel_rect.mLeft + conversations_panel_width; + mConversationsPane->handleReshape(conversations_panel_rect, true); + } + + // Init the sort order now that the root had been created + setSortOrder(LLConversationSort(gSavedSettings.getU32("ConversationSortOrder"))); + + //We should expand nearby chat participants list for the new user + if(gAgent.isFirstLogin() || !gSavedPerAccountSettings.getBOOL("ConversationsParticipantListCollapsed")) + { + expandConversation(); + } + // Keep the xml set title around for when we have to overwrite it + mGeneralTitle = getTitle(); + + mInitialized = true; + + mIsFirstOpen = 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::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLFloaterIMContainer::processParticipantsStyleUpdate, this)); + + mParticipantRefreshTimer.setTimerExpirySec(0); + mParticipantRefreshTimer.start(); + + return true; +} + +void LLFloaterIMContainer::onOpen(const LLSD& key) +{ + LLMultiFloater::onOpen(key); + reSelectConversation(); + assignResizeLimits(); + + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession); + session_floater->onOpen(key); +} + +// 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; + } + + LLUUID session_id = floaterp->getKey(); + + // Add the floater + LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point); + + + + LLIconCtrl* icon = 0; + bool is_in_group = gAgent.isInGroup(session_id, true); + LLUUID icon_id; + + if (is_in_group) + { + LLGroupIconCtrl::Params icon_params; + icon_params.group_id = session_id; + icon = LLUICtrlFactory::instance().create<LLGroupIconCtrl>(icon_params); + icon_id = session_id; + + 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<LLAvatarIconCtrl>(icon_params); + icon_id = avatar_id; + + mSessions[session_id] = floaterp; + floaterp->mCloseSignal.connect(boost::bind(&LLFloaterIMContainer::onCloseFloater, this, session_id)); + } + + LLFloaterIMSessionTab* floater = LLFloaterIMSessionTab::getConversation(session_id); + if (floater) + { + floater->updateChatIcon(icon_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); +} + +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::onStubCollapseButtonClicked() +{ + collapseMessagesPane(true); +} + +void LLFloaterIMContainer::onSpeakButtonPressed() +{ + LLVoiceClient::getInstance()->inputUserControlState(true); + updateSpeakBtnState(); +} + +void LLFloaterIMContainer::onSpeakButtonReleased() +{ + LLVoiceClient::getInstance()->inputUserControlState(false); + updateSpeakBtnState(); +} + +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()); + } + reSelectConversation(); +} + +LLFloaterIMContainer* LLFloaterIMContainer::findInstance() +{ + return LLFloaterReg::findTypedInstance<LLFloaterIMContainer>("im_container"); +} + +LLFloaterIMContainer* LLFloaterIMContainer::getInstance() +{ + return LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("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<LLConversationItemParticipant*>(*current_participant_model); + if (participant_model) + { + // Get the avatar name for this participant id from the cache and update the model + participant_model->updateName(); + } + // Next participant + current_participant_model++; + } + } +} + +// static +void LLFloaterIMContainer::idle(void* user_data) +{ + LLFloaterIMContainer* self = static_cast<LLFloaterIMContainer*>(user_data); + + self->idleProcessEvents(); + + if (!self->getVisible() || self->isMinimized()) + { + return; + } + self->idleUpdate(); +} + +void LLFloaterIMContainer::idleUpdate() +{ + 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); + } + + U32 sort_order = mConversationViewModel.getSorter().getSortOrderParticipants(); + + if (mParticipantRefreshTimer.hasExpired()) + { + const LLConversationItem *current_session = getCurSelectedViewModelItem(); + if (current_session) + { + if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP) + { + // Update moderator options visibility + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = current_session->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = current_session->getChildrenEnd(); + bool is_moderator = isGroupModerator(); + bool can_ban = haveAbilityToBan(); + while (current_participant_model != end_participant_model) + { + LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(*current_participant_model); + if (participant_model) + { + participant_model->setModeratorOptionsVisible(is_moderator); + participant_model->setGroupBanVisible(can_ban && participant_model->getUUID() != gAgentID); + } + + current_participant_model++; + } + } + + // Update floater's title as required by the currently selected session or use the default title + LLFloaterIMSession * conversation_floaterp = LLFloaterIMSession::findInstance(current_session->getUUID()); + setTitle(conversation_floaterp && conversation_floaterp->needsTitleOverwrite() ? conversation_floaterp->getTitle() : mGeneralTitle); + } + + mParticipantRefreshTimer.setTimerExpirySec(1.0f); + } + + // 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 (sort_order == LLConversationFilter::SO_DISTANCE) + { + // almost real-time updates + setNearbyDistances(); //calls arrange all + } + mConversationsRoot->update(); //arranges, resizes, heavy + + // "Manually" resize of mConversationsPane: same as temporarity cancellation of the flag "auto_resize=false" for it + if (!mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed()) + { + LLRect stack_rect = mConversationsStack->getRect(); + mConversationsPane->reshape(stack_rect.getWidth(), stack_rect.getHeight(), true); + } +} + +void LLFloaterIMContainer::idleProcessEvents() +{ + LLUUID current_session_id = getSelectedSession(); + conversations_items_deque::iterator iter = mConversationEventQueue.begin(); + conversations_items_deque::iterator end = mConversationEventQueue.end(); + while (iter != end) + { + std::deque<LLSD> &events = iter->second; + if (!events.empty()) + { + S32 events_to_handle; + S32 query_size = (S32)events.size(); + if (current_session_id == iter->first) + { + events_to_handle = EVENTS_PER_IDLE_LOOP_CURRENT_SESSION; + } + else + { + events_to_handle = EVENTS_PER_IDLE_LOOP_BACKGROUND; + } + + if (events_to_handle <= query_size) + { + // Some groups can be very large and can generate huge amount of updates, scale processing up to keep up + events_to_handle = llmax(events_to_handle, (S32)(query_size * EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE)); + } + else + { + events_to_handle = query_size; + } + + for (S32 i = 0; i < events_to_handle; i++) + { + handleConversationModelEvent(events.back()); + events.pop_back(); + } + } + iter++; + } +} + +bool LLFloaterIMContainer::onConversationModelEvent(const LLSD& event) +{ + LLUUID id = event.get("session_uuid").asUUID(); + mConversationEventQueue[id].push_front(event); + return true; +} + + +void LLFloaterIMContainer::handleConversationModelEvent(const LLSD& event) +{ + + // 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<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,session_id)); + if (!session_view) + { + // We skip events that are not associated with a session + return; + } + LLConversationViewParticipant* participant_view = session_view->findParticipant(participant_id); + LLFloaterIMSessionTab *conversation_floater = (session_id.isNull() ? + (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("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") + { + LLConversationItem* item = getSessionModel(session_id); + LLConversationItemSession* session_model = dynamic_cast<LLConversationItemSession*>(item); + LLConversationItemParticipant* participant_model = (session_model ? session_model->findParticipant(participant_id) : NULL); + LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(session_id); + if (!participant_view && session_model && participant_model) + { + 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) + { + bool skip_updating = im_sessionp && im_sessionp->isGroupChat(); + conversation_floater->addConversationViewParticipant(participant_model, !skip_updating); + } + } + 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(); + } + + mConversationViewModel.requestSortAll(); + mConversationsRoot->arrangeAll(); +} + +void LLFloaterIMContainer::draw() +{ + 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); + } +} + +//Shows/hides the stub panel when a conversation floater is torn off +void LLFloaterIMContainer::showStub(bool stub_is_visible) +{ + S32 tabCount = 0; + LLPanel * tabPanel = NULL; + + if(stub_is_visible) + { + tabCount = mTabContainer->getTabCount(); + + //Hide all tabs even stub + for(S32 i = 0; i < tabCount; ++i) + { + tabPanel = mTabContainer->getPanelByIndex(i); + + if(tabPanel) + { + tabPanel->setVisible(false); + } + } + + //Set the index to the stub panel since we will be showing the stub + mTabContainer->setCurrentPanelIndex(0); + } + + //Now show/hide the stub + mStubPanel->setVisible(stub_is_visible); +} + +// listener for click on mStubTextBox2 +void LLFloaterIMContainer::returnFloaterToHost() +{ + LLUUID session_id = this->getSelectedSession(); + LLFloaterIMSessionTab* floater = LLFloaterIMSessionTab::getConversation(session_id); + floater->onTearOffClicked(); +} + +void LLFloaterIMContainer::setMinimized(bool b) +{ + bool was_minimized = isMinimized(); + LLMultiFloater::setMinimized(b); + + //Switching from minimized to un-minimized + if(was_minimized && !b) + { + gToolBarView->flashCommand(LLCommandId("chat"), false); + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(mSelectedSession); + + if(session_floater && !session_floater->isTornOff()) + { + //When in DND mode, remove stored IM notifications + //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal + if(gAgent.isDoNotDisturb() && mSelectedSession.notNull()) + { + LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSelectedSession); + } + } + } +} + +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<LLFloaterIMNearbyChat>("nearby_chat"); + if ((nearby_chat == NULL) || mIsFirstOpen) + { + mIsFirstOpen = false; + // 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); + selectConversationPair(LLUUID(NULL), false, false); + } + + flashConversationItemWidget(mSelectedSession,false); + + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(mSelectedSession); + if(session_floater && !session_floater->isMinimized()) + { + //When in DND mode, remove stored IM notifications + //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal + if(gAgent.isDoNotDisturb() && mSelectedSession.notNull()) + { + LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSelectedSession); + } + } + } + + nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat"); + if (nearby_chat) + { + LLFloaterIMSessionTab::addToHost(LLUUID()); + } + + if (!LLFloater::isQuitRequested()) + { + // 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<LLConversationViewSession*>(widget_it->second); + if (widget) + { + LLFloater* session_floater = widget->getSessionFloater(); + if (session_floater != nearby_chat) + { + widget->setVisibleIfDetached(visible); + } + } + } + } + + // Now, do the normal multifloater show/hide + LLMultiFloater::setVisible(visible); +} + +void LLFloaterIMContainer::getDetachedConversationFloaters(floater_list_t& floaters) +{ + LLFloaterIMNearbyChat *nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat"); + + for (const auto& [key, fvi] : mConversationsWidgets) + { + LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(fvi); + if (widget) + { + LLFloater* session_floater = widget->getSessionFloater(); + + // Exclude nearby chat from output, as it should be handled separately + if (session_floater && session_floater->isDetachedAndNotMinimized() + && session_floater != nearby_chat) + { + floaters.push_back(session_floater); + } + } + } +} + +void LLFloaterIMContainer::setVisibleAndFrontmost(bool take_focus, const LLSD& key) +{ + LLMultiFloater::setVisibleAndFrontmost(take_focus, key); + // Do not select "Nearby Chat" conversation, since it will bring its window to front + // Only select other sessions + if (!getSelectedSession().isNull()) + { + selectConversationPair(getSelectedSession(), false, take_focus); + } + if (mInitialized && mIsFirstLaunch) + { + collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); + mIsFirstLaunch = false; +} +} + +void LLFloaterIMContainer::updateResizeLimits() +{ + LLMultiFloater::updateResizeLimits(); + assignResizeLimits(); +} + +bool LLFloaterIMContainer::isMessagesPaneCollapsed() +{ + return mMessagesPane->isCollapsed(); +} + +bool LLFloaterIMContainer::isConversationsPaneCollapsed() +{ + return mConversationsPane->isCollapsed(); +} + +void LLFloaterIMContainer::collapseMessagesPane(bool collapse) +{ + if (mMessagesPane->isCollapsed() == collapse) + { + return; + } + + // Save current width of panels before collapsing/expanding right pane. + S32 conv_pane_width = mConversationsPane->getRect().getWidth(); + S32 msg_pane_width = mMessagesPane->getRect().getWidth(); + + if (collapse) + { + // Save the messages pane width before collapsing it. + gSavedPerAccountSettings.setS32("ConversationsMessagePaneWidth", msg_pane_width); + + // Save the order in which the panels are closed to reverse user's last action. + gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", mConversationsPane->isCollapsed()); + } + + mConversationsPane->setIgnoreReshape(collapse); + + // Show/hide the messages pane. + mConversationsStack->collapsePanel(mMessagesPane, collapse); + + // Make sure layout is updated before resizing conversation pane. + mConversationsStack->updateLayout(); + + reshapeFloaterAndSetResizeLimits(collapse, gSavedPerAccountSettings.getS32("ConversationsMessagePaneWidth")); + + if (!collapse) + { + // Restore conversation's pane previous width after expanding messages pane. + mConversationsPane->setTargetDim(conv_pane_width); + } +} + +void LLFloaterIMContainer::collapseConversationsPane(bool collapse, bool save_is_allowed /*=true*/) +{ + if (mConversationsPane->isCollapsed() == collapse) + { + return; + } + + LLView* button_panel = getChild<LLView>("conversations_pane_buttons_expanded"); + button_panel->setVisible(!collapse); + mExpandCollapseBtn->setImageOverlay(getString(collapse ? "expand_icon" : "collapse_icon")); + + // Save current width of Conversation panel before collapsing/expanding right pane. + S32 conv_pane_width = mConversationsPane->getRect().getWidth(); + + if (collapse && save_is_allowed) + { + // Save the conversations pane width before collapsing it. + gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", conv_pane_width); + + // Save the order in which the panels are closed to reverse user's last action. + gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", !mMessagesPane->isCollapsed()); + } + + mConversationsStack->collapsePanel(mConversationsPane, collapse); + if (!collapse) + { + // Make sure layout is updated before resizing conversation pane. + mConversationsStack->updateLayout(); + // Restore conversation's pane previous width. + mConversationsPane->setTargetDim(gSavedPerAccountSettings.getS32("ConversationsListPaneWidth")); + } + + S32 delta_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth") + - mConversationsPane->getMinDim() - mConversationsStack->getPanelSpacing() + 1; + + reshapeFloaterAndSetResizeLimits(collapse, delta_width); + + for (conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + widget_it != mConversationsWidgets.end(); ++widget_it) + { + LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(widget_it->second); + if (widget) + { + widget->toggleCollapsedMode(collapse); + + // force closing all open conversations when collapsing to minimized state + if (collapse) + { + widget->setOpen(false); + } + widget->requestArrange(); + } + } +} + +void LLFloaterIMContainer::reshapeFloaterAndSetResizeLimits(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 at_least_one_panel_is_expanded = + ! (mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed()); + + setCanResize(at_least_one_panel_is_expanded); + setCanMinimize(at_least_one_panel_is_expanded); + + assignResizeLimits(); +} + +void LLFloaterIMContainer::assignResizeLimits() +{ + bool is_conv_pane_expanded = !mConversationsPane->isCollapsed(); + bool is_msg_pane_expanded = !mMessagesPane->isCollapsed(); + + S32 summary_width_of_visible_borders = (is_msg_pane_expanded ? mConversationsStack->getPanelSpacing() : 0) + 1; + + S32 conv_pane_target_width = is_conv_pane_expanded + ? ( is_msg_pane_expanded?mConversationsPane->getRect().getWidth():mConversationsPane->getExpandedMinDim() ) + : mConversationsPane->getMinDim(); + + S32 msg_pane_min_width = is_msg_pane_expanded ? mMessagesPane->getExpandedMinDim() : 0; + S32 new_min_width = conv_pane_target_width + msg_pane_min_width + summary_width_of_visible_borders; + + setResizeLimits(new_min_width, getMinHeight()); + + mConversationsStack->updateLayout(); +} + +void LLFloaterIMContainer::onAddButtonClicked() +{ + LLView * button = findChild<LLView>("conversations_pane_buttons_expanded")->findChild<LLButton>("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_prefp = LLFloaterReg::showTypedInstance<LLFloaterPreference>("preferences"); + if (floater_prefp) + { + floater_prefp->selectChatPanel(); + } + } + if ("privacy_preferences" == command) + { + LLFloaterPreference * floater_prefp = LLFloaterReg::showTypedInstance<LLFloaterPreference>("preferences"); + if (floater_prefp) + { + floater_prefp->selectPrivacyPanel(); + } + } + if ("Translating.Toggle" == command) + { + gSavedSettings.setBOOL("TranslateChat", !gSavedSettings.getBOOL("TranslateChat")); + } +} + +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); + } + if ("Translating.Enabled" == command) + { + return gSavedPerAccountSettings.getBOOL("TranslatingEnabled"); + } + if ("Translating.On" == command) + { + return gSavedSettings.getBOOL("TranslateChat"); + } + 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<LLFloaterIMNearbyChat>("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, bool participant_uuids/* = true*/) +{ + const std::set<LLFolderViewItem*> selectedItems = mConversationsRoot->getSelectionList(); + + std::set<LLFolderViewItem*>::const_iterator it = selectedItems.begin(); + const std::set<LLFolderViewItem*>::const_iterator it_end = selectedItems.end(); + LLConversationItem * conversationItem; + + for (; it != it_end; ++it) + { + conversationItem = static_cast<LLConversationItem *>((*it)->getViewModelItem()); + + //When a one-on-one conversation exists, retrieve the participant id from the conversation floater + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1 && participant_uuids) + { + LLFloaterIMSession * conversation_floaterp = LLFloaterIMSession::findInstance(conversationItem->getUUID()); + LLUUID participant_id = conversation_floaterp->getOtherParticipantUUID(); + selected_uuids.push_back(participant_id); + } + else + { + selected_uuids.push_back(conversationItem->getUUID()); + } + } +} + +const LLConversationItem * LLFloaterIMContainer::getCurSelectedViewModelItem() +{ + LLConversationItem * conversation_item = NULL; + + if(mConversationsRoot && + mConversationsRoot->getCurSelectedItem() && + mConversationsRoot->getCurSelectedItem()->getViewModelItem()) + { + LLFloaterIMSessionTab *selected_session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession); + if (selected_session_floater && !selected_session_floater->getHost() && selected_session_floater->getCurSelectedViewModelItem()) + { + conversation_item = selected_session_floater->getCurSelectedViewModelItem(); + } + else + { + conversation_item = static_cast<LLConversationItem *>(mConversationsRoot->getCurSelectedItem()->getViewModelItem()); + } + } + + return conversation_item; +} + +void LLFloaterIMContainer::getParticipantUUIDs(uuid_vec_t& selected_uuids) +{ + //Find the conversation floater associated with the selected id + const LLConversationItem * conversation_item = getCurSelectedViewModelItem(); + + if (NULL == conversation_item) + { + return; + } + + getSelectedUUIDs(selected_uuids); +} + +void LLFloaterIMContainer::doToParticipants(const std::string& command, uuid_vec_t& selectedIDS) +{ + if (selectedIDS.size() == 1) + { + const LLUUID& userID = selectedIDS.front(); + if ("view_profile" == command) + { + LLAvatarActions::showProfile(userID); + } + else if ("im" == command) + { + if (gAgent.getID() != userID) + { + LLAvatarActions::startIM(userID); + } + } + else if ("offer_teleport" == command) + { + LLAvatarActions::offerTeleport(selectedIDS); + } + else if ("request_teleport" == command) + { + LLAvatarActions::teleportRequest(selectedIDS.front()); + } + 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 ("zoom_in" == command) + { + handle_zoom_to_object(userID); + } + else if ("map" == command) + { + LLAvatarActions::showOnMap(userID); + } + else if ("share" == command) + { + LLAvatarActions::share(userID); + } + else if ("pay" == command) + { + LLAvatarActions::pay(userID); + } + else if ("report_abuse" == command) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(userID, &av_name)) + { + LLFloaterReporter::showFromAvatar(userID, av_name.getCompleteName()); + } + else + { + LLFloaterReporter::showFromAvatar(userID, "not avaliable"); + } + } + else if ("block_unblock" == command) + { + LLAvatarActions::toggleMute(userID, LLMute::flagVoiceChat); + } + else if ("mute_unmute" == command) + { + LLAvatarActions::toggleMute(userID, LLMute::flagTextChat); + } + else if ("selected" == command || "mute_all" == command || "unmute_all" == command) + { + moderateVoice(command, userID); + } + else if ("toggle_allow_text_chat" == command) + { + toggleAllowTextChat(userID); + } + else if ("ban_member" == command) + { + banSelectedMember(userID); + } + } + else if (selectedIDS.size() > 1) + { + if ("im" == command) + { + LLAvatarActions::startConference(selectedIDS); + } + else if ("offer_teleport" == command) + { + LLAvatarActions::offerTeleport(selectedIDS); + } + else if ("voice_call" == command) + { + LLAvatarActions::startAdhocCall(selectedIDS); + } + else if ("remove_friend" == command) + { + LLAvatarActions::removeFriendsDialog(selectedIDS); + } + } +} + +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("close_selected_conversations" == command) + { + getSelectedUUIDs(selectedIDS,false); + closeSelectedConversations(selectedIDS); + } + 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) + { + if (selectedIDS.size() > 0) + { + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_GROUP) + { + LLFloaterReg::showInstance("preview_conversation", conversationItem->getUUID(), true); + } + else if(conversationItem->getType() == LLConversationItem::CONV_SESSION_AD_HOC) + { + LLConversation* conv = LLConversationLog::instance().findConversation(LLIMModel::getInstance()->findIMSession(conversationItem->getUUID())); + if(conv) + { + LLFloaterReg::showInstance("preview_conversation", conv->getSessionID(), true); + } + } + else + { + LLAvatarActions::viewChatHistory(selectedIDS.front()); + } + } + } + else + { + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1) + { + doToParticipants(command, selectedIDS); + } + } + } + //if there is no LLFloaterIMSession* instance for selected conversation it might be Nearby chat + else + { + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_NEARBY) + { + if("chat_history" == command) + { + LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true); + } +} + } +} + +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(); + + if (action == "group_profile") + { + LLGroupActions::show(mSelectedSession); + } + else if (action == "activate_group") + { + LLGroupActions::activate(mSelectedSession); + } + else if (action == "leave_group") + { + LLGroupActions::leave(mSelectedSession); + } +} + +bool LLFloaterIMContainer::enableContextMenuItem(const LLSD& userdata) +{ + const std::string& item = userdata.asString(); + uuid_vec_t uuids; + getParticipantUUIDs(uuids); + + + //If there is group or ad-hoc chat in multiselection, everything needs to be disabled + if(uuids.size() > 1) + { + const std::set<LLFolderViewItem*> selectedItems = mConversationsRoot->getSelectionList(); + LLConversationItem * conversationItem; + for(std::set<LLFolderViewItem*>::const_iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) + { + conversationItem = static_cast<LLConversationItem *>((*it)->getViewModelItem()); + if((conversationItem->getType() == LLConversationItem::CONV_SESSION_GROUP) || (conversationItem->getType() == LLConversationItem::CONV_SESSION_AD_HOC)) + { + return false; + } + } + } + + if ("conversation_log" == item) + { + return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; + } + + //Enable Chat history item for ad-hoc and group conversations + if ("can_chat_history" == item && uuids.size() > 0) + { + //Disable menu item if selected participant is user agent + if(uuids.front() != gAgentID) + { + if (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_NEARBY) + { + return LLLogChat::isNearbyTranscriptExist(); + } + else if (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_AD_HOC) + { + const LLConversation* conv = LLConversationLog::instance().findConversation(LLIMModel::getInstance()->findIMSession(uuids.front())); + if(conv) + { + return LLLogChat::isAdHocTranscriptExist(conv->getHistoryFileName()); + } + return false; + } + else + { + bool is_group = (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_GROUP); + return LLLogChat::isTranscriptExist(uuids.front(),is_group); + } + } + } + + // If nothing is selected(and selected item is not group chat), everything needs to be disabled + if (uuids.size() <= 0) + { + if(getCurSelectedViewModelItem()) + { + return getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_GROUP; + } + return false; + } + + if("can_activate_group" == item) + { + LLUUID selected_group_id = getCurSelectedViewModelItem()->getUUID(); + return gAgent.getGroupID() != selected_group_id; + } + + return enableContextMenuItem(item, uuids); +} + +bool LLFloaterIMContainer::enableContextMenuItem(const std::string& item, uuid_vec_t& uuids) +{ + // Extract the single select info + bool is_single_select = (uuids.size() == 1); + const LLUUID& single_id = uuids.front(); + + if ("can_chat_history" == item && is_single_select) + { + return LLLogChat::isTranscriptExist(uuids.front(),false); + } + + // Handle options that are applicable to all including the user agent + if ("can_view_profile" == item) + { + return is_single_select; + } + + bool is_moderator_option = ("can_moderate_voice" == item) || ("can_allow_text_chat" == item) || ("can_mute" == item) || ("can_unmute" == item); + + // Beyond that point, if only the user agent is selected, everything is disabled + if (is_single_select && (single_id == gAgentID)) + { + if (is_moderator_option) + { + return enableModerateContextMenuItem(item, true); + } + else + { + return false; + } + } + + // If the user agent is selected with others, everything is disabled + for (uuid_vec_t::const_iterator id = uuids.begin(); id != uuids.end(); ++id) + { + if (gAgent.getID() == *id) + { + return false; + } + } + + // Handle all other options + if (("can_invite" == item) + || ("can_chat_history" == item) + || ("can_share" == item) + || ("can_pay" == item) + || ("report_abuse" == item)) + { + // Those menu items are enable only if a single avatar is selected + return is_single_select; + } + else if ("can_block" == item) + { + return (is_single_select ? LLAvatarActions::canBlock(single_id) : false); + } + else if ("can_add" == item) + { + // We can add friends if: + // - there is only 1 selected avatar (EXT-7389) + // - this avatar is not already a friend + return (is_single_select ? !LLAvatarActions::isFriend(single_id) : false); + } + else if ("can_delete" == item) + { + // We can remove friends if there are only friends among the selection + bool result = true; + for (uuid_vec_t::const_iterator id = uuids.begin(); id != uuids.end(); ++id) + { + result &= LLAvatarActions::isFriend(*id); + } + return result; + } + else if ("can_call" == item) + { + return LLAvatarActions::canCall(); + } + else if ("can_open_voice_conversation" == item) + { + return is_single_select && LLAvatarActions::canCall(); + } + else if ("can_open_voice_conversation" == item) + { + return is_single_select && LLAvatarActions::canCall(); + } + else if ("can_zoom_in" == item) + { + return is_single_select && gObjectList.findObject(single_id); + } + else if ("can_show_on_map" == item) + { + return (is_single_select ? (LLAvatarTracker::instance().isBuddyOnline(single_id) && is_agent_mappable(single_id)) || gAgent.isGodlike() : false); + } + else if ("can_offer_teleport" == item) + { + return LLAvatarActions::canOfferTeleport(uuids); + } + else if ("can_ban_member" == item) + { + return canBanSelectedMember(single_id); + } + else if (is_moderator_option) + { + // *TODO : get that out of here... + return enableModerateContextMenuItem(item); + } + + // By default, options that not explicitely disabled are enabled + return true; +} + +bool LLFloaterIMContainer::checkContextMenuItem(const LLSD& userdata) +{ + std::string item = userdata.asString(); + uuid_vec_t uuids; + getParticipantUUIDs(uuids); + + return checkContextMenuItem(item, uuids); +} + +bool LLFloaterIMContainer::checkContextMenuItem(const std::string& item, uuid_vec_t& uuids) +{ + if (uuids.size() == 1) + { + if ("is_blocked" == item) + { + return LLMuteList::getInstance()->isMuted(uuids.front(), LLMute::flagVoiceChat); + } + else if (item == "is_muted") + { + return LLMuteList::getInstance()->isMuted(uuids.front(), LLMute::flagTextChat); + } + else if ("is_allowed_text_chat" == item) + { + const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); + + if (NULL != speakerp) + { + return !speakerp->mModeratorMutedText; + } + } + } + + return false; +} + +bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata) +{ + const LLConversationItem *conversation_item = getCurSelectedViewModelItem(); + if(!conversation_item) + { + return false; + } + + const std::string& item = userdata.asString(); + + if ("show_mute" == item) + { + return !isMuted(conversation_item->getUUID()); + } + else if ("show_unmute" == item) + { + return isMuted(conversation_item->getUUID()); + } + + return true; +} + +void LLFloaterIMContainer::showConversation(const LLUUID& session_id) +{ + setVisibleAndFrontmost(false); + selectConversationPair(session_id, true); + + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); + if (session_floater) + { + session_floater->restoreFloater(); + } +} + +void LLFloaterIMContainer::clearAllFlashStates() +{ + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + for (;widget_it != mConversationsWidgets.end(); ++widget_it) + { + LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(widget_it->second); + if (widget) + { + widget->setFlashState(false); + } + } +} + +void LLFloaterIMContainer::selectConversation(const LLUUID& session_id) +{ + selectConversationPair(session_id, true); +} + +// Select the conversation *after* (or before if none after) the passed uuid conversation +// Used to change the selection on key hits +void LLFloaterIMContainer::selectNextConversationByID(const LLUUID& uuid) +{ + bool new_selection = false; + selectConversation(uuid); + new_selection = selectNextorPreviousConversation(true); + if (!new_selection) + { + selectNextorPreviousConversation(false); + } +} + +// Synchronous select the conversation item and the conversation floater +bool LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater/*=true*/) +{ + bool handled = true; + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); + + /* widget processing */ + if (select_widget && mConversationsRoot->getSelectedCount() <= 1) + { + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,session_id); + if (widget && widget->getParentFolder()) + { + widget->getParentFolder()->setSelection(widget, false, false); + mConversationsRoot->scrollToShowSelection(); + } + } + + /* floater processing */ + + if (NULL != session_floater && !session_floater->isDead()) + { + if (session_id != getSelectedSession()) + { + // Store the active session + setSelectedSession(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); + } + else + { + showStub(true); + } + + //When in DND mode, remove stored IM notifications + //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal + if(gAgent.isDoNotDisturb() && session_id.notNull()) + { + LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, session_id); + } + } + + // Set the focus on the selected floater + if (!session_floater->hasFocus() && !session_floater->isMinimized()) + { + session_floater->setFocus(focus_floater); + } + } + flashConversationItemWidget(session_id,false); + return handled; +} + +void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& participant_id) +{ + LLConversationItemSession* item = dynamic_cast<LLConversationItemSession*>(getSessionModel(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<LLConversationItemSession*>(getSessionModel(LLUUID())); + if (item) + { + // Get the positions of the nearby avatars and their ids + std::vector<LLVector3d> 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; + } + + // 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) + { + LL_WARNS() << "Couldn't create conversation session item : " << display_name << LL_ENDL; + return NULL; + } + item->renameItem(display_name); + item->updateName(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<LLConversationItem*>(*current_participant_model); + LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); + participant_view->addToFolder(widget); + current_participant_model++; + } + } + + if (uuid.notNull() && im_sessionp->isP2PSessionType()) + { + item->fetchAvatarName(false); + } + + // Do that too for the conversation dialog + LLFloaterIMSessionTab *conversation_floater = (uuid.isNull() ? (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat")) : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(uuid))); + if (conversation_floater) + { + conversation_floater->buildConversationViewParticipant(); + } + + // set the widget to minimized mode if conversations pane is collapsed + widget->toggleCollapsedMode(mConversationsPane->isCollapsed()); + + if (isWidgetSelected || 0 == mConversationsRoot->getSelectedCount()) + { + selectConversationPair(uuid, true); + widget->requestArrange(); + + // 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 is_widget_selected = false; + LLFolderViewItem* new_selection = NULL; + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); + if (widget) + { + is_widget_selected = widget->isSelected(); + if (mConversationsRoot) + { + new_selection = mConversationsRoot->getNextFromChild(widget, false); + if (!new_selection) + { + new_selection = mConversationsRoot->getPreviousFromChild(widget, false); + } + } + + // Will destroy views and delete models that are not assigned to any views + widget->destroyView(); + } + + // Suppress the conversation items and widgets from their respective maps + mConversationsItems.erase(uuid); + mConversationsWidgets.erase(uuid); + // Clear event query (otherwise reopening session in some way can bombard session with stale data) + mConversationEventQueue.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) + { + if (mConversationsWidgets.size() == 1) + { + // If only one widget is left, it has to be the Nearby Chat. Select it directly. + selectConversationPair(LLUUID(NULL), true); + } + else + { + LLConversationItem* vmi = dynamic_cast<LLConversationItem*>(new_selection->getViewModelItem()); + if (vmi) + { + selectConversationPair(vmi->getUUID(), true); + } + } + } + } + return is_widget_selected; +} + +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; + + //Indentation for aligning the p2p converstation image with the nearby chat arrow + if(item->getType() == LLConversationItem::CONV_SESSION_1_ON_1) + { + params.folder_indentation = 3; + } + + return LLUICtrlFactory::create<LLConversationViewSession>(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 = 27; + + return LLUICtrlFactory::create<LLConversationViewParticipant>(params); +} + +bool LLFloaterIMContainer::enableModerateContextMenuItem(const std::string& userdata, bool is_self) +{ + // 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) && !is_self) + { + 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()) && !is_self; +} + +bool LLFloaterIMContainer::isGroupModerator() +{ + LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); + if (NULL == speaker_manager) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + 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; +} + +bool LLFloaterIMContainer::haveAbilityToBan() +{ + LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); + if (NULL == speaker_manager) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return false; + } + LLUUID group_uuid = speaker_manager->getSessionID(); + + return gAgent.isInGroup(group_uuid) && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS); +} + +bool LLFloaterIMContainer::canBanSelectedMember(const LLUUID& participant_uuid) +{ + LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); + if (NULL == speaker_manager) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return false; + } + LLUUID group_uuid = speaker_manager->getSessionID(); + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); + if(!gdatap) + { + LL_WARNS("Groups") << "Unable to get group data for group " << group_uuid << LL_ENDL; + return false; + } + + if (gdatap->mPendingBanRequest) + { + return false; + } + + if (gdatap->isRoleMemberDataComplete()) + { + if (gdatap->mMembers.size()) + { + LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find((participant_uuid)); + if (mi != gdatap->mMembers.end()) + { + LLGroupMemberData* member_data = (*mi).second; + // Is the member an owner? + if (member_data && member_data->isInRole(gdatap->mOwnerRole)) + { + return false; + } + } + } + } + + if( gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) && + gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS) ) + { + return true; + } + + return false; +} + +void LLFloaterIMContainer::banSelectedMember(const LLUUID& participant_uuid) +{ + LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); + if (NULL == speaker_manager) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return; + } + + LLUUID group_uuid = speaker_manager->getSessionID(); + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); + if(!gdatap) + { + LL_WARNS("Groups") << "Unable to get group data for group " << group_uuid << LL_ENDL; + return; + } + + gdatap->banMemberById(participant_uuid); + +} + +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<LLIMSpeakerMgr*>(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<LLIMSpeakerMgr*> ( + 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<LLIMSpeakerMgr *>(getSpeakerMgrForSelectedParticipant()); + + if (NULL != speaker_managerp) + { + speaker_managerp->moderateVoiceParticipant(avatar_id, unmute); + } +} + +LLSpeakerMgr * LLFloaterIMContainer::getSpeakerMgrForSelectedParticipant() +{ + LLFolderViewItem *selectedItem = mConversationsRoot->getCurSelectedItem(); + if (NULL == selectedItem) + { + LL_WARNS() << "Current selected item is null" << LL_ENDL; + return NULL; + } + + 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 == selectedItem || iter->second == selectedItem->getParentFolder()) + { + conversation_uuidp = &iter->first; + break; + } + ++iter; + } + if (NULL == conversation_uuidp) + { + LL_WARNS() << "Cannot find conversation item widget" << LL_ENDL; + return NULL; + } + + return conversation_uuidp->isNull() ? (LLSpeakerMgr *)LLLocalSpeakerMgr::getInstance() + : LLIMModel::getInstance()->getSpeakerManager(*conversation_uuidp); +} + +LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp) +{ + if (NULL == speaker_managerp) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return NULL; + } + + const LLConversationItem * participant_itemp = getCurSelectedViewModelItem(); + if (NULL == participant_itemp) + { + LL_WARNS() << "Cannot evaluate current selected view model item" << LL_ENDL; + return NULL; + } + + return speaker_managerp->findSpeaker(participant_itemp->getUUID()); +} + +void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid) +{ + LLIMSpeakerMgr * speaker_managerp = dynamic_cast<LLIMSpeakerMgr*>(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)&&(!mConversationsPane->isCollapsed())) + { + LLConversationViewSession* nearby_chat = dynamic_cast<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,LLUUID())); + if (nearby_chat) + { + reSelectConversation(); + nearby_chat->setOpen(true); + } + } +} + +void LLFloaterIMContainer::reSelectConversation() +{ + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession); + if (session_floater->getHost()) + { + selectFloater(session_floater); + } +} + +void LLFloaterIMContainer::updateSpeakBtnState() +{ + mSpeakBtn->setToggleState(LLVoiceClient::getInstance()->getUserPTTState()); + mSpeakBtn->setEnabled(LLAgent::isActionAllowed("speak")); +} + +bool LLFloaterIMContainer::isConversationLoggingAllowed() +{ + return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; +} + +void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, bool is_flashes) +{ + //Finds the conversation line item to flash using the session_id + LLConversationViewSession * widget = dynamic_cast<LLConversationViewSession *>(get_ptr_in_map(mConversationsWidgets,session_id)); + + if (widget) + { + widget->setFlashState(is_flashes); + } +} + +void LLFloaterIMContainer::highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted) +{ + //Finds the conversation line item to highlight using the session_id + LLConversationViewSession * widget = dynamic_cast<LLConversationViewSession *>(get_ptr_in_map(mConversationsWidgets,session_id)); + + if (widget) + { + widget->setHighlightState(is_highlighted); + } +} + +bool LLFloaterIMContainer::isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget) +{ + llassert(conversation_item_widget != NULL); + + // make sure the widget is actually in the right spot first + mConversationsRoot->arrange(NULL, NULL); + + // check whether the widget is in the visible portion of the scroll container + LLRect widget_rect; + conversation_item_widget->localRectToOtherView(conversation_item_widget->getLocalRect(), &widget_rect, mConversationsRoot); + return !mConversationsRoot->getVisibleRect().overlaps(widget_rect); +} + +bool LLFloaterIMContainer::handleKeyHere(KEY key, MASK mask ) +{ + bool handled = false; + + if(mask == MASK_ALT) + { + if (KEY_RETURN == key ) + { + expandConversation(); + handled = true; + } + + if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) + { + selectNextorPreviousConversation(true); + handled = true; + } + if ((KEY_UP == key) || (KEY_LEFT == key)) + { + selectNextorPreviousConversation(false); + handled = true; + } + } + return handled; +} + +bool LLFloaterIMContainer::selectAdjacentConversation(bool focus_selected) +{ + bool selectedAdjacentConversation = selectNextorPreviousConversation(true, focus_selected); + + if(!selectedAdjacentConversation) + { + selectedAdjacentConversation = selectNextorPreviousConversation(false, focus_selected); + } + + return selectedAdjacentConversation; +} + +bool LLFloaterIMContainer::selectNextorPreviousConversation(bool select_next, bool focus_selected) +{ + if (mConversationsWidgets.size() > 1) + { + LLFolderViewItem* new_selection = NULL; + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,getSelectedSession()); + if (widget) + { + if(select_next) + { + new_selection = mConversationsRoot->getNextFromChild(widget, false); + } + else + { + new_selection = mConversationsRoot->getPreviousFromChild(widget, false); + } + if (new_selection) + { + LLConversationItem* vmi = dynamic_cast<LLConversationItem*>(new_selection->getViewModelItem()); + if (vmi) + { + selectConversationPair(vmi->getUUID(), true, focus_selected); + return true; + } + } + } + } + return false; +} + +void LLFloaterIMContainer::expandConversation() +{ + if(!mConversationsPane->isCollapsed()) + { + LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,getSelectedSession())); + if (widget) + { + widget->setOpen(!widget->isOpen()); + } + } +} +bool LLFloaterIMContainer::isParticipantListExpanded() +{ + bool is_expanded = false; + if(!mConversationsPane->isCollapsed()) + { + LLConversationViewSession* widget = dynamic_cast<LLConversationViewSession*>(get_ptr_in_map(mConversationsWidgets,getSelectedSession())); + if (widget) + { + is_expanded = widget->isOpen(); + } + } + return is_expanded; +} + +// By default, if torn off session is currently frontmost, LLFloater::isFrontmost() will return false, which can lead to some bugs +// So LLFloater::isFrontmost() is overriden here to check both selected session and the IM floater itself +// Exclude "Nearby Chat" session from the check, as "Nearby Chat" window and "Conversations" floater can be brought +// to front independently +/*virtual*/ +bool LLFloaterIMContainer::isFrontmost() +{ + LLFloaterIMSessionTab* selected_session = LLFloaterIMSessionTab::getConversation(mSelectedSession); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat"); + return (selected_session && selected_session->isFrontmost() && (selected_session != nearby_chat)) + || LLFloater::isFrontmost(); +} + +// For conversations, closeFloater() (linked to Ctrl-W) does not actually close the floater but the active conversation. +// This is intentional so it doesn't confuse the user. onClickCloseBtn() closes the whole floater. +void LLFloaterIMContainer::onClickCloseBtn(bool app_quitting/* = false*/) +{ + gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", mConversationsPane->getRect().getWidth()); + LLMultiFloater::closeFloater(app_quitting); +} + +void LLFloaterIMContainer::closeHostedFloater() +{ + onClickCloseBtn(); +} + +void LLFloaterIMContainer::closeAllConversations() +{ + std::vector<LLUUID> ids; + for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) + { + LLUUID session_id = it_session->first; + if (session_id != LLUUID()) + { + ids.push_back(session_id); + } + } + + for (std::vector<LLUUID>::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(*it); + LLFloater::onClickClose(conversationFloater); + } +} + +void LLFloaterIMContainer::closeSelectedConversations(const uuid_vec_t& ids) +{ + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + //We don't need to close Nearby chat, so skip it + if (*it != LLUUID()) + { + LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(*it); + if(conversationFloater) + { + LLFloater::onClickClose(conversationFloater); + } + } + } +} +void LLFloaterIMContainer::closeFloater(bool app_quitting/* = false*/) +{ + if(app_quitting) + { + closeAllConversations(); + onClickCloseBtn(app_quitting); + } + else + { + // Check for currently active session + LLUUID session_id = getSelectedSession(); + // If current session is Nearby Chat or there is only one session remaining, close the floater + if (mConversationsItems.size() == 1 || session_id == LLUUID() || app_quitting) + { + onClickCloseBtn(); + } + else + { + // Otherwise, close current conversation + LLFloaterIMSessionTab* active_conversation = LLFloaterIMSessionTab::getConversation(session_id); + if (active_conversation) + { + active_conversation->closeFloater(); + } + } + } +} + +void LLFloaterIMContainer::handleReshape(const LLRect& rect, bool by_user) +{ + LLMultiFloater::handleReshape(rect, by_user); + storeRectControl(); +} + +// EOF |