From cf573d4143491abe627b69dd7e6b0e0349109913 Mon Sep 17 00:00:00 2001 From: Armin Weatherwax Date: Mon, 24 Jan 2011 18:46:25 +0100 Subject: Henri Beauchamp: Display Names support v4 --- linden/etc/message.xml | 20 +- linden/indra/llcommon/CMakeLists.txt | 2 + linden/indra/llcommon/llavatarname.cpp | 150 ++++ linden/indra/llcommon/llavatarname.h | 105 +++ linden/indra/llmessage/CMakeLists.txt | 2 + linden/indra/llmessage/llavatarnamecache.cpp | 859 +++++++++++++++++++++ linden/indra/llmessage/llavatarnamecache.h | 105 +++ linden/indra/llmessage/llcachename.h | 2 + linden/indra/newview/CMakeLists.txt | 4 + linden/indra/newview/app_settings/settings.xml | 36 + linden/indra/newview/llappviewer.cpp | 73 ++ linden/indra/newview/llappviewer.h | 1 + linden/indra/newview/llcallingcard.cpp | 50 ++ linden/indra/newview/llcallingcard.h | 3 + linden/indra/newview/llfloateractivespeakers.cpp | 7 +- linden/indra/newview/llfloateractivespeakers.h | 3 +- linden/indra/newview/llfloateravatarinfo.cpp | 10 +- linden/indra/newview/llfloateravatarinfo.h | 4 +- linden/indra/newview/llfloaterchatterbox.cpp | 3 + linden/indra/newview/llfloaterdisplayname.cpp | 224 ++++++ linden/indra/newview/llfloaterdisplayname.h | 48 ++ linden/indra/newview/llfloaterfriends.cpp | 75 ++ linden/indra/newview/llfloaternewim.cpp | 20 +- linden/indra/newview/llhoverview.cpp | 21 +- linden/indra/newview/llimpanel.cpp | 66 +- linden/indra/newview/llimpanel.h | 6 + linden/indra/newview/llnamelistctrl.cpp | 46 +- linden/indra/newview/llnamelistctrl.h | 5 + linden/indra/newview/llnetmap.cpp | 25 +- linden/indra/newview/llpanelavatar.cpp | 42 + linden/indra/newview/llpanelavatar.h | 4 + linden/indra/newview/llstartup.cpp | 6 + linden/indra/newview/llviewercontrol.cpp | 27 + linden/indra/newview/llviewerdisplayname.cpp | 208 +++++ linden/indra/newview/llviewerdisplayname.h | 53 ++ linden/indra/newview/llviewermenu.cpp | 17 + linden/indra/newview/llviewermessage.cpp | 27 + linden/indra/newview/llviewerregion.cpp | 17 +- linden/indra/newview/llviewerregion.h | 6 + linden/indra/newview/llvoavatar.cpp | 80 +- linden/indra/newview/llvoavatar.h | 5 + .../default/xui/en-us/floater_display_name.xml | 42 + .../skins/default/xui/en-us/floater_new_im.xml | 5 +- .../skins/default/xui/en-us/menu_viewer.xml | 5 + .../skins/default/xui/en-us/notifications.xml | 86 ++- .../skins/default/xui/en-us/panel_avatar.xml | 5 + 46 files changed, 2564 insertions(+), 46 deletions(-) create mode 100644 linden/indra/llcommon/llavatarname.cpp create mode 100644 linden/indra/llcommon/llavatarname.h create mode 100644 linden/indra/llmessage/llavatarnamecache.cpp create mode 100644 linden/indra/llmessage/llavatarnamecache.h create mode 100644 linden/indra/newview/llfloaterdisplayname.cpp create mode 100644 linden/indra/newview/llfloaterdisplayname.h create mode 100644 linden/indra/newview/llviewerdisplayname.cpp create mode 100644 linden/indra/newview/llviewerdisplayname.h create mode 100644 linden/indra/newview/skins/default/xui/en-us/floater_display_name.xml (limited to 'linden') diff --git a/linden/etc/message.xml b/linden/etc/message.xml index 0fdf364..8d34dd8 100644 --- a/linden/etc/message.xml +++ b/linden/etc/message.xml @@ -386,7 +386,17 @@ true - ParcelVoiceInfo + + DisplayNameUpdate + + flavor + llsd + trusted-sender + true + + + + ParcelVoiceInfo flavor llsd @@ -442,6 +452,14 @@ true + SetDisplayNameReply + + flavor + llsd + trusted-sender + true + + DirLandReply flavor diff --git a/linden/indra/llcommon/CMakeLists.txt b/linden/indra/llcommon/CMakeLists.txt index 5d590a9..af9b247 100644 --- a/linden/indra/llcommon/CMakeLists.txt +++ b/linden/indra/llcommon/CMakeLists.txt @@ -18,6 +18,7 @@ set(llcommon_SOURCE_FILES llapp.cpp llapr.cpp llassettype.cpp + llavatarname.cpp llbase32.cpp llbase64.cpp llcommon.cpp @@ -87,6 +88,7 @@ set(llcommon_HEADER_FILES llassettype.h llassoclist.h llavatarconstants.h + llavatarname.h llbase32.h llbase64.h llboost.h diff --git a/linden/indra/llcommon/llavatarname.cpp b/linden/indra/llcommon/llavatarname.cpp new file mode 100644 index 0000000..ebe8c88 --- /dev/null +++ b/linden/indra/llcommon/llavatarname.cpp @@ -0,0 +1,150 @@ +/** + * @file llavatarname.cpp + * @brief Represents name-related data for an avatar, such as the + * username/SLID ("bobsmith123" or "james.linden") and the display + * name ("James Cook") + * + * $LicenseInfo:firstyear=2010&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 "linden_common.h" + +#include "llavatarname.h" + +#include "lldate.h" +#include "llsd.h" + +bool LLAvatarName::sOmitResidentAsLastName = false; + +// Store these in pre-built std::strings to avoid memory allocations in +// LLSD map lookups +static const std::string USERNAME("username"); +static const std::string DISPLAY_NAME("display_name"); +static const std::string LEGACY_FIRST_NAME("legacy_first_name"); +static const std::string LEGACY_LAST_NAME("legacy_last_name"); +static const std::string IS_DISPLAY_NAME_DEFAULT("is_display_name_default"); +static const std::string DISPLAY_NAME_EXPIRES("display_name_expires"); +static const std::string DISPLAY_NAME_NEXT_UPDATE("display_name_next_update"); + +LLAvatarName::LLAvatarName() +: mUsername(), + mDisplayName(), + mLegacyFirstName(), + mLegacyLastName(), + mIsDisplayNameDefault(false), + mIsDummy(false), + mExpires(F64_MAX), + mNextUpdate(0.0) +{ } + +bool LLAvatarName::operator<(const LLAvatarName& rhs) const +{ + if (mUsername == rhs.mUsername) + return mDisplayName < rhs.mDisplayName; + else + return mUsername < rhs.mUsername; +} + +LLSD LLAvatarName::asLLSD() const +{ + LLSD sd; + sd[USERNAME] = mUsername; + sd[DISPLAY_NAME] = mDisplayName; + sd[LEGACY_FIRST_NAME] = mLegacyFirstName; + sd[LEGACY_LAST_NAME] = mLegacyLastName; + sd[IS_DISPLAY_NAME_DEFAULT] = mIsDisplayNameDefault; + sd[DISPLAY_NAME_EXPIRES] = LLDate(mExpires); + sd[DISPLAY_NAME_NEXT_UPDATE] = LLDate(mNextUpdate); + return sd; +} + +void LLAvatarName::fromLLSD(const LLSD& sd) +{ + mUsername = sd[USERNAME].asString(); + mDisplayName = sd[DISPLAY_NAME].asString(); + mLegacyFirstName = sd[LEGACY_FIRST_NAME].asString(); + mLegacyLastName = sd[LEGACY_LAST_NAME].asString(); + mIsDisplayNameDefault = sd[IS_DISPLAY_NAME_DEFAULT].asBoolean(); + LLDate expires = sd[DISPLAY_NAME_EXPIRES]; + mExpires = expires.secondsSinceEpoch(); + LLDate next_update = sd[DISPLAY_NAME_NEXT_UPDATE]; + mNextUpdate = next_update.secondsSinceEpoch(); +} + +std::string LLAvatarName::getCompleteName() const +{ + std::string name; + if (!mUsername.empty()) + { + name = mDisplayName + " (" + mUsername + ")"; + } + else + { + // ...display names are off, legacy name is in mDisplayName + name = mDisplayName; + } + return name; +} + +std::string LLAvatarName::getLegacyName() const +{ + std::string name; + name.reserve(mLegacyFirstName.size() + 1 + mLegacyLastName.size()); + name = mLegacyFirstName; + if (!sOmitResidentAsLastName || mLegacyLastName != "Resident") + { + name += " "; + name += mLegacyLastName; + } + return name; +} + +std::string LLAvatarName::getNames(bool linefeed) const +{ + std::string name; + + if (mUsername.empty()) + { + // ...display names are off, legacy name is in mDisplayName + name = mDisplayName; + if (sOmitResidentAsLastName) + { + LLStringUtil::replaceString(name, " Resident", ""); + } + } + else + { + name = getLegacyName(); + if (name != mDisplayName) + { + if (linefeed) + { + name = mDisplayName + "\n[" + name + "]"; + } + else + { + name = mDisplayName + " [" + name + "]"; + } + } + } + + return name; +} diff --git a/linden/indra/llcommon/llavatarname.h b/linden/indra/llcommon/llavatarname.h new file mode 100644 index 0000000..3b6c6ea --- /dev/null +++ b/linden/indra/llcommon/llavatarname.h @@ -0,0 +1,105 @@ +/** + * @file llavatarname.h + * @brief Represents name-related data for an avatar, such as the + * username/SLID ("bobsmith123" or "james.linden") and the display + * name ("James Cook") + * + * $LicenseInfo:firstyear=2010&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$ + */ +#ifndef LLAVATARNAME_H +#define LLAVATARNAME_H + +#include + +class LLSD; + +class LL_COMMON_API LLAvatarName +{ +public: + LLAvatarName(); + + bool operator<(const LLAvatarName& rhs) const; + + LLSD asLLSD() const; + + void fromLLSD(const LLSD& sd); + + // For normal names, returns "James Linden (james.linden)" + // When display names are disabled returns just "James Linden" + std::string getCompleteName() const; + + // For normal names, returns "Whatever Display Name (John Doe)" when + // display name and legacy name are different, or just "John Doe" + // when they are equal or when display names are disabled. + // When linefeed == true, the space between the display name and + // the opening parenthesis for the legacy name is replaced with a + // line feed. + std::string getNames(bool linefeed = false) const; + + // Returns "James Linden" or "bobsmith123 Resident" for backwards + // compatibility with systems like voice and muting + std::string getLegacyName() const; + + // "bobsmith123" or "james.linden", US-ASCII only + std::string mUsername; + + // "Jose' Sanchez" or "James Linden", UTF-8 encoded Unicode + // Contains data whether or not user has explicitly set + // a display name; may duplicate their username. + std::string mDisplayName; + + // For "James Linden", "James" + // For "bobsmith123", "bobsmith123" + // Used to communicate with legacy systems like voice and muting which + // rely on old-style names. + std::string mLegacyFirstName; + + // For "James Linden", "Linden" + // For "bobsmith123", "Resident" + // see above for rationale + std::string mLegacyLastName; + + // If true, both display name and SLID were generated from + // a legacy first and last name, like "James Linden (james.linden)" + bool mIsDisplayNameDefault; + + // Under error conditions, we may insert "dummy" records with + // names equal to legacy name into caches as placeholders. + // These can be shown in UI, but are not serialized. + bool mIsDummy; + + // Names can change, so need to keep track of when name was + // last checked. + // Unix time-from-epoch seconds for efficiency + F64 mExpires; + + // You can only change your name every N hours, so record + // when the next update is allowed + // Unix time-from-epoch seconds + F64 mNextUpdate; + + // true to prevent the displaying of "Resident" as a last name + // in legacy names + static bool sOmitResidentAsLastName; +}; + +#endif diff --git a/linden/indra/llmessage/CMakeLists.txt b/linden/indra/llmessage/CMakeLists.txt index a5e4249..9965191 100644 --- a/linden/indra/llmessage/CMakeLists.txt +++ b/linden/indra/llmessage/CMakeLists.txt @@ -22,6 +22,7 @@ include_directories( set(llmessage_SOURCE_FILES llares.cpp llassetstorage.cpp + llavatarnamecache.cpp llblowfishcipher.cpp llbuffer.cpp llbufferstream.cpp @@ -105,6 +106,7 @@ set(llmessage_HEADER_FILES llares.h llassetstorage.h + llavatarnamecache.h llblowfishcipher.h llbuffer.h llbufferstream.h diff --git a/linden/indra/llmessage/llavatarnamecache.cpp b/linden/indra/llmessage/llavatarnamecache.cpp new file mode 100644 index 0000000..45048e1 --- /dev/null +++ b/linden/indra/llmessage/llavatarnamecache.cpp @@ -0,0 +1,859 @@ +/** + * @file llavatarnamecache.cpp + * @brief Provides lookup of avatar SLIDs ("bobsmith123") and display names + * ("James Cook") from avatar UUIDs. + * + * $LicenseInfo:firstyear=2010&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 "linden_common.h" + +#include "llavatarnamecache.h" + +#include "llcachename.h" // we wrap this system +#include "llframetimer.h" +#include "llhttpclient.h" +#include "llsd.h" +#include "llsdserialize.h" + +#include + +#include +#include + +namespace LLAvatarNameCache +{ + use_display_name_signal_t mUseDisplayNamesSignal; + + // Manual override for display names: 0 = legacy names, + // 1 = display name and legacy name, 2 = display name (legacy if absent) + U32 sUseDisplayNames = 0; + + // Cache starts in a paused state until we can determine if the + // current region supports display names. + bool sRunning = false; + + // Base lookup URL for name service. + // On simulator, loaded from indra.xml + // On viewer, usually a simulator capability (at People API team's request) + // Includes the trailing slash, like "http://pdp60.lindenlab.com:8000/agents/" + std::string sNameLookupURL; + + // accumulated agent IDs for next query against service + typedef std::set ask_queue_t; + ask_queue_t sAskQueue; + + // agent IDs that have been requested, but with no reply + // maps agent ID to frame time request was made + typedef std::map pending_queue_t; + pending_queue_t sPendingQueue; + + // Callbacks to fire when we received a name. + // May have multiple callbacks for a single ID, which are + // represented as multiple slots bound to the signal. + // Avoid copying signals via pointers. + typedef std::map signal_map_t; + signal_map_t sSignalMap; + + // names we know about + typedef std::map cache_t; + cache_t sCache; + + // Send bulk lookup requests a few times a second at most + // only need per-frame timing resolution + LLFrameTimer sRequestTimer; + + // Periodically clean out expired entries from the cache + //LLFrameTimer sEraseExpiredTimer; + + //----------------------------------------------------------------------- + // Internal methods + //----------------------------------------------------------------------- + + // Handle name response off network. + // Optionally skip adding to cache, used when this is a fallback to the + // legacy name system. + void processName(const LLUUID& agent_id, + const LLAvatarName& av_name, + bool add_to_cache); + + void requestNamesViaCapability(); + + // Legacy name system callback + void legacyNameCallback(const LLUUID& agent_id, + const std::string& first, const std::string& last, + BOOL is_group, void* data); + + void requestNamesViaLegacy(); + + // Fill in an LLAvatarName with the legacy name data + void buildLegacyName(const std::string& full_name, + LLAvatarName* av_name); + + // Do a single callback to a given slot + void fireSignal(const LLUUID& agent_id, + const callback_slot_t& slot, + const LLAvatarName& av_name); + + // Is a request in-flight over the network? + bool isRequestPending(const LLUUID& agent_id); + + // Erase expired names from cache + void eraseExpired(); + + bool expirationFromCacheControl(LLSD headers, F64 *expires); +} + +/* Sample response: + + + + agents + + + display_name_next_update + 2010-04-16T21:34:02+00:00Z + display_name_expires + 2010-04-16T21:32:26.142178+00:00Z + display_name + MickBot390 LLQABot + sl_id + mickbot390.llqabot + id + 0012809d-7d2d-4c24-9609-af1230a37715 + is_display_name_default + false + + + display_name_next_update + 2010-04-16T21:34:02+00:00Z + display_name_expires + 2010-04-16T21:32:26.142178+00:00Z + display_name + Bjork Gudmundsdottir + sl_id + sardonyx.linden + id + 3941037e-78ab-45f0-b421-bd6e77c1804d + is_display_name_default + true + + + + +*/ + +class LLAvatarNameResponder : public LLHTTPClient::Responder +{ +private: + // need to store agent ids that are part of this request in case of + // an error, so we can flag them as unavailable + std::vector mAgentIDs; + + // Need the headers to look up Expires: and Retry-After: + LLSD mHeaders; + +public: + LLAvatarNameResponder(const std::vector& agent_ids) + : mAgentIDs(agent_ids), + mHeaders() + { } + + /*virtual*/ void completedHeader(U32 status, const std::string& reason, + const LLSD& headers) + { + mHeaders = headers; + } + + /*virtual*/ void result(const LLSD& content) + { + // Pull expiration out of headers if available + F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(mHeaders); + + LLSD agents = content["agents"]; + LLSD::array_const_iterator it; + for (it = agents.beginArray(); it != agents.endArray(); ++it) + { + const LLSD& row = *it; + LLUUID agent_id = row["id"].asUUID(); + + LLAvatarName av_name; + av_name.fromLLSD(row); + + // Use expiration time from header + av_name.mExpires = expires; + + // Some avatars don't have explicit display names set + if (av_name.mDisplayName.empty()) + { + av_name.mDisplayName = av_name.mUsername; + } + + // cache it and fire signals + LLAvatarNameCache::processName(agent_id, av_name, true); + } + + // Same logic as error response case + LLSD unresolved_agents = content["bad_ids"]; + if (unresolved_agents.size() > 0) + { + std::string first, last; + LLAvatarName av_name; + av_name.mIsDisplayNameDefault = false; + av_name.mIsDummy = true; + av_name.mExpires = expires; + + for (it = unresolved_agents.beginArray(); it != unresolved_agents.endArray(); ++it) + { + const LLUUID& agent_id = *it; + gCacheName->getName(agent_id, first, last); + av_name.mLegacyFirstName = first; + av_name.mLegacyLastName = last; + av_name.mDisplayName = first; + if (last == "Resident") + { + av_name.mDisplayName = first; + av_name.mUsername = first; + } + else + { + av_name.mDisplayName = first + " " + last; + av_name.mUsername = first + "." + last; + } + LLStringUtil::toLower(av_name.mUsername); + // cache it and fire signals + LLAvatarNameCache::processName(agent_id, av_name, true); + } + } + } + + /*virtual*/ void error(U32 status, const std::string& reason) + { + // We're going to construct a dummy record and cache it for a while, + // either briefly for a 503 Service Unavailable, or longer for other + // errors. + F64 retry_timestamp = errorRetryTimestamp(status); + + std::string first, last; + LLAvatarName av_name; + av_name.mIsDisplayNameDefault = false; + av_name.mIsDummy = true; + av_name.mExpires = retry_timestamp; + + // Add dummy records for all agent IDs in this request + std::vector::const_iterator it; + for (it = mAgentIDs.begin(); it != mAgentIDs.end(); ++it) + { + const LLUUID& agent_id = *it; + gCacheName->getName(agent_id, first, last); + av_name.mLegacyFirstName = first; + av_name.mLegacyLastName = last; + av_name.mDisplayName = first; + if (last == "Resident") + { + av_name.mDisplayName = first; + av_name.mUsername = first; + } + else + { + av_name.mDisplayName = first + " " + last; + av_name.mUsername = first + "." + last; + } + LLStringUtil::toLower(av_name.mUsername); + // cache it and fire signals + LLAvatarNameCache::processName(agent_id, av_name, true); + } + } + + // Return time to retry a request that generated an error, based on + // error type and headers. Return value is seconds-since-epoch. + F64 errorRetryTimestamp(S32 status) + { + F64 now = LLFrameTimer::getTotalSeconds(); + + // Retry-After takes priority + LLSD retry_after = mHeaders["retry-after"]; + if (retry_after.isDefined()) + { + // We only support the delta-seconds type + S32 delta_seconds = retry_after.asInteger(); + if (delta_seconds > 0) + { + // ...valid delta-seconds + return now + F64(delta_seconds); + } + } + + // If no Retry-After, look for Cache-Control max-age + F64 expires = 0.0; + if (LLAvatarNameCache::expirationFromCacheControl(mHeaders, &expires)) + { + return expires; + } + + // No information in header, make a guess + if (status == 503) + { + // ...service unavailable, retry soon + const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min + return now + SERVICE_UNAVAILABLE_DELAY; + } + else + { + // ...other unexpected error + const F64 DEFAULT_DELAY = 3600.0; // 1 hour + return now + DEFAULT_DELAY; + } + } +}; + +void LLAvatarNameCache::processName(const LLUUID& agent_id, + const LLAvatarName& av_name, + bool add_to_cache) +{ + if (add_to_cache) + { + sCache[agent_id] = av_name; + } + + sPendingQueue.erase(agent_id); + + // signal everyone waiting on this name + signal_map_t::iterator sig_it = sSignalMap.find(agent_id); + if (sig_it != sSignalMap.end()) + { + callback_signal_t* signal = sig_it->second; + (*signal)(agent_id, av_name); + + sSignalMap.erase(agent_id); + + delete signal; + signal = NULL; + } +} + +void LLAvatarNameCache::requestNamesViaCapability() +{ + F64 now = LLFrameTimer::getTotalSeconds(); + + // URL format is like: + // http://pdp60.lindenlab.com:8000/agents/?ids=3941037e-78ab-45f0-b421-bd6e77c1804d&ids=0012809d-7d2d-4c24-9609-af1230a37715&ids=0019aaba-24af-4f0a-aa72-6457953cf7f0 + // + // Apache can handle URLs of 4096 chars, but let's be conservative + const U32 NAME_URL_MAX = 4096; + const U32 NAME_URL_SEND_THRESHOLD = 3000; + std::string url; + url.reserve(NAME_URL_MAX); + + std::vector agent_ids; + agent_ids.reserve(128); + + ask_queue_t::const_iterator it = sAskQueue.begin(); + for ( ; it != sAskQueue.end(); ++it) + { + const LLUUID& agent_id = *it; + + if (url.empty()) + { + // ...starting new request + url += sNameLookupURL; + url += "?ids="; + } + else + { + // ...continuing existing request + url += "&ids="; + } + url += agent_id.asString(); + agent_ids.push_back(agent_id); + + // mark request as pending + sPendingQueue[agent_id] = now; + + if (url.size() > NAME_URL_SEND_THRESHOLD) + { + //llinfos << "requestNames " << url << llendl; + LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids)); + url.clear(); + agent_ids.clear(); + } + } + + if (!url.empty()) + { + //llinfos << "requestNames " << url << llendl; + LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids)); + url.clear(); + agent_ids.clear(); + } + + // We've moved all asks to the pending request queue + sAskQueue.clear(); +} + +void LLAvatarNameCache::legacyNameCallback(const LLUUID& agent_id, + const std::string& first, const std::string& last, + BOOL is_group, void* data) +{ + std::string full_name = first + " " + last; + // Construct a dummy record for this name. By convention, SLID is blank + // Never expires, but not written to disk, so lasts until end of session. + LLAvatarName av_name; + buildLegacyName(full_name, &av_name); + + // Don't add to cache, the data already exists in the legacy name system + // cache and we don't want or need duplicate storage, because keeping the + // two copies in sync is complex. + processName(agent_id, av_name, false); +} + +void LLAvatarNameCache::requestNamesViaLegacy() +{ + F64 now = LLFrameTimer::getTotalSeconds(); + std::string full_name; + ask_queue_t::const_iterator it = sAskQueue.begin(); + for (; it != sAskQueue.end(); ++it) + { + const LLUUID& agent_id = *it; + + // Mark as pending first, just in case the callback is immediately + // invoked below. This should never happen in practice. + sPendingQueue[agent_id] = now; + + gCacheName->get(agent_id, false, legacyNameCallback); + } + + // We've either answered immediately or moved all asks to the + // pending queue + sAskQueue.clear(); +} + +void LLAvatarNameCache::initClass(bool running) +{ + sRunning = running; +} + +void LLAvatarNameCache::cleanupClass() +{ +} + +void LLAvatarNameCache::importFile(std::istream& istr) +{ + LLSD data; + S32 parse_count = LLSDSerialize::fromXMLDocument(data, istr); + if (parse_count < 1) return; + + // by convention LLSD storage is a map + // we only store one entry in the map + LLSD agents = data["agents"]; + + LLUUID agent_id; + LLAvatarName av_name; + LLSD::map_const_iterator it = agents.beginMap(); + for ( ; it != agents.endMap(); ++it) + { + agent_id.set(it->first); + av_name.fromLLSD( it->second ); + sCache[agent_id] = av_name; + } + // entries may have expired since we last ran the viewer, just + // clean them out now + eraseExpired(); + llinfos << "loaded " << sCache.size() << llendl; +} + +void LLAvatarNameCache::exportFile(std::ostream& ostr) +{ + LLSD agents; + cache_t::const_iterator it = sCache.begin(); + for ( ; it != sCache.end(); ++it) + { + const LLUUID& agent_id = it->first; + const LLAvatarName& av_name = it->second; + if (!av_name.mIsDummy) + { + // key must be a string + agents[agent_id.asString()] = av_name.asLLSD(); + } + } + LLSD data; + data["agents"] = agents; + LLSDSerialize::toPrettyXML(data, ostr); +} + +void LLAvatarNameCache::setNameLookupURL(const std::string& name_lookup_url) +{ + sNameLookupURL = name_lookup_url; +} + +bool LLAvatarNameCache::hasNameLookupURL() +{ + return !sNameLookupURL.empty(); +} + +void LLAvatarNameCache::idle() +{ + // By convention, start running at first idle() call + sRunning = true; + + // *TODO: Possibly re-enabled this based on People API load measurements + // 100 ms is the threshold for "user speed" operations, so we can + // stall for about that long to batch up requests. + //const F32 SECS_BETWEEN_REQUESTS = 0.1f; + //if (!sRequestTimer.checkExpirationAndReset(SECS_BETWEEN_REQUESTS)) + //{ + // return; + //} + + // Must be large relative to above + + // No longer deleting expired entries, just re-requesting in the get + // this way first synchronous get call on an expired entry won't return + // legacy name. LF + + //const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds + //if (sEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT)) + //{ + // eraseExpired(); + //} + + if (sAskQueue.empty()) + { + return; + } + + if (useDisplayNames()) + { + requestNamesViaCapability(); + } + else + { + // ...fall back to legacy name cache system + requestNamesViaLegacy(); + } +} + +bool LLAvatarNameCache::isRequestPending(const LLUUID& agent_id) +{ + const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0; + F64 now = LLFrameTimer::getTotalSeconds(); + F64 expire_time = now - PENDING_TIMEOUT_SECS; + + pending_queue_t::const_iterator it = sPendingQueue.find(agent_id); + if (it != sPendingQueue.end()) + { + bool request_expired = (it->second < expire_time); + return !request_expired; + } + return false; +} + +void LLAvatarNameCache::eraseExpired() +{ + F64 now = LLFrameTimer::getTotalSeconds(); + cache_t::iterator it = sCache.begin(); + while (it != sCache.end()) + { + cache_t::iterator cur = it; + ++it; + const LLAvatarName& av_name = cur->second; + if (av_name.mExpires < now) + { + sCache.erase(cur); + } + } +} + +void LLAvatarNameCache::buildLegacyName(const std::string& full_name, + LLAvatarName* av_name) +{ + llassert(av_name); + av_name->mUsername = ""; + av_name->mDisplayName = full_name; + av_name->mIsDisplayNameDefault = true; + av_name->mIsDummy = true; + av_name->mExpires = F64_MAX; +} + +// fills in av_name if it has it in the cache, even if expired (can check expiry time) +// returns bool specifying if av_name was filled, false otherwise +bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name) +{ + if (sRunning) + { + // ...only do immediate lookups when cache is running + if (useDisplayNames()) + { + // ...use display names cache + std::map::iterator it = sCache.find(agent_id); + if (it != sCache.end()) + { + *av_name = it->second; + + // re-request name if entry is expired + if (av_name->mExpires < LLFrameTimer::getTotalSeconds()) + { + if (!isRequestPending(agent_id)) + { + sAskQueue.insert(agent_id); + } + } + + return true; + } + } + else + { + // ...use legacy names cache + std::string full_name; + if (gCacheName->getFullName(agent_id, full_name)) + { + buildLegacyName(full_name, av_name); + return true; + } + } + } + + if (!isRequestPending(agent_id)) + { + sAskQueue.insert(agent_id); + } + + return false; +} + +void LLAvatarNameCache::fireSignal(const LLUUID& agent_id, + const callback_slot_t& slot, + const LLAvatarName& av_name) +{ + callback_signal_t signal; + signal.connect(slot); + signal(agent_id, av_name); +} + +void LLAvatarNameCache::get(const LLUUID& agent_id, callback_slot_t slot) +{ + if (sRunning) + { + // ...only do immediate lookups when cache is running + if (useDisplayNames()) + { + // ...use new cache + std::map::iterator it = sCache.find(agent_id); + if (it != sCache.end()) + { + const LLAvatarName& av_name = it->second; + + if (av_name.mExpires > LLFrameTimer::getTotalSeconds()) + { + // ...name already exists in cache, fire callback now + fireSignal(agent_id, slot, av_name); + + return; + } + } + } + else + { + // ...use old name system + std::string full_name; + if (gCacheName->getFullName(agent_id, full_name)) + { + LLAvatarName av_name; + buildLegacyName(full_name, &av_name); + fireSignal(agent_id, slot, av_name); + return; + } + } + } + + // schedule a request + if (!isRequestPending(agent_id)) + { + sAskQueue.insert(agent_id); + } + + // always store additional callback, even if request is pending + signal_map_t::iterator sig_it = sSignalMap.find(agent_id); + if (sig_it == sSignalMap.end()) + { + // ...new callback for this id + callback_signal_t* signal = new callback_signal_t(); + signal->connect(slot); + sSignalMap[agent_id] = signal; + } + else + { + // ...existing callback, bind additional slot + callback_signal_t* signal = sig_it->second; + signal->connect(slot); + } +} + + +void LLAvatarNameCache::setUseDisplayNames(U32 use) +{ + if (use != sUseDisplayNames) + { + if (use > 2) + { + sUseDisplayNames = 1; + } + else + { + sUseDisplayNames = use; + } + // flush our cache + sCache.clear(); + + mUseDisplayNamesSignal(); + } +} + +U32 LLAvatarNameCache::useDisplayNames() +{ + // Must be both manually set on and able to look up names. + if (sNameLookupURL.empty()) + { + return 0; + } + else + { + return sUseDisplayNames; + } +} + +void LLAvatarNameCache::erase(const LLUUID& agent_id) +{ + sCache.erase(agent_id); +} + +void LLAvatarNameCache::fetch(const LLUUID& agent_id) +{ + // re-request, even if request is already pending + sAskQueue.insert(agent_id); +} + +void LLAvatarNameCache::insert(const LLUUID& agent_id, const LLAvatarName& av_name) +{ + // *TODO: update timestamp if zero? + sCache[agent_id] = av_name; +} + +F64 LLAvatarNameCache::nameExpirationFromHeaders(LLSD headers) +{ + F64 expires = 0.0; + if (expirationFromCacheControl(headers, &expires)) + { + return expires; + } + else + { + // With no expiration info, default to an hour + const F64 DEFAULT_EXPIRES = 60.0 * 60.0; + F64 now = LLFrameTimer::getTotalSeconds(); + return now + DEFAULT_EXPIRES; + } +} + +bool LLAvatarNameCache::expirationFromCacheControl(LLSD headers, F64 *expires) +{ + // Allow the header to override the default + LLSD cache_control_header = headers["cache-control"]; + if (cache_control_header.isDefined()) + { + S32 max_age = 0; + std::string cache_control = cache_control_header.asString(); + if (max_age_from_cache_control(cache_control, &max_age)) + { + F64 now = LLFrameTimer::getTotalSeconds(); + *expires = now + (F64)max_age; + return true; + } + } + return false; +} + + +void LLAvatarNameCache::addUseDisplayNamesCallback(const use_display_name_signal_t::slot_type& cb) +{ + mUseDisplayNamesSignal.connect(cb); +} + + +static const std::string MAX_AGE("max-age"); +static const boost::char_separator EQUALS_SEPARATOR("="); +static const boost::char_separator COMMA_SEPARATOR(","); + +bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age) +{ + // Split the string on "," to get a list of directives + typedef boost::tokenizer > tokenizer; + tokenizer directives(cache_control, COMMA_SEPARATOR); + + tokenizer::iterator token_it = directives.begin(); + for ( ; token_it != directives.end(); ++token_it) + { + // Tokens may have leading or trailing whitespace + std::string token = *token_it; + LLStringUtil::trim(token); + + if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0) + { + // ...this token starts with max-age, so let's chop it up by "=" + tokenizer subtokens(token, EQUALS_SEPARATOR); + tokenizer::iterator subtoken_it = subtokens.begin(); + + // Must have a token + if (subtoken_it == subtokens.end()) return false; + std::string subtoken = *subtoken_it; + + // Must exactly equal "max-age" + LLStringUtil::trim(subtoken); + if (subtoken != MAX_AGE) return false; + + // Must have another token + ++subtoken_it; + if (subtoken_it == subtokens.end()) return false; + subtoken = *subtoken_it; + + // Must be a valid integer + // *NOTE: atoi() returns 0 for invalid values, so we have to + // check the string first. + // *TODO: Do servers ever send "0000" for zero? We don't handle it + LLStringUtil::trim(subtoken); + if (subtoken == "0") + { + *max_age = 0; + return true; + } + S32 val = atoi( subtoken.c_str() ); + if (val > 0 && val < S32_MAX) + { + *max_age = val; + return true; + } + return false; + } + } + return false; +} + diff --git a/linden/indra/llmessage/llavatarnamecache.h b/linden/indra/llmessage/llavatarnamecache.h new file mode 100644 index 0000000..a2519cd --- /dev/null +++ b/linden/indra/llmessage/llavatarnamecache.h @@ -0,0 +1,105 @@ +/** + * @file llavatarnamecache.h + * @brief Provides lookup of avatar SLIDs ("bobsmith123") and display names + * ("James Cook") from avatar UUIDs. + * + * $LicenseInfo:firstyear=2010&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$ + */ + +#ifndef LLAVATARNAMECACHE_H +#define LLAVATARNAMECACHE_H + +#include "llavatarname.h" // for convenience + +#include + +// We have display names support (this is for use by patches) +#define LL_DISPLAY_NAMES + +class LLSD; +class LLUUID; + +namespace LLAvatarNameCache +{ + + typedef boost::signals2::signal use_display_name_signal_t; + + // Until the cache is set running, immediate lookups will fail and + // async lookups will be queued. This allows us to block requests + // until we know if the first region supports display names. + void initClass(bool running); + void cleanupClass(); + + void importFile(std::istream& istr); + void exportFile(std::ostream& ostr); + + // On the viewer, usually a simulator capabilitity + // If empty, name cache will fall back to using legacy name + // lookup system + void setNameLookupURL(const std::string& name_lookup_url); + + // Do we have a valid lookup URL, hence are we trying to use the + // new display name lookup system? + bool hasNameLookupURL(); + + // Periodically makes a batch request for display names not already in + // cache. Call once per frame. + void idle(); + + // If name is in cache, returns true and fills in provided LLAvatarName + // otherwise returns false + bool get(const LLUUID& agent_id, LLAvatarName *av_name); + + // Callback types for get() below + typedef boost::signals2::signal< + void (const LLUUID& agent_id, const LLAvatarName& av_name)> + callback_signal_t; + typedef callback_signal_t::slot_type callback_slot_t; + + // Fetches name information and calls callback. + // If name information is in cache, callback will be called immediately. + void get(const LLUUID& agent_id, callback_slot_t slot); + + void setUseDisplayNames(U32 use); + U32 useDisplayNames(); + + void erase(const LLUUID& agent_id); + + // Force a re-fetch of the most recent data, but keep the current + // data in cache + void fetch(const LLUUID& agent_id); + + void insert(const LLUUID& agent_id, const LLAvatarName& av_name); + + // Compute name expiration time from HTTP Cache-Control header, + // or return default value, in seconds from epoch. + F64 nameExpirationFromHeaders(LLSD headers); + + void addUseDisplayNamesCallback(const use_display_name_signal_t::slot_type& cb); +} + +// Parse a cache-control header to get the max-age delta-seconds. +// Returns true if header has max-age param and it parses correctly. +// Exported here to ease unit testing. +bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age); + +#endif diff --git a/linden/indra/llmessage/llcachename.h b/linden/indra/llmessage/llcachename.h index 2757b86..cb3cc1f 100644 --- a/linden/indra/llmessage/llcachename.h +++ b/linden/indra/llmessage/llcachename.h @@ -33,6 +33,8 @@ #ifndef LL_LLCACHENAME_H #define LL_LLCACHENAME_H +#include "llavatarnamecache.h" // for convenience + class LLMessageSystem; class LLHost; class LLUUID; diff --git a/linden/indra/newview/CMakeLists.txt b/linden/indra/newview/CMakeLists.txt index 4a30be3..0eb5b41 100644 --- a/linden/indra/newview/CMakeLists.txt +++ b/linden/indra/newview/CMakeLists.txt @@ -173,6 +173,7 @@ set(viewer_SOURCE_FILES llfloatercustomize.cpp llfloaterdaycycle.cpp llfloaterdirectory.cpp + llfloaterdisplayname.cpp llfloatereditui.cpp llfloaterenvsettings.cpp llfloaterevent.cpp @@ -410,6 +411,7 @@ set(viewer_SOURCE_FILES llviewercamera.cpp llviewercontrol.cpp llviewerdisplay.cpp + llviewerdisplayname.cpp llviewergenericmessage.cpp llviewergesture.cpp llviewerimage.cpp @@ -632,6 +634,7 @@ set(viewer_HEADER_FILES llfloatercustomize.h llfloaterdaycycle.h llfloaterdirectory.h + llfloaterdisplayname.h llfloatereditui.h llfloaterenvsettings.h llfloaterevent.h @@ -873,6 +876,7 @@ set(viewer_HEADER_FILES llviewercamera.h llviewercontrol.h llviewerdisplay.h + llviewerdisplayname.h llviewergenericmessage.h llviewergesture.h llviewerimage.h diff --git a/linden/indra/newview/app_settings/settings.xml b/linden/indra/newview/app_settings/settings.xml index b7d6433..d0db713 100644 --- a/linden/indra/newview/app_settings/settings.xml +++ b/linden/indra/newview/app_settings/settings.xml @@ -419,6 +419,42 @@ Value 0 + + + DisplayNamesUsage + + Comment + Usage type for display names: 0 = Legacy name only, 1 = Display name with legacy name, 2 = display name only (legacy name when absent) + Persist + 1 + Type + U32 + Value + 1 + + OmitResidentAsLastName + + Comment + Do not display "Resident" as the last name for new residents in their legacy name + Persist + 1 + Type + Boolean + Value + 1 + + LegacyNamesForFriends + + Comment + When TRUE, forces the use of the legacy names for the friends list and online notifications + Persist + 1 + Type + Boolean + Value + 1 + + EmeraldTemporaryUpload Comment diff --git a/linden/indra/newview/llappviewer.cpp b/linden/indra/newview/llappviewer.cpp index d920e84..0c3c657 100644 --- a/linden/indra/newview/llappviewer.cpp +++ b/linden/indra/newview/llappviewer.cpp @@ -1259,6 +1259,7 @@ bool LLAppViewer::cleanup() LLPolyMesh::freeAllMeshes(); + LLAvatarNameCache::cleanupClass(); delete gCacheName; gCacheName = NULL; @@ -3300,6 +3301,14 @@ void LLAppViewer::saveFinalSnapshot() void LLAppViewer::loadNameCache() { + // display names cache + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml"); + llifstream name_cache_stream(filename); + if (name_cache_stream.is_open()) + { + LLAvatarNameCache::importFile(name_cache_stream); + } + if (!gCacheName) return; std::string name_cache; @@ -3322,6 +3331,14 @@ void LLAppViewer::loadNameCache() void LLAppViewer::saveNameCache() { + // display names cache + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml"); + llofstream name_cache_stream(filename); + if (name_cache_stream.is_open()) + { + LLAvatarNameCache::exportFile(name_cache_stream); + } + if (!gCacheName) return; std::string name_cache; @@ -3526,6 +3543,8 @@ void LLAppViewer::idle() // floating throughout the various object lists. // + idleNameCache(); + gFrameStats.start(LLFrameStats::IDLE_NETWORK); stop_glerror(); idleNetwork(); @@ -3907,6 +3926,60 @@ void LLAppViewer::sendLogoutRequest() } } +void LLAppViewer::idleNameCache() +{ + // Neither old nor new name cache can function before agent has a region + LLViewerRegion* region = gAgent.getRegion(); + if (!region) return; + + // deal with any queued name requests and replies. + gCacheName->processPending(); + + // Can't run the new cache until we have the list of capabilities + // for the agent region, and can therefore decide whether to use + // display names or fall back to the old name system. + if (!region->capabilitiesReceived()) return; + + // Agent may have moved to a different region, so need to update cap URL + // for name lookups. Can't do this in the cap grant code, as caps are + // granted to neighbor regions before the main agent gets there. Can't + // do it in the move-into-region code because cap not guaranteed to be + // granted yet, for example on teleport. + bool had_capability = LLAvatarNameCache::hasNameLookupURL(); + std::string name_lookup_url; + name_lookup_url.reserve(128); // avoid a memory allocation below + name_lookup_url = region->getCapability("GetDisplayNames"); + bool have_capability = !name_lookup_url.empty(); + if (have_capability) + { + // we have support for display names, use it + U32 url_size = name_lookup_url.size(); + // capabilities require URLs with slashes before query params: + // https://:/cap//?ids= + // but the caps are granted like: + // https://:/cap/ + if (url_size > 0 && name_lookup_url[url_size-1] != '/') + { + name_lookup_url += '/'; + } + LLAvatarNameCache::setNameLookupURL(name_lookup_url); + } + else + { + // Display names not available on this region + LLAvatarNameCache::setNameLookupURL( std::string() ); + } + + // Error recovery - did we change state? + if (had_capability != have_capability) + { + // name tags are persistant on screen, so make sure they refresh + LLVOAvatar::invalidateNameTags(); + } + + LLAvatarNameCache::idle(); +} + // // Handle messages, and all message related stuff // diff --git a/linden/indra/newview/llappviewer.h b/linden/indra/newview/llappviewer.h index 7b3230a..42c49de 100644 --- a/linden/indra/newview/llappviewer.h +++ b/linden/indra/newview/llappviewer.h @@ -192,6 +192,7 @@ private: void idle(); void idleShutdown(); + void idleNameCache(); void idleNetwork(); void sendLogoutRequest(); diff --git a/linden/indra/newview/llcallingcard.cpp b/linden/indra/newview/llcallingcard.cpp index 15be0eb..31d7bec 100644 --- a/linden/indra/newview/llcallingcard.cpp +++ b/linden/indra/newview/llcallingcard.cpp @@ -613,6 +613,25 @@ void LLAvatarTracker::processChange(LLMessageSystem* msg) LLSD args; if(gCacheName->getName(agent_id, first, last)) { + if (LLAvatarNameCache::useDisplayNames() && !gSavedSettings.getBOOL("LegacyNamesForFriends")) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(agent_id, &avatar_name)) + { + // Always show "Display Name [Legacy Name]" for security reasons + first = avatar_name.getNames(); + size_t i = first.find(" "); + if (i != std::string::npos) + { + last = first.substr(i + 1); + first = first.substr(0, i); + } + else + { + last = ""; + } + } + } args["FIRST_NAME"] = first; args["LAST_NAME"] = last; } @@ -669,6 +688,31 @@ void LLAvatarTracker::processNotify(LLMessageSystem* msg, bool online) std::string first, last; if(gCacheName->getName(agent_id, first, last)) { + if (LLAvatarNameCache::useDisplayNames() && !gSavedSettings.getBOOL("LegacyNamesForFriends")) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(agent_id, &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + first = avatar_name.mDisplayName; + } + else + { + first = avatar_name.getNames(); + } + size_t i = first.find(" "); + if (i != std::string::npos) + { + last = first.substr(i + 1); + first = first.substr(0, i); + } + else + { + last = ""; + } + } + } notify = TRUE; args["FIRST"] = first; args["LAST"] = last; @@ -745,6 +789,12 @@ void LLAvatarTracker::processTerminateFriendship(LLMessageSystem* msg, void**) } } +void LLAvatarTracker::dirtyBuddies() +{ + mModifyMask |= LLFriendObserver::REMOVE | LLFriendObserver::ADD; + notifyObservers(); +} + ///---------------------------------------------------------------------------- /// Tracking Data ///---------------------------------------------------------------------------- diff --git a/linden/indra/newview/llcallingcard.h b/linden/indra/newview/llcallingcard.h index d3f53c6..d5496ae 100644 --- a/linden/indra/newview/llcallingcard.h +++ b/linden/indra/newview/llcallingcard.h @@ -122,6 +122,9 @@ public: // deal with termination of friendhsip void terminateBuddy(const LLUUID& id); + // flag the buddy list dirty to force an update + void dirtyBuddies(); + // get full info const LLRelationship* getBuddyInfo(const LLUUID& id) const; diff --git a/linden/indra/newview/llfloateractivespeakers.cpp b/linden/indra/newview/llfloateractivespeakers.cpp index 59de717..75cf176 100644 --- a/linden/indra/newview/llfloateractivespeakers.cpp +++ b/linden/indra/newview/llfloateractivespeakers.cpp @@ -92,18 +92,19 @@ LLSpeaker::LLSpeaker(const LLUUID& id, const std::string& name, const ESpeakerTy void LLSpeaker::lookupName() { - gCacheName->getName(mID, onAvatarNameLookup, new LLHandle(getHandle())); + LLAvatarNameCache::get(mID, boost::bind(&LLSpeaker::onAvatarNameLookup, _1, _2, new LLHandle(getHandle()))); } //static -void LLSpeaker::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data) +void LLSpeaker::onAvatarNameLookup(const LLUUID& id, const LLAvatarName& avatar_name, void* user_data) { LLSpeaker* speaker_ptr = ((LLHandle*)user_data)->get(); delete (LLHandle*)user_data; if (speaker_ptr) { - speaker_ptr->mDisplayName = first + " " + last; + // Always show "Display Name [Legacy Name]" for security reasons + speaker_ptr->mDisplayName = avatar_name.getNames(); // [RLVa:KB] - Checked: 2009-07-10 (RLVa-1.0.0g) | Added: RLVa-1.0.0g // TODO-RLVa: this seems to get called per frame which is very likely an LL bug that will eventuall get fixed if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES)) diff --git a/linden/indra/newview/llfloateractivespeakers.h b/linden/indra/newview/llfloateractivespeakers.h index b2c44e2..dc3dd73 100644 --- a/linden/indra/newview/llfloateractivespeakers.h +++ b/linden/indra/newview/llfloateractivespeakers.h @@ -33,6 +33,7 @@ #ifndef LL_LLFLOATERACTIVESPEAKERS_H #define LL_LLFLOATERACTIVESPEAKERS_H +#include "llavatarnamecache.h" #include "llfloater.h" #include "llmemory.h" #include "llvoiceclient.h" @@ -73,7 +74,7 @@ public: ~LLSpeaker() {}; void lookupName(); - static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data); + static void onAvatarNameLookup(const LLUUID& id, const LLAvatarName& avatar_name, void* user_data); ESpeakerStatus mStatus; // current activity status in speech group F32 mLastSpokeTime; // timestamp when this speaker last spoke diff --git a/linden/indra/newview/llfloateravatarinfo.cpp b/linden/indra/newview/llfloateravatarinfo.cpp index 0618875..13805fb 100644 --- a/linden/indra/newview/llfloateravatarinfo.cpp +++ b/linden/indra/newview/llfloateravatarinfo.cpp @@ -122,7 +122,7 @@ LLFloaterAvatarInfo::LLFloaterAvatarInfo(const std::string& name, const LLRect & } gAvatarInfoInstances.addData(avatar_id, this); // must be done before callback below is called. - gCacheName->get(avatar_id, FALSE, callbackLoadAvatarName); + LLAvatarNameCache::get(avatar_id, boost::bind(&LLFloaterAvatarInfo::callbackLoadAvatarName, _1, _2)); } // virtual @@ -242,18 +242,16 @@ void LLFloaterAvatarInfo::showProfileCallback(S32 option, void *userdata) // static void LLFloaterAvatarInfo::callbackLoadAvatarName(const LLUUID& id, - const std::string& first, - const std::string& last, - BOOL is_group, - void* data) + const LLAvatarName& avatar_name) { LLFloaterAvatarInfo *floater = gAvatarInfoInstances.getIfThere(id); if (floater) { // Build a new title including the avatar name. + // Always show "Display Name [Legacy Name]" for security reasons std::ostringstream title; - title << first << " " << last << " - " << floater->getTitle(); + title << avatar_name.getNames() << " - " << floater->getTitle(); floater->setTitle(title.str()); } } diff --git a/linden/indra/newview/llfloateravatarinfo.h b/linden/indra/newview/llfloateravatarinfo.h index 1cc17d1..f65e1f4 100644 --- a/linden/indra/newview/llfloateravatarinfo.h +++ b/linden/indra/newview/llfloateravatarinfo.h @@ -39,6 +39,7 @@ #ifndef LL_LLFLOATERAVATARINFO_H #define LL_LLFLOATERAVATARINFO_H +#include "llavatarnamecache.h" #include "llfloater.h" #include "llpreview.h" #include "lluuid.h" @@ -91,8 +92,7 @@ public: static LLFloaterAvatarInfo* getInstance(const LLUUID &id); static void showProfileCallback(S32 option, void *userdata); - static void callbackLoadAvatarName(const LLUUID& id, - const std::string& first, const std::string& last, BOOL is_group, void* data); + static void callbackLoadAvatarName(const LLUUID& agent_id, const LLAvatarName& avatar_name); void resetGroupList(); private: diff --git a/linden/indra/newview/llfloaterchatterbox.cpp b/linden/indra/newview/llfloaterchatterbox.cpp index 1ef75d1..ba1c83c 100644 --- a/linden/indra/newview/llfloaterchatterbox.cpp +++ b/linden/indra/newview/llfloaterchatterbox.cpp @@ -43,6 +43,7 @@ #include "llviewercontrol.h" #include "llimview.h" #include "llimpanel.h" +#include "llcallingcard.h" // // LLFloaterMyFriends @@ -218,6 +219,8 @@ void LLFloaterChatterBox::draw() void LLFloaterChatterBox::onOpen() { gSavedSettings.setBOOL("ShowCommunicate", TRUE); + // Force a refresh to get latest display names in the new IM panel. + LLAvatarTracker::instance().dirtyBuddies(); } void LLFloaterChatterBox::onClose(bool app_quitting) diff --git a/linden/indra/newview/llfloaterdisplayname.cpp b/linden/indra/newview/llfloaterdisplayname.cpp new file mode 100644 index 0000000..31ceda9 --- /dev/null +++ b/linden/indra/newview/llfloaterdisplayname.cpp @@ -0,0 +1,224 @@ +/** + * @file llfloaterdisplayname.cpp + * @author Leyla Farazha + * @brief Implementation of the LLFloaterDisplayName class. + * + * $LicenseInfo:firstyear=2002&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 "llfloater.h" + +#include "llnotifications.h" +#include "llviewerdisplayname.h" + +#include "llnotify.h" +#include "llfloaterdisplayname.h" +#include "llavatarnamecache.h" +#include "lluictrlfactory.h" +#include "llviewercontrol.h" + +#include "llagent.h" + + +LLFloaterDisplayName* LLFloaterDisplayName::sInstance = NULL; + +LLFloaterDisplayName::LLFloaterDisplayName() : LLFloater(std::string("Display Name")) +{ + LLUICtrlFactory::getInstance()->buildFloater(this, "floater_display_name.xml"); + LLFloaterDisplayName::sInstance = this; +} + +LLFloaterDisplayName::~LLFloaterDisplayName() +{ + LLFloaterDisplayName::sInstance = NULL; +} + +void LLFloaterDisplayName::show() +{ + if (!LLFloaterDisplayName::sInstance) + { + LLFloaterDisplayName::sInstance = new LLFloaterDisplayName(); + } + LLFloaterDisplayName::sInstance->open(); +} + +void LLFloaterDisplayName::onOpen() +{ + getChild("display_name_editor")->clear(); + getChild("display_name_confirm")->clear(); + + LLAvatarName av_name; + LLAvatarNameCache::get(gAgent.getID(), &av_name); + + F64 now_secs = LLDate::now().secondsSinceEpoch(); + + if (now_secs < av_name.mNextUpdate) + { + // ...can't update until some time in the future + F64 next_update_local_secs = av_name.mNextUpdate; + std::string next_update_string; +#ifdef LOCALIZED_TIME + timeToFormattedString((time_t)next_update_local_secs, + gSavedSettings.getString("LongDateFormat"), + next_update_string); +#else + LLDate next_update_local(next_update_local_secs); + next_update_string = next_update_local.asString(); +#endif + getChild("lockout_text")->setTextArg("[TIME]", next_update_string); + getChild("lockout_text")->setVisible(true); + getChild("now_ok_text")->setVisible(false); + getChild("save_btn")->setEnabled(false); + getChild("display_name_editor")->setEnabled(false); + getChild("display_name_confirm")->setEnabled(false); + getChild("cancel_btn")->setFocus(TRUE); + + } + else + { + getChild("lockout_text")->setVisible(false); + getChild("now_ok_text")->setVisible(true); + getChild("save_btn")->setEnabled(true); + getChild("display_name_editor")->setEnabled(true); + getChild("display_name_confirm")->setEnabled(true); + + } +} + +BOOL LLFloaterDisplayName::postBuild() +{ + childSetAction("reset_btn", onReset, this); + childSetAction("cancel_btn", onCancel, this); + childSetAction("save_btn", onSave, this); + + center(); + + return TRUE; +} + +void LLFloaterDisplayName::onCacheSetName(bool success, + const std::string& reason, + const LLSD& content) +{ + if (success) + { + // Inform the user that the change took place, but will take a while + // to percolate. + LLSD args; + args["DISPLAY_NAME"] = content["display_name"]; + LLNotifications::instance().add("SetDisplayNameSuccess", args); + + // Re-fetch my name, as it may have been sanitized by the service + //LLAvatarNameCache::get(getAvatarId(), + // boost::bind(&LLPanelMyProfileEdit::onNameCache, this, _1, _2)); + return; + } + + // Request failed, notify the user + std::string error_tag = content["error_tag"].asString(); + llinfos << "set name failure error_tag " << error_tag << llendl; + + // We might have a localized string for this message + // error_args will usually be empty from the server. + if (!error_tag.empty() + && LLNotifications::getInstance()->templateExists(error_tag)) + { + LLNotifications::instance().add(error_tag); + return; + } + + // The server error might have a localized message for us + std::string lang_code = LLUI::getLanguage(); + LLSD error_desc = content["error_description"]; + if (error_desc.has( lang_code )) + { + LLSD args; + args["MESSAGE"] = error_desc[lang_code].asString(); + LLNotifications::instance().add("GenericAlert", args); + return; + } + + // No specific error, throw a generic one + LLNotifications::instance().add("SetDisplayNameFailedGeneric"); +} + +void LLFloaterDisplayName::onCancel(void* data) +{ + LLFloaterDisplayName* self = (LLFloaterDisplayName*)data; + self->setVisible(false); +} + +void LLFloaterDisplayName::onReset(void* data) +{ + LLFloaterDisplayName* self = (LLFloaterDisplayName*)data; + + if (LLAvatarNameCache::useDisplayNames()) + { + LLViewerDisplayName::set("", + boost::bind(&LLFloaterDisplayName::onCacheSetName, self, _1, _2, _3)); + } + else + { + LLNotifications::instance().add("SetDisplayNameFailedGeneric"); + } + + self->setVisible(false); +} + + +void LLFloaterDisplayName::onSave(void* data) +{ + LLFloaterDisplayName* self = (LLFloaterDisplayName*)data; + + std::string display_name_utf8 = self->getChild("display_name_editor")->getValue().asString(); + std::string display_name_confirm = self->getChild("display_name_confirm")->getValue().asString(); + + if (display_name_utf8.compare(display_name_confirm)) + { + LLNotifications::instance().add("SetDisplayNameMismatch"); + return; + } + + const U32 DISPLAY_NAME_MAX_LENGTH = 31; // characters, not bytes + LLWString display_name_wstr = utf8string_to_wstring(display_name_utf8); + if (display_name_wstr.size() > DISPLAY_NAME_MAX_LENGTH) + { + LLSD args; + args["LENGTH"] = llformat("%d", DISPLAY_NAME_MAX_LENGTH); + LLNotifications::instance().add("SetDisplayNameFailedLength", args); + return; + } + + if (LLAvatarNameCache::useDisplayNames()) + { + LLViewerDisplayName::set(display_name_utf8, + boost::bind(&LLFloaterDisplayName::onCacheSetName, self, _1, _2, _3)); + } + else + { + LLNotifications::instance().add("SetDisplayNameFailedGeneric"); + } + + self->setVisible(false); +} diff --git a/linden/indra/newview/llfloaterdisplayname.h b/linden/indra/newview/llfloaterdisplayname.h new file mode 100644 index 0000000..4b25670 --- /dev/null +++ b/linden/indra/newview/llfloaterdisplayname.h @@ -0,0 +1,48 @@ +/** + * @file llfloaterdisplayname.h + * + * $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$ + */ + +#ifndef LLFLOATERDISPLAYNAME_H +#define LLFLOATERDISPLAYNAME_H + + +class LLFloaterDisplayName : public LLFloater +{ +public: + LLFloaterDisplayName(); + virtual ~LLFloaterDisplayName(); + /* virtual */ BOOL postBuild(); + /* virtual */ void onOpen(); + static void show(); + static void onSave(void* data); + static void onReset(void* data); + static void onCancel(void* data); + +private: + static LLFloaterDisplayName* sInstance; + void onCacheSetName(bool success, const std::string& reason, const LLSD& content); +}; + + +#endif diff --git a/linden/indra/newview/llfloaterfriends.cpp b/linden/indra/newview/llfloaterfriends.cpp index c33deae..d3fd9b9 100644 --- a/linden/indra/newview/llfloaterfriends.cpp +++ b/linden/indra/newview/llfloaterfriends.cpp @@ -39,6 +39,7 @@ #include +#include "llavatarnamecache.h" #include "lldir.h" #include "llagent.h" @@ -254,6 +255,24 @@ BOOL LLPanelFriends::addFriend(const LLUUID& agent_id) std::string fullname; BOOL have_name = gCacheName->getFullName(agent_id, fullname); + if (have_name) + { + if (LLAvatarNameCache::useDisplayNames() && !gSavedSettings.getBOOL("LegacyNamesForFriends")) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(agent_id, &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + fullname = avatar_name.mDisplayName; + } + else + { + fullname = avatar_name.getNames(); + } + } + } + } LLSD element; element["id"] = agent_id; @@ -332,6 +351,24 @@ BOOL LLPanelFriends::updateFriendItem(const LLUUID& agent_id, const LLRelationsh std::string fullname; BOOL have_name = gCacheName->getFullName(agent_id, fullname); + if (have_name) + { + if (LLAvatarNameCache::useDisplayNames() && !gSavedSettings.getBOOL("LegacyNamesForFriends")) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(agent_id, &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + fullname = avatar_name.mDisplayName; + } + else + { + fullname = avatar_name.getNames(); + } + } + } + } // Name of the status icon to use std::string statusIcon; @@ -790,6 +827,25 @@ void LLPanelFriends::onClickRemove(void* user_data) std::string first, last; if(gCacheName->getName(agent_id, first, last)) { + if (LLAvatarNameCache::useDisplayNames() && !gSavedSettings.getBOOL("LegacyNamesForFriends")) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(agent_id, &avatar_name)) + { + // Always show "Display Name [Legacy Name]" for security reasons + first = avatar_name.getNames(); + size_t i = first.find(" "); + if (i != std::string::npos) + { + last = first.substr(i + 1); + first = first.substr(0, i); + } + else + { + last = ""; + } + } + } args["FIRST_NAME"] = first; args["LAST_NAME"] = last; } @@ -854,6 +910,25 @@ void LLPanelFriends::confirmModifyRights(rights_map_t& ids, EGrantRevoke command std::string first, last; if(gCacheName->getName(agent_id, first, last)) { + if (LLAvatarNameCache::useDisplayNames() && !gSavedSettings.getBOOL("LegacyNamesForFriends")) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(agent_id, &avatar_name)) + { + // Always show "Display Name [Legacy Name]" for security reasons + first = avatar_name.getNames(); + size_t i = first.find(" "); + if (i != std::string::npos) + { + last = first.substr(i + 1); + first = first.substr(0, i); + } + else + { + last = ""; + } + } + } args["FIRST_NAME"] = first; args["LAST_NAME"] = last; } diff --git a/linden/indra/newview/llfloaternewim.cpp b/linden/indra/newview/llfloaternewim.cpp index 48920c1..272cb53 100644 --- a/linden/indra/newview/llfloaternewim.cpp +++ b/linden/indra/newview/llfloaternewim.cpp @@ -38,10 +38,9 @@ #include "lltabcontainer.h" #include "llimview.h" -S32 COL_1_WIDTH = 200; +S32 COL_1_WIDTH = 400; static std::string sOnlineDescriptor = "*"; -static std::string sNameFormat = "[FIRST] [LAST]"; LLFloaterNewIM::LLFloaterNewIM() { @@ -69,7 +68,6 @@ BOOL LLFloaterNewIM::postBuild() llwarns << "LLUICtrlFactory::getNameListByName() returned NULL for 'user_list'" << llendl; } sOnlineDescriptor = getString("online_descriptor"); - sNameFormat = getString("name_format"); setDefaultBtn("start_btn"); return TRUE; } @@ -135,11 +133,8 @@ void LLFloaterNewIM::addGroup(const LLUUID& uuid, void* data, BOOL bold, BOOL on void LLFloaterNewIM::addAgent(const LLUUID& uuid, void* data, BOOL online) { - std::string first, last; - gCacheName->getName(uuid, first, last); - LLUIString fullname = sNameFormat; - fullname.setArg("[FIRST]", first); - fullname.setArg("[LAST]", last); + std::string fullname; + gCacheName->getFullName(uuid, fullname); LLSD row; row["id"] = uuid; @@ -184,13 +179,20 @@ void LLFloaterNewIM::onStart(void* userdata) const LLScrollListCell* cell = item->getColumn(0); std::string name(cell->getValue()); - // *NOTE: Do a live detrmination of what type of session it + // *NOTE: Do a live determination of what type of session it // should be. If we restrict the new im panel to online users, // then we can remove some of this code. EInstantMessage type; EInstantMessage* t = (EInstantMessage*)item->getUserdata(); if(t) type = (*t); else type = LLIMMgr::defaultIMTypeForAgent(item->getUUID()); + if (type != IM_SESSION_GROUP_START) + { + // Needed to avoid catching a display name, which would + // make us use a wrong IM log file... + gCacheName->getFullName(item->getUUID(), name); + } + gIMMgr->addSession(name, type, item->getUUID()); make_ui_sound("UISndStartIM"); diff --git a/linden/indra/newview/llhoverview.cpp b/linden/indra/newview/llhoverview.cpp index 10d27cd..e8d8428 100644 --- a/linden/indra/newview/llhoverview.cpp +++ b/linden/indra/newview/llhoverview.cpp @@ -253,10 +253,29 @@ void LLHoverView::updateText() LLNameValue* lastname = hit_object->getNVPair("LastName"); if (firstname && lastname) { + std::string complete_name = firstname->getString(); + complete_name += " "; + complete_name += lastname->getString(); + + if (LLAvatarNameCache::useDisplayNames()) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(hit_object->getID(), &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + complete_name = avatar_name.mDisplayName; + } + else + { + complete_name = avatar_name.getNames(); + } + } + } // [RLVa:KB] - Checked: 2009-07-08 (RLVa-1.0.0e) if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNAMES)) { - line = RlvStrings::getAnonym(line.append(firstname->getString()).append(1, ' ').append(lastname->getString())); + line = RlvStrings::getAnonym(complete_name); } else { diff --git a/linden/indra/newview/llimpanel.cpp b/linden/indra/newview/llimpanel.cpp index 41727f2..54e9bab 100644 --- a/linden/indra/newview/llimpanel.cpp +++ b/linden/indra/newview/llimpanel.cpp @@ -106,6 +106,8 @@ LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL; BOOL LLVoiceChannel::sSuspended = FALSE; +std::set LLFloaterIMPanel::sFloaterIMPanels; + void session_starter_helper( const LLUUID& temp_session_id, const LLUUID& other_participant_id, @@ -1108,6 +1110,7 @@ LLFloaterIMPanel::LLFloaterIMPanel( mFirstKeystrokeTimer(), mLastKeystrokeTimer() { + sFloaterIMPanels.insert(this); init(session_label); } @@ -1149,6 +1152,7 @@ LLFloaterIMPanel::LLFloaterIMPanel( void LLFloaterIMPanel::init(const std::string& session_label) { mSessionLabel = session_label; + mProfileButtonEnabled = FALSE; std::string xml_filename; switch(mDialog) @@ -1211,6 +1215,10 @@ void LLFloaterIMPanel::init(const std::string& session_label) FALSE); setTitle(mSessionLabel); + if (mProfileButtonEnabled) + { + lookupName(); + } mInputEditor->setMaxTextLength(DB_IM_MSG_STR_LEN); // enable line history support for instant message bar @@ -1253,9 +1261,32 @@ void LLFloaterIMPanel::init(const std::string& session_label) } } +void LLFloaterIMPanel::lookupName() +{ + LLAvatarNameCache::get(mOtherParticipantUUID, boost::bind(&LLFloaterIMPanel::onAvatarNameLookup, _1, _2, this)); +} + +//static +void LLFloaterIMPanel::onAvatarNameLookup(const LLUUID& id, const LLAvatarName& avatar_name, void* user_data) +{ + LLFloaterIMPanel* self = (LLFloaterIMPanel*)user_data; + + if (self && sFloaterIMPanels.count(self) != 0) + { + // Always show "Display Name [Legacy Name]" for security reasons + std::string title = avatar_name.getNames(); + if (!title.empty()) + { + self->setTitle(title); + } + } +} + LLFloaterIMPanel::~LLFloaterIMPanel() { + sFloaterIMPanels.erase(this); + delete mSpeakers; mSpeakers = NULL; @@ -1550,8 +1581,9 @@ BOOL LLFloaterIMPanel::inviteToSession(const LLDynamicArray& ids) return TRUE; } -void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, const LLColor4& color, bool log_to_file, const LLUUID& source, const std::string& name) +void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, const LLColor4& color, bool log_to_file, const LLUUID& source, const std::string& const_name) { + std::string name = const_name; // start tab flashing when receiving im for background session from user if (source != LLUUID::null) { @@ -1602,6 +1634,21 @@ void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, const LLColor4 } else { + if (LLAvatarNameCache::useDisplayNames() && source != LLUUID::null) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(source, &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + name = avatar_name.mDisplayName; + } + else + { + name = avatar_name.getNames(); + } + } + } // Convert the name to a hotlink and add to message. const LLStyleSP &source_style = LLStyleMap::instance().lookupAgent(source); mHistoryEditor->appendStyledText(name,false,prepend_newline,source_style); @@ -1619,7 +1666,7 @@ void LLFloaterIMPanel::addHistoryLine(const std::string &utf8msg, const LLColor4 else histstr = name + utf8msg; - LLLogChat::saveHistory(getTitle(),histstr); + LLLogChat::saveHistory(mSessionLabel, histstr); } if (!isInVisibleChain()) @@ -2168,6 +2215,21 @@ void LLFloaterIMPanel::sendMsg() { std::string history_echo; gAgent.buildFullname(history_echo); + if (LLAvatarNameCache::useDisplayNames()) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(gAgent.getID(), &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + history_echo = avatar_name.mDisplayName; + } + else + { + history_echo = avatar_name.getNames(); + } + } + } // Look for IRC-style emotes here. std::string prefix = utf8_text.substr(0, 4); diff --git a/linden/indra/newview/llimpanel.h b/linden/indra/newview/llimpanel.h index ddcbdc7..b5a0165 100644 --- a/linden/indra/newview/llimpanel.h +++ b/linden/indra/newview/llimpanel.h @@ -33,6 +33,7 @@ #ifndef LL_IMPANEL_H #define LL_IMPANEL_H +#include "llavatarnamecache.h" #include "llfloater.h" #include "lllogchat.h" #include "lluuid.h" @@ -194,6 +195,9 @@ public: EInstantMessage dialog); virtual ~LLFloaterIMPanel(); + void lookupName(); + static void onAvatarNameLookup(const LLUUID& id, const LLAvatarName& avatar_name, void* user_data); + /*virtual*/ BOOL postBuild(); // Check typing timeout timer. @@ -365,6 +369,8 @@ private: typedef std::map styleMap; static styleMap mStyleMap; + static std::set sFloaterIMPanels; + typedef enum e_im_format { IM_PANEL_PLAIN, diff --git a/linden/indra/newview/llnamelistctrl.cpp b/linden/indra/newview/llnamelistctrl.cpp index e445df5..8b268c5 100644 --- a/linden/indra/newview/llnamelistctrl.cpp +++ b/linden/indra/newview/llnamelistctrl.cpp @@ -39,6 +39,7 @@ #include "llcachename.h" #include "llagent.h" #include "llinventory.h" +#include "llviewercontrol.h" static LLRegisterWidget r("name_list"); @@ -56,7 +57,8 @@ LLNameListCtrl::LLNameListCtrl(const std::string& name, : LLScrollListCtrl(name, rect, cb, userdata, allow_multiple_selection, draw_border), mNameColumnIndex(name_column_index), - mAllowCallingCardDrop(FALSE) + mAllowCallingCardDrop(FALSE), + mUseDisplayNames(FALSE) { setToolTip(tooltip); LLNameListCtrl::sInstances.insert(this); @@ -77,7 +79,7 @@ BOOL LLNameListCtrl::addNameItem(const LLUUID& agent_id, EAddPosition pos, //llinfos << "LLNameListCtrl::addNameItem " << agent_id << llendl; std::string fullname; - BOOL result = gCacheName->getFullName(agent_id, fullname); + BOOL result = getResidentName(agent_id, fullname); fullname.append(suffix); @@ -164,7 +166,7 @@ BOOL LLNameListCtrl::addNameItem(LLScrollListItem* item, EAddPosition pos) //llinfos << "LLNameListCtrl::addNameItem " << item->getUUID() << llendl; std::string fullname; - BOOL result = gCacheName->getFullName(item->getUUID(), fullname); + BOOL result = getResidentName(item->getUUID(), fullname); LLScrollListCell* cell = (LLScrollListCell*)item->getColumn(mNameColumnIndex); ((LLScrollListText*)cell)->setText( fullname ); @@ -199,7 +201,7 @@ LLScrollListItem* LLNameListCtrl::addElement(const LLSD& value, EAddPosition pos else // normal resident { std::string name; - if (gCacheName->getFullName(item->getUUID(), name)) + if (getResidentName(item->getUUID(), name)) { fullname = name; } @@ -346,6 +348,12 @@ LLView* LLNameListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFacto name_list->setAllowCallingCardDrop(allow_calling_card_drop); } + BOOL use_display_names; + if (node->getAttributeBOOL("use_display_names", use_display_names)) + { + name_list->setUseDisplayNames(use_display_names); + } + name_list->setScrollListParameters(node); name_list->initFromXML(node, parent); @@ -456,5 +464,31 @@ LLView* LLNameListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFacto return name_list; } - - +bool LLNameListCtrl::getResidentName(const LLUUID& agent_id, std::string& fullname) +{ + std::string name; + if (gCacheName->getFullName(agent_id, name)) + { + fullname = name; + if (mUseDisplayNames && LLAvatarNameCache::useDisplayNames() && !gSavedSettings.getBOOL("LegacyNamesForFriends")) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(agent_id, &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + fullname = avatar_name.mDisplayName; + } + else + { + fullname = avatar_name.getNames(); + } + } + } + return true; + } + else + { + return false; + } +} diff --git a/linden/indra/newview/llnamelistctrl.h b/linden/indra/newview/llnamelistctrl.h index beb4ede..d6cf578 100644 --- a/linden/indra/newview/llnamelistctrl.h +++ b/linden/indra/newview/llnamelistctrl.h @@ -84,7 +84,12 @@ public: void setAllowCallingCardDrop(BOOL b) { mAllowCallingCardDrop = b; } + void setUseDisplayNames(BOOL b) { mUseDisplayNames = b; } + private: + bool getResidentName(const LLUUID& agent_id, std::string& fullname); + BOOL mUseDisplayNames; + static std::set sInstances; S32 mNameColumnIndex; BOOL mAllowCallingCardDrop; diff --git a/linden/indra/newview/llnetmap.cpp b/linden/indra/newview/llnetmap.cpp index b9dc482..438478f 100644 --- a/linden/indra/newview/llnetmap.cpp +++ b/linden/indra/newview/llnetmap.cpp @@ -36,6 +36,7 @@ #include "llnetmap.h" #include "indra_constants.h" +#include "llavatarnamecache.h" #include "llui.h" #include "llmath.h" // clampf() #include "llfocusmgr.h" @@ -640,7 +641,29 @@ BOOL LLNetMap::handleToolTip( S32 x, S32 y, std::string& msg, LLRect* sticky_rec { msg.assign(""); std::string fullname; - if(mClosestAgentToCursor.notNull() && gCacheName->getFullName(mClosestAgentToCursor, fullname)) + BOOL result = FALSE; + if (!LLAvatarNameCache::useDisplayNames()) + { + result = gCacheName->getFullName(mClosestAgentToCursor, fullname); + } + else + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(mClosestAgentToCursor, &avatar_name)) + { + result = TRUE; + if (LLAvatarNameCache::useDisplayNames() == 2) + { + fullname = avatar_name.mDisplayName; + } + else + { + fullname = avatar_name.getNames(true); + } + } + } + + if(mClosestAgentToCursor.notNull() && result) { // msg.append(fullname); // [RLVa:KB] - Version: 1.23.4 | Checked: 2009-07-08 (RLVa-1.0.0e) | Modified: RLVa-0.2.0b diff --git a/linden/indra/newview/llpanelavatar.cpp b/linden/indra/newview/llpanelavatar.cpp index 6110e44..6b3be29 100644 --- a/linden/indra/newview/llpanelavatar.cpp +++ b/linden/indra/newview/llpanelavatar.cpp @@ -1357,7 +1357,33 @@ void LLPanelAvatar::setAvatarID(const LLUUID &avatar_id, const std::string &name { name_edit->setText(name); } + childSetVisible("name", TRUE); } + LLNameEditor* complete_name_edit = getChild("complete_name"); + if (complete_name_edit) + { + if (LLAvatarNameCache::useDisplayNames()) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(avatar_id, &avatar_name)) + { + // Always show "Display Name [Legacy Name]" for security reasons + complete_name_edit->setText(avatar_name.getNames()); + } + else + { + complete_name_edit->setText(name_edit->getText()); + LLAvatarNameCache::get(avatar_id, boost::bind(&LLPanelAvatar::completeNameCallback, _1, _2, this)); + } + childSetVisible("name", FALSE); + childSetVisible("complete_name", TRUE); + } + else + { + childSetVisible("complete_name", FALSE); + } + } + // if (avatar_changed) { // While we're waiting for data off the network, clear out the @@ -1480,6 +1506,22 @@ void LLPanelAvatar::setAvatarID(const LLUUID &avatar_id, const std::string &name } } +void LLPanelAvatar::completeNameCallback(const LLUUID& agent_id, + const LLAvatarName& avatar_name, + void *userdata) +{ + LLPanelAvatar* self = (LLPanelAvatar*)userdata; + if (!LLAvatarNameCache::useDisplayNames() || agent_id != self->mAvatarID) + { + return; + } + LLLineEditor* complete_name_edit = self->getChild("complete_name"); + if (complete_name_edit) + { + // Always show "Display Name [Legacy Name]" for security reasons + complete_name_edit->setText(avatar_name.getNames()); + } +} void LLPanelAvatar::resetGroupList() { diff --git a/linden/indra/newview/llpanelavatar.h b/linden/indra/newview/llpanelavatar.h index 9a2f450..3a90196 100644 --- a/linden/indra/newview/llpanelavatar.h +++ b/linden/indra/newview/llpanelavatar.h @@ -33,6 +33,7 @@ #ifndef LL_LLPANELAVATAR_H #define LL_LLPANELAVATAR_H +#include "llavatarnamecache.h" #include "llpanel.h" #include "v3dmath.h" #include "lluuid.h" @@ -335,6 +336,9 @@ private: static bool finishUnfreeze(const LLSD& notification, const LLSD& response); static void showProfileCallback(S32 option, void *userdata); + static void completeNameCallback(const LLUUID& agent_id, + const LLAvatarName& avatar_name, + void *userdata); static void* createPanelAvatar(void* data); static void* createFloaterAvatarInfo(void* data); diff --git a/linden/indra/newview/llstartup.cpp b/linden/indra/newview/llstartup.cpp index 939c2e6..4bfcf52 100644 --- a/linden/indra/newview/llstartup.cpp +++ b/linden/indra/newview/llstartup.cpp @@ -2034,6 +2034,12 @@ bool idle_startup() // Load stored cache if possible LLAppViewer::instance()->loadNameCache(); + + // Start cache in not-running state until we figure out if we have + // capabilities for display name lookup + LLAvatarNameCache::initClass(false); + LLAvatarNameCache::setUseDisplayNames(gSavedSettings.getU32("DisplayNamesUsage")); + LLAvatarName::sOmitResidentAsLastName = (bool)gSavedSettings.getBOOL("OmitResidentAsLastName"); } // *Note: this is where gWorldMap used to be initialized. diff --git a/linden/indra/newview/llviewercontrol.cpp b/linden/indra/newview/llviewercontrol.cpp index 00e3a9a..572f64a 100644 --- a/linden/indra/newview/llviewercontrol.cpp +++ b/linden/indra/newview/llviewercontrol.cpp @@ -41,6 +41,8 @@ // #include "llaudioengine.h" #include "kokuastreamingaudio.h" #include "llagent.h" +#include "llavatarnamecache.h" +#include "llcallingcard.h" #include "llconsole.h" #include "lldrawpoolterrain.h" #include "llflexibleobject.h" @@ -426,6 +428,28 @@ static bool handleAuditTextureChanged(const LLSD& newvalue) return true; } +static bool handleDisplayNamesUsageChanged(const LLSD& newvalue) +{ + LLAvatarNameCache::setUseDisplayNames((U32)newvalue.asInteger()); + LLVOAvatar::invalidateNameTags(); + LLAvatarTracker::instance().dirtyBuddies(); + return true; +} + +static bool handleOmitResidentAsLastNameChanged(const LLSD& newvalue) +{ + LLAvatarName::sOmitResidentAsLastName =(bool)newvalue.asBoolean(); + LLVOAvatar::invalidateNameTags(); + LLAvatarTracker::instance().dirtyBuddies(); + return true; +} + +static bool handleLegacyNamesForFriendsChanged(const LLSD& newvalue) +{ + LLAvatarTracker::instance().dirtyBuddies(); + return true; +} + static bool handleRenderDebugGLChanged(const LLSD& newvalue) { gDebugGL = newvalue.asBoolean(); @@ -573,6 +597,9 @@ void settings_setup_listeners() gSavedSettings.getControl("AudioLevelRolloff")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _1)); gSavedSettings.getControl("AudioStreamingMusic")->getSignal()->connect(boost::bind(&handleAudioStreamMusicChanged, _1)); gSavedSettings.getControl("AuditTexture")->getSignal()->connect(boost::bind(&handleAuditTextureChanged, _1)); + gSavedSettings.getControl("DisplayNamesUsage")->getSignal()->connect(boost::bind(&handleDisplayNamesUsageChanged, _1)); + gSavedSettings.getControl("OmitResidentAsLastName")->getSignal()->connect(boost::bind(&handleOmitResidentAsLastNameChanged, _1)); + gSavedSettings.getControl("LegacyNamesForFriends")->getSignal()->connect(boost::bind(&handleLegacyNamesForFriendsChanged, _1)); gSavedSettings.getControl("MuteAudio")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _1)); gSavedSettings.getControl("MuteMusic")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _1)); gSavedSettings.getControl("MuteMedia")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _1)); diff --git a/linden/indra/newview/llviewerdisplayname.cpp b/linden/indra/newview/llviewerdisplayname.cpp new file mode 100644 index 0000000..6a7cab3 --- /dev/null +++ b/linden/indra/newview/llviewerdisplayname.cpp @@ -0,0 +1,208 @@ +/** + * @file llviewerdisplayname.cpp + * @brief Wrapper for display name functionality + * + * $LicenseInfo:firstyear=2010&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 "llviewerdisplayname.h" + +// viewer includes +#include "llagent.h" +#include "llviewerregion.h" +#include "llvoavatar.h" + +// library includes +#include "llavatarnamecache.h" +#include "llhttpclient.h" +#include "llhttpnode.h" +#include "llnotifications.h" +#include "llui.h" // getLanguage() + +namespace LLViewerDisplayName +{ + // Fired when viewer receives server response to display name change + set_name_signal_t sSetDisplayNameSignal; + + // Fired when there is a change in the agent's name + name_changed_signal_t sNameChangedSignal; + + void addNameChangedCallback(const name_changed_signal_t::slot_type& cb) + { + sNameChangedSignal.connect(cb); + } + +} + +class LLSetDisplayNameResponder : public LLHTTPClient::Responder +{ +public: + // only care about errors + /*virtual*/ void error(U32 status, const std::string& reason) + { + LLViewerDisplayName::sSetDisplayNameSignal(false, "", LLSD()); + LLViewerDisplayName::sSetDisplayNameSignal.disconnect_all_slots(); + } +}; + +void LLViewerDisplayName::set(const std::string& display_name, const set_name_slot_t& slot) +{ + // TODO: simple validation here + + LLViewerRegion* region = gAgent.getRegion(); + llassert(region); + std::string cap_url = region->getCapability("SetDisplayName"); + if (cap_url.empty()) + { + // this server does not support display names, report error + slot(false, "unsupported", LLSD()); + return; + } + + // People API can return localized error messages. Indicate our + // language preference via header. + LLSD headers; + headers["Accept-Language"] = LLUI::getLanguage(); + + // People API requires both the old and new value to change a variable. + // Our display name will be in cache before the viewer's UI is available + // to request a change, so we can use direct lookup without callback. + LLAvatarName av_name; + if (!LLAvatarNameCache::get( gAgent.getID(), &av_name)) + { + slot(false, "name unavailable", LLSD()); + return; + } + + // People API expects array of [ "old value", "new value" ] + LLSD change_array = LLSD::emptyArray(); + change_array.append(av_name.mDisplayName); + change_array.append(display_name); + + llinfos << "Set name POST to " << cap_url << llendl; + + // Record our caller for when the server sends back a reply + sSetDisplayNameSignal.connect(slot); + + // POST the requested change. The sim will not send a response back to + // this request directly, rather it will send a separate message after it + // communicates with the back-end. + LLSD body; + body["display_name"] = change_array; + LLHTTPClient::post(cap_url, body, new LLSetDisplayNameResponder, headers); +} + +class LLSetDisplayNameReply : public LLHTTPNode +{ + LOG_CLASS(LLSetDisplayNameReply); +public: + /*virtual*/ void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + LLSD body = input["body"]; + + S32 status = body["status"].asInteger(); + bool success = (status == 200); + std::string reason = body["reason"].asString(); + LLSD content = body["content"]; + + llinfos << "status " << status << " reason " << reason << llendl; + + // If viewer's concept of display name is out-of-date, the set request + // will fail with 409 Conflict. If that happens, fetch up-to-date + // name information. + if (status == 409) + { + LLUUID agent_id = gAgent.getID(); + // Flush stale data + LLAvatarNameCache::erase( agent_id ); + // Queue request for new data + LLAvatarName ignored; + LLAvatarNameCache::get( agent_id, &ignored ); + // Kill name tag, as it is wrong + LLVOAvatar::invalidateNameTag( agent_id ); + } + + // inform caller of result + LLViewerDisplayName::sSetDisplayNameSignal(success, reason, content); + LLViewerDisplayName::sSetDisplayNameSignal.disconnect_all_slots(); + } +}; + + +class LLDisplayNameUpdate : public LLHTTPNode +{ + /*virtual*/ void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + LLSD body = input["body"]; + LLUUID agent_id = body["agent_id"]; + std::string old_display_name = body["old_display_name"]; + // By convention this record is called "agent" in the People API + LLSD name_data = body["agent"]; + + // Inject the new name data into cache + LLAvatarName av_name; + av_name.fromLLSD( name_data ); + + llinfos << "name-update now " << LLDate::now() + << " next_update " << LLDate(av_name.mNextUpdate) + << llendl; + + // Name expiration time may be provided in headers, or we may use a + // default value + // *TODO: get actual headers out of ResponsePtr + //LLSD headers = response->mHeaders; + LLSD headers; + av_name.mExpires = + LLAvatarNameCache::nameExpirationFromHeaders(headers); + + LLAvatarNameCache::insert(agent_id, av_name); + + // force name tag to update + LLVOAvatar::invalidateNameTag(agent_id); + + LLSD args; + args["OLD_NAME"] = old_display_name; + args["SLID"] = av_name.mUsername; + args["NEW_NAME"] = av_name.mDisplayName; + LLNotifications::instance().add("DisplayNameUpdate", args); + if (agent_id == gAgent.getID()) + { + LLViewerDisplayName::sNameChangedSignal(); + } + } +}; + +LLHTTPRegistration + gHTTPRegistrationMessageSetDisplayNameReply( + "/message/SetDisplayNameReply"); + +LLHTTPRegistration + gHTTPRegistrationMessageDisplayNameUpdate( + "/message/DisplayNameUpdate"); diff --git a/linden/indra/newview/llviewerdisplayname.h b/linden/indra/newview/llviewerdisplayname.h new file mode 100644 index 0000000..16d59ae --- /dev/null +++ b/linden/indra/newview/llviewerdisplayname.h @@ -0,0 +1,53 @@ +/** + * @file llviewerdisplayname.h + * @brief Wrapper for display name functionality + * + * $LicenseInfo:firstyear=2010&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$ + */ + +#ifndef LLVIEWERDISPLAYNAME_H +#define LLVIEWERDISPLAYNAME_H + +#include + +class LLSD; +class LLUUID; + +namespace LLViewerDisplayName +{ + typedef boost::signals2::signal< + void (bool success, const std::string& reason, const LLSD& content)> + set_name_signal_t; + typedef set_name_signal_t::slot_type set_name_slot_t; + + typedef boost::signals2::signal name_changed_signal_t; + typedef name_changed_signal_t::slot_type name_changed_slot_t; + + // Sends an update to the server to change a display name + // and call back when done. May not succeed due to service + // unavailable or name not available. + void set(const std::string& display_name, const set_name_slot_t& slot); + + void addNameChangedCallback(const name_changed_signal_t::slot_type& cb); +} + +#endif // LLVIEWERDISPLAYNAME_H diff --git a/linden/indra/newview/llviewermenu.cpp b/linden/indra/newview/llviewermenu.cpp index c5d78d8..777a871 100644 --- a/linden/indra/newview/llviewermenu.cpp +++ b/linden/indra/newview/llviewermenu.cpp @@ -41,6 +41,7 @@ // linden library includes #include "llaudioengine.h" +#include "llavatarnamecache.h" #include "indra_constants.h" #include "llassetstorage.h" #include "llchat.h" @@ -102,6 +103,7 @@ #include "llfloatercustomize.h" #include "llfloaterdaycycle.h" #include "llfloaterdirectory.h" +#include "llfloaterdisplayname.h" #include "llfloatereditui.h" #include "llfloaterchatterbox.h" #include "llfloaterfriends.h" @@ -3434,6 +3436,16 @@ class LLEditEnableCustomizeAvatar : public view_listener_t } }; +class LLEditEnableDisplayName : public view_listener_t +{ + bool handleEvent(LLPointer event, const LLSD& userdata) + { + bool new_value = (LLAvatarNameCache::useDisplayNames() != 0); + gMenuHolder->findControl(userdata["control"].asString())->setValue(new_value); + return true; + } +}; + // only works on pie menu bool handle_sit_or_stand() { @@ -5959,6 +5971,10 @@ class LLShowFloater : public view_listener_t { LLToolBar::toggle(NULL); } + else if (floater_name == "displayname") + { + LLFloaterDisplayName::show(); + } else if (floater_name == "chat history") { LLFloaterChat::toggleInstance(LLSD()); @@ -11132,6 +11148,7 @@ void initialize_menus() addMenu(new LLEditEnableDuplicate(), "Edit.EnableDuplicate"); addMenu(new LLEditEnableTakeOff(), "Edit.EnableTakeOff"); addMenu(new LLEditEnableCustomizeAvatar(), "Edit.EnableCustomizeAvatar"); + addMenu(new LLEditEnableDisplayName(), "Edit.EnableDisplayName"); addMenu(new LLAdvancedRebakeTextures(), "Advanced.RebakeTextures"); // View menu diff --git a/linden/indra/newview/llviewermessage.cpp b/linden/indra/newview/llviewermessage.cpp index 5f333e9..8cb154e 100755 --- a/linden/indra/newview/llviewermessage.cpp +++ b/linden/indra/newview/llviewermessage.cpp @@ -2998,6 +2998,33 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) if (is_audible) { + if (chatter && chatter->isAvatar()) + { +#ifdef LL_RRINTERFACE_H //MK + if (!gRRenabled || !gAgent.mRRInterface.mContainsShownames) + { +#endif //mk + if (LLAvatarNameCache::useDisplayNames()) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(from_id, &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + from_name = avatar_name.mDisplayName; + } + else + { + from_name = avatar_name.getNames(); + } + } + chat.mFromName = from_name; + } +#ifdef LL_RRINTERFACE_H //MK + } +#endif //mk + } + BOOL visible_in_chat_bubble = FALSE; std::string verb; diff --git a/linden/indra/newview/llviewerregion.cpp b/linden/indra/newview/llviewerregion.cpp index d93c425..a8047b8 100644 --- a/linden/indra/newview/llviewerregion.cpp +++ b/linden/indra/newview/llviewerregion.cpp @@ -127,6 +127,8 @@ public: } } + mRegion->setCapabilitiesReceived(true); + if (STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) { LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); @@ -172,7 +174,8 @@ LLViewerRegion::LLViewerRegion(const U64 &handle, mCacheEntriesCount(0), mCacheID(), mEventPoll(NULL), - mReleaseNotesRequested(FALSE) + mReleaseNotesRequested(FALSE), + mCapabilitiesReceived(false) { mWidth = region_width_meters; mOriginGlobal = from_region_handle(handle); @@ -1431,6 +1434,8 @@ void LLViewerRegion::setSeedCapability(const std::string& url) capabilityNames.append("FetchInventory"); capabilityNames.append("FetchLib"); capabilityNames.append("FetchLibDescendents"); + capabilityNames.append("GetDisplayNames"); + capabilityNames.append("SetDisplayName"); capabilityNames.append("GetTexture"); capabilityNames.append("GroupProposalBallot"); capabilityNames.append("HomeLocation"); @@ -1509,6 +1514,16 @@ std::string LLViewerRegion::getCapability(const std::string& name) const return iter->second; } +bool LLViewerRegion::capabilitiesReceived() const +{ + return mCapabilitiesReceived; +} + +void LLViewerRegion::setCapabilitiesReceived(bool received) +{ + mCapabilitiesReceived = received; +} + void LLViewerRegion::logActiveCapabilities() const { int count = 0; diff --git a/linden/indra/newview/llviewerregion.h b/linden/indra/newview/llviewerregion.h index 8cc80e3..5ce8b2e 100644 --- a/linden/indra/newview/llviewerregion.h +++ b/linden/indra/newview/llviewerregion.h @@ -229,6 +229,11 @@ public: void setSeedCapability(const std::string& url); void setCapability(const std::string& name, const std::string& url); std::string getCapability(const std::string& name) const; + + // has region received its final (not seed) capability list? + bool capabilitiesReceived() const; + void setCapabilitiesReceived(bool received); + static bool isSpecialCapabilityName(const std::string &name); void logActiveCapabilities() const; @@ -399,6 +404,7 @@ private: private: bool mAlive; // can become false if circuit disconnects + bool mCapabilitiesReceived; //spatial partitions for objects in this region std::vector mObjectPartition; diff --git a/linden/indra/newview/llvoavatar.cpp b/linden/indra/newview/llvoavatar.cpp index 35cabc1..a3222df 100644 --- a/linden/indra/newview/llvoavatar.cpp +++ b/linden/indra/newview/llvoavatar.cpp @@ -38,6 +38,7 @@ #include #include "llaudioengine.h" +#include "llavatarnamecache.h" #include "noise.h" #include "llagent.h" // Get state values from here @@ -748,6 +749,7 @@ LLVOAvatar::LLVOAvatar(const LLUUID& id, mAppearanceAnimating(FALSE), mNameString(), mTitle(), + mCompleteName(), mNameAway(FALSE), mNameBusy(FALSE), mNameMute(FALSE), @@ -3609,6 +3611,35 @@ void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last) if (mNameText.notNull() && firstname && lastname) { + std::string complete_name = firstname->getString(); + if (sRenderGroupTitles) + { + complete_name += " "; + } + else + { + // If all group titles are turned off, stack first name + // on a line above last name + complete_name += "\n"; + } + complete_name += lastname->getString(); + + if (LLAvatarNameCache::useDisplayNames()) + { + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(getID(), &avatar_name)) + { + if (LLAvatarNameCache::useDisplayNames() == 2) + { + complete_name = avatar_name.mDisplayName; + } + else + { + complete_name = avatar_name.getNames(true); + } + } + } + BOOL is_away = mSignaledAnimations.find(ANIM_AGENT_AWAY) != mSignaledAnimations.end(); BOOL is_busy = mSignaledAnimations.find(ANIM_AGENT_BUSY) != mSignaledAnimations.end(); BOOL is_appearance = mSignaledAnimations.find(ANIM_AGENT_CUSTOMIZE) != mSignaledAnimations.end(); @@ -3623,7 +3654,7 @@ void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last) } if (mNameString.empty() || - new_name || + new_name || complete_name != mCompleteName || (!title && !mTitle.empty()) || (title && mTitle != title->getString()) || (is_away != mNameAway || is_busy != mNameBusy || is_muted != mNameMute) @@ -3639,20 +3670,19 @@ void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last) line += title->getString(); //LLStringFn::replace_ascii_controlchars(line,LL_UNKNOWN_CHAR); IMP-136 -- McCabe line += "\n"; - line += firstname->getString(); + line += complete_name; } else { - line += firstname->getString(); + line += complete_name; } - line += " "; - line += lastname->getString(); + // [RLVa:KB] - Version: 1.23.4 | Checked: 2009-07-08 (RLVa-1.0.0e) | Added: RLVa-0.2.0b } else { - line = RlvStrings::getAnonym(line.assign(firstname->getString()).append(" ").append(lastname->getString())); + line = RlvStrings::getAnonym(complete_name); } // [/RLVa:KB] @@ -3662,7 +3692,7 @@ void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last) bool show_client = client.length() != 0 && (*sShowClientNameTag); if (is_away || is_muted || is_busy || show_client) { - line += " ("; + line += "\n("; if (is_away) { line += "Away"; @@ -3707,6 +3737,7 @@ void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last) mNameMute = is_muted; mNameAppearance = is_appearance; mTitle = title ? title->getString() : ""; + mCompleteName = complete_name; //LLStringFn::replace_ascii_controlchars(mTitle,LL_UNKNOWN_CHAR); IMP-136 -- McCabe mNameString = utf8str_to_wstring(line); new_name = TRUE; @@ -3823,6 +3854,41 @@ void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last) } } +void LLVOAvatar::clearNameTag() +{ + mNameString.clear(); + if (mNameText) + { + mNameText->setLabel(""); + mNameText->setString(mNameString); + } +} + +//static +void LLVOAvatar::invalidateNameTag(const LLUUID& agent_id) +{ + LLViewerObject* obj = gObjectList.findObject(agent_id); + if (!obj) return; + + LLVOAvatar* avatar = dynamic_cast(obj); + if (!avatar) return; + + avatar->clearNameTag(); +} + +//static +void LLVOAvatar::invalidateNameTags() +{ + std::vector::iterator it; + for (it = LLCharacter::sInstances.begin(); it != LLCharacter::sInstances.end(); ++it) + { + LLVOAvatar* avatar = dynamic_cast(*it); + if (!avatar) continue; + if (avatar->isDead()) continue; + + avatar->clearNameTag(); + } +} void LLVOAvatar::idleUpdateTractorBeam() { diff --git a/linden/indra/newview/llvoavatar.h b/linden/indra/newview/llvoavatar.h index a23a9df..e585a6a 100644 --- a/linden/indra/newview/llvoavatar.h +++ b/linden/indra/newview/llvoavatar.h @@ -112,6 +112,10 @@ public: void idleUpdateWindEffect(); void idleUpdateBoobEffect(); void idleUpdateNameTag(const LLVector3& root_pos_last); + void clearNameTag(); + static void invalidateNameTag(const LLUUID& agent_id); + // force all name tags to rebuild, useful when display names turned on/off + static void invalidateNameTags(); void idleUpdateRenderCost(); void idleUpdateTractorBeam(); void idleUpdateBelowWater(); @@ -677,6 +681,7 @@ protected: LLWString mNameString; std::string mTitle; + std::string mCompleteName; BOOL mNameAway; BOOL mNameBusy; BOOL mNameMute; diff --git a/linden/indra/newview/skins/default/xui/en-us/floater_display_name.xml b/linden/indra/newview/skins/default/xui/en-us/floater_display_name.xml new file mode 100644 index 0000000..5875efb --- /dev/null +++ b/linden/indra/newview/skins/default/xui/en-us/floater_display_name.xml @@ -0,0 +1,42 @@ + + + + The name you give to your avatar is called your Display Name. + + + You can change it once a week. + + + You can change it now if you so wish. + + + You cannot change it before: [TIME]. + + + New Display Name: + + + + Type your new name again to confirm: + + +