/** * @file llparticipantlist.cpp * @brief LLParticipantList intended to update view(LLAvatarList) according to incoming messages * * $LicenseInfo:firstyear=2009&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llavataractions.h" #include "llagent.h" #include "llmutelist.h" #include "llparticipantlist.h" #include "llscrolllistctrl.h" #include "llscrolllistitem.h" #include "llspeakers.h" #include "llviewerwindow.h" #include "llvoiceclient.h" #include "llworld.h" // Edit: For ghost detection // [RLVa:KB] #include "rlvhandler.h" // [/RLVa:KB] LLParticipantList::LLParticipantList(LLSpeakerMgr* data_source, bool show_text_chatters) : mSpeakerMgr(data_source), mAvatarList(NULL), mShowTextChatters(show_text_chatters), mValidateSpeakerCallback(NULL) { setMouseOpaque(false); /* Singu TODO: Avaline? mAvalineUpdater = new LLAvalineUpdater(boost::bind(&LLParticipantList::onAvalineCallerFound, this, _1), boost::bind(&LLParticipantList::onAvalineCallerRemoved, this, _1));*/ mSpeakerAddListener = new SpeakerAddListener(*this); mSpeakerRemoveListener = new SpeakerRemoveListener(*this); mSpeakerClearListener = new SpeakerClearListener(*this); //mSpeakerModeratorListener = new SpeakerModeratorUpdateListener(*this); mSpeakerMuteListener = new SpeakerMuteListener(*this); mSpeakerMgr->addListener(mSpeakerAddListener, "add"); mSpeakerMgr->addListener(mSpeakerRemoveListener, "remove"); mSpeakerMgr->addListener(mSpeakerClearListener, "clear"); //mSpeakerMgr->addListener(mSpeakerModeratorListener, "update_moderator"); } BOOL LLParticipantList::postBuild() { mAvatarList = getChild("speakers_list"); mAvatarList->sortByColumn(gSavedSettings.getString("FloaterActiveSpeakersSortColumn"), gSavedSettings.getBOOL("FloaterActiveSpeakersSortAscending")); mAvatarList->setDoubleClickCallback(boost::bind(&LLParticipantList::onAvatarListDoubleClicked, this)); mAvatarList->setCommitOnSelectionChange(true); mAvatarList->setCommitCallback(boost::bind(&LLParticipantList::handleSpeakerSelect, this)); mAvatarList->setSortChangedCallback(boost::bind(&LLParticipantList::onSortChanged, this)); if (LLUICtrl* ctrl = findChild("mute_text_btn")) ctrl->setCommitCallback(boost::bind(&LLParticipantList::toggleMuteText, this)); if (LLUICtrl* ctrl = findChild("mute_btn")) ctrl->setCommitCallback(boost::bind(&LLParticipantList::toggleMuteVoice, this)); if (LLUICtrl* ctrl = findChild("speaker_volume")) ctrl->setCommitCallback(boost::bind(&LLParticipantList::onVolumeChange, this, _2)); if (LLUICtrl* ctrl = findChild("profile_btn")) ctrl->setCommitCallback(boost::bind(&LLParticipantList::onClickProfile, this)); if (LLUICtrl* ctrl = findChild("moderator_allow_voice")) ctrl->setCommitCallback(boost::bind(&LLParticipantList::moderateVoiceParticipant, this, _2)); if (LLUICtrl* ctrl = findChild("moderator_allow_text")) ctrl->setCommitCallback(boost::bind(&LLParticipantList::toggleAllowTextChat, this, _2)); if (LLUICtrl* ctrl = findChild("moderator_mode")) ctrl->setCommitCallback(boost::bind(&LLParticipantList::moderateVoiceAllParticipants, this, _2)); // update speaker UI handleSpeakerSelect(); return true; } LLParticipantList::~LLParticipantList() { /* Singu TODO? mAvatarListDoubleClickConnection.disconnect(); mAvatarListRefreshConnection.disconnect(); mAvatarListReturnConnection.disconnect(); mAvatarListToggleIconsConnection.disconnect(); // It is possible Participant List will be re-created from LLCallFloater::onCurrentChannelChanged() // See ticket EXT-3427 // hide menu before deleting it to stop enable and check handlers from triggering. if(mParticipantListMenu && !LLApp::isExiting()) { mParticipantListMenu->hide(); } if (mParticipantListMenu) { delete mParticipantListMenu; mParticipantListMenu = NULL; } mAvatarList->setContextMenu(NULL); mAvatarList->setComparator(NULL); delete mAvalineUpdater; */ } /*void LLParticipantList::setSpeakingIndicatorsVisible(BOOL visible) { mAvatarList->setSpeakingIndicatorsVisible(visible); }*/ void LLParticipantList::onAvatarListDoubleClicked() { LLAvatarActions::startIM(mAvatarList->getValue().asUUID()); } void LLParticipantList::handleSpeakerSelect() { const LLUUID& speaker_id = mAvatarList->getValue().asUUID(); LLPointer selected_speakerp = mSpeakerMgr->findSpeaker(speaker_id); if (speaker_id.isNull() || selected_speakerp.isNull()) { // Disable normal controls if (LLView* view = findChild("mute_btn")) view->setEnabled(false); if (LLView* view = findChild("mute_text_btn")) view->setEnabled(false); if (LLView* view = findChild("speaker_volume")) view->setEnabled(false); // Hide moderator controls if (LLView* view = findChild("moderation_mode_panel")) view->setVisible(false); if (LLView* view = findChild("moderator_controls")) view->setVisible(false); // Clear the name if (LLUICtrl* ctrl = findChild("resident_name")) ctrl->setValue(LLStringUtil::null); return; } mSpeakerMuteListener->clearDispatchers(); bool valid_speaker = selected_speakerp->mType == LLSpeaker::SPEAKER_AGENT || selected_speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL; bool can_mute = valid_speaker && LLAvatarActions::canBlock(speaker_id); bool voice_enabled = valid_speaker && LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->getVoiceEnabled(speaker_id); // since setting these values is delayed by a round trip to the Vivox servers // update them only when selecting a new speaker or // asynchronously when an update arrives if (LLUICtrl* ctrl = findChild("moderator_allow_voice")) { ctrl->setEnabled(voice_enabled); ctrl->setValue(!selected_speakerp->mModeratorMutedVoice); } if (LLUICtrl* ctrl = findChild("moderator_allow_text")) { ctrl->setEnabled(true); ctrl->setValue(!selected_speakerp->mModeratorMutedText); } if (LLView* view = findChild("moderator_controls_label")) { view->setEnabled(true); } // update UI for selected participant if (LLUICtrl* ctrl = findChild("mute_btn")) { ctrl->setValue(LLAvatarActions::isVoiceMuted(speaker_id)); ctrl->setEnabled(can_mute && voice_enabled); } if (LLUICtrl* ctrl = findChild("mute_text_btn")) { ctrl->setValue(LLAvatarActions::isBlocked(speaker_id)); ctrl->setEnabled(can_mute); } if (LLUICtrl* ctrl = findChild("speaker_volume")) { // Singu Note: Allow modifying own voice volume during a voice session (Which is valued at half of what it should be) ctrl->setValue(gAgentID == speaker_id ? gSavedSettings.getF32("AudioLevelMic")/2 : LLVoiceClient::getInstance()->getUserVolume(speaker_id)); ctrl->setEnabled(valid_speaker && voice_enabled); } if (LLView* view = findChild("profile_btn")) { view->setEnabled(selected_speakerp->mType != LLSpeaker::SPEAKER_EXTERNAL); } // show selected user name in large font if (LLUICtrl* ctrl = findChild("resident_name")) { ctrl->setValue(selected_speakerp->mDisplayName); } selected_speakerp->addListener(mSpeakerMuteListener); //update moderator capabilities LLPointer self_speakerp = mSpeakerMgr->findSpeaker(gAgentID); if (self_speakerp.isNull()) return; if (LLView* view = findChild("moderation_mode_panel")) { view->setVisible(self_speakerp->mIsModerator && mSpeakerMgr->isVoiceActive()); } if (LLView* view = findChild("moderator_controls")) { view->setVisible(self_speakerp->mIsModerator); } } void LLParticipantList::refreshSpeakers() { // store off current selection and scroll state to preserve across list rebuilds const S32 scroll_pos = mAvatarList->getScrollInterface()->getScrollPos(); // decide whether it's ok to resort the list then update the speaker manager appropriately. // rapid resorting by activity makes it hard to interact with speakers in the list // so we freeze the sorting while the user appears to be interacting with the control. // we assume this is the case whenever the mouse pointer is within the active speaker // panel and hasn't been motionless for more than a few seconds. see DEV-6655 -MG LLRect screen_rect; localRectToScreen(getLocalRect(), &screen_rect); mSpeakerMgr->update(!(screen_rect.pointInRect(gViewerWindow->getCurrentMouseX(), gViewerWindow->getCurrentMouseY()) && gMouseIdleTimer.getElapsedTimeF32() < 5.f)); std::vector items = mAvatarList->getAllData(); for (std::vector::iterator item_it = items.begin(); item_it != items.end(); ++item_it) { LLScrollListItem* itemp = (*item_it); LLPointer speakerp = mSpeakerMgr->findSpeaker(itemp->getUUID()); if (speakerp.isNull()) continue; if (LLScrollListCell* icon_cell = itemp->getColumn(0)) { if (speakerp->mStatus == LLSpeaker::STATUS_MUTED) { icon_cell->setValue("mute_icon.tga"); static const LLCachedControl sAscentMutedColor("AscentMutedColor"); icon_cell->setColor(speakerp->mModeratorMutedVoice ? /*LLColor4::grey*/sAscentMutedColor : LLColor4(1.f, 71.f / 255.f, 71.f / 255.f, 1.f)); } else { switch(llmin(2, llfloor((speakerp->mSpeechVolume / LLVoiceClient::OVERDRIVEN_POWER_LEVEL) * 3.f))) { case 0: icon_cell->setValue("icn_active-speakers-dot-lvl0.tga"); break; case 1: icon_cell->setValue("icn_active-speakers-dot-lvl1.tga"); break; case 2: icon_cell->setValue("icn_active-speakers-dot-lvl2.tga"); break; } // non voice speakers have hidden icons, render as transparent icon_cell->setColor(speakerp->mStatus > LLSpeaker::STATUS_VOICE_ACTIVE ? LLColor4::transparent : speakerp->mDotColor); } } // update name column if (LLScrollListCell* name_cell = itemp->getColumn(1)) { if (speakerp->mStatus == LLSpeaker::STATUS_NOT_IN_CHANNEL) { // draw inactive speakers in different color static const LLCachedControl sSpeakersInactive(gColors, "SpeakersInactive"); name_cell->setColor(sSpeakersInactive); } else { // bool found = mShowTextChatters || speakerp->mID == gAgentID; const LLWorld::region_list_t& regions = LLWorld::getInstance()->getRegionList(); for (LLWorld::region_list_t::const_iterator iter = regions.begin(); !found && iter != regions.end(); ++iter) { // Are they in this sim? if (const LLViewerRegion* regionp = *iter) if (regionp->mMapAvatarIDs.find(speakerp->mID) != -1) found = true; } if (!found) { static const LLCachedControl sSpeakersGhost(gColors, "SpeakersGhost"); name_cell->setColor(sSpeakersGhost); } else // { static const LLCachedControl sDefaultListText(gColors, "DefaultListText"); name_cell->setColor(sDefaultListText); } } std::string speaker_name = speakerp->mDisplayName.empty() ? LLCacheName::getDefaultName() : speakerp->mDisplayName; if (speakerp->mIsModerator) speaker_name += " " + getString("moderator_label"); name_cell->setValue(speaker_name); static_cast(name_cell)->setFontStyle(speakerp->mIsModerator ? LLFontGL::BOLD : LLFontGL::NORMAL); } // update speaking order column if (LLScrollListCell* speaking_status_cell = itemp->getColumn(2)) { // since we are forced to sort by text, encode sort order as string // print speaking ordinal in a text-sorting friendly manner speaking_status_cell->setValue(llformat("%010d", speakerp->mSortIndex)); } } // we potentially modified the sort order by touching the list items mAvatarList->setNeedsSort(); // keep scroll value stable mAvatarList->getScrollInterface()->setScrollPos(scroll_pos); } void LLParticipantList::setValidateSpeakerCallback(validate_speaker_callback_t cb) { mValidateSpeakerCallback = cb; } bool LLParticipantList::onAddItemEvent(LLPointer event, const LLSD& userdata) { LLUUID uu_id = event->getValue().asUUID(); if (mValidateSpeakerCallback && !mValidateSpeakerCallback(uu_id)) { return true; } addAvatarIDExceptAgent(uu_id); return true; } bool LLParticipantList::onRemoveItemEvent(LLPointer event, const LLSD& userdata) { const S32 pos = mAvatarList->getItemIndex(event->getValue().asUUID()); if (pos != -1) { mAvatarList->deleteSingleItem(pos); } return true; } bool LLParticipantList::onClearListEvent(LLPointer event, const LLSD& userdata) { mAvatarList->clearRows(); return true; } bool LLParticipantList::onSpeakerMuteEvent(LLPointer event, const LLSD& userdata) { LLPointer speakerp = (LLSpeaker*)event->getSource(); if (speakerp.isNull()) return false; // update UI on confirmation of moderator mutes if (event->getValue().asString() == "voice") { childSetValue("moderator_allow_voice", !speakerp->mModeratorMutedVoice); } else if (event->getValue().asString() == "text") { childSetValue("moderator_allow_text", !speakerp->mModeratorMutedText); } return true; } void LLParticipantList::addAvatarIDExceptAgent(const LLUUID& avatar_id) { //if (mExcludeAgent && gAgent.getID() == avatar_id) return; if (mAvatarList->getItemIndex(avatar_id) != -1) return; /* Singu TODO: Avaline? bool is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(avatar_id); if (is_avatar) { mAvatarList->getIDs().push_back(avatar_id); mAvatarList->setDirty(); } else { std::string display_name = LLVoiceClient::getInstance()->getDisplayName(avatar_id); mAvatarList->addAvalineItem(avatar_id, mSpeakerMgr->getSessionID(), display_name.empty() ? LLTrans::getString("AvatarNameWaiting") : display_name); mAvalineUpdater->watchAvalineCaller(avatar_id); }*/ adjustParticipant(avatar_id); } void LLParticipantList::adjustParticipant(const LLUUID& speaker_id) { LLPointer speakerp = mSpeakerMgr->findSpeaker(speaker_id); if (speakerp.isNull()) return; LLSD row; row["id"] = speaker_id; LLSD& columns = row["columns"]; columns[0]["column"] = "icon_speaking_status"; columns[0]["type"] = "icon"; columns[0]["value"] = "icn_active-speakers-dot-lvl0.tga"; const std::string& display_name = LLVoiceClient::getInstance()->getDisplayName(speaker_id); columns[1]["column"] = "speaker_name"; columns[1]["type"] = "text"; columns[1]["value"] = display_name.empty() ? LLCacheName::getDefaultName() : display_name; columns[2]["column"] = "speaking_status"; columns[2]["type"] = "text"; columns[2]["value"] = llformat("%010d", speakerp->mSortIndex); // print speaking ordinal in a text-sorting friendly manner mAvatarList->addElement(row); // add listener to process moderation changes speakerp->addListener(mSpeakerMuteListener); } // // LLParticipantList::SpeakerAddListener // bool LLParticipantList::SpeakerAddListener::handleEvent(LLPointer event, const LLSD& userdata) { /** * We need to filter speaking objects. These objects shouldn't appear in the list * @see LLFloaterChat::addChat() in llviewermessage.cpp to get detailed call hierarchy */ const LLUUID& speaker_id = event->getValue().asUUID(); LLPointer speaker = mParent.mSpeakerMgr->findSpeaker(speaker_id); if(speaker.isNull() || speaker->mType == LLSpeaker::SPEAKER_OBJECT) { return false; } return mParent.onAddItemEvent(event, userdata); } // // LLParticipantList::SpeakerRemoveListener // bool LLParticipantList::SpeakerRemoveListener::handleEvent(LLPointer event, const LLSD& userdata) { return mParent.onRemoveItemEvent(event, userdata); } // // LLParticipantList::SpeakerClearListener // bool LLParticipantList::SpeakerClearListener::handleEvent(LLPointer event, const LLSD& userdata) { return mParent.onClearListEvent(event, userdata); } // // LLParticipantList::SpeakerMuteListener // bool LLParticipantList::SpeakerMuteListener::handleEvent(LLPointer event, const LLSD& userdata) { return mParent.onSpeakerMuteEvent(event, userdata); } // Singu Note: The following functions are actually of the LLParticipantListMenu class, but we haven't married lists with menus yet. void LLParticipantList::toggleAllowTextChat(const LLSD& userdata) { if (!gAgent.getRegion()) return; LLIMSpeakerMgr* mgr = dynamic_cast(mSpeakerMgr); if (mgr) { const LLUUID speaker_id = mAvatarList->getValue(); mgr->toggleAllowTextChat(speaker_id); } } void LLParticipantList::toggleMute(const LLSD& userdata, U32 flags) { const LLUUID speaker_id = userdata.asUUID(); //mUUIDs.front(); BOOL is_muted = LLMuteList::getInstance()->isMuted(speaker_id, flags); std::string name; //fill in name using voice client's copy of name cache LLPointer speakerp = mSpeakerMgr->findSpeaker(speaker_id); if (speakerp.isNull()) { LL_WARNS("Speakers") << "Speaker " << speaker_id << " not found" << llendl; return; } name = speakerp->mDisplayName; LLMute::EType mute_type; switch (speakerp->mType) { case LLSpeaker::SPEAKER_AGENT: mute_type = LLMute::AGENT; break; case LLSpeaker::SPEAKER_OBJECT: mute_type = LLMute::OBJECT; break; case LLSpeaker::SPEAKER_EXTERNAL: default: mute_type = LLMute::EXTERNAL; break; } LLMute mute(speaker_id, name, mute_type); if (!is_muted) { LLMuteList::getInstance()->add(mute, flags); } else { LLMuteList::getInstance()->remove(mute, flags); } } void LLParticipantList::toggleMuteText() { toggleMute(mAvatarList->getValue(), LLMute::flagTextChat); } void LLParticipantList::toggleMuteVoice() { toggleMute(mAvatarList->getValue(), LLMute::flagVoiceChat); } void LLParticipantList::moderateVoiceParticipant(const LLSD& param) { if (!gAgent.getRegion()) return; LLIMSpeakerMgr* mgr = dynamic_cast(mSpeakerMgr); if (mgr) { mgr->moderateVoiceParticipant(mAvatarList->getValue(), param); } } void LLParticipantList::moderateVoiceAllParticipants(const LLSD& param) { if (!gAgent.getRegion()) return; // Singu Note: moderateVoiceAllParticipants ends up flipping the boolean passed to it before the actual post if (LLIMSpeakerMgr* speaker_manager = dynamic_cast(mSpeakerMgr)) { if (param.asString() == "unmoderated") { speaker_manager->moderateVoiceAllParticipants(true); } else if (param.asString() == "moderated") { speaker_manager->moderateVoiceAllParticipants(false); } } } // Singu Note: The following callbacks are not found upstream void LLParticipantList::onVolumeChange(const LLSD& param) { // Singu Note: Allow modifying own voice volume during a voice session (Which is valued at half of what it should be) const LLUUID& speaker_id = mAvatarList->getValue().asUUID(); if (gAgentID == speaker_id) { gSavedSettings.setF32("AudioLevelMic", param.asFloat()*2); } else { LLVoiceClient::getInstance()->setUserVolume(speaker_id, param.asFloat()); } } void LLParticipantList::onClickProfile() { // [RLVa:KB] - Checked: 2009-07-10 (RLVa-1.0.0g) | Added: RLVa-1.0.0g if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES)) return; // [/RLVa:KB] LLAvatarActions::showProfile(mAvatarList->getValue().asUUID()); } void LLParticipantList::onSortChanged() { gSavedSettings.setString("FloaterActiveSpeakersSortColumn", mAvatarList->getSortColumnName()); gSavedSettings.setBOOL("FloaterActiveSpeakersSortAscending", mAvatarList->getSortAscending()); } void LLParticipantList::setVoiceModerationCtrlMode(const bool& moderated_voice) { if (LLUICtrl* voice_moderation_ctrl = findChild("moderation_mode")) { voice_moderation_ctrl->setValue(moderated_voice ? "moderated" : "unmoderated"); } }