From a5261a5fa8fad810ecb5c260d92c3e771822bf58 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Tue, 20 Feb 2024 23:46:23 +0100 Subject: Convert BOOL to bool in llui --- indra/newview/llfloaterimsessiontab.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'indra/newview/llfloaterimsessiontab.cpp') diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 0b0dce29fb..0a62481468 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -136,7 +136,7 @@ LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid return conv; }; -void LLFloaterIMSessionTab::setVisible(BOOL visible) +void LLFloaterIMSessionTab::setVisible(bool visible) { if(visible && !mHasVisibleBeenInitialized) { @@ -159,7 +159,7 @@ void LLFloaterIMSessionTab::setVisible(BOOL visible) } /*virtual*/ -void LLFloaterIMSessionTab::setFocus(BOOL focus) +void LLFloaterIMSessionTab::setFocus(bool focus) { LLTransientDockableFloater::setFocus(focus); @@ -170,7 +170,7 @@ void LLFloaterIMSessionTab::setFocus(BOOL focus) if (mInputEditor) { - mInputEditor->setFocus(TRUE); + mInputEditor->setFocus(true); } } } @@ -233,9 +233,9 @@ void LLFloaterIMSessionTab::assignResizeLimits() this->mParticipantListAndHistoryStack->updateLayout(); } -BOOL LLFloaterIMSessionTab::postBuild() +bool LLFloaterIMSessionTab::postBuild() { - BOOL result; + bool result; mBodyStack = getChild("main_stack"); mParticipantListAndHistoryStack = getChild("im_panels"); @@ -283,8 +283,8 @@ BOOL LLFloaterIMSessionTab::postBuild() mInputEditor->setTextExpandedCallback(boost::bind(&LLFloaterIMSessionTab::reshapeChatLayoutPanel, this)); mInputEditor->setMouseUpCallback(boost::bind(&LLFloaterIMSessionTab::onInputEditorClicked, this)); - mInputEditor->setCommitOnFocusLost( FALSE ); - mInputEditor->setPassDelete(TRUE); + mInputEditor->setCommitOnFocusLost(false); + mInputEditor->setPassDelete(true); mInputEditor->setFont(LLViewerChat::getChatFont()); mChatLayoutPanelHeight = mChatLayoutPanel->getRect().getHeight(); @@ -1019,8 +1019,7 @@ void LLFloaterIMSessionTab::onTearOffClicked() void LLFloaterIMSessionTab::updateGearBtn() { - - BOOL prevVisibility = mGearBtn->getVisible(); + bool prevVisibility = mGearBtn->getVisible(); mGearBtn->setVisible(checkIfTornOff() && mIsP2PChat); @@ -1165,9 +1164,9 @@ LLView* LLFloaterIMSessionTab::getChatHistory() return mChatHistory; } -BOOL LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask ) +bool LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask ) { - BOOL handled = FALSE; + bool handled = false; if(mask == MASK_ALT) { @@ -1175,17 +1174,17 @@ BOOL LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask ) if (KEY_RETURN == key && !isTornOff()) { floater_container->expandConversation(); - handled = TRUE; + handled = true; } if ((KEY_UP == key) || (KEY_LEFT == key)) { floater_container->selectNextorPreviousConversation(false); - handled = TRUE; + handled = true; } if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) { floater_container->selectNextorPreviousConversation(true); - handled = TRUE; + handled = true; } } return handled; -- cgit v1.2.3 From 60d3dd98a44230c21803c1606552ee098ed9fa7c Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 21 Feb 2024 21:05:14 +0100 Subject: Convert remaining BOOL to bool --- indra/newview/llfloaterimsessiontab.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/newview/llfloaterimsessiontab.cpp') diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 0a62481468..005e9f68ee 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -75,7 +75,7 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) mInputPanels(NULL), mChatLayoutPanelHeight(0) { - setAutoFocus(FALSE); + setAutoFocus(false); mSession = LLIMModel::getInstance()->findIMSession(mSessionID); mCommitCallbackRegistrar.add("IMSession.Menu.Action", @@ -536,7 +536,7 @@ void LLFloaterIMSessionTab::addConversationViewParticipant(LLConversationItem* p mConversationsWidgets[uuid] = participant_view; participant_view->addToFolder(mConversationsRoot); participant_view->addToSession(mSessionID); - participant_view->setVisible(TRUE); + participant_view->setVisible(true); } } @@ -581,7 +581,7 @@ void LLFloaterIMSessionTab::refreshConversation() if (widget_it->second->getViewModelItem()) { widget_it->second->refresh(); - widget_it->second->setVisible(TRUE); + widget_it->second->setVisible(true); } ++widget_it; } @@ -826,7 +826,7 @@ void LLFloaterIMSessionTab::forceReshape() void LLFloaterIMSessionTab::reshapeChatLayoutPanel() { - mChatLayoutPanel->reshape(mChatLayoutPanel->getRect().getWidth(), mInputEditor->getRect().getHeight() + mInputEditorPad, FALSE); + mChatLayoutPanel->reshape(mChatLayoutPanel->getRect().getWidth(), mInputEditor->getRect().getHeight() + mInputEditorPad, false); } // static @@ -987,7 +987,7 @@ void LLFloaterIMSessionTab::onOpen(const LLSD& key) mInputButtonPanel->setVisible(isTornOff()); - setFocus(TRUE); + setFocus(true); } -- cgit v1.2.3 From 5dcc0606a8512159660c652cb25e7f29292f2df0 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 23 Feb 2024 04:18:03 +0200 Subject: Issue#880 Crash on a dead pointer in a chat session --- indra/newview/llfloaterimsessiontab.cpp | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'indra/newview/llfloaterimsessiontab.cpp') diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 005e9f68ee..6c2649984f 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -99,6 +99,26 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) LLFloaterIMSessionTab::~LLFloaterIMSessionTab() { delete mRefreshTimer; + + LLFloaterIMContainer* im_container = LLFloaterIMContainer::findInstance(); + if (im_container) + { + LLParticipantList* session = dynamic_cast(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 @@ -545,6 +565,27 @@ void LLFloaterIMSessionTab::removeConversationViewParticipant(const LLUUID& part 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); -- cgit v1.2.3 From d31de6afb2ac9f659efc13c438df727372fcac08 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Sat, 24 Feb 2024 00:54:57 +0200 Subject: Issue#884 Crash on ~LLSearchEditor Crash seems to be specific to LLFilterEditor and only in a couple specific floaters. Based on older calltacks, commiting on exit was crashing. So I'm making sure that panels that potentially do not own the element in question clean the callback in case panels get deleted before the search editor. --- indra/newview/llfloaterimsessiontab.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'indra/newview/llfloaterimsessiontab.cpp') diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 6c2649984f..fd2d3095b3 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -470,7 +470,6 @@ void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD &args) { im_box->setTimeNow(mSessionID,chat.mFromID); } - LLChat& tmp_chat = const_cast(chat); -- cgit v1.2.3 From f9473e8afcb624cc1b101195bf15943ec372b56f Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 6 May 2024 16:52:34 +0200 Subject: secondlife/viewer#1333 BOOL to bool conversion leftovers: ternaries --- indra/newview/llfloaterimsessiontab.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/llfloaterimsessiontab.cpp') diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 9b1fc96706..9a7737657d 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -473,7 +473,7 @@ void LLFloaterIMSessionTab::onInputEditorClicked() void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked() { - bool show = mEmojiRecentPanel->getVisible() ? false : true; + bool show = !mEmojiRecentPanel->getVisible(); if (show) { initEmojiRecentPanel(); -- cgit v1.2.3 From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/newview/llfloaterimsessiontab.cpp | 2700 +++++++++++++++---------------- 1 file changed, 1350 insertions(+), 1350 deletions(-) (limited to 'indra/newview/llfloaterimsessiontab.cpp') 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(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("nearby_chat"); - } - else - { - conv = LLFloaterReg::findTypedInstance("impanel", LLSD(uuid)); - } - - return conv; -}; - -// static -LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid) -{ - LLFloaterIMSessionTab* conv; - - if (uuid.isNull()) - { - conv = LLFloaterReg::getTypedInstance("nearby_chat"); - } - else - { - conv = LLFloaterReg::getTypedInstance("impanel", LLSD(uuid)); - conv->setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); - } - - return conv; - -}; - -// virtual -void LLFloaterIMSessionTab::setVisible(bool visible) -{ - if (visible && !mHasVisibleBeenInitialized) - { - mHasVisibleBeenInitialized = true; - if (!gAgentCamera.cameraMouselook()) - { - LLFloaterReg::getTypedInstance("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("main_stack"); - mParticipantListAndHistoryStack = getChild("im_panels"); - - mCloseBtn = getChild("close_btn"); - mCloseBtn->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickClose(this); }); - - mExpandCollapseBtn = getChild("expand_collapse_btn"); - mExpandCollapseBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onSlide(this); }); - - mExpandCollapseLineBtn = getChild("minz_btn"); - mExpandCollapseLineBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onCollapseToLine(this); }); - - mTearOffBtn = getChild("tear_off_btn"); - mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this)); - - mEmojiRecentPanelToggleBtn = getChild("emoji_recent_panel_toggle_btn"); - mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); - - mEmojiRecentPanel = getChild("emoji_recent_layout_panel"); - mEmojiRecentPanel->setVisible(false); - - mEmojiRecentEmptyText = getChild("emoji_recent_empty_text"); - mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText()); - mEmojiRecentEmptyText->setVisible(false); - - mEmojiRecentContainer = getChild("emoji_recent_container"); - mEmojiRecentContainer->setVisible(false); - - mEmojiRecentIconsCtrl = getChild("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("emoji_picker_show_btn"); - mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); }); - - mGearBtn = getChild("gear_btn"); - mAddBtn = getChild("add_btn"); - mVoiceButton = getChild("voice_call_btn"); - - mParticipantListPanel = getChild("speakers_list_panel"); - mRightPartPanel = getChild("right_part_holder"); - - mToolbarPanel = getChild("toolbar_panel"); - mContentPanel = getChild("body_panel"); - mInputButtonPanel = getChild("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()); - scroller_params.rect(scroller_view_rect); - mScroller = LLUICtrlFactory::create(scroller_params); - mScroller->setFollowsAll(); - - // Insert that scroller into the panel widgets hierarchy - mParticipantListPanel->addChild(mScroller); - - mChatHistory = getChild("chat_history"); - - mInputEditor = getChild("chat_editor"); - - mChatLayoutPanel = getChild("chat_layout_panel"); - mInputPanels = getChild("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()); - 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(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(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& 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(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(*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(*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(params); -} - -void LLFloaterIMSessionTab::setSortOrder(const LLConversationSort& order) -{ - mConversationViewModel.setSorter(order); - mConversationsRoot->arrangeAll(); - refreshConversation(); -} - -void LLFloaterIMSessionTab::onIMSessionMenuItemClicked(const LLSD& userdata) -{ - std::string item = userdata.asString(); - - if (item == "compact_view" || item == "expanded_view") - { - gSavedSettings.setBOOL("PlainTextChatHistory", item == "compact_view"); - } - else - { - bool prev_value = gSavedSettings.getBOOL(item); - gSavedSettings.setBOOL(item, !prev_value); - } - - LLFloaterIMSessionTab::processChatHistoryStyleUpdate(); -} - -bool LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck(const LLSD& userdata) -{ - std::string item = userdata.asString(); - bool is_plain_text_mode = gSavedSettings.getBOOL("PlainTextChatHistory"); - - return is_plain_text_mode? item == "compact_view" : item == "expanded_view"; -} - - -bool LLFloaterIMSessionTab::onIMShowModesMenuItemCheck(const LLSD& userdata) -{ - return gSavedSettings.getBOOL(userdata.asString()); -} - -// enable/disable states for the "show time" and "show names" items of the show-modes menu -bool LLFloaterIMSessionTab::onIMShowModesMenuItemEnable(const LLSD& userdata) -{ - std::string item = userdata.asString(); - bool plain_text = gSavedSettings.getBOOL("PlainTextChatHistory"); - bool is_not_names = (item != "IMShowNamesForP2PConv"); - return (plain_text && (is_not_names || mIsP2PChat)); -} - -void LLFloaterIMSessionTab::hideOrShowTitle() -{ - const LLFloater::Params& default_params = LLFloater::getDefaultParams(); - S32 floater_header_size = default_params.header_height; - LLView* floater_contents = getChild("contents_view"); - - LLRect floater_rect = getLocalRect(); - S32 top_border_of_contents = floater_rect.mTop - (isTornOff()? floater_header_size : 0); - LLRect handle_rect (0, floater_rect.mTop, floater_rect.mRight, top_border_of_contents); - LLRect contents_rect (0, top_border_of_contents, floater_rect.mRight, floater_rect.mBottom); - mDragHandle->setShape(handle_rect); - mDragHandle->setVisible(isTornOff()); - floater_contents->setShape(contents_rect); -} - -void LLFloaterIMSessionTab::updateSessionName(const std::string& name) -{ - mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + name); -} - -void LLFloaterIMSessionTab::updateChatIcon(const LLUUID& id) -{ - if (mSession) - { - if (mSession->isP2PSessionType()) - { - LLAvatarIconCtrl* icon = getChild(ICN_AVATAR); - icon->setVisible(true); - icon->setValue(id); - } - if (mSession->isAdHocSessionType()) - { - LLGroupIconCtrl* icon = getChild(ICN_GROUP); - icon->setVisible(true); - } - if (mSession->isGroupSessionType()) - { - LLGroupIconCtrl* icon = getChild(ICN_GROUP); - icon->setVisible(true); - icon->setValue(id); - } - } - else - { - if (mIsNearbyChat) - { - LLIconCtrl* icon = getChild(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(*iter); - if (floater) - { - floater->reloadMessages(clean_messages); - } - } - - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("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(*iter); - if (floater && floater->getLastChatMessageIndex() == -1) - { - floater->reloadMessages(true); - } - } - - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("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(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(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(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("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 selected_items = mConversationsRoot->getSelectionList(); - - std::set::const_iterator it = selected_items.begin(); - const std::set::const_iterator it_end = selected_items.end(); - - for (; it != it_end; ++it) - { - LLConversationItem* conversation_item = static_cast((*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(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(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("nearby_chat"); + } + else + { + conv = LLFloaterReg::findTypedInstance("impanel", LLSD(uuid)); + } + + return conv; +}; + +// static +LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid) +{ + LLFloaterIMSessionTab* conv; + + if (uuid.isNull()) + { + conv = LLFloaterReg::getTypedInstance("nearby_chat"); + } + else + { + conv = LLFloaterReg::getTypedInstance("impanel", LLSD(uuid)); + conv->setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); + } + + return conv; + +}; + +// virtual +void LLFloaterIMSessionTab::setVisible(bool visible) +{ + if (visible && !mHasVisibleBeenInitialized) + { + mHasVisibleBeenInitialized = true; + if (!gAgentCamera.cameraMouselook()) + { + LLFloaterReg::getTypedInstance("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("main_stack"); + mParticipantListAndHistoryStack = getChild("im_panels"); + + mCloseBtn = getChild("close_btn"); + mCloseBtn->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickClose(this); }); + + mExpandCollapseBtn = getChild("expand_collapse_btn"); + mExpandCollapseBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onSlide(this); }); + + mExpandCollapseLineBtn = getChild("minz_btn"); + mExpandCollapseLineBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onCollapseToLine(this); }); + + mTearOffBtn = getChild("tear_off_btn"); + mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this)); + + mEmojiRecentPanelToggleBtn = getChild("emoji_recent_panel_toggle_btn"); + mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); + + mEmojiRecentPanel = getChild("emoji_recent_layout_panel"); + mEmojiRecentPanel->setVisible(false); + + mEmojiRecentEmptyText = getChild("emoji_recent_empty_text"); + mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText()); + mEmojiRecentEmptyText->setVisible(false); + + mEmojiRecentContainer = getChild("emoji_recent_container"); + mEmojiRecentContainer->setVisible(false); + + mEmojiRecentIconsCtrl = getChild("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("emoji_picker_show_btn"); + mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); }); + + mGearBtn = getChild("gear_btn"); + mAddBtn = getChild("add_btn"); + mVoiceButton = getChild("voice_call_btn"); + + mParticipantListPanel = getChild("speakers_list_panel"); + mRightPartPanel = getChild("right_part_holder"); + + mToolbarPanel = getChild("toolbar_panel"); + mContentPanel = getChild("body_panel"); + mInputButtonPanel = getChild("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()); + scroller_params.rect(scroller_view_rect); + mScroller = LLUICtrlFactory::create(scroller_params); + mScroller->setFollowsAll(); + + // Insert that scroller into the panel widgets hierarchy + mParticipantListPanel->addChild(mScroller); + + mChatHistory = getChild("chat_history"); + + mInputEditor = getChild("chat_editor"); + + mChatLayoutPanel = getChild("chat_layout_panel"); + mInputPanels = getChild("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()); + 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(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(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& 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(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(*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(*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(params); +} + +void LLFloaterIMSessionTab::setSortOrder(const LLConversationSort& order) +{ + mConversationViewModel.setSorter(order); + mConversationsRoot->arrangeAll(); + refreshConversation(); +} + +void LLFloaterIMSessionTab::onIMSessionMenuItemClicked(const LLSD& userdata) +{ + std::string item = userdata.asString(); + + if (item == "compact_view" || item == "expanded_view") + { + gSavedSettings.setBOOL("PlainTextChatHistory", item == "compact_view"); + } + else + { + bool prev_value = gSavedSettings.getBOOL(item); + gSavedSettings.setBOOL(item, !prev_value); + } + + LLFloaterIMSessionTab::processChatHistoryStyleUpdate(); +} + +bool LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + bool is_plain_text_mode = gSavedSettings.getBOOL("PlainTextChatHistory"); + + return is_plain_text_mode? item == "compact_view" : item == "expanded_view"; +} + + +bool LLFloaterIMSessionTab::onIMShowModesMenuItemCheck(const LLSD& userdata) +{ + return gSavedSettings.getBOOL(userdata.asString()); +} + +// enable/disable states for the "show time" and "show names" items of the show-modes menu +bool LLFloaterIMSessionTab::onIMShowModesMenuItemEnable(const LLSD& userdata) +{ + std::string item = userdata.asString(); + bool plain_text = gSavedSettings.getBOOL("PlainTextChatHistory"); + bool is_not_names = (item != "IMShowNamesForP2PConv"); + return (plain_text && (is_not_names || mIsP2PChat)); +} + +void LLFloaterIMSessionTab::hideOrShowTitle() +{ + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + LLView* floater_contents = getChild("contents_view"); + + LLRect floater_rect = getLocalRect(); + S32 top_border_of_contents = floater_rect.mTop - (isTornOff()? floater_header_size : 0); + LLRect handle_rect (0, floater_rect.mTop, floater_rect.mRight, top_border_of_contents); + LLRect contents_rect (0, top_border_of_contents, floater_rect.mRight, floater_rect.mBottom); + mDragHandle->setShape(handle_rect); + mDragHandle->setVisible(isTornOff()); + floater_contents->setShape(contents_rect); +} + +void LLFloaterIMSessionTab::updateSessionName(const std::string& name) +{ + mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + name); +} + +void LLFloaterIMSessionTab::updateChatIcon(const LLUUID& id) +{ + if (mSession) + { + if (mSession->isP2PSessionType()) + { + LLAvatarIconCtrl* icon = getChild(ICN_AVATAR); + icon->setVisible(true); + icon->setValue(id); + } + if (mSession->isAdHocSessionType()) + { + LLGroupIconCtrl* icon = getChild(ICN_GROUP); + icon->setVisible(true); + } + if (mSession->isGroupSessionType()) + { + LLGroupIconCtrl* icon = getChild(ICN_GROUP); + icon->setVisible(true); + icon->setValue(id); + } + } + else + { + if (mIsNearbyChat) + { + LLIconCtrl* icon = getChild(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(*iter); + if (floater) + { + floater->reloadMessages(clean_messages); + } + } + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("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(*iter); + if (floater && floater->getLastChatMessageIndex() == -1) + { + floater->reloadMessages(true); + } + } + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("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(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(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(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("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 selected_items = mConversationsRoot->getSelectionList(); + + std::set::const_iterator it = selected_items.begin(); + const std::set::const_iterator it_end = selected_items.end(); + + for (; it != it_end; ++it) + { + LLConversationItem* conversation_item = static_cast((*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(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; +} -- cgit v1.2.3 From 2696b3de088877c3a406e817ed232c252700a16c Mon Sep 17 00:00:00 2001 From: Ansariel Date: Fri, 24 May 2024 03:25:28 +0200 Subject: Introduce LLWStringView to prevent unnecessary memory allocations --- indra/newview/llfloaterimsessiontab.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/newview/llfloaterimsessiontab.cpp') diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 81a4f9936c..a0b56b14f0 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -587,13 +587,13 @@ void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD& args) mChatHistory->appendMessage(chat, chat_args); } -void LLFloaterIMSessionTab::updateUsedEmojis(LLWString text) +void LLFloaterIMSessionTab::updateUsedEmojis(LLWStringView text) { LLEmojiDictionary* dictionary = LLEmojiDictionary::getInstance(); llassert_always(dictionary); bool emojiSent = false; - for (llwchar& c : text) + for (const llwchar& c : text) { if (dictionary->isEmoji(c)) { -- cgit v1.2.3