/**
 * @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);
    }

    for (auto& session : mConversationsItems)
    {
        LLConversationItemSession* session_model = dynamic_cast<LLConversationItemSession*>(session.second.get());
        if (session_model)
        {
            // Models have overcomplicated double ownership, clear
            // and resolve '0 references' ownership now, before owned
            // part of the models gets deleted by their owners
            session_model->clearAndDeparentModels();
        }
    }
}

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();

    mGeneralTitleInUse = true; // avoid reseting strings on idle
    setTitle(mGeneralTitle);

    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());
            bool needs_override = conversation_floaterp && conversation_floaterp->needsTitleOverwrite();
            if (mGeneralTitleInUse == needs_override)
            {
                mGeneralTitleInUse = !needs_override;
                setTitle(needs_override ? 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);
    if (button && root_floater)
    {
        LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterIMContainer::onAvatarPicked, this, _1), true, true, true, root_floater->getName(), button);

        if (picker)
        {
            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
        auto avatar_count = positions.size();
        for (size_t 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(bool app_quitting)
{
    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, app_quitting);
    }
}

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(app_quitting);
        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