/**
 * @file llconversationview.cpp
 * @brief Implementation of conversations list widgets and views
 *
 * $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 "llconversationview.h"

#include <boost/bind.hpp>
#include "llagentdata.h"
#include "llavataractions.h"
#include "llconversationmodel.h"
#include "llfloaterimsession.h"
#include "llfloaterimnearbychat.h"
#include "llfloaterimsessiontab.h"
#include "llfloaterimcontainer.h"
#include "llfloaterreg.h"
#include "llgroupiconctrl.h"
#include "lluictrlfactory.h"
#include "lltoolbarview.h"

//
// Implementation of conversations list session widgets
//
static LLDefaultChildRegistry::Register<LLConversationViewSession> r_conversation_view_session("conversation_view_session");

const LLColor4U DEFAULT_WHITE(255, 255, 255);

class LLNearbyVoiceClientStatusObserver : public LLVoiceClientStatusObserver
{
public:

    LLNearbyVoiceClientStatusObserver(LLConversationViewSession* conv)
    :   conversation(conv)
    {}

    virtual void onChange(EStatusType status, const LLSD& channelInfo, bool proximal)
    {
        conversation->showVoiceIndicator(conversation
            && status != STATUS_JOINING
            && status != STATUS_LEFT_CHANNEL
            && LLVoiceClient::getInstance()->voiceEnabled()
            && LLVoiceClient::getInstance()->isVoiceWorking());
    }

private:
    LLConversationViewSession* conversation;
};

LLConversationViewSession::Params::Params() :
    container()
{}

LLConversationViewSession::LLConversationViewSession(const LLConversationViewSession::Params& p):
    LLFolderViewFolder(p),
    mContainer(p.container),
    mItemPanel(NULL),
    mCallIconLayoutPanel(NULL),
    mSessionTitle(NULL),
    mSpeakingIndicator(NULL),
    mVoiceClientObserver(NULL),
    mCollapsedMode(false),
    mHasArrow(true),
    mIsInActiveVoiceChannel(false),
    mFlashStateOn(false),
    mFlashStarted(false)
{
    mFlashTimer = new LLFlashTimer();
    mAreChildrenInited = true; // inventory only
}

LLConversationViewSession::~LLConversationViewSession()
{
    mActiveVoiceChannelConnection.disconnect();

    if (mVoiceClientObserver)
    {
        LLVoiceClient::removeObserver(mVoiceClientObserver);
        delete mVoiceClientObserver;
    }

    mFlashTimer->unset();
    delete mFlashTimer;
    mFlashStateOn = false;
}

void LLConversationViewSession::destroyView()
{
    // Chat can create and parent models(listeners) to session's model before creating
    // coresponding views, such participant's models normally will wait for idle cycles
    // but since we are deleting session and won't be processing any more events, make
    // sure unowned LLConversationItemParticipant models are removed as well.

    LLConversationItemSession* vmi = dynamic_cast<LLConversationItemSession*>(getViewModelItem());

    // CONV_SESSION_1_ON_1 stores participants as two models that belong to views independent
    // from session (nasty! These views are widgets in LLFloaterIMSessionTab, see buildConversationViewParticipant)
    if (vmi && vmi->getType() != LLConversationItem::CONV_SESSION_1_ON_1)
    {
        // Destroy existing views
        while (!mItems.empty())
        {
            LLFolderViewItem *itemp = mItems.back();
            mItems.pop_back();

            LLFolderViewModelItem* item_vmi = itemp->getViewModelItem();
            if (item_vmi) // supposed to exist
            {
                // unparent to remove from child list
                vmi->removeChild(item_vmi);
            }
            itemp->destroyView();
        }

        // Not needed in scope of sessions, but just in case
        while (!mFolders.empty())
        {
            LLFolderViewFolder *folderp = mFolders.back();
            mFolders.pop_back();

            LLFolderViewModelItem* folder_vmi = folderp->getViewModelItem();
            if (folder_vmi)
            {
                vmi->removeChild(folder_vmi);
            }
            folderp->destroyView();
        }

        // Now everything that is left in model(listener) is not owned by views,
        // only by sessions, deparent so it won't point to soon to be dead model
        vmi->clearAndDeparentModels();
    }

    LLFolderViewFolder::destroyView();
}

void LLConversationViewSession::setFlashState(bool flash_state)
{
    if (flash_state && !mFlashStateOn)
    {
        // flash chat toolbar button if scrolled out of sight (because flashing will not be visible)
        if (mContainer->isScrolledOutOfSight(this))
        {
            gToolBarView->flashCommand(LLCommandId("chat"), true);
        }
    }

    mFlashStateOn = flash_state;
    mFlashStarted = false;
    mFlashTimer->stopFlashing();
}

void LLConversationViewSession::setHighlightState(bool hihglight_state)
{
    mFlashStateOn = hihglight_state;
    mFlashStarted = true;
    mFlashTimer->stopFlashing();
}

void LLConversationViewSession::startFlashing()
{
    // Need to start flashing only when "Conversations" is opened or brought on top
    if (isInVisibleChain()
        && mFlashStateOn
        && !mFlashStarted
        && ! LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container")->isMinimized() )
    {
        mFlashStarted = true;
        mFlashTimer->startFlashing();
    }
}

bool LLConversationViewSession::isHighlightAllowed()
{
    return mFlashStateOn || mIsSelected;
}

bool LLConversationViewSession::isHighlightActive()
{
    return (mFlashStateOn ? (mFlashTimer->isFlashingInProgress() ? mFlashTimer->isCurrentlyHighlighted() : true) : mIsCurSelection);
}

bool LLConversationViewSession::postBuild()
{
    LLFolderViewItem::postBuild();

    mItemPanel = LLUICtrlFactory::getInstance()->createFromFile<LLPanel>("panel_conversation_list_item.xml", NULL, LLPanel::child_registry_t::instance());
    addChild(mItemPanel);

    mCallIconLayoutPanel = mItemPanel->getChild<LLPanel>("call_icon_panel");
    mSessionTitle = mItemPanel->getChild<LLTextBox>("conversation_title");

    mActiveVoiceChannelConnection = LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLConversationViewSession::onCurrentVoiceSessionChanged, this, _1));
    mSpeakingIndicator = getChild<LLOutputMonitorCtrl>("speaking_indicator");

    LLConversationItem* vmi = dynamic_cast<LLConversationItem*>(getViewModelItem());
    if (vmi)
    {
        switch(vmi->getType())
        {
        case LLConversationItem::CONV_PARTICIPANT:
        case LLConversationItem::CONV_SESSION_1_ON_1:
        {
            LLIMModel::LLIMSession* session=  LLIMModel::instance().findIMSession(vmi->getUUID());
            if (session)
            {
                LLAvatarIconCtrl* icon = mItemPanel->getChild<LLAvatarIconCtrl>("avatar_icon");
                icon->setVisible(true);
                icon->setValue(session->mOtherParticipantID);
                mSpeakingIndicator->setSpeakerId(session->mOtherParticipantID, session->mSessionID, true);
                mHasArrow = false;
            }
            break;
        }
        case LLConversationItem::CONV_SESSION_AD_HOC:
        {
            LLGroupIconCtrl* icon = mItemPanel->getChild<LLGroupIconCtrl>("group_icon");
            icon->setVisible(true);
            mSpeakingIndicator->setSpeakerId(gAgentID, vmi->getUUID(), true);
            break;
        }
        case LLConversationItem::CONV_SESSION_GROUP:
        {
            LLGroupIconCtrl* icon = mItemPanel->getChild<LLGroupIconCtrl>("group_icon");
            icon->setVisible(true);
            icon->setValue(vmi->getUUID());
            mSpeakingIndicator->setSpeakerId(gAgentID, vmi->getUUID(), true);
            break;
        }
        case LLConversationItem::CONV_SESSION_NEARBY:
        {
            LLIconCtrl* icon = mItemPanel->getChild<LLIconCtrl>("nearby_chat_icon");
            icon->setVisible(true);
            mSpeakingIndicator->setSpeakerId(gAgentID, LLUUID::null, true);
            mIsInActiveVoiceChannel = true;

                if (mVoiceClientObserver)
                {
                LLVoiceClient::removeObserver(mVoiceClientObserver);
                    delete mVoiceClientObserver;
                }
                mVoiceClientObserver = new LLNearbyVoiceClientStatusObserver(this);
            LLVoiceClient::addObserver(mVoiceClientObserver);

            break;
        }
        default:
            break;
        }

        refresh(); // requires vmi
    }

    return true;
}

void LLConversationViewSession::draw()
{
    getViewModelItem()->update();

    const LLFolderViewItem::Params& default_params = LLUICtrlFactory::getDefaultParams<LLFolderViewItem>();
    const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false);

    // Indicate that flash can start (moot operation if already started, done or not flashing)
    startFlashing();

    // draw highlight for selected items
    drawHighlight(show_context, true, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor);

    // Draw children if root folder, or any other folder that is open. Do not draw children when animating to closed state or you get rendering overlap.
    bool draw_children = getRoot() == static_cast<LLFolderViewFolder*>(this) || isOpen();

    // Todo/fix this: arrange hides children 'out of bonds', session 'slowly' adjusts container size, unhides children
    // this process repeats until children fit
    for (folders_t::iterator iter = mFolders.begin();
        iter != mFolders.end();)
    {
        folders_t::iterator fit = iter++;
        (*fit)->setVisible(draw_children);
    }
    for (items_t::iterator iter = mItems.begin();
        iter != mItems.end();)
    {
        items_t::iterator iit = iter++;
        (*iit)->setVisible(draw_children);
    }

    // we don't draw the open folder arrow in minimized mode
    if (mHasArrow && !mCollapsedMode)
    {
        // update the rotation angle of open folder arrow
        updateLabelRotation();
        drawOpenFolderArrow(default_params, sFgColor);
    }
    LLView::draw();
}

bool LLConversationViewSession::handleMouseDown( S32 x, S32 y, MASK mask )
{
    //Will try to select a child node and then itself (if a child was not selected)
    bool result = LLFolderViewFolder::handleMouseDown(x, y, mask);

    //This node (conversation) was selected and a child (participant) was not
    if(result && getRoot())
    {
        if(getRoot()->getCurSelectedItem() == this)
        {
            LLConversationItem* item = dynamic_cast<LLConversationItem *>(getViewModelItem());
            LLUUID session_id = item? item->getUUID() : LLUUID();

            LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container");
            if (im_container->isConversationsPaneCollapsed() && im_container->getSelectedSession() == session_id)
            {
                im_container->collapseMessagesPane(!im_container->isMessagesPaneCollapsed());
            }
            else
            {
                im_container->collapseMessagesPane(false);
            }
        }
        selectConversationItem();
    }

    return result;
}

bool LLConversationViewSession::handleMouseUp( S32 x, S32 y, MASK mask )
{
    bool result = LLFolderViewFolder::handleMouseUp(x, y, mask);

    LLFloater* volume_floater = LLFloaterReg::findInstance("floater_voice_volume");
    LLFloater* chat_volume_floater = LLFloaterReg::findInstance("chat_voice");
    if (result
        && getRoot() && (getRoot()->getCurSelectedItem() == this)
        && !(volume_floater && volume_floater->isShown() && volume_floater->hasFocus())
        && !(chat_volume_floater && chat_volume_floater->isShown() && chat_volume_floater->hasFocus()))
    {
        LLConversationItem* item = dynamic_cast<LLConversationItem *>(getViewModelItem());
        LLUUID session_id = item? item->getUUID() : LLUUID();
        LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id);
        if(session_floater && !session_floater->hasFocus())
        {
            session_floater->setFocus(true);
        }
    }

    return result;
}

bool LLConversationViewSession::handleRightMouseDown( S32 x, S32 y, MASK mask )
{
    bool result = LLFolderViewFolder::handleRightMouseDown(x, y, mask);

    if(result)
    {
        selectConversationItem();
    }

    return result;
}

void LLConversationViewSession::selectConversationItem()
{
    if(getRoot()->getCurSelectedItem() == this)
    {
        LLConversationItem* item = dynamic_cast<LLConversationItem *>(getViewModelItem());
        LLUUID session_id = item? item->getUUID() : LLUUID();

        LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container");
        im_container->flashConversationItemWidget(session_id,false);
        im_container->selectConversationPair(session_id, false);
    }
}

// virtual
S32 LLConversationViewSession::arrange(S32* width, S32* height)
{
    //LLFolderViewFolder::arrange computes value for getIndentation() function below
    S32 arranged = LLFolderViewFolder::arrange(width, height);

    S32 h_pad = mHasArrow ? getIndentation() + mArrowSize : getIndentation();

    LLRect rect(mCollapsedMode ? getLocalRect().mLeft : h_pad,
                getLocalRect().mTop,
                getLocalRect().mRight,
                getLocalRect().mTop - getItemHeight());
    mItemPanel->setShape(rect);

    return arranged;
}

// virtual
void LLConversationViewSession::toggleOpen()
{
    // conversations should not be opened while in minimized mode
    if (!mCollapsedMode)
    {
        LLFolderViewFolder::toggleOpen();

        // do item's selection when opened
        if (LLFolderViewFolder::isOpen())
        {
            getParentFolder()->setSelection(this, true);
        }
        mContainer->reSelectConversation();
    }
}

void LLConversationViewSession::toggleCollapsedMode(bool is_collapsed)
{
    mCollapsedMode = is_collapsed;

    // hide the layout stack which contains all item's child widgets
    // except for the icon which we display in minimized mode
    getChild<LLView>("conversation_item_stack")->setVisible(!mCollapsedMode);

    S32 h_pad = mHasArrow ? getIndentation() + mArrowSize : getIndentation();

    mItemPanel->translate(mCollapsedMode ? -h_pad : h_pad, 0);
}

void LLConversationViewSession::setVisibleIfDetached(bool visible)
{
    // Do this only if the conversation floater has been torn off (i.e. no multi floater host) and is not minimized
    // Note: minimized dockable floaters are brought to front hence unminimized when made visible and we don't want that here
    LLFloater* session_floater = getSessionFloater();
    if (session_floater && session_floater->isDetachedAndNotMinimized())
    {
        session_floater->setVisible(visible);
    }
}

LLFloater* LLConversationViewSession::getSessionFloater()
{
    LLFolderViewModelItem* item = mViewModelItem;
    LLUUID session_uuid = dynamic_cast<LLConversationItem*>(item)->getUUID();
    return LLFloaterIMSessionTab::getConversation(session_uuid);
}

LLConversationViewParticipant* LLConversationViewSession::findParticipant(const LLUUID& participant_id)
{
    // This is *not* a general tree parsing algorithm. We search only in the mItems list
    // assuming there is no mFolders which makes sense for sessions (sessions don't contain
    // sessions).
    LLConversationViewParticipant* participant = NULL;
    items_t::const_iterator iter;
    for (iter = getItemsBegin(); iter != getItemsEnd(); iter++)
    {
        participant = dynamic_cast<LLConversationViewParticipant*>(*iter);
        if (participant->hasSameValue(participant_id))
        {
            break;
        }
    }
    return (iter == getItemsEnd() ? NULL : participant);
}

void LLConversationViewSession::showVoiceIndicator(bool visible)
{
    mCallIconLayoutPanel->setVisible(visible && LLVoiceChannel::getCurrentVoiceChannel()->getSessionID().isNull());
    requestArrange();
}

void LLConversationViewSession::refresh()
{
    // Refresh the session view from its model data
    LLConversationItem* vmi = dynamic_cast<LLConversationItem*>(getViewModelItem());
    if (vmi)
    {
        vmi->resetRefresh();

        if (mSessionTitle)
        {
            if (!highlightFriendTitle(vmi))
            {
                LLStyle::Params title_style;
                title_style.color = LLUIColorTable::instance().getColor("LabelTextColor");
                mSessionTitle->setText(vmi->getDisplayName(), title_style);
            }
        }
    }

    // Update all speaking indicators
    LLSpeakingIndicatorManager::updateSpeakingIndicators();

    // we should show indicator for specified voice session only if this is current channel. EXT-5562.
    if (mSpeakingIndicator)
    {
        mSpeakingIndicator->setIsActiveChannel(mIsInActiveVoiceChannel);
        mSpeakingIndicator->setShowParticipantsSpeaking(mIsInActiveVoiceChannel);
    }

    LLConversationViewParticipant* participant = NULL;
    items_t::const_iterator iter;
    for (iter = getItemsBegin(); iter != getItemsEnd(); iter++)
    {
        participant = dynamic_cast<LLConversationViewParticipant*>(*iter);
        if (participant)
        {
            participant->allowSpeakingIndicator(mIsInActiveVoiceChannel);
        }
    }

    requestArrange();
    if (vmi)
    {
        // Do the regular upstream refresh
        LLFolderViewFolder::refresh();
    }
}

void LLConversationViewSession::onCurrentVoiceSessionChanged(const LLUUID& session_id)
{
    LLConversationItem* vmi = dynamic_cast<LLConversationItem*>(getViewModelItem());

    if (vmi)
    {
        bool old_value = mIsInActiveVoiceChannel;
        mIsInActiveVoiceChannel = vmi->getUUID() == session_id;
        mCallIconLayoutPanel->setVisible(mIsInActiveVoiceChannel);
        if (old_value != mIsInActiveVoiceChannel)
        {
            refresh();
        }
    }
}

bool LLConversationViewSession::highlightFriendTitle(LLConversationItem* vmi)
{
    if(vmi->getType() == LLConversationItem::CONV_PARTICIPANT || vmi->getType() == LLConversationItem::CONV_SESSION_1_ON_1)
    {
        LLIMModel::LLIMSession* session=  LLIMModel::instance().findIMSession(vmi->getUUID());
        if (session && LLAvatarActions::isFriend(session->mOtherParticipantID))
        {
            LLStyle::Params title_style;
            title_style.color = LLUIColorTable::instance().getColor("ConversationFriendColor");
            mSessionTitle->setText(vmi->getDisplayName(), title_style);
            return true;
        }
    }
    return false;
}

//
// Implementation of conversations list participant (avatar) widgets
//

static LLDefaultChildRegistry::Register<LLConversationViewParticipant> r("conversation_view_participant");
bool LLConversationViewParticipant::sStaticInitialized = false;
S32 LLConversationViewParticipant::sChildrenWidths[LLConversationViewParticipant::ALIC_COUNT];

LLConversationViewParticipant::Params::Params() :
container(),
participant_id(),
avatar_icon("avatar_icon"),
info_button("info_button"),
output_monitor("output_monitor")
{}

LLConversationViewParticipant::LLConversationViewParticipant( const LLConversationViewParticipant::Params& p ):
    LLFolderViewItem(p),
    mAvatarIcon(NULL),
    mInfoBtn(NULL),
    mSpeakingIndicator(NULL),
    mUUID(p.participant_id)
{
}

LLConversationViewParticipant::~LLConversationViewParticipant()
{
    mActiveVoiceChannelConnection.disconnect();
}

void LLConversationViewParticipant::initFromParams(const LLConversationViewParticipant::Params& params)
{
    LLAvatarIconCtrl::Params avatar_icon_params(params.avatar_icon());
    applyXUILayout(avatar_icon_params, this);
    LLAvatarIconCtrl * avatarIcon = LLUICtrlFactory::create<LLAvatarIconCtrl>(avatar_icon_params);
    addChild(avatarIcon);

    LLButton::Params info_button_params(params.info_button());
    applyXUILayout(info_button_params, this);
    LLButton * button = LLUICtrlFactory::create<LLButton>(info_button_params);
    addChild(button);

    LLOutputMonitorCtrl::Params output_monitor_params(params.output_monitor());
    applyXUILayout(output_monitor_params, this);
    LLOutputMonitorCtrl * outputMonitor = LLUICtrlFactory::create<LLOutputMonitorCtrl>(output_monitor_params);
    addChild(outputMonitor);
}

bool LLConversationViewParticipant::postBuild()
{
    mAvatarIcon = getChild<LLAvatarIconCtrl>("avatar_icon");

    mInfoBtn = getChild<LLButton>("info_btn");
    mInfoBtn->setClickedCallback(boost::bind(&LLConversationViewParticipant::onInfoBtnClick, this));
    mInfoBtn->setVisible(false);

    mSpeakingIndicator = getChild<LLOutputMonitorCtrl>("speaking_indicator");

    if (!sStaticInitialized)
    {
        // Remember children widths including their padding from the next sibling,
        // so that we can hide and show them again later.
        initChildrenWidths(this);
        sStaticInitialized = true;
    }

    updateChildren();
    if (getViewModelItem())
    {
        LLFolderViewItem::postBuild();
        refresh();
    }
    return true;
}

void LLConversationViewParticipant::draw()
{
    static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE);
    static LLUIColor sFgDisabledColor = LLUIColorTable::instance().getColor("MenuItemDisabledColor", DEFAULT_WHITE);
    static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE);
    static LLUIColor sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE);
    static LLUIColor sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE);
    static LLUIColor sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE);
    static LLUIColor sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE);
    static LLUIColor sFriendColor = LLUIColorTable::instance().getColor("ConversationFriendColor");

    const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false);

    const LLFontGL* font = getLabelFontForStyle(mLabelStyle);
    F32 right_x  = 0;

    F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad;
    F32 text_left = (F32)getLabelXPos();

    LLUIColor* color;

    LLLocalSpeakerMgr *speakerMgr = LLLocalSpeakerMgr::getInstance();

    if (speakerMgr && speakerMgr->isSpeakerToBeRemoved(mUUID))
    {
        color = &sFgDisabledColor;
    }
    else
    {
        if (LLAvatarActions::isFriend(mUUID))
        {
            color = &sFriendColor;
        }
        else
        {
            color = mIsSelected ? &sHighlightFgColor : &sFgColor;
        }
    }

    LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(getViewModelItem());
    if (participant_model)
    {
        mSpeakingIndicator->setIsModeratorMuted(participant_model->isModeratorMuted());
    }

    drawHighlight(show_context, mIsSelected, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor);
    drawLabel(font, text_left, y, color->get(), right_x);

    LLView::draw();
}

// virtual
S32 LLConversationViewParticipant::arrange(S32* width, S32* height)
{
    //Need to call arrange first since it computes value used in getIndentation()
    S32 arranged = LLFolderViewItem::arrange(width, height);

    //Adjusts the avatar icon based upon the indentation
    LLRect avatarRect(getIndentation(),
                        mAvatarIcon->getRect().mTop,
                        getIndentation() + mAvatarIcon->getRect().getWidth(),
                        mAvatarIcon->getRect().mBottom);
    mAvatarIcon->setShape(avatarRect);

    //Since dimensions changed, adjust the children (info button, speaker indicator)
    updateChildren();

    return arranged;
}

// virtual
void LLConversationViewParticipant::refresh()
{
    // Refresh the participant view from its model data
    LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(getViewModelItem());
    if (participant_model)
    {
        participant_model->resetRefresh();

        // *TODO: We should also do something with vmi->isModerator() to echo that state in the UI somewhat
        mSpeakingIndicator->setIsModeratorMuted(participant_model->isModeratorMuted());

        // Do the regular upstream refresh
        LLFolderViewItem::refresh();
    }
}

void LLConversationViewParticipant::addToFolder(LLFolderViewFolder* folder)
{
    // Add the item to the folder (conversation)
    LLFolderViewItem::addToFolder(folder);

    // Retrieve the folder (conversation) UUID, which is also the speaker session UUID
    LLFolderViewFolder *prnt = getParentFolder();
    if (prnt)
    {
        LLConversationItem* vmi = dynamic_cast<LLConversationItem*>(prnt->getViewModelItem());
        if (vmi)
        {
            addToSession(vmi->getUUID());
        }
        LLConversationViewSession* session = dynamic_cast<LLConversationViewSession*>(prnt);
        if (session)
        {
            allowSpeakingIndicator(session->isInActiveVoiceChannel());
        }
    }
}

void LLConversationViewParticipant::addToSession(const LLUUID& session_id)
{
    //Allows speaking icon image to be loaded based on mUUID
    mAvatarIcon->setValue(mUUID);

    //Allows the speaker indicator to be activated based on the user and conversation
    mSpeakingIndicator->setSpeakerId(mUUID, session_id);
}

void LLConversationViewParticipant::onInfoBtnClick()
{
    LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mUUID));
}

bool LLConversationViewParticipant::handleMouseDown( S32 x, S32 y, MASK mask )
{
    bool result = LLFolderViewItem::handleMouseDown(x, y, mask);

    if(result && getRoot())
    {
        if(getRoot()->getCurSelectedItem() == this)
        {
            LLConversationItem* vmi = getParentFolder() ? dynamic_cast<LLConversationItem*>(getParentFolder()->getViewModelItem()) : NULL;
            LLUUID session_id = vmi? vmi->getUUID() : LLUUID();

            LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container");
            LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id);
            im_container->setSelectedSession(session_id);
            im_container->flashConversationItemWidget(session_id,false);
            im_container->selectFloater(session_floater);
            im_container->collapseMessagesPane(false);
        }
    }
    return result;
}

void LLConversationViewParticipant::onMouseEnter(S32 x, S32 y, MASK mask)
{
    mInfoBtn->setVisible(true);
    updateChildren();
    LLFolderViewItem::onMouseEnter(x, y, mask);
}

void LLConversationViewParticipant::onMouseLeave(S32 x, S32 y, MASK mask)
{
    mInfoBtn->setVisible(false);
    updateChildren();
    LLFolderViewItem::onMouseLeave(x, y, mask);
}

S32 LLConversationViewParticipant::getLabelXPos()
{
    return getIndentation() + mAvatarIcon->getRect().getWidth() + mIconPad;
}

// static
void LLConversationViewParticipant::initChildrenWidths(LLConversationViewParticipant* self)
{
    //speaking indicator width + padding
    S32 speaking_indicator_width = self->getRect().getWidth() - self->mSpeakingIndicator->getRect().mLeft;

    //info btn width + padding
    S32 info_btn_width = self->mSpeakingIndicator->getRect().mLeft - self->mInfoBtn->getRect().mLeft;

    S32 index = ALIC_COUNT;
    sChildrenWidths[--index] = info_btn_width;
    sChildrenWidths[--index] = speaking_indicator_width;
    llassert(index == 0);
}

void LLConversationViewParticipant::updateChildren()
{
    mLabelPaddingRight = DEFAULT_LABEL_PADDING_RIGHT;
    LLView* control;
    S32 ctrl_width;
    LLRect controlRect;

    //Cycles through controls starting from right to left
    for (S32 i = 0; i < ALIC_COUNT; ++i)
    {
        control = getItemChildView((EAvatarListItemChildIndex)i);

        // skip invisible views
        if (!control->getVisible()) continue;

        //Get current pos/dimensions
        controlRect = control->getRect();

        ctrl_width = sChildrenWidths[i]; // including space between current & left controls
        // accumulate the amount of space taken by the controls
        mLabelPaddingRight += ctrl_width;

        //Reposition visible controls in case adjacent controls to the right are hidden.
        controlRect.setLeftTopAndSize(
            getLocalRect().getWidth() - mLabelPaddingRight,
            controlRect.mTop,
            controlRect.getWidth(),
            controlRect.getHeight());

        //Sets the new position
        control->setShape(controlRect);
    }
}

LLView* LLConversationViewParticipant::getItemChildView(EAvatarListItemChildIndex child_view_index)
{
    LLView* child_view = NULL;

    switch (child_view_index)
    {
        case ALIC_SPEAKER_INDICATOR:
            child_view = mSpeakingIndicator;
            break;
        case ALIC_INFO_BUTTON:
            child_view = mInfoBtn;
            break;
        default:
            LL_WARNS("AvatarItemReshape") << "Unexpected child view index is passed: " << child_view_index << LL_ENDL;
            llassert(0);
            break;
            // leave child_view untouched
    }

    return child_view;
}

void LLConversationViewParticipant::allowSpeakingIndicator(bool val)
{
    mSpeakingIndicator->setIsActiveChannel(val);
}

// EOF