/** * @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(LLFloaterFriends* floater) : mFloater(floater) {} virtual ~LLLocalFriendsObserver() { mFloater = NULL; } virtual void changed(U32 mask) { mFloater->updateFriends(mask); } protected: LLFloaterFriends* mFloater; }; LLFloaterFriends* LLFloaterFriends::sInstance = NULL; LLFloaterFriends::LLFloaterFriends() : LLFloater("Friends"), LLEventTimer(1000000), mObserver(NULL), mMenuState(0), mShowMaxSelectWarning(TRUE), mAllowRightsChange(TRUE), mNumRightsChanged(0) { mTimer.stop(); sInstance = this; mObserver = new LLLocalFriendsObserver(this); LLAvatarTracker::instance().addObserver(mObserver); gSavedSettings.setBOOL("ShowFriends", TRUE); // Builds and adds to gFloaterView gUICtrlFactory->buildFloater(this, "floater_friends.xml"); } LLFloaterFriends::~LLFloaterFriends() { LLAvatarTracker::instance().removeObserver(mObserver); delete mObserver; sInstance = NULL; gSavedSettings.setBOOL("ShowFriends", FALSE); } void LLFloaterFriends::tick() { mTimer.stop(); mPeriod = 1000000; mAllowRightsChange = TRUE; updateFriends(LLFriendObserver::ADD); } // static void LLFloaterFriends::show(void*) { if(sInstance) { sInstance->open(); /*Flawfinder: ignore*/ } else { LLFloaterFriends* self = new LLFloaterFriends; self->open(); /*Flawfinder: ignore*/ } } // static BOOL LLFloaterFriends::visible(void*) { return sInstance && sInstance->getVisible(); } // static void LLFloaterFriends::toggle(void*) { if (sInstance) { sInstance->close(); } else { show(); } } void LLFloaterFriends::updateFriends(U32 changed_mask) { LLUUID selected_id; LLCtrlListInterface *friends_list = sInstance->childGetListInterface("friend_list"); if (!friends_list) return; LLCtrlScrollInterface *friends_scroll = sInstance->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 = sInstance->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; mTimer.start(); mTimer.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 LLFloaterFriends::postBuild() { mFriendsList = LLUICtrlFactory::getScrollListByName(this, "friend_list"); mFriendsList->setMaxSelectable(MAX_FRIEND_SELECT); mFriendsList->setMaxiumumSelectCallback(onMaximumSelect); 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); childSetAction("close_btn", onClickClose, this); refreshUI(); return TRUE; } void LLFloaterFriends::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 LLFloaterFriends::refreshRightsChangeList(U8 state) { LLDynamicArray friends = getSelectedIDs(); const LLRelationship* friend_status = NULL; if(friends.size() > 0) friend_status = LLAvatarTracker::instance().getBuddyInfo(friends[0]); LLSD row; bool can_change_visibility = false; bool can_change_modify = false; bool can_change_online_multiple = true; bool can_change_map_multiple = true; LLTextBox* processing_label = LLUICtrlFactory::getTextBoxByName(this, "process_rights_label"); if(!mAllowRightsChange) { if(processing_label) { processing_label->setVisible(true); state = 0; } } else { if(processing_label) { processing_label->setVisible(false); } } if(state == 1) { if(!friend_status->isOnline()) { childSetEnabled("offer_teleport_btn", false); } can_change_visibility = true; can_change_modify = true; } else if (state == 2) { can_change_visibility = true; can_change_modify = false; for(LLDynamicArray::iterator itr = friends.begin(); itr != friends.end(); ++itr) { friend_status = LLAvatarTracker::instance().getBuddyInfo(*itr); if(!friend_status->isOnline()) { childSetEnabled("offer_teleport_btn", false); } if(!friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)) { can_change_online_multiple = false; can_change_map_multiple = false; } else if(!friend_status->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION)) { can_change_map_multiple = false; } } } LLCheckboxCtrl* check; mMenuState = 0; check = LLUICtrlFactory::getCheckBoxByName(this, "online_status_cb"); check->setEnabled(can_change_visibility); check->set(FALSE); if(!mAllowRightsChange) check->setVisible(FALSE); else check->setVisible(TRUE); if(friend_status) { check->set(friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS) && can_change_online_multiple); if(friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)) mMenuState |= LLRelationship::GRANT_ONLINE_STATUS; } check = LLUICtrlFactory::getCheckBoxByName(this, "map_status_cb"); check->setEnabled(false); check->set(FALSE); if(!mAllowRightsChange) check->setVisible(FALSE); else check->setVisible(TRUE); if(friend_status) { check->setEnabled(friend_status->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS) && can_change_visibility && can_change_online_multiple); check->set(friend_status->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION) && can_change_map_multiple); if(friend_status->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION)) mMenuState |= LLRelationship::GRANT_MAP_LOCATION; } check = LLUICtrlFactory::getCheckBoxByName(this, "modify_status_cb"); check->setEnabled(can_change_modify); check->set(FALSE); if(!mAllowRightsChange) check->setVisible(FALSE); else check->setVisible(TRUE); if(can_change_modify) { if(friend_status) { check->set(friend_status->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)); if(friend_status->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)) mMenuState |= LLRelationship::GRANT_MODIFY_OBJECTS; } } } void LLFloaterFriends::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 LLFloaterFriends::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); //Note: We reset this in refreshRightsChangeList since we already have to iterate //through all selected friends there childSetEnabled("offer_teleport_btn", single_selected); refreshRightsChangeList((single_selected + multiple_selected)); } // static LLDynamicArray LLFloaterFriends::getSelectedIDs() { LLUUID selected_id; LLDynamicArray friend_ids; if(sInstance) { std::vector selected = sInstance->mFriendsList->getAllSelected(); for(std::vector::iterator itr = selected.begin(); itr != selected.end(); ++itr) { friend_ids.push_back((*itr)->getUUID()); } } return friend_ids; } // static void LLFloaterFriends::onSelectName(LLUICtrl* ctrl, void* user_data) { if(sInstance) { sInstance->refreshUI(); } } //static void LLFloaterFriends::onMaximumSelect(void* user_data) { LLString::format_map_t args; args["[MAX_SELECT]"] = llformat("%d", MAX_FRIEND_SELECT); LLNotifyBox::showXml("MaxListSelectMessage", args); }; // static void LLFloaterFriends::onClickProfile(void* user_data) { //llinfos << "LLFloaterFriends::onClickProfile()" << llendl; LLDynamicArray ids = 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 LLFloaterFriends::onClickIM(void* user_data) { //llinfos << "LLFloaterFriends::onClickIM()" << llendl; LLDynamicArray ids = 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 */ gIMView->setFloaterOpen(TRUE); gIMView->addSession( buffer, IM_NOTHING_SPECIAL, agent_id); } } else { LLUUID session_id; session_id.generate(); gIMView->setFloaterOpen(TRUE); gIMView->addSession( "Friends Conference", IM_SESSION_ADD, session_id, ids); } } } // static void LLFloaterFriends::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 LLFloaterFriends::callbackAddFriend(S32 option, void* data) { LLAddFriendData* add = (LLAddFriendData*)data; if (option == 0) { requestFriendship(add->mID, add->mName); } delete add; } // static void LLFloaterFriends::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 LLFloaterFriends::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 LLFloaterFriends::onClickAddFriend(void* user_data) { LLFloaterAvatarPicker::show(onPickAvatar, user_data, FALSE, TRUE); } // static void LLFloaterFriends::onClickRemove(void* user_data) { //llinfos << "LLFloaterFriends::onClickRemove()" << llendl; LLDynamicArray ids = 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 LLFloaterFriends::onClickOfferTeleport(void*) { LLDynamicArray ids = getSelectedIDs(); if(ids.size() > 0) { handle_lure(ids); } } // static void LLFloaterFriends::onClickPay(void*) { LLDynamicArray ids = getSelectedIDs(); if(ids.size() == 1) { handle_pay_by_id(ids[0]); } } // static void LLFloaterFriends::onClickClose(void* user_data) { //llinfos << "LLFloaterFriends::onClickClose()" << llendl; if(sInstance) { sInstance->onClose(false); } } void LLFloaterFriends::onClickOnlineStatus(LLUICtrl* ctrl, void* user_data) { bool checked = ctrl->getValue(); sInstance->updateMenuState(LLRelationship::GRANT_ONLINE_STATUS, checked); sInstance->applyRightsToFriends(LLRelationship::GRANT_ONLINE_STATUS, checked); } void LLFloaterFriends::onClickMapStatus(LLUICtrl* ctrl, void* user_data) { bool checked = ctrl->getValue(); sInstance->updateMenuState(LLRelationship::GRANT_MAP_LOCATION, checked); sInstance->applyRightsToFriends(LLRelationship::GRANT_MAP_LOCATION, checked); } void LLFloaterFriends::onClickModifyStatus(LLUICtrl* ctrl, void* user_data) { bool checked = ctrl->getValue(); LLDynamicArray ids = 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, NULL); else gViewerWindow->alertXml("RevokeModifyRights", args, handleModifyRights, NULL); } else return; } } void LLFloaterFriends::handleModifyRights(S32 option, void* user_data) { if(sInstance) { if(!option) { sInstance->updateMenuState(LLRelationship::GRANT_MODIFY_OBJECTS, !((sInstance->getMenuState() & LLRelationship::GRANT_MODIFY_OBJECTS) != 0)); sInstance->applyRightsToFriends(LLRelationship::GRANT_MODIFY_OBJECTS, ((sInstance->getMenuState() & LLRelationship::GRANT_MODIFY_OBJECTS) != 0)); } sInstance->refreshUI(); } } void LLFloaterFriends::updateMenuState(S32 flag, BOOL value) { if(value) mMenuState |= flag; else mMenuState &= ~flag; } void LLFloaterFriends::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 LLFloaterFriends::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; }