/** * @file llfloaterfriends.cpp * @author Phoenix * @date 2005-01-13 * @brief Implementation of the friends floater * * Copyright (c) 2005-$CurrentYear$, Linden Research, Inc. * $License$ */ #include "llviewerprecompiledheaders.h" #include "llfloaterfriends.h" #include #include "lldir.h" #include "llagent.h" #include "llfloateravatarpicker.h" #include "llviewerwindow.h" #include "llbutton.h" #include "llcallingcard.h" #include "llfloateravatarinfo.h" #include "llinventorymodel.h" #include "llnamelistctrl.h" #include "llnotify.h" #include "llresmgr.h" #include "llimview.h" #include "llvieweruictrlfactory.h" #include "llmenucommands.h" #include "llviewercontrol.h" #include "llviewermessage.h" #include "lltimer.h" #include "lltextbox.h" //Maximum number of people you can select to do an operation on at once. #define MAX_FRIEND_SELECT 20 #define RIGHTS_CHANGE_TIMEOUT 5.0 // simple class to observe the calling cards. class LLLocalFriendsObserver : public LLFriendObserver { public: LLLocalFriendsObserver(LLPanelFriends* floater) : mFloater(floater) {} virtual ~LLLocalFriendsObserver() { mFloater = NULL; } virtual void changed(U32 mask) { mFloater->updateFriends(mask); } protected: LLPanelFriends* mFloater; }; LLPanelFriends::LLPanelFriends() : LLPanel(), LLEventTimer(1000000), mObserver(NULL), mMenuState(0), mShowMaxSelectWarning(TRUE), mAllowRightsChange(TRUE), mNumRightsChanged(0) { mEventTimer.stop(); mObserver = new LLLocalFriendsObserver(this); LLAvatarTracker::instance().addObserver(mObserver); } LLPanelFriends::~LLPanelFriends() { LLAvatarTracker::instance().removeObserver(mObserver); delete mObserver; } BOOL LLPanelFriends::tick() { mEventTimer.stop(); mPeriod = 1000000; mAllowRightsChange = TRUE; updateFriends(LLFriendObserver::ADD); return FALSE; } void LLPanelFriends::updateFriends(U32 changed_mask) { LLUUID selected_id; LLCtrlListInterface *friends_list = childGetListInterface("friend_list"); if (!friends_list) return; LLCtrlScrollInterface *friends_scroll = childGetScrollInterface("friend_list"); if (!friends_scroll) return; // We kill the selection warning, otherwise we'll spam with warning popups // if the maximum amount of friends are selected mShowMaxSelectWarning = false; LLDynamicArray selected_friends = getSelectedIDs(); if(changed_mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) { refreshNames(); } else if(changed_mask & LLFriendObserver::POWERS) { --mNumRightsChanged; if(mNumRightsChanged > 0) { mPeriod = RIGHTS_CHANGE_TIMEOUT; mEventTimer.start(); mEventTimer.reset(); mAllowRightsChange = FALSE; } else { tick(); } } if(selected_friends.size() > 0) { // only non-null if friends was already found. This may fail, // but we don't really care here, because refreshUI() will // clean up the interface. friends_list->setCurrentByID(selected_id); for(LLDynamicArray::iterator itr = selected_friends.begin(); itr != selected_friends.end(); ++itr) { friends_list->setSelectedByValue(*itr, true); } } refreshUI(); mShowMaxSelectWarning = true; } // virtual BOOL LLPanelFriends::postBuild() { mFriendsList = LLUICtrlFactory::getScrollListByName(this, "friend_list"); mFriendsList->setMaxSelectable(MAX_FRIEND_SELECT); mFriendsList->setMaxiumumSelectCallback(onMaximumSelect); mFriendsList->setCommitOnSelectionChange(TRUE); childSetCommitCallback("friend_list", onSelectName, this); childSetDoubleClickCallback("friend_list", onClickIM); refreshNames(); childSetCommitCallback("online_status_cb", onClickOnlineStatus, this); childSetCommitCallback("map_status_cb", onClickMapStatus, this); childSetCommitCallback("modify_status_cb", onClickModifyStatus, this); childSetAction("im_btn", onClickIM, this); childSetAction("profile_btn", onClickProfile, this); childSetAction("offer_teleport_btn", onClickOfferTeleport, this); childSetAction("pay_btn", onClickPay, this); childSetAction("add_btn", onClickAddFriend, this); childSetAction("remove_btn", onClickRemove, this); setDefaultBtn("im_btn"); updateFriends(LLFriendObserver::ADD); refreshUI(); return TRUE; } void LLPanelFriends::addFriend(const std::string& name, const LLUUID& agent_id) { LLAvatarTracker& at = LLAvatarTracker::instance(); const LLRelationship* relationInfo = at.getBuddyInfo(agent_id); if(!relationInfo) return; BOOL online = relationInfo->isOnline(); LLSD element; element["id"] = agent_id; element["columns"][LIST_FRIEND_NAME]["column"] = "friend_name"; element["columns"][LIST_FRIEND_NAME]["value"] = name.c_str(); element["columns"][LIST_FRIEND_NAME]["font"] = "SANSSERIF"; element["columns"][LIST_FRIEND_NAME]["font-style"] = "NORMAL"; element["columns"][LIST_ONLINE_STATUS]["column"] = "icon_online_status"; element["columns"][LIST_ONLINE_STATUS]["type"] = "text"; if (online) { element["columns"][LIST_FRIEND_NAME]["font-style"] = "BOLD"; element["columns"][LIST_ONLINE_STATUS]["type"] = "icon"; element["columns"][LIST_ONLINE_STATUS]["value"] = gViewerArt.getString("icon_avatar_online.tga"); } element["columns"][LIST_VISIBLE_ONLINE]["column"] = "icon_visible_online"; element["columns"][LIST_VISIBLE_ONLINE]["type"] = "text"; if(relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)) { element["columns"][LIST_VISIBLE_ONLINE]["type"] = "icon"; element["columns"][LIST_VISIBLE_ONLINE]["value"] = gViewerArt.getString("ff_visible_online.tga"); } element["columns"][LIST_VISIBLE_MAP]["column"] = "icon_visible_map"; element["columns"][LIST_VISIBLE_MAP]["type"] = "text"; if(relationInfo->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION)) { element["columns"][LIST_VISIBLE_MAP]["type"] = "icon"; element["columns"][LIST_VISIBLE_MAP]["value"] = gViewerArt.getString("ff_visible_map.tga"); } element["columns"][LIST_EDIT_MINE]["column"] = "icon_edit_mine"; element["columns"][LIST_EDIT_MINE]["type"] = "text"; if(relationInfo->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)) { element["columns"][LIST_EDIT_MINE]["type"] = "icon"; element["columns"][LIST_EDIT_MINE]["value"] = gViewerArt.getString("ff_edit_mine.tga"); } element["columns"][LIST_EDIT_THEIRS]["column"] = "icon_edit_theirs"; element["columns"][LIST_EDIT_THEIRS]["type"] = "text"; if(relationInfo->isRightGrantedFrom(LLRelationship::GRANT_MODIFY_OBJECTS)) { element["columns"][LIST_EDIT_THEIRS]["type"] = "icon"; element["columns"][LIST_EDIT_THEIRS]["value"] = gViewerArt.getString("ff_edit_theirs.tga"); } mFriendsList->addElement(element, ADD_BOTTOM); } void LLPanelFriends::refreshRightsChangeList() { LLDynamicArray friends = getSelectedIDs(); S32 num_selected = friends.size(); LLSD row; bool can_offer_teleport = num_selected >= 1; // aggregate permissions over all selected friends bool friends_see_online = true; bool friends_see_on_map = true; bool friends_modify_objects = true; // do at least some of the friends selected have these rights? bool some_friends_see_online = false; bool some_friends_see_on_map = false; bool some_friends_modify_objects = false; bool selected_friends_online = true; LLTextBox* processing_label = LLUICtrlFactory::getTextBoxByName(this, "process_rights_label"); if(!mAllowRightsChange) { if(processing_label) { processing_label->setVisible(true); // ignore selection for now friends.clear(); num_selected = 0; } } else { if(processing_label) { processing_label->setVisible(false); } } const LLRelationship* friend_status = NULL; for(LLDynamicArray::iterator itr = friends.begin(); itr != friends.end(); ++itr) { friend_status = LLAvatarTracker::instance().getBuddyInfo(*itr); if (friend_status) { bool can_see_online = friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS); bool can_see_on_map = friend_status->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION); bool can_modify_objects = friend_status->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS); // aggregate rights of this friend into total selection friends_see_online &= can_see_online; friends_see_on_map &= can_see_on_map; friends_modify_objects &= can_modify_objects; // can at least one of your selected friends do any of these? some_friends_see_online |= can_see_online; some_friends_see_on_map |= can_see_on_map; some_friends_modify_objects |= can_modify_objects; if(!friend_status->isOnline()) { can_offer_teleport = false; selected_friends_online = false; } } else // missing buddy info, don't allow any operations { can_offer_teleport = false; friends_see_online = false; friends_see_on_map = false; friends_modify_objects = false; some_friends_see_online = false; some_friends_see_on_map = false; some_friends_modify_objects = false; } } // seeing a friend on the map requires seeing online status as a prerequisite friends_see_on_map &= friends_see_online; mMenuState = 0; // make checkboxes visible after we have finished processing rights childSetVisible("online_status_cb", mAllowRightsChange); childSetVisible("map_status_cb", mAllowRightsChange); childSetVisible("modify_status_cb", mAllowRightsChange); if (num_selected == 0) // nothing selected { childSetEnabled("im_btn", FALSE); childSetEnabled("offer_teleport_btn", FALSE); childSetEnabled("online_status_cb", FALSE); childSetValue("online_status_cb", FALSE); childSetTentative("online_status_cb", FALSE); childSetEnabled("map_status_cb", FALSE); childSetValue("map_status_cb", FALSE); childSetTentative("map_status_cb", FALSE); childSetEnabled("modify_status_cb", FALSE); childSetValue("modify_status_cb", FALSE); childSetTentative("modify_status_cb", FALSE); } else // we have at least one friend selected... { // only allow IMs to groups when everyone in the group is online // to be consistent with context menus in inventory and because otherwise // offline friends would be silently dropped from the session childSetEnabled("im_btn", selected_friends_online || num_selected == 1); childSetEnabled("offer_teleport_btn", can_offer_teleport); childSetEnabled("online_status_cb", TRUE); childSetValue("online_status_cb", some_friends_see_online); childSetTentative("online_status_cb", some_friends_see_online != friends_see_online); if (friends_see_online) { mMenuState |= LLRelationship::GRANT_ONLINE_STATUS; } childSetEnabled("map_status_cb", TRUE); childSetValue("map_status_cb", some_friends_see_on_map); childSetTentative("map_status_cb", some_friends_see_on_map != friends_see_on_map); if(friends_see_on_map) { mMenuState |= LLRelationship::GRANT_MAP_LOCATION; } // for now, don't allow modify rights change for multiple select childSetEnabled("modify_status_cb", num_selected == 1); childSetValue("modify_status_cb", some_friends_modify_objects); childSetTentative("modify_status_cb", some_friends_modify_objects != friends_modify_objects); if(friends_modify_objects) { mMenuState |= LLRelationship::GRANT_MODIFY_OBJECTS; } } } void LLPanelFriends::refreshNames() { LLDynamicArray selected_ids = getSelectedIDs(); S32 pos = mFriendsList->getScrollPos(); mFriendsList->operateOnAll(LLCtrlListInterface::OP_DELETE); LLCollectAllBuddies collect; LLAvatarTracker::instance().applyFunctor(collect); LLCollectAllBuddies::buddy_map_t::const_iterator it = collect.mOnline.begin(); LLCollectAllBuddies::buddy_map_t::const_iterator end = collect.mOnline.end(); for ( ; it != end; ++it) { const std::string& name = it->first; const LLUUID& agent_id = it->second; addFriend(name, agent_id); } it = collect.mOffline.begin(); end = collect.mOffline.end(); for ( ; it != end; ++it) { const std::string& name = it->first; const LLUUID& agent_id = it->second; addFriend(name, agent_id); } mFriendsList->selectMultiple(selected_ids); mFriendsList->setScrollPos(pos); } void LLPanelFriends::refreshUI() { BOOL single_selected = FALSE; BOOL multiple_selected = FALSE; int num_selected = mFriendsList->getAllSelected().size(); if(num_selected > 0) { single_selected = TRUE; if(num_selected > 1) { childSetText("friend_name_label", "Multiple friends..."); multiple_selected = TRUE; } else { childSetText("friend_name_label", mFriendsList->getFirstSelected()->getColumn(LIST_FRIEND_NAME)->getText() + "..."); } } else { childSetText("friend_name_label", ""); } //Options that can only be performed with one friend selected childSetEnabled("profile_btn", single_selected && !multiple_selected); childSetEnabled("pay_btn", single_selected && !multiple_selected); //Options that can be performed with up to MAX_FRIEND_SELECT friends selected //(single_selected will always be true in this situations) childSetEnabled("remove_btn", single_selected); childSetEnabled("im_btn", single_selected); childSetEnabled("friend_rights", single_selected); refreshRightsChangeList(); } LLDynamicArray LLPanelFriends::getSelectedIDs() { LLUUID selected_id; LLDynamicArray friend_ids; std::vector selected = mFriendsList->getAllSelected(); for(std::vector::iterator itr = selected.begin(); itr != selected.end(); ++itr) { friend_ids.push_back((*itr)->getUUID()); } return friend_ids; } // static void LLPanelFriends::onSelectName(LLUICtrl* ctrl, void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; if(panelp) { panelp->refreshUI(); } } //static void LLPanelFriends::onMaximumSelect(void* user_data) { LLString::format_map_t args; args["[MAX_SELECT]"] = llformat("%d", MAX_FRIEND_SELECT); LLNotifyBox::showXml("MaxListSelectMessage", args); }; // static void LLPanelFriends::onClickProfile(void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; //llinfos << "LLPanelFriends::onClickProfile()" << llendl; LLDynamicArray ids = panelp->getSelectedIDs(); if(ids.size() > 0) { LLUUID agent_id = ids[0]; BOOL online; online = LLAvatarTracker::instance().isBuddyOnline(agent_id); LLFloaterAvatarInfo::showFromFriend(agent_id, online); } } // static void LLPanelFriends::onClickIM(void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; //llinfos << "LLPanelFriends::onClickIM()" << llendl; LLDynamicArray ids = panelp->getSelectedIDs(); if(ids.size() > 0) { if(ids.size() == 1) { LLUUID agent_id = ids[0]; const LLRelationship* info = LLAvatarTracker::instance().getBuddyInfo(agent_id); char first[DB_FIRST_NAME_BUF_SIZE]; /* Flawfinder: ignore */ char last[DB_LAST_NAME_BUF_SIZE]; /* Flawfinder: ignore */ if(info && gCacheName->getName(agent_id, first, last)) { char buffer[MAX_STRING]; /* Flawfinder: ignore */ snprintf(buffer, MAX_STRING, "%s %s", first, last); /* Flawfinder: ignore */ gIMMgr->setFloaterOpen(TRUE); gIMMgr->addSession( buffer, IM_NOTHING_SPECIAL, agent_id); } } else { gIMMgr->setFloaterOpen(TRUE); gIMMgr->addSession("Friends Conference", IM_SESSION_CONFERENCE_START, ids[0], ids); } make_ui_sound("UISndStartIM"); } } // static void LLPanelFriends::requestFriendship(const LLUUID& target_id, const LLString& target_name) { // HACK: folder id stored as "message" LLUUID calling_card_folder_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_CALLINGCARD); std::string message = calling_card_folder_id.asString(); send_improved_im(target_id, target_name.c_str(), message.c_str(), IM_ONLINE, IM_FRIENDSHIP_OFFERED); } struct LLAddFriendData { LLUUID mID; std::string mName; }; // static void LLPanelFriends::callbackAddFriend(S32 option, void* data) { LLAddFriendData* add = (LLAddFriendData*)data; if (option == 0) { requestFriendship(add->mID, add->mName); } delete add; } // static void LLPanelFriends::onPickAvatar(const std::vector& names, const std::vector& ids, void* ) { if (names.empty()) return; if (ids.empty()) return; requestFriendshipDialog(ids[0], names[0]); } // static void LLPanelFriends::requestFriendshipDialog(const LLUUID& id, const std::string& name) { if(id == gAgentID) { LLNotifyBox::showXml("AddSelfFriend"); return; } LLAddFriendData* data = new LLAddFriendData(); data->mID = id; data->mName = name; // TODO: accept a line of text with this dialog LLString::format_map_t args; args["[NAME]"] = name; gViewerWindow->alertXml("AddFriend", args, callbackAddFriend, data); } // static void LLPanelFriends::onClickAddFriend(void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; LLFloater* root_floater = gFloaterView->getParentFloater(panelp); LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(onPickAvatar, user_data, FALSE, TRUE); if (root_floater) { root_floater->addDependentFloater(picker); } } // static void LLPanelFriends::onClickRemove(void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; //llinfos << "LLPanelFriends::onClickRemove()" << llendl; LLDynamicArray ids = panelp->getSelectedIDs(); LLStringBase::format_map_t args; if(ids.size() > 0) { LLString msgType = "RemoveFromFriends"; if(ids.size() == 1) { LLUUID agent_id = ids[0]; char first[DB_FIRST_NAME_BUF_SIZE]; /*Flawfinder: ignore*/ char last[DB_LAST_NAME_BUF_SIZE]; /*Flawfinder: ignore*/ if(gCacheName->getName(agent_id, first, last)) { args["[FIRST_NAME]"] = first; args["[LAST_NAME]"] = last; } } else { msgType = "RemoveMultipleFromFriends"; } gViewerWindow->alertXml(msgType, args, &handleRemove, (void*)new LLDynamicArray(ids)); } } // static void LLPanelFriends::onClickOfferTeleport(void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; LLDynamicArray ids = panelp->getSelectedIDs(); if(ids.size() > 0) { handle_lure(ids); } } // static void LLPanelFriends::onClickPay(void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; LLDynamicArray ids = panelp->getSelectedIDs(); if(ids.size() == 1) { handle_pay_by_id(ids[0]); } } void LLPanelFriends::onClickOnlineStatus(LLUICtrl* ctrl, void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; bool checked = ctrl->getValue(); panelp->updateMenuState(LLRelationship::GRANT_ONLINE_STATUS, checked); panelp->applyRightsToFriends(LLRelationship::GRANT_ONLINE_STATUS, checked); } void LLPanelFriends::onClickMapStatus(LLUICtrl* ctrl, void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; bool checked = ctrl->getValue(); panelp->updateMenuState(LLRelationship::GRANT_MAP_LOCATION, checked); panelp->applyRightsToFriends(LLRelationship::GRANT_MAP_LOCATION, checked); } void LLPanelFriends::onClickModifyStatus(LLUICtrl* ctrl, void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; bool checked = ctrl->getValue(); LLDynamicArray ids = panelp->getSelectedIDs(); LLStringBase::format_map_t args; if(ids.size() > 0) { if(ids.size() == 1) { LLUUID agent_id = ids[0]; char first[DB_FIRST_NAME_BUF_SIZE]; /*Flawfinder: ignore*/ char last[DB_LAST_NAME_BUF_SIZE]; /*Flawfinder: ignore*/ if(gCacheName->getName(agent_id, first, last)) { args["[FIRST_NAME]"] = first; args["[LAST_NAME]"] = last; } if(checked) gViewerWindow->alertXml("GrantModifyRights", args, handleModifyRights, user_data); else gViewerWindow->alertXml("RevokeModifyRights", args, handleModifyRights, user_data); } else return; } } void LLPanelFriends::handleModifyRights(S32 option, void* user_data) { LLPanelFriends* panelp = (LLPanelFriends*)user_data; if(panelp) { if(!option) { panelp->updateMenuState(LLRelationship::GRANT_MODIFY_OBJECTS, !((panelp->getMenuState() & LLRelationship::GRANT_MODIFY_OBJECTS) != 0)); panelp->applyRightsToFriends(LLRelationship::GRANT_MODIFY_OBJECTS, ((panelp->getMenuState() & LLRelationship::GRANT_MODIFY_OBJECTS) != 0)); } panelp->refreshUI(); } } void LLPanelFriends::updateMenuState(S32 flag, BOOL value) { if(value) mMenuState |= flag; else mMenuState &= ~flag; } void LLPanelFriends::applyRightsToFriends(S32 flag, BOOL value) { LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_GrantUserRights); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUID(_PREHASH_AgentID, gAgent.getID()); msg->addUUID(_PREHASH_SessionID, gAgent.getSessionID()); LLDynamicArray ids = getSelectedIDs(); S32 rights; for(LLDynamicArray::iterator itr = ids.begin(); itr != ids.end(); ++itr) { rights = LLAvatarTracker::instance().getBuddyInfo(*itr)->getRightsGrantedTo(); if(LLAvatarTracker::instance().getBuddyInfo(*itr)->isRightGrantedTo(flag) != (bool)value) { if(value) rights |= flag; else rights &= ~flag; msg->nextBlockFast(_PREHASH_Rights); msg->addUUID(_PREHASH_AgentRelated, *itr); msg->addS32(_PREHASH_RelatedRights, rights); } } mNumRightsChanged = ids.size(); gAgent.sendReliableMessage(); } // static void LLPanelFriends::handleRemove(S32 option, void* user_data) { LLDynamicArray* ids = static_cast*>(user_data); for(LLDynamicArray::iterator itr = ids->begin(); itr != ids->end(); ++itr) { LLUUID id = (*itr); const LLRelationship* ip = LLAvatarTracker::instance().getBuddyInfo(id); if(ip) { switch(option) { case 0: // YES if( ip->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)) { LLAvatarTracker::instance().empower(id, FALSE); LLAvatarTracker::instance().notifyObservers(); } LLAvatarTracker::instance().terminateBuddy(id); LLAvatarTracker::instance().notifyObservers(); gInventory.addChangedMask(LLInventoryObserver::LABEL | LLInventoryObserver::CALLING_CARD, LLUUID::null); gInventory.notifyObservers(); break; case 1: // NO default: llinfos << "No removal performed." << llendl; break; } } } delete ids; }