diff options
author | Ansariel <ansariel.hiller@phoenixviewer.com> | 2024-05-22 21:25:21 +0200 |
---|---|---|
committer | Andrey Lihatskiy <alihatskiy@productengine.com> | 2024-05-22 22:40:26 +0300 |
commit | e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 (patch) | |
tree | 1bb897489ce524986f6196201c10ac0d8861aa5f /indra/newview/llfloaterimsessiontab.cpp | |
parent | 069ea06848f766466f1a281144c82a0f2bd79f3a (diff) |
Fix line endlings
Diffstat (limited to 'indra/newview/llfloaterimsessiontab.cpp')
-rw-r--r-- | indra/newview/llfloaterimsessiontab.cpp | 2700 |
1 files changed, 1350 insertions, 1350 deletions
diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 6efb0f9efb..81a4f9936c 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -1,1350 +1,1350 @@ -/**
- * @file llfloaterimsessiontab.cpp
- * @brief LLFloaterIMSessionTab class implements the common behavior of LNearbyChatBar
- * @brief and LLFloaterIMSession for hosting both in LLIMContainer
- *
- * $LicenseInfo:firstyear=2012&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2012, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include "llfloaterimsessiontab.h"
-
-#include "llagent.h"
-#include "llagentcamera.h"
-#include "llavataractions.h"
-#include "llavatariconctrl.h"
-#include "llchatentry.h"
-#include "llchathistory.h"
-#include "llchiclet.h"
-#include "llchicletbar.h"
-#include "lldraghandle.h"
-#include "llemojidictionary.h"
-#include "llfloaterreg.h"
-#include "llfloateremojipicker.h"
-#include "llfloaterimsession.h"
-#include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container
-#include "llfloaterimnearbychat.h"
-#include "llgroupiconctrl.h"
-#include "lllayoutstack.h"
-#include "llpanelemojicomplete.h"
-#include "lltoolbarview.h"
-
-const F32 REFRESH_INTERVAL = 1.0f;
-const std::string ICN_GROUP("group_chat_icon");
-const std::string ICN_NEARBY("nearby_chat_icon");
-const std::string ICN_AVATAR("avatar_icon");
-
-void cb_group_do_nothing()
-{
-}
-
-LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id)
-: super(NULL, false, session_id),
- mIsP2PChat(false),
- mExpandCollapseBtn(NULL),
- mTearOffBtn(NULL),
- mCloseBtn(NULL),
- mSessionID(session_id.asUUID()),
- mConversationsRoot(NULL),
- mScroller(NULL),
- mChatHistory(NULL),
- mInputEditor(NULL),
- mInputEditorPad(0),
- mRefreshTimer(new LLTimer()),
- mIsHostAttached(false),
- mHasVisibleBeenInitialized(false),
- mIsParticipantListExpanded(true),
- mChatLayoutPanel(NULL),
- mInputPanels(NULL),
- mChatLayoutPanelHeight(0)
-{
- setAutoFocus(false);
- mSession = LLIMModel::getInstance()->findIMSession(mSessionID);
-
- mCommitCallbackRegistrar.add("IMSession.Menu.Action",
- boost::bind(&LLFloaterIMSessionTab::onIMSessionMenuItemClicked, this, _2));
- mEnableCallbackRegistrar.add("IMSession.Menu.CompactExpandedModes.CheckItem",
- boost::bind(&LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck, this, _2));
- mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.CheckItem",
- boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemCheck, this, _2));
- mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.Enable",
- boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemEnable, this, _2));
-
- // Right click menu handling
- mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMSessionTab::checkContextMenuItem, this, _2));
- mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMSessionTab::enableContextMenuItem, this, _2));
- mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMSessionTab::doToSelected, this, _2));
- mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&cb_group_do_nothing));
-
- mMinFloaterHeight = getMinHeight();
-}
-
-LLFloaterIMSessionTab::~LLFloaterIMSessionTab()
-{
- delete mRefreshTimer;
-
- LLFloaterIMContainer* im_container = LLFloaterIMContainer::findInstance();
- if (im_container)
- {
- LLParticipantList* session = dynamic_cast<LLParticipantList*>(im_container->getSessionModel(mSessionID));
- if (session)
- {
- for (const conversations_widgets_map::value_type& widget_pair : mConversationsWidgets)
- {
- LLFolderViewItem* widget = widget_pair.second;
- LLFolderViewModelItem* item_vmi = widget->getViewModelItem();
- if (item_vmi && item_vmi->getNumRefs() == 1)
- {
- // This is the last pointer, remove participant from session
- // before participant gets deleted on destroyView.
- session->removeChild(item_vmi);
- }
- }
- }
- }
-}
-
-// static
-LLFloaterIMSessionTab* LLFloaterIMSessionTab::findConversation(const LLUUID& uuid)
-{
- LLFloaterIMSessionTab* conv;
-
- if (uuid.isNull())
- {
- conv = LLFloaterReg::findTypedInstance<LLFloaterIMSessionTab>("nearby_chat");
- }
- else
- {
- conv = LLFloaterReg::findTypedInstance<LLFloaterIMSessionTab>("impanel", LLSD(uuid));
- }
-
- return conv;
-};
-
-// static
-LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid)
-{
- LLFloaterIMSessionTab* conv;
-
- if (uuid.isNull())
- {
- conv = LLFloaterReg::getTypedInstance<LLFloaterIMSessionTab>("nearby_chat");
- }
- else
- {
- conv = LLFloaterReg::getTypedInstance<LLFloaterIMSessionTab>("impanel", LLSD(uuid));
- conv->setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE);
- }
-
- return conv;
-
-};
-
-// virtual
-void LLFloaterIMSessionTab::setVisible(bool visible)
-{
- if (visible && !mHasVisibleBeenInitialized)
- {
- mHasVisibleBeenInitialized = true;
- if (!gAgentCamera.cameraMouselook())
- {
- LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container")->setVisible(true);
- }
- LLFloaterIMSessionTab::addToHost(mSessionID);
- LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID);
-
- if (conversp && conversp->isNearbyChat() && gSavedPerAccountSettings.getBOOL("NearbyChatIsNotCollapsed"))
- {
- onCollapseToLine(this);
- }
- mInputButtonPanel->setVisible(isTornOff());
- }
-
- super::setVisible(visible);
-}
-
-// virtual
-void LLFloaterIMSessionTab::setFocus(bool focus)
-{
- super::setFocus(focus);
-
- // Redirect focus to input editor
- if (focus)
- {
- updateMessages();
-
- if (mInputEditor)
- {
- mInputEditor->setFocus(true);
- }
- }
-}
-
-void LLFloaterIMSessionTab::addToHost(const LLUUID& session_id)
-{
- if ((session_id.notNull() && !gIMMgr->hasSession(session_id))
- || !LLFloaterIMSessionTab::isChatMultiTab())
- {
- return;
- }
-
- // Get the floater: this will create the instance if it didn't exist
- LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(session_id);
- if (conversp)
- {
- LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance();
-
- // Do not add again existing floaters
- if (floater_container && !conversp->isHostAttached())
- {
- conversp->setHostAttached(true);
-
- if (!conversp->isNearbyChat()
- || gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff"))
- {
- floater_container->addFloater(conversp, false, LLTabContainer::RIGHT_OF_CURRENT);
- }
- else
- {
- // setting of the "potential" host for Nearby Chat: this sequence sets
- // LLFloater::mHostHandle = NULL (a current host), but
- // LLFloater::mLastHostHandle = floater_container (a "future" host)
- conversp->setHost(floater_container);
- conversp->setHost(NULL);
-
- conversp->forceReshape();
- }
- // Added floaters share some state (like sort order) with their host
- conversp->setSortOrder(floater_container->getSortOrder());
- }
- }
-}
-
-void LLFloaterIMSessionTab::assignResizeLimits()
-{
- bool is_participants_pane_collapsed = mParticipantListPanel->isCollapsed();
-
- // disable a layoutstack's functionality when participant list panel is collapsed
- mRightPartPanel->setIgnoreReshape(is_participants_pane_collapsed);
-
- S32 participants_pane_target_width = is_participants_pane_collapsed?
- 0 : (mParticipantListPanel->getRect().getWidth() + mParticipantListAndHistoryStack->getPanelSpacing());
-
- S32 new_min_width = participants_pane_target_width + mRightPartPanel->getExpandedMinDim() + mFloaterExtraWidth;
-
- setResizeLimits(new_min_width, getMinHeight());
-
- this->mParticipantListAndHistoryStack->updateLayout();
-}
-
-// virtual
-bool LLFloaterIMSessionTab::postBuild()
-{
- bool result;
-
- mBodyStack = getChild<LLLayoutStack>("main_stack");
- mParticipantListAndHistoryStack = getChild<LLLayoutStack>("im_panels");
-
- mCloseBtn = getChild<LLButton>("close_btn");
- mCloseBtn->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickClose(this); });
-
- mExpandCollapseBtn = getChild<LLButton>("expand_collapse_btn");
- mExpandCollapseBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onSlide(this); });
-
- mExpandCollapseLineBtn = getChild<LLButton>("minz_btn");
- mExpandCollapseLineBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onCollapseToLine(this); });
-
- mTearOffBtn = getChild<LLButton>("tear_off_btn");
- mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this));
-
- mEmojiRecentPanelToggleBtn = getChild<LLButton>("emoji_recent_panel_toggle_btn");
- mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); });
-
- mEmojiRecentPanel = getChild<LLLayoutPanel>("emoji_recent_layout_panel");
- mEmojiRecentPanel->setVisible(false);
-
- mEmojiRecentEmptyText = getChild<LLTextBox>("emoji_recent_empty_text");
- mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText());
- mEmojiRecentEmptyText->setVisible(false);
-
- mEmojiRecentContainer = getChild<LLPanel>("emoji_recent_container");
- mEmojiRecentContainer->setVisible(false);
-
- mEmojiRecentIconsCtrl = getChild<LLPanelEmojiComplete>("emoji_recent_icons_ctrl");
- mEmojiRecentIconsCtrl->setFocusReceivedCallback([this](LLFocusableElement*) { onEmojiRecentPanelFocusReceived(); });
- mEmojiRecentIconsCtrl->setFocusLostCallback([this](LLFocusableElement*) { onEmojiRecentPanelFocusLost(); });
- mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); });
-
- mEmojiPickerShowBtn = getChild<LLButton>("emoji_picker_show_btn");
- mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); });
-
- mGearBtn = getChild<LLButton>("gear_btn");
- mAddBtn = getChild<LLButton>("add_btn");
- mVoiceButton = getChild<LLButton>("voice_call_btn");
-
- mParticipantListPanel = getChild<LLLayoutPanel>("speakers_list_panel");
- mRightPartPanel = getChild<LLLayoutPanel>("right_part_holder");
-
- mToolbarPanel = getChild<LLLayoutPanel>("toolbar_panel");
- mContentPanel = getChild<LLLayoutPanel>("body_panel");
- mInputButtonPanel = getChild<LLLayoutPanel>("input_button_layout_panel");
- mInputButtonPanel->setVisible(false);
- // Add a scroller for the folder (participant) view
- LLRect scroller_view_rect = mParticipantListPanel->getRect();
- scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom);
- LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams<LLFolderViewScrollContainer>());
- scroller_params.rect(scroller_view_rect);
- mScroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(scroller_params);
- mScroller->setFollowsAll();
-
- // Insert that scroller into the panel widgets hierarchy
- mParticipantListPanel->addChild(mScroller);
-
- mChatHistory = getChild<LLChatHistory>("chat_history");
-
- mInputEditor = getChild<LLChatEntry>("chat_editor");
-
- mChatLayoutPanel = getChild<LLLayoutPanel>("chat_layout_panel");
- mInputPanels = getChild<LLLayoutStack>("input_panels");
-
- mInputEditor->setTextExpandedCallback(boost::bind(&LLFloaterIMSessionTab::reshapeChatLayoutPanel, this));
- mInputEditor->setMouseUpCallback(boost::bind(&LLFloaterIMSessionTab::onInputEditorClicked, this));
- mInputEditor->setCommitOnFocusLost( false );
- mInputEditor->setPassDelete(true);
- mInputEditor->setFont(LLViewerChat::getChatFont());
-
- mChatLayoutPanelHeight = mChatLayoutPanel->getRect().getHeight();
- mInputEditorPad = mChatLayoutPanelHeight - mInputEditor->getRect().getHeight();
-
- setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE);
-
- mSaveRect = isNearbyChat()
- && !gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff");
- initRectControl();
-
- if (isChatMultiTab())
- {
- result = LLFloater::postBuild();
- }
- else
- {
- result = LLDockableFloater::postBuild();
- }
-
- // Create the root using an ad-hoc base item
- LLConversationItem* base_item = new LLConversationItem(mSessionID, mConversationViewModel);
- LLFolderView::Params p(LLUICtrlFactory::getDefaultParams<LLFolderView>());
- p.rect = LLRect(0, 0, getRect().getWidth(), 0);
- p.parent_panel = mParticipantListPanel;
- p.listener = base_item;
- p.view_model = &mConversationViewModel;
- p.root = NULL;
- p.use_ellipses = true;
- p.options_menu = "menu_conversation.xml";
- p.name = "root";
- mConversationsRoot = LLUICtrlFactory::create<LLFolderView>(p);
- mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar);
- mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar);
- // Attach that root to the scroller
- mScroller->addChild(mConversationsRoot);
- mConversationsRoot->setScrollContainer(mScroller);
- mConversationsRoot->setFollowsAll();
- mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox);
-
- setMessagePaneExpanded(true);
-
- buildConversationViewParticipant();
- refreshConversation();
-
- // Zero expiry time is set only once to allow initial update.
- mRefreshTimer->setTimerExpirySec(0);
- mRefreshTimer->start();
- initBtns();
-
- if (mIsParticipantListExpanded != (bool)gSavedSettings.getBOOL("IMShowControlPanel"))
- {
- LLFloaterIMSessionTab::onSlide(this);
- }
-
- // The resize limits for LLFloaterIMSessionTab should be updated, based on current values of width of conversation and message panels
- mParticipantListPanel->getResizeBar()->setResizeListener(boost::bind(&LLFloaterIMSessionTab::assignResizeLimits, this));
- mFloaterExtraWidth =
- getRect().getWidth()
- - mParticipantListAndHistoryStack->getRect().getWidth()
- - (mParticipantListPanel->isCollapsed()? 0 : LLPANEL_BORDER_WIDTH);
-
- assignResizeLimits();
-
- return result;
-}
-
-LLParticipantList* LLFloaterIMSessionTab::getParticipantList()
-{
- return dynamic_cast<LLParticipantList*>(LLFloaterIMContainer::getInstance()->getSessionModel(mSessionID));
-}
-
-// virtual
-void LLFloaterIMSessionTab::draw()
-{
- if (mRefreshTimer->hasExpired())
- {
- LLParticipantList* item = getParticipantList();
- if (item)
- {
- // Update all model items
- item->update();
- // If the model and view list diverge in count, rebuild
- // Note: this happens sometimes right around init (add participant events fire but get dropped) and is the cause
- // of missing participants, often, the user agent itself. As there will be no other event fired, there's
- // no other choice but get those inconsistencies regularly (and lightly) checked and scrubbed.
- if (item->getChildrenCount() != mConversationsWidgets.size())
- {
- buildConversationViewParticipant();
- }
- refreshConversation();
- }
-
- // Restart the refresh timer
- mRefreshTimer->setTimerExpirySec(REFRESH_INTERVAL);
- }
-
- super::draw();
-}
-
-void LLFloaterIMSessionTab::enableDisableCallBtn()
-{
- if (LLVoiceClient::instanceExists() && mVoiceButton)
- {
- mVoiceButton->setEnabled(
- mSessionID.notNull()
- && mSession
- && mSession->mSessionInitialized
- && LLVoiceClient::getInstance()->voiceEnabled()
- && LLVoiceClient::getInstance()->isVoiceWorking()
- && mSession->mCallBackEnabled);
- }
-}
-
-// virtual
-void LLFloaterIMSessionTab::onFocusReceived()
-{
- setBackgroundOpaque(true);
-
- if (mSessionID.notNull() && isInVisibleChain())
- {
- LLIMModel::instance().sendNoUnreadMessages(mSessionID);
- }
-
- super::onFocusReceived();
-}
-
-// virtual
-void LLFloaterIMSessionTab::onFocusLost()
-{
- setBackgroundOpaque(false);
- super::onFocusLost();
-}
-
-void LLFloaterIMSessionTab::onInputEditorClicked()
-{
- LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance();
- if (im_box)
- {
- im_box->flashConversationItemWidget(mSessionID,false);
- }
- gToolBarView->flashCommand(LLCommandId("chat"), false);
-}
-
-void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked()
-{
- bool show = !mEmojiRecentPanel->getVisible();
- if (show)
- {
- initEmojiRecentPanel();
- }
-
- mEmojiRecentPanel->setVisible(show);
- mInputEditor->setFocus(true);
-}
-
-void LLFloaterIMSessionTab::onEmojiPickerShowBtnClicked()
-{
- mInputEditor->setFocus(true);
- mInputEditor->showEmojiHelper();
-}
-
-void LLFloaterIMSessionTab::initEmojiRecentPanel()
-{
- std::list<llwchar>& recentlyUsed = LLFloaterEmojiPicker::getRecentlyUsed();
- if (recentlyUsed.empty())
- {
- mEmojiRecentEmptyText->setVisible(true);
- mEmojiRecentContainer->setVisible(false);
- }
- else
- {
- LLWString emojis;
- for (llwchar emoji : recentlyUsed)
- {
- emojis += emoji;
- }
- mEmojiRecentIconsCtrl->setEmojis(emojis);
- mEmojiRecentEmptyText->setVisible(false);
- mEmojiRecentContainer->setVisible(true);
- }
-}
-
-// static
-void LLFloaterIMSessionTab::onEmojiRecentPanelFocusReceived()
-{
- mEmojiRecentContainer->addBorder();
-}
-
-// static
-void LLFloaterIMSessionTab::onEmojiRecentPanelFocusLost()
-{
- mEmojiRecentContainer->removeBorder();
-}
-
-void LLFloaterIMSessionTab::onRecentEmojiPicked(const LLSD& value)
-{
- LLSD::String str = value.asString();
- if (str.size())
- {
- LLWString wstr = utf8string_to_wstring(str);
- if (wstr.size())
- {
- llwchar emoji = wstr[0];
- mInputEditor->insertEmoji(emoji);
- }
- }
-}
-
-void LLFloaterIMSessionTab::closeFloater(bool app_quitting)
-{
- LLFloaterEmojiPicker::saveState();
- super::closeFloater(app_quitting);
-}
-
-std::string LLFloaterIMSessionTab::appendTime()
-{
- std::string timeStr = "[" + LLTrans::getString("TimeHour") + "]:"
- "[" + LLTrans::getString("TimeMin") + "]";
-
- LLSD substitution;
- substitution["datetime"] = (S32)time_corrected();
- LLStringUtil::format(timeStr, substitution);
-
- return timeStr;
-}
-
-void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD& args)
-{
- if (chat.mMuted || !mChatHistory)
- return;
-
- // Update the participant activity time
- LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance();
- if (im_box)
- {
- im_box->setTimeNow(mSessionID, chat.mFromID);
- }
-
- LLChat& tmp_chat = const_cast<LLChat&>(chat);
-
- if (tmp_chat.mTimeStr.empty())
- tmp_chat.mTimeStr = appendTime();
-
- tmp_chat.mFromName = chat.mFromName;
-
- LLSD chat_args = args;
- chat_args["use_plain_text_chat_history"] =
- gSavedSettings.getBOOL("PlainTextChatHistory");
- chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime");
- chat_args["show_names_for_p2p_conv"] = !mIsP2PChat ||
- gSavedSettings.getBOOL("IMShowNamesForP2PConv");
-
- mChatHistory->appendMessage(chat, chat_args);
-}
-
-void LLFloaterIMSessionTab::updateUsedEmojis(LLWString text)
-{
- LLEmojiDictionary* dictionary = LLEmojiDictionary::getInstance();
- llassert_always(dictionary);
-
- bool emojiSent = false;
- for (llwchar& c : text)
- {
- if (dictionary->isEmoji(c))
- {
- LLFloaterEmojiPicker::onEmojiUsed(c);
- emojiSent = true;
- }
- }
-
- if (!emojiSent)
- return;
-
- LLFloaterEmojiPicker::saveState();
-
- if (mEmojiRecentPanel->getVisible())
- {
- initEmojiRecentPanel();
- }
-}
-
-static LLTrace::BlockTimerStatHandle FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT("Build Conversation View");
-void LLFloaterIMSessionTab::buildConversationViewParticipant()
-{
- LL_RECORD_BLOCK_TIME(FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT);
- // Clear the widget list since we are rebuilding afresh from the model
- conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin();
- while (widget_it != mConversationsWidgets.end())
- {
- removeConversationViewParticipant(widget_it->first);
- // Iterators are invalidated by erase so we need to pick begin again
- widget_it = mConversationsWidgets.begin();
- }
-
- // Get the model list
- LLParticipantList* item = getParticipantList();
- if (!item)
- {
- // Nothing to do if the model list is inexistent
- return;
- }
-
- // Create the participants widgets now
- LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin();
- LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd();
- while (current_participant_model != end_participant_model)
- {
- LLConversationItem* participant_model = dynamic_cast<LLConversationItem*>(*current_participant_model);
- if (participant_model)
- {
- addConversationViewParticipant(participant_model);
- }
- current_participant_model++;
- }
-}
-
-void LLFloaterIMSessionTab::addConversationViewParticipant(LLConversationItem* participant_model, bool update_view)
-{
- if (!participant_model)
- {
- // Nothing to do if the model is inexistent
- return;
- }
-
- // Check if the model already has an associated view
- LLUUID uuid = participant_model->getUUID();
- LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid);
-
- // If not already present, create the participant view and attach it to the root, otherwise, just refresh it
- if (widget)
- {
- if (update_view)
- {
- updateConversationViewParticipant(uuid); // overkill?
- }
- }
- else
- {
- LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model);
- mConversationsWidgets[uuid] = participant_view;
- participant_view->addToFolder(mConversationsRoot);
- participant_view->addToSession(mSessionID);
- participant_view->setVisible(true);
- }
-}
-
-void LLFloaterIMSessionTab::removeConversationViewParticipant(const LLUUID& participant_id)
-{
- LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id);
- if (widget)
- {
- LLFolderViewModelItem* item_vmi = widget->getViewModelItem();
- if (item_vmi && item_vmi->getNumRefs() == 1)
- {
- // This is the last pointer, remove participant from session
- // before participant gets deleted on destroyView.
- //
- // Floater (widget) and participant's view can simultaneously
- // co-own the model, in which case view is responsible for
- // the deletion and floater is free to clear and recreate
- // the list, yet there are cases where only widget owns
- // the pointer so it should do the cleanup.
- // See "add_participant".
- //
- // Todo: If it keeps causing issues turn participants
- // into LLPointers in the session
- LLParticipantList* session = getParticipantList();
- if (session)
- {
- session->removeChild(item_vmi);
- }
- }
- widget->destroyView();
- }
- mConversationsWidgets.erase(participant_id);
-}
-
-void LLFloaterIMSessionTab::updateConversationViewParticipant(const LLUUID& participant_id)
-{
- LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id);
- if (widget && widget->getViewModelItem())
- {
- widget->refresh();
- }
-}
-
-void LLFloaterIMSessionTab::refreshConversation()
-{
- // Note: We collect participants names to change the session name only in the case of ad-hoc conversations
- bool is_ad_hoc = (mSession ? mSession->isAdHocSessionType() : false);
- uuid_vec_t participants_uuids; // uuids vector for building the added participants name string
- // For P2P chat, we still need to update the session name who may have changed (switch display name for instance)
- if (mIsP2PChat && mSession)
- {
- participants_uuids.push_back(mSession->mOtherParticipantID);
- }
-
- conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin();
- while (widget_it != mConversationsWidgets.end())
- {
- // Add the participant to the list except if it's the agent itself (redundant)
- if (is_ad_hoc && (widget_it->first != gAgentID))
- {
- participants_uuids.push_back(widget_it->first);
- }
- if (widget_it->second->getViewModelItem())
- {
- widget_it->second->refresh();
- widget_it->second->setVisible(true);
- }
- ++widget_it;
- }
- if (is_ad_hoc || mIsP2PChat)
- {
- // Build the session name and update it
- std::string session_name;
- if (participants_uuids.size() != 0)
- {
- LLAvatarActions::buildResidentsString(participants_uuids, session_name);
- }
- else
- {
- session_name = LLIMModel::instance().getName(mSessionID);
- }
- updateSessionName(session_name);
- }
-
- if (mSessionID.notNull())
- {
- LLParticipantList* participant_list = getParticipantList();
- if (participant_list)
- {
- LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = participant_list->getChildrenBegin();
- LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = participant_list->getChildrenEnd();
- LLIMSpeakerMgr *speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
- while (current_participant_model != end_participant_model)
- {
- LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(*current_participant_model);
- if (speaker_mgr && participant_model)
- {
- LLSpeaker *participant_speaker = speaker_mgr->findSpeaker(participant_model->getUUID());
- LLSpeaker *agent_speaker = speaker_mgr->findSpeaker(gAgentID);
- if (participant_speaker && agent_speaker)
- {
- participant_model->setDisplayModeratorRole(agent_speaker->mIsModerator && participant_speaker->mIsModerator);
- }
- }
- current_participant_model++;
- }
- }
- }
-
- mConversationViewModel.requestSortAll();
- if(mConversationsRoot != NULL)
- {
- mConversationsRoot->arrangeAll();
- mConversationsRoot->update();
- }
- updateHeaderAndToolbar();
- refresh();
-}
-
-// Copied from LLFloaterIMContainer::createConversationViewParticipant(). Refactor opportunity!
-LLConversationViewParticipant* LLFloaterIMSessionTab::createConversationViewParticipant(LLConversationItem* item)
-{
- LLRect panel_rect = mParticipantListPanel->getRect();
-
- LLConversationViewParticipant::Params params;
- params.name = item->getDisplayName();
- params.root = mConversationsRoot;
- params.listener = item;
- params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); // *TODO: use conversation_view_participant.xml itemHeight value in lieu of 24
- params.tool_tip = params.name;
- params.participant_id = item->getUUID();
-
- return LLUICtrlFactory::create<LLConversationViewParticipant>(params);
-}
-
-void LLFloaterIMSessionTab::setSortOrder(const LLConversationSort& order)
-{
- mConversationViewModel.setSorter(order);
- mConversationsRoot->arrangeAll();
- refreshConversation();
-}
-
-void LLFloaterIMSessionTab::onIMSessionMenuItemClicked(const LLSD& userdata)
-{
- std::string item = userdata.asString();
-
- if (item == "compact_view" || item == "expanded_view")
- {
- gSavedSettings.setBOOL("PlainTextChatHistory", item == "compact_view");
- }
- else
- {
- bool prev_value = gSavedSettings.getBOOL(item);
- gSavedSettings.setBOOL(item, !prev_value);
- }
-
- LLFloaterIMSessionTab::processChatHistoryStyleUpdate();
-}
-
-bool LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck(const LLSD& userdata)
-{
- std::string item = userdata.asString();
- bool is_plain_text_mode = gSavedSettings.getBOOL("PlainTextChatHistory");
-
- return is_plain_text_mode? item == "compact_view" : item == "expanded_view";
-}
-
-
-bool LLFloaterIMSessionTab::onIMShowModesMenuItemCheck(const LLSD& userdata)
-{
- return gSavedSettings.getBOOL(userdata.asString());
-}
-
-// enable/disable states for the "show time" and "show names" items of the show-modes menu
-bool LLFloaterIMSessionTab::onIMShowModesMenuItemEnable(const LLSD& userdata)
-{
- std::string item = userdata.asString();
- bool plain_text = gSavedSettings.getBOOL("PlainTextChatHistory");
- bool is_not_names = (item != "IMShowNamesForP2PConv");
- return (plain_text && (is_not_names || mIsP2PChat));
-}
-
-void LLFloaterIMSessionTab::hideOrShowTitle()
-{
- const LLFloater::Params& default_params = LLFloater::getDefaultParams();
- S32 floater_header_size = default_params.header_height;
- LLView* floater_contents = getChild<LLView>("contents_view");
-
- LLRect floater_rect = getLocalRect();
- S32 top_border_of_contents = floater_rect.mTop - (isTornOff()? floater_header_size : 0);
- LLRect handle_rect (0, floater_rect.mTop, floater_rect.mRight, top_border_of_contents);
- LLRect contents_rect (0, top_border_of_contents, floater_rect.mRight, floater_rect.mBottom);
- mDragHandle->setShape(handle_rect);
- mDragHandle->setVisible(isTornOff());
- floater_contents->setShape(contents_rect);
-}
-
-void LLFloaterIMSessionTab::updateSessionName(const std::string& name)
-{
- mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + name);
-}
-
-void LLFloaterIMSessionTab::updateChatIcon(const LLUUID& id)
-{
- if (mSession)
- {
- if (mSession->isP2PSessionType())
- {
- LLAvatarIconCtrl* icon = getChild<LLAvatarIconCtrl>(ICN_AVATAR);
- icon->setVisible(true);
- icon->setValue(id);
- }
- if (mSession->isAdHocSessionType())
- {
- LLGroupIconCtrl* icon = getChild<LLGroupIconCtrl>(ICN_GROUP);
- icon->setVisible(true);
- }
- if (mSession->isGroupSessionType())
- {
- LLGroupIconCtrl* icon = getChild<LLGroupIconCtrl>(ICN_GROUP);
- icon->setVisible(true);
- icon->setValue(id);
- }
- }
- else
- {
- if (mIsNearbyChat)
- {
- LLIconCtrl* icon = getChild<LLIconCtrl>(ICN_NEARBY);
- icon->setVisible(true);
- }
- }
-
-}
-
-void LLFloaterIMSessionTab::hideAllStandardButtons()
-{
- for (S32 i = 0; i < BUTTON_COUNT; i++)
- {
- if (mButtons[i])
- {
- // Hide the standard header buttons in a docked IM floater.
- mButtons[i]->setVisible(false);
- }
- }
-}
-
-void LLFloaterIMSessionTab::updateHeaderAndToolbar()
-{
- // prevent start conversation before its container
- LLFloaterIMContainer::getInstance();
-
- bool is_not_torn_off = !checkIfTornOff();
- if (is_not_torn_off)
- {
- hideAllStandardButtons();
- }
-
- hideOrShowTitle();
-
- // Participant list should be visible only in torn off floaters.
- bool is_participant_list_visible =
- !is_not_torn_off
- && mIsParticipantListExpanded
- && !mIsP2PChat;
-
- mParticipantListAndHistoryStack->collapsePanel(mParticipantListPanel, !is_participant_list_visible);
- mParticipantListPanel->setVisible(is_participant_list_visible);
-
- // Display collapse image (<<) if the floater is hosted
- // or if it is torn off but has an open control panel.
- bool is_expanded = is_not_torn_off || is_participant_list_visible;
-
- mExpandCollapseBtn->setImageOverlay(getString(is_expanded ? "collapse_icon" : "expand_icon"));
- mExpandCollapseBtn->setToolTip(
- is_not_torn_off?
- getString("expcol_button_not_tearoff_tooltip") :
- (is_expanded?
- getString("expcol_button_tearoff_and_expanded_tooltip") :
- getString("expcol_button_tearoff_and_collapsed_tooltip")));
-
- // toggle floater's drag handle and title visibility
- if (mDragHandle)
- {
- mDragHandle->setTitleVisible(!is_not_torn_off);
- }
-
- // The button (>>) should be disabled for torn off P2P conversations.
- mExpandCollapseBtn->setEnabled(is_not_torn_off || !mIsP2PChat);
-
- mTearOffBtn->setImageOverlay(getString(is_not_torn_off? "tear_off_icon" : "return_icon"));
- mTearOffBtn->setToolTip(getString(is_not_torn_off? "tooltip_to_separate_window" : "tooltip_to_main_window"));
-
-
- mCloseBtn->setVisible(is_not_torn_off && !mIsNearbyChat);
-
- enableDisableCallBtn();
-}
-
-void LLFloaterIMSessionTab::forceReshape()
-{
- LLRect floater_rect = getRect();
- reshape(llmax(floater_rect.getWidth(), this->getMinWidth()),
- llmax(floater_rect.getHeight(), this->getMinHeight()),
- true);
-}
-
-
-void LLFloaterIMSessionTab::reshapeChatLayoutPanel()
-{
- mChatLayoutPanel->reshape(mChatLayoutPanel->getRect().getWidth(), mInputEditor->getRect().getHeight() + mInputEditorPad, false);
-}
-
-// static
-void LLFloaterIMSessionTab::processChatHistoryStyleUpdate(bool clean_messages/* = false*/)
-{
- LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel");
- for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin();
- iter != inst_list.end(); ++iter)
- {
- LLFloaterIMSession* floater = dynamic_cast<LLFloaterIMSession*>(*iter);
- if (floater)
- {
- floater->reloadMessages(clean_messages);
- }
- }
-
- LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
- if (nearby_chat)
- {
- nearby_chat->reloadMessages(clean_messages);
- }
-}
-
-// static
-void LLFloaterIMSessionTab::reloadEmptyFloaters()
-{
- LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel");
- for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin();
- iter != inst_list.end(); ++iter)
- {
- LLFloaterIMSession* floater = dynamic_cast<LLFloaterIMSession*>(*iter);
- if (floater && floater->getLastChatMessageIndex() == -1)
- {
- floater->reloadMessages(true);
- }
- }
-
- LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
- if (nearby_chat && nearby_chat->getMessageArchiveLength() == 0)
- {
- nearby_chat->reloadMessages(true);
- }
-}
-
-void LLFloaterIMSessionTab::updateCallBtnState(bool callIsActive)
-{
- mVoiceButton->setImageOverlay(callIsActive? getString("call_btn_stop") : getString("call_btn_start"));
- mVoiceButton->setToolTip(callIsActive? getString("end_call_button_tooltip") : getString("start_call_button_tooltip"));
-
- enableDisableCallBtn();
-}
-
-void LLFloaterIMSessionTab::onSlide(LLFloaterIMSessionTab* self)
-{
- LLFloaterIMContainer* host_floater = dynamic_cast<LLFloaterIMContainer*>(self->getHost());
- bool should_be_expanded = false;
- if (host_floater)
- {
- // Hide the messages pane if a floater is hosted in the Conversations
- host_floater->collapseMessagesPane(true);
- }
- else ///< floater is torn off
- {
- if (!self->mIsP2PChat)
- {
- // The state must toggle the collapsed state of the panel
- should_be_expanded = self->mParticipantListPanel->isCollapsed();
-
- // Update the expand/collapse flag of the participant list panel and save it
- gSavedSettings.setBOOL("IMShowControlPanel", should_be_expanded);
- self->mIsParticipantListExpanded = should_be_expanded;
-
- // Refresh for immediate feedback
- self->refreshConversation();
- }
- }
-
- self->assignResizeLimits();
- if (should_be_expanded)
- {
- self->forceReshape();
- }
-}
-
-void LLFloaterIMSessionTab::onCollapseToLine(LLFloaterIMSessionTab* self)
-{
- LLFloaterIMContainer* host_floater = dynamic_cast<LLFloaterIMContainer*>(self->getHost());
- if (!host_floater)
- {
- bool expand = self->isMessagePaneExpanded();
- self->mExpandCollapseLineBtn->setImageOverlay(self->getString(expand ? "collapseline_icon" : "expandline_icon"));
- self->mContentPanel->setVisible(!expand);
- self->mToolbarPanel->setVisible(!expand);
- self->mInputEditor->enableSingleLineMode(expand);
- self->reshapeFloater(expand);
- self->setMessagePaneExpanded(!expand);
- }
-}
-
-void LLFloaterIMSessionTab::reshapeFloater(bool collapse)
-{
- LLRect floater_rect = getRect();
-
- if(collapse)
- {
- mFloaterHeight = floater_rect.getHeight();
- S32 height = mContentPanel->getRect().getHeight() + mToolbarPanel->getRect().getHeight()
- + mChatLayoutPanel->getRect().getHeight() - mChatLayoutPanelHeight + 2;
- floater_rect.mTop -= height;
-
- setResizeLimits(getMinWidth(), floater_rect.getHeight());
- }
- else
- {
- floater_rect.mTop = floater_rect.mBottom + mFloaterHeight;
- setResizeLimits(getMinWidth(), mMinFloaterHeight);
- }
-
- enableResizeCtrls(true, true, !collapse);
-
- saveCollapsedState();
- setShape(floater_rect, true);
- mBodyStack->updateLayout();
-}
-
-void LLFloaterIMSessionTab::restoreFloater()
-{
- if(!isMessagePaneExpanded())
- {
- if(isMinimized())
- {
- setMinimized(false);
- }
- mContentPanel->setVisible(true);
- mToolbarPanel->setVisible(true);
- LLRect floater_rect = getRect();
- floater_rect.mTop = floater_rect.mBottom + mFloaterHeight;
- setShape(floater_rect, true);
- mBodyStack->updateLayout();
- mExpandCollapseLineBtn->setImageOverlay(getString("expandline_icon"));
- setResizeLimits(getMinWidth(), mMinFloaterHeight);
- setMessagePaneExpanded(true);
- saveCollapsedState();
- mInputEditor->enableSingleLineMode(false);
- enableResizeCtrls(true, true, true);
- }
-}
-
-/*virtual*/
-void LLFloaterIMSessionTab::onOpen(const LLSD& key)
-{
- if (!checkIfTornOff())
- {
- LLFloaterIMContainer* host_floater = dynamic_cast<LLFloaterIMContainer*>(getHost());
- // Show the messages pane when opening a floater hosted in the Conversations
- host_floater->collapseMessagesPane(false);
- }
-
- mInputButtonPanel->setVisible(isTornOff());
-
- setFocus(true);
-}
-
-
-void LLFloaterIMSessionTab::onTearOffClicked()
-{
- restoreFloater();
- setFollows(isTornOff()? FOLLOWS_ALL : FOLLOWS_NONE);
- mSaveRect = isTornOff();
- initRectControl();
- LLFloater::onClickTearOff(this);
- LLFloaterIMContainer* container = LLFloaterReg::findTypedInstance<LLFloaterIMContainer>("im_container");
-
- if (isTornOff())
- {
- container->selectAdjacentConversation(false);
- forceReshape();
- }
- //Upon re-docking the torn off floater, select the corresponding conversation line item
- else
- {
- container->selectConversation(mSessionID);
-
- }
- mInputButtonPanel->setVisible(isTornOff());
-
- refreshConversation();
- updateGearBtn();
-}
-
-void LLFloaterIMSessionTab::updateGearBtn()
-{
- bool prevVisibility = mGearBtn->getVisible();
- mGearBtn->setVisible(checkIfTornOff() && mIsP2PChat);
-
-
- // Move buttons if Gear button changed visibility
- if(prevVisibility != mGearBtn->getVisible())
- {
- LLRect gear_btn_rect = mGearBtn->getRect();
- LLRect add_btn_rect = mAddBtn->getRect();
- LLRect call_btn_rect = mVoiceButton->getRect();
- S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight;
- S32 right_shift = gear_btn_rect.getWidth() + gap_width;
- if(mGearBtn->getVisible())
- {
- // Move buttons to the right to give space for Gear button
- add_btn_rect.translate(right_shift,0);
- call_btn_rect.translate(right_shift,0);
- }
- else
- {
- add_btn_rect.translate(-right_shift,0);
- call_btn_rect.translate(-right_shift,0);
- }
- mAddBtn->setRect(add_btn_rect);
- mVoiceButton->setRect(call_btn_rect);
- }
-}
-
-void LLFloaterIMSessionTab::initBtns()
-{
- LLRect gear_btn_rect = mGearBtn->getRect();
- LLRect add_btn_rect = mAddBtn->getRect();
- LLRect call_btn_rect = mVoiceButton->getRect();
- S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight;
- S32 right_shift = gear_btn_rect.getWidth() + gap_width;
-
- add_btn_rect.translate(-right_shift,0);
- call_btn_rect.translate(-right_shift,0);
-
- mAddBtn->setRect(add_btn_rect);
- mVoiceButton->setRect(call_btn_rect);
-}
-
-// static
-bool LLFloaterIMSessionTab::isChatMultiTab()
-{
- // Restart is required in order to change chat window type.
- return true;
-}
-
-bool LLFloaterIMSessionTab::checkIfTornOff()
-{
- bool isTorn = !getHost();
-
- if (isTorn != isTornOff())
- {
- setTornOff(isTorn);
- refreshConversation();
- }
-
- return isTorn;
-}
-
-void LLFloaterIMSessionTab::doToSelected(const LLSD& userdata)
-{
- // Get the list of selected items in the tab
- std::string command = userdata.asString();
- uuid_vec_t selected_uuids;
- getSelectedUUIDs(selected_uuids);
-
- // Perform the command (IM, profile, etc...) on the list using the general conversation container method
- LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance();
- // Note: By construction, those can only be participants so we can call doToParticipants() directly
- floater_container->doToParticipants(command, selected_uuids);
-}
-
-bool LLFloaterIMSessionTab::enableContextMenuItem(const LLSD& userdata)
-{
- // Get the list of selected items in the tab
- std::string command = userdata.asString();
- uuid_vec_t selected_uuids;
- getSelectedUUIDs(selected_uuids);
-
- // Perform the item enable test on the list using the general conversation container method
- LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance();
- return floater_container->enableContextMenuItem(command, selected_uuids);
-}
-
-bool LLFloaterIMSessionTab::checkContextMenuItem(const LLSD& userdata)
-{
- // Get the list of selected items in the tab
- std::string command = userdata.asString();
- uuid_vec_t selected_uuids;
- getSelectedUUIDs(selected_uuids);
-
- // Perform the item check on the list using the general conversation container method
- LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance();
- return floater_container->checkContextMenuItem(command, selected_uuids);
-}
-
-void LLFloaterIMSessionTab::getSelectedUUIDs(uuid_vec_t& selected_uuids)
-{
- const std::set<LLFolderViewItem*> selected_items = mConversationsRoot->getSelectionList();
-
- std::set<LLFolderViewItem*>::const_iterator it = selected_items.begin();
- const std::set<LLFolderViewItem*>::const_iterator it_end = selected_items.end();
-
- for (; it != it_end; ++it)
- {
- LLConversationItem* conversation_item = static_cast<LLConversationItem *>((*it)->getViewModelItem());
- if (conversation_item)
- {
- selected_uuids.push_back(conversation_item->getUUID());
- }
- }
-}
-
-LLConversationItem* LLFloaterIMSessionTab::getCurSelectedViewModelItem()
-{
- LLConversationItem *conversationItem = NULL;
-
- if(mConversationsRoot &&
- mConversationsRoot->getCurSelectedItem() &&
- mConversationsRoot->getCurSelectedItem()->getViewModelItem())
- {
- conversationItem = static_cast<LLConversationItem *>(mConversationsRoot->getCurSelectedItem()->getViewModelItem()) ;
- }
-
- return conversationItem;
-}
-
-void LLFloaterIMSessionTab::saveCollapsedState()
-{
- LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID);
- if(conversp->isNearbyChat())
- {
- gSavedPerAccountSettings.setBOOL("NearbyChatIsNotCollapsed", isMessagePaneExpanded());
- }
-}
-
-LLView* LLFloaterIMSessionTab::getChatHistory()
-{
- return mChatHistory;
-}
-
-bool LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask )
-{
- bool handled = false;
-
- if(mask == MASK_ALT)
- {
- LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance();
- if (KEY_RETURN == key && !isTornOff())
- {
- floater_container->expandConversation();
- handled = true;
- }
- if ((KEY_UP == key) || (KEY_LEFT == key))
- {
- floater_container->selectNextorPreviousConversation(false);
- handled = true;
- }
- if ((KEY_DOWN == key ) || (KEY_RIGHT == key))
- {
- floater_container->selectNextorPreviousConversation(true);
- handled = true;
- }
- }
- return handled;
-}
+/** + * @file llfloaterimsessiontab.cpp + * @brief LLFloaterIMSessionTab class implements the common behavior of LNearbyChatBar + * @brief and LLFloaterIMSession for hosting both in LLIMContainer + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimsessiontab.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llavataractions.h" +#include "llavatariconctrl.h" +#include "llchatentry.h" +#include "llchathistory.h" +#include "llchiclet.h" +#include "llchicletbar.h" +#include "lldraghandle.h" +#include "llemojidictionary.h" +#include "llfloaterreg.h" +#include "llfloateremojipicker.h" +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container +#include "llfloaterimnearbychat.h" +#include "llgroupiconctrl.h" +#include "lllayoutstack.h" +#include "llpanelemojicomplete.h" +#include "lltoolbarview.h" + +const F32 REFRESH_INTERVAL = 1.0f; +const std::string ICN_GROUP("group_chat_icon"); +const std::string ICN_NEARBY("nearby_chat_icon"); +const std::string ICN_AVATAR("avatar_icon"); + +void cb_group_do_nothing() +{ +} + +LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) +: super(NULL, false, session_id), + mIsP2PChat(false), + mExpandCollapseBtn(NULL), + mTearOffBtn(NULL), + mCloseBtn(NULL), + mSessionID(session_id.asUUID()), + mConversationsRoot(NULL), + mScroller(NULL), + mChatHistory(NULL), + mInputEditor(NULL), + mInputEditorPad(0), + mRefreshTimer(new LLTimer()), + mIsHostAttached(false), + mHasVisibleBeenInitialized(false), + mIsParticipantListExpanded(true), + mChatLayoutPanel(NULL), + mInputPanels(NULL), + mChatLayoutPanelHeight(0) +{ + setAutoFocus(false); + mSession = LLIMModel::getInstance()->findIMSession(mSessionID); + + mCommitCallbackRegistrar.add("IMSession.Menu.Action", + boost::bind(&LLFloaterIMSessionTab::onIMSessionMenuItemClicked, this, _2)); + mEnableCallbackRegistrar.add("IMSession.Menu.CompactExpandedModes.CheckItem", + boost::bind(&LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck, this, _2)); + mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.CheckItem", + boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemCheck, this, _2)); + mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.Enable", + boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemEnable, this, _2)); + + // Right click menu handling + mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMSessionTab::checkContextMenuItem, this, _2)); + mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMSessionTab::enableContextMenuItem, this, _2)); + mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMSessionTab::doToSelected, this, _2)); + mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&cb_group_do_nothing)); + + mMinFloaterHeight = getMinHeight(); +} + +LLFloaterIMSessionTab::~LLFloaterIMSessionTab() +{ + delete mRefreshTimer; + + LLFloaterIMContainer* im_container = LLFloaterIMContainer::findInstance(); + if (im_container) + { + LLParticipantList* session = dynamic_cast<LLParticipantList*>(im_container->getSessionModel(mSessionID)); + if (session) + { + for (const conversations_widgets_map::value_type& widget_pair : mConversationsWidgets) + { + LLFolderViewItem* widget = widget_pair.second; + LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); + if (item_vmi && item_vmi->getNumRefs() == 1) + { + // This is the last pointer, remove participant from session + // before participant gets deleted on destroyView. + session->removeChild(item_vmi); + } + } + } + } +} + +// static +LLFloaterIMSessionTab* LLFloaterIMSessionTab::findConversation(const LLUUID& uuid) +{ + LLFloaterIMSessionTab* conv; + + if (uuid.isNull()) + { + conv = LLFloaterReg::findTypedInstance<LLFloaterIMSessionTab>("nearby_chat"); + } + else + { + conv = LLFloaterReg::findTypedInstance<LLFloaterIMSessionTab>("impanel", LLSD(uuid)); + } + + return conv; +}; + +// static +LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid) +{ + LLFloaterIMSessionTab* conv; + + if (uuid.isNull()) + { + conv = LLFloaterReg::getTypedInstance<LLFloaterIMSessionTab>("nearby_chat"); + } + else + { + conv = LLFloaterReg::getTypedInstance<LLFloaterIMSessionTab>("impanel", LLSD(uuid)); + conv->setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); + } + + return conv; + +}; + +// virtual +void LLFloaterIMSessionTab::setVisible(bool visible) +{ + if (visible && !mHasVisibleBeenInitialized) + { + mHasVisibleBeenInitialized = true; + if (!gAgentCamera.cameraMouselook()) + { + LLFloaterReg::getTypedInstance<LLFloaterIMContainer>("im_container")->setVisible(true); + } + LLFloaterIMSessionTab::addToHost(mSessionID); + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID); + + if (conversp && conversp->isNearbyChat() && gSavedPerAccountSettings.getBOOL("NearbyChatIsNotCollapsed")) + { + onCollapseToLine(this); + } + mInputButtonPanel->setVisible(isTornOff()); + } + + super::setVisible(visible); +} + +// virtual +void LLFloaterIMSessionTab::setFocus(bool focus) +{ + super::setFocus(focus); + + // Redirect focus to input editor + if (focus) + { + updateMessages(); + + if (mInputEditor) + { + mInputEditor->setFocus(true); + } + } +} + +void LLFloaterIMSessionTab::addToHost(const LLUUID& session_id) +{ + if ((session_id.notNull() && !gIMMgr->hasSession(session_id)) + || !LLFloaterIMSessionTab::isChatMultiTab()) + { + return; + } + + // Get the floater: this will create the instance if it didn't exist + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(session_id); + if (conversp) + { + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + + // Do not add again existing floaters + if (floater_container && !conversp->isHostAttached()) + { + conversp->setHostAttached(true); + + if (!conversp->isNearbyChat() + || gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff")) + { + floater_container->addFloater(conversp, false, LLTabContainer::RIGHT_OF_CURRENT); + } + else + { + // setting of the "potential" host for Nearby Chat: this sequence sets + // LLFloater::mHostHandle = NULL (a current host), but + // LLFloater::mLastHostHandle = floater_container (a "future" host) + conversp->setHost(floater_container); + conversp->setHost(NULL); + + conversp->forceReshape(); + } + // Added floaters share some state (like sort order) with their host + conversp->setSortOrder(floater_container->getSortOrder()); + } + } +} + +void LLFloaterIMSessionTab::assignResizeLimits() +{ + bool is_participants_pane_collapsed = mParticipantListPanel->isCollapsed(); + + // disable a layoutstack's functionality when participant list panel is collapsed + mRightPartPanel->setIgnoreReshape(is_participants_pane_collapsed); + + S32 participants_pane_target_width = is_participants_pane_collapsed? + 0 : (mParticipantListPanel->getRect().getWidth() + mParticipantListAndHistoryStack->getPanelSpacing()); + + S32 new_min_width = participants_pane_target_width + mRightPartPanel->getExpandedMinDim() + mFloaterExtraWidth; + + setResizeLimits(new_min_width, getMinHeight()); + + this->mParticipantListAndHistoryStack->updateLayout(); +} + +// virtual +bool LLFloaterIMSessionTab::postBuild() +{ + bool result; + + mBodyStack = getChild<LLLayoutStack>("main_stack"); + mParticipantListAndHistoryStack = getChild<LLLayoutStack>("im_panels"); + + mCloseBtn = getChild<LLButton>("close_btn"); + mCloseBtn->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickClose(this); }); + + mExpandCollapseBtn = getChild<LLButton>("expand_collapse_btn"); + mExpandCollapseBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onSlide(this); }); + + mExpandCollapseLineBtn = getChild<LLButton>("minz_btn"); + mExpandCollapseLineBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onCollapseToLine(this); }); + + mTearOffBtn = getChild<LLButton>("tear_off_btn"); + mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this)); + + mEmojiRecentPanelToggleBtn = getChild<LLButton>("emoji_recent_panel_toggle_btn"); + mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); + + mEmojiRecentPanel = getChild<LLLayoutPanel>("emoji_recent_layout_panel"); + mEmojiRecentPanel->setVisible(false); + + mEmojiRecentEmptyText = getChild<LLTextBox>("emoji_recent_empty_text"); + mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText()); + mEmojiRecentEmptyText->setVisible(false); + + mEmojiRecentContainer = getChild<LLPanel>("emoji_recent_container"); + mEmojiRecentContainer->setVisible(false); + + mEmojiRecentIconsCtrl = getChild<LLPanelEmojiComplete>("emoji_recent_icons_ctrl"); + mEmojiRecentIconsCtrl->setFocusReceivedCallback([this](LLFocusableElement*) { onEmojiRecentPanelFocusReceived(); }); + mEmojiRecentIconsCtrl->setFocusLostCallback([this](LLFocusableElement*) { onEmojiRecentPanelFocusLost(); }); + mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); }); + + mEmojiPickerShowBtn = getChild<LLButton>("emoji_picker_show_btn"); + mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); }); + + mGearBtn = getChild<LLButton>("gear_btn"); + mAddBtn = getChild<LLButton>("add_btn"); + mVoiceButton = getChild<LLButton>("voice_call_btn"); + + mParticipantListPanel = getChild<LLLayoutPanel>("speakers_list_panel"); + mRightPartPanel = getChild<LLLayoutPanel>("right_part_holder"); + + mToolbarPanel = getChild<LLLayoutPanel>("toolbar_panel"); + mContentPanel = getChild<LLLayoutPanel>("body_panel"); + mInputButtonPanel = getChild<LLLayoutPanel>("input_button_layout_panel"); + mInputButtonPanel->setVisible(false); + // Add a scroller for the folder (participant) view + LLRect scroller_view_rect = mParticipantListPanel->getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams<LLFolderViewScrollContainer>()); + scroller_params.rect(scroller_view_rect); + mScroller = LLUICtrlFactory::create<LLFolderViewScrollContainer>(scroller_params); + mScroller->setFollowsAll(); + + // Insert that scroller into the panel widgets hierarchy + mParticipantListPanel->addChild(mScroller); + + mChatHistory = getChild<LLChatHistory>("chat_history"); + + mInputEditor = getChild<LLChatEntry>("chat_editor"); + + mChatLayoutPanel = getChild<LLLayoutPanel>("chat_layout_panel"); + mInputPanels = getChild<LLLayoutStack>("input_panels"); + + mInputEditor->setTextExpandedCallback(boost::bind(&LLFloaterIMSessionTab::reshapeChatLayoutPanel, this)); + mInputEditor->setMouseUpCallback(boost::bind(&LLFloaterIMSessionTab::onInputEditorClicked, this)); + mInputEditor->setCommitOnFocusLost( false ); + mInputEditor->setPassDelete(true); + mInputEditor->setFont(LLViewerChat::getChatFont()); + + mChatLayoutPanelHeight = mChatLayoutPanel->getRect().getHeight(); + mInputEditorPad = mChatLayoutPanelHeight - mInputEditor->getRect().getHeight(); + + setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); + + mSaveRect = isNearbyChat() + && !gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff"); + initRectControl(); + + if (isChatMultiTab()) + { + result = LLFloater::postBuild(); + } + else + { + result = LLDockableFloater::postBuild(); + } + + // Create the root using an ad-hoc base item + LLConversationItem* base_item = new LLConversationItem(mSessionID, mConversationViewModel); + LLFolderView::Params p(LLUICtrlFactory::getDefaultParams<LLFolderView>()); + p.rect = LLRect(0, 0, getRect().getWidth(), 0); + p.parent_panel = mParticipantListPanel; + p.listener = base_item; + p.view_model = &mConversationViewModel; + p.root = NULL; + p.use_ellipses = true; + p.options_menu = "menu_conversation.xml"; + p.name = "root"; + mConversationsRoot = LLUICtrlFactory::create<LLFolderView>(p); + mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); + mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar); + // Attach that root to the scroller + mScroller->addChild(mConversationsRoot); + mConversationsRoot->setScrollContainer(mScroller); + mConversationsRoot->setFollowsAll(); + mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox); + + setMessagePaneExpanded(true); + + buildConversationViewParticipant(); + refreshConversation(); + + // Zero expiry time is set only once to allow initial update. + mRefreshTimer->setTimerExpirySec(0); + mRefreshTimer->start(); + initBtns(); + + if (mIsParticipantListExpanded != (bool)gSavedSettings.getBOOL("IMShowControlPanel")) + { + LLFloaterIMSessionTab::onSlide(this); + } + + // The resize limits for LLFloaterIMSessionTab should be updated, based on current values of width of conversation and message panels + mParticipantListPanel->getResizeBar()->setResizeListener(boost::bind(&LLFloaterIMSessionTab::assignResizeLimits, this)); + mFloaterExtraWidth = + getRect().getWidth() + - mParticipantListAndHistoryStack->getRect().getWidth() + - (mParticipantListPanel->isCollapsed()? 0 : LLPANEL_BORDER_WIDTH); + + assignResizeLimits(); + + return result; +} + +LLParticipantList* LLFloaterIMSessionTab::getParticipantList() +{ + return dynamic_cast<LLParticipantList*>(LLFloaterIMContainer::getInstance()->getSessionModel(mSessionID)); +} + +// virtual +void LLFloaterIMSessionTab::draw() +{ + if (mRefreshTimer->hasExpired()) + { + LLParticipantList* item = getParticipantList(); + if (item) + { + // Update all model items + item->update(); + // If the model and view list diverge in count, rebuild + // Note: this happens sometimes right around init (add participant events fire but get dropped) and is the cause + // of missing participants, often, the user agent itself. As there will be no other event fired, there's + // no other choice but get those inconsistencies regularly (and lightly) checked and scrubbed. + if (item->getChildrenCount() != mConversationsWidgets.size()) + { + buildConversationViewParticipant(); + } + refreshConversation(); + } + + // Restart the refresh timer + mRefreshTimer->setTimerExpirySec(REFRESH_INTERVAL); + } + + super::draw(); +} + +void LLFloaterIMSessionTab::enableDisableCallBtn() +{ + if (LLVoiceClient::instanceExists() && mVoiceButton) + { + mVoiceButton->setEnabled( + mSessionID.notNull() + && mSession + && mSession->mSessionInitialized + && LLVoiceClient::getInstance()->voiceEnabled() + && LLVoiceClient::getInstance()->isVoiceWorking() + && mSession->mCallBackEnabled); + } +} + +// virtual +void LLFloaterIMSessionTab::onFocusReceived() +{ + setBackgroundOpaque(true); + + if (mSessionID.notNull() && isInVisibleChain()) + { + LLIMModel::instance().sendNoUnreadMessages(mSessionID); + } + + super::onFocusReceived(); +} + +// virtual +void LLFloaterIMSessionTab::onFocusLost() +{ + setBackgroundOpaque(false); + super::onFocusLost(); +} + +void LLFloaterIMSessionTab::onInputEditorClicked() +{ + LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); + if (im_box) + { + im_box->flashConversationItemWidget(mSessionID,false); + } + gToolBarView->flashCommand(LLCommandId("chat"), false); +} + +void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked() +{ + bool show = !mEmojiRecentPanel->getVisible(); + if (show) + { + initEmojiRecentPanel(); + } + + mEmojiRecentPanel->setVisible(show); + mInputEditor->setFocus(true); +} + +void LLFloaterIMSessionTab::onEmojiPickerShowBtnClicked() +{ + mInputEditor->setFocus(true); + mInputEditor->showEmojiHelper(); +} + +void LLFloaterIMSessionTab::initEmojiRecentPanel() +{ + std::list<llwchar>& recentlyUsed = LLFloaterEmojiPicker::getRecentlyUsed(); + if (recentlyUsed.empty()) + { + mEmojiRecentEmptyText->setVisible(true); + mEmojiRecentContainer->setVisible(false); + } + else + { + LLWString emojis; + for (llwchar emoji : recentlyUsed) + { + emojis += emoji; + } + mEmojiRecentIconsCtrl->setEmojis(emojis); + mEmojiRecentEmptyText->setVisible(false); + mEmojiRecentContainer->setVisible(true); + } +} + +// static +void LLFloaterIMSessionTab::onEmojiRecentPanelFocusReceived() +{ + mEmojiRecentContainer->addBorder(); +} + +// static +void LLFloaterIMSessionTab::onEmojiRecentPanelFocusLost() +{ + mEmojiRecentContainer->removeBorder(); +} + +void LLFloaterIMSessionTab::onRecentEmojiPicked(const LLSD& value) +{ + LLSD::String str = value.asString(); + if (str.size()) + { + LLWString wstr = utf8string_to_wstring(str); + if (wstr.size()) + { + llwchar emoji = wstr[0]; + mInputEditor->insertEmoji(emoji); + } + } +} + +void LLFloaterIMSessionTab::closeFloater(bool app_quitting) +{ + LLFloaterEmojiPicker::saveState(); + super::closeFloater(app_quitting); +} + +std::string LLFloaterIMSessionTab::appendTime() +{ + std::string timeStr = "[" + LLTrans::getString("TimeHour") + "]:" + "[" + LLTrans::getString("TimeMin") + "]"; + + LLSD substitution; + substitution["datetime"] = (S32)time_corrected(); + LLStringUtil::format(timeStr, substitution); + + return timeStr; +} + +void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD& args) +{ + if (chat.mMuted || !mChatHistory) + return; + + // Update the participant activity time + LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); + if (im_box) + { + im_box->setTimeNow(mSessionID, chat.mFromID); + } + + LLChat& tmp_chat = const_cast<LLChat&>(chat); + + if (tmp_chat.mTimeStr.empty()) + tmp_chat.mTimeStr = appendTime(); + + tmp_chat.mFromName = chat.mFromName; + + LLSD chat_args = args; + chat_args["use_plain_text_chat_history"] = + gSavedSettings.getBOOL("PlainTextChatHistory"); + chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); + chat_args["show_names_for_p2p_conv"] = !mIsP2PChat || + gSavedSettings.getBOOL("IMShowNamesForP2PConv"); + + mChatHistory->appendMessage(chat, chat_args); +} + +void LLFloaterIMSessionTab::updateUsedEmojis(LLWString text) +{ + LLEmojiDictionary* dictionary = LLEmojiDictionary::getInstance(); + llassert_always(dictionary); + + bool emojiSent = false; + for (llwchar& c : text) + { + if (dictionary->isEmoji(c)) + { + LLFloaterEmojiPicker::onEmojiUsed(c); + emojiSent = true; + } + } + + if (!emojiSent) + return; + + LLFloaterEmojiPicker::saveState(); + + if (mEmojiRecentPanel->getVisible()) + { + initEmojiRecentPanel(); + } +} + +static LLTrace::BlockTimerStatHandle FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT("Build Conversation View"); +void LLFloaterIMSessionTab::buildConversationViewParticipant() +{ + LL_RECORD_BLOCK_TIME(FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT); + // Clear the widget list since we are rebuilding afresh from the model + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + while (widget_it != mConversationsWidgets.end()) + { + removeConversationViewParticipant(widget_it->first); + // Iterators are invalidated by erase so we need to pick begin again + widget_it = mConversationsWidgets.begin(); + } + + // Get the model list + LLParticipantList* item = getParticipantList(); + if (!item) + { + // Nothing to do if the model list is inexistent + return; + } + + // Create the participants widgets now + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); + while (current_participant_model != end_participant_model) + { + LLConversationItem* participant_model = dynamic_cast<LLConversationItem*>(*current_participant_model); + if (participant_model) + { + addConversationViewParticipant(participant_model); + } + current_participant_model++; + } +} + +void LLFloaterIMSessionTab::addConversationViewParticipant(LLConversationItem* participant_model, bool update_view) +{ + if (!participant_model) + { + // Nothing to do if the model is inexistent + return; + } + + // Check if the model already has an associated view + LLUUID uuid = participant_model->getUUID(); + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); + + // If not already present, create the participant view and attach it to the root, otherwise, just refresh it + if (widget) + { + if (update_view) + { + updateConversationViewParticipant(uuid); // overkill? + } + } + else + { + LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); + mConversationsWidgets[uuid] = participant_view; + participant_view->addToFolder(mConversationsRoot); + participant_view->addToSession(mSessionID); + participant_view->setVisible(true); + } +} + +void LLFloaterIMSessionTab::removeConversationViewParticipant(const LLUUID& participant_id) +{ + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); + if (widget) + { + LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); + if (item_vmi && item_vmi->getNumRefs() == 1) + { + // This is the last pointer, remove participant from session + // before participant gets deleted on destroyView. + // + // Floater (widget) and participant's view can simultaneously + // co-own the model, in which case view is responsible for + // the deletion and floater is free to clear and recreate + // the list, yet there are cases where only widget owns + // the pointer so it should do the cleanup. + // See "add_participant". + // + // Todo: If it keeps causing issues turn participants + // into LLPointers in the session + LLParticipantList* session = getParticipantList(); + if (session) + { + session->removeChild(item_vmi); + } + } + widget->destroyView(); + } + mConversationsWidgets.erase(participant_id); +} + +void LLFloaterIMSessionTab::updateConversationViewParticipant(const LLUUID& participant_id) +{ + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); + if (widget && widget->getViewModelItem()) + { + widget->refresh(); + } +} + +void LLFloaterIMSessionTab::refreshConversation() +{ + // Note: We collect participants names to change the session name only in the case of ad-hoc conversations + bool is_ad_hoc = (mSession ? mSession->isAdHocSessionType() : false); + uuid_vec_t participants_uuids; // uuids vector for building the added participants name string + // For P2P chat, we still need to update the session name who may have changed (switch display name for instance) + if (mIsP2PChat && mSession) + { + participants_uuids.push_back(mSession->mOtherParticipantID); + } + + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + while (widget_it != mConversationsWidgets.end()) + { + // Add the participant to the list except if it's the agent itself (redundant) + if (is_ad_hoc && (widget_it->first != gAgentID)) + { + participants_uuids.push_back(widget_it->first); + } + if (widget_it->second->getViewModelItem()) + { + widget_it->second->refresh(); + widget_it->second->setVisible(true); + } + ++widget_it; + } + if (is_ad_hoc || mIsP2PChat) + { + // Build the session name and update it + std::string session_name; + if (participants_uuids.size() != 0) + { + LLAvatarActions::buildResidentsString(participants_uuids, session_name); + } + else + { + session_name = LLIMModel::instance().getName(mSessionID); + } + updateSessionName(session_name); + } + + if (mSessionID.notNull()) + { + LLParticipantList* participant_list = getParticipantList(); + if (participant_list) + { + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = participant_list->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = participant_list->getChildrenEnd(); + LLIMSpeakerMgr *speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + while (current_participant_model != end_participant_model) + { + LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(*current_participant_model); + if (speaker_mgr && participant_model) + { + LLSpeaker *participant_speaker = speaker_mgr->findSpeaker(participant_model->getUUID()); + LLSpeaker *agent_speaker = speaker_mgr->findSpeaker(gAgentID); + if (participant_speaker && agent_speaker) + { + participant_model->setDisplayModeratorRole(agent_speaker->mIsModerator && participant_speaker->mIsModerator); + } + } + current_participant_model++; + } + } + } + + mConversationViewModel.requestSortAll(); + if(mConversationsRoot != NULL) + { + mConversationsRoot->arrangeAll(); + mConversationsRoot->update(); + } + updateHeaderAndToolbar(); + refresh(); +} + +// Copied from LLFloaterIMContainer::createConversationViewParticipant(). Refactor opportunity! +LLConversationViewParticipant* LLFloaterIMSessionTab::createConversationViewParticipant(LLConversationItem* item) +{ + LLRect panel_rect = mParticipantListPanel->getRect(); + + LLConversationViewParticipant::Params params; + params.name = item->getDisplayName(); + params.root = mConversationsRoot; + params.listener = item; + params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); // *TODO: use conversation_view_participant.xml itemHeight value in lieu of 24 + params.tool_tip = params.name; + params.participant_id = item->getUUID(); + + return LLUICtrlFactory::create<LLConversationViewParticipant>(params); +} + +void LLFloaterIMSessionTab::setSortOrder(const LLConversationSort& order) +{ + mConversationViewModel.setSorter(order); + mConversationsRoot->arrangeAll(); + refreshConversation(); +} + +void LLFloaterIMSessionTab::onIMSessionMenuItemClicked(const LLSD& userdata) +{ + std::string item = userdata.asString(); + + if (item == "compact_view" || item == "expanded_view") + { + gSavedSettings.setBOOL("PlainTextChatHistory", item == "compact_view"); + } + else + { + bool prev_value = gSavedSettings.getBOOL(item); + gSavedSettings.setBOOL(item, !prev_value); + } + + LLFloaterIMSessionTab::processChatHistoryStyleUpdate(); +} + +bool LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + bool is_plain_text_mode = gSavedSettings.getBOOL("PlainTextChatHistory"); + + return is_plain_text_mode? item == "compact_view" : item == "expanded_view"; +} + + +bool LLFloaterIMSessionTab::onIMShowModesMenuItemCheck(const LLSD& userdata) +{ + return gSavedSettings.getBOOL(userdata.asString()); +} + +// enable/disable states for the "show time" and "show names" items of the show-modes menu +bool LLFloaterIMSessionTab::onIMShowModesMenuItemEnable(const LLSD& userdata) +{ + std::string item = userdata.asString(); + bool plain_text = gSavedSettings.getBOOL("PlainTextChatHistory"); + bool is_not_names = (item != "IMShowNamesForP2PConv"); + return (plain_text && (is_not_names || mIsP2PChat)); +} + +void LLFloaterIMSessionTab::hideOrShowTitle() +{ + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + LLView* floater_contents = getChild<LLView>("contents_view"); + + LLRect floater_rect = getLocalRect(); + S32 top_border_of_contents = floater_rect.mTop - (isTornOff()? floater_header_size : 0); + LLRect handle_rect (0, floater_rect.mTop, floater_rect.mRight, top_border_of_contents); + LLRect contents_rect (0, top_border_of_contents, floater_rect.mRight, floater_rect.mBottom); + mDragHandle->setShape(handle_rect); + mDragHandle->setVisible(isTornOff()); + floater_contents->setShape(contents_rect); +} + +void LLFloaterIMSessionTab::updateSessionName(const std::string& name) +{ + mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + name); +} + +void LLFloaterIMSessionTab::updateChatIcon(const LLUUID& id) +{ + if (mSession) + { + if (mSession->isP2PSessionType()) + { + LLAvatarIconCtrl* icon = getChild<LLAvatarIconCtrl>(ICN_AVATAR); + icon->setVisible(true); + icon->setValue(id); + } + if (mSession->isAdHocSessionType()) + { + LLGroupIconCtrl* icon = getChild<LLGroupIconCtrl>(ICN_GROUP); + icon->setVisible(true); + } + if (mSession->isGroupSessionType()) + { + LLGroupIconCtrl* icon = getChild<LLGroupIconCtrl>(ICN_GROUP); + icon->setVisible(true); + icon->setValue(id); + } + } + else + { + if (mIsNearbyChat) + { + LLIconCtrl* icon = getChild<LLIconCtrl>(ICN_NEARBY); + icon->setVisible(true); + } + } + +} + +void LLFloaterIMSessionTab::hideAllStandardButtons() +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + if (mButtons[i]) + { + // Hide the standard header buttons in a docked IM floater. + mButtons[i]->setVisible(false); + } + } +} + +void LLFloaterIMSessionTab::updateHeaderAndToolbar() +{ + // prevent start conversation before its container + LLFloaterIMContainer::getInstance(); + + bool is_not_torn_off = !checkIfTornOff(); + if (is_not_torn_off) + { + hideAllStandardButtons(); + } + + hideOrShowTitle(); + + // Participant list should be visible only in torn off floaters. + bool is_participant_list_visible = + !is_not_torn_off + && mIsParticipantListExpanded + && !mIsP2PChat; + + mParticipantListAndHistoryStack->collapsePanel(mParticipantListPanel, !is_participant_list_visible); + mParticipantListPanel->setVisible(is_participant_list_visible); + + // Display collapse image (<<) if the floater is hosted + // or if it is torn off but has an open control panel. + bool is_expanded = is_not_torn_off || is_participant_list_visible; + + mExpandCollapseBtn->setImageOverlay(getString(is_expanded ? "collapse_icon" : "expand_icon")); + mExpandCollapseBtn->setToolTip( + is_not_torn_off? + getString("expcol_button_not_tearoff_tooltip") : + (is_expanded? + getString("expcol_button_tearoff_and_expanded_tooltip") : + getString("expcol_button_tearoff_and_collapsed_tooltip"))); + + // toggle floater's drag handle and title visibility + if (mDragHandle) + { + mDragHandle->setTitleVisible(!is_not_torn_off); + } + + // The button (>>) should be disabled for torn off P2P conversations. + mExpandCollapseBtn->setEnabled(is_not_torn_off || !mIsP2PChat); + + mTearOffBtn->setImageOverlay(getString(is_not_torn_off? "tear_off_icon" : "return_icon")); + mTearOffBtn->setToolTip(getString(is_not_torn_off? "tooltip_to_separate_window" : "tooltip_to_main_window")); + + + mCloseBtn->setVisible(is_not_torn_off && !mIsNearbyChat); + + enableDisableCallBtn(); +} + +void LLFloaterIMSessionTab::forceReshape() +{ + LLRect floater_rect = getRect(); + reshape(llmax(floater_rect.getWidth(), this->getMinWidth()), + llmax(floater_rect.getHeight(), this->getMinHeight()), + true); +} + + +void LLFloaterIMSessionTab::reshapeChatLayoutPanel() +{ + mChatLayoutPanel->reshape(mChatLayoutPanel->getRect().getWidth(), mInputEditor->getRect().getHeight() + mInputEditorPad, false); +} + +// static +void LLFloaterIMSessionTab::processChatHistoryStyleUpdate(bool clean_messages/* = false*/) +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); + iter != inst_list.end(); ++iter) + { + LLFloaterIMSession* floater = dynamic_cast<LLFloaterIMSession*>(*iter); + if (floater) + { + floater->reloadMessages(clean_messages); + } + } + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat"); + if (nearby_chat) + { + nearby_chat->reloadMessages(clean_messages); + } +} + +// static +void LLFloaterIMSessionTab::reloadEmptyFloaters() +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); + iter != inst_list.end(); ++iter) + { + LLFloaterIMSession* floater = dynamic_cast<LLFloaterIMSession*>(*iter); + if (floater && floater->getLastChatMessageIndex() == -1) + { + floater->reloadMessages(true); + } + } + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat"); + if (nearby_chat && nearby_chat->getMessageArchiveLength() == 0) + { + nearby_chat->reloadMessages(true); + } +} + +void LLFloaterIMSessionTab::updateCallBtnState(bool callIsActive) +{ + mVoiceButton->setImageOverlay(callIsActive? getString("call_btn_stop") : getString("call_btn_start")); + mVoiceButton->setToolTip(callIsActive? getString("end_call_button_tooltip") : getString("start_call_button_tooltip")); + + enableDisableCallBtn(); +} + +void LLFloaterIMSessionTab::onSlide(LLFloaterIMSessionTab* self) +{ + LLFloaterIMContainer* host_floater = dynamic_cast<LLFloaterIMContainer*>(self->getHost()); + bool should_be_expanded = false; + if (host_floater) + { + // Hide the messages pane if a floater is hosted in the Conversations + host_floater->collapseMessagesPane(true); + } + else ///< floater is torn off + { + if (!self->mIsP2PChat) + { + // The state must toggle the collapsed state of the panel + should_be_expanded = self->mParticipantListPanel->isCollapsed(); + + // Update the expand/collapse flag of the participant list panel and save it + gSavedSettings.setBOOL("IMShowControlPanel", should_be_expanded); + self->mIsParticipantListExpanded = should_be_expanded; + + // Refresh for immediate feedback + self->refreshConversation(); + } + } + + self->assignResizeLimits(); + if (should_be_expanded) + { + self->forceReshape(); + } +} + +void LLFloaterIMSessionTab::onCollapseToLine(LLFloaterIMSessionTab* self) +{ + LLFloaterIMContainer* host_floater = dynamic_cast<LLFloaterIMContainer*>(self->getHost()); + if (!host_floater) + { + bool expand = self->isMessagePaneExpanded(); + self->mExpandCollapseLineBtn->setImageOverlay(self->getString(expand ? "collapseline_icon" : "expandline_icon")); + self->mContentPanel->setVisible(!expand); + self->mToolbarPanel->setVisible(!expand); + self->mInputEditor->enableSingleLineMode(expand); + self->reshapeFloater(expand); + self->setMessagePaneExpanded(!expand); + } +} + +void LLFloaterIMSessionTab::reshapeFloater(bool collapse) +{ + LLRect floater_rect = getRect(); + + if(collapse) + { + mFloaterHeight = floater_rect.getHeight(); + S32 height = mContentPanel->getRect().getHeight() + mToolbarPanel->getRect().getHeight() + + mChatLayoutPanel->getRect().getHeight() - mChatLayoutPanelHeight + 2; + floater_rect.mTop -= height; + + setResizeLimits(getMinWidth(), floater_rect.getHeight()); + } + else + { + floater_rect.mTop = floater_rect.mBottom + mFloaterHeight; + setResizeLimits(getMinWidth(), mMinFloaterHeight); + } + + enableResizeCtrls(true, true, !collapse); + + saveCollapsedState(); + setShape(floater_rect, true); + mBodyStack->updateLayout(); +} + +void LLFloaterIMSessionTab::restoreFloater() +{ + if(!isMessagePaneExpanded()) + { + if(isMinimized()) + { + setMinimized(false); + } + mContentPanel->setVisible(true); + mToolbarPanel->setVisible(true); + LLRect floater_rect = getRect(); + floater_rect.mTop = floater_rect.mBottom + mFloaterHeight; + setShape(floater_rect, true); + mBodyStack->updateLayout(); + mExpandCollapseLineBtn->setImageOverlay(getString("expandline_icon")); + setResizeLimits(getMinWidth(), mMinFloaterHeight); + setMessagePaneExpanded(true); + saveCollapsedState(); + mInputEditor->enableSingleLineMode(false); + enableResizeCtrls(true, true, true); + } +} + +/*virtual*/ +void LLFloaterIMSessionTab::onOpen(const LLSD& key) +{ + if (!checkIfTornOff()) + { + LLFloaterIMContainer* host_floater = dynamic_cast<LLFloaterIMContainer*>(getHost()); + // Show the messages pane when opening a floater hosted in the Conversations + host_floater->collapseMessagesPane(false); + } + + mInputButtonPanel->setVisible(isTornOff()); + + setFocus(true); +} + + +void LLFloaterIMSessionTab::onTearOffClicked() +{ + restoreFloater(); + setFollows(isTornOff()? FOLLOWS_ALL : FOLLOWS_NONE); + mSaveRect = isTornOff(); + initRectControl(); + LLFloater::onClickTearOff(this); + LLFloaterIMContainer* container = LLFloaterReg::findTypedInstance<LLFloaterIMContainer>("im_container"); + + if (isTornOff()) + { + container->selectAdjacentConversation(false); + forceReshape(); + } + //Upon re-docking the torn off floater, select the corresponding conversation line item + else + { + container->selectConversation(mSessionID); + + } + mInputButtonPanel->setVisible(isTornOff()); + + refreshConversation(); + updateGearBtn(); +} + +void LLFloaterIMSessionTab::updateGearBtn() +{ + bool prevVisibility = mGearBtn->getVisible(); + mGearBtn->setVisible(checkIfTornOff() && mIsP2PChat); + + + // Move buttons if Gear button changed visibility + if(prevVisibility != mGearBtn->getVisible()) + { + LLRect gear_btn_rect = mGearBtn->getRect(); + LLRect add_btn_rect = mAddBtn->getRect(); + LLRect call_btn_rect = mVoiceButton->getRect(); + S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight; + S32 right_shift = gear_btn_rect.getWidth() + gap_width; + if(mGearBtn->getVisible()) + { + // Move buttons to the right to give space for Gear button + add_btn_rect.translate(right_shift,0); + call_btn_rect.translate(right_shift,0); + } + else + { + add_btn_rect.translate(-right_shift,0); + call_btn_rect.translate(-right_shift,0); + } + mAddBtn->setRect(add_btn_rect); + mVoiceButton->setRect(call_btn_rect); + } +} + +void LLFloaterIMSessionTab::initBtns() +{ + LLRect gear_btn_rect = mGearBtn->getRect(); + LLRect add_btn_rect = mAddBtn->getRect(); + LLRect call_btn_rect = mVoiceButton->getRect(); + S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight; + S32 right_shift = gear_btn_rect.getWidth() + gap_width; + + add_btn_rect.translate(-right_shift,0); + call_btn_rect.translate(-right_shift,0); + + mAddBtn->setRect(add_btn_rect); + mVoiceButton->setRect(call_btn_rect); +} + +// static +bool LLFloaterIMSessionTab::isChatMultiTab() +{ + // Restart is required in order to change chat window type. + return true; +} + +bool LLFloaterIMSessionTab::checkIfTornOff() +{ + bool isTorn = !getHost(); + + if (isTorn != isTornOff()) + { + setTornOff(isTorn); + refreshConversation(); + } + + return isTorn; +} + +void LLFloaterIMSessionTab::doToSelected(const LLSD& userdata) +{ + // Get the list of selected items in the tab + std::string command = userdata.asString(); + uuid_vec_t selected_uuids; + getSelectedUUIDs(selected_uuids); + + // Perform the command (IM, profile, etc...) on the list using the general conversation container method + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + // Note: By construction, those can only be participants so we can call doToParticipants() directly + floater_container->doToParticipants(command, selected_uuids); +} + +bool LLFloaterIMSessionTab::enableContextMenuItem(const LLSD& userdata) +{ + // Get the list of selected items in the tab + std::string command = userdata.asString(); + uuid_vec_t selected_uuids; + getSelectedUUIDs(selected_uuids); + + // Perform the item enable test on the list using the general conversation container method + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + return floater_container->enableContextMenuItem(command, selected_uuids); +} + +bool LLFloaterIMSessionTab::checkContextMenuItem(const LLSD& userdata) +{ + // Get the list of selected items in the tab + std::string command = userdata.asString(); + uuid_vec_t selected_uuids; + getSelectedUUIDs(selected_uuids); + + // Perform the item check on the list using the general conversation container method + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + return floater_container->checkContextMenuItem(command, selected_uuids); +} + +void LLFloaterIMSessionTab::getSelectedUUIDs(uuid_vec_t& selected_uuids) +{ + const std::set<LLFolderViewItem*> selected_items = mConversationsRoot->getSelectionList(); + + std::set<LLFolderViewItem*>::const_iterator it = selected_items.begin(); + const std::set<LLFolderViewItem*>::const_iterator it_end = selected_items.end(); + + for (; it != it_end; ++it) + { + LLConversationItem* conversation_item = static_cast<LLConversationItem *>((*it)->getViewModelItem()); + if (conversation_item) + { + selected_uuids.push_back(conversation_item->getUUID()); + } + } +} + +LLConversationItem* LLFloaterIMSessionTab::getCurSelectedViewModelItem() +{ + LLConversationItem *conversationItem = NULL; + + if(mConversationsRoot && + mConversationsRoot->getCurSelectedItem() && + mConversationsRoot->getCurSelectedItem()->getViewModelItem()) + { + conversationItem = static_cast<LLConversationItem *>(mConversationsRoot->getCurSelectedItem()->getViewModelItem()) ; + } + + return conversationItem; +} + +void LLFloaterIMSessionTab::saveCollapsedState() +{ + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID); + if(conversp->isNearbyChat()) + { + gSavedPerAccountSettings.setBOOL("NearbyChatIsNotCollapsed", isMessagePaneExpanded()); + } +} + +LLView* LLFloaterIMSessionTab::getChatHistory() +{ + return mChatHistory; +} + +bool LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask ) +{ + bool handled = false; + + if(mask == MASK_ALT) + { + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + if (KEY_RETURN == key && !isTornOff()) + { + floater_container->expandConversation(); + handled = true; + } + if ((KEY_UP == key) || (KEY_LEFT == key)) + { + floater_container->selectNextorPreviousConversation(false); + handled = true; + } + if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) + { + floater_container->selectNextorPreviousConversation(true); + handled = true; + } + } + return handled; +} |