/** * @file llavatarlist.h * @brief Generic avatar list * * $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 "llavatarlist.h" // common #include "lltrans.h" #include "llcommonutils.h" // llui #include "lltextutil.h" // newview #include "llagentdata.h" // for comparator #include "llavatariconctrl.h" #include "llavatarnamecache.h" #include "llcallingcard.h" // for LLAvatarTracker #include "llcachename.h" #include "lllistcontextmenu.h" #include "llrecentpeople.h" #include "lluuid.h" #include "llvoiceclient.h" #include "llviewercontrol.h" // for gSavedSettings #include "lltooldraganddrop.h" // [RLVa:KB] - Checked: 2010-06-04 (RLVa-1.2.2a) #include "rlvhandler.h" #include "rlvactions.h" // [/RLVa:KB] static LLDefaultChildRegistry::Register r("avatar_list"); // Last interaction time update period. static const F32 LIT_UPDATE_PERIOD = 5.f; // Maximum number of avatars that can be added to a list in one pass. // Used to limit time spent for avatar list update per frame. static const unsigned ADD_LIMIT = 50; bool LLAvatarList::contains(const LLUUID& id) { const uuid_vec_t& ids = getIDs(); return std::find(ids.begin(), ids.end(), id) != ids.end(); } void LLAvatarList::toggleIcons() { // Save the new value for new items to use. mShowIcons = !mShowIcons; gSavedSettings.setBOOL(mIconParamName, mShowIcons); // Show/hide icons for all existing items. std::vector items; getItems(items); for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) { static_cast(*it)->setAvatarIconVisible(mShowIcons); } } void LLAvatarList::setSpeakingIndicatorsVisible(bool visible) { // Save the new value for new items to use. mShowSpeakingIndicator = visible; // Show/hide icons for all existing items. std::vector items; getItems(items); for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) { static_cast(*it)->showSpeakingIndicator(mShowSpeakingIndicator); } } void LLAvatarList::showPermissions(bool visible) { // Save the value for new items to use. mShowPermissions = visible; // Enable or disable showing permissions icons for all existing items. std::vector items; getItems(items); for(std::vector::const_iterator it = items.begin(), end_it = items.end(); it != end_it; ++it) { static_cast(*it)->setShowPermissions(mShowPermissions); } } void LLAvatarList::showDisplayName(bool visible) { mShowDisplayName = visible; std::vector items; getItems(items); for(std::vector::const_iterator it = items.begin(), end_it = items.end(); it != end_it; ++it) { static_cast(*it)->showDisplayName(visible); } mNeedUpdateNames = true; } void LLAvatarList::showUsername(bool visible) { mShowUsername = visible; std::vector items; getItems(items); for(std::vector::const_iterator it = items.begin(), end_it = items.end(); it != end_it; ++it) { static_cast(*it)->showUsername(visible); } mNeedUpdateNames = true; } // [FS:CR] Refresh names void LLAvatarList::refreshNames() { mNeedUpdateNames = true; } // [FS:CR] void LLAvatarList::showVoiceVolume(bool visible) { mShowVoiceVolume=visible; } static bool findInsensitive(std::string haystack, const std::string& needle_upper) { LLStringUtil::toUpper(haystack); return haystack.find(needle_upper) != std::string::npos; } //comparators static const LLAvatarItemNameComparator NAME_COMPARATOR; static const LLFlatListView::ItemReverseComparator REVERSE_NAME_COMPARATOR(NAME_COMPARATOR); // FIRE-5283: Sort by username static const LLAvatarItemUserNameComparator USERNAME_COMPARATOR; // [FS Communication UI] static const LLAvatarItemAgentOnTopComparator AGENT_ON_TOP_NAME_COMPARATOR; LLAvatarList::Params::Params() : ignore_online_status("ignore_online_status", false) , show_last_interaction_time("show_last_interaction_time", false) , show_info_btn("show_info_btn", false) , show_profile_btn("show_profile_btn", true) , show_speaking_indicator("show_speaking_indicator", true) , show_permissions_granted("show_permissions_granted", false) , show_icons("show_icons",true) , show_voice_volume("show_voice_volume", false) { } LLAvatarList::LLAvatarList(const Params& p) : LLFlatListViewEx(p) , mIgnoreOnlineStatus(p.ignore_online_status) , mShowLastInteractionTime(p.show_last_interaction_time) , mContextMenu(NULL) , mDirty(true) // to force initial update , mNeedUpdateNames(false) , mLITUpdateTimer(NULL) , mShowIcons(p.show_icons) , mShowInfoBtn(p.show_info_btn) , mShowProfileBtn(p.show_profile_btn) , mShowSpeakingIndicator(p.show_speaking_indicator) , mShowPermissions(p.show_permissions_granted) , mShowCompleteName(false) , mForceCompleteName(false) // [RLVa:KB] - Checked: RLVa-1.2.0 , mRlvCheckShowNames(false) // [/RLVa:KB] , mShowVoiceVolume(p.show_voice_volume) , mShowUsername(gSavedSettings.getBOOL("NameTagShowUsernames")) , mShowDisplayName(gSavedSettings.getBOOL("UseDisplayNames")) { setCommitOnSelectionChange(true); // Set default sort order. setComparator(&NAME_COMPARATOR); if (mShowLastInteractionTime) { mLITUpdateTimer = new LLTimer(); mLITUpdateTimer->setTimerExpirySec(0); // zero to force initial update mLITUpdateTimer->start(); } LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLAvatarList::handleDisplayNamesOptionChanged, this)); // FIRE-1089: List needs to update also if we change the username setting gSavedSettings.getControl("NameTagShowUsernames")->getSignal()->connect(boost::bind(&LLAvatarList::handleDisplayNamesOptionChanged, this)); // Update voice volume slider on RLVa shownames restriction update mRlvBehaviorCallbackConnection = gRlvHandler.setBehaviourCallback(boost::bind(&LLAvatarList::updateRlvRestrictions, this, _1, _2)); } void LLAvatarList::handleDisplayNamesOptionChanged() { // FIRE-1089: Set the proper name options for the AvatarListItem before we update the list. mShowUsername = gSavedSettings.getBOOL("NameTagShowUsernames"); mShowDisplayName = gSavedSettings.getBOOL("UseDisplayNames"); std::vector items; getItems(items); for (auto panel : items) { LLAvatarListItem* item = static_cast(panel); item->showUsername(mShowUsername, false); item->showDisplayName(mShowDisplayName, false); } // mNeedUpdateNames = true; } LLAvatarList::~LLAvatarList() { delete mLITUpdateTimer; // Update voice volume slider on RLVa shownames restriction update if (mRlvBehaviorCallbackConnection.connected()) { mRlvBehaviorCallbackConnection.disconnect(); } // } void LLAvatarList::setShowIcons(std::string param_name) { mIconParamName= param_name; mShowIcons = gSavedSettings.getBOOL(mIconParamName); } std::string LLAvatarList::getAvatarName(LLAvatarName av_name) { return mShowCompleteName? av_name.getCompleteName(false, mForceCompleteName) : av_name.getDisplayName(); } // Update voice volume slider on RLVa shownames restriction update void LLAvatarList::updateRlvRestrictions(ERlvBehaviour behavior, ERlvParamType type) { if (behavior == RLV_BHVR_SHOWNAMES) { std::vector items; getItems(items); for (auto panel : items) { LLAvatarListItem* item = static_cast(panel); item->updateRlvRestrictions(); } } } // // virtual void LLAvatarList::draw() { // *NOTE dzaporozhan // Call refresh() after draw() to avoid flickering of avatar list items. LLFlatListViewEx::draw(); if (mNeedUpdateNames) { updateAvatarNames(); } if (mDirty) refresh(); if (mShowLastInteractionTime && mLITUpdateTimer->hasExpired()) { updateLastInteractionTimes(); mLITUpdateTimer->setTimerExpirySec(LIT_UPDATE_PERIOD); // restart the timer } } //virtual void LLAvatarList::clear() { getIDs().clear(); // [RLVa:KB] - Checked: RLVa-2.0.3 // We need to be able to call this *somehow* and it actually makes moderate sense to call this in here updateNoItemsMessage(mNameFilter); // [/RLVa:KB] setDirty(true); LLFlatListViewEx::clear(); } void LLAvatarList::setNameFilter(const std::string& filter) { std::string filter_upper = filter; LLStringUtil::toUpper(filter_upper); if (mNameFilter != filter_upper) { mNameFilter = filter_upper; // update message for empty state here instead of refresh() to avoid blinking when switch // between tabs. updateNoItemsMessage(filter); setDirty(); } } // [FS Communication UI] //void LLAvatarList::sortByName() //{ // setComparator(&NAME_COMPARATOR); void LLAvatarList::sortByName(bool agent_on_top /* = false*/) { if (agent_on_top) { setComparator(&AGENT_ON_TOP_NAME_COMPARATOR); } else { setComparator(&NAME_COMPARATOR); } // sort(); } // FIRE-5283: Sort by username void LLAvatarList::sortByUserName() { setComparator(&USERNAME_COMPARATOR); sort(); } // void LLAvatarList::setDirty(bool val /*= true*/, bool force_refresh /*= false*/) { mDirty = val; if(mDirty && force_refresh) { refresh(); } } ////////////////////////////////////////////////////////////////////////// // PROTECTED SECTION ////////////////////////////////////////////////////////////////////////// void LLAvatarList::refresh() { bool have_names = true; bool add_limit_exceeded = false; bool modified = false; bool have_filter = !mNameFilter.empty(); // Save selection. uuid_vec_t selected_ids; getSelectedUUIDs(selected_ids); LLUUID current_id = getSelectedUUID(); // Determine what to add and what to remove. uuid_vec_t added, removed; LLAvatarList::computeDifference(getIDs(), added, removed); // Handle added items. unsigned nadded = 0; const std::string waiting_str = LLTrans::getString("AvatarNameWaiting"); for (uuid_vec_t::const_iterator it=added.begin(); it != added.end(); it++) { const LLUUID& buddy_id = *it; LLAvatarName av_name; have_names &= LLAvatarNameCache::get(buddy_id, &av_name); // FIRE-12750: Name filter not working correctly //if (!have_filter || findInsensitive(getAvatarName(av_name), mNameFilter)) if (!have_filter || findInsensitive(getNameForDisplay(buddy_id, av_name, mShowDisplayName, mShowUsername, mForceCompleteName, mRlvCheckShowNames), mNameFilter)) // { if (nadded >= ADD_LIMIT) { add_limit_exceeded = true; break; } else { // *NOTE: If you change the UI to show a different string, // be sure to change the filter code below. // Always show usernames on avatar lists // The passed name is not used as of 21-01-2014 //std::string display_name = getAvatarName(av_name); //addNewItem(buddy_id, // display_name.empty() ? waiting_str : display_name, // LLAvatarTracker::instance().isBuddyOnline(buddy_id)); addNewItem(buddy_id, av_name.getCompleteName(), LLAvatarTracker::instance().isBuddyOnline(buddy_id)); // modified = true; nadded++; } } } // Handle removed items. for (uuid_vec_t::const_iterator it=removed.begin(); it != removed.end(); it++) { removeItemByUUID(*it); modified = true; } // Handle filter. if (have_filter) { std::vector cur_values; getValues(cur_values); for (std::vector::const_iterator it=cur_values.begin(); it != cur_values.end(); it++) { const LLUUID& buddy_id = it->asUUID(); LLAvatarName av_name; have_names &= LLAvatarNameCache::get(buddy_id, &av_name); // FIRE-12750: Name filter not working correctly //if (!findInsensitive(getAvatarName(av_name), mNameFilter)) if (!findInsensitive(getNameForDisplay(buddy_id, av_name, mShowDisplayName, mShowUsername, mForceCompleteName, mRlvCheckShowNames), mNameFilter)) // { removeItemByUUID(buddy_id); modified = true; } } } // Changed item in place, need to request sort and update columns // because we might have changed data in a column on which the user // has already sorted. JC sort(); // re-select items // selectMultiple(selected_ids); // TODO: implement in LLFlatListView if need selectItemByUUID(current_id); // If the name filter is specified and the names are incomplete, // we need to re-update when the names are complete so that // the filter can be applied correctly. // // Otherwise, if we have no filter then no need to update again // because the items will update their names. bool dirty = add_limit_exceeded || (have_filter && !have_names); setDirty(dirty); // Refreshed all items. if(!dirty) { // Highlight items matching the filter. std::vector items; getItems(items); for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) { static_cast(*it)->setHighlight(mNameFilter); } // Send refresh_complete signal. mRefreshCompleteSignal(this, LLSD((S32)size(false))); } // Commit if we've added/removed items. if (modified) onCommit(); } void LLAvatarList::updateAvatarNames() { std::vector items; getItems(items); for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) { LLAvatarListItem* item = static_cast(*it); item->setShowCompleteName(mShowCompleteName, mForceCompleteName); item->updateAvatarName(); } mNeedUpdateNames = false; } bool LLAvatarList::filterHasMatches() { uuid_vec_t values = getIDs(); for (uuid_vec_t::const_iterator it=values.begin(); it != values.end(); it++) { const LLUUID& buddy_id = *it; LLAvatarName av_name; bool have_name = LLAvatarNameCache::get(buddy_id, &av_name); // If name has not been loaded yet we consider it as a match. // When the name will be loaded the filter will be applied again(in refresh()). // FIRE-12750: Name filter not working correctly //if (have_name && !findInsensitive(getAvatarName(av_name), mNameFilter)) if (have_name && !findInsensitive(getNameForDisplay(buddy_id, av_name, mShowDisplayName, mShowUsername, mForceCompleteName, mRlvCheckShowNames), mNameFilter)) // { continue; } return true; } return false; } boost::signals2::connection LLAvatarList::setRefreshCompleteCallback(const commit_signal_t::slot_type& cb) { return mRefreshCompleteSignal.connect(cb); } boost::signals2::connection LLAvatarList::setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb) { return mItemDoubleClickSignal.connect(cb); } boost::signals2::connection LLAvatarList::setItemClickedCallback(const mouse_signal_t::slot_type& cb) { return mItemClickedSignal.connect(cb); } //virtual S32 LLAvatarList::notifyParent(const LLSD& info) { // FIRE-11344: Group IM chatter list (and probably other) not sorting properly //if (info.has("sort") && &NAME_COMPARATOR == mItemComparator) if (info.has("sort") && ( &NAME_COMPARATOR == mItemComparator || &USERNAME_COMPARATOR == mItemComparator || &AGENT_ON_TOP_NAME_COMPARATOR == mItemComparator )) // { sort(); return 1; } // [SL:KB] - Patch: UI-AvatarListDndShare | Checked: 2011-06-19 (Catznip-2.6.0c) | Added: Catznip-2.6.0c else if (info.has("select") && info["select"].isUUID()) { const LLSD& sdValue = getSelectedValue(); const LLUUID idItem = info["select"].asUUID(); if (!sdValue.isDefined() || (sdValue.isUUID() && sdValue.asUUID() != idItem)) { resetSelection(); selectItemByUUID(info["select"].asUUID()); } } // [/SL:KB] return LLFlatListViewEx::notifyParent(info); } void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, bool is_online, EAddPosition pos) { LLAvatarListItem* item = new LLAvatarListItem(); item->setShowCompleteName(mShowCompleteName, mForceCompleteName); // [RLVa:KB] - Checked: RLVa-1.2.0 item->setRlvCheckShowNames(mRlvCheckShowNames); // [/RLVa:KB] // This sets the name as a side effect item->setAvatarId(id, mSessionID, mIgnoreOnlineStatus); item->setOnline(mIgnoreOnlineStatus ? true : is_online); item->showLastInteractionTime(mShowLastInteractionTime); item->setAvatarIconVisible(mShowIcons); item->setShowInfoBtn(mShowInfoBtn); item->setShowVoiceVolume(mShowVoiceVolume); item->setShowProfileBtn(mShowProfileBtn); item->showSpeakingIndicator(mShowSpeakingIndicator); item->setShowPermissions(mShowPermissions); item->showUsername(mShowUsername); item->showDisplayName(mShowDisplayName); item->setDoubleClickCallback(boost::bind(&LLAvatarList::onItemDoubleClicked, this, _1, _2, _3, _4)); item->setMouseDownCallback(boost::bind(&LLAvatarList::onItemClicked, this, _1, _2, _3, _4)); addItem(item, id, pos); } // virtual bool LLAvatarList::handleRightMouseDown(S32 x, S32 y, MASK mask) { bool handled = LLUICtrl::handleRightMouseDown(x, y, mask); // if ( mContextMenu) // [RLVa:KB] - Checked: 2010-06-04 (RLVa-1.2.2a) | Modified: RLVa-1.2.0d if (mContextMenu && ((!mRlvCheckShowNames) || (!RlvActions::hasBehaviour(RLV_BHVR_SHOWNAMES))) ) // [/RLVa:KB] { uuid_vec_t selected_uuids; getSelectedUUIDs(selected_uuids); mContextMenu->show(this, selected_uuids, x, y); } return handled; } bool LLAvatarList::handleMouseDown(S32 x, S32 y, MASK mask) { gFocusMgr.setMouseCapture(this); S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y); LLToolDragAndDrop::getInstance()->setDragStart(screen_x, screen_y); return LLFlatListViewEx::handleMouseDown(x, y, mask); } bool LLAvatarList::handleMouseUp( S32 x, S32 y, MASK mask ) { if(hasMouseCapture()) { gFocusMgr.setMouseCapture(NULL); } return LLFlatListViewEx::handleMouseUp(x, y, mask); } bool LLAvatarList::handleHover(S32 x, S32 y, MASK mask) { bool handled = hasMouseCapture(); if(handled) { S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y); if(LLToolDragAndDrop::getInstance()->isOverThreshold(screen_x, screen_y)) { // First, create the global drag and drop object std::vector types; uuid_vec_t cargo_ids; getSelectedUUIDs(cargo_ids); types.resize(cargo_ids.size(), DAD_PERSON); LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_PEOPLE; LLToolDragAndDrop::getInstance()->beginMultiDrag(types, cargo_ids, src); } } if(!handled) { handled = LLFlatListViewEx::handleHover(x, y, mask); } return handled; } void LLAvatarList::setVisible(bool visible) { if (!visible && mContextMenu ) { mContextMenu->hide(); } LLFlatListViewEx::setVisible(visible); } void LLAvatarList::computeDifference( const uuid_vec_t& vnew_unsorted, uuid_vec_t& vadded, uuid_vec_t& vremoved) { uuid_vec_t vcur; // Convert LLSDs to LLUUIDs. { std::vector vcur_values; getValues(vcur_values); for (size_t i=0; i items; getItems(items); for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) { // *TODO: error handling LLAvatarListItem* item = static_cast(*it); S32 secs_since = now - (S32) LLRecentPeople::instance().getDate(item->getAvatarId()).secondsSinceEpoch(); if (secs_since >= 0) item->setLastInteractionTime(secs_since); } } void LLAvatarList::onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask) { // mItemDoubleClickSignal(ctrl, x, y, mask); // [RLVa:KB] - Checked: 2010-06-05 (RLVa-1.2.2a) | Added: RLVa-1.2.0d if ( (!mRlvCheckShowNames) || (!RlvActions::hasBehaviour(RLV_BHVR_SHOWNAMES)) ) mItemDoubleClickSignal(ctrl, x, y, mask); // [/RLVa:KB] } void LLAvatarList::onItemClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask) { mItemClickedSignal(ctrl, x, y, mask); } // FIRE-12750: Name filter not working correctly // static std::string LLAvatarList::getNameForDisplay(const LLUUID& avatar_id, const LLAvatarName& av_name, bool show_displayname, bool show_username, bool force_use_complete_name, bool rlv_check_shownames) { const bool fRlvCanShowName = (!rlv_check_shownames) || (RlvActions::canShowName(RlvActions::SNC_DEFAULT, avatar_id)); if (show_displayname && !show_username) { return (fRlvCanShowName ? av_name.getDisplayName() : RlvStrings::getAnonym(av_name)); } else if (!show_displayname && show_username) { return (fRlvCanShowName ? av_name.getUserName() : RlvStrings::getAnonym(av_name)); } else { return (fRlvCanShowName ? av_name.getCompleteName(true, force_use_complete_name) : RlvStrings::getAnonym(av_name)); } } // bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const { const LLAvatarListItem* avatar_item1 = dynamic_cast(item1); const LLAvatarListItem* avatar_item2 = dynamic_cast(item2); if (!avatar_item1 || !avatar_item2) { LL_ERRS() << "item1 and item2 cannot be null" << LL_ENDL; return true; } return doCompare(avatar_item1, avatar_item2); } bool LLAvatarItemNameComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const { std::string name1 = avatar_item1->getAvatarName(); std::string name2 = avatar_item2->getAvatarName(); LLStringUtil::toUpper(name1); LLStringUtil::toUpper(name2); return name1 < name2; } bool LLAvatarItemAgentOnTopComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const { //keep agent on top, if first is agent, //then we need to return true to elevate this id, otherwise false. if(avatar_item1->getAvatarId() == gAgentID) { return true; } else if (avatar_item2->getAvatarId() == gAgentID) { return false; } return LLAvatarItemNameComparator::doCompare(avatar_item1,avatar_item2); } // FIRE-5283: Sort by username bool LLAvatarItemUserNameComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const { std::string name1 = avatar_item1->getUserName(); std::string name2 = avatar_item2->getUserName(); LLStringUtil::toUpper(name1); LLStringUtil::toUpper(name2); return name1 < name2; } //