diff options
Diffstat (limited to 'indra')
96 files changed, 10080 insertions, 1422 deletions
diff --git a/indra/llinventory/llparcel.cpp b/indra/llinventory/llparcel.cpp index e400877b3b..0784986e3a 100755 --- a/indra/llinventory/llparcel.cpp +++ b/indra/llinventory/llparcel.cpp @@ -40,6 +40,8 @@ #include "llsdutil_math.h" #include "message.h" #include "u64.h" +#include "llregionflags.h" +#include <boost/range/adaptor/map.hpp> static const F32 SOME_BIG_NUMBER = 1000.0f; static const F32 SOME_BIG_NEG_NUMBER = -1000.0f; @@ -627,8 +629,8 @@ void LLParcel::unpackMessage(LLMessageSystem* msg) void LLParcel::packAccessEntries(LLMessageSystem* msg, const std::map<LLUUID,LLAccessEntry>& list) { - access_map_const_iterator cit = list.begin(); - access_map_const_iterator end = list.end(); + LLAccessEntry::map::const_iterator cit = list.begin(); + LLAccessEntry::map::const_iterator end = list.end(); if (cit == end) { @@ -679,9 +681,28 @@ void LLParcel::unpackAccessEntries(LLMessageSystem* msg, } +void LLParcel::unpackExperienceEntries( LLMessageSystem* msg, U32 type ) +{ + LLUUID id; + + S32 i; + S32 count = msg->getNumberOfBlocksFast(_PREHASH_List); + for (i = 0; i < count; i++) + { + msg->getUUIDFast(_PREHASH_List, _PREHASH_ID, id, i); + + if (id.notNull()) + { + mExperienceKeys[id]=type; + } + } +} + + + void LLParcel::expirePasses(S32 now) { - access_map_iterator itor = mAccessList.begin(); + LLAccessEntry::map::iterator itor = mAccessList.begin(); while (itor != mAccessList.end()) { const LLAccessEntry& entry = (*itor).second; @@ -771,7 +792,7 @@ BOOL LLParcel::addToAccessList(const LLUUID& agent_id, S32 time) // Can't add owner to these lists return FALSE; } - access_map_iterator itor = mAccessList.begin(); + LLAccessEntry::map::iterator itor = mAccessList.begin(); while (itor != mAccessList.end()) { const LLAccessEntry& entry = (*itor).second; @@ -816,7 +837,7 @@ BOOL LLParcel::addToBanList(const LLUUID& agent_id, S32 time) return FALSE; } - access_map_iterator itor = mBanList.begin(); + LLAccessEntry::map::iterator itor = mBanList.begin(); while (itor != mBanList.end()) { const LLAccessEntry& entry = (*itor).second; @@ -852,7 +873,7 @@ BOOL remove_from_access_array(std::map<LLUUID,LLAccessEntry>* list, const LLUUID& agent_id) { BOOL removed = FALSE; - access_map_iterator itor = list->begin(); + LLAccessEntry::map::iterator itor = list->begin(); while (itor != list->end()) { const LLAccessEntry& entry = (*itor).second; @@ -1195,3 +1216,58 @@ LLParcel::ECategory category_ui_string_to_category(const std::string& s) // is a distinct option from "None" and "Other" return LLParcel::C_ANY; } + +LLAccessEntry::map LLParcel::getExperienceKeysByType( U32 type ) const +{ + LLAccessEntry::map access; + LLAccessEntry entry; + xp_type_map_t::const_iterator it = mExperienceKeys.begin(); + for(/**/; it != mExperienceKeys.end(); ++it) + { + if(it->second == type) + { + entry.mID = it->first; + access[entry.mID] = entry; + } + } + return access; +} + +void LLParcel::clearExperienceKeysByType( U32 type ) +{ + xp_type_map_t::iterator it = mExperienceKeys.begin(); + while(it != mExperienceKeys.end()) + { + if(it->second == type) + { + mExperienceKeys.erase(it++); + } + else + { + ++it; + } + } +} + +void LLParcel::setExperienceKeyType( const LLUUID& experience_key, U32 type ) +{ + if(type == EXPERIENCE_KEY_TYPE_NONE) + { + mExperienceKeys.erase(experience_key); + } + else + { + if(countExperienceKeyType(type) < PARCEL_MAX_EXPERIENCE_LIST) + { + mExperienceKeys[experience_key] = type; + } + } +} + +U32 LLParcel::countExperienceKeyType( U32 type ) +{ + return std::count_if( + boost::begin(mExperienceKeys | boost::adaptors::map_values), + boost::end(mExperienceKeys | boost::adaptors::map_values), + std::bind2nd(std::equal_to<U32>(), type)); +} diff --git a/indra/llinventory/llparcel.h b/indra/llinventory/llparcel.h index c4363a48df..e68331b99a 100755 --- a/indra/llinventory/llparcel.h +++ b/indra/llinventory/llparcel.h @@ -53,6 +53,9 @@ const S32 PARCEL_MAX_ACCESS_LIST = 300; //for access/ban lists. const F32 PARCEL_MAX_ENTRIES_PER_PACKET = 48.f; +// Maximum number of experiences +const S32 PARCEL_MAX_EXPERIENCE_LIST = 24; + // Weekly charge for listing a parcel in the directory const S32 PARCEL_DIRECTORY_FEE = 30; @@ -130,9 +133,11 @@ class LLSD; class LLAccessEntry { public: + + typedef std::map<LLUUID,LLAccessEntry> map; + LLAccessEntry() - : mID(), - mTime(0), + : mTime(0), mFlags(0) {} @@ -141,8 +146,6 @@ public: U32 mFlags; // Not used - currently should always be zero }; -typedef std::map<LLUUID,LLAccessEntry>::iterator access_map_iterator; -typedef std::map<LLUUID,LLAccessEntry>::const_iterator access_map_const_iterator; class LLParcel { @@ -320,6 +323,9 @@ public: void unpackAccessEntries(LLMessageSystem* msg, std::map<LLUUID,LLAccessEntry>* list); + void unpackExperienceEntries(LLMessageSystem* msg, U32 type); + + void setAABBMin(const LLVector3& min) { mAABBMin = min; } void setAABBMax(const LLVector3& max) { mAABBMax = max; } @@ -665,6 +671,17 @@ public: std::map<LLUUID,LLAccessEntry> mTempBanList; std::map<LLUUID,LLAccessEntry> mTempAccessList; + typedef std::map<LLUUID, U32> xp_type_map_t; + + void setExperienceKeyType(const LLUUID& experience_key, U32 type); + U32 countExperienceKeyType(U32 type); + U32 getExperienceKeyType(const LLUUID& experience_key)const; + LLAccessEntry::map getExperienceKeysByType(U32 type)const; + void clearExperienceKeysByType(U32 type); + +private: + xp_type_map_t mExperienceKeys; + }; diff --git a/indra/llinventory/llparcelflags.h b/indra/llinventory/llparcelflags.h index b1a917df73..25b27a281a 100755 --- a/indra/llinventory/llparcelflags.h +++ b/indra/llinventory/llparcelflags.h @@ -90,8 +90,10 @@ const U32 PF_DEFAULT = PF_ALLOW_FLY | PF_USE_ESTATE_VOICE_CHAN; // Access list flags -const U32 AL_ACCESS = (1 << 0); -const U32 AL_BAN = (1 << 1); +const U32 AL_ACCESS = (1 << 0); +const U32 AL_BAN = (1 << 1); +const U32 AL_ALLOW_EXPERIENCE = (1 << 3); +const U32 AL_BLOCK_EXPERIENCE = (1 << 4); //const U32 AL_RENTER = (1 << 2); // Block access return values. BA_ALLOWED is the only success case diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 8bd134dc84..08edf1e5fe 100755 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -39,6 +39,7 @@ set(llmessage_SOURCE_FILES llcurl.cpp lldatapacker.cpp lldispatcher.cpp + llexperiencecache.cpp llfiltersd2xmlrpc.cpp llhost.cpp llhttpassetstorage.cpp @@ -129,6 +130,7 @@ set(llmessage_HEADER_FILES lldbstrings.h lldispatcher.h lleventflags.h + llexperiencecache.h llextendedstatus.h llfiltersd2xmlrpc.h llfollowcamparams.h diff --git a/indra/llmessage/llexperiencecache.cpp b/indra/llmessage/llexperiencecache.cpp new file mode 100644 index 0000000000..52b60a176e --- /dev/null +++ b/indra/llmessage/llexperiencecache.cpp @@ -0,0 +1,641 @@ +/** + * @file llexperiencecache.cpp + * @brief llexperiencecache and related class definitions + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 "llexperiencecache.h" + +#include "llavatarname.h" +#include "llframetimer.h" +#include "llhttpclient.h" +#include "llsdserialize.h" +#include <set> +#include <map> +#include "boost/tokenizer.hpp" + + +namespace LLExperienceCache +{ + + typedef std::map<LLUUID, LLUUID> KeyMap; + KeyMap privateToPublicKeyMap; + + void mapKeys(const LLSD& legacyKeys); + + std::string sLookupURL; + + typedef std::map<LLUUID, std::string> ask_queue_t; + ask_queue_t sAskQueue; + + typedef std::map<LLUUID, F64> pending_queue_t; + pending_queue_t sPendingQueue; + + cache_t sCache; + int sMaximumLookups = 10; + + LLFrameTimer sRequestTimer; + + // Periodically clean out expired entries from the cache + LLFrameTimer sEraseExpiredTimer; + + // 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<LLUUID, callback_signal_t*> signal_map_t; + signal_map_t sSignalMap; + + + + bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age); + void eraseExpired(); + + void processExperience( const LLUUID& public_key, const LLSD& experience ) + { + sCache[public_key]=experience; + LLSD & row = sCache[public_key]; + + if(row.has(EXPIRES)) + { + row[EXPIRES] = row[EXPIRES].asReal() + LLFrameTimer::getTotalSeconds(); + } + + if(row.has(EXPERIENCE_ID)) + { + sPendingQueue.erase(row[EXPERIENCE_ID].asUUID()); + } + + //signal + signal_map_t::iterator sig_it = sSignalMap.find(public_key); + if (sig_it != sSignalMap.end()) + { + callback_signal_t* signal = sig_it->second; + (*signal)(experience); + + sSignalMap.erase(public_key); + + delete signal; + } + } + + void initClass( ) + { + } + + const cache_t& getCached() + { + return sCache; + } + + void setMaximumLookups( int maximumLookups) + { + sMaximumLookups = maximumLookups; + } + + void bootstrap(const LLSD& legacyKeys, int initialExpiration) + { + mapKeys(legacyKeys); + LLSD::array_const_iterator it = legacyKeys.beginArray(); + for(/**/; it != legacyKeys.endArray(); ++it) + { + LLSD experience = *it; + if(experience.has(EXPERIENCE_ID)) + { + if(!experience.has(EXPIRES)) + { + experience[EXPIRES] = initialExpiration; + } + processExperience(experience[EXPERIENCE_ID].asUUID(), experience); + } + else + { + LL_WARNS("ExperienceCache") + << "Skipping bootstrap entry which is missing " << EXPERIENCE_ID + << LL_ENDL; + } + } + } + + + + bool 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)) + { + LL_WARNS("ExperienceCache") + << "got EXPIRES from headers, max_age " << max_age + << LL_ENDL; + F64 now = LLFrameTimer::getTotalSeconds(); + *expires = now + (F64)max_age; + return true; + } + } + return false; + } + + + static const std::string MAX_AGE("max-age"); + static const boost::char_separator<char> EQUALS_SEPARATOR("="); + static const boost::char_separator<char> 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<boost::char_separator<char> > 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; + } + + + void importFile(std::istream& istr) + { + LLSD data; + S32 parse_count = LLSDSerialize::fromXMLDocument(data, istr); + if(parse_count < 1) return; + + LLSD experiences = data["experiences"]; + + LLUUID public_key; + LLSD::map_const_iterator it = experiences.beginMap(); + for(; it != experiences.endMap() ; ++it) + { + public_key.set(it->first); + sCache[public_key]=it->second; + } + + LL_DEBUGS("ExperienceCache") << "importFile() loaded " << sCache.size() << LL_ENDL; + } + + void exportFile(std::ostream& ostr) + { + LLSD experiences; + + cache_t::const_iterator it =sCache.begin(); + for( ; it != sCache.end() ; ++it) + { + if(!it->second.has(EXPERIENCE_ID) || it->second[EXPERIENCE_ID].asUUID().isNull() || + it->second.has("DoesNotExist") || (it->second.has(PROPERTIES) && it->second[PROPERTIES].asInteger() & PROPERTY_INVALID)) + continue; + + experiences[it->first.asString()] = it->second; + } + + LLSD data; + data["experiences"] = experiences; + + LLSDSerialize::toPrettyXML(data, ostr); + } + + class LLExperienceResponder : public LLHTTPClient::Responder + { + public: + LLExperienceResponder(const ask_queue_t& keys) + :mKeys(keys) + { + + } + + /*virtual*/ void httpCompleted() + { + LLSD experiences = getContent()["experience_keys"]; + LLSD::array_const_iterator it = experiences.beginArray(); + for( /**/ ; it != experiences.endArray(); ++it) + { + const LLSD& row = *it; + LLUUID public_key = row[EXPERIENCE_ID].asUUID(); + + + LL_DEBUGS("ExperienceCache") << "Received result for " << public_key + << " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL ; + + processExperience(public_key, row); + } + + LLSD error_ids = getContent()["error_ids"]; + LLSD::array_const_iterator errIt = error_ids.beginArray(); + for( /**/ ; errIt != error_ids.endArray() ; ++errIt ) + { + LLUUID id = errIt->asUUID(); + LLSD exp; + exp[EXPIRES]=DEFAULT_EXPIRATION; + exp[EXPERIENCE_ID] = id; + exp[PROPERTIES]=PROPERTY_INVALID; + exp[MISSING]=true; + exp[QUOTA] = DEFAULT_QUOTA; + + processExperience(id, exp); + LL_WARNS("ExperienceCache") << "LLExperienceResponder::result() error result for " << id << LL_ENDL ; + } + + LL_DEBUGS("ExperienceCache") << sCache.size() << " cached experiences" << LL_ENDL; + } + + /*virtual*/ void httpFailure() + { + LL_WARNS("ExperienceCache") << "Request failed "<<getStatus()<<" "<<getReason()<< LL_ENDL; + // 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(getStatus()); + + + // Add dummy records for all agent IDs in this request + ask_queue_t::const_iterator it = mKeys.begin(); + for ( ; it != mKeys.end(); ++it) + { + + LLSD exp = get(it->first); + //leave the properties alone if we already have a cache entry for this xp + if(exp.isUndefined()) + { + exp[PROPERTIES]=PROPERTY_INVALID; + } + exp[EXPIRES]=retry_timestamp; + exp[EXPERIENCE_ID] = it->first; + exp["key_type"] = it->second; + exp["uuid"] = it->first; + exp["error"] = (LLSD::Integer)getStatus(); + exp[QUOTA] = DEFAULT_QUOTA; + + LLExperienceCache::processExperience(it->first, exp); + } + + } + + // 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) + { + + // Retry-After takes priority + LLSD retry_after = getResponseHeaders()["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 F64(delta_seconds); + } + } + + // If no Retry-After, look for Cache-Control max-age + F64 expires = 0.0; + if (LLExperienceCache::expirationFromCacheControl(getResponseHeaders(), &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 SERVICE_UNAVAILABLE_DELAY; + } + else if (status == 499) + { + // ...we were probably too busy, retry quickly + const F64 BUSY_DELAY = 10.0; // 10 seconds + return BUSY_DELAY; + + } + else + { + // ...other unexpected error + const F64 DEFAULT_DELAY = 3600.0; // 1 hour + return DEFAULT_DELAY; + } + } + + private: + ask_queue_t mKeys; + }; + + void requestExperiences() + { + if(sAskQueue.empty() || sLookupURL.empty()) + return; + + F64 now = LLFrameTimer::getTotalSeconds(); + + const U32 EXP_URL_SEND_THRESHOLD = 3000; + const U32 PAGE_SIZE = EXP_URL_SEND_THRESHOLD/UUID_STR_LENGTH; + + std::ostringstream ostr; + + ask_queue_t keys; + + ostr << sLookupURL << "?page_size=" << PAGE_SIZE; + + + int request_count = 0; + while(!sAskQueue.empty() && request_count < sMaximumLookups) + { + ask_queue_t::iterator it = sAskQueue.begin(); + const LLUUID& key = it->first; + const std::string& key_type = it->second; + + ostr << '&' << key_type << '=' << key.asString() ; + + keys[key]=key_type; + request_count++; + + sPendingQueue[key] = now; + + if(ostr.tellp() > EXP_URL_SEND_THRESHOLD) + { + LL_DEBUGS("ExperienceCache") << "requestExperiences() query: " << ostr.str() << LL_ENDL; + LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys)); + ostr.clear(); + ostr.str(sLookupURL); + ostr << "?page_size=" << PAGE_SIZE; + keys.clear(); + } + sAskQueue.erase(it); + } + + if(ostr.tellp() > sLookupURL.size()) + { + LL_DEBUGS("ExperienceCache") << "requestExperiences() query 2: " << ostr.str() << LL_ENDL; + LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys)); + } + } + + bool isRequestPending(const LLUUID& public_key) + { + bool isPending = false; + const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0; + + pending_queue_t::const_iterator it = sPendingQueue.find(public_key); + + if(it != sPendingQueue.end()) + { + F64 expire_time = LLFrameTimer::getTotalSeconds() - PENDING_TIMEOUT_SECS; + isPending = (it->second > expire_time); + } + + return isPending; + } + + + void setLookupURL( const std::string& lookup_url ) + { + sLookupURL = lookup_url; + if(!sLookupURL.empty()) + { + sLookupURL += "id/"; + } + } + + bool hasLookupURL() + { + return !sLookupURL.empty(); + } + + void idle() + { + + const F32 SECS_BETWEEN_REQUESTS = 0.1f; + if (!sRequestTimer.checkExpirationAndReset(SECS_BETWEEN_REQUESTS)) + { + return; + } + + // Must be large relative to above + const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds + if (sEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT)) + { + eraseExpired(); + } + + + if(!sAskQueue.empty()) + { + requestExperiences(); + } + } + + void erase( const LLUUID& key ) + { + cache_t::iterator it = sCache.find(key); + + if(it != sCache.end()) + { + sCache.erase(it); + } + } + + void eraseExpired() + { + F64 now = LLFrameTimer::getTotalSeconds(); + cache_t::iterator it = sCache.begin(); + while (it != sCache.end()) + { + cache_t::iterator cur = it; + LLSD& exp = cur->second; + ++it; + + if(exp.has(EXPIRES) && exp[EXPIRES].asReal() < now) + { + if(!exp.has(EXPERIENCE_ID)) + { + LL_WARNS("ExperienceCache") << "Removing experience with no id " << LL_ENDL ; + sCache.erase(cur); + } + else + { + LLUUID id = exp[EXPERIENCE_ID].asUUID(); + LLUUID private_key = exp.has(LLExperienceCache::PRIVATE_KEY) ? exp[LLExperienceCache::PRIVATE_KEY].asUUID():LLUUID::null; + if(private_key.notNull() || !exp.has("DoesNotExist")) + { + fetch(id, true); + } + else + { + LL_WARNS("ExperienceCache") << "Removing invalid experience " << id << LL_ENDL ; + sCache.erase(cur); + } + } + } + } + } + + + bool fetch( const LLUUID& key, bool refresh/* = true*/ ) + { + if(!key.isNull() && !isRequestPending(key) && (refresh || sCache.find(key)==sCache.end())) + { + LL_DEBUGS("ExperienceCache") << " queue request for " << EXPERIENCE_ID << " " << key << LL_ENDL ; + sAskQueue[key]=EXPERIENCE_ID; + + return true; + } + return false; + } + + void insert(const LLSD& experience_data ) + { + if(experience_data.has(EXPERIENCE_ID)) + { + processExperience(experience_data[EXPERIENCE_ID].asUUID(), experience_data); + } + else + { + LL_WARNS("ExperienceCache") << ": Ignoring cache insert of experience which is missing " << EXPERIENCE_ID << LL_ENDL; + } + } + static LLSD empty; + const LLSD& get(const LLUUID& key) + { + if(key.isNull()) return empty; + cache_t::const_iterator it = sCache.find(key); + + if (it != sCache.end()) + { + return it->second; + } + + fetch(key); + + return empty; + } + void get( const LLUUID& key, callback_slot_t slot ) + { + if(key.isNull()) return; + + cache_t::const_iterator it = sCache.find(key); + if (it != sCache.end()) + { + // ...name already exists in cache, fire callback now + callback_signal_t signal; + signal.connect(slot); + + signal(it->second); + return; + } + + fetch(key); + + // always store additional callback, even if request is pending + signal_map_t::iterator sig_it = sSignalMap.find(key); + if (sig_it == sSignalMap.end()) + { + // ...new callback for this id + callback_signal_t* signal = new callback_signal_t(); + signal->connect(slot); + sSignalMap[key] = signal; + } + else + { + // ...existing callback, bind additional slot + callback_signal_t* signal = sig_it->second; + signal->connect(slot); + } + } + +} + + +void LLExperienceCache::mapKeys( const LLSD& legacyKeys ) +{ + LLSD::array_const_iterator exp = legacyKeys.beginArray(); + for(/**/ ; exp != legacyKeys.endArray() ; ++exp) + { + if(exp->has(LLExperienceCache::EXPERIENCE_ID) && exp->has(LLExperienceCache::PRIVATE_KEY)) + { + privateToPublicKeyMap[(*exp)[LLExperienceCache::PRIVATE_KEY].asUUID()]=(*exp)[LLExperienceCache::EXPERIENCE_ID].asUUID(); + } + } +} + + +LLUUID LLExperienceCache::getExperienceId(const LLUUID& private_key, bool null_if_not_found) +{ + if (private_key.isNull()) + return LLUUID::null; + + KeyMap::const_iterator it=privateToPublicKeyMap.find(private_key); + if(it == privateToPublicKeyMap.end()) + { + if(null_if_not_found) + { + return LLUUID::null; + } + return private_key; + } + LL_WARNS("LLExperience") << "converted private key " << private_key << " to experience_id " << it->second << LL_ENDL; + return it->second; +} diff --git a/indra/llmessage/llexperiencecache.h b/indra/llmessage/llexperiencecache.h new file mode 100644 index 0000000000..e669ee888e --- /dev/null +++ b/indra/llmessage/llexperiencecache.h @@ -0,0 +1,104 @@ +/** + * @file llexperiencecache.h + * @brief Caches information relating to experience keys + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 LL_LLEXPERIENCECACHE_H +#define LL_LLEXPERIENCECACHE_H + +#include "linden_common.h" +#include <boost/signals2.hpp> + +class LLSD; +class LLUUID; + + + +namespace LLExperienceCache +{ + const std::string PRIVATE_KEY = "private_id"; + const std::string MISSING = "DoesNotExist"; + + const std::string AGENT_ID = "agent_id"; + const std::string GROUP_ID = "group_id"; + const std::string EXPERIENCE_ID = "public_id"; + const std::string NAME = "name"; + const std::string PROPERTIES = "properties"; + const std::string EXPIRES = "expiration"; + const std::string DESCRIPTION = "description"; + const std::string QUOTA = "quota"; + const std::string MATURITY = "maturity"; + const std::string METADATA = "extended_metadata"; + const std::string SLURL = "slurl"; + + + // should be in sync with experience-api/experiences/models.py + const int PROPERTY_INVALID = 1 << 0; + const int PROPERTY_PRIVILEGED = 1 << 3; + const int PROPERTY_GRID = 1 << 4; + const int PROPERTY_PRIVATE = 1 << 5; + const int PROPERTY_DISABLED = 1 << 6; + const int PROPERTY_SUSPENDED = 1 << 7; + + + // default values + const static F64 DEFAULT_EXPIRATION = 600.0; + const static S32 DEFAULT_QUOTA = 128; // this is megabytes + + // Callback types for get() below + typedef boost::signals2::signal<void (const LLSD& experience)> + callback_signal_t; + typedef callback_signal_t::slot_type callback_slot_t; + typedef std::map<LLUUID, LLSD> cache_t; + + + void setLookupURL(const std::string& lookup_url); + bool hasLookupURL(); + + void setMaximumLookups(int maximumLookups); + + void idle(); + void exportFile(std::ostream& ostr); + void importFile(std::istream& istr); + void initClass(); + void bootstrap(const LLSD& legacyKeys, int initialExpiration); + + void erase(const LLUUID& key); + bool fetch(const LLUUID& key, bool refresh=false); + void insert(const LLSD& experience_data); + const LLSD& get(const LLUUID& key); + + // If name information is in cache, callback will be called immediately. + void get(const LLUUID& key, callback_slot_t slot); + + const cache_t& getCached(); + + // maps an experience private key to the experience id + LLUUID getExperienceId(const LLUUID& private_key, bool null_if_not_found=false); + +}; + +#endif // LL_LLEXPERIENCECACHE_H diff --git a/indra/llmessage/llregionflags.h b/indra/llmessage/llregionflags.h index 40d7b04a90..eb0c4e6d1e 100755 --- a/indra/llmessage/llregionflags.h +++ b/indra/llmessage/llregionflags.h @@ -148,19 +148,20 @@ const U32 ESTATE_ACCESS_ALL = ESTATE_ACCESS_ALLOWED_AGENTS | ESTATE_ACCESS_BANNED_AGENTS | ESTATE_ACCESS_MANAGERS; -// for EstateOwnerRequest, estateaccessdelta message -const U32 ESTATE_ACCESS_APPLY_TO_ALL_ESTATES = 1 << 0; -const U32 ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES = 1 << 1; - -const U32 ESTATE_ACCESS_ALLOWED_AGENT_ADD = 1 << 2; -const U32 ESTATE_ACCESS_ALLOWED_AGENT_REMOVE = 1 << 3; -const U32 ESTATE_ACCESS_ALLOWED_GROUP_ADD = 1 << 4; -const U32 ESTATE_ACCESS_ALLOWED_GROUP_REMOVE = 1 << 5; -const U32 ESTATE_ACCESS_BANNED_AGENT_ADD = 1 << 6; -const U32 ESTATE_ACCESS_BANNED_AGENT_REMOVE = 1 << 7; -const U32 ESTATE_ACCESS_MANAGER_ADD = 1 << 8; -const U32 ESTATE_ACCESS_MANAGER_REMOVE = 1 << 9; -const U32 ESTATE_ACCESS_NO_REPLY = 1 << 10; +// for EstateOwnerRequest, estateaccessdelta, estateexperiencedelta messages +const U32 ESTATE_ACCESS_APPLY_TO_ALL_ESTATES = 1U << 0; +const U32 ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES = 1U << 1; + +const U32 ESTATE_ACCESS_ALLOWED_AGENT_ADD = 1U << 2; +const U32 ESTATE_ACCESS_ALLOWED_AGENT_REMOVE = 1U << 3; +const U32 ESTATE_ACCESS_ALLOWED_GROUP_ADD = 1U << 4; +const U32 ESTATE_ACCESS_ALLOWED_GROUP_REMOVE = 1U << 5; +const U32 ESTATE_ACCESS_BANNED_AGENT_ADD = 1U << 6; +const U32 ESTATE_ACCESS_BANNED_AGENT_REMOVE = 1U << 7; +const U32 ESTATE_ACCESS_MANAGER_ADD = 1U << 8; +const U32 ESTATE_ACCESS_MANAGER_REMOVE = 1U << 9; +const U32 ESTATE_ACCESS_NO_REPLY = 1U << 10; +const U32 ESTATE_ACCESS_FAILED_BAN_ESTATE_MANAGER = 1U << 11; const S32 ESTATE_MAX_MANAGERS = 10; const S32 ESTATE_MAX_ACCESS_IDS = 500; // max for access, banned @@ -171,6 +172,26 @@ const U32 SWD_OTHERS_LAND_ONLY = (1 << 0); const U32 SWD_ALWAYS_RETURN_OBJECTS = (1 << 1); const U32 SWD_SCRIPTED_ONLY = (1 << 2); +// Controls experience key validity in the estate +const U32 EXPERIENCE_KEY_TYPE_NONE = 0; +const U32 EXPERIENCE_KEY_TYPE_BLOCKED = 1; +const U32 EXPERIENCE_KEY_TYPE_ALLOWED = 2; +const U32 EXPERIENCE_KEY_TYPE_TRUSTED = 3; + +const U32 EXPERIENCE_KEY_TYPE_FIRST = EXPERIENCE_KEY_TYPE_BLOCKED; +const U32 EXPERIENCE_KEY_TYPE_LAST = EXPERIENCE_KEY_TYPE_TRUSTED; + +// +const U32 ESTATE_EXPERIENCE_TRUSTED_ADD = 1U << 2; +const U32 ESTATE_EXPERIENCE_TRUSTED_REMOVE = 1U << 3; +const U32 ESTATE_EXPERIENCE_ALLOWED_ADD = 1U << 4; +const U32 ESTATE_EXPERIENCE_ALLOWED_REMOVE = 1U << 5; +const U32 ESTATE_EXPERIENCE_BLOCKED_ADD = 1U << 6; +const U32 ESTATE_EXPERIENCE_BLOCKED_REMOVE = 1U << 7; + +const S32 ESTATE_MAX_EXPERIENCE_IDS = 8; + + #endif diff --git a/indra/llmessage/message_prehash.cpp b/indra/llmessage/message_prehash.cpp index 39cfb6019e..e0f69dc2d3 100755 --- a/indra/llmessage/message_prehash.cpp +++ b/indra/llmessage/message_prehash.cpp @@ -1383,3 +1383,5 @@ char const* const _PREHASH_GroupAVSounds = LLMessageStringTable::getInstance()-> char const* const _PREHASH_AppearanceData = LLMessageStringTable::getInstance()->getString("AppearanceData"); char const* const _PREHASH_AppearanceVersion = LLMessageStringTable::getInstance()->getString("AppearanceVersion"); char const* const _PREHASH_CofVersion = LLMessageStringTable::getInstance()->getString("CofVersion"); +char const* const _PREHASH_Experience = LLMessageStringTable::getInstance()->getString("Experience"); +char const* const _PREHASH_ExperienceID = LLMessageStringTable::getInstance()->getString("ExperienceID"); diff --git a/indra/llmessage/message_prehash.h b/indra/llmessage/message_prehash.h index 573e10dc0b..fb8dcc17fb 100755 --- a/indra/llmessage/message_prehash.h +++ b/indra/llmessage/message_prehash.h @@ -1383,4 +1383,6 @@ extern char const* const _PREHASH_GroupAVSounds; extern char const* const _PREHASH_AppearanceData; extern char const* const _PREHASH_AppearanceVersion; extern char const* const _PREHASH_CofVersion; +extern char const* const _PREHASH_Experience; +extern char const* const _PREHASH_ExperienceID; #endif diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index de14391d1f..7e235997d8 100755 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -678,7 +678,7 @@ void LLNotification::respond(const LLSD& response) // and then call it functor(asLLSD(), response); } - else + else if (mCombinedNotifications.empty()) { // no registered responder return; @@ -700,6 +700,14 @@ void LLNotification::respond(const LLSD& response) } } + for (std::vector<LLNotificationPtr>::const_iterator it = mCombinedNotifications.begin(); it != mCombinedNotifications.end(); ++it) + { + if ((*it)) + { + (*it)->respond(response); + } + } + update(); } @@ -1322,6 +1330,28 @@ bool LLNotifications::failedUniquenessTest(const LLSD& payload) } } break; + case LLNotification::COMBINE_WITH_NEW: + // Add to the existing unique notification with the data from this particular instance... + // This guarantees that duplicate notifications will be collapsed to the one + // most recently triggered + for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); + existing_it != mUniqueNotifications.end(); + ++existing_it) + { + LLNotificationPtr existing_notification = existing_it->second; + if (pNotif != existing_notification + && pNotif->isEquivalentTo(existing_notification)) + { + // copy the notifications from the newest instance into the oldest + existing_notification->mCombinedNotifications.push_back(pNotif); + existing_notification->mCombinedNotifications.insert(existing_notification->mCombinedNotifications.end(), + pNotif->mCombinedNotifications.begin(), pNotif->mCombinedNotifications.end()); + + // pop up again + existing_notification->update(); + } + } + break; case LLNotification::KEEP_OLD: break; case LLNotification::CANCEL_OLD: diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 3cf432f330..0d673d178b 100755 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -414,6 +414,9 @@ private: using the same mechanism. */ bool mTemporaryResponder; + + // keep track of other notifications combined with COMBINE_WITH_NEW + std::vector<LLNotificationPtr> mCombinedNotifications; void init(const std::string& template_name, const LLSD& form_elements); @@ -560,6 +563,7 @@ public: typedef enum e_combine_behavior { REPLACE_WITH_NEW, + COMBINE_WITH_NEW, KEEP_OLD, CANCEL_OLD diff --git a/indra/llui/llnotificationtemplate.h b/indra/llui/llnotificationtemplate.h index 0315ddbea8..c23fc53763 100755 --- a/indra/llui/llnotificationtemplate.h +++ b/indra/llui/llnotificationtemplate.h @@ -43,6 +43,7 @@ struct LLNotificationTemplate static void declareValues() { declare("replace_with_new", LLNotification::REPLACE_WITH_NEW); + declare("combine_with_new", LLNotification::COMBINE_WITH_NEW); declare("keep_old", LLNotification::KEEP_OLD); declare("cancel_old", LLNotification::CANCEL_OLD); } diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index 6f858cdeb3..4b08798892 100755 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -2094,3 +2094,8 @@ void LLTabContainer::commitHoveredButton(S32 x, S32 y) } } } + +S32 LLTabContainer::getTotalTabWidth() const +{ + return mTotalTabWidth; +} diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h index 57862fc626..057809dc42 100755 --- a/indra/llui/lltabcontainer.h +++ b/indra/llui/lltabcontainer.h @@ -182,7 +182,8 @@ public: LLPanel* getPanelByIndex(S32 index); S32 getIndexForPanel(LLPanel* panel); S32 getPanelIndexByTitle(const std::string& title); - LLPanel* getPanelByName(const std::string& name); + LLPanel* getPanelByName(const std::string& name); + S32 getTotalTabWidth() const; void setCurrentTabName(const std::string& name); void selectFirstTab(); @@ -287,7 +288,7 @@ private: S32 mMaxTabWidth; S32 mTotalTabWidth; - S32 mTabHeight; + S32 mTabHeight; // Padding under the text labels of tab buttons S32 mLabelPadBottom; diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 6f3122e7a1..67da98b95a 100755 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -37,6 +37,7 @@ #include "lltrans.h" #include "lluicolortable.h" #include "message.h" +#include "llexperiencecache.h" #define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" @@ -1201,3 +1202,51 @@ std::string LLUrlEntryIcon::getIcon(const std::string &url) LLStringUtil::trim(mIcon); return mIcon; } + +LLUrlEntryExperienceProfile::LLUrlEntryExperienceProfile() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/experience/[\\da-f-]+/\\w+\\S*", + boost::regex::perl|boost::regex::icase); + mIcon = "Generic_Experience"; + mMenuName = "menu_url_experience.xml"; +} + +std::string LLUrlEntryExperienceProfile::getLabel( const std::string &url, const LLUrlLabelCallback &cb ) +{ + if (!gCacheName) + { + // probably at the login screen, use short string for layout + return LLTrans::getString("LoadingData"); + } + + std::string experience_id_string = getIDStringFromUrl(url); + if (experience_id_string.empty()) + { + // something went wrong, just give raw url + return unescapeUrl(url); + } + + LLUUID experience_id(experience_id_string); + if (experience_id.isNull()) + { + return LLTrans::getString("ExperienceNameNull"); + } + + const LLSD& experience_details = LLExperienceCache::get(experience_id); + if(!experience_details.isUndefined()) + { + return experience_details[LLExperienceCache::NAME].asString(); + } + + addObserver(experience_id_string, url, cb); + LLExperienceCache::get(experience_id, boost::bind(&LLUrlEntryExperienceProfile::onExperienceDetails, this, _1)); + return LLTrans::getString("LoadingData"); + +} + +void LLUrlEntryExperienceProfile::onExperienceDetails( const LLSD& experience_details ) +{ + callObservers(experience_details[LLExperienceCache::EXPERIENCE_ID].asString(), experience_details[LLExperienceCache::NAME].asString(), LLStringUtil::null); +} + + diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index d4684e2e1e..85fef2c18b 100755 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -259,6 +259,20 @@ private: }; /// +/// LLUrlEntryExperienceProfile Describes a Second Life experience profile Url, e.g., +/// secondlife:///app/experience/0e346d8b-4433-4d66-a6b0-fd37083abc4c/profile +/// that displays the experience name +class LLUrlEntryExperienceProfile : public LLUrlEntryBase +{ +public: + LLUrlEntryExperienceProfile(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); +private: + void onExperienceDetails(const LLSD& experience_details); +}; + + +/// /// LLUrlEntryGroup Describes a Second Life group Url, e.g., /// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about /// diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index bccc646821..4590f0ec28 100755 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -61,6 +61,7 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntryPlace()); registerUrl(new LLUrlEntryInventory()); registerUrl(new LLUrlEntryObjectIM()); + registerUrl(new LLUrlEntryExperienceProfile()); //LLUrlEntrySL and LLUrlEntrySLLabel have more common pattern, //so it should be registered in the end of list registerUrl(new LLUrlEntrySL()); diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index c3f0e92cb0..15f2354552 100755 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -32,9 +32,23 @@ #include "lltut.h" #include "../lluicolortable.h" #include "../llrender/lluiimage.h" +#include "../llmessage/llexperiencecache.h" #include <boost/regex.hpp> + +namespace LLExperienceCache +{ + const LLSD& get( const LLUUID& key) + { + static LLSD boo; + return boo; + } + + void get( const LLUUID& key, callback_slot_t slot ){} + +} + typedef std::map<std::string, LLControlGroup*> settings_map_t; settings_map_t LLUI::sSettingGroups; diff --git a/indra/lscript/lscript_compile/indra.l b/indra/lscript/lscript_compile/indra.l index 7772c95609..aad7c317d2 100755 --- a/indra/lscript/lscript_compile/indra.l +++ b/indra/lscript/lscript_compile/indra.l @@ -737,6 +737,23 @@ int yyerror(const char *fmt, ...); "STATUS_INTERNAL_ERROR" { count(); yylval.ival = LSL_STATUS_INTERNAL_ERROR; return(INTEGER_CONSTANT); } "STATUS_WHITELIST_FAILED" { count(); yylval.ival = LSL_STATUS_WHITELIST_FAILED; return(INTEGER_CONSTANT); } +"XP_ERROR_NONE" { const char* sval= "no error"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_THROTTLED" { const char* sval= "exceeded throttle"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_EXPERIENCES_DISABLED" { const char* sval= "experiences are disabled"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_INVALID_PARAMETERS" { const char* sval= "invalid parameters"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_NOT_PERMITTED" { const char* sval= "operation not permitted"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_NO_EXPERIENCE" { const char* sval= "script not associated with an experience";yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_NOT_FOUND" { const char* sval= "not found"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_INVALID_EXPERIENCE" { const char* sval= "invalid experience"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_EXPERIENCE_DISABLED" { const char* sval= "experience is disabled"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_EXPERIENCE_SUSPENDED" { const char* sval= "experience is suspended"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_UNKNOWN_ERROR" { const char* sval= "unknown error"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_QUOTA_EXCEEDED" { const char* sval= "experience data quota exceeded"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_STORE_DISABLED" { const char* sval= "key-value store is disabled"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_STORAGE_EXCEPTION" { const char* sval= "key-value store communication failed"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_KEY_NOT_FOUND" { const char* sval= "key doesn't exist"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } +"XP_ERROR_RETRY_UPDATE" { const char* sval= "retry update"; yylval.sval = new char[strlen(sval)+1]; strcpy(yylval.sval, sval); return(STRING_CONSTANT); } + {L}({L}|{N})* { count(); yylval.sval = new char[strlen(yytext) + 1]; strcpy(yylval.sval, yytext); return(IDENTIFIER); } {N}+{E} { count(); yylval.fval = (F32)atof(yytext); return(FP_CONSTANT); } diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 3e296c8d9f..752508ea5e 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -189,6 +189,8 @@ set(viewer_SOURCE_FILES lleventnotifier.cpp lleventpoll.cpp llexpandabletextbox.cpp + llexperienceassociationresponder.cpp + llexperiencelog.cpp llexternaleditor.cpp llface.cpp llfacebookconnect.cpp @@ -230,6 +232,9 @@ set(viewer_SOURCE_FILES llfloatereditwater.cpp llfloaterenvironmentsettings.cpp llfloaterevent.cpp + llfloaterexperiencepicker.cpp + llfloaterexperienceprofile.cpp + llfloaterexperiences.cpp llfloaterfacebook.cpp llfloaterflickr.cpp llfloaterfonttest.cpp @@ -408,11 +413,16 @@ set(viewer_SOURCE_FILES llpanelclassified.cpp llpanelcontents.cpp llpaneleditwearable.cpp + llpanelexperiencelisteditor.cpp + llpanelexperiencelog.cpp + llpanelexperiencepicker.cpp + llpanelexperiences.cpp llpanelface.cpp llpanelgenerictip.cpp llpanelgroup.cpp llpanelgroupbulk.cpp llpanelgroupbulkban.cpp + llpanelgroupexperiences.cpp llpanelgroupgeneral.cpp llpanelgroupinvite.cpp llpanelgrouplandmoney.cpp @@ -793,6 +803,8 @@ set(viewer_HEADER_FILES lleventnotifier.h lleventpoll.h llexpandabletextbox.h + llexperienceassociationresponder.h + llexperiencelog.h llexternaleditor.h llface.h llfacebookconnect.h @@ -834,6 +846,9 @@ set(viewer_HEADER_FILES llfloatereditwater.h llfloaterenvironmentsettings.h llfloaterevent.h + llfloaterexperiencepicker.h + llfloaterexperienceprofile.h + llfloaterexperiences.h llfloaterfacebook.h llfloaterflickr.h llfloaterfonttest.h @@ -1005,12 +1020,17 @@ set(viewer_HEADER_FILES llpanelclassified.h llpanelcontents.h llpaneleditwearable.h + llpanelexperiencelisteditor.h + llpanelexperiencelog.h + llpanelexperiencepicker.h + llpanelexperiences.h llpanelface.h llpanelgenerictip.h llpanelgroup.h llpanelgroupbulk.h llpanelgroupbulkimpl.h llpanelgroupbulkban.h + llpanelgroupexperiences.h llpanelgroupgeneral.h llpanelgroupinvite.h llpanelgrouplandmoney.h diff --git a/indra/newview/app_settings/keywords.ini b/indra/newview/app_settings/keywords.ini index 17c70ef1c5..a070c8bcd0 100755 --- a/indra/newview/app_settings/keywords.ini +++ b/indra/newview/app_settings/keywords.ini @@ -52,6 +52,7 @@ remote_data remote_data(integer event_type, key channel, key message_id, str http_response http_response(key request_id, integer status, list metadata, string body):Triggered when task receives a response to one of its llHTTPRequests http_request http_request(key id, string method, string body):Triggered when task receives an http request against a public URL + # integer constants [word .1, .1, .5] TRUE Integer constant for Boolean operations @@ -714,6 +715,25 @@ TEXTURE_TRANSPARENT UUID for the "White - Transparent" texture URL_REQUEST_GRANTED Used with http_request when a public URL is successfully granted URL_REQUEST_DENIED Used with http_request when a public URL is not available +XP_ERROR_NONE No error was detected +XP_ERROR_THROTTLED The call failed due to too many recent calls. +XP_ERROR_EXPERIENCES_DISABLED The region currently has experiences disabled. +XP_ERROR_INVALID_PARAMETERS One of the string arguments was too big to fit in the key-value store. +XP_ERROR_NOT_PERMITTED This experience is not allowed to run on the current region. +XP_ERROR_NO_EXPERIENCE This script is not associated with an experience. +XP_ERROR_NOT_FOUND The sim was unable to verify the validity of the experience. Retrying after a short wait is advised. +XP_ERROR_INVALID_EXPERIENCE The script is associated with an experience that no longer exists. +XP_ERROR_EXPERIENCE_DISABLED The experience owner has temporarily disabled the experience. +XP_ERROR_EXPERIENCE_SUSPENDED The experience has been suspended by Linden Customer Support. +XP_ERROR_QUOTA_EXCEEDED An attempted write data to the key-value store failed due to the data quota being met. +XP_ERROR_STORE_DISABLED The key-value store is currently disabled on this region. +XP_ERROR_STORAGE_EXCEPTION Unable to communicate with the key-value store. +XP_ERROR_KEY_NOT_FOUND The requested key does not exist. +XP_ERROR_RETRY_UPDATE A checked update failed due to an out of date request. +XP_ERROR_MATURITY_EXCEEDED The request failed due to agent content preferences. +XP_ERROR_UNKNOWN_ERROR Other unknown error. + + # float constants [word .3, .1, .5] PI 3.1415926535897932384626433832795 diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 24150daea4..cd9e5da7a7 100755 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -105,6 +105,7 @@ // Linden library includes #include "llavatarnamecache.h" #include "lldiriterator.h" +#include "llexperiencecache.h" #include "llimagej2c.h" #include "llmemory.h" #include "llprimitive.h" @@ -4666,7 +4667,7 @@ void LLAppViewer::loadNameCache() } void LLAppViewer::saveNameCache() - { +{ // display names cache std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml"); @@ -4674,7 +4675,7 @@ void LLAppViewer::saveNameCache() if(name_cache_stream.is_open()) { LLAvatarNameCache::exportFile(name_cache_stream); -} + } if (!gCacheName) return; @@ -4687,6 +4688,32 @@ void LLAppViewer::saveNameCache() } } + +void LLAppViewer::saveExperienceCache() +{ + std::string filename = + gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "experience_cache.xml"); + LL_INFOS("ExperienceCache") << "Saving " << filename << LL_ENDL; + llofstream cache_stream(filename); + if(cache_stream.is_open()) + { + LLExperienceCache::exportFile(cache_stream); + } +} + +void LLAppViewer::loadExperienceCache() +{ + std::string filename = + gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "experience_cache.xml"); + LL_INFOS("ExperienceCache") << "Loading " << filename << LL_ENDL; + llifstream cache_stream(filename); + if(cache_stream.is_open()) + { + LLExperienceCache::importFile(cache_stream); + } +} + + /*! @brief This class is an LLFrameTimer that can be created with an elapsed time that starts counting up from the given value rather than 0.0. @@ -4880,7 +4907,7 @@ void LLAppViewer::idle() // floating throughout the various object lists. // idleNameCache(); - + idleExperienceCache(); idleNetwork(); @@ -5307,6 +5334,22 @@ void LLAppViewer::idleNameCache() LLAvatarNameCache::idle(); } +void LLAppViewer::idleExperienceCache() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (!region) return; + + std::string lookup_url=region->getCapability("GetExperienceInfo"); + if(!lookup_url.empty() && *lookup_url.rbegin() != '/') + { + lookup_url += '/'; + } + + LLExperienceCache::setLookupURL(lookup_url); + + LLExperienceCache::idle(); +} + // // Handle messages, and all message related stuff // @@ -5469,6 +5512,7 @@ void LLAppViewer::disconnectViewer() } saveNameCache(); + saveExperienceCache(); // close inventory interface, close all windows LLFloaterInventory::cleanup(); diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index e0f3f326c7..95b31cb94e 100755 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -122,6 +122,9 @@ public: void loadNameCache(); void saveNameCache(); + void loadExperienceCache(); + void saveExperienceCache(); + void removeMarkerFiles(); void removeDumpDir(); @@ -230,6 +233,7 @@ private: void idle(); void idleShutdown(); // update avatar SLID and display name caches + void idleExperienceCache(); void idleNameCache(); void idleNetwork(); diff --git a/indra/newview/llassetuploadqueue.cpp b/indra/newview/llassetuploadqueue.cpp index 8833c57948..359ee1e221 100755 --- a/indra/newview/llassetuploadqueue.cpp +++ b/indra/newview/llassetuploadqueue.cpp @@ -168,6 +168,7 @@ void LLAssetUploadQueue::request(LLAssetUploadQueueSupplier** supplier) body["item_id"] = data.mItemId; body["is_script_running"] = data.mIsRunning; body["target"] = data.mIsTargetMono? "mono" : "lsl2"; + body["experience"] = data.mExperienceId; std::string url = ""; LLViewerObject* object = gObjectList.findObject(data.mTaskId); @@ -191,7 +192,8 @@ void LLAssetUploadQueue::queue(const std::string& filename, const LLUUID& queue_id, U8* script_data, U32 data_size, - std::string script_name) + std::string script_name, + const LLUUID& experience_id) { UploadData data; data.mTaskId = task_id; @@ -203,6 +205,7 @@ void LLAssetUploadQueue::queue(const std::string& filename, data.mData = script_data; data.mDataSize = data_size; data.mScriptName = script_name; + data.mExperienceId = experience_id; mQueue.push_back(data); diff --git a/indra/newview/llassetuploadqueue.h b/indra/newview/llassetuploadqueue.h index 434f3e5c03..2ceee8f700 100755 --- a/indra/newview/llassetuploadqueue.h +++ b/indra/newview/llassetuploadqueue.h @@ -50,7 +50,8 @@ public: const LLUUID& queue_id, U8* data, U32 data_size, - std::string script_name); + std::string script_name, + const LLUUID& experience_id); bool isEmpty() const {return mQueue.empty();} @@ -69,6 +70,7 @@ private: U8* mData; U32 mDataSize; std::string mScriptName; + LLUUID mExperienceId; }; // Ownership of mSupplier passed to currently waiting responder diff --git a/indra/newview/llcompilequeue.cpp b/indra/newview/llcompilequeue.cpp index b0916d769a..d9fd4509a5 100755 --- a/indra/newview/llcompilequeue.cpp +++ b/indra/newview/llcompilequeue.cpp @@ -59,6 +59,8 @@ #include "lltrans.h" #include "llselectmgr.h" +#include "llexperienceassociationresponder.h" +#include "llexperiencecache.h" // *TODO: This should be separated into the script queue, and the floater views of that queue. // There should only be one floater class that can view any queue type @@ -70,11 +72,13 @@ struct LLScriptQueueData { LLUUID mQueueID; - std::string mScriptName; LLUUID mTaskId; - LLUUID mItemId; - LLScriptQueueData(const LLUUID& q_id, const std::string& name, const LLUUID& task_id, const LLUUID& item_id) : - mQueueID(q_id), mScriptName(name), mTaskId(task_id), mItemId(item_id) {} + LLPointer<LLInventoryItem> mItem; + LLHost mHost; + LLUUID mExperienceId; + std::string mExperiencename; + LLScriptQueueData(const LLUUID& q_id, const LLUUID& task_id, LLInventoryItem* item) : + mQueueID(q_id), mTaskId(task_id), mItem(new LLInventoryItem(item)) {} }; @@ -88,6 +92,7 @@ LLFloaterScriptQueue::LLFloaterScriptQueue(const LLSD& key) : mDone(false), mMono(false) { + } // Destroys the object @@ -167,7 +172,7 @@ BOOL LLFloaterScriptQueue::start() getChild<LLScrollListCtrl>("queue output")->addSimpleElement(buffer, ADD_BOTTOM); - return nextObject(); + return startQueue(); } BOOL LLFloaterScriptQueue::isDone() const @@ -232,6 +237,40 @@ BOOL LLFloaterScriptQueue::popNext() return rv; } +BOOL LLFloaterScriptQueue::startQueue() +{ + return nextObject(); +} + +class CompileQueueExperienceResponder : public LLHTTPClient::Responder +{ +public: + CompileQueueExperienceResponder(const LLUUID& parent):mParent(parent) + { + } + + LLUUID mParent; + + /*virtual*/ void httpSuccess() + { + sendResult(getContent()); + } + /*virtual*/ void httpFailure() + { + sendResult(LLSD()); + } + void sendResult(const LLSD& content) + { + LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance<LLFloaterCompileQueue>("compile_queue", mParent); + if(!queue) + return; + + queue->experienceIdsReceived(content["experience_ids"]); + } +}; + + + ///---------------------------------------------------------------------------- /// Class LLFloaterCompileQueue @@ -284,6 +323,21 @@ LLFloaterCompileQueue::~LLFloaterCompileQueue() { } +void LLFloaterCompileQueue::experienceIdsReceived( const LLSD& content ) +{ + for(LLSD::array_const_iterator it = content.beginArray(); it != content.endArray(); ++it) + { + mExperienceIds.insert(it->asUUID()); + } + nextObject(); +} + +BOOL LLFloaterCompileQueue::hasExperience( const LLUUID& id ) const +{ + return mExperienceIds.find(id) != mExperienceIds.end(); +} + + void LLFloaterCompileQueue::handleInventory(LLViewerObject *viewer_object, LLInventoryObject::object_list_t* inv) { @@ -324,25 +378,52 @@ void LLFloaterCompileQueue::handleInventory(LLViewerObject *viewer_object, { LLInventoryItem *itemp = iter->second; LLScriptQueueData* datap = new LLScriptQueueData(getKey().asUUID(), - itemp->getName(), - viewer_object->getID(), - itemp->getUUID()); - - //LL_INFOS() << "ITEM NAME 2: " << names.get(i) << LL_ENDL; - gAssetStorage->getInvItemAsset(viewer_object->getRegion()->getHost(), - gAgent.getID(), - gAgent.getSessionID(), - itemp->getPermissions().getOwner(), - viewer_object->getID(), - itemp->getUUID(), - itemp->getAssetUUID(), - itemp->getType(), - LLFloaterCompileQueue::scriptArrived, - (void*)datap); + viewer_object->getID(), itemp); + + ExperienceAssociationResponder::fetchAssociatedExperience(itemp->getParentUUID(), itemp->getUUID(), + boost::bind(LLFloaterCompileQueue::requestAsset, datap, _1)); + } + } +} + + +void LLFloaterCompileQueue::requestAsset( LLScriptQueueData* datap, const LLSD& experience ) +{ + LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance<LLFloaterCompileQueue>("compile_queue", datap->mQueueID); + if(!queue) + { + delete datap; + return; + } + if(experience.has(LLExperienceCache::EXPERIENCE_ID)) + { + datap->mExperienceId=experience[LLExperienceCache::EXPERIENCE_ID].asUUID(); + if(!queue->hasExperience(datap->mExperienceId)) + { + std::string buffer = LLTrans::getString("CompileNoExperiencePerm", LLSD::emptyMap() + .with("SCRIPT", datap->mItem->getName()) + .with("EXPERIENCE", experience[LLExperienceCache::NAME].asString())); + + queue->getChild<LLScrollListCtrl>("queue output")->addSimpleElement(buffer, ADD_BOTTOM); + queue->removeItemByItemID(datap->mItem->getUUID()); + delete datap; + return; } } + //LL_INFOS() << "ITEM NAME 2: " << names.get(i) << LL_ENDL; + gAssetStorage->getInvItemAsset(datap->mHost, + gAgent.getID(), + gAgent.getSessionID(), + datap->mItem->getPermissions().getOwner(), + datap->mTaskId, + datap->mItem->getUUID(), + datap->mItem->getAssetUUID(), + datap->mItem->getType(), + LLFloaterCompileQueue::scriptArrived, + (void*)datap); } + // This is the callback for when each script arrives // static void LLFloaterCompileQueue::scriptArrived(LLVFS *vfs, const LLUUID& asset_id, @@ -382,12 +463,12 @@ void LLFloaterCompileQueue::scriptArrived(LLVFS *vfs, const LLUUID& asset_id, file.read(script_data, script_size); queue->mUploadQueue->queue(filename, data->mTaskId, - data->mItemId, is_running, queue->mMono, queue->getKey().asUUID(), - script_data, script_size, data->mScriptName); + data->mItem->getUUID(), is_running, queue->mMono, queue->getKey().asUUID(), + script_data, script_size, data->mItem->getName(), data->mExperienceId); } else { - buffer = LLTrans::getString("CompileQueueServiceUnavailable") + (": ") + data->mScriptName; + buffer = LLTrans::getString("CompileQueueServiceUnavailable") + (": ") + data->mItem->getName(); } } } @@ -399,7 +480,7 @@ void LLFloaterCompileQueue::scriptArrived(LLVFS *vfs, const LLUUID& asset_id, args["MESSAGE"] = LLTrans::getString("CompileQueueScriptNotFound"); LLNotificationsUtil::add("SystemMessage", args); - buffer = LLTrans::getString("CompileQueueProblemDownloading") + (": ") + data->mScriptName; + buffer = LLTrans::getString("CompileQueueProblemDownloading") + (": ") + data->mItem->getName(); } else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) { @@ -407,15 +488,15 @@ void LLFloaterCompileQueue::scriptArrived(LLVFS *vfs, const LLUUID& asset_id, args["MESSAGE"] = LLTrans::getString("CompileQueueInsufficientPermDownload"); LLNotificationsUtil::add("SystemMessage", args); - buffer = LLTrans::getString("CompileQueueInsufficientPermFor") + (": ") + data->mScriptName; + buffer = LLTrans::getString("CompileQueueInsufficientPermFor") + (": ") + data->mItem->getName(); } else { - buffer = LLTrans::getString("CompileQueueUnknownFailure") + (" ") + data->mScriptName; + buffer = LLTrans::getString("CompileQueueUnknownFailure") + (" ") + data->mItem->getName(); } LL_WARNS() << "Problem downloading script asset." << LL_ENDL; - if(queue) queue->removeItemByItemID(data->mItemId); + if(queue) queue->removeItemByItemID(data->mItem->getUUID()); } if(queue && (buffer.size() > 0)) { @@ -564,6 +645,23 @@ void LLFloaterCompileQueue::removeItemByItemID(const LLUUID& asset_id) } } +BOOL LLFloaterCompileQueue::startQueue() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string lookup_url=region->getCapability("GetCreatorExperiences"); + if(!lookup_url.empty()) + { + LLHTTPClient::get(lookup_url, new CompileQueueExperienceResponder(getKey().asUUID())); + return TRUE; + } + } + return nextObject(); +} + + + void LLFloaterNotRunQueue::handleInventory(LLViewerObject* viewer_obj, LLInventoryObject::object_list_t* inv) { diff --git a/indra/newview/llcompilequeue.h b/indra/newview/llcompilequeue.h index 28f4625de8..54842bb302 100755 --- a/indra/newview/llcompilequeue.h +++ b/indra/newview/llcompilequeue.h @@ -81,13 +81,15 @@ protected: // returns true if this is done BOOL isDone() const; + virtual BOOL startQueue(); + // go to the next object. If no objects left, it falls out // silently and waits to be killed by the deleteIfDone() callback. BOOL nextObject(); BOOL popNext(); void setStartString(const std::string& s) { mStartString = s; } - + protected: // UI LLScrollListCtrl* mMessages; @@ -131,6 +133,9 @@ public: LLAssetUploadQueue* getUploadQueue() { return mUploadQueue; } + void experienceIdsReceived( const LLSD& content ); + BOOL hasExperience(const LLUUID& id)const; + protected: LLFloaterCompileQueue(const LLSD& key); virtual ~LLFloaterCompileQueue(); @@ -139,16 +144,21 @@ protected: virtual void handleInventory(LLViewerObject* viewer_obj, LLInventoryObject::object_list_t* inv); + static void requestAsset(struct LLScriptQueueData* datap, const LLSD& experience); + + // This is the callback for when each script arrives static void scriptArrived(LLVFS *vfs, const LLUUID& asset_id, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); + virtual BOOL startQueue(); protected: LLViewerInventoryItem::item_array_t mCurrentScripts; private: LLAssetUploadQueue* mUploadQueue; + uuid_list_t mExperienceIds; }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/indra/newview/llexperienceassociationresponder.cpp b/indra/newview/llexperienceassociationresponder.cpp new file mode 100644 index 0000000000..b50c81eedc --- /dev/null +++ b/indra/newview/llexperienceassociationresponder.cpp @@ -0,0 +1,97 @@ +/** + * @file llexperienceassociationresponder.cpp + * @brief llexperienceassociationresponder implementation. This class combines + * a lookup for a script association and an experience details request. The first + * is always async, but the second may be cached locally. + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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 "llexperienceassociationresponder.h" +#include "llexperiencecache.h" +#include "llviewerregion.h" +#include "llagent.h" + +ExperienceAssociationResponder::ExperienceAssociationResponder(ExperienceAssociationResponder::callback_t callback):mCallback(callback) +{ + ref(); +} + +void ExperienceAssociationResponder::fetchAssociatedExperience( const LLUUID& object_id, const LLUUID& item_id, callback_t callback ) +{ + LLSD request; + request["object-id"]=object_id; + request["item-id"]=item_id; + fetchAssociatedExperience(request, callback); +} + +void ExperienceAssociationResponder::fetchAssociatedExperience(LLSD& request, callback_t callback) +{ + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string lookup_url=region->getCapability("GetMetadata"); + if(!lookup_url.empty()) + { + LLSD fields; + fields.append("experience"); + request["fields"] = fields; + LLHTTPClient::post(lookup_url, request, new ExperienceAssociationResponder(callback)); + } + } +} + +void ExperienceAssociationResponder::httpFailure() +{ + LLSD msg; + msg["error"]=(LLSD::Integer)getStatus(); + msg["message"]=getReason(); + LL_INFOS("ExperienceAssociation") << "Failed to look up associated experience: " << getStatus() << ": " << getReason() << LL_ENDL; + + sendResult(msg); + +} +void ExperienceAssociationResponder::httpSuccess() +{ + if(!getContent().has("experience")) + { + + LLSD msg; + msg["message"]="no experience"; + msg["error"]=-1; + sendResult(msg); + return; + } + + LLExperienceCache::get(getContent()["experience"].asUUID(), boost::bind(&ExperienceAssociationResponder::sendResult, this, _1)); + +} + +void ExperienceAssociationResponder::sendResult( const LLSD& experience ) +{ + mCallback(experience); + unref(); +} + + + diff --git a/indra/newview/llexperienceassociationresponder.h b/indra/newview/llexperienceassociationresponder.h new file mode 100644 index 0000000000..2bdc3d251b --- /dev/null +++ b/indra/newview/llexperienceassociationresponder.h @@ -0,0 +1,58 @@ +#include "llhttpclient.h" +#include "llsd.h" +/** + * @file llexperienceassociationresponder.h + * @brief llexperienceassociationresponder and related class definitions + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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 LL_LLEXPERIENCEASSOCIATIONRESPONDER_H +#define LL_LLEXPERIENCEASSOCIATIONRESPONDER_H + +#include "llhttpclient.h" +#include "llsd.h" + +class ExperienceAssociationResponder : public LLHTTPClient::Responder +{ +public: + typedef boost::function<void(const LLSD& experience)> callback_t; + + ExperienceAssociationResponder(callback_t callback); + + /*virtual*/ void httpSuccess(); + /*virtual*/ void httpFailure(); + + static void fetchAssociatedExperience(const LLUUID& object_it, const LLUUID& item_id, callback_t callback); + +private: + static void fetchAssociatedExperience(LLSD& request, callback_t callback); + + void sendResult(const LLSD& experience); + + callback_t mCallback; + +}; + +#endif // LL_LLEXPERIENCEASSOCIATIONRESPONDER_H diff --git a/indra/newview/llexperiencelog.cpp b/indra/newview/llexperiencelog.cpp new file mode 100644 index 0000000000..7b594577db --- /dev/null +++ b/indra/newview/llexperiencelog.cpp @@ -0,0 +1,273 @@ +/** + * @file llexperiencelog.cpp + * @brief llexperiencelog implementation + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, 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 "llexperiencelog.h" + +#include "lldispatcher.h" +#include "llsdserialize.h" +#include "llviewergenericmessage.h" +#include "llnotificationsutil.h" +#include "lltrans.h" +#include "llerror.h" +#include "lldate.h" + + +class LLExperienceLogDispatchHandler : public LLDispatchHandler +{ +public: + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) + { + LLSD message; + + sparam_t::const_iterator it = strings.begin(); + if(it != strings.end()){ + const std::string& llsdRaw = *it++; + std::istringstream llsdData(llsdRaw); + if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length())) + { + LL_WARNS() << "LLExperienceLogDispatchHandler: Attempted to read parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; + } + } + message["public_id"] = invoice; + + // Object Name + if(it != strings.end()) + { + message["ObjectName"] = *it++; + } + + // parcel Name + if(it != strings.end()) + { + message["ParcelName"] = *it++; + } + message["Count"] = 1; + + LLExperienceLog::instance().handleExperienceMessage(message); + return true; + } +}; + +static LLExperienceLogDispatchHandler experience_log_dispatch_handler; + +void LLExperienceLog::handleExperienceMessage(LLSD& message) +{ + time_t now; + time(&now); + char daybuf[16];/* Flawfinder: ignore */ + char time_of_day[16];/* Flawfinder: ignore */ + strftime(daybuf, 16, "%Y-%m-%d", localtime(&now)); + strftime(time_of_day, 16, " %H:%M:%S", localtime(&now)); + message["Time"] = time_of_day; + + std::string day = daybuf; + + if(!mEvents.has(day)) + { + mEvents[day] = LLSD::emptyArray(); + } + LLSD& dayEvents = mEvents[day]; + if(dayEvents.size() > 0) + { + LLSD& last = *(dayEvents.rbeginArray()); + if( last["public_id"].asUUID() == message["public_id"].asUUID() + && last["ObjectName"].asString() == message["ObjectName"].asString() + && last["OwnerID"].asUUID() == message["OwnerID"].asUUID() + && last["ParcelName"].asString() == message["ParcelName"].asString() + && last["Permission"].asInteger() == message["Permission"].asInteger()) + { + last["Count"] = last["Count"].asInteger() + 1; + last["Time"] = time_of_day; + mSignals(last); + return; + } + } + message["Time"] = time_of_day; + mEvents[day].append(message); + mSignals(message); +} + +LLExperienceLog::LLExperienceLog() + : mMaxDays(7) + , mPageSize(25) + , mNotifyNewEvent(false) +{ +} + +void LLExperienceLog::initialize() +{ + loadEvents(); + if(!gGenericDispatcher.isHandlerPresent("ExperienceEvent")) + { + gGenericDispatcher.addHandler("ExperienceEvent", &experience_log_dispatch_handler); + } +} + +std::string LLExperienceLog::getFilename() +{ + return gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "experience_events.xml"); +} + + +std::string LLExperienceLog::getPermissionString( const LLSD& message, const std::string& base ) +{ + std::ostringstream buf; + if(message.has("Permission")) + { + buf << base << message["Permission"].asInteger(); + std::string entry; + if(LLTrans::findString(entry, buf.str())) + { + buf.str(entry); + } + else + { + buf.str(); + } + } + + if(buf.str().empty()) + { + buf << base << "Unknown"; + + buf.str(LLTrans::getString(buf.str(), message)); + } + + return buf.str(); +} + +void LLExperienceLog::notify( LLSD& message ) +{ + message["EventType"] = getPermissionString(message, "ExperiencePermission"); + if(message.has("IsAttachment") && message["IsAttachment"].asBoolean()) + { + LLNotificationsUtil::add("ExperienceEventAttachment", message); + } + else + { + LLNotificationsUtil::add("ExperienceEvent", message); + } + message.erase("EventType"); +} + +void LLExperienceLog::saveEvents() +{ + eraseExpired(); + std::string filename = getFilename(); + LLSD settings = LLSD::emptyMap().with("Events", mEvents); + + settings["MaxDays"] = (int)mMaxDays; + settings["Notify"] = mNotifyNewEvent; + settings["PageSize"] = (int)mPageSize; + + llofstream stream(filename); + LLSDSerialize::toPrettyXML(settings, stream); +} + + +void LLExperienceLog::loadEvents() +{ + LLSD settings = LLSD::emptyMap(); + + std::string filename = getFilename(); + llifstream stream(filename); + LLSDSerialize::fromXMLDocument(settings, stream); + + if(settings.has("MaxDays")) + { + setMaxDays((U32)settings["MaxDays"].asInteger()); + } + if(settings.has("Notify")) + { + setNotifyNewEvent(settings["Notify"].asBoolean()); + } + if(settings.has("PageSize")) + { + setPageSize((U32)settings["PageSize"].asInteger()); + } + mEvents.clear(); + if(mMaxDays > 0 && settings.has("Events")) + { + mEvents = settings["Events"]; + } + + eraseExpired(); +} + +LLExperienceLog::~LLExperienceLog() +{ + saveEvents(); +} + +void LLExperienceLog::eraseExpired() +{ + while(mEvents.size() > mMaxDays && mMaxDays > 0) + { + mEvents.erase(mEvents.beginMap()->first); + } +} + +const LLSD& LLExperienceLog::getEvents() const +{ + return mEvents; +} + +void LLExperienceLog::clear() +{ + mEvents.clear(); +} + +void LLExperienceLog::setMaxDays( U32 val ) +{ + mMaxDays = val; + if(mMaxDays > 0) + { + eraseExpired(); + } +} + +LLExperienceLog::callback_connection_t LLExperienceLog::addUpdateSignal( const callback_slot_t& cb ) +{ + return mSignals.connect(cb); +} + +void LLExperienceLog::setNotifyNewEvent( bool val ) +{ + mNotifyNewEvent = val; + if(!val && mNotifyConnection.connected()) + { + mNotifyConnection.disconnect(); + } + else if( val && !mNotifyConnection.connected()) + { + mNotifyConnection = addUpdateSignal(boost::function<void(LLSD&)>(LLExperienceLog::notify)); + } +} diff --git a/indra/newview/llexperiencelog.h b/indra/newview/llexperiencelog.h new file mode 100644 index 0000000000..1e473e27d5 --- /dev/null +++ b/indra/newview/llexperiencelog.h @@ -0,0 +1,85 @@ +/** + * @file llexperiencelog.h + * @brief llexperiencelog and related class definitions + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, 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 LL_LLEXPERIENCELOG_H +#define LL_LLEXPERIENCELOG_H + +#include "llsingleton.h" + +class LLExperienceLog : public LLSingleton<LLExperienceLog> +{ +public: + typedef boost::signals2::signal<void(LLSD&)> + callback_signal_t; + typedef callback_signal_t::slot_type callback_slot_t; + typedef boost::signals2::connection callback_connection_t; + callback_connection_t addUpdateSignal(const callback_slot_t& cb); + + void initialize(); + + U32 getMaxDays() const { return mMaxDays; } + void setMaxDays(U32 val); + + bool getNotifyNewEvent() const { return mNotifyNewEvent; } + void setNotifyNewEvent(bool val); + + U32 getPageSize() const { return mPageSize; } + void setPageSize(U32 val) { mPageSize = val; } + + const LLSD& getEvents()const; + void clear(); + + virtual ~LLExperienceLog(); + + static void notify(LLSD& message); + static std::string getFilename(); + static std::string getPermissionString(const LLSD& message, const std::string& base); +protected: + LLExperienceLog(); + void handleExperienceMessage(LLSD& message); + + + void loadEvents(); + void saveEvents(); + void eraseExpired(); + + LLSD mEvents; + callback_signal_t mSignals; + callback_connection_t mNotifyConnection; + U32 mMaxDays; + U32 mPageSize; + bool mNotifyNewEvent; + + friend class LLExperienceLogDispatchHandler; + friend class LLSingleton<LLExperienceLog>; +}; + + + + +#endif // LL_LLEXPERIENCELOG_H diff --git a/indra/newview/llfloaterauction.cpp b/indra/newview/llfloaterauction.cpp index 51b59a7a74..b661fed276 100755 --- a/indra/newview/llfloaterauction.cpp +++ b/indra/newview/llfloaterauction.cpp @@ -376,13 +376,16 @@ void LLFloaterAuction::doResetParcel() msg->sendReliable(region->getHost()); // Clear the access lists - clearParcelAccessLists(parcelp, region); + clearParcelAccessList(parcelp, region, AL_ACCESS); + clearParcelAccessList(parcelp, region, AL_BAN); + clearParcelAccessList(parcelp, region, AL_ALLOW_EXPERIENCE); + clearParcelAccessList(parcelp, region, AL_BLOCK_EXPERIENCE); } } -void LLFloaterAuction::clearParcelAccessLists(LLParcel* parcel, LLViewerRegion* region) +void LLFloaterAuction::clearParcelAccessList(LLParcel* parcel, LLViewerRegion* region, U32 list) { if (!region || !parcel) return; @@ -391,15 +394,12 @@ void LLFloaterAuction::clearParcelAccessLists(LLParcel* parcel, LLViewerRegion* LLMessageSystem* msg = gMessageSystem; - // Clear access list - // parcel->mAccessList.clear(); - msg->newMessageFast(_PREHASH_ParcelAccessListUpdate); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); msg->nextBlockFast(_PREHASH_Data); - msg->addU32Fast(_PREHASH_Flags, AL_ACCESS); + msg->addU32Fast(_PREHASH_Flags, list); msg->addS32(_PREHASH_LocalID, parcel->getLocalID() ); msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID); msg->addS32Fast(_PREHASH_SequenceID, 1); // sequence_id @@ -412,27 +412,6 @@ void LLFloaterAuction::clearParcelAccessLists(LLParcel* parcel, LLViewerRegion* msg->addU32Fast(_PREHASH_Flags, 0 ); msg->sendReliable( region->getHost() ); - - // Send message for empty ban list - //parcel->mBanList.clear(); - msg->newMessageFast(_PREHASH_ParcelAccessListUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->nextBlockFast(_PREHASH_Data); - msg->addU32Fast(_PREHASH_Flags, AL_BAN); - msg->addS32(_PREHASH_LocalID, parcel->getLocalID() ); - msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID); - msg->addS32Fast(_PREHASH_SequenceID, 1); // sequence_id - msg->addS32Fast(_PREHASH_Sections, 0); // num_sections - - // pack an empty block since there will be no data - msg->nextBlockFast(_PREHASH_List); - msg->addUUIDFast(_PREHASH_ID, LLUUID::null ); - msg->addS32Fast(_PREHASH_Time, 0 ); - msg->addU32Fast(_PREHASH_Flags, 0 ); - - msg->sendReliable( region->getHost() ); } diff --git a/indra/newview/llfloaterauction.h b/indra/newview/llfloaterauction.h index 9c0c0f7775..c83a11ba8b 100755 --- a/indra/newview/llfloaterauction.h +++ b/indra/newview/llfloaterauction.h @@ -67,7 +67,7 @@ private: void doResetParcel(); void doSellToAnyone(); - void clearParcelAccessLists( LLParcel* parcel, LLViewerRegion* region ); + void clearParcelAccessList( LLParcel* parcel, LLViewerRegion* region, U32 list); void cleanupAndClose(); private: diff --git a/indra/newview/llfloaterexperiencepicker.cpp b/indra/newview/llfloaterexperiencepicker.cpp new file mode 100644 index 0000000000..bb54c57baf --- /dev/null +++ b/indra/newview/llfloaterexperiencepicker.cpp @@ -0,0 +1,162 @@ +/** +* @file llfloaterexperiencepicker.cpp +* @brief Implementation of llfloaterexperiencepicker +* @author dolphin@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, 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 "llfloaterexperiencepicker.h" + + +#include "lllineeditor.h" +#include "llfloaterreg.h" +#include "llscrolllistctrl.h" +#include "llviewerregion.h" +#include "llagent.h" +#include "llexperiencecache.h" +#include "llslurl.h" +#include "llavatarnamecache.h" +#include "llfloaterexperienceprofile.h" +#include "llcombobox.h" +#include "llviewercontrol.h" +#include "lldraghandle.h" +#include "llpanelexperiencepicker.h" + +LLFloaterExperiencePicker* LLFloaterExperiencePicker::show( select_callback_t callback, const LLUUID& key, BOOL allow_multiple, BOOL close_on_select, filter_list filters, LLView * frustumOrigin ) +{ + LLFloaterExperiencePicker* floater = + LLFloaterReg::showTypedInstance<LLFloaterExperiencePicker>("experience_search", key); + if (!floater) + { + LL_WARNS() << "Cannot instantiate experience picker" << LL_ENDL; + return NULL; + } + + if (floater->mSearchPanel) + { + floater->mSearchPanel->mSelectionCallback = callback; + floater->mSearchPanel->mCloseOnSelect = close_on_select; + floater->mSearchPanel->setAllowMultiple(allow_multiple); + floater->mSearchPanel->setDefaultFilters(); + floater->mSearchPanel->addFilters(filters.begin(), filters.end()); + floater->mSearchPanel->filterContent(); + } + + if(frustumOrigin) + { + floater->mFrustumOrigin = frustumOrigin->getHandle(); + } + + return floater; +} + +void LLFloaterExperiencePicker::drawFrustum() +{ + if(mFrustumOrigin.get()) + { + LLView * frustumOrigin = mFrustumOrigin.get(); + LLRect origin_rect; + frustumOrigin->localRectToOtherView(frustumOrigin->getLocalRect(), &origin_rect, this); + // draw context cone connecting color picker with color swatch in parent floater + LLRect local_rect = getLocalRect(); + if (hasFocus() && frustumOrigin->isInVisibleChain() && mContextConeOpacity > 0.001f) + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLEnable(GL_CULL_FACE); + gGL.begin(LLRender::QUADS); + { + gGL.color4f(0.f, 0.f, 0.f, mContextConeInAlpha * mContextConeOpacity); + gGL.vertex2i(origin_rect.mLeft, origin_rect.mTop); + gGL.vertex2i(origin_rect.mRight, origin_rect.mTop); + gGL.color4f(0.f, 0.f, 0.f, mContextConeOutAlpha * mContextConeOpacity); + gGL.vertex2i(local_rect.mRight, local_rect.mTop); + gGL.vertex2i(local_rect.mLeft, local_rect.mTop); + + gGL.color4f(0.f, 0.f, 0.f, mContextConeOutAlpha * mContextConeOpacity); + gGL.vertex2i(local_rect.mLeft, local_rect.mTop); + gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); + gGL.color4f(0.f, 0.f, 0.f, mContextConeInAlpha * mContextConeOpacity); + gGL.vertex2i(origin_rect.mLeft, origin_rect.mBottom); + gGL.vertex2i(origin_rect.mLeft, origin_rect.mTop); + + gGL.color4f(0.f, 0.f, 0.f, mContextConeOutAlpha * mContextConeOpacity); + gGL.vertex2i(local_rect.mRight, local_rect.mBottom); + gGL.vertex2i(local_rect.mRight, local_rect.mTop); + gGL.color4f(0.f, 0.f, 0.f, mContextConeInAlpha * mContextConeOpacity); + gGL.vertex2i(origin_rect.mRight, origin_rect.mTop); + gGL.vertex2i(origin_rect.mRight, origin_rect.mBottom); + + gGL.color4f(0.f, 0.f, 0.f, mContextConeOutAlpha * mContextConeOpacity); + gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); + gGL.vertex2i(local_rect.mRight, local_rect.mBottom); + gGL.color4f(0.f, 0.f, 0.f, mContextConeInAlpha * mContextConeOpacity); + gGL.vertex2i(origin_rect.mRight, origin_rect.mBottom); + gGL.vertex2i(origin_rect.mLeft, origin_rect.mBottom); + } + gGL.end(); + } + + if (gFocusMgr.childHasMouseCapture(getDragHandle())) + { + mContextConeOpacity = lerp(mContextConeOpacity, gSavedSettings.getF32("PickerContextOpacity"), LLCriticalDamp::getInterpolant(mContextConeFadeTime)); + } + else + { + mContextConeOpacity = lerp(mContextConeOpacity, 0.f, LLCriticalDamp::getInterpolant(mContextConeFadeTime)); + } + } +} + +void LLFloaterExperiencePicker::draw() +{ + drawFrustum(); + LLFloater::draw(); +} + +LLFloaterExperiencePicker::LLFloaterExperiencePicker( const LLSD& key ) + :LLFloater(key) + ,mSearchPanel(NULL) + ,mContextConeOpacity(0.f) + ,mContextConeInAlpha(0.f) + ,mContextConeOutAlpha(0.f) + ,mContextConeFadeTime(0.f) +{ + mContextConeInAlpha = gSavedSettings.getF32("ContextConeInAlpha"); + mContextConeOutAlpha = gSavedSettings.getF32("ContextConeOutAlpha"); + mContextConeFadeTime = gSavedSettings.getF32("ContextConeFadeTime"); +} + +LLFloaterExperiencePicker::~LLFloaterExperiencePicker() +{ + gFocusMgr.releaseFocusIfNeeded( this ); +} + +BOOL LLFloaterExperiencePicker::postBuild() +{ + mSearchPanel = new LLPanelExperiencePicker(); + addChild(mSearchPanel); + mSearchPanel->setOrigin(0, 0); + return LLFloater::postBuild(); +} diff --git a/indra/newview/llfloaterexperiencepicker.h b/indra/newview/llfloaterexperiencepicker.h new file mode 100644 index 0000000000..29054a57db --- /dev/null +++ b/indra/newview/llfloaterexperiencepicker.h @@ -0,0 +1,67 @@ +/** +* @file llfloaterexperiencepicker.h +* @brief Header file for llfloaterexperiencepicker +* @author dolphin@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, 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 LL_LLFLOATEREXPERIENCEPICKER_H +#define LL_LLFLOATEREXPERIENCEPICKER_H + +#include "llfloater.h" + +class LLScrollListCtrl; +class LLLineEditor; +class LLPanelExperiencePicker; + + +class LLFloaterExperiencePicker : public LLFloater +{ +public: + + typedef boost::function<void (const uuid_vec_t&)> select_callback_t; + // filter function for experiences, return true if the experience should be hidden. + typedef boost::function<bool (const LLSD&)> filter_function; + typedef std::vector<filter_function> filter_list; + + static LLFloaterExperiencePicker* show( select_callback_t callback, const LLUUID& key, BOOL allow_multiple, BOOL close_on_select, filter_list filters, LLView * frustumOrigin); + + LLFloaterExperiencePicker(const LLSD& key); + virtual ~LLFloaterExperiencePicker(); + + BOOL postBuild(); + + virtual void draw(); +private: + + LLPanelExperiencePicker* mSearchPanel; + + void drawFrustum(); + LLHandle <LLView> mFrustumOrigin; + F32 mContextConeOpacity; + F32 mContextConeInAlpha; + F32 mContextConeOutAlpha; + F32 mContextConeFadeTime; +}; + +#endif // LL_LLFLOATEREXPERIENCEPICKER_H + diff --git a/indra/newview/llfloaterexperienceprofile.cpp b/indra/newview/llfloaterexperienceprofile.cpp new file mode 100644 index 0000000000..4ee31f5df1 --- /dev/null +++ b/indra/newview/llfloaterexperienceprofile.cpp @@ -0,0 +1,998 @@ +/** + * @file llfloaterexperienceprofile.cpp + * @brief llfloaterexperienceprofile and related class definitions + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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 "llfloaterexperienceprofile.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llcommandhandler.h" +#include "llexpandabletextbox.h" +#include "llexperiencecache.h" +#include "llfloaterreg.h" +#include "llhttpclient.h" +#include "lllayoutstack.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "llsdserialize.h" +#include "llslurl.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewerregion.h" +#include "llevents.h" +#include "llfloatergroups.h" +#include "llnotifications.h" +#include "llfloaterreporter.h" + +#define XML_PANEL_EXPERIENCE_PROFILE "floater_experienceprofile.xml" +#define TF_NAME "experience_title" +#define TF_DESC "experience_description" +#define TF_SLURL "LocationTextText" +#define TF_MRKT "marketplace" +#define TF_MATURITY "ContentRatingText" +#define TF_OWNER "OwnerText" +#define TF_GROUP "GroupText" +#define TF_GRID_WIDE "grid_wide" +#define TF_PRIVILEGED "privileged" +#define EDIT "edit_" + +#define IMG_LOGO "logo" + +#define PNL_TOP "top panel" +#define PNL_IMAGE "image_panel" +#define PNL_DESC "description panel" +#define PNL_LOC "location panel" +#define PNL_MRKT "marketplace panel" +#define PNL_GROUP "group_panel" +#define PNL_PERMS "perm panel" + +#define BTN_ALLOW "allow_btn" +#define BTN_BLOCK "block_btn" +#define BTN_CANCEL "cancel_btn" +#define BTN_CLEAR_LOCATION "clear_btn" +#define BTN_EDIT "edit_btn" +#define BTN_ENABLE "enable_btn" +#define BTN_FORGET "forget_btn" +#define BTN_PRIVATE "private_btn" +#define BTN_REPORT "report_btn" +#define BTN_SAVE "save_btn" +#define BTN_SET_GROUP "Group_btn" +#define BTN_SET_LOCATION "location_btn" + + +class LLExperienceHandler : public LLCommandHandler +{ +public: + LLExperienceHandler() : LLCommandHandler("experience", UNTRUSTED_THROTTLE) { } + + bool handle(const LLSD& params, const LLSD& query_map, + LLMediaCtrl* web) + { + if(params.size() != 2 || params[1].asString() != "profile") + return false; + + LLExperienceCache::get(params[0].asUUID(), boost::bind(&LLExperienceHandler::experienceCallback, this, _1)); + return true; + } + + void experienceCallback(const LLSD& experienceDetails) + { + if(!experienceDetails.has(LLExperienceCache::MISSING)) + { + LLFloaterReg::showInstance("experience_profile", experienceDetails[LLExperienceCache::EXPERIENCE_ID].asUUID(), true); + } + } +}; + +LLExperienceHandler gExperienceHandler; + + +LLFloaterExperienceProfile::LLFloaterExperienceProfile(const LLSD& data) + : LLFloater(data) + , mExperienceId(data.asUUID()) + , mSaveCompleteAction(NOTHING) + , mDirty(false) + , mForceClose(false) +{ + +} + + +LLFloaterExperienceProfile::~LLFloaterExperienceProfile() +{ + +} + +template<class T> +class HandleResponder : public LLHTTPClient::Responder +{ +public: + HandleResponder(const LLHandle<T>& parent):mParent(parent){} + LLHandle<T> mParent; + + virtual void httpFailure() + { + LL_WARNS() << "HandleResponder failed with code: " << getStatus() << ", reason: " << getReason() << LL_ENDL; + } +}; + +class ExperienceUpdateResponder : public HandleResponder<LLFloaterExperienceProfile> +{ +public: + ExperienceUpdateResponder(const LLHandle<LLFloaterExperienceProfile>& parent):HandleResponder<LLFloaterExperienceProfile>(parent) + { + } + + virtual void httpSuccess() + { + LLFloaterExperienceProfile* parent=mParent.get(); + if(parent) + { + parent->onSaveComplete(getContent()); + } + } +}; + + + +class ExperiencePreferencesResponder : public LLHTTPClient::Responder +{ +public: + ExperiencePreferencesResponder(const LLUUID& single = LLUUID::null):mId(single) + { + } + + bool sendSingle(const LLSD& content, const LLSD& permission, const char* name) + { + if(!content.has(name)) + return false; + + LLEventPump& pump = LLEventPumps::instance().obtain("experience_permission"); + const LLSD& list = content[name]; + LLSD::array_const_iterator it = list.beginArray(); + while(it != list.endArray()) + { + if(it->asUUID() == mId) + { + LLSD message; + message[it->asString()] = permission; + message["experience"] = mId; + pump.post(message); + return true; + } + ++it; + } + return false; + } + + bool hasPermission(const LLSD& content, const char* name) + { + if(!content.has(name)) + return false; + + const LLSD& list = content[name]; + LLSD::array_const_iterator it = list.beginArray(); + while(it != list.endArray()) + { + if(it->asUUID() == mId) + { + return true; + } + ++it; + } + return false; + } + + const char* getPermission(const LLSD& content) + { + if(hasPermission(content, "experiences")) + { + return "Allow"; + } + else if(hasPermission(content, "blocked")) + { + return "Block"; + } + return "Forget"; + } + + + virtual void httpSuccess() + { + if(mId.notNull()) + { + post(getPermission(getContent())); + return; + } + LLEventPumps::instance().obtain("experience_permission").post(getContent()); + } + + void post( const char* perm ) + { + LLSD experience; + LLSD message; + experience["permission"]=perm; + message["experience"] = mId; + message[mId.asString()] = experience; + LLEventPumps::instance().obtain("experience_permission").post(message); + } + +private: + LLUUID mId; +}; + + +class IsAdminResponder : public HandleResponder<LLFloaterExperienceProfile> +{ +public: + IsAdminResponder(const LLHandle<LLFloaterExperienceProfile>& parent):HandleResponder<LLFloaterExperienceProfile>(parent) + { + } + + virtual void httpSuccess() + { + LLFloaterExperienceProfile* parent = mParent.get(); + if(!parent) + return; + + bool enabled = true; + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + enabled = false; + } + else + { + std::string url=region->getCapability("UpdateExperience"); + if(url.empty()) + enabled = false; + } + if(enabled && getContent()["status"].asBoolean()) + { + parent->getChild<LLLayoutPanel>(PNL_TOP)->setVisible(TRUE); + parent->getChild<LLButton>(BTN_EDIT)->setVisible(TRUE); + } + } +}; + +BOOL LLFloaterExperienceProfile::postBuild() +{ + + if (mExperienceId.notNull()) + { + LLExperienceCache::fetch(mExperienceId, true); + LLExperienceCache::get(mExperienceId, boost::bind(&LLFloaterExperienceProfile::experienceCallback, + getDerivedHandle<LLFloaterExperienceProfile>(), _1)); + + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string lookup_url=region->getCapability("IsExperienceAdmin"); + if(!lookup_url.empty()) + { + LLHTTPClient::get(lookup_url+"?experience_id="+mExperienceId.asString(), new IsAdminResponder(getDerivedHandle<LLFloaterExperienceProfile>())); + } + } + } + + childSetAction(BTN_EDIT, boost::bind(&LLFloaterExperienceProfile::onClickEdit, this)); + childSetAction(BTN_ALLOW, boost::bind(&LLFloaterExperienceProfile::onClickPermission, this, "Allow")); + childSetAction(BTN_FORGET, boost::bind(&LLFloaterExperienceProfile::onClickForget, this)); + childSetAction(BTN_BLOCK, boost::bind(&LLFloaterExperienceProfile::onClickPermission, this, "Block")); + childSetAction(BTN_CANCEL, boost::bind(&LLFloaterExperienceProfile::onClickCancel, this)); + childSetAction(BTN_SAVE, boost::bind(&LLFloaterExperienceProfile::onClickSave, this)); + childSetAction(BTN_SET_LOCATION, boost::bind(&LLFloaterExperienceProfile::onClickLocation, this)); + childSetAction(BTN_CLEAR_LOCATION, boost::bind(&LLFloaterExperienceProfile::onClickClear, this)); + childSetAction(BTN_SET_GROUP, boost::bind(&LLFloaterExperienceProfile::onPickGroup, this)); + childSetAction(BTN_REPORT, boost::bind(&LLFloaterExperienceProfile::onReportExperience, this)); + + getChild<LLTextEditor>(EDIT TF_DESC)->setKeystrokeCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this)); + getChild<LLUICtrl>(EDIT TF_MATURITY)->setCommitCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this)); + getChild<LLLineEditor>(EDIT TF_MRKT)->setKeystrokeCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + getChild<LLLineEditor>(EDIT TF_NAME)->setKeystrokeCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + + childSetCommitCallback(EDIT BTN_ENABLE, boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + childSetCommitCallback(EDIT BTN_PRIVATE, boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + + childSetCommitCallback(EDIT IMG_LOGO, boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + + getChild<LLTextEditor>(EDIT TF_DESC)->setCommitOnFocusLost(TRUE); + + + LLEventPumps::instance().obtain("experience_permission").listen(mExperienceId.asString()+"-profile", + boost::bind(&LLFloaterExperienceProfile::experiencePermission, getDerivedHandle<LLFloaterExperienceProfile>(this), _1)); + + return TRUE; +} + +void LLFloaterExperienceProfile::experienceCallback(LLHandle<LLFloaterExperienceProfile> handle, const LLSD& experience ) +{ + LLFloaterExperienceProfile* pllpep = handle.get(); + if(pllpep) + { + pllpep->refreshExperience(experience); + } +} + + +bool LLFloaterExperienceProfile::experiencePermission( LLHandle<LLFloaterExperienceProfile> handle, const LLSD& permission ) +{ + LLFloaterExperienceProfile* pllpep = handle.get(); + if(pllpep) + { + pllpep->updatePermission(permission); + } + return false; +} + + +void LLFloaterExperienceProfile::onClickEdit() +{ + LLTabContainer* tabs = getChild<LLTabContainer>("tab_container"); + + tabs->selectTabByName("edit_panel_experience_info"); +} + + +void LLFloaterExperienceProfile::onClickCancel() +{ + changeToView(); +} + +void LLFloaterExperienceProfile::onClickSave() +{ + doSave(NOTHING); +} + + +void LLFloaterExperienceProfile::onClickPermission(const char* perm) +{ + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return; + + std::string lookup_url=region->getCapability("ExperiencePreferences"); + if(lookup_url.empty()) + return; + LLSD permission; + LLSD data; + permission["permission"]=perm; + + data[mExperienceId.asString()]=permission; + LLHTTPClient::put(lookup_url, data, new ExperiencePreferencesResponder(mExperienceId)); + +} + + +void LLFloaterExperienceProfile::onClickForget() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return; + + std::string lookup_url=region->getCapability("ExperiencePreferences"); + if(lookup_url.empty()) + return; + + LLHTTPClient::del(lookup_url+"?"+mExperienceId.asString(), new ExperiencePreferencesResponder(mExperienceId)); +} + +bool LLFloaterExperienceProfile::setMaturityString( U8 maturity, LLTextBox* child, LLComboBox* combo ) +{ + LLStyle::Params style; + std::string access; + if(maturity <= SIM_ACCESS_PG) + { + style.image(LLUI::getUIImage(getString("maturity_icon_general"))); + access = LLTrans::getString("SIM_ACCESS_PG"); + combo->setCurrentByIndex(2); + } + else if(maturity <= SIM_ACCESS_MATURE) + { + style.image(LLUI::getUIImage(getString("maturity_icon_moderate"))); + access = LLTrans::getString("SIM_ACCESS_MATURE"); + combo->setCurrentByIndex(1); + } + else if(maturity <= SIM_ACCESS_ADULT) + { + style.image(LLUI::getUIImage(getString("maturity_icon_adult"))); + access = LLTrans::getString("SIM_ACCESS_ADULT"); + combo->setCurrentByIndex(0); + } + else + { + return false; + } + + child->setText(LLStringUtil::null); + + child->appendImageSegment(style); + + child->appendText(access, false); + + return true; +} + + +void LLFloaterExperienceProfile::refreshExperience( const LLSD& experience ) +{ + mExperienceDetails = experience; + mPackage = experience; + + + LLLayoutPanel* imagePanel = getChild<LLLayoutPanel>(PNL_IMAGE); + LLLayoutPanel* descriptionPanel = getChild<LLLayoutPanel>(PNL_DESC); + LLLayoutPanel* locationPanel = getChild<LLLayoutPanel>(PNL_LOC); + LLLayoutPanel* marketplacePanel = getChild<LLLayoutPanel>(PNL_MRKT); + LLLayoutPanel* topPanel = getChild<LLLayoutPanel>(PNL_TOP); + + + imagePanel->setVisible(FALSE); + descriptionPanel->setVisible(FALSE); + locationPanel->setVisible(FALSE); + marketplacePanel->setVisible(FALSE); + topPanel->setVisible(FALSE); + + + LLTextBox* child = getChild<LLTextBox>(TF_NAME); + //child->setText(experience[LLExperienceCache::NAME].asString()); + child->setText(LLSLURL("experience", experience[LLExperienceCache::EXPERIENCE_ID], "profile").getSLURLString()); + + LLLineEditor* linechild = getChild<LLLineEditor>(EDIT TF_NAME); + linechild->setText(experience[LLExperienceCache::NAME].asString()); + + std::string value = experience[LLExperienceCache::DESCRIPTION].asString(); + LLExpandableTextBox* exchild = getChild<LLExpandableTextBox>(TF_DESC); + exchild->setText(value); + descriptionPanel->setVisible(value.length()>0); + + LLTextEditor* edit_child = getChild<LLTextEditor>(EDIT TF_DESC); + edit_child->setText(value); + + mLocationSLURL = experience[LLExperienceCache::SLURL].asString(); + child = getChild<LLTextBox>(TF_SLURL); + bool has_slurl = mLocationSLURL.length()>0; + locationPanel->setVisible(has_slurl); + mLocationSLURL = LLSLURL(mLocationSLURL).getSLURLString(); + child->setText(mLocationSLURL); + + + child = getChild<LLTextBox>(EDIT TF_SLURL); + if(has_slurl) + { + child->setText(mLocationSLURL); + } + else + { + child->setText(getString("empty_slurl")); + } + + setMaturityString((U8)(experience[LLExperienceCache::MATURITY].asInteger()), getChild<LLTextBox>(TF_MATURITY), getChild<LLComboBox>(EDIT TF_MATURITY)); + + LLUUID id = experience[LLExperienceCache::AGENT_ID].asUUID(); + child = getChild<LLTextBox>(TF_OWNER); + value = LLSLURL("agent", id, "inspect").getSLURLString(); + child->setText(value); + + + id = experience[LLExperienceCache::GROUP_ID].asUUID(); + bool id_null = id.isNull(); + child = getChild<LLTextBox>(TF_GROUP); + value = LLSLURL("group", id, "inspect").getSLURLString(); + child->setText(value); + getChild<LLLayoutPanel>(PNL_GROUP)->setVisible(!id_null); + + setEditGroup(id); + + getChild<LLButton>(BTN_SET_GROUP)->setEnabled(experience[LLExperienceCache::AGENT_ID].asUUID() == gAgent.getID()); + + LLCheckBoxCtrl* enable = getChild<LLCheckBoxCtrl>(EDIT BTN_ENABLE); + S32 properties = mExperienceDetails[LLExperienceCache::PROPERTIES].asInteger(); + enable->set(!(properties & LLExperienceCache::PROPERTY_DISABLED)); + + enable = getChild<LLCheckBoxCtrl>(EDIT BTN_PRIVATE); + enable->set(properties & LLExperienceCache::PROPERTY_PRIVATE); + if(properties & LLExperienceCache::PROPERTY_GRID) + { + topPanel->setVisible(TRUE); + child=getChild<LLTextBox>(TF_GRID_WIDE); + child->setVisible(TRUE); + child->setText(LLTrans::getString("GRID_WIDE")); + } + + if(getChild<LLButton>(BTN_EDIT)->getVisible()) + { + topPanel->setVisible(TRUE); + } + + if(properties & LLExperienceCache::PROPERTY_PRIVILEGED) + { + child = getChild<LLTextBox>(TF_PRIVILEGED); + child->setVisible(TRUE); + } + else + { + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string lookup_url=region->getCapability("ExperiencePreferences"); + if(!lookup_url.empty()) + { + LLHTTPClient::get(lookup_url+"?"+mExperienceId.asString(), new ExperiencePreferencesResponder(mExperienceId)); + } + } + } + + value=experience[LLExperienceCache::METADATA].asString(); + if(value.empty()) + return; + + LLPointer<LLSDParser> parser = new LLSDXMLParser(); + + LLSD data; + + std::istringstream is(value); + if(LLSDParser::PARSE_FAILURE != parser->parse(is, data, value.size())) + { + value=""; + if(data.has(TF_MRKT)) + { + value=data[TF_MRKT].asString(); + + child = getChild<LLTextBox>(TF_MRKT); + child->setText(value); + if(value.size()) + { + marketplacePanel->setVisible(TRUE); + } + else + { + marketplacePanel->setVisible(FALSE); + } + } + else + { + marketplacePanel->setVisible(FALSE); + } + + linechild = getChild<LLLineEditor>(EDIT TF_MRKT); + linechild->setText(value); + + if(data.has(IMG_LOGO)) + { + LLTextureCtrl* logo = getChild<LLTextureCtrl>(IMG_LOGO); + + LLUUID id = data[IMG_LOGO].asUUID(); + logo->setImageAssetID(id); + imagePanel->setVisible(TRUE); + + logo = getChild<LLTextureCtrl>(EDIT IMG_LOGO); + logo->setImageAssetID(data[IMG_LOGO].asUUID()); + + imagePanel->setVisible(id.notNull()); + } + } + else + { + marketplacePanel->setVisible(FALSE); + imagePanel->setVisible(FALSE); + } + + mDirty=false; + mForceClose = false; + getChild<LLButton>(BTN_SAVE)->setEnabled(mDirty); +} + +void LLFloaterExperienceProfile::setPreferences( const LLSD& content ) +{ + S32 properties = mExperienceDetails[LLExperienceCache::PROPERTIES].asInteger(); + if(properties & LLExperienceCache::PROPERTY_PRIVILEGED) + { + return; + } + + const LLSD& experiences = content["experiences"]; + const LLSD& blocked = content["blocked"]; + + + for(LLSD::array_const_iterator it = experiences.beginArray(); it != experiences.endArray() ; ++it) + { + if(it->asUUID()==mExperienceId) + { + experienceAllowed(); + return; + } + } + + for(LLSD::array_const_iterator it = blocked.beginArray(); it != blocked.endArray() ; ++it) + { + if(it->asUUID()==mExperienceId) + { + experienceBlocked(); + return; + } + } + + experienceForgotten(); +} + +void LLFloaterExperienceProfile::onFieldChanged() +{ + updatePackage(); + + if(!getChild<LLButton>(BTN_EDIT)->getVisible()) + { + return; + } + LLSD::map_const_iterator st = mExperienceDetails.beginMap(); + LLSD::map_const_iterator dt = mPackage.beginMap(); + + mDirty = false; + while( !mDirty && st != mExperienceDetails.endMap() && dt != mPackage.endMap()) + { + mDirty = st->first != dt->first || st->second.asString() != dt->second.asString(); + ++st;++dt; + } + + if(!mDirty && (st != mExperienceDetails.endMap() || dt != mPackage.endMap())) + { + mDirty = true; + } + + getChild<LLButton>(BTN_SAVE)->setEnabled(mDirty); +} + + +BOOL LLFloaterExperienceProfile::canClose() +{ + if(mForceClose || !mDirty) + { + return TRUE; + } + else + { + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLFloaterExperienceProfile::handleSaveChangesDialog, this, _1, _2, CLOSE)); + return FALSE; + } +} + +bool LLFloaterExperienceProfile::handleSaveChangesDialog( const LLSD& notification, const LLSD& response, PostSaveAction action ) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch( option ) + { + case 0: // "Yes" + // close after saving + doSave( action ); + break; + + case 1: // "No" + if(action != NOTHING) + { + mForceClose = TRUE; + if(action==CLOSE) + { + closeFloater(); + } + else + { + changeToView(); + } + } + break; + + case 2: // "Cancel" + default: + // If we were quitting, we didn't really mean it. + LLAppViewer::instance()->abortQuit(); + break; + } + return false; +} + +void LLFloaterExperienceProfile::doSave( int success_action ) +{ + mSaveCompleteAction=success_action; + + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return; + + std::string url=region->getCapability("UpdateExperience"); + if(url.empty()) + return; + + mPackage.erase(LLExperienceCache::QUOTA); + mPackage.erase(LLExperienceCache::EXPIRES); + mPackage.erase(LLExperienceCache::AGENT_ID); + + LLHTTPClient::post(url, mPackage, new ExperienceUpdateResponder(getDerivedHandle<LLFloaterExperienceProfile>())); +} + +void LLFloaterExperienceProfile::onSaveComplete( const LLSD& content ) +{ + LLUUID id = getExperienceId(); + + if(content.has("removed")) + { + const LLSD& removed = content["removed"]; + LLSD::map_const_iterator it = removed.beginMap(); + for(/**/; it != removed.endMap(); ++it) + { + const std::string& field = it->first; + if(field == LLExperienceCache::EXPERIENCE_ID) + { + //this message should be removed by the experience api + continue; + } + const LLSD& data = it->second; + std::string error_tag = data["error_tag"].asString()+ "ExperienceProfileMessage"; + LLSD fields; + if( LLNotifications::instance().getTemplate(error_tag)) + { + fields["field"] = field; + fields["extra_info"] = data["extra_info"]; + LLNotificationsUtil::add(error_tag, fields); + } + else + { + fields["MESSAGE"]=data["en"]; + LLNotificationsUtil::add("GenericAlert", fields); + } + } + } + + if(!content.has("experience_keys")) + { + LL_WARNS() << "LLFloaterExperienceProfile::onSaveComplete called with bad content" << LL_ENDL; + return; + } + + const LLSD& experiences = content["experience_keys"]; + + LLSD::array_const_iterator it = experiences.beginArray(); + if(it == experiences.endArray()) + { + LL_WARNS() << "LLFloaterExperienceProfile::onSaveComplete called with empty content" << LL_ENDL; + return; + } + + if(!it->has(LLExperienceCache::EXPERIENCE_ID) || ((*it)[LLExperienceCache::EXPERIENCE_ID].asUUID() != id)) + { + LL_WARNS() << "LLFloaterExperienceProfile::onSaveComplete called with unexpected experience id" << LL_ENDL; + return; + } + + refreshExperience(*it); + LLExperienceCache::insert(*it); + LLExperienceCache::fetch(id, true); + + if(mSaveCompleteAction==VIEW) + { + LLTabContainer* tabs = getChild<LLTabContainer>("tab_container"); + tabs->selectTabByName("panel_experience_info"); + } + else if(mSaveCompleteAction == CLOSE) + { + closeFloater(); + } +} + +void LLFloaterExperienceProfile::changeToView() +{ + if(mForceClose || !mDirty) + { + refreshExperience(mExperienceDetails); + LLTabContainer* tabs = getChild<LLTabContainer>("tab_container"); + + tabs->selectTabByName("panel_experience_info"); + } + else + { + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLFloaterExperienceProfile::handleSaveChangesDialog, this, _1, _2, VIEW)); + } +} + +void LLFloaterExperienceProfile::onClickLocation() +{ + LLViewerRegion* region = gAgent.getRegion(); + if(region) + { + LLTextBox* child = getChild<LLTextBox>(EDIT TF_SLURL); + mLocationSLURL = LLSLURL(region->getName(), gAgent.getPositionGlobal()).getSLURLString(); + child->setText(mLocationSLURL); + onFieldChanged(); + } +} + +void LLFloaterExperienceProfile::onClickClear() +{ + LLTextBox* child = getChild<LLTextBox>(EDIT TF_SLURL); + mLocationSLURL = ""; + child->setText(getString("empty_slurl")); + onFieldChanged(); +} + +void LLFloaterExperienceProfile::updatePermission( const LLSD& permission ) +{ + if(permission.has("experience")) + { + if(permission["experience"].asUUID() != mExperienceId) + { + return; + } + + std::string str = permission[mExperienceId.asString()]["permission"].asString(); + if(str == "Allow") + { + experienceAllowed(); + } + else if(str == "Block") + { + experienceBlocked(); + } + else if(str == "Forget") + { + experienceForgotten(); + } + } + else + { + setPreferences(permission); + } +} + +void LLFloaterExperienceProfile::experienceAllowed() +{ + LLButton* button=getChild<LLButton>(BTN_ALLOW); + button->setEnabled(FALSE); + + button=getChild<LLButton>(BTN_FORGET); + button->setEnabled(TRUE); + + button=getChild<LLButton>(BTN_BLOCK); + button->setEnabled(TRUE); +} + +void LLFloaterExperienceProfile::experienceForgotten() +{ + LLButton* button=getChild<LLButton>(BTN_ALLOW); + button->setEnabled(TRUE); + + button=getChild<LLButton>(BTN_FORGET); + button->setEnabled(FALSE); + + button=getChild<LLButton>(BTN_BLOCK); + button->setEnabled(TRUE); +} + +void LLFloaterExperienceProfile::experienceBlocked() +{ + LLButton* button=getChild<LLButton>(BTN_ALLOW); + button->setEnabled(TRUE); + + button=getChild<LLButton>(BTN_FORGET); + button->setEnabled(TRUE); + + button=getChild<LLButton>(BTN_BLOCK); + button->setEnabled(FALSE); +} + +void LLFloaterExperienceProfile::onClose( bool app_quitting ) +{ + LLEventPumps::instance().obtain("experience_permission").stopListening(mExperienceId.asString()+"-profile"); + LLFloater::onClose(app_quitting); +} + +void LLFloaterExperienceProfile::updatePackage() +{ + mPackage[LLExperienceCache::NAME] = getChild<LLLineEditor>(EDIT TF_NAME)->getText(); + mPackage[LLExperienceCache::DESCRIPTION] = getChild<LLTextEditor>(EDIT TF_DESC)->getText(); + if(mLocationSLURL.empty()) + { + mPackage[LLExperienceCache::SLURL] = LLStringUtil::null; + } + else + { + mPackage[LLExperienceCache::SLURL] = mLocationSLURL; + } + + mPackage[LLExperienceCache::MATURITY] = getChild<LLComboBox>(EDIT TF_MATURITY)->getSelectedValue().asInteger(); + + LLSD metadata; + + metadata[TF_MRKT] = getChild<LLLineEditor>(EDIT TF_MRKT)->getText(); + metadata[IMG_LOGO] = getChild<LLTextureCtrl>(EDIT IMG_LOGO)->getImageAssetID(); + + LLPointer<LLSDXMLFormatter> formatter = new LLSDXMLFormatter(); + + std::ostringstream os; + if(formatter->format(metadata, os)) + { + mPackage[LLExperienceCache::METADATA]=os.str(); + } + + int properties = mPackage[LLExperienceCache::PROPERTIES].asInteger(); + LLCheckBoxCtrl* enable = getChild<LLCheckBoxCtrl>(EDIT BTN_ENABLE); + if(enable->get()) + { + properties &= ~LLExperienceCache::PROPERTY_DISABLED; + } + else + { + properties |= LLExperienceCache::PROPERTY_DISABLED; + } + + enable = getChild<LLCheckBoxCtrl>(EDIT BTN_PRIVATE); + if(enable->get()) + { + properties |= LLExperienceCache::PROPERTY_PRIVATE; + } + else + { + properties &= ~LLExperienceCache::PROPERTY_PRIVATE; + } + + mPackage[LLExperienceCache::PROPERTIES] = properties; +} + +void LLFloaterExperienceProfile::onPickGroup() +{ + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + + LLFloaterGroupPicker* widget = LLFloaterReg::showTypedInstance<LLFloaterGroupPicker>("group_picker", LLSD(gAgent.getID())); + if (widget) + { + widget->setSelectGroupCallback(boost::bind(&LLFloaterExperienceProfile::setEditGroup, this, _1)); + if (parent_floater) + { + LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, widget); + widget->setOrigin(new_rect.mLeft, new_rect.mBottom); + parent_floater->addDependentFloater(widget); + } + } +} + +void LLFloaterExperienceProfile::setEditGroup( LLUUID group_id ) +{ + LLTextBox* child = getChild<LLTextBox>(EDIT TF_GROUP); + std::string value = LLSLURL("group", group_id, "inspect").getSLURLString(); + child->setText(value); + mPackage[LLExperienceCache::GROUP_ID] = group_id; + onFieldChanged(); +} + +void LLFloaterExperienceProfile::onReportExperience() +{ + LLFloaterReporter::showFromExperience(mExperienceId); +} diff --git a/indra/newview/llfloaterexperienceprofile.h b/indra/newview/llfloaterexperienceprofile.h new file mode 100644 index 0000000000..78d54eb447 --- /dev/null +++ b/indra/newview/llfloaterexperienceprofile.h @@ -0,0 +1,104 @@ +/** + * @file llfloaterexperienceprofile.h + * @brief llfloaterexperienceprofile and related class definitions + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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 LL_LLFLOATEREXPERIENCEPROFILE_H +#define LL_LLFLOATEREXPERIENCEPROFILE_H + +#include "llfloater.h" +#include "lluuid.h" +#include "llsd.h" + +class LLLayoutPanel; +class LLTextBox; +class LLComboBox; + +class LLFloaterExperienceProfile : public LLFloater +{ + LOG_CLASS(LLFloaterExperienceProfile); +public: + enum PostSaveAction + { + NOTHING, + CLOSE, + VIEW, + }; + + + LLFloaterExperienceProfile(const LLSD& data); + virtual ~LLFloaterExperienceProfile(); + + LLUUID getExperienceId() const { return mExperienceId; } + void setPreferences( const LLSD& content ); + + + void refreshExperience(const LLSD& experience); + void onSaveComplete( const LLSD& content ); + virtual BOOL canClose(); + + virtual void onClose(bool app_quitting); +protected: + void onClickEdit(); + void onClickPermission(const char* permission); + void onClickForget(); + void onClickCancel(); + void onClickSave(); + void onClickLocation(); + void onClickClear(); + void onPickGroup(); + void onFieldChanged(); + void onReportExperience(); + + void setEditGroup(LLUUID group_id); + + void changeToView(); + + void experienceForgotten(); + void experienceBlocked(); + void experienceAllowed(); + + static void experienceCallback(LLHandle<LLFloaterExperienceProfile> handle, const LLSD& experience); + static bool experiencePermission(LLHandle<LLFloaterExperienceProfile> handle, const LLSD& permission); + + BOOL postBuild(); + bool setMaturityString(U8 maturity, LLTextBox* child, LLComboBox* combo); + bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response, PostSaveAction action); + void doSave( int success_action ); + + void updatePackage(); + + void updatePermission( const LLSD& permission ); + LLUUID mExperienceId; + LLSD mExperienceDetails; + LLSD mPackage; + std::string mLocationSLURL; + int mSaveCompleteAction; + bool mDirty; + bool mForceClose; +}; + +#endif // LL_LLFLOATEREXPERIENCEPROFILE_H diff --git a/indra/newview/llfloaterexperiences.cpp b/indra/newview/llfloaterexperiences.cpp new file mode 100644 index 0000000000..ac79c9ab43 --- /dev/null +++ b/indra/newview/llfloaterexperiences.cpp @@ -0,0 +1,325 @@ +/** + * @file llfloaterexperiences.cpp + * @brief LLFloaterExperiences class implementation + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 "llfloaterexperiences.h" + +#include "llagent.h" +#include "llevents.h" +#include "llexperiencecache.h" +#include "llfloaterregioninfo.h" +#include "llhttpclient.h" +#include "llnotificationsutil.h" +#include "llpanelexperiencelog.h" +#include "llpanelexperiencepicker.h" +#include "llpanelexperiences.h" +#include "lltabcontainer.h" +#include "lltrans.h" +#include "llviewerregion.h" + + +#define SHOW_RECENT_TAB (0) + +class LLExperienceListResponder : public LLHTTPClient::Responder +{ +public: + typedef std::map<std::string, std::string> NameMap; + typedef boost::function<void(LLPanelExperiences*, const LLSD&)> Callback; + LLExperienceListResponder(const LLHandle<LLFloaterExperiences>& parent, NameMap& nameMap, const std::string& errorMessage="ErrorMessage"):mParent(parent),mErrorMessage(errorMessage) + { + mNameMap.swap(nameMap); + } + + Callback mCallback; + LLHandle<LLFloaterExperiences> mParent; + NameMap mNameMap; + const std::string mErrorMessage; + /*virtual*/ void httpSuccess() + { + if(mParent.isDead()) + return; + + LLFloaterExperiences* parent=mParent.get(); + LLTabContainer* tabs = parent->getChild<LLTabContainer>("xp_tabs"); + + NameMap::iterator it = mNameMap.begin(); + while(it != mNameMap.end()) + { + if(getContent().has(it->first)) + { + LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName(it->second); + if(tab) + { + const LLSD& ids = getContent()[it->first]; + tab->setExperienceList(ids); + if(!mCallback.empty()) + { + mCallback(tab, getContent()); + } + } + } + ++it; + } + } + + /*virtual*/ void httpFailure() + { + LLSD subs; + subs["ERROR_MESSAGE"] = getReason(); + LLNotificationsUtil::add(mErrorMessage, subs); + } +}; + + + +LLFloaterExperiences::LLFloaterExperiences(const LLSD& data) + :LLFloater(data) +{ +} + +LLPanelExperiences* LLFloaterExperiences::addTab(const std::string& name, bool select) +{ + LLPanelExperiences* newPanel = LLPanelExperiences::create(name); + getChild<LLTabContainer>("xp_tabs")->addTabPanel(LLTabContainer::TabPanelParams(). + panel(newPanel). + label(LLTrans::getString(name)). + select_tab(select)); + + return newPanel; +} + +BOOL LLFloaterExperiences::postBuild() +{ + getChild<LLTabContainer>("xp_tabs")->addTabPanel(new LLPanelExperiencePicker()); + addTab("Allowed_Experiences_Tab", true); + addTab("Blocked_Experiences_Tab", false); + addTab("Admin_Experiences_Tab", false); + addTab("Contrib_Experiences_Tab", false); + LLPanelExperiences* owned = addTab("Owned_Experiences_Tab", false); + owned->setButtonAction("acquire", boost::bind(&LLFloaterExperiences::sendPurchaseRequest, this)); + owned->enableButton(false); +#if SHOW_RECENT_TAB + addTab("Recent_Experiences_Tab", false); +#endif //SHOW_RECENT_TAB + getChild<LLTabContainer>("xp_tabs")->addTabPanel(new LLPanelExperienceLog()); + resizeToTabs(); + + + LLEventPumps::instance().obtain("experience_permission").listen("LLFloaterExperiences", + boost::bind(&LLFloaterExperiences::updatePermissions, this, _1)); + + return TRUE; +} + + +void LLFloaterExperiences::clearFromRecent(const LLSD& ids) +{ +#if SHOW_RECENT_TAB + LLTabContainer* tabs = getChild<LLTabContainer>("xp_tabs"); + + LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Recent_Experiences_Tab"); + if(!tab) + return; + + tab->removeExperiences(ids); +#endif // SHOW_RECENT_TAB +} + +void LLFloaterExperiences::setupRecentTabs() +{ +#if SHOW_RECENT_TAB + LLTabContainer* tabs = getChild<LLTabContainer>("xp_tabs"); + + LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Recent_Experiences_Tab"); + if(!tab) + return; + + LLSD recent; + + const LLExperienceCache::cache_t& experiences = LLExperienceCache::getCached(); + + LLExperienceCache::cache_t::const_iterator it = experiences.begin(); + while( it != experiences.end() ) + { + if(!it->second.has(LLExperienceCache::MISSING)) + { + recent.append(it->first); + } + ++it; + } + + tab->setExperienceList(recent); +#endif // SHOW_RECENT_TAB +} + + +void LLFloaterExperiences::resizeToTabs() +{ + const S32 TAB_WIDTH_PADDING = 16; + + LLTabContainer* tabs = getChild<LLTabContainer>("xp_tabs"); + LLRect rect = getRect(); + if(rect.getWidth() < tabs->getTotalTabWidth() + TAB_WIDTH_PADDING) + { + rect.mRight = rect.mLeft + tabs->getTotalTabWidth() + TAB_WIDTH_PADDING; + } + reshape(rect.getWidth(), rect.getHeight(), FALSE); +} + +void LLFloaterExperiences::refreshContents() +{ + setupRecentTabs(); + + LLViewerRegion* region = gAgent.getRegion(); + + if (region) + { + LLExperienceListResponder::NameMap nameMap; + std::string lookup_url=region->getCapability("GetExperiences"); + if(!lookup_url.empty()) + { + nameMap["experiences"]="Allowed_Experiences_Tab"; + nameMap["blocked"]="Blocked_Experiences_Tab"; + LLHTTPClient::get(lookup_url, new LLExperienceListResponder(getDerivedHandle<LLFloaterExperiences>(), nameMap)); + } + + lookup_url = region->getCapability("GetAdminExperiences"); + if(!lookup_url.empty()) + { + nameMap["experience_ids"]="Admin_Experiences_Tab"; + LLHTTPClient::get(lookup_url, new LLExperienceListResponder(getDerivedHandle<LLFloaterExperiences>(), nameMap)); + } + + lookup_url = region->getCapability("GetCreatorExperiences"); + if(!lookup_url.empty()) + { + nameMap["experience_ids"]="Contrib_Experiences_Tab"; + LLHTTPClient::get(lookup_url, new LLExperienceListResponder(getDerivedHandle<LLFloaterExperiences>(), nameMap)); + } + + lookup_url = region->getCapability("AgentExperiences"); + if(!lookup_url.empty()) + { + nameMap["experience_ids"]="Owned_Experiences_Tab"; + LLHTTPClient::get(lookup_url, new LLExperienceListResponder(getDerivedHandle<LLFloaterExperiences>(), nameMap)); + } + } +} + +void LLFloaterExperiences::onOpen( const LLSD& key ) +{ + LLViewerRegion* region = gAgent.getRegion(); + if(region) + { + if(region->capabilitiesReceived()) + { + refreshContents(); + return; + } + region->setCapabilitiesReceivedCallback(boost::bind(&LLFloaterExperiences::refreshContents, this)); + return; + } +} + +bool LLFloaterExperiences::updatePermissions( const LLSD& permission ) +{ + LLTabContainer* tabs = getChild<LLTabContainer>("xp_tabs"); + LLUUID experience; + std::string permission_string; + if(permission.has("experience")) + { + experience = permission["experience"].asUUID(); + permission_string = permission[experience.asString()]["permission"].asString(); + + } + LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Allowed_Experiences_Tab"); + if(tab) + { + if(permission.has("experiences")) + { + tab->setExperienceList(permission["experiences"]); + } + else if(experience.notNull()) + { + if(permission_string != "Allow") + { + tab->removeExperience(experience); + } + else + { + tab->addExperience(experience); + } + } + } + + tab = (LLPanelExperiences*)tabs->getPanelByName("Blocked_Experiences_Tab"); + if(tab) + { + if(permission.has("blocked")) + { + tab->setExperienceList(permission["blocked"]); + } + else if(experience.notNull()) + { + if(permission_string != "Block") + { + tab->removeExperience(experience); + } + else + { + tab->addExperience(experience); + } + } + } + return false; +} + +void LLFloaterExperiences::onClose( bool app_quitting ) +{ + LLEventPumps::instance().obtain("experience_permission").stopListening("LLFloaterExperiences"); + LLFloater::onClose(app_quitting); +} + +void LLFloaterExperiences::checkPurchaseInfo(LLPanelExperiences* panel, const LLSD& content) const +{ + panel->enableButton(content.has("purchase")); +} + +void LLFloaterExperiences::sendPurchaseRequest() const +{ + LLViewerRegion* region = gAgent.getRegion(); + std::string url = region->getCapability("AgentExperiences"); + if(!url.empty()) + { + LLSD content; + + LLExperienceListResponder::NameMap nameMap; + nameMap["experience_ids"]="Owned_Experiences_Tab"; + LLExperienceListResponder* responder = new LLExperienceListResponder(getDerivedHandle<LLFloaterExperiences>(), nameMap, "ExperienceAcquireFailed"); + responder->mCallback = boost::bind(&LLFloaterExperiences::checkPurchaseInfo, this, _1, _2); + LLHTTPClient::post(url, content, responder); + } +} diff --git a/indra/newview/llfloaterexperiences.h b/indra/newview/llfloaterexperiences.h new file mode 100644 index 0000000000..f1a0f49245 --- /dev/null +++ b/indra/newview/llfloaterexperiences.h @@ -0,0 +1,58 @@ +/** + * @file llfloaterexperiences.h + * @brief LLFloaterExperiences class definition + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 LL_LLFLOATEREXPERIENCES_H +#define LL_LLFLOATEREXPERIENCES_H + +#include "llfloater.h" + +class LLPanelExperiences; + +class LLFloaterExperiences : + public LLFloater +{ +public: + LLFloaterExperiences(const LLSD& data); + virtual void onClose(bool app_quitting); + + virtual void onOpen(const LLSD& key); +protected: + void clearFromRecent(const LLSD& ids); + void resizeToTabs(); + /*virtual*/ BOOL postBuild(); + void refreshContents(); + void setupRecentTabs(); + LLPanelExperiences* addTab(const std::string& name, bool select); + + bool updatePermissions(const LLSD& permission); + void sendPurchaseRequest() const; + void checkPurchaseInfo(LLPanelExperiences* panel, const LLSD& content)const; + +private: + +}; + +#endif //LL_LLFLOATEREXPERIENCES_H diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 7213064746..4fda1ebb7d 100755 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -75,6 +75,9 @@ #include "llviewercontrol.h" #include "roles_constants.h" #include "lltrans.h" +#include "llpanelexperiencelisteditor.h" +#include "llpanelexperiencepicker.h" +#include "llexperiencecache.h" #include "llgroupactions.h" @@ -86,7 +89,6 @@ static std::string MATURITY = "[MATURITY]"; // constants used in callbacks below - syntactic sugar. static const BOOL BUY_GROUP_LAND = TRUE; static const BOOL BUY_PERSONAL_LAND = FALSE; -LLPointer<LLParcelSelection> LLPanelLandGeneral::sSelectionForBuyPass = NULL; // Statics LLParcelSelectionObserver* LLFloaterLand::sObserver = NULL; @@ -162,191 +164,117 @@ void send_parcel_select_objects(S32 parcel_local_id, U32 return_type, msg->sendReliable(region->getHost()); } -LLParcel* LLFloaterLand::getCurrentSelectedParcel() +void send_other_clean_time_message(S32 parcel_local_id, S32 other_clean_time) { - return mParcel->getParcel(); -}; + LLMessageSystem *msg = gMessageSystem; -//static -LLPanelLandObjects* LLFloaterLand::getCurrentPanelLandObjects() -{ - LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance<LLFloaterLand>("about_land"); - if(land_instance) - { - return land_instance->mPanelObjects; - } - else - { - return NULL; - } -} + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (!region) return; -//static -LLPanelLandCovenant* LLFloaterLand::getCurrentPanelLandCovenant() -{ - LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance<LLFloaterLand>("about_land"); - if(land_instance) - { - return land_instance->mPanelCovenant; - } - else - { - return NULL; - } -} + msg->newMessageFast(_PREHASH_ParcelSetOtherCleanTime); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); + msg->addS32Fast(_PREHASH_OtherCleanTime, other_clean_time); -// static -void LLFloaterLand::refreshAll() -{ - LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance<LLFloaterLand>("about_land"); - if(land_instance) - { - land_instance->refresh(); - } + msg->sendReliable(region->getHost()); } -void LLFloaterLand::onOpen(const LLSD& key) +// inserts maturity info(icon and text) into target textbox +// names_floater - pointer to floater which contains strings with maturity icons filenames +// str_to_parse is string in format "txt1[MATURITY]txt2" where maturity icon and text will be inserted instead of [MATURITY] +void insert_maturity_into_textbox(LLTextBox* target_textbox, LLFloater* names_floater, std::string str_to_parse) { - // moved from triggering show instance in llviwermenu.cpp - - if (LLViewerParcelMgr::getInstance()->selectionEmpty()) - { - LLViewerParcelMgr::getInstance()->selectParcelAt(gAgent.getPositionGlobal()); - } - - // Done automatically when the selected parcel's properties arrive - // (and hence we have the local id). - // LLViewerParcelMgr::getInstance()->sendParcelAccessListRequest(AL_ACCESS | AL_BAN | AL_RENTER); + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (!region) + return; - mParcel = LLViewerParcelMgr::getInstance()->getFloatingParcelSelection(); - - // Refresh even if not over a region so we don't get an - // uninitialized dialog. The dialog is 0-region aware. - refresh(); -} + LLStyle::Params style; -void LLFloaterLand::onVisibilityChanged(const LLSD& visible) -{ - if (!visible.asBoolean()) - { - // Might have been showing owned objects - LLSelectMgr::getInstance()->unhighlightAll(); + U8 sim_access = region->getSimAccess(); - // Save which panel we had open - sLastTab = mTabLand->getCurrentPanelIndex(); - } -} + switch(sim_access) + { + case SIM_ACCESS_PG: + style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_general"))); + break; + case SIM_ACCESS_ADULT: + style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_adult"))); + break; -LLFloaterLand::LLFloaterLand(const LLSD& seed) -: LLFloater(seed) -{ - mFactoryMap["land_general_panel"] = LLCallbackMap(createPanelLandGeneral, this); - mFactoryMap["land_covenant_panel"] = LLCallbackMap(createPanelLandCovenant, this); - mFactoryMap["land_objects_panel"] = LLCallbackMap(createPanelLandObjects, this); - mFactoryMap["land_options_panel"] = LLCallbackMap(createPanelLandOptions, this); - mFactoryMap["land_audio_panel"] = LLCallbackMap(createPanelLandAudio, this); - mFactoryMap["land_media_panel"] = LLCallbackMap(createPanelLandMedia, this); - mFactoryMap["land_access_panel"] = LLCallbackMap(createPanelLandAccess, this); + case SIM_ACCESS_MATURE: + style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_moderate"))); + break; - sObserver = new LLParcelSelectionObserver(); - LLViewerParcelMgr::getInstance()->addObserver( sObserver ); -} + default: + break; + } -BOOL LLFloaterLand::postBuild() -{ - setVisibleCallback(boost::bind(&LLFloaterLand::onVisibilityChanged, this, _2)); + size_t maturity_pos = str_to_parse.find(MATURITY); - LLTabContainer* tab = getChild<LLTabContainer>("landtab"); - - mTabLand = (LLTabContainer*) tab; - - if (tab) + if (maturity_pos == std::string::npos) { - tab->selectTab(sLastTab); + return; } - return TRUE; -} + std::string text_before_rating = str_to_parse.substr(0, maturity_pos); + std::string text_after_rating = str_to_parse.substr(maturity_pos + MATURITY.length()); + target_textbox->setText(text_before_rating); -// virtual -LLFloaterLand::~LLFloaterLand() -{ - LLViewerParcelMgr::getInstance()->removeObserver( sObserver ); - delete sObserver; - sObserver = NULL; -} + target_textbox->appendImageSegment(style); -// public -void LLFloaterLand::refresh() -{ - mPanelGeneral->refresh(); - mPanelObjects->refresh(); - mPanelOptions->refresh(); - mPanelAudio->refresh(); - mPanelMedia->refresh(); - mPanelAccess->refresh(); - mPanelCovenant->refresh(); + target_textbox->appendText(LLViewerParcelMgr::getInstance()->getSelectionRegion()->getSimAccessString(), false); + target_textbox->appendText(text_after_rating, false); } - - -void* LLFloaterLand::createPanelLandGeneral(void* data) +void send_return_objects_message(S32 parcel_local_id, S32 return_type, + uuid_list_t* owner_ids = NULL) { - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelGeneral = new LLPanelLandGeneral(self->mParcel); - return self->mPanelGeneral; -} + LLMessageSystem *msg = gMessageSystem; -// static -void* LLFloaterLand::createPanelLandCovenant(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelCovenant = new LLPanelLandCovenant(self->mParcel); - return self->mPanelCovenant; -} + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (!region) return; + msg->newMessageFast(_PREHASH_ParcelReturnObjects); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); + msg->addU32Fast(_PREHASH_ReturnType, (U32) return_type); -// static -void* LLFloaterLand::createPanelLandObjects(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelObjects = new LLPanelLandObjects(self->mParcel); - return self->mPanelObjects; -} + // Dummy task id, not used + msg->nextBlock("TaskIDs"); + msg->addUUID("TaskID", LLUUID::null); -// static -void* LLFloaterLand::createPanelLandOptions(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelOptions = new LLPanelLandOptions(self->mParcel); - return self->mPanelOptions; -} + // Throw all return ids into the packet. + // TODO: Check for too many ids. + if (owner_ids) + { + uuid_list_t::iterator end = owner_ids->end(); + for (uuid_list_t::iterator it = owner_ids->begin(); + it != end; + ++it) + { + msg->nextBlockFast(_PREHASH_OwnerIDs); + msg->addUUIDFast(_PREHASH_OwnerID, (*it)); + } + } + else + { + msg->nextBlockFast(_PREHASH_OwnerIDs); + msg->addUUIDFast(_PREHASH_OwnerID, LLUUID::null); + } -// static -void* LLFloaterLand::createPanelLandAudio(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelAudio = new LLPanelLandAudio(self->mParcel); - return self->mPanelAudio; + msg->sendReliable(region->getHost()); } -// static -void* LLFloaterLand::createPanelLandMedia(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelMedia = new LLPanelLandMedia(self->mParcel); - return self->mPanelMedia; -} -// static -void* LLFloaterLand::createPanelLandAccess(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelAccess = new LLPanelLandAccess(self->mParcel); - return self->mPanelAccess; -} +LLPointer<LLParcelSelection> LLPanelLandGeneral::sSelectionForBuyPass = NULL; //--------------------------------------------------------------------------- // LLPanelLandGeneral @@ -1063,6 +991,8 @@ void LLPanelLandGeneral::onClickStopSellLand(void* data) LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate(parcel); } + + //--------------------------------------------------------------------------- // LLPanelLandObjects //--------------------------------------------------------------------------- @@ -1308,66 +1238,6 @@ void LLPanelLandObjects::draw() LLPanel::draw(); } -void send_other_clean_time_message(S32 parcel_local_id, S32 other_clean_time) -{ - LLMessageSystem *msg = gMessageSystem; - - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (!region) return; - - msg->newMessageFast(_PREHASH_ParcelSetOtherCleanTime); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); - msg->addS32Fast(_PREHASH_OtherCleanTime, other_clean_time); - - msg->sendReliable(region->getHost()); -} - -void send_return_objects_message(S32 parcel_local_id, S32 return_type, - uuid_list_t* owner_ids = NULL) -{ - LLMessageSystem *msg = gMessageSystem; - - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (!region) return; - - msg->newMessageFast(_PREHASH_ParcelReturnObjects); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); - msg->addU32Fast(_PREHASH_ReturnType, (U32) return_type); - - // Dummy task id, not used - msg->nextBlock("TaskIDs"); - msg->addUUID("TaskID", LLUUID::null); - - // Throw all return ids into the packet. - // TODO: Check for too many ids. - if (owner_ids) - { - uuid_list_t::iterator end = owner_ids->end(); - for (uuid_list_t::iterator it = owner_ids->begin(); - it != end; - ++it) - { - msg->nextBlockFast(_PREHASH_OwnerIDs); - msg->addUUIDFast(_PREHASH_OwnerID, (*it)); - } - } - else - { - msg->nextBlockFast(_PREHASH_OwnerIDs); - msg->addUUIDFast(_PREHASH_OwnerID, LLUUID::null); - } - - msg->sendReliable(region->getHost()); -} - bool LLPanelLandObjects::callbackReturnOwnerObjects(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); @@ -1804,6 +1674,52 @@ void LLPanelLandObjects::onCommitClean(LLUICtrl *caller, void* user_data) } +class LLPanelLandOptions + : public LLPanel +{ +public: + LLPanelLandOptions(LLSafeHandle<LLParcelSelection>& parcelp); + virtual ~LLPanelLandOptions(); + /*virtual*/ BOOL postBuild(); + /*virtual*/ void draw(); + /*virtual*/ void refresh(); + +private: + // Refresh the "show in search" checkbox and category selector. + void refreshSearch(); + + static void onCommitAny(LLUICtrl* ctrl, void *userdata); + static void onClickSet(void* userdata); + static void onClickClear(void* userdata); + +private: + LLCheckBoxCtrl* mCheckEditObjects; + LLCheckBoxCtrl* mCheckEditGroupObjects; + LLCheckBoxCtrl* mCheckAllObjectEntry; + LLCheckBoxCtrl* mCheckGroupObjectEntry; + LLCheckBoxCtrl* mCheckSafe; + LLCheckBoxCtrl* mCheckFly; + LLCheckBoxCtrl* mCheckGroupScripts; + LLCheckBoxCtrl* mCheckOtherScripts; + + LLCheckBoxCtrl* mCheckShowDirectory; + LLComboBox* mCategoryCombo; + LLComboBox* mLandingTypeCombo; + + LLTextureCtrl* mSnapshotCtrl; + + LLTextBox* mLocationText; + LLButton* mSetBtn; + LLButton* mClearBtn; + + LLCheckBoxCtrl *mMatureCtrl; + LLCheckBoxCtrl *mPushRestrictionCtrl; + LLCheckBoxCtrl *mSeeAvatarsCtrl; + + LLSafeHandle<LLParcelSelection>& mParcel; +}; + + //--------------------------------------------------------------------------- // LLPanelLandOptions //--------------------------------------------------------------------------- @@ -2320,6 +2236,38 @@ void LLPanelLandOptions::onClickClear(void* userdata) } +class LLPanelLandAccess + : public LLPanel +{ +public: + LLPanelLandAccess(LLSafeHandle<LLParcelSelection>& parcelp); + virtual ~LLPanelLandAccess(); + void refresh(); + void refresh_ui(); + void refreshNames(); + virtual void draw(); + + static void onCommitPublicAccess(LLUICtrl* ctrl, void *userdata); + static void onCommitAny(LLUICtrl* ctrl, void *userdata); + static void onCommitGroupCheck(LLUICtrl* ctrl, void *userdata); + static void onClickRemoveAccess(void*); + static void onClickRemoveBanned(void*); + + virtual BOOL postBuild(); + + void onClickAddAccess(); + void onClickAddBanned(); + void callbackAvatarCBBanned(const uuid_vec_t& ids); + void callbackAvatarCBAccess(const uuid_vec_t& ids); + +protected: + LLNameListCtrl* mListAccess; + LLNameListCtrl* mListBanned; + + LLSafeHandle<LLParcelSelection>& mParcel; +}; + + //--------------------------------------------------------------------------- // LLPanelLandAccess //--------------------------------------------------------------------------- @@ -2398,7 +2346,7 @@ void LLPanelLandAccess::refresh() getChild<LLUICtrl>("AccessList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",count)); getChild<LLUICtrl>("AccessList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",PARCEL_MAX_ACCESS_LIST)); - for (access_map_const_iterator cit = parcel->mAccessList.begin(); + for (LLAccessEntry::map::const_iterator cit = parcel->mAccessList.begin(); cit != parcel->mAccessList.end(); ++cit) { const LLAccessEntry& entry = (*cit).second; @@ -2444,7 +2392,7 @@ void LLPanelLandAccess::refresh() getChild<LLUICtrl>("BannedList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",count)); getChild<LLUICtrl>("BannedList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",PARCEL_MAX_ACCESS_LIST)); - for (access_map_const_iterator cit = parcel->mBanList.begin(); + for (LLAccessEntry::map::const_iterator cit = parcel->mBanList.begin(); cit != parcel->mBanList.end(); ++cit) { const LLAccessEntry& entry = (*cit).second; @@ -2777,8 +2725,8 @@ void LLPanelLandAccess::callbackAvatarCBAccess(const uuid_vec_t& ids) LLUUID id = ids[0]; LLParcel* parcel = mParcel->getParcel(); if (parcel) - { - parcel->addToAccessList(id, 0); + { + parcel->addToAccessList(id, 0); LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(AL_ACCESS); refresh(); } @@ -2860,6 +2808,7 @@ void LLPanelLandAccess::onClickRemoveBanned(void* data) } } + //--------------------------------------------------------------------------- // LLPanelLandCovenant //--------------------------------------------------------------------------- @@ -2973,51 +2922,321 @@ void LLPanelLandCovenant::updateEstateOwnerName(const std::string& name) } } -// inserts maturity info(icon and text) into target textbox -// names_floater - pointer to floater which contains strings with maturity icons filenames -// str_to_parse is string in format "txt1[MATURITY]txt2" where maturity icon and text will be inserted instead of [MATURITY] -void insert_maturity_into_textbox(LLTextBox* target_textbox, LLFloater* names_floater, std::string str_to_parse) +class LLPanelLandExperiences + : public LLPanel { - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (!region) +public: + LLPanelLandExperiences(LLSafeHandle<LLParcelSelection>& parcelp); + virtual BOOL postBuild(); + void refresh(); + + void experienceAdded(const LLUUID& id, U32 xp_type, U32 access_type); + void experienceRemoved(const LLUUID& id, U32 access_type); +protected: + LLPanelExperienceListEditor* setupList( const char* control_name, U32 xp_type, U32 access_type ); + void refreshPanel(LLPanelExperienceListEditor* panel, U32 xp_type); + + LLSafeHandle<LLParcelSelection>& mParcel; + + + LLPanelExperienceListEditor* mAllowed; + LLPanelExperienceListEditor* mBlocked; +}; + +LLPanelLandExperiences::LLPanelLandExperiences( LLSafeHandle<LLParcelSelection>& parcelp ) + : mParcel(parcelp) +{ + +} + + +BOOL LLPanelLandExperiences::postBuild() +{ + mAllowed = setupList("panel_allowed", EXPERIENCE_KEY_TYPE_ALLOWED, AL_ALLOW_EXPERIENCE); + mBlocked = setupList("panel_blocked", EXPERIENCE_KEY_TYPE_BLOCKED, AL_BLOCK_EXPERIENCE); + + // only non-grid-wide experiences + mAllowed->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_GRID)); + + // only grid-wide experiences + mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithoutProperty, _1, LLExperienceCache::PROPERTY_GRID)); + // but not privileged ones + mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_PRIVILEGED)); + + getChild<LLLayoutPanel>("trusted_layout_panel")->setVisible(FALSE); + getChild<LLTextBox>("experiences_help_text")->setVisible(FALSE); + getChild<LLTextBox>("allowed_text_help")->setText(getString("allowed_parcel_text")); + getChild<LLTextBox>("blocked_text_help")->setText(getString("blocked_parcel_text")); + + return LLPanel::postBuild(); +} + +LLPanelExperienceListEditor* LLPanelLandExperiences::setupList( const char* control_name, U32 xp_type, U32 access_type ) +{ + LLPanelExperienceListEditor* child = findChild<LLPanelExperienceListEditor>(control_name); + if(child) + { + child->getChild<LLTextBox>("text_name")->setText(child->getString(control_name)); + child->setMaxExperienceIDs(PARCEL_MAX_EXPERIENCE_LIST); + child->setAddedCallback(boost::bind(&LLPanelLandExperiences::experienceAdded, this, _1, xp_type, access_type)); + child->setRemovedCallback(boost::bind(&LLPanelLandExperiences::experienceRemoved, this, _1, access_type)); + } + + return child; +} + +void LLPanelLandExperiences::experienceAdded( const LLUUID& id, U32 xp_type, U32 access_type ) +{ + LLParcel* parcel = mParcel->getParcel(); + if (parcel) + { + parcel->setExperienceKeyType(id, xp_type); + LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(access_type); + refresh(); + } +} + +void LLPanelLandExperiences::experienceRemoved( const LLUUID& id, U32 access_type ) +{ + LLParcel* parcel = mParcel->getParcel(); + if (parcel) + { + parcel->setExperienceKeyType(id, EXPERIENCE_KEY_TYPE_NONE); + LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(access_type); + refresh(); + } +} + +void LLPanelLandExperiences::refreshPanel(LLPanelExperienceListEditor* panel, U32 xp_type) +{ + LLParcel *parcel = mParcel->getParcel(); + + // Display options + if (panel == NULL) + { return; + } + if (parcel == NULL) + { + // disable the panel + panel->setEnabled(FALSE); + panel->setExperienceIds(LLSD::emptyArray()); + } + else + { + // enable the panel + panel->setEnabled(TRUE); + LLAccessEntry::map entries = parcel->getExperienceKeysByType(xp_type); + LLAccessEntry::map::iterator it = entries.begin(); + LLSD ids = LLSD::emptyArray(); + for (/**/; it != entries.end(); ++it) + { + ids.append(it->second.mID); + } + panel->setExperienceIds(ids); + panel->setReadonly(!LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_OPTIONS)); + panel->refreshExperienceCounter(); + } +} - LLStyle::Params style; +void LLPanelLandExperiences::refresh() +{ + refreshPanel(mAllowed, EXPERIENCE_KEY_TYPE_ALLOWED); + refreshPanel(mBlocked, EXPERIENCE_KEY_TYPE_BLOCKED); +} - U8 sim_access = region->getSimAccess(); +LLParcel* LLFloaterLand::getCurrentSelectedParcel() +{ + return mParcel->getParcel(); +}; - switch(sim_access) +//static +LLPanelLandObjects* LLFloaterLand::getCurrentPanelLandObjects() +{ + LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance<LLFloaterLand>("about_land"); + if(land_instance) { - case SIM_ACCESS_PG: - style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_general"))); - break; + return land_instance->mPanelObjects; + } + else + { + return NULL; + } +} - case SIM_ACCESS_ADULT: - style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_adult"))); - break; +//static +LLPanelLandCovenant* LLFloaterLand::getCurrentPanelLandCovenant() +{ + LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance<LLFloaterLand>("about_land"); + if(land_instance) + { + return land_instance->mPanelCovenant; + } + else + { + return NULL; + } +} - case SIM_ACCESS_MATURE: - style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_moderate"))); - break; +// static +void LLFloaterLand::refreshAll() +{ + LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance<LLFloaterLand>("about_land"); + if(land_instance) + { + land_instance->refresh(); + } +} - default: - break; +void LLFloaterLand::onOpen(const LLSD& key) +{ + // moved from triggering show instance in llviwermenu.cpp + + if (LLViewerParcelMgr::getInstance()->selectionEmpty()) + { + LLViewerParcelMgr::getInstance()->selectParcelAt(gAgent.getPositionGlobal()); } + + // Done automatically when the selected parcel's properties arrive + // (and hence we have the local id). + // LLViewerParcelMgr::getInstance()->sendParcelAccessListRequest(AL_ACCESS | AL_BAN | AL_RENTER); - size_t maturity_pos = str_to_parse.find(MATURITY); + mParcel = LLViewerParcelMgr::getInstance()->getFloatingParcelSelection(); - if (maturity_pos == std::string::npos) + // Refresh even if not over a region so we don't get an + // uninitialized dialog. The dialog is 0-region aware. + refresh(); +} + +void LLFloaterLand::onVisibilityChanged(const LLSD& visible) +{ + if (!visible.asBoolean()) { - return; + // Might have been showing owned objects + LLSelectMgr::getInstance()->unhighlightAll(); + + // Save which panel we had open + sLastTab = mTabLand->getCurrentPanelIndex(); } +} - std::string text_before_rating = str_to_parse.substr(0, maturity_pos); - std::string text_after_rating = str_to_parse.substr(maturity_pos + MATURITY.length()); - target_textbox->setText(text_before_rating); +LLFloaterLand::LLFloaterLand(const LLSD& seed) +: LLFloater(seed) +{ + mFactoryMap["land_general_panel"] = LLCallbackMap(createPanelLandGeneral, this); + mFactoryMap["land_covenant_panel"] = LLCallbackMap(createPanelLandCovenant, this); + mFactoryMap["land_objects_panel"] = LLCallbackMap(createPanelLandObjects, this); + mFactoryMap["land_options_panel"] = LLCallbackMap(createPanelLandOptions, this); + mFactoryMap["land_audio_panel"] = LLCallbackMap(createPanelLandAudio, this); + mFactoryMap["land_media_panel"] = LLCallbackMap(createPanelLandMedia, this); + mFactoryMap["land_access_panel"] = LLCallbackMap(createPanelLandAccess, this); + mFactoryMap["land_experiences_panel"] = LLCallbackMap(createPanelLandExperiences, this); - target_textbox->appendImageSegment(style); + sObserver = new LLParcelSelectionObserver(); + LLViewerParcelMgr::getInstance()->addObserver( sObserver ); +} - target_textbox->appendText(LLViewerParcelMgr::getInstance()->getSelectionRegion()->getSimAccessString(), false); - target_textbox->appendText(text_after_rating, false); +BOOL LLFloaterLand::postBuild() +{ + setVisibleCallback(boost::bind(&LLFloaterLand::onVisibilityChanged, this, _2)); + + LLTabContainer* tab = getChild<LLTabContainer>("landtab"); + + mTabLand = (LLTabContainer*) tab; + + if (tab) + { + tab->selectTab(sLastTab); + } + + return TRUE; +} + + +// virtual +LLFloaterLand::~LLFloaterLand() +{ + LLViewerParcelMgr::getInstance()->removeObserver( sObserver ); + delete sObserver; + sObserver = NULL; +} + +// public +void LLFloaterLand::refresh() +{ + mPanelGeneral->refresh(); + mPanelObjects->refresh(); + mPanelOptions->refresh(); + mPanelAudio->refresh(); + mPanelMedia->refresh(); + mPanelAccess->refresh(); + mPanelCovenant->refresh(); + mPanelExperiences->refresh(); +} + + + +void* LLFloaterLand::createPanelLandGeneral(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelGeneral = new LLPanelLandGeneral(self->mParcel); + return self->mPanelGeneral; +} + +// static +void* LLFloaterLand::createPanelLandCovenant(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelCovenant = new LLPanelLandCovenant(self->mParcel); + return self->mPanelCovenant; +} + + +// static +void* LLFloaterLand::createPanelLandObjects(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelObjects = new LLPanelLandObjects(self->mParcel); + return self->mPanelObjects; +} + +// static +void* LLFloaterLand::createPanelLandOptions(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelOptions = new LLPanelLandOptions(self->mParcel); + return self->mPanelOptions; +} + +// static +void* LLFloaterLand::createPanelLandAudio(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelAudio = new LLPanelLandAudio(self->mParcel); + return self->mPanelAudio; +} + +// static +void* LLFloaterLand::createPanelLandMedia(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelMedia = new LLPanelLandMedia(self->mParcel); + return self->mPanelMedia; +} + +// static +void* LLFloaterLand::createPanelLandAccess(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelAccess = new LLPanelLandAccess(self->mParcel); + return self->mPanelAccess; +} + +// static +void* LLFloaterLand::createPanelLandExperiences(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelExperiences = new LLPanelLandExperiences(self->mParcel); + return self->mPanelExperiences; } diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h index 1d9bd33720..ae9eecd374 100755 --- a/indra/newview/llfloaterland.h +++ b/indra/newview/llfloaterland.h @@ -66,6 +66,7 @@ class LLPanelLandBan; class LLPanelLandRenters; class LLPanelLandCovenant; class LLParcel; +class LLPanelLandExperiences; class LLFloaterLand : public LLFloater @@ -101,6 +102,7 @@ protected: static void* createPanelLandAudio(void* data); static void* createPanelLandMedia(void* data); static void* createPanelLandAccess(void* data); + static void* createPanelLandExperiences(void* data); static void* createPanelLandBan(void* data); @@ -116,6 +118,7 @@ protected: LLPanelLandMedia* mPanelMedia; LLPanelLandAccess* mPanelAccess; LLPanelLandCovenant* mPanelCovenant; + LLPanelLandExperiences* mPanelExperiences; LLSafeHandle<LLParcelSelection> mParcel; @@ -306,85 +309,6 @@ protected: LLSafeHandle<LLParcelSelection>& mParcel; }; - -class LLPanelLandOptions -: public LLPanel -{ -public: - LLPanelLandOptions(LLSafeHandle<LLParcelSelection>& parcelp); - virtual ~LLPanelLandOptions(); - /*virtual*/ BOOL postBuild(); - /*virtual*/ void draw(); - /*virtual*/ void refresh(); - -private: - // Refresh the "show in search" checkbox and category selector. - void refreshSearch(); - - static void onCommitAny(LLUICtrl* ctrl, void *userdata); - static void onClickSet(void* userdata); - static void onClickClear(void* userdata); - -private: - LLCheckBoxCtrl* mCheckEditObjects; - LLCheckBoxCtrl* mCheckEditGroupObjects; - LLCheckBoxCtrl* mCheckAllObjectEntry; - LLCheckBoxCtrl* mCheckGroupObjectEntry; - LLCheckBoxCtrl* mCheckSafe; - LLCheckBoxCtrl* mCheckFly; - LLCheckBoxCtrl* mCheckGroupScripts; - LLCheckBoxCtrl* mCheckOtherScripts; - - LLCheckBoxCtrl* mCheckShowDirectory; - LLComboBox* mCategoryCombo; - LLComboBox* mLandingTypeCombo; - - LLTextureCtrl* mSnapshotCtrl; - - LLTextBox* mLocationText; - LLButton* mSetBtn; - LLButton* mClearBtn; - - LLCheckBoxCtrl *mMatureCtrl; - LLCheckBoxCtrl *mPushRestrictionCtrl; - LLCheckBoxCtrl *mSeeAvatarsCtrl; - - LLSafeHandle<LLParcelSelection>& mParcel; -}; - - -class LLPanelLandAccess -: public LLPanel -{ -public: - LLPanelLandAccess(LLSafeHandle<LLParcelSelection>& parcelp); - virtual ~LLPanelLandAccess(); - void refresh(); - void refresh_ui(); - void refreshNames(); - virtual void draw(); - - static void onCommitPublicAccess(LLUICtrl* ctrl, void *userdata); - static void onCommitAny(LLUICtrl* ctrl, void *userdata); - static void onCommitGroupCheck(LLUICtrl* ctrl, void *userdata); - static void onClickRemoveAccess(void*); - static void onClickRemoveBanned(void*); - - virtual BOOL postBuild(); - - void onClickAddAccess(); - void onClickAddBanned(); - void callbackAvatarCBBanned(const uuid_vec_t& ids); - void callbackAvatarCBAccess(const uuid_vec_t& ids); - -protected: - LLNameListCtrl* mListAccess; - LLNameListCtrl* mListBanned; - - LLSafeHandle<LLParcelSelection>& mParcel; -}; - - class LLPanelLandCovenant : public LLPanel { diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 5e9b25b474..f70152fc59 100755 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -92,6 +92,11 @@ #include "llagentui.h" #include "llmeshrepository.h" #include "llfloaterregionrestarting.h" +#include "llpanelexperiencelisteditor.h" +#include <boost/function.hpp> +#include "llpanelexperiencepicker.h" +#include "llexperiencecache.h" +#include "llpanelexperiences.h" const S32 TERRAIN_TEXTURE_COUNT = 4; const S32 CORNER_COUNT = 4; @@ -126,6 +131,18 @@ public: const sparam_t& strings); }; +class LLDispatchSetEstateExperience : public LLDispatchHandler +{ +public: + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings); + + LLSD getIDs( sparam_t::const_iterator it, sparam_t::const_iterator end, S32 count ); +}; + /* void unpack_request_params( @@ -215,6 +232,14 @@ BOOL LLFloaterRegionInfo::postBuild() panel->buildFromFile("panel_region_debug.xml"); mTab->addTabPanel(panel); + if(!gAgent.getRegion()->getCapability("RegionExperiences").empty()) + { + panel = new LLPanelRegionExperiences; + mInfoPanels.push_back(panel); + panel->buildFromFile("panel_region_experiences.xml"); + mTab->addTabPanel(panel); + } + gMessageSystem->setHandlerFunc( "EstateOwnerMessage", &processEstateOwnerRequest); @@ -445,6 +470,16 @@ LLPanelRegionTerrainInfo* LLFloaterRegionInfo::getPanelRegionTerrain() return panel; } +LLPanelRegionExperiences* LLFloaterRegionInfo::getPanelExperiences() +{ + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance<LLFloaterRegionInfo>("region_info"); + if (!floater) return NULL; + LLTabContainer* tab = floater->getChild<LLTabContainer>("region_panels"); + return (LLPanelRegionExperiences*)tab->getChild<LLPanel>("Experiences"); +} + + + void LLFloaterRegionInfo::onTabSelected(const LLSD& param) { LLPanel* active_panel = getChild<LLPanel>(param.asString()); @@ -1373,6 +1408,11 @@ void LLPanelEstateInfo::initDispatch(LLDispatcher& dispatch) static LLDispatchSetEstateAccess set_access; dispatch.addHandler(name, &set_access); + + name.assign("setexperience"); + static LLDispatchSetEstateExperience set_experience; + dispatch.addHandler(name, &set_experience); + estate_dispatch_initialized = true; } @@ -2882,6 +2922,56 @@ bool LLDispatchSetEstateAccess::operator()( return true; } +LLSD LLDispatchSetEstateExperience::getIDs( sparam_t::const_iterator it, sparam_t::const_iterator end, S32 count ) +{ + LLSD idList = LLSD::emptyArray(); + LLUUID id; + while(count--> 0) + { + memcpy(id.mData, (*(it++)).data(), UUID_BYTES); + idList.append(id); + } + return idList; +} + +// key = "setexperience" +// strings[0] = str(estate_id) +// strings[1] = str(send_to_agent_only) +// strings[2] = str(num blocked) +// strings[3] = str(num trusted) +// strings[4] = str(num allowed) +// strings[8] = bin(uuid) ... +// ... +bool LLDispatchSetEstateExperience::operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) +{ + LLPanelRegionExperiences* panel = LLFloaterRegionInfo::getPanelExperiences(); + if (!panel) return true; + + sparam_t::const_iterator it = strings.begin(); + ++it; // U32 estate_id = strtol((*it).c_str(), NULL, 10); + ++it; // U32 send_to_agent_only = strtoul((*(++it)).c_str(), NULL, 10); + + LLUUID id; + S32 num_blocked = strtol((*(it++)).c_str(), NULL, 10); + S32 num_trusted = strtol((*(it++)).c_str(), NULL, 10); + S32 num_allowed = strtol((*(it++)).c_str(), NULL, 10); + + LLSD ids = LLSD::emptyMap() + .with("blocked", getIDs(it, strings.end(), num_blocked)) + .with("trusted", getIDs(it + (num_blocked), strings.end(), num_trusted)) + .with("allowed", getIDs(it + (num_blocked+num_trusted), strings.end(), num_allowed)); + + panel->processResponse(ids); + + return true; +} + + + LLPanelEnvironmentInfo::LLPanelEnvironmentInfo() : mEnableEditing(false), mRegionSettingsRadioGroup(NULL), @@ -3473,3 +3563,284 @@ void LLPanelEnvironmentInfo::onRegionSettingsApplied(bool ok) LLEnvManagerNew::instance().requestRegionSettings(); } } + +BOOL LLPanelRegionExperiences::postBuild() +{ + mAllowed = setupList("panel_allowed", ESTATE_EXPERIENCE_ALLOWED_ADD, ESTATE_EXPERIENCE_ALLOWED_REMOVE); + mTrusted = setupList("panel_trusted", ESTATE_EXPERIENCE_TRUSTED_ADD, ESTATE_EXPERIENCE_TRUSTED_REMOVE); + mBlocked = setupList("panel_blocked", ESTATE_EXPERIENCE_BLOCKED_ADD, ESTATE_EXPERIENCE_BLOCKED_REMOVE); + + getChild<LLLayoutPanel>("trusted_layout_panel")->setVisible(TRUE); + getChild<LLTextBox>("experiences_help_text")->setText(getString("estate_caption")); + getChild<LLTextBox>("trusted_text_help")->setText(getString("trusted_estate_text")); + getChild<LLTextBox>("allowed_text_help")->setText(getString("allowed_estate_text")); + getChild<LLTextBox>("blocked_text_help")->setText(getString("blocked_estate_text")); + + return LLPanelRegionInfo::postBuild(); +} + +LLPanelExperienceListEditor* LLPanelRegionExperiences::setupList( const char* control_name, U32 add_id, U32 remove_id ) +{ + LLPanelExperienceListEditor* child = findChild<LLPanelExperienceListEditor>(control_name); + if(child) + { + child->getChild<LLTextBox>("text_name")->setText(child->getString(control_name)); + child->setMaxExperienceIDs(ESTATE_MAX_EXPERIENCE_IDS); + child->setAddedCallback( boost::bind(&LLPanelRegionExperiences::itemChanged, this, add_id, _1)); + child->setRemovedCallback(boost::bind(&LLPanelRegionExperiences::itemChanged, this, remove_id, _1)); + } + + return child; +} + + +void LLPanelRegionExperiences::processResponse( const LLSD& content ) +{ + if(content.has("default")) + { + mDefaultExperience = content["default"].asUUID(); + } + + mAllowed->setExperienceIds(content["allowed"]); + mBlocked->setExperienceIds(content["blocked"]); + + LLSD trusted = content["trusted"]; + if(mDefaultExperience.notNull()) + { + mTrusted->setStickyFunction(boost::bind(LLPanelExperiencePicker::FilterMatching, _1, mDefaultExperience)); + trusted.append(mDefaultExperience); + } + + mTrusted->setExperienceIds(trusted); + + mAllowed->refreshExperienceCounter(); + mBlocked->refreshExperienceCounter(); + mTrusted->refreshExperienceCounter(); + +} + + +class LLRegionExperienceResponder : public LLHTTPClient::Responder +{ +public: + typedef boost::function<void (const LLSD&)> callback_t; + + callback_t mCallback; + + LLRegionExperienceResponder(callback_t callback) : mCallback(callback) { } + +protected: + /*virtual*/ void httpSuccess() + { + mCallback(getContent()); + } + + /*virtual*/ void httpFailure() + { + LL_WARNS() << "experience responder failed [status:" << getStatus() << "]: " << getContent() << LL_ENDL; + } +}; + + +// Used for both access add and remove operations, depending on the flag +// passed in (ESTATE_EXPERIENCE_ALLOWED_ADD, ESTATE_EXPERIENCE_ALLOWED_REMOVE, etc.) +// static +bool LLPanelRegionExperiences::experienceCoreConfirm(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + const U32 originalFlags = (U32)notification["payload"]["operation"].asInteger(); + + LLViewerRegion* region = gAgent.getRegion(); + + LLSD::array_const_iterator end_it = notification["payload"]["allowed_ids"].endArray(); + + for (LLSD::array_const_iterator iter = notification["payload"]["allowed_ids"].beginArray(); + iter != end_it; + iter++) + { + U32 flags = originalFlags; + if (iter + 1 != end_it) + flags |= ESTATE_ACCESS_NO_REPLY; + + const LLUUID id = iter->asUUID(); + switch(option) + { + case 0: + // This estate + sendEstateExperienceDelta(flags, id); + break; + case 1: + { + // All estates, either than I own or manage for this owner. + // This will be verified on simulator. JC + if (!region) break; + if (region->getOwner() == gAgent.getID() + || gAgent.isGodlike()) + { + flags |= ESTATE_ACCESS_APPLY_TO_ALL_ESTATES; + sendEstateExperienceDelta(flags, id); + } + else if (region->isEstateManager()) + { + flags |= ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES; + sendEstateExperienceDelta(flags, id); + } + break; + } + case 2: + default: + break; + } + } + return false; +} + + +// Send the actual "estateexperiencedelta" message +void LLPanelRegionExperiences::sendEstateExperienceDelta(U32 flags, const LLUUID& experience_id) +{ + strings_t str(3, std::string()); + gAgent.getID().toString(str[0]); + str[1] = llformat("%u", flags); + experience_id.toString(str[2]); + + LLPanelRegionExperiences* panel = LLFloaterRegionInfo::getPanelExperiences(); + if (panel) + { + panel->sendEstateOwnerMessage(gMessageSystem, "estateexperiencedelta", LLFloaterRegionInfo::getLastInvoice(), str); + } +} + + +void LLPanelRegionExperiences::infoCallback(LLHandle<LLPanelRegionExperiences> handle, const LLSD& content) +{ + if(handle.isDead()) + return; + + LLPanelRegionExperiences* floater = handle.get(); + if (floater) + { + floater->processResponse(content); + } +} + + +bool LLPanelRegionExperiences::refreshFromRegion(LLViewerRegion* region) +{ + BOOL allow_modify = gAgent.isGodlike() || (region && region->canManageEstate()); + + mAllowed->loading(); + mAllowed->setReadonly(!allow_modify); + // remove grid-wide experiences + mAllowed->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_GRID)); + // remove default experience + mAllowed->addFilter(boost::bind(LLPanelExperiencePicker::FilterMatching, _1, mDefaultExperience)); + + mBlocked->loading(); + mBlocked->setReadonly(!allow_modify); + // only grid-wide experiences + mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithoutProperty, _1, LLExperienceCache::PROPERTY_GRID)); + // but not privileged ones + mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_PRIVILEGED)); + // remove default experience + mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterMatching, _1, mDefaultExperience)); + + mTrusted->loading(); + mTrusted->setReadonly(!allow_modify); + + std::string url = region->getCapability("RegionExperiences"); + if (!url.empty()) + { + LLHTTPClient::get(url, new LLRegionExperienceResponder(boost::bind(&LLPanelRegionExperiences::infoCallback, + getDerivedHandle<LLPanelRegionExperiences>(), _1))); + } + return LLPanelRegionInfo::refreshFromRegion(region); +} + +LLSD LLPanelRegionExperiences::addIds(LLPanelExperienceListEditor* panel) +{ + LLSD ids; + const uuid_list_t& id_list = panel->getExperienceIds(); + for(uuid_list_t::const_iterator it = id_list.begin(); it != id_list.end(); ++it) + { + ids.append(*it); + } + return ids; +} + + +BOOL LLPanelRegionExperiences::sendUpdate() +{ + LLViewerRegion* region = gAgent.getRegion(); + std::string url = region->getCapability("RegionExperiences"); + if (!url.empty()) + { + LLSD content; + + content["allowed"]=addIds(mAllowed); + content["blocked"]=addIds(mBlocked); + content["trusted"]=addIds(mTrusted); + + LLHTTPClient::post(url, content, new LLRegionExperienceResponder(boost::bind(&LLPanelRegionExperiences::infoCallback, + getDerivedHandle<LLPanelRegionExperiences>(), _1))); + } + + return TRUE; +} + +void LLPanelRegionExperiences::itemChanged( U32 event_type, const LLUUID& id ) +{ + std::string dialog_name; + switch (event_type) + { + case ESTATE_EXPERIENCE_ALLOWED_ADD: + dialog_name = "EstateAllowedExperienceAdd"; + break; + + case ESTATE_EXPERIENCE_ALLOWED_REMOVE: + dialog_name = "EstateAllowedExperienceRemove"; + break; + + case ESTATE_EXPERIENCE_TRUSTED_ADD: + dialog_name = "EstateTrustedExperienceAdd"; + break; + + case ESTATE_EXPERIENCE_TRUSTED_REMOVE: + dialog_name = "EstateTrustedExperienceRemove"; + break; + + case ESTATE_EXPERIENCE_BLOCKED_ADD: + dialog_name = "EstateBlockedExperienceAdd"; + break; + + case ESTATE_EXPERIENCE_BLOCKED_REMOVE: + dialog_name = "EstateBlockedExperienceRemove"; + break; + + default: + return; + } + + LLSD payload; + payload["operation"] = (S32)event_type; + payload["dialog_name"] = dialog_name; + payload["allowed_ids"].append(id); + + LLSD args; + args["ALL_ESTATES"] = all_estates_text(); + + LLNotification::Params params(dialog_name); + params.payload(payload) + .substitutions(args) + .functor.function(LLPanelRegionExperiences::experienceCoreConfirm); + if (LLPanelEstateInfo::isLindenEstate()) + { + LLNotifications::instance().forceResponse(params, 0); + } + else + { + LLNotifications::instance().add(params); + } + + onChangeAnything(); +} diff --git a/indra/newview/llfloaterregioninfo.h b/indra/newview/llfloaterregioninfo.h index 5bc4273d5f..15eeb5ed7e 100755 --- a/indra/newview/llfloaterregioninfo.h +++ b/indra/newview/llfloaterregioninfo.h @@ -60,6 +60,9 @@ class LLPanelRegionDebugInfo; class LLPanelRegionTerrainInfo; class LLPanelEstateInfo; class LLPanelEstateCovenant; +class LLPanelExperienceListEditor; +class LLPanelExperiences; +class LLPanelRegionExperiences; class LLEventTimer; class LLEnvironmentSettings; @@ -90,6 +93,7 @@ public: static LLPanelEstateInfo* getPanelEstate(); static LLPanelEstateCovenant* getPanelCovenant(); static LLPanelRegionTerrainInfo* getPanelRegionTerrain(); + static LLPanelRegionExperiences* getPanelExperiences(); // from LLPanel virtual void refresh(); @@ -451,4 +455,34 @@ private: LLComboBox* mDayCyclePresetCombo; }; +class LLPanelRegionExperiences : public LLPanelRegionInfo +{ + LOG_CLASS(LLPanelEnvironmentInfo); + +public: + LLPanelRegionExperiences(){} + /*virtual*/ BOOL postBuild(); + virtual BOOL sendUpdate(); + + static bool experienceCoreConfirm(const LLSD& notification, const LLSD& response); + static void sendEstateExperienceDelta(U32 flags, const LLUUID& agent_id); + + static void infoCallback(LLHandle<LLPanelRegionExperiences> handle, const LLSD& content); + bool refreshFromRegion(LLViewerRegion* region); + void sendPurchaseRequest()const; + void processResponse( const LLSD& content ); +private: + void refreshRegionExperiences(); + + LLPanelExperienceListEditor* setupList(const char* control_name, U32 add_id, U32 remove_id); + static LLSD addIds( LLPanelExperienceListEditor* panel ); + + void itemChanged(U32 event_type, const LLUUID& id); + + LLPanelExperienceListEditor* mTrusted; + LLPanelExperienceListEditor* mAllowed; + LLPanelExperienceListEditor* mBlocked; + LLUUID mDefaultExperience; +}; + #endif diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index a3b9713e3e..7bba67627e 100755 --- a/indra/newview/llfloaterreporter.cpp +++ b/indra/newview/llfloaterreporter.cpp @@ -81,6 +81,7 @@ #include "llagentui.h" #include "lltrans.h" +#include "llexperiencecache.h" const U32 INCLUDE_SCREENSHOT = 0x01 << 0; @@ -227,6 +228,30 @@ void LLFloaterReporter::enableControls(BOOL enable) getChildView("cancel_btn")->setEnabled(enable); } +void LLFloaterReporter::getExperienceInfo(const LLUUID& experience_id) +{ + mExperienceID = experience_id; + + if (LLUUID::null != mExperienceID) + { + const LLSD& experience = LLExperienceCache::get(mExperienceID); + std::stringstream desc; + + if(experience.isDefined()) + { + setFromAvatarID(experience[LLExperienceCache::AGENT_ID]); + desc << "Experience id: " << mExperienceID; + } + else + { + desc << "Unable to retrieve details for id: "<< mExperienceID; + } + + LLUICtrl* details = getChild<LLUICtrl>("details_edit"); + details->setValue(desc.str()); + } +} + void LLFloaterReporter::getObjectInfo(const LLUUID& object_id) { // TODO -- @@ -472,7 +497,7 @@ void LLFloaterReporter::showFromMenu(EReportType report_type) } // static -void LLFloaterReporter::show(const LLUUID& object_id, const std::string& avatar_name) +void LLFloaterReporter::show(const LLUUID& object_id, const std::string& avatar_name, const LLUUID& experience_id) { LLFloaterReporter* f = LLFloaterReg::showTypedInstance<LLFloaterReporter>("reporter"); @@ -485,6 +510,23 @@ void LLFloaterReporter::show(const LLUUID& object_id, const std::string& avatar_ { f->setFromAvatarID(object_id); } + if(experience_id.notNull()) + { + f->getExperienceInfo(experience_id); + } + + // Need to deselect on close + f->mDeselectOnClose = TRUE; + + f->openFloater(); +} + + + +void LLFloaterReporter::showFromExperience( const LLUUID& experience_id ) +{ + LLFloaterReporter* f = LLFloaterReg::showTypedInstance<LLFloaterReporter>("reporter"); + f->getExperienceInfo(experience_id); // Need to deselect on close f->mDeselectOnClose = TRUE; @@ -494,9 +536,9 @@ void LLFloaterReporter::show(const LLUUID& object_id, const std::string& avatar_ // static -void LLFloaterReporter::showFromObject(const LLUUID& object_id) +void LLFloaterReporter::showFromObject(const LLUUID& object_id, const LLUUID& experience_id) { - show(object_id); + show(object_id, LLStringUtil::null, experience_id); } // static @@ -856,6 +898,7 @@ void LLFloaterReporter::setPosBox(const LLVector3d &pos) getChild<LLUICtrl>("pos_field")->setValue(pos_string); } + // void LLFloaterReporter::setDescription(const std::string& description, LLMeanCollisionData *mcd) // { // LLFloaterReporter *self = LLFloaterReg::findTypedInstance<LLFloaterReporter>("reporter"); diff --git a/indra/newview/llfloaterreporter.h b/indra/newview/llfloaterreporter.h index d54e7f6ab0..5eb5c20665 100755 --- a/indra/newview/llfloaterreporter.h +++ b/indra/newview/llfloaterreporter.h @@ -88,8 +88,9 @@ public: // Enables all buttons static void showFromMenu(EReportType report_type); - static void showFromObject(const LLUUID& object_id); + static void showFromObject(const LLUUID& object_id, const LLUUID& experience_id = LLUUID::null); static void showFromAvatar(const LLUUID& avatar_id, const std::string avatar_name); + static void showFromExperience(const LLUUID& experience_id); static void onClickSend (void *userdata); static void onClickCancel (void *userdata); @@ -106,7 +107,7 @@ public: void setPickedObjectProperties(const std::string& object_name, const std::string& owner_name, const LLUUID owner_id); private: - static void show(const LLUUID& object_id, const std::string& avatar_name = LLStringUtil::null); + static void show(const LLUUID& object_id, const std::string& avatar_name = LLStringUtil::null, const LLUUID& experience_id = LLUUID::null); void takeScreenshot(); void sendReportViaCaps(std::string url); @@ -118,6 +119,7 @@ private: void sendReportViaCaps(std::string url, std::string sshot_url, const LLSD & report); void setPosBox(const LLVector3d &pos); void enableControls(BOOL own_avatar); + void getExperienceInfo(const LLUUID& object_id); void getObjectInfo(const LLUUID& object_id); void callbackAvatarID(const uuid_vec_t& ids, const std::vector<LLAvatarName> names); void setFromAvatarID(const LLUUID& avatar_id); @@ -128,6 +130,7 @@ private: LLUUID mObjectID; LLUUID mScreenID; LLUUID mAbuserID; + LLUUID mExperienceID; // Store the real name, not the link, for upstream reporting std::string mOwnerName; BOOL mDeselectOnClose; diff --git a/indra/newview/llnotificationhandler.h b/indra/newview/llnotificationhandler.h index bff4efa9ea..3e7f05b5e1 100755 --- a/indra/newview/llnotificationhandler.h +++ b/indra/newview/llnotificationhandler.h @@ -169,7 +169,9 @@ public: virtual ~LLScriptHandler(); virtual void onDelete(LLNotificationPtr p); + virtual void onChange(LLNotificationPtr p); virtual bool processNotification(const LLNotificationPtr& p); + virtual void addToastWithNotification(const LLNotificationPtr& p); protected: virtual void onDeleteToast(LLToast* toast); diff --git a/indra/newview/llnotificationscripthandler.cpp b/indra/newview/llnotificationscripthandler.cpp index a3b15931c6..7acb2f9e90 100755 --- a/indra/newview/llnotificationscripthandler.cpp +++ b/indra/newview/llnotificationscripthandler.cpp @@ -68,6 +68,30 @@ void LLScriptHandler::initChannel() } //-------------------------------------------------------------------------- +void LLScriptHandler::addToastWithNotification(const LLNotificationPtr& notification) +{ + LLToastPanel* notify_box = LLToastPanel::buidPanelFromNotification(notification); + + LLToast::Params p; + p.notif_id = notification->getID(); + p.notification = notification; + p.panel = notify_box; + p.on_delete_toast = boost::bind(&LLScriptHandler::onDeleteToast, this, _1); + if(gAgent.isDoNotDisturb()) + { + p.force_show = notification->getName() == "SystemMessage" + || notification->getName() == "GodMessage" + || notification->getPriority() >= NOTIFICATION_PRIORITY_HIGH; + } + + LLScreenChannel* channel = dynamic_cast<LLScreenChannel*>(mChannel.get()); + if(channel) + { + channel->addToast(p); + } +} + +//-------------------------------------------------------------------------- bool LLScriptHandler::processNotification(const LLNotificationPtr& notification) { if(mChannel.isDead()) @@ -92,42 +116,33 @@ bool LLScriptHandler::processNotification(const LLNotificationPtr& notification) } else if (notification->canShowToast()) { - LLToastPanel* notify_box = LLToastPanel::buidPanelFromNotification(notification); - - LLToast::Params p; - p.notif_id = notification->getID(); - p.notification = notification; - p.panel = notify_box; - p.on_delete_toast = boost::bind(&LLScriptHandler::onDeleteToast, this, _1); - if(gAgent.isDoNotDisturb()) - { - p.force_show = notification->getName() == "SystemMessage" - || notification->getName() == "GodMessage" - || notification->getPriority() >= NOTIFICATION_PRIORITY_HIGH; - } - - LLScreenChannel* channel = dynamic_cast<LLScreenChannel*>(mChannel.get()); - if(channel) - { - channel->addToast(p); - } + addToastWithNotification(notification); } return false; } +void LLScriptHandler::onChange( LLNotificationPtr notification ) +{ + LLScreenChannel* channel = dynamic_cast<LLScreenChannel*>(mChannel.get()); + if (channel) + { + channel->removeToastByNotificationID(notification->getID()); + addToastWithNotification(notification); + } +} void LLScriptHandler::onDelete( LLNotificationPtr notification ) - { +{ if(notification->hasFormElements() && !notification->canShowToast()) - { - LLScriptFloaterManager::getInstance()->onRemoveNotification(notification->getID()); - } - else - { - mChannel.get()->removeToastByNotificationID(notification->getID()); - } + { + LLScriptFloaterManager::getInstance()->onRemoveNotification(notification->getID()); } + else + { + mChannel.get()->removeToastByNotificationID(notification->getID()); + } +} //-------------------------------------------------------------------------- diff --git a/indra/newview/llpanelexperiencelisteditor.cpp b/indra/newview/llpanelexperiencelisteditor.cpp new file mode 100644 index 0000000000..3561268063 --- /dev/null +++ b/indra/newview/llpanelexperiencelisteditor.cpp @@ -0,0 +1,254 @@ +/** + * @file llpanelexperiencelisteditor.cpp + * @brief Editor for building a list of experiences + * + * $LicenseInfo:firstyear=2014&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 "llpanelexperiencelisteditor.h" + +#include "llbutton.h" +#include "llexperiencecache.h" +#include "llfloaterexperiencepicker.h" +#include "llfloaterreg.h" +#include "llhandle.h" +#include "llscrolllistctrl.h" +#include "llviewerregion.h" +#include "llagent.h" +#include "lltextbox.h" +#include "lltrans.h" + + +static LLPanelInjector<LLPanelExperienceListEditor> t_panel_experience_list_editor("panel_experience_list_editor"); + + +LLPanelExperienceListEditor::LLPanelExperienceListEditor() + :mItems(NULL) + ,mProfile(NULL) + ,mRemove(NULL) + ,mReadonly(false) + ,mMaxExperienceIDs(0) +{ +} + +BOOL LLPanelExperienceListEditor::postBuild() +{ + mItems = getChild<LLScrollListCtrl>("experience_list"); + mAdd = getChild<LLButton>("btn_add"); + mRemove = getChild<LLButton>("btn_remove"); + mProfile = getChild<LLButton>("btn_profile"); + + childSetAction("btn_add", boost::bind(&LLPanelExperienceListEditor::onAdd, this)); + childSetAction("btn_remove", boost::bind(&LLPanelExperienceListEditor::onRemove, this)); + childSetAction("btn_profile", boost::bind(&LLPanelExperienceListEditor::onProfile, this)); + + mItems->setCommitCallback(boost::bind(&LLPanelExperienceListEditor::checkButtonsEnabled, this)); + + checkButtonsEnabled(); + return TRUE; +} + +const uuid_list_t& LLPanelExperienceListEditor::getExperienceIds() const +{ + return mExperienceIds; +} + +void LLPanelExperienceListEditor::addExperienceIds( const uuid_vec_t& experience_ids ) +{ + // the commented out code in this function is handled by the callback and no longer necessary! + + //mExperienceIds.insert(experience_ids.begin(), experience_ids.end()); + //onItems(); + if(!mAddedCallback.empty()) + { + for(uuid_vec_t::const_iterator it = experience_ids.begin(); it != experience_ids.end(); ++it) + { + mAddedCallback(*it); + } + } +} + + +void LLPanelExperienceListEditor::setExperienceIds( const LLSD& experience_ids ) +{ + mExperienceIds.clear(); + mExperienceIds.insert(experience_ids.beginArray(), experience_ids.endArray()); + onItems(); +} + +void LLPanelExperienceListEditor::addExperience( const LLUUID& id ) +{ + mExperienceIds.insert(id); + onItems(); +} +void LLPanelExperienceListEditor::onAdd() +{ + if(!mPicker.isDead()) + { + mPicker.markDead(); + } + + mKey.generateNewID(); + + LLFloaterExperiencePicker* picker=LLFloaterExperiencePicker::show(boost::bind(&LLPanelExperienceListEditor::addExperienceIds, this, _1), mKey, FALSE, TRUE, mFilters, mAdd); + mPicker = picker->getDerivedHandle<LLFloaterExperiencePicker>(); +} + + +void LLPanelExperienceListEditor::onRemove() +{ + // the commented out code in this function is handled by the callback and no longer necessary! + + std::vector<LLScrollListItem*> items= mItems->getAllSelected(); + std::vector<LLScrollListItem*>::iterator it = items.begin(); + for(/**/; it != items.end(); ++it) + { + if((*it) != NULL) + { + //mExperienceIds.erase((*it)->getValue()); + mRemovedCallback((*it)->getValue()); + } + } + //onItems(); +} + +void LLPanelExperienceListEditor::onProfile() +{ + LLScrollListItem* item = mItems->getFirstSelected(); + if(item) + { + LLFloaterReg::showInstance("experience_profile", item->getUUID(), true); + } +} + +void LLPanelExperienceListEditor::checkButtonsEnabled() +{ + mAdd->setEnabled(!mReadonly); + int selected = mItems->getNumSelected(); + + bool remove_enabled = !mReadonly && selected>0; + if(remove_enabled && mSticky) + { + std::vector<LLScrollListItem*> items= mItems->getAllSelected(); + std::vector<LLScrollListItem*>::iterator it = items.begin(); + for(/**/; it != items.end() && remove_enabled; ++it) + { + if((*it) != NULL) + { + remove_enabled = !mSticky((*it)->getValue()); + } + } + + + } + mRemove->setEnabled(remove_enabled); + mProfile->setEnabled(selected==1); +} + +void LLPanelExperienceListEditor::onItems() +{ + mItems->deleteAllItems(); + + LLSD item; + uuid_list_t::iterator it = mExperienceIds.begin(); + for(/**/; it != mExperienceIds.end(); ++it) + { + const LLUUID& experience = *it; + item["id"]=experience; + LLSD& columns = item["columns"]; + columns[0]["column"] = "experience_name"; + columns[0]["value"] = getString("loading"); + mItems->addElement(item); + + LLExperienceCache::get(experience, boost::bind(&LLPanelExperienceListEditor::experienceDetailsCallback, + getDerivedHandle<LLPanelExperienceListEditor>(), _1)); + } + + + if(mItems->getItemCount() == 0) + { + mItems->setCommentText(getString("no_results")); + } + + + checkButtonsEnabled(); +} + +void LLPanelExperienceListEditor::experienceDetailsCallback( LLHandle<LLPanelExperienceListEditor> panel, const LLSD& experience ) +{ + if(!panel.isDead()) + { + panel.get()->onExperienceDetails(experience); + } +} + +void LLPanelExperienceListEditor::onExperienceDetails( const LLSD& experience ) +{ + LLScrollListItem* item = mItems->getItem(experience[LLExperienceCache::EXPERIENCE_ID]); + if(!item) + return; + + item->getColumn(0)->setValue(experience[LLExperienceCache::NAME]); +} + +LLPanelExperienceListEditor::~LLPanelExperienceListEditor() +{ + if(!mPicker.isDead()) + { + mPicker.get()->closeFloater(); + } +} + +void LLPanelExperienceListEditor::loading() +{ + mItems->clear(); + mItems->setCommentText( getString("loading")); +} + +void LLPanelExperienceListEditor::setReadonly( bool val ) +{ + mReadonly = val; + checkButtonsEnabled(); +} + +void LLPanelExperienceListEditor::refreshExperienceCounter() +{ + if(mMaxExperienceIDs > 0) + { + LLStringUtil::format_map_t args; + args["[EXPERIENCES]"] = llformat("%d", mItems->getItemCount()); + args["[MAXEXPERIENCES]"] = llformat("%d", mMaxExperienceIDs); + getChild<LLTextBox>("text_count")->setText(LLTrans::getString("ExperiencesCounter", args)); + } +} + +boost::signals2::connection LLPanelExperienceListEditor::setAddedCallback( list_changed_signal_t::slot_type cb ) +{ + return mAddedCallback.connect(cb); +} + +boost::signals2::connection LLPanelExperienceListEditor::setRemovedCallback( list_changed_signal_t::slot_type cb ) +{ + return mRemovedCallback.connect(cb); +} diff --git a/indra/newview/llpanelexperiencelisteditor.h b/indra/newview/llpanelexperiencelisteditor.h new file mode 100644 index 0000000000..f69f0509be --- /dev/null +++ b/indra/newview/llpanelexperiencelisteditor.h @@ -0,0 +1,100 @@ +/** +* @file llpanelexperiencelisteditor.cpp +* @brief Editor for building a list of experiences +* +* $LicenseInfo:firstyear=2014&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 LL_LLPANELEXPERIENCELISTEDITOR_H +#define LL_LLPANELEXPERIENCELISTEDITOR_H + +#include "llpanel.h" +#include "lluuid.h" +#include <set> + +class LLScrollListCtrl; +class LLButton; +class LLFloaterExperiencePicker; + +class LLPanelExperienceListEditor : public LLPanel +{ +public: + + typedef boost::signals2::signal<void (const LLUUID&) > list_changed_signal_t; + // filter function for experiences, return true if the experience should be hidden. + typedef boost::function<bool (const LLSD&)> experience_function; + typedef std::vector<experience_function> filter_list; + typedef LLHandle<LLFloaterExperiencePicker> PickerHandle; + LLPanelExperienceListEditor(); + ~LLPanelExperienceListEditor(); + BOOL postBuild(); + + void loading(); + + const uuid_list_t& getExperienceIds()const; + void setExperienceIds(const LLSD& experience_ids); + void addExperienceIds(const uuid_vec_t& experience_ids); + + void addExperience(const LLUUID& id); + + boost::signals2::connection setAddedCallback(list_changed_signal_t::slot_type cb ); + boost::signals2::connection setRemovedCallback(list_changed_signal_t::slot_type cb ); + + bool getReadonly() const { return mReadonly; } + void setReadonly(bool val); + + void refreshExperienceCounter(); + + void addFilter(experience_function func){mFilters.push_back(func);} + void setStickyFunction(experience_function func){mSticky = func;} + U32 getMaxExperienceIDs() const { return mMaxExperienceIDs; } + void setMaxExperienceIDs(U32 val) { mMaxExperienceIDs = val; } +private: + + void onItems(); + void onRemove(); + void onAdd(); + void onProfile(); + + void checkButtonsEnabled(); + static void experienceDetailsCallback( LLHandle<LLPanelExperienceListEditor> panel, const LLSD& experience ); + void onExperienceDetails( const LLSD& experience ); + void processResponse( const LLSD& content ); + uuid_list_t mExperienceIds; + + + LLScrollListCtrl* mItems; + filter_list mFilters; + LLButton* mAdd; + LLButton* mRemove; + LLButton* mProfile; + PickerHandle mPicker; + list_changed_signal_t mAddedCallback; + list_changed_signal_t mRemovedCallback; + LLUUID mKey; + bool mReadonly; + experience_function mSticky; + U32 mMaxExperienceIDs; + +}; + +#endif //LL_LLPANELEXPERIENCELISTEDITOR_H diff --git a/indra/newview/llpanelexperiencelog.cpp b/indra/newview/llpanelexperiencelog.cpp new file mode 100644 index 0000000000..df03ef7526 --- /dev/null +++ b/indra/newview/llpanelexperiencelog.cpp @@ -0,0 +1,264 @@ +/** + * @file llpanelexperiencelog.cpp + * @brief llpanelexperiencelog + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, 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 "llpanelexperiencelog.h" + +#include "llexperiencelog.h" +#include "llexperiencecache.h" +#include "llbutton.h" +#include "llscrolllistctrl.h" +#include "llcombobox.h" +#include "llspinctrl.h" +#include "llcheckboxctrl.h" +#include "llfloaterreg.h" +#include "llfloaterreporter.h" +#include "llinventoryfunctions.h" + + +#define BTN_PROFILE_XP "btn_profile_xp" +#define BTN_REPORT_XP "btn_report_xp" + +static LLPanelInjector<LLPanelExperienceLog> register_experiences_panel("experience_log"); + + +LLPanelExperienceLog::LLPanelExperienceLog( ) + : mEventList(NULL) + , mPageSize(25) + , mCurrentPage(0) +{ + buildFromFile("panel_experience_log.xml"); +} + + +BOOL LLPanelExperienceLog::postBuild( void ) +{ + LLExperienceLog* log = LLExperienceLog::getInstance(); + mEventList = getChild<LLScrollListCtrl>("experience_log_list"); + mEventList->setCommitCallback(boost::bind(&LLPanelExperienceLog::onSelectionChanged, this)); + mEventList->setDoubleClickCallback( boost::bind(&LLPanelExperienceLog::onProfileExperience, this)); + + getChild<LLButton>("btn_clear")->setCommitCallback(boost::bind(&LLExperienceLog::clear, log)); + getChild<LLButton>("btn_clear")->setCommitCallback(boost::bind(&LLPanelExperienceLog::refresh, this)); + + getChild<LLButton>(BTN_PROFILE_XP)->setCommitCallback(boost::bind(&LLPanelExperienceLog::onProfileExperience, this)); + getChild<LLButton>(BTN_REPORT_XP )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onReportExperience, this)); + getChild<LLButton>("btn_notify" )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onNotify, this)); + getChild<LLButton>("btn_next" )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onNext, this)); + getChild<LLButton>("btn_prev" )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onPrev, this)); + + LLCheckBoxCtrl* check = getChild<LLCheckBoxCtrl>("notify_all"); + check->set(log->getNotifyNewEvent()); + check->setCommitCallback(boost::bind(&LLPanelExperienceLog::notifyChanged, this)); + + + LLSpinCtrl* spin = getChild<LLSpinCtrl>("logsizespinner"); + spin->set(log->getMaxDays()); + spin->setCommitCallback(boost::bind(&LLPanelExperienceLog::logSizeChanged, this)); + + mPageSize = log->getPageSize(); + refresh(); + mNewEvent = LLExperienceLog::instance().addUpdateSignal(boost::bind(&LLPanelExperienceLog::refresh, this)); + return TRUE; +} + +LLPanelExperienceLog* LLPanelExperienceLog::create() +{ + return new LLPanelExperienceLog(); +} + +void LLPanelExperienceLog::refresh() +{ + S32 selected = mEventList->getFirstSelectedIndex(); + mEventList->deleteAllItems(); + const LLSD events = LLExperienceLog::instance().getEvents(); + + if(events.size() == 0) + { + mEventList->setCommentText(getString("no_events")); + return; + } + + setAllChildrenEnabled(FALSE); + + LLSD item; + bool waiting = false; + LLUUID waiting_id; + + int itemsToSkip = mPageSize*mCurrentPage; + int items = 0; + bool moreItems = false; + + if (!events.emptyMap()) + { + LLSD::map_const_iterator day = events.endMap(); + do + { + --day; + const LLSD& dayArray = day->second; + int size = dayArray.size(); + if(itemsToSkip > size) + { + itemsToSkip -= size; + continue; + } + if(items >= mPageSize && size > 0) + { + moreItems = true; + break; + } + for(int i = dayArray.size() - itemsToSkip - 1; i >= 0; i--) + { + if(items >= mPageSize) + { + moreItems = true; + break; + } + const LLSD event = dayArray[i]; + LLUUID id = event[LLExperienceCache::EXPERIENCE_ID].asUUID(); + const LLSD& experience = LLExperienceCache::get(id); + if(experience.isUndefined()){ + waiting = true; + waiting_id = id; + } + if(!waiting) + { + item["id"] = event; + + LLSD& columns = item["columns"]; + columns[0]["column"] = "time"; + columns[0]["value"] = day->first+event["Time"].asString(); + columns[1]["column"] = "event"; + columns[1]["value"] = LLExperienceLog::getPermissionString(event, "ExperiencePermissionShort"); + columns[2]["column"] = "experience_name"; + columns[2]["value"] = experience[LLExperienceCache::NAME].asString(); + columns[3]["column"] = "object_name"; + columns[3]["value"] = event["ObjectName"].asString(); + mEventList->addElement(item); + } + ++items; + } + } while (day != events.beginMap()); + } + if(waiting) + { + mEventList->deleteAllItems(); + mEventList->setCommentText(getString("loading")); + LLExperienceCache::get(waiting_id, boost::bind(&LLPanelExperienceLog::refresh, this)); + } + else + { + setAllChildrenEnabled(TRUE); + + mEventList->setEnabled(TRUE); + getChild<LLButton>("btn_next")->setEnabled(moreItems); + getChild<LLButton>("btn_prev")->setEnabled(mCurrentPage>0); + getChild<LLButton>("btn_clear")->setEnabled(mEventList->getItemCount()>0); + if(selected<0) + { + selected = 0; + } + mEventList->selectNthItem(selected); + onSelectionChanged(); + } +} + +void LLPanelExperienceLog::onProfileExperience() +{ + LLSD event = getSelectedEvent(); + if(event.isDefined()) + { + LLFloaterReg::showInstance("experience_profile", event[LLExperienceCache::EXPERIENCE_ID].asUUID(), true); + } +} + +void LLPanelExperienceLog::onReportExperience() +{ + LLSD event = getSelectedEvent(); + if(event.isDefined()) + { + LLFloaterReporter::showFromExperience(event[LLExperienceCache::EXPERIENCE_ID].asUUID()); + } +} + +void LLPanelExperienceLog::onNotify() +{ + LLSD event = getSelectedEvent(); + if(event.isDefined()) + { + LLExperienceLog::instance().notify(event); + } +} + +void LLPanelExperienceLog::onNext() +{ + mCurrentPage++; + refresh(); +} + +void LLPanelExperienceLog::onPrev() +{ + if(mCurrentPage>0) + { + mCurrentPage--; + refresh(); + } +} + +void LLPanelExperienceLog::notifyChanged() +{ + LLExperienceLog::instance().setNotifyNewEvent(getChild<LLCheckBoxCtrl>("notify_all")->get()); +} + +void LLPanelExperienceLog::logSizeChanged() +{ + int value = (int)(getChild<LLSpinCtrl>("logsizespinner")->get()); + bool dirty = value > 0 && value < LLExperienceLog::instance().getMaxDays(); + LLExperienceLog::instance().setMaxDays(value); + if(dirty) + { + refresh(); + } +} + +void LLPanelExperienceLog::onSelectionChanged() +{ + bool enabled = (1 == mEventList->getNumSelected()); + getChild<LLButton>(BTN_REPORT_XP)->setEnabled(enabled); + getChild<LLButton>(BTN_PROFILE_XP)->setEnabled(enabled); + getChild<LLButton>("btn_notify")->setEnabled(enabled); +} + +LLSD LLPanelExperienceLog::getSelectedEvent() +{ + LLScrollListItem* item = mEventList->getFirstSelected(); + if(item) + { + return item->getValue(); + } + return LLSD(); +} diff --git a/indra/newview/llpanelexperiencelog.h b/indra/newview/llpanelexperiencelog.h new file mode 100644 index 0000000000..4135d9cac9 --- /dev/null +++ b/indra/newview/llpanelexperiencelog.h @@ -0,0 +1,64 @@ +/** + * @file llpanelexperiencelog.h + * @brief llpanelexperiencelog and related class definitions + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, 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 LL_LLPANELEXPERIENCELOG_H +#define LL_LLPANELEXPERIENCELOG_H + +#include "llpanel.h" +class LLScrollListCtrl; + +class LLPanelExperienceLog + : public LLPanel +{ +public: + + LLPanelExperienceLog(); + + static LLPanelExperienceLog* create(); + + /*virtual*/ BOOL postBuild(void); + + void refresh(); +protected: + void logSizeChanged(); + void notifyChanged(); + void onNext(); + void onNotify(); + void onPrev(); + void onProfileExperience(); + void onReportExperience(); + void onSelectionChanged(); + + LLSD getSelectedEvent(); +private: + LLScrollListCtrl* mEventList; + U32 mPageSize; + U32 mCurrentPage; + boost::signals2::scoped_connection mNewEvent; +}; + +#endif // LL_LLPANELEXPERIENCELOG_H diff --git a/indra/newview/llpanelexperiencepicker.cpp b/indra/newview/llpanelexperiencepicker.cpp new file mode 100644 index 0000000000..0a572a8a5c --- /dev/null +++ b/indra/newview/llpanelexperiencepicker.cpp @@ -0,0 +1,431 @@ +/** +* @file llpanelexperiencepicker.cpp +* @brief Implementation of llpanelexperiencepicker +* @author dolphin@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, 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 "llpanelexperiencepicker.h" + + +#include "lllineeditor.h" +#include "llfloaterreg.h" +#include "llscrolllistctrl.h" +#include "llviewerregion.h" +#include "llagent.h" +#include "llexperiencecache.h" +#include "llslurl.h" +#include "llavatarnamecache.h" +#include "llcombobox.h" +#include "llviewercontrol.h" +#include "llfloater.h" + +#define BTN_FIND "find" +#define BTN_OK "ok_btn" +#define BTN_CANCEL "cancel_btn" +#define BTN_PROFILE "profile_btn" +#define BTN_LEFT "left_btn" +#define BTN_RIGHT "right_btn" +#define TEXT_EDIT "edit" +#define TEXT_MATURITY "maturity" +#define LIST_RESULTS "search_results" +#define PANEL_SEARCH "search_panel" + +const static std::string columnSpace = " "; + +static LLPanelInjector<LLPanelExperiencePicker> t_panel_status("llpanelexperiencepicker"); + +class LLExperienceSearchResponder : public LLHTTPClient::Responder +{ +public: + LLUUID mQueryID; + LLHandle<LLPanelExperiencePicker> mParent; + + LLExperienceSearchResponder(const LLUUID& id, const LLHandle<LLPanelExperiencePicker>& parent) : mQueryID(id), mParent(parent) { } + +protected: + /*virtual*/ void httpSuccess() + { + if(mParent.isDead()) + return; + + LLPanelExperiencePicker* panel =mParent.get(); + if (panel) + { + panel->processResponse(mQueryID, getContent()); + } + } + + /*virtual*/ void httpFailure() + { + if(mParent.isDead()) + return; + + LLPanelExperiencePicker* panel =mParent.get(); + if (panel) + { + panel->processResponse(mQueryID, LLSD()); + } + LL_WARNS() << "experience picker failed [status:" << getStatus() << "]: " << getContent() << LL_ENDL; + } +}; + +LLPanelExperiencePicker::LLPanelExperiencePicker() + :LLPanel() +{ + buildFromFile("panel_experience_search.xml"); + setDefaultFilters(); +} + +LLPanelExperiencePicker::~LLPanelExperiencePicker() +{ +} + +BOOL LLPanelExperiencePicker::postBuild() +{ + getChild<LLLineEditor>(TEXT_EDIT)->setKeystrokeCallback( boost::bind(&LLPanelExperiencePicker::editKeystroke, this, _1, _2),NULL); + + childSetAction(BTN_FIND, boost::bind(&LLPanelExperiencePicker::onBtnFind, this)); + getChildView(BTN_FIND)->setEnabled(FALSE); + + LLScrollListCtrl* searchresults = getChild<LLScrollListCtrl>(LIST_RESULTS); + searchresults->setDoubleClickCallback( boost::bind(&LLPanelExperiencePicker::onBtnSelect, this)); + searchresults->setCommitCallback(boost::bind(&LLPanelExperiencePicker::onList, this)); + getChildView(LIST_RESULTS)->setEnabled(FALSE); + getChild<LLScrollListCtrl>(LIST_RESULTS)->setCommentText(getString("no_results")); + + childSetAction(BTN_OK, boost::bind(&LLPanelExperiencePicker::onBtnSelect, this)); + getChildView(BTN_OK)->setEnabled(FALSE); + childSetAction(BTN_CANCEL, boost::bind(&LLPanelExperiencePicker::onBtnClose, this)); + childSetAction(BTN_PROFILE, boost::bind(&LLPanelExperiencePicker::onBtnProfile, this)); + getChildView(BTN_PROFILE)->setEnabled(FALSE); + + getChild<LLComboBox>(TEXT_MATURITY)->setCurrentByIndex(2); + getChild<LLComboBox>(TEXT_MATURITY)->setCommitCallback(boost::bind(&LLPanelExperiencePicker::onMaturity, this)); + getChild<LLUICtrl>(TEXT_EDIT)->setFocus(TRUE); + + childSetAction(BTN_LEFT, boost::bind(&LLPanelExperiencePicker::onPage, this, -1)); + childSetAction(BTN_RIGHT, boost::bind(&LLPanelExperiencePicker::onPage, this, 1)); + + LLPanel* search_panel = getChild<LLPanel>(PANEL_SEARCH); + if (search_panel) + { + // Start searching when Return is pressed in the line editor. + search_panel->setDefaultBtn(BTN_FIND); + } + return TRUE; +} + +void LLPanelExperiencePicker::editKeystroke( class LLLineEditor* caller, void* user_data ) +{ + getChildView(BTN_FIND)->setEnabled(caller->getText().size() > 0); +} + +void LLPanelExperiencePicker::onBtnFind() +{ + mCurrentPage=1; + find(); +} + +void LLPanelExperiencePicker::onList() +{ + bool enabled = isSelectButtonEnabled(); + getChildView(BTN_OK)->setEnabled(enabled); + + enabled = enabled && getChild<LLScrollListCtrl>(LIST_RESULTS)->getNumSelected() == 1; + getChildView(BTN_PROFILE)->setEnabled(enabled); +} + +void LLPanelExperiencePicker::find() +{ + std::string text = getChild<LLUICtrl>(TEXT_EDIT)->getValue().asString(); + mQueryID.generate(); + std::ostringstream url; + + LLViewerRegion* region = gAgent.getRegion(); + std::string cap = region->getCapability("FindExperienceByName"); + if (!cap.empty()) + { + url << cap << "?page=" << mCurrentPage << "&page_size=30&query=" << LLURI::escape(text); + LLHTTPClient::get(url.str(), new LLExperienceSearchResponder(mQueryID, getDerivedHandle<LLPanelExperiencePicker>())); + + } + getChild<LLScrollListCtrl>(LIST_RESULTS)->deleteAllItems(); + getChild<LLScrollListCtrl>(LIST_RESULTS)->setCommentText(getString("searching")); + + getChildView(BTN_OK)->setEnabled(FALSE); + getChildView(BTN_PROFILE)->setEnabled(FALSE); + + getChildView(BTN_RIGHT)->setEnabled(FALSE); + getChildView(BTN_LEFT)->setEnabled(FALSE); +} + + +bool LLPanelExperiencePicker::isSelectButtonEnabled() +{ + LLScrollListCtrl* list=getChild<LLScrollListCtrl>(LIST_RESULTS); + return list->getFirstSelectedIndex() >=0; +} + +void LLPanelExperiencePicker::getSelectedExperienceIds( const LLScrollListCtrl* results, uuid_vec_t &experience_ids ) +{ + std::vector<LLScrollListItem*> items = results->getAllSelected(); + for(std::vector<LLScrollListItem*>::iterator it = items.begin(); it != items.end(); ++it) + { + LLScrollListItem* item = *it; + if (item->getUUID().notNull()) + { + experience_ids.push_back(item->getUUID()); + } + } +} + +void LLPanelExperiencePicker::setAllowMultiple( bool allow_multiple ) +{ + getChild<LLScrollListCtrl>(LIST_RESULTS)->setAllowMultipleSelection(allow_multiple); +} + + +void name_callback(const LLHandle<LLPanelExperiencePicker>& floater, const LLUUID& experience_id, const LLUUID& agent_id, const LLAvatarName& av_name) +{ + if(floater.isDead()) + return; + LLPanelExperiencePicker* picker = floater.get(); + LLScrollListCtrl* search_results = picker->getChild<LLScrollListCtrl>(LIST_RESULTS); + + LLScrollListItem* item = search_results->getItem(experience_id); + if(!item) + return; + + item->getColumn(2)->setValue(columnSpace+av_name.getDisplayName()); + +} + +void LLPanelExperiencePicker::processResponse( const LLUUID& query_id, const LLSD& content ) +{ + if(query_id != mQueryID) + { + return; + } + + mResponse = content; + + const LLSD& experiences=mResponse["experience_keys"]; + LLSD::array_const_iterator it = experiences.beginArray(); + for ( ; it != experiences.endArray(); ++it) + { + LLExperienceCache::insert(*it); + } + + getChildView(BTN_RIGHT)->setEnabled(content.has("next_page_url")); + getChildView(BTN_LEFT)->setEnabled(content.has("previous_page_url")); + + filterContent(); + +} + +void LLPanelExperiencePicker::onBtnSelect() +{ + if(!isSelectButtonEnabled()) + { + return; + } + + if(mSelectionCallback) + { + const LLScrollListCtrl* results = getChild<LLScrollListCtrl>(LIST_RESULTS); + uuid_vec_t experience_ids; + + getSelectedExperienceIds(results, experience_ids); + mSelectionCallback(experience_ids); + getChild<LLScrollListCtrl>(LIST_RESULTS)->deselectAllItems(TRUE); + if(mCloseOnSelect) + { + mCloseOnSelect = FALSE; + onBtnClose(); + } + } + else + { + onBtnProfile(); + } +} + +void LLPanelExperiencePicker::onBtnClose() +{ + LLFloater* floater = getParentByType<LLFloater>(); + if (floater) + { + floater->closeFloater(); + } +} + +void LLPanelExperiencePicker::onBtnProfile() +{ + LLScrollListItem* item = getChild<LLScrollListCtrl>(LIST_RESULTS)->getFirstSelected(); + if(item) + { + LLFloaterReg::showInstance("experience_profile", item->getUUID(), true); + } +} + +std::string LLPanelExperiencePicker::getMaturityString(int maturity) +{ + if(maturity <= SIM_ACCESS_PG) + { + return getString("maturity_icon_general"); + } + else if(maturity <= SIM_ACCESS_MATURE) + { + return getString("maturity_icon_moderate"); + } + return getString("maturity_icon_adult"); +} + +void LLPanelExperiencePicker::filterContent() +{ + LLScrollListCtrl* search_results = getChild<LLScrollListCtrl>(LIST_RESULTS); + + const LLSD& experiences=mResponse["experience_keys"]; + + search_results->deleteAllItems(); + + LLSD item; + LLSD::array_const_iterator it = experiences.beginArray(); + for ( ; it != experiences.endArray(); ++it) + { + const LLSD& experience = *it; + + if(isExperienceHidden(experience)) + continue; + + item["id"]=experience[LLExperienceCache::EXPERIENCE_ID]; + LLSD& columns = item["columns"]; + columns[0]["column"] = "maturity"; + columns[0]["value"] = getMaturityString(experience[LLExperienceCache::MATURITY].asInteger()); + columns[0]["type"]="icon"; + columns[0]["halign"]="right"; + columns[1]["column"] = "experience_name"; + columns[1]["value"] = columnSpace+experience[LLExperienceCache::NAME].asString(); + columns[2]["column"] = "owner"; + columns[2]["value"] = columnSpace+getString("loading"); + search_results->addElement(item); + LLAvatarNameCache::get(experience[LLExperienceCache::AGENT_ID], boost::bind(name_callback, getDerivedHandle<LLPanelExperiencePicker>(), experience[LLExperienceCache::EXPERIENCE_ID], _1, _2)); + } + + if (search_results->isEmpty()) + { + LLStringUtil::format_map_t map; + std::string search_text = getChild<LLUICtrl>(TEXT_EDIT)->getValue().asString(); + map["[TEXT]"] = search_text; + if (search_text.empty()) + { + getChild<LLScrollListCtrl>(LIST_RESULTS)->setCommentText(getString("no_results")); + } + else + { + getChild<LLScrollListCtrl>(LIST_RESULTS)->setCommentText(getString("not_found", map)); + } + search_results->setEnabled(false); + getChildView(BTN_OK)->setEnabled(false); + getChildView(BTN_PROFILE)->setEnabled(false); + } + else + { + getChildView(BTN_OK)->setEnabled(true); + search_results->setEnabled(true); + search_results->sortByColumnIndex(1, TRUE); + std::string text = getChild<LLUICtrl>(TEXT_EDIT)->getValue().asString(); + if (!search_results->selectItemByLabel(text, TRUE, 1)) + { + search_results->selectFirstItem(); + } + onList(); + search_results->setFocus(TRUE); + } +} + +void LLPanelExperiencePicker::onMaturity() +{ + if(mResponse.has("experience_keys") && mResponse["experience_keys"].beginArray() != mResponse["experience_keys"].endArray()) + { + filterContent(); + } +} + +bool LLPanelExperiencePicker::isExperienceHidden( const LLSD& experience) const +{ + bool hide=false; + filter_list::const_iterator it = mFilters.begin(); + for(/**/;it != mFilters.end(); ++it) + { + if((*it)(experience)){ + return true; + } + } + + return hide; +} + +bool LLPanelExperiencePicker::FilterOverRating( const LLSD& experience ) +{ + int maturity = getChild<LLComboBox>(TEXT_MATURITY)->getSelectedValue().asInteger(); + return experience[LLExperienceCache::MATURITY].asInteger() > maturity; +} + +bool LLPanelExperiencePicker::FilterWithProperty( const LLSD& experience, S32 prop) +{ + return (experience[LLExperienceCache::PROPERTIES].asInteger() & prop) != 0; +} + +bool LLPanelExperiencePicker::FilterWithoutProperty( const LLSD& experience, S32 prop ) +{ + return (experience[LLExperienceCache::PROPERTIES].asInteger() & prop) == 0; +} + +void LLPanelExperiencePicker::setDefaultFilters() +{ + mFilters.clear(); + addFilter(boost::bind(&LLPanelExperiencePicker::FilterOverRating, this, _1)); +} + +bool LLPanelExperiencePicker::FilterMatching( const LLSD& experience, const LLUUID& id ) +{ + if(experience.isUUID()) + { + return experience.asUUID() == id; + } + return experience[LLExperienceCache::EXPERIENCE_ID].asUUID() == id; +} + +void LLPanelExperiencePicker::onPage( S32 direction ) +{ + mCurrentPage += direction; + if(mCurrentPage < 1) + { + mCurrentPage = 1; + } + find(); +} diff --git a/indra/newview/llpanelexperiencepicker.h b/indra/newview/llpanelexperiencepicker.h new file mode 100644 index 0000000000..b2ba7f26ee --- /dev/null +++ b/indra/newview/llpanelexperiencepicker.h @@ -0,0 +1,94 @@ +/** +* @file llpanelexperiencepicker.h +* @brief Header file for llpanelexperiencepicker +* @author dolphin@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, 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 LL_LLPANELEXPERIENCEPICKER_H +#define LL_LLPANELEXPERIENCEPICKER_H + +#include "llpanel.h" + +class LLScrollListCtrl; +class LLLineEditor; + + +class LLPanelExperiencePicker : public LLPanel +{ +public: + friend class LLExperienceSearchResponder; + friend class LLFloaterExperiencePicker; + + typedef boost::function<void (const uuid_vec_t&)> select_callback_t; + // filter function for experiences, return true if the experience should be hidden. + typedef boost::function<bool (const LLSD&)> filter_function; + typedef std::vector<filter_function> filter_list; + + LLPanelExperiencePicker(); + virtual ~LLPanelExperiencePicker(); + + BOOL postBuild(); + + void addFilter(filter_function func){mFilters.push_back(func);} + template <class IT> + void addFilters(IT begin, IT end){mFilters.insert(mFilters.end(), begin, end);} + void setDefaultFilters(); + + static bool FilterWithProperty(const LLSD& experience, S32 prop); + static bool FilterWithoutProperty(const LLSD& experience, S32 prop); + static bool FilterMatching(const LLSD& experience, const LLUUID& id); + bool FilterOverRating(const LLSD& experience); + +private: + void editKeystroke(LLLineEditor* caller, void* user_data); + + void onBtnFind(); + void onBtnSelect(); + void onBtnClose(); + void onBtnProfile(); + void onList(); + void onMaturity(); + void onPage(S32 direction); + + void getSelectedExperienceIds( const LLScrollListCtrl* results, uuid_vec_t &experience_ids ); + void setAllowMultiple(bool allow_multiple); + + + void find(); + bool isSelectButtonEnabled(); + void processResponse( const LLUUID& query_id, const LLSD& content ); + + void filterContent(); + bool isExperienceHidden(const LLSD& experience) const ; + std::string getMaturityString(int maturity); + + + select_callback_t mSelectionCallback; + filter_list mFilters; + LLUUID mQueryID; + LLSD mResponse; + bool mCloseOnSelect; + S32 mCurrentPage; +}; + +#endif // LL_LLPANELEXPERIENCEPICKER_H diff --git a/indra/newview/llpanelexperiences.cpp b/indra/newview/llpanelexperiences.cpp new file mode 100644 index 0000000000..3ee4b5e968 --- /dev/null +++ b/indra/newview/llpanelexperiences.cpp @@ -0,0 +1,218 @@ +/** + * @file llpanelexperiences.cpp + * @brief LLPanelExperiences class implementation + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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 "llpanelprofile.h" +#include "lluictrlfactory.h" +#include "llexperiencecache.h" +#include "llagent.h" + +#include "llpanelexperiences.h" +#include "llslurl.h" +#include "lllayoutstack.h" + + + +static LLPanelInjector<LLPanelExperiences> register_experiences_panel("experiences_panel"); + + +//comparators +static const LLExperienceItemComparator NAME_COMPARATOR; + +LLPanelExperiences::LLPanelExperiences( ) + : mExperiencesList(NULL) +{ + buildFromFile("panel_experiences.xml"); +} + +BOOL LLPanelExperiences::postBuild( void ) +{ + mExperiencesList = getChild<LLFlatListView>("experiences_list"); + if (hasString("loading_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("loading_experiences")); + } + else if (hasString("no_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("no_experiences")); + } + mExperiencesList->setComparator(&NAME_COMPARATOR); + + return TRUE; +} + + + +LLExperienceItem* LLPanelExperiences::getSelectedExperienceItem() +{ + LLPanel* selected_item = mExperiencesList->getSelectedItem(); + if (!selected_item) return NULL; + + return dynamic_cast<LLExperienceItem*>(selected_item); +} + +void LLPanelExperiences::setExperienceList( const LLSD& experiences ) +{ + if (hasString("no_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("no_experiences")); + } + mExperiencesList->clear(); + + LLSD::array_const_iterator it = experiences.beginArray(); + for( /**/ ; it != experiences.endArray(); ++it) + { + LLUUID public_key = it->asUUID(); + LLExperienceItem* item = new LLExperienceItem(); + + item->init(public_key); + mExperiencesList->addItem(item, public_key); + } + + mExperiencesList->sort(); +} + +LLPanelExperiences* LLPanelExperiences::create(const std::string& name) +{ + LLPanelExperiences* panel= new LLPanelExperiences(); + panel->setName(name); + return panel; +} + +void LLPanelExperiences::removeExperiences( const LLSD& ids ) +{ + LLSD::array_const_iterator it = ids.beginArray(); + for( /**/ ; it != ids.endArray(); ++it) + { + removeExperience(it->asUUID()); + } +} + +void LLPanelExperiences::removeExperience( const LLUUID& id ) +{ + mExperiencesList->removeItemByUUID(id); +} + +void LLPanelExperiences::addExperience( const LLUUID& id ) +{ + if(!mExperiencesList->getItemByValue(id)) + { + LLExperienceItem* item = new LLExperienceItem(); + + item->init(id); + mExperiencesList->addItem(item, id); + mExperiencesList->sort(); + } +} + +void LLPanelExperiences::setButtonAction(const std::string& label, const commit_signal_t::slot_type& cb ) +{ + if(label.empty()) + { + getChild<LLLayoutPanel>("button_panel")->setVisible(false); + } + else + { + getChild<LLLayoutPanel>("button_panel")->setVisible(true); + LLButton* child = getChild<LLButton>("btn_action"); + child->setCommitCallback(cb); + child->setLabel(getString(label)); + } +} + +void LLPanelExperiences::enableButton( bool enable ) +{ + getChild<LLButton>("btn_action")->setEnabled(enable); +} + + +LLExperienceItem::LLExperienceItem() + : mName(NULL) +{ + buildFromFile("panel_experience_list_item.xml"); +} + +void LLExperienceItem::init( const LLUUID& id) +{ + mName = getChild<LLUICtrl>("experience_name"); + mName->setValue(LLSLURL("experience", id, "profile").getSLURLString()); +} + +LLExperienceItem::~LLExperienceItem() +{ + +} + +std::string LLExperienceItem::getExperienceName() const +{ + if (mName) + { + return mName->getValue(); + } + + return ""; +} + +void LLPanelSearchExperiences::doSearch() +{ + +} + +LLPanelSearchExperiences* LLPanelSearchExperiences::create( const std::string& name ) +{ + LLPanelSearchExperiences* panel= new LLPanelSearchExperiences(); + panel->getChild<LLPanel>("results")->addChild(LLPanelExperiences::create(name)); + return panel; +} + +BOOL LLPanelSearchExperiences::postBuild( void ) +{ + childSetAction("search_button", boost::bind(&LLPanelSearchExperiences::doSearch, this)); + return TRUE; +} + +bool LLExperienceItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const +{ + const LLExperienceItem* experience_item1 = dynamic_cast<const LLExperienceItem*>(item1); + const LLExperienceItem* experience_item2 = dynamic_cast<const LLExperienceItem*>(item2); + + if (!experience_item1 || !experience_item2) + { + LL_ERRS() << "item1 and item2 cannot be null" << LL_ENDL; + return true; + } + + std::string name1 = experience_item1->getExperienceName(); + std::string name2 = experience_item2->getExperienceName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; +} diff --git a/indra/newview/llpanelexperiences.h b/indra/newview/llpanelexperiences.h new file mode 100644 index 0000000000..0370499583 --- /dev/null +++ b/indra/newview/llpanelexperiences.h @@ -0,0 +1,97 @@ +/** + * @file llpanelexperiences.h + * @brief LLPanelExperiences class definition + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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 LL_LLPANELEXPERIENCES_H +#define LL_LLPANELEXPERIENCES_H + +#include "llaccordionctrltab.h" +#include "llflatlistview.h" +#include "llpanelavatar.h" + +class LLExperienceItem; +class LLPanelProfile; + + +class LLPanelSearchExperiences + : public LLPanel +{ +public: + LLPanelSearchExperiences(){} + static LLPanelSearchExperiences* create(const std::string& name); + /*virtual*/ BOOL postBuild(void); + + void doSearch(); +}; + +class LLPanelExperiences + : public LLPanel +{ +public: + LLPanelExperiences(); + + static LLPanelExperiences* create(const std::string& name); + + /*virtual*/ BOOL postBuild(void); + /*virtual*/ void onClosePanel(); + + void setExperienceList(const LLSD& experiences); + + LLExperienceItem* getSelectedExperienceItem(); + void removeExperiences( const LLSD& ids ); + void removeExperience( const LLUUID& id); + void addExperience( const LLUUID& id); + void setButtonAction(const std::string& label, const commit_signal_t::slot_type& cb); + void enableButton(bool enable); +protected: + +private: + LLFlatListView* mExperiencesList; +}; + +class LLExperienceItemComparator : public LLFlatListView::ItemComparator +{ + LOG_CLASS(LLExperienceItemComparator); + +public: + LLExperienceItemComparator() {}; + virtual ~LLExperienceItemComparator() {}; + + virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; +}; + +class LLExperienceItem + : public LLPanel +{ +public: + LLExperienceItem(); + ~LLExperienceItem(); + + void init(const LLUUID& experience_id); + std::string getExperienceName() const; +protected: + LLUICtrl* mName; +}; +#endif // LL_LLPANELEXPERIENCES_H diff --git a/indra/newview/llpanelgroup.cpp b/indra/newview/llpanelgroup.cpp index f4aab6bd4e..f67a90fd01 100755 --- a/indra/newview/llpanelgroup.cpp +++ b/indra/newview/llpanelgroup.cpp @@ -177,11 +177,13 @@ BOOL LLPanelGroup::postBuild() LLPanelGroupTab* panel_roles = findChild<LLPanelGroupTab>("group_roles_tab_panel"); LLPanelGroupTab* panel_notices = findChild<LLPanelGroupTab>("group_notices_tab_panel"); LLPanelGroupTab* panel_land = findChild<LLPanelGroupTab>("group_land_tab_panel"); + LLPanelGroupTab* panel_experiences = findChild<LLPanelGroupTab>("group_experiences_tab_panel"); if(panel_general) mTabs.push_back(panel_general); if(panel_roles) mTabs.push_back(panel_roles); if(panel_notices) mTabs.push_back(panel_notices); if(panel_land) mTabs.push_back(panel_land); + if(panel_experiences) mTabs.push_back(panel_experiences); if(panel_general) { @@ -418,6 +420,7 @@ void LLPanelGroup::setGroupID(const LLUUID& group_id) LLAccordionCtrlTab* tab_roles = getChild<LLAccordionCtrlTab>("group_roles_tab"); LLAccordionCtrlTab* tab_notices = getChild<LLAccordionCtrlTab>("group_notices_tab"); LLAccordionCtrlTab* tab_land = getChild<LLAccordionCtrlTab>("group_land_tab"); + LLAccordionCtrlTab* tab_experiences = getChild<LLAccordionCtrlTab>("group_experiences_tab"); if(mButtonJoin) mButtonJoin->setVisible(false); @@ -434,10 +437,13 @@ void LLPanelGroup::setGroupID(const LLUUID& group_id) tab_notices->changeOpenClose(tab_notices->getDisplayChildren()); if(tab_land->getDisplayChildren()) tab_land->changeOpenClose(tab_land->getDisplayChildren()); + if(tab_experiences->getDisplayChildren()) + tab_experiences->changeOpenClose(tab_land->getDisplayChildren()); tab_roles->setVisible(false); tab_notices->setVisible(false); tab_land->setVisible(false); + tab_experiences->setVisible(false); getChild<LLUICtrl>("group_name")->setVisible(false); getChild<LLUICtrl>("group_name_editor")->setVisible(true); @@ -459,6 +465,8 @@ void LLPanelGroup::setGroupID(const LLUUID& group_id) tab_notices->changeOpenClose(tab_notices->getDisplayChildren()); if(tab_land->getDisplayChildren()) tab_land->changeOpenClose(tab_land->getDisplayChildren()); + if(tab_experiences->getDisplayChildren()) + tab_experiences->changeOpenClose(tab_land->getDisplayChildren()); } LLGroupData agent_gdatap; @@ -467,6 +475,7 @@ void LLPanelGroup::setGroupID(const LLUUID& group_id) tab_roles->setVisible(is_member); tab_notices->setVisible(is_member); tab_land->setVisible(is_member); + tab_experiences->setVisible(is_member); getChild<LLUICtrl>("group_name")->setVisible(true); getChild<LLUICtrl>("group_name_editor")->setVisible(false); @@ -534,6 +543,7 @@ bool LLPanelGroup::apply() && apply(findChild<LLPanelGroupTab>("group_roles_tab_panel")) && apply(findChild<LLPanelGroupTab>("group_notices_tab_panel")) && apply(findChild<LLPanelGroupTab>("group_land_tab_panel")) + && apply(findChild<LLPanelGroupTab>("group_experiences_tab_panel")) ; } diff --git a/indra/newview/llpanelgroupexperiences.cpp b/indra/newview/llpanelgroupexperiences.cpp new file mode 100644 index 0000000000..76b68122fb --- /dev/null +++ b/indra/newview/llpanelgroupexperiences.cpp @@ -0,0 +1,143 @@ +/** + * @file llpanelgroupexperiences.cpp + * @brief List of experiences owned by a group. + * + * $LicenseInfo:firstyear=2006&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 "llpanelgroupexperiences.h" + +#include "lluictrlfactory.h" +#include "roles_constants.h" + +#include "llhttpclient.h" +#include "llagent.h" +#include "llviewerregion.h" +#include "llflatlistview.h" +#include "llpanelexperiences.h" +#include "llsd.h" + + +static LLPanelInjector<LLPanelGroupExperiences> t_panel_group_experiences("panel_group_experiences"); + + +class LLGroupExperienceResponder : public LLHTTPClient::Responder +{ +public: + LLHandle<LLPanelGroupExperiences> mHandle; + + LLGroupExperienceResponder(LLHandle<LLPanelGroupExperiences> handle) : mHandle(handle) { } + +protected: + /*virtual*/ void httpSuccess() + { + if (mHandle.isDead()) + { + return; + } + + LLPanelGroupExperiences* panel = mHandle.get(); + if (panel) + { + panel->setExperienceList(getContent().get("experience_ids")); + } + } + + /*virtual*/ void httpFailure() + { + LL_WARNS() << "experience responder failed [status:" << getStatus() << "]: " << getContent() << LL_ENDL; + } +}; + +LLPanelGroupExperiences::LLPanelGroupExperiences() +: LLPanelGroupTab(), mExperiencesList(NULL) +{ +} + +LLPanelGroupExperiences::~LLPanelGroupExperiences() +{ +} + +BOOL LLPanelGroupExperiences::postBuild() +{ + mExperiencesList = getChild<LLFlatListView>("experiences_list"); + if (hasString("loading_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("loading_experiences")); + } + else if (hasString("no_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("no_experiences")); + } + + return LLPanelGroupTab::postBuild(); +} + +void LLPanelGroupExperiences::activate() +{ + if (getGroupID() == LLUUID::null) + { + return; + } + + // search for experiences owned by the current group + std::string url = gAgent.getRegion()->getCapability("GroupExperiences"); + if (!url.empty()) + { + url += "?" + getGroupID().asString(); + + LLHTTPClient::get(url, new LLGroupExperienceResponder(getDerivedHandle<LLPanelGroupExperiences>())); + } +} + +void LLPanelGroupExperiences::setGroupID(const LLUUID& id) +{ + LLPanelGroupTab::setGroupID(id); + + if(id == LLUUID::null) + { + return; + } + + activate(); +} + +void LLPanelGroupExperiences::setExperienceList(const LLSD& experiences) +{ + if (hasString("no_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("no_experiences")); + } + mExperiencesList->clear(); + + LLSD::array_const_iterator it = experiences.beginArray(); + for ( /**/ ; it != experiences.endArray(); ++it) + { + LLUUID public_key = it->asUUID(); + LLExperienceItem* item = new LLExperienceItem(); + + item->init(public_key); + mExperiencesList->addItem(item, public_key); + } +} diff --git a/indra/newview/llpanelgroupexperiences.h b/indra/newview/llpanelgroupexperiences.h new file mode 100644 index 0000000000..ae1ecc1ac5 --- /dev/null +++ b/indra/newview/llpanelgroupexperiences.h @@ -0,0 +1,53 @@ +/** + * @file llpanelgroupexperiences.h + * @brief List of experiences owned by a group. + * + * $LicenseInfo:firstyear=2006&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 LL_LLPANELGROUPEXPERIENCES_H +#define LL_LLPANELGROUPEXPERIENCES_H + +#include "llpanelgroup.h" + +class LLFlatListView; + +class LLPanelGroupExperiences : public LLPanelGroupTab +{ +public: + LLPanelGroupExperiences(); + virtual ~LLPanelGroupExperiences(); + + // LLPanelGroupTab + virtual void activate(); + + virtual BOOL postBuild(); + + virtual void setGroupID(const LLUUID& id); + + void setExperienceList(const LLSD& experiences); + +protected: + LLFlatListView* mExperiencesList; +}; + +#endif diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 9411b8265b..932aa503e9 100755 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -86,19 +86,22 @@ #include "llviewercontrol.h" #include "llappviewer.h" #include "llfloatergotoline.h" +#include "llexperiencecache.h" +#include "llfloaterexperienceprofile.h" +#include "llexperienceassociationresponder.h" const std::string HELLO_LSL = "default\n" "{\n" - " state_entry()\n" - " {\n" - " llSay(0, \"Hello, Avatar!\");\n" - " }\n" + " state_entry()\n" + " {\n" + " llSay(0, \"Hello, Avatar!\");\n" + " }\n" "\n" - " touch_start(integer total_number)\n" - " {\n" - " llSay(0, \"Touched.\");\n" - " }\n" + " touch_start(integer total_number)\n" + " {\n" + " llSay(0, \"Touched.\");\n" + " }\n" "}\n"; const std::string HELP_LSL_PORTAL_TOPIC = "LSL_Portal"; @@ -118,6 +121,26 @@ static bool have_script_upload_cap(LLUUID& object_id) return object && (! object->getRegion()->getCapability("UpdateScriptTask").empty()); } + +class ExperienceResponder : public LLHTTPClient::Responder +{ +public: + ExperienceResponder(const LLHandle<LLLiveLSLEditor>& parent):mParent(parent) + { + } + + LLHandle<LLLiveLSLEditor> mParent; + + /*virtual*/ void httpSuccess() + { + LLLiveLSLEditor* parent = mParent.get(); + if(!parent) + return; + + parent->setExperienceIds(getContent()["experience_ids"]); + } +}; + /// --------------------------------------------------------------------------- /// LLLiveLSLFile /// --------------------------------------------------------------------------- @@ -196,7 +219,7 @@ private: protected: LLLineEditor* mSearchBox; LLLineEditor* mReplaceBox; - void onSearchBoxCommit(); + void onSearchBoxCommit(); }; LLFloaterScriptSearch* LLFloaterScriptSearch::sInstance = NULL; @@ -405,6 +428,55 @@ LLScriptEdCore::~LLScriptEdCore() } } +void LLLiveLSLEditor::experienceChanged() +{ + if(mScriptEd->getAssociatedExperience() != mExperiences->getSelectedValue().asUUID()) + { + mScriptEd->enableSave(getIsModifiable()); + //getChildView("Save_btn")->setEnabled(TRUE); + mScriptEd->setAssociatedExperience(mExperiences->getSelectedValue().asUUID()); + updateExperiencePanel(); + } +} + +void LLLiveLSLEditor::onViewProfile( LLUICtrl *ui, void* userdata ) +{ + LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; + + LLUUID id; + if(self->mExperienceEnabled->get()) + { + id=self->mScriptEd->getAssociatedExperience(); + if(id.notNull()) + { + LLFloaterReg::showInstance("experience_profile", id, true); + } + } + +} + +void LLLiveLSLEditor::onToggleExperience( LLUICtrl *ui, void* userdata ) +{ + LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; + + LLUUID id; + if(self->mExperienceEnabled->get()) + { + if(self->mScriptEd->getAssociatedExperience().isNull()) + { + id=self->mExperienceIds.beginArray()->asUUID(); + } + } + + if(id != self->mScriptEd->getAssociatedExperience()) + { + self->mScriptEd->enableSave(self->getIsModifiable()); + } + self->mScriptEd->setAssociatedExperience(id); + + self->updateExperiencePanel(); +} + BOOL LLScriptEdCore::postBuild() { mErrorList = getChild<LLScrollListCtrl>("lsl errors"); @@ -811,7 +883,7 @@ bool LLScriptEdCore::handleSaveChangesDialog(const LLSD& notification, const LLS case 2: // "Cancel" default: // If we were quitting, we didn't really mean it. - LLAppViewer::instance()->abortQuit(); + LLAppViewer::instance()->abortQuit(); break; } return false; @@ -846,8 +918,8 @@ void LLScriptEdCore::onBtnDynamicHelp() LLKeywordToken *token; LLKeywords::keyword_iterator_t token_it; for (token_it = mEditor->keywordsBegin(); - token_it != mEditor->keywordsEnd(); - ++token_it) + token_it != mEditor->keywordsEnd(); + ++token_it) { token = token_it->second; help_combo->add(wstring_to_utf8str(token->getToken())); @@ -1199,6 +1271,130 @@ bool LLScriptEdCore::enableLoadFromFileMenu(void* userdata) return (self && self->mEditor) ? self->mEditor->canLoadOrSaveToFile() : FALSE; } +LLUUID LLScriptEdCore::getAssociatedExperience()const +{ + return mAssociatedExperience; +} + +void LLLiveLSLEditor::setExperienceIds( const LLSD& experience_ids ) +{ + mExperienceIds=experience_ids; + updateExperiencePanel(); +} + + +void LLLiveLSLEditor::updateExperiencePanel() +{ + if(mScriptEd->getAssociatedExperience().isNull()) + { + mExperienceEnabled->set(FALSE); + mExperiences->setVisible(FALSE); + if(mExperienceIds.size()>0) + { + mExperienceEnabled->setEnabled(TRUE); + mExperienceEnabled->setToolTip(getString("add_experiences")); + } + else + { + mExperienceEnabled->setEnabled(FALSE); + mExperienceEnabled->setToolTip(getString("no_experiences")); + } + getChild<LLButton>("view_profile")->setVisible(FALSE); + } + else + { + mExperienceEnabled->setToolTip(getString("experience_enabled")); + mExperienceEnabled->setEnabled(getIsModifiable()); + mExperiences->setVisible(TRUE); + mExperienceEnabled->set(TRUE); + buildExperienceList(); + } +} + +void LLLiveLSLEditor::buildExperienceList() +{ + mExperiences->clearRows(); + bool foundAssociated=false; + const LLUUID& associated = mScriptEd->getAssociatedExperience(); + LLUUID last; + LLScrollListItem* item; + for(LLSD::array_const_iterator it = mExperienceIds.beginArray(); it != mExperienceIds.endArray(); ++it) + { + LLUUID id = it->asUUID(); + EAddPosition position = ADD_BOTTOM; + if(id == associated) + { + foundAssociated = true; + position = ADD_TOP; + } + + const LLSD& experience = LLExperienceCache::get(id); + if(experience.isUndefined()) + { + mExperiences->add(getString("loading"), id, position); + last = id; + } + else + { + mExperiences->add(experience[LLExperienceCache::NAME].asString(), id, position); + } + } + + if(!foundAssociated ) + { + const LLSD& experience = LLExperienceCache::get(associated); + if(experience.isDefined()) + { + item=mExperiences->add(experience[LLExperienceCache::NAME].asString(), associated, ADD_TOP); + } + else + { + item=mExperiences->add(getString("loading"), associated, ADD_TOP); + last = associated; + } + item->setEnabled(FALSE); + } + + if(last.notNull()) + { + mExperiences->setEnabled(FALSE); + LLExperienceCache::get(last, boost::bind(&LLLiveLSLEditor::buildExperienceList, this)); + } + else + { + mExperiences->setEnabled(TRUE); + getChild<LLButton>("view_profile")->setVisible(TRUE); + } +} + + +void LLScriptEdCore::setAssociatedExperience( const LLUUID& experience_id ) +{ + mAssociatedExperience = experience_id; +} + + + +void LLLiveLSLEditor::requestExperiences() +{ + if (!getIsModifiable()) + { + return; + } + + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string lookup_url=region->getCapability("GetCreatorExperiences"); + if(!lookup_url.empty()) + { + LLHTTPClient::get(lookup_url, new ExperienceResponder(getDerivedHandle<LLLiveLSLEditor>())); + } + } +} + + + /// --------------------------------------------------------------------------- /// LLScriptEdContainer /// --------------------------------------------------------------------------- @@ -1217,7 +1413,7 @@ std::string LLScriptEdContainer::getTmpFileName() std::string script_id = mObjectUUID.asString() + "_" + mItemUUID.asString(); // Use MD5 sum to make the file name shorter and not exceed maximum path length. - char script_id_hash_str[33]; /* Flawfinder: ignore */ + char script_id_hash_str[33]; /* Flawfinder: ignore */ LLMD5 script_id_hash((const U8 *)script_id.c_str()); script_id_hash.hex_digest(script_id_hash_str); @@ -1755,6 +1951,16 @@ BOOL LLLiveLSLEditor::postBuild() mScriptEd->mEditor->makePristine(); mScriptEd->mEditor->setFocus(TRUE); + + mExperiences = getChild<LLComboBox>("Experiences..."); + mExperiences->setCommitCallback(boost::bind(&LLLiveLSLEditor::experienceChanged, this)); + + mExperienceEnabled = getChild<LLCheckBoxCtrl>("enable_xp"); + + childSetCommitCallback("enable_xp", onToggleExperience, this); + childSetCommitCallback("view_profile", onViewProfile, this); + + return LLPreview::postBuild(); } @@ -1798,60 +2004,59 @@ void LLLiveLSLEditor::loadAsset() if(object) { LLViewerInventoryItem* item = dynamic_cast<LLViewerInventoryItem*>(object->getInventoryObject(mItemUUID)); - if(item - && (gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE) - || gAgent.isGodlike())) - { - mItem = new LLViewerInventoryItem(item); - //LL_INFOS() << "asset id " << mItem->getAssetUUID() << LL_ENDL; - } - if(!gAgent.isGodlike() - && (item - && (!gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE) - || !gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)))) - { - mItem = new LLViewerInventoryItem(item); - mScriptEd->setScriptText(getString("not_allowed"), FALSE); - mScriptEd->mEditor->makePristine(); - mScriptEd->enableSave(FALSE); - mAssetStatus = PREVIEW_ASSET_LOADED; - } - else if(item && mItem.notNull()) + if(item) { - // request the text from the object - LLUUID* user_data = new LLUUID(mItemUUID); // ^ mObjectUUID - gAssetStorage->getInvItemAsset(object->getRegion()->getHost(), - gAgent.getID(), - gAgent.getSessionID(), - item->getPermissions().getOwner(), - object->getID(), - item->getUUID(), - item->getAssetUUID(), - item->getType(), - &LLLiveLSLEditor::onLoadComplete, - (void*)user_data, - TRUE); - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_GetScriptRunning); - msg->nextBlockFast(_PREHASH_Script); - msg->addUUIDFast(_PREHASH_ObjectID, mObjectUUID); - msg->addUUIDFast(_PREHASH_ItemID, mItemUUID); - msg->sendReliable(object->getRegion()->getHost()); - mAskedForRunningInfo = TRUE; - mAssetStatus = PREVIEW_ASSET_LOADING; + ExperienceAssociationResponder::fetchAssociatedExperience(item->getParentUUID(), item->getUUID(), boost::bind(&LLLiveLSLEditor::setAssociatedExperience, getDerivedHandle<LLLiveLSLEditor>(), _1)); + + bool isGodlike = gAgent.isGodlike(); + bool copyManipulate = gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE); + mIsModifiable = gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE); + + if(!isGodlike && (!copyManipulate || !mIsModifiable)) + { + mItem = new LLViewerInventoryItem(item); + mScriptEd->setScriptText(getString("not_allowed"), FALSE); + mScriptEd->mEditor->makePristine(); + mScriptEd->enableSave(FALSE); + mAssetStatus = PREVIEW_ASSET_LOADED; + } + else if(copyManipulate || isGodlike) + { + mItem = new LLViewerInventoryItem(item); + // request the text from the object + LLUUID* user_data = new LLUUID(mItemUUID); // ^ mObjectUUID + gAssetStorage->getInvItemAsset(object->getRegion()->getHost(), + gAgent.getID(), + gAgent.getSessionID(), + item->getPermissions().getOwner(), + object->getID(), + item->getUUID(), + item->getAssetUUID(), + item->getType(), + &LLLiveLSLEditor::onLoadComplete, + (void*)user_data, + TRUE); + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_GetScriptRunning); + msg->nextBlockFast(_PREHASH_Script); + msg->addUUIDFast(_PREHASH_ObjectID, mObjectUUID); + msg->addUUIDFast(_PREHASH_ItemID, mItemUUID); + msg->sendReliable(object->getRegion()->getHost()); + mAskedForRunningInfo = TRUE; + mAssetStatus = PREVIEW_ASSET_LOADING; + } } - else + + if(mItem.isNull()) { mScriptEd->setScriptText(LLStringUtil::null, FALSE); mScriptEd->mEditor->makePristine(); mAssetStatus = PREVIEW_ASSET_LOADED; + mIsModifiable = FALSE; } - mIsModifiable = item && gAgent.allowOperation(PERM_MODIFY, - item->getPermissions(), - GP_OBJECT_MANIPULATE); - + refreshFromItem(); // This is commented out, because we don't completely // handle script exports yet. /* @@ -1888,6 +2093,8 @@ void LLLiveLSLEditor::loadAsset() time_corrected()); mAssetStatus = PREVIEW_ASSET_LOADED; } + + requestExperiences(); } // static @@ -2150,7 +2357,7 @@ void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) BOOL is_running = getChild<LLCheckBoxCtrl>( "running")->get(); if (!url.empty()) { - uploadAssetViaCaps(url, filename, mObjectUUID, mItemUUID, is_running); + uploadAssetViaCaps(url, filename, mObjectUUID, mItemUUID, is_running, mScriptEd->getAssociatedExperience()); } else if (gAssetStorage) { @@ -2162,7 +2369,8 @@ void LLLiveLSLEditor::uploadAssetViaCaps(const std::string& url, const std::string& filename, const LLUUID& task_id, const LLUUID& item_id, - BOOL is_running) + BOOL is_running, + const LLUUID& experience_public_id ) { LL_INFOS() << "Update Task Inventory via capability " << url << LL_ENDL; LLSD body; @@ -2170,6 +2378,7 @@ void LLLiveLSLEditor::uploadAssetViaCaps(const std::string& url, body["item_id"] = item_id; body["is_script_running"] = is_running; body["target"] = monoChecked() ? "mono" : "lsl2"; + body["experience"] = experience_public_id; LLHTTPClient::post(url, body, new LLUpdateTaskInventoryResponder(body, filename, LLAssetType::AT_LSL_TEXT)); } @@ -2421,3 +2630,18 @@ BOOL LLLiveLSLEditor::monoChecked() const } return FALSE; } + +void LLLiveLSLEditor::setAssociatedExperience( LLHandle<LLLiveLSLEditor> editor, const LLSD& experience ) +{ + LLLiveLSLEditor* scriptEd = editor.get(); + if(scriptEd) + { + LLUUID id; + if(experience.has(LLExperienceCache::EXPERIENCE_ID)) + { + id=experience[LLExperienceCache::EXPERIENCE_ID].asUUID(); + } + scriptEd->mScriptEd->setAssociatedExperience(id); + scriptEd->updateExperiencePanel(); + } +} diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 9ea191e928..fd94f32c72 100755 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -51,6 +51,7 @@ class LLVFS; class LLViewerInventoryItem; class LLScriptEdContainer; class LLFloaterGotoLine; +class LLFloaterExperienceProfile; // Inner, implementation class. LLPreviewScript and LLLiveLSLEditor each own one of these. class LLScriptEdCore : public LLPanel @@ -107,12 +108,14 @@ public: static void onBtnInsertSample(void*); static void onBtnInsertFunction(LLUICtrl*, void*); static void onBtnLoadFromFile(void*); - static void onBtnSaveToFile(void*); + static void onBtnSaveToFile(void*); static bool enableSaveToFileMenu(void* userdata); static bool enableLoadFromFileMenu(void* userdata); - virtual bool hasAccelerators() const { return true; } + virtual bool hasAccelerators() const { return true; } + LLUUID getAssociatedExperience()const; + void setAssociatedExperience( const LLUUID& experience_id ); private: void onBtnHelp(); @@ -142,8 +145,8 @@ private: void (*mLoadCallback)(void* userdata); void (*mSaveCallback)(void* userdata, BOOL close_after_save); void (*mSearchReplaceCallback) (void* userdata); - void* mUserdata; - LLComboBox *mFunctions; + void* mUserdata; + LLComboBox *mFunctions; BOOL mForceClose; LLPanel* mCodePanel; LLScrollListCtrl* mErrorList; @@ -155,6 +158,7 @@ private: BOOL mEnableSave; BOOL mHasScriptData; LLLiveLSLFile* mLiveFile; + LLUUID mAssociatedExperience; LLScriptEdContainer* mContainer; // parent view @@ -241,7 +245,18 @@ public: /*virtual*/ BOOL postBuild(); - void setIsNew() { mIsNew = TRUE; } + void setIsNew() { mIsNew = TRUE; } + + static void setAssociatedExperience( LLHandle<LLLiveLSLEditor> editor, const LLSD& experience ); + static void onToggleExperience(LLUICtrl *ui, void* userdata); + static void onViewProfile(LLUICtrl *ui, void* userdata); + + void setExperienceIds(const LLSD& experience_ids); + void buildExperienceList(); + void updateExperiencePanel(); + void requestExperiences(); + void experienceChanged(); + void addAssociatedExperience(const LLSD& experience); private: virtual BOOL canClose(); @@ -252,10 +267,11 @@ private: void loadAsset(BOOL is_new); /*virtual*/ void saveIfNeeded(bool sync = true); void uploadAssetViaCaps(const std::string& url, - const std::string& filename, + const std::string& filename, const LLUUID& task_id, const LLUUID& item_id, - BOOL is_running); + BOOL is_running, + const LLUUID& experience_public_id); void uploadAssetLegacy(const std::string& filename, LLViewerObject* object, const LLTransactionID& tid, @@ -296,9 +312,16 @@ private: S32 mPendingUploads; BOOL getIsModifiable() const { return mIsModifiable; } // Evaluated on load assert - + LLCheckBoxCtrl* mMonoCheckbox; BOOL mIsModifiable; + + + LLComboBox* mExperiences; + LLCheckBoxCtrl* mExperienceEnabled; + LLSD mExperienceIds; + + LLHandle<LLFloater> mExperienceProfile; }; #endif // LL_LLPREVIEWSCRIPT_H diff --git a/indra/newview/llscreenchannel.cpp b/indra/newview/llscreenchannel.cpp index 8708fb87ee..6d94b178dd 100755 --- a/indra/newview/llscreenchannel.cpp +++ b/indra/newview/llscreenchannel.cpp @@ -541,19 +541,23 @@ void LLScreenChannel::modifyToastByNotificationID(LLUUID id, LLPanel* panel) { std::vector<ToastElem>::iterator it = find(mToastList.begin(), mToastList.end(), id); + LLPanel* panel_to_delete = panel; + if( it != mToastList.end() && panel) { LLToast* toast = it->getToast(); if (toast) { - LLPanel* old_panel = toast->getPanel(); - toast->removeChild(old_panel); - delete old_panel; - toast->insertPanel(panel); - toast->startTimer(); + LLPanel* old_panel = toast->getPanel(); + toast->removeChild(old_panel); + panel_to_delete = old_panel; + toast->insertPanel(panel); + toast->startTimer(); } redrawToasts(); } + + delete panel_to_delete; } //-------------------------------------------------------------------------- diff --git a/indra/newview/llsidepaneliteminfo.cpp b/indra/newview/llsidepaneliteminfo.cpp index 1d20b7bed5..d75d4ae951 100755 --- a/indra/newview/llsidepaneliteminfo.cpp +++ b/indra/newview/llsidepaneliteminfo.cpp @@ -43,6 +43,9 @@ #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewerobjectlist.h" +#include "llexperienceassociationresponder.h" +#include "llexperiencecache.h" +#include "lltrans.h" //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -316,6 +319,15 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) is_obj_modify = object->permOwnerModify(); } + if(item->getInventoryType() == LLInventoryType::IT_LSL) + { + getChildView("LabelItemExperienceTitle")->setVisible(TRUE); + LLTextBox* tb = getChild<LLTextBox>("LabelItemExperience"); + tb->setText(getString("loading_experience")); + tb->setVisible(TRUE); + ExperienceAssociationResponder::fetchAssociatedExperience(item->getParentUUID(), item->getUUID(), boost::bind(&LLSidepanelItemInfo::setAssociatedExperience, getDerivedHandle<LLSidepanelItemInfo>(), _1)); + } + ////////////////////// // ITEM NAME & DESC // ////////////////////// @@ -666,6 +678,29 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) } } + +void LLSidepanelItemInfo::setAssociatedExperience( LLHandle<LLSidepanelItemInfo> hInfo, const LLSD& experience ) +{ + LLSidepanelItemInfo* info = hInfo.get(); + if(info) + { + LLUUID id; + if(experience.has(LLExperienceCache::EXPERIENCE_ID)) + { + id=experience[LLExperienceCache::EXPERIENCE_ID].asUUID(); + } + if(id.notNull()) + { + info->getChild<LLTextBox>("LabelItemExperience")->setText(LLSLURL("experience", id, "profile").getSLURLString()); + } + else + { + info->getChild<LLTextBox>("LabelItemExperience")->setText(LLTrans::getString("ExperienceNameNull")); + } + } +} + + void LLSidepanelItemInfo::startObjectInventoryObserver() { if (!mObjectInventoryObserver) diff --git a/indra/newview/llsidepaneliteminfo.h b/indra/newview/llsidepaneliteminfo.h index 12aaca923e..2e24e58a2a 100755 --- a/indra/newview/llsidepaneliteminfo.h +++ b/indra/newview/llsidepaneliteminfo.h @@ -67,6 +67,8 @@ protected: void refreshFromItem(LLViewerInventoryItem* item); private: + static void setAssociatedExperience( LLHandle<LLSidepanelItemInfo> hInfo, const LLSD& experience ); + void startObjectInventoryObserver(); void stopObjectInventoryObserver(); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 0c282a19a5..884d0cc8d2 100755 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -48,6 +48,7 @@ #include "llares.h" #include "llavatarnamecache.h" +#include "llexperiencecache.h" #include "lllandmark.h" #include "llcachename.h" #include "lldir.h" @@ -194,6 +195,7 @@ #include "llevents.h" #include "llstartuplistener.h" #include "lltoolbarview.h" +#include "llexperiencelog.h" #if LL_WINDOWS #include "lldxhardware.h" @@ -1306,6 +1308,9 @@ bool idle_startup() gAgent.setPositionAgent(agent_start_position_region); display_startup(); + LLStartUp::initExperiences(); + + display_startup(); LLStartUp::setStartupState( STATE_MULTIMEDIA_INIT ); LLConversationLog::getInstance(); @@ -2836,6 +2841,14 @@ void LLStartUp::initNameCache() LLAvatarNameCache::setUseUsernames(gSavedSettings.getBOOL("NameTagShowUsernames")); } + +void LLStartUp::initExperiences() +{ + LLAppViewer::instance()->loadExperienceCache(); + LLExperienceCache::initClass(); + LLExperienceLog::instance().initialize(); +} + void LLStartUp::cleanupNameCache() { LLAvatarNameCache::cleanupClass(); @@ -3537,3 +3550,4 @@ void transition_back_to_login_panel(const std::string& emsg) reset_login(); // calls LLStartUp::setStartupState( STATE_LOGIN_SHOW ); gSavedSettings.setBOOL("AutoLogin", FALSE); } + diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h index e39810713a..d434e86f82 100755 --- a/indra/newview/llstartup.h +++ b/indra/newview/llstartup.h @@ -103,6 +103,7 @@ public: static void fontInit(); static void initNameCache(); + static void initExperiences(); static void cleanupNameCache(); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 4e491f257d..7f497aed4a 100755 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -61,6 +61,9 @@ #include "llfloatereditsky.h" #include "llfloatereditwater.h" #include "llfloaterenvironmentsettings.h" +#include "llfloaterexperienceprofile.h" +#include "llfloaterexperiences.h" +#include "llfloaterexperiencepicker.h" #include "llfloaterevent.h" #include "llfloaterfacebook.h" #include "llfloaterflickr.h" @@ -212,8 +215,11 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("env_edit_water", "floater_edit_water_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEditWater>); LLFloaterReg::add("env_edit_day_cycle", "floater_edit_day_cycle.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEditDayCycle>); - LLFloaterReg::add("event", "floater_event.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEvent>); - + LLFloaterReg::add("event", "floater_event.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEvent>); + LLFloaterReg::add("experiences", "floater_experiences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterExperiences>); + LLFloaterReg::add("experience_profile", "floater_experienceprofile.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterExperienceProfile>); + LLFloaterReg::add("experience_search", "floater_experience_search.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterExperiencePicker>); + LLFloaterReg::add("font_test", "floater_font_test.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFontTest>); LLFloaterReg::add("gestures", "floater_gesture.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGesture>); diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 9d5c3c4d4a..929affe311 100755 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -117,12 +117,15 @@ #include <boost/regex.hpp> #include "llnotificationmanager.h" // +#include "llexperiencecache.h" #if LL_MSVC // disable boost::lexical_cast warning #pragma warning (disable:4702) #endif + + extern void on_new_message(const LLSD& msg); // @@ -145,6 +148,7 @@ extern bool gShiftFrame; bool check_offer_throttle(const std::string& from_name, bool check_only); bool check_asset_previewable(const LLAssetType::EType asset_type); static void process_money_balance_reply_extended(LLMessageSystem* msg); +bool handle_trusted_experiences_notification(const LLSD&); //inventory offer throttle globals LLFrameTimer gThrottleTimer; @@ -5687,146 +5691,174 @@ bool handle_prompt_for_maturity_level_change_and_reteleport_callback(const LLSD& // some of the server notifications need special handling. This is where we do that. bool handle_special_notification(std::string notificationID, LLSD& llsdBlock) { - U8 regionAccess = static_cast<U8>(llsdBlock["_region_access"].asInteger()); - std::string regionMaturity = LLViewerRegion::accessToString(regionAccess); - LLStringUtil::toLower(regionMaturity); - llsdBlock["REGIONMATURITY"] = regionMaturity; bool returnValue = false; - LLNotificationPtr maturityLevelNotification; - std::string notifySuffix = "_Notify"; - if (regionAccess == SIM_ACCESS_MATURE) + if(llsdBlock.has("_region_access")) { - if (gAgent.isTeen()) + U8 regionAccess = static_cast<U8>(llsdBlock["_region_access"].asInteger()); + std::string regionMaturity = LLViewerRegion::accessToString(regionAccess); + LLStringUtil::toLower(regionMaturity); + llsdBlock["REGIONMATURITY"] = regionMaturity; + LLNotificationPtr maturityLevelNotification; + std::string notifySuffix = "_Notify"; + if (regionAccess == SIM_ACCESS_MATURE) { - gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); - returnValue = true; + if (gAgent.isTeen()) + { + gAgent.clearTeleportRequest(); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); + returnValue = true; - notifySuffix = "_NotifyAdultsOnly"; + notifySuffix = "_NotifyAdultsOnly"; + } + else if (gAgent.prefersPG()) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } + else if (LLStringUtil::compareStrings(notificationID, "RegionEntryAccessBlocked") == 0) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock); + returnValue = true; + } } - else if (gAgent.prefersPG()) + else if (regionAccess == SIM_ACCESS_ADULT) { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; + if (!gAgent.isAdult()) + { + gAgent.clearTeleportRequest(); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); + returnValue = true; + + notifySuffix = "_NotifyAdultsOnly"; + } + else if (gAgent.prefersPG() || gAgent.prefersMature()) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } + else if (LLStringUtil::compareStrings(notificationID, "RegionEntryAccessBlocked") == 0) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock); + returnValue = true; + } } - else if (LLStringUtil::compareStrings(notificationID, "RegionEntryAccessBlocked") == 0) + + if ((maturityLevelNotification == NULL) || maturityLevelNotification->isIgnored()) { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock); - returnValue = true; + // Given a simple notification if no maturityLevelNotification is set or it is ignore + LLNotificationsUtil::add(notificationID + notifySuffix, llsdBlock); } } - else if (regionAccess == SIM_ACCESS_ADULT) - { - if (!gAgent.isAdult()) - { - gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); - returnValue = true; - notifySuffix = "_NotifyAdultsOnly"; - } - else if (gAgent.prefersPG() || gAgent.prefersMature()) + return returnValue; +} + +bool handle_trusted_experiences_notification(const LLSD& llsdBlock) +{ + if(llsdBlock.has("trusted_experiences")) + { + std::ostringstream str; + const LLSD& experiences = llsdBlock["trusted_experiences"]; + LLSD::array_const_iterator it = experiences.beginArray(); + for(/**/; it != experiences.endArray(); ++it) { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; + str<<LLSLURL("experience", it->asUUID(), "profile").getSLURLString() << "\n"; } - else if (LLStringUtil::compareStrings(notificationID, "RegionEntryAccessBlocked") == 0) + std::string str_list = str.str(); + if(!str_list.empty()) { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock); - returnValue = true; + LLNotificationsUtil::add("TrustedExperiencesAvailable", LLSD::emptyMap().with("EXPERIENCE_LIST", (LLSD)str_list)); + return true; } } - - if ((maturityLevelNotification == NULL) || maturityLevelNotification->isIgnored()) - { - // Given a simple notification if no maturityLevelNotification is set or it is ignore - LLNotificationsUtil::add(notificationID + notifySuffix, llsdBlock); - } - - return returnValue; + return false; } // some of the server notifications need special handling. This is where we do that. bool handle_teleport_access_blocked(LLSD& llsdBlock) { - std::string notificationID("TeleportEntryAccessBlocked"); - U8 regionAccess = static_cast<U8>(llsdBlock["_region_access"].asInteger()); - std::string regionMaturity = LLViewerRegion::accessToString(regionAccess); - LLStringUtil::toLower(regionMaturity); - llsdBlock["REGIONMATURITY"] = regionMaturity; - bool returnValue = false; - LLNotificationPtr maturityLevelNotification; - std::string notifySuffix = "_Notify"; - if (regionAccess == SIM_ACCESS_MATURE) + if(llsdBlock.has("_region_access")) { - if (gAgent.isTeen()) - { - gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); - returnValue = true; - - notifySuffix = "_NotifyAdultsOnly"; - } - else if (gAgent.prefersPG()) + std::string notificationID("TeleportEntryAccessBlocked"); + U8 regionAccess = static_cast<U8>(llsdBlock["_region_access"].asInteger()); + std::string regionMaturity = LLViewerRegion::accessToString(regionAccess); + LLStringUtil::toLower(regionMaturity); + llsdBlock["REGIONMATURITY"] = regionMaturity; + + LLNotificationPtr maturityLevelNotification; + std::string notifySuffix = "_Notify"; + if (regionAccess == SIM_ACCESS_MATURE) { - if (gAgent.hasRestartableFailedTeleportRequest()) + if (gAgent.isTeen()) { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_ChangeAndReTeleport", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_and_reteleport_callback); + gAgent.clearTeleportRequest(); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); returnValue = true; + + notifySuffix = "_NotifyAdultsOnly"; + } + else if (gAgent.prefersPG()) + { + if (gAgent.hasRestartableFailedTeleportRequest()) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_ChangeAndReTeleport", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_and_reteleport_callback); + returnValue = true; + } + else + { + gAgent.clearTeleportRequest(); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } } else { gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); returnValue = true; } } - else + else if (regionAccess == SIM_ACCESS_ADULT) { - gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; - } - } - else if (regionAccess == SIM_ACCESS_ADULT) - { - if (!gAgent.isAdult()) - { - gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); - returnValue = true; - - notifySuffix = "_NotifyAdultsOnly"; - } - else if (gAgent.prefersPG() || gAgent.prefersMature()) - { - if (gAgent.hasRestartableFailedTeleportRequest()) + if (!gAgent.isAdult()) { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_ChangeAndReTeleport", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_and_reteleport_callback); + gAgent.clearTeleportRequest(); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); returnValue = true; + + notifySuffix = "_NotifyAdultsOnly"; + } + else if (gAgent.prefersPG() || gAgent.prefersMature()) + { + if (gAgent.hasRestartableFailedTeleportRequest()) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_ChangeAndReTeleport", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_and_reteleport_callback); + returnValue = true; + } + else + { + gAgent.clearTeleportRequest(); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } } else { gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); returnValue = true; } - } - else + } + + if ((maturityLevelNotification == NULL) || maturityLevelNotification->isIgnored()) { - gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; - } + // Given a simple notification if no maturityLevelNotification is set or it is ignore + LLNotificationsUtil::add(notificationID + notifySuffix, llsdBlock); } - - if ((maturityLevelNotification == NULL) || maturityLevelNotification->isIgnored()) - { - // Given a simple notification if no maturityLevelNotification is set or it is ignore - LLNotificationsUtil::add(notificationID + notifySuffix, llsdBlock); } + handle_trusted_experiences_notification(llsdBlock); return returnValue; } @@ -5855,6 +5887,9 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem) LL_WARNS() << "attempt_standard_notification: Attempted to read notification parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; } } + + + handle_trusted_experiences_notification(llsdBlock); if ( (notificationID == "RegionEntryAccessBlocked") || @@ -5938,6 +5973,7 @@ bool attempt_standard_notification(LLMessageSystem* msgsystem) } LLNotificationsUtil::add(notificationID, llsdBlock); + return true; } return false; @@ -6320,6 +6356,12 @@ bool script_question_cb(const LLSD& notification, const LLSD& response) return false; } + LLUUID experience; + if(notification["payload"].has("experience")) + { + experience = notification["payload"]["experience"].asUUID(); + } + // check whether permissions were granted or denied BOOL allowed = TRUE; // the "yes/accept" button is the first button in the template, making it button 0 @@ -6328,7 +6370,17 @@ bool script_question_cb(const LLSD& notification, const LLSD& response) { new_questions = 0; allowed = FALSE; - } + } + else if(experience.notNull()) + { + LLSD permission; + LLSD data; + permission["permission"]="Allow"; + + data[experience.asString()]=permission; + data["experience"]=experience; + LLEventPumps::instance().obtain("experience_permission").post(data); + } LLUUID task_id = notification["payload"]["task_id"].asUUID(); LLUUID item_id = notification["payload"]["item_id"].asUUID(); @@ -6355,7 +6407,27 @@ bool script_question_cb(const LLSD& notification, const LLSD& response) { script_question_mute(task_id,notification["payload"]["object_name"].asString()); } + if ( response["BlockExperience"] ) + { + if(experience.notNull()) + { + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return false; + + std::string lookup_url=region->getCapability("ExperiencePreferences"); + if(lookup_url.empty()) + return false; + LLSD permission; + LLSD data; + permission["permission"]="Block"; + data[experience.asString()]=permission; + LLHTTPClient::put(lookup_url, data, NULL); + data["experience"]=experience; + LLEventPumps::instance().obtain("experience_permission").post(data); + } + } return false; } @@ -6388,8 +6460,24 @@ void script_question_mute(const LLUUID& task_id, const std::string& object_name) static LLNotificationFunctorRegistration script_question_cb_reg_1("ScriptQuestion", script_question_cb); static LLNotificationFunctorRegistration script_question_cb_reg_2("ScriptQuestionCaution", script_question_cb); +static LLNotificationFunctorRegistration script_question_cb_reg_3("ScriptQuestionExperience", script_question_cb); static LLNotificationFunctorRegistration unknown_script_question_cb_reg("UnknownScriptQuestion", unknown_script_question_cb); +void process_script_experience_details(const LLSD& experience_details, LLSD args, LLSD payload) +{ + if(experience_details[LLExperienceCache::PROPERTIES].asInteger() & LLExperienceCache::PROPERTY_GRID) + { + args["GRID_WIDE"] = LLTrans::getString("GRID_WIDE")+ " "; + } + else + { + args["GRID_WIDE"] = ""; + } + args["EXPERIENCE"] = LLSLURL("experience", experience_details[LLExperienceCache::EXPERIENCE_ID].asUUID(), "profile").getSLURLString(); + + LLNotificationsUtil::add("ScriptQuestionExperience", args, payload); +} + void process_script_question(LLMessageSystem *msg, void **user_data) { // *TODO: Translate owner name -> [FIRST] [LAST] @@ -6401,6 +6489,7 @@ void process_script_question(LLMessageSystem *msg, void **user_data) S32 questions; std::string object_name; std::string owner_name; + LLUUID experienceid; // taskid -> object key of object requesting permissions msg->getUUIDFast(_PREHASH_Data, _PREHASH_TaskID, taskid ); @@ -6410,6 +6499,11 @@ void process_script_question(LLMessageSystem *msg, void **user_data) msg->getStringFast(_PREHASH_Data, _PREHASH_ObjectOwner, owner_name); msg->getS32Fast(_PREHASH_Data, _PREHASH_Questions, questions ); + if(msg->has(_PREHASH_Experience)) + { + msg->getUUIDFast(_PREHASH_Experience, _PREHASH_ExperienceID, experienceid); + } + // Special case. If the objects are owned by this agent, throttle per-object instead // of per-owner. It's common for residents to reset a ton of scripts that re-request // permissions, as with tier boxes. UUIDs can't be valid agent names and vice-versa, @@ -6495,21 +6589,21 @@ void process_script_question(LLMessageSystem *msg, void **user_data) payload["object_name"] = object_name; payload["owner_name"] = owner_name; - // check whether cautions are even enabled or not - if (gSavedSettings.getBOOL("PermissionsCautionEnabled")) + const char* notification = "ScriptQuestion"; + + if(caution && gSavedSettings.getBOOL("PermissionsCautionEnabled")) { - if (caution) - { - args["FOOTERTEXT"] = (count > 1) ? LLTrans::getString("AdditionalPermissionsRequestHeader") + "\n\n" + script_question : ""; - } - // display the caution permissions prompt - LLNotificationsUtil::add(caution ? "ScriptQuestionCaution" : "ScriptQuestion", args, payload); + args["FOOTERTEXT"] = (count > 1) ? LLTrans::getString("AdditionalPermissionsRequestHeader") + "\n\n" + script_question : ""; + notification = "ScriptQuestionCaution"; } - else + else if(experienceid.notNull()) { - // fall back to default behavior if cautions are entirely disabled - LLNotificationsUtil::add("ScriptQuestion", args, payload); + payload["experience"]=experienceid; + LLExperienceCache::get(experienceid, boost::bind(process_script_experience_details, _1, args, payload)); + return; } + + LLNotificationsUtil::add(notification, args, payload); } } } diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp index cdb08f4707..30d743b409 100755 --- a/indra/newview/llviewerparcelmgr.cpp +++ b/indra/newview/llviewerparcelmgr.cpp @@ -204,8 +204,8 @@ void LLViewerParcelMgr::dump() mCurrentParcel->dump(); LL_INFOS() << "banning " << mCurrentParcel->mBanList.size() << LL_ENDL; - access_map_const_iterator cit = mCurrentParcel->mBanList.begin(); - access_map_const_iterator end = mCurrentParcel->mBanList.end(); + LLAccessEntry::map::const_iterator cit = mCurrentParcel->mBanList.begin(); + LLAccessEntry::map::const_iterator end = mCurrentParcel->mBanList.end(); for ( ; cit != end; ++cit) { LL_INFOS() << "ban id " << (*cit).first << LL_ENDL; @@ -891,7 +891,7 @@ void LLViewerParcelMgr::sendParcelAccessListRequest(U32 flags) if (!region) return; LLMessageSystem *msg = gMessageSystem; - + if (flags & AL_BAN) { @@ -901,6 +901,14 @@ void LLViewerParcelMgr::sendParcelAccessListRequest(U32 flags) { mCurrentParcel->mAccessList.clear(); } + if (flags & AL_ALLOW_EXPERIENCE) + { + mCurrentParcel->clearExperienceKeysByType(EXPERIENCE_KEY_TYPE_ALLOWED); + } + if (flags & AL_BLOCK_EXPERIENCE) + { + mCurrentParcel->clearExperienceKeysByType(EXPERIENCE_KEY_TYPE_BLOCKED); + } // Only the headers differ msg->newMessageFast(_PREHASH_ParcelAccessListRequest); @@ -1656,7 +1664,7 @@ void LLViewerParcelMgr::processParcelProperties(LLMessageSystem *msg, void **use } // Request access list information for this land - parcel_mgr.sendParcelAccessListRequest(AL_ACCESS | AL_BAN); + parcel_mgr.sendParcelAccessListRequest(AL_ACCESS | AL_BAN | AL_ALLOW_EXPERIENCE | AL_BLOCK_EXPERIENCE); // Request dwell for this land, if it's not public land. parcel_mgr.mSelectedDwell = DWELL_NAN; @@ -1821,6 +1829,14 @@ void LLViewerParcelMgr::processParcelAccessListReply(LLMessageSystem *msg, void { parcel->unpackAccessEntries(msg, &(parcel->mBanList) ); } + else if (message_flags & AL_ALLOW_EXPERIENCE) + { + parcel->unpackExperienceEntries(msg, EXPERIENCE_KEY_TYPE_ALLOWED); + } + else if (message_flags & AL_BLOCK_EXPERIENCE) + { + parcel->unpackExperienceEntries(msg, EXPERIENCE_KEY_TYPE_BLOCKED); + } /*else if (message_flags & AL_RENTER) { parcel->unpackAccessEntries(msg, &(parcel->mRenterList) ); @@ -1855,10 +1871,6 @@ void LLViewerParcelMgr::processParcelDwellReply(LLMessageSystem* msg, void**) void LLViewerParcelMgr::sendParcelAccessListUpdate(U32 which) { - - LLUUID transactionUUID; - transactionUUID.generate(); - if (!mSelected) { return; @@ -1867,125 +1879,92 @@ void LLViewerParcelMgr::sendParcelAccessListUpdate(U32 which) LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); if (!region) return; - LLMessageSystem* msg = gMessageSystem; - LLParcel* parcel = mCurrentParcel; if (!parcel) return; if (which & AL_ACCESS) { - S32 count = parcel->mAccessList.size(); - S32 num_sections = (S32) ceil(count/PARCEL_MAX_ENTRIES_PER_PACKET); - S32 sequence_id = 1; - BOOL start_message = TRUE; - BOOL initial = TRUE; - - access_map_const_iterator cit = parcel->mAccessList.begin(); - access_map_const_iterator end = parcel->mAccessList.end(); - while ( (cit != end) || initial ) - { - if (start_message) - { - msg->newMessageFast(_PREHASH_ParcelAccessListUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->nextBlockFast(_PREHASH_Data); - msg->addU32Fast(_PREHASH_Flags, AL_ACCESS); - msg->addS32(_PREHASH_LocalID, parcel->getLocalID() ); - msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID); - msg->addS32Fast(_PREHASH_SequenceID, sequence_id); - msg->addS32Fast(_PREHASH_Sections, num_sections); - start_message = FALSE; - - if (initial && (cit == end)) - { - // pack an empty block if there will be no data - msg->nextBlockFast(_PREHASH_List); - msg->addUUIDFast(_PREHASH_ID, LLUUID::null ); - msg->addS32Fast(_PREHASH_Time, 0 ); - msg->addU32Fast(_PREHASH_Flags, 0 ); - } + sendParcelAccessListUpdate(AL_ACCESS, parcel->mAccessList, region, parcel->getLocalID()); + } - initial = FALSE; - sequence_id++; + if (which & AL_BAN) + { + sendParcelAccessListUpdate(AL_BAN, parcel->mBanList, region, parcel->getLocalID()); + } - } - - while ( (cit != end) && (msg->getCurrentSendTotal() < MTUBYTES)) - { + if(which & AL_ALLOW_EXPERIENCE) + { + sendParcelAccessListUpdate(AL_ALLOW_EXPERIENCE, parcel->getExperienceKeysByType(EXPERIENCE_KEY_TYPE_ALLOWED), region, parcel->getLocalID()); + } + if(which & AL_BLOCK_EXPERIENCE) + { + sendParcelAccessListUpdate(AL_BLOCK_EXPERIENCE, parcel->getExperienceKeysByType(EXPERIENCE_KEY_TYPE_BLOCKED), region, parcel->getLocalID()); + } +} - const LLAccessEntry& entry = (*cit).second; - - msg->nextBlockFast(_PREHASH_List); - msg->addUUIDFast(_PREHASH_ID, entry.mID ); - msg->addS32Fast(_PREHASH_Time, entry.mTime ); - msg->addU32Fast(_PREHASH_Flags, entry.mFlags ); - ++cit; - } +void LLViewerParcelMgr::sendParcelAccessListUpdate(U32 flags, const LLAccessEntry::map& entries, LLViewerRegion* region, S32 parcel_local_id) +{ + S32 count = entries.size(); + S32 num_sections = (S32) ceil(count/PARCEL_MAX_ENTRIES_PER_PACKET); + S32 sequence_id = 1; + BOOL start_message = TRUE; + BOOL initial = TRUE; - start_message = TRUE; - msg->sendReliable( region->getHost() ); - } - } + LLUUID transactionUUID; + transactionUUID.generate(); - if (which & AL_BAN) - { - S32 count = parcel->mBanList.size(); - S32 num_sections = (S32) ceil(count/PARCEL_MAX_ENTRIES_PER_PACKET); - S32 sequence_id = 1; - BOOL start_message = TRUE; - BOOL initial = TRUE; - - access_map_const_iterator cit = parcel->mBanList.begin(); - access_map_const_iterator end = parcel->mBanList.end(); - while ( (cit != end) || initial ) - { - if (start_message) - { - msg->newMessageFast(_PREHASH_ParcelAccessListUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->nextBlockFast(_PREHASH_Data); - msg->addU32Fast(_PREHASH_Flags, AL_BAN); - msg->addS32(_PREHASH_LocalID, parcel->getLocalID() ); - msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID); - msg->addS32Fast(_PREHASH_SequenceID, sequence_id); - msg->addS32Fast(_PREHASH_Sections, num_sections); - start_message = FALSE; - - if (initial && (cit == end)) - { - // pack an empty block if there will be no data - msg->nextBlockFast(_PREHASH_List); - msg->addUUIDFast(_PREHASH_ID, LLUUID::null ); - msg->addS32Fast(_PREHASH_Time, 0 ); - msg->addU32Fast(_PREHASH_Flags, 0 ); - } - initial = FALSE; - sequence_id++; + LLMessageSystem* msg = gMessageSystem; - } - - while ( (cit != end) && (msg->getCurrentSendTotal() < MTUBYTES)) + LLAccessEntry::map::const_iterator cit = entries.begin(); + LLAccessEntry::map::const_iterator end = entries.end(); + while ( (cit != end) || initial ) + { + if (start_message) + { + msg->newMessageFast(_PREHASH_ParcelAccessListUpdate); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->nextBlockFast(_PREHASH_Data); + msg->addU32Fast(_PREHASH_Flags, flags); + msg->addS32(_PREHASH_LocalID, parcel_local_id); + msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID); + msg->addS32Fast(_PREHASH_SequenceID, sequence_id); + msg->addS32Fast(_PREHASH_Sections, num_sections); + start_message = FALSE; + + if (initial && (cit == end)) { - const LLAccessEntry& entry = (*cit).second; - + // pack an empty block if there will be no data msg->nextBlockFast(_PREHASH_List); - msg->addUUIDFast(_PREHASH_ID, entry.mID ); - msg->addS32Fast(_PREHASH_Time, entry.mTime ); - msg->addU32Fast(_PREHASH_Flags, entry.mFlags ); - ++cit; + msg->addUUIDFast(_PREHASH_ID, LLUUID::null ); + msg->addS32Fast(_PREHASH_Time, 0 ); + msg->addU32Fast(_PREHASH_Flags, 0 ); } - start_message = TRUE; - msg->sendReliable( region->getHost() ); + initial = FALSE; + sequence_id++; + } + + while ( (cit != end) && (msg->getCurrentSendTotal() < MTUBYTES)) + { + const LLAccessEntry& entry = (*cit).second; + + msg->nextBlockFast(_PREHASH_List); + msg->addUUIDFast(_PREHASH_ID, entry.mID ); + msg->addS32Fast(_PREHASH_Time, entry.mTime ); + msg->addU32Fast(_PREHASH_Flags, entry.mFlags ); + ++cit; + } + + start_message = TRUE; + msg->sendReliable( region->getHost() ); } } + void LLViewerParcelMgr::deedLandToGroup() { std::string group_name; diff --git a/indra/newview/llviewerparcelmgr.h b/indra/newview/llviewerparcelmgr.h index 58d398e141..e2c4bad313 100755 --- a/indra/newview/llviewerparcelmgr.h +++ b/indra/newview/llviewerparcelmgr.h @@ -218,7 +218,7 @@ public: // Takes an Access List flag, like AL_ACCESS or AL_BAN void sendParcelAccessListUpdate(U32 which); - + // Takes an Access List flag, like AL_ACCESS or AL_BAN void sendParcelAccessListRequest(U32 flags); @@ -291,6 +291,8 @@ public: static BOOL isParcelModifiableByAgent(const LLParcel* parcelp, U64 group_proxy_power); private: + static void sendParcelAccessListUpdate(U32 flags, const std::map<LLUUID, class LLAccessEntry>& entries, LLViewerRegion* region, S32 parcel_local_id); + static void sendParcelExperienceUpdate( const U32 flags, uuid_vec_t experience_ids, LLViewerRegion* region, S32 parcel_local_id ); static bool releaseAlertCB(const LLSD& notification, const LLSD& response); // If the user is claiming land and the current selection diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index cd5f64b9ca..f70ed68e49 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -2703,8 +2703,21 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) } capabilityNames.append("GetDisplayNames"); + capabilityNames.append("GetExperiences"); + capabilityNames.append("AgentExperiences"); + capabilityNames.append("FindExperienceByName"); + capabilityNames.append("GetExperienceInfo"); + capabilityNames.append("GetAdminExperiences"); + capabilityNames.append("GetCreatorExperiences"); + capabilityNames.append("ExperiencePreferences"); + capabilityNames.append("GroupExperiences"); + capabilityNames.append("UpdateExperience"); + capabilityNames.append("IsExperienceAdmin"); + capabilityNames.append("IsExperienceContributor"); + capabilityNames.append("RegionExperiences"); capabilityNames.append("GetMesh"); capabilityNames.append("GetMesh2"); + capabilityNames.append("GetMetadata"); capabilityNames.append("GetObjectCost"); capabilityNames.append("GetObjectPhysicsData"); capabilityNames.append("GetTexture"); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 9f42776d78..9e66086f6d 100755 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -43,6 +43,7 @@ #include "llanimationstates.h" #include "llavatarnamecache.h" #include "llavatarpropertiesprocessor.h" +#include "llexperiencecache.h" #include "llphysicsmotion.h" #include "llviewercontrol.h" #include "llcallingcard.h" // IDEVO for LLAvatarTracker @@ -2150,7 +2151,7 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time) idleUpdateBelowWater(); // wind effect uses this idleUpdateWindEffect(); } - + idleUpdateNameTag( root_pos_last ); idleUpdateRenderCost(); } diff --git a/indra/newview/roles_constants.h b/indra/newview/roles_constants.h index 8fd7978fa1..24792dd731 100644 --- a/indra/newview/roles_constants.h +++ b/indra/newview/roles_constants.h @@ -145,6 +145,9 @@ const U64 GP_SESSION_JOIN = 0x1LL << 16; //can join session const U64 GP_SESSION_VOICE = 0x1LL << 27; //can hear/talk const U64 GP_SESSION_MODERATOR = 0x1LL << 37; //can mute people's session +const U64 GP_EXPERIENCE_ADMIN = 0x1LL << 49; // has admin rights to any experiences owned by this group +const U64 GP_EXPERIENCE_CREATOR = 0x1LL << 50; // can sign scripts for experiences owned by this group + // Group Banning const U64 GP_GROUP_BAN_ACCESS = 0x1LL << 51; // Allows access to ban / un-ban agents from a group. diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 46698b3949..393c8314b2 100755 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -222,6 +222,7 @@ with the same filename but different name <texture name="ForwardArrow_Off" file_name="icons/ForwardArrow_Off.png" preload="false" /> <texture name="ForwardArrow_Press" file_name="icons/ForwardArrow_Press.png" preload="false" /> + <texture name="Generic_Experience" file_name="Blank.png" preload="false" /> <texture name="Generic_Group" file_name="icons/Generic_Group.png" preload="false" /> <texture name="Generic_Group_Large" file_name="icons/Generic_Group_Large.png" preload="false" /> <texture name="icon_group.tga" file_name="icons/Generic_Group.png" preload="false" /> diff --git a/indra/newview/skins/default/xui/en/floater_about_land.xml b/indra/newview/skins/default/xui/en/floater_about_land.xml index a660e812cc..62ab8ed193 100755 --- a/indra/newview/skins/default/xui/en/floater_about_land.xml +++ b/indra/newview/skins/default/xui/en/floater_about_land.xml @@ -2096,5 +2096,17 @@ Only large parcels can be listed in search. width="100" /> </panel> </panel> + <panel + border="true" + follows="all" + label="EXPERIENCES" + layout="topleft" + left="0" + top="0" + help_topic="land_experiences_tab" + name="land_experiences_panel" + class="land_experiences_panel" + filename="panel_region_experiences.xml"> + </panel> </tab_container> </floater> diff --git a/indra/newview/skins/default/xui/en/floater_experience_search.xml b/indra/newview/skins/default/xui/en/floater_experience_search.xml new file mode 100644 index 0000000000..15a4b5665e --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_experience_search.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + positioning="cascading" + legacy_header_height="18" + can_resize="true" + height="350" + layout="topleft" + min_height="200" + min_width="400" + name="experiencepicker" + help_topic="experiencepicker" + title="CHOOSE EXPERIENCE" + width="350"> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_experienceprofile.xml b/indra/newview/skins/default/xui/en/floater_experienceprofile.xml new file mode 100644 index 0000000000..d146d1306f --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_experienceprofile.xml @@ -0,0 +1,687 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> + +<floater + positioning="cascading" + can_close="true" + enabled="true" + can_resize="true" + help_topic="floater_experienceprofile" + title="EXPERIENCE PROFILE" + save_rect="true" + min_width="325" + min_height="325" + width="358" + height="650"> + <floater.string + name="empty_slurl"> + (none) + </floater.string> + <floater.string + name="maturity_icon_general"> + "Parcel_PG_Light" + </floater.string> + <floater.string + name="maturity_icon_moderate"> + "Parcel_M_Light" + </floater.string> + <floater.string + name="maturity_icon_adult"> + "Parcel_R_Light" + </floater.string> + <text + follows="top|left|right" + font="SansSerifHugeBold" + height="26" + layout="topleft" + left_pad="4" + name="edit_title" + top="2" + value="Experience Profile" + use_ellipses="true" + left="6" + right="-3"/> + <tab_container + hide_tabs="true" + follows="all" + height="615" + layout="topleft" + left="5" + min_height="250" + top_pad="3" + width="348" + name="tab_container"> + <panel + background_visible="true" + follows="all" + height="540" + layout="topleft" + left="0" + min_height="250" + top="0" + width="348" + name="panel_experience_info"> + <scroll_container + color="DkGray2" + follows="all" + height="520" + layout="topleft" + left="9" + name="xp_scroll" + opaque="true" + top_pad="10" + width="330"> + <panel + bg_alpha_color="DkGray2" + follows="top|left|right" + height="550" + layout="topleft" + left="0" + name="scrolling_panel" + top="0" + width="315" + min_width="315"> + <layout_stack + follows="all" + height="550" + layout="topleft" + left="0" + animate="false" + top="0" + orientation="vertical" + width="315"> + <layout_panel + follows="all" + height="29" + layout="topleft" + left="0" + top="0" + auto_resize="false" + visible="false" + width="315" + name="top panel"> + <text + type="string" + length="1" + follows="left|top|right" + font="SansSerif" + height="19" + top="10" + layout="topleft" + left="10" + right="-123" + visible="false" + name="grid_wide"/> + <button + follows="top|right" + height="23" + label="Edit" + layout="topleft" + name="edit_btn" + width="100" + visible="false" + top_pad="-23" + right="-14"/> + </layout_panel> + <layout_panel + follows="all" + height="197" + layout="topleft" + left="0" + top="0" + auto_resize="false" + visible="false" + width="315" + name="image_panel"> + <texture_picker + enabled="false" + fallback_image="default_land_picture.j2c" + follows="left|top" + height="197" + layout="topleft" + left="10" + name="logo" + top="10" + width="290" /> + </layout_panel> + <layout_panel + follows="all" + height="19" + layout="topleft" + left="0" + top="5" + width="313" + auto_resize="false"> + <text + follows="left|top|right" + font="SansSerifLarge" + height="14" + layout="topleft" + left="10" + name="experience_title" + top="0" + use_ellipses="true" + value="" + width="288"/> + </layout_panel> + <layout_panel + follows="" + height="50" + layout="topleft" + left="0" + top="0" + auto_resize="false" + width="315" + visible="false" + name="description panel"> + <expandable_text + follows="left|top|right" + font="SansSerif" + height="50" + layout="topleft" + left="7" + name="experience_description" + top="0" + value="" + width="293"/> + </layout_panel> + <layout_panel + follows="" + height="18" + layout="topleft" + left="0" + top="0" + auto_resize="false" + width="315" + visible="true" + name="maturity panel"> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="ContentRating" + width="75"> + Rating: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="18" + layout="topleft" + left_pad="2" + valign="center" + name="ContentRatingText" + top_delta="-3" + width="188"> + </text> + </layout_panel> + <layout_panel + follows="all" + height="46" + layout="topleft" + left="0" + top="5" + width="313" + visible="false" + auto_resize="false" + name="location panel"> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="Location" + width="290"> + Location: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="18" + layout="topleft" + left="10" + valign="center" + use_ellipses="true" + name="LocationTextText" + width="288"> + </text> + </layout_panel> + <layout_panel + follows="all" + height="53" + layout="topleft" + left="0" + top="5" + width="313" + visible="false" + auto_resize="false" + name="marketplace panel"> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + width="290"> + Marketplace store: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="18" + layout="topleft" + left="10" + valign="center" + use_ellipses="true" + name="marketplace" + width="288"> + </text> + </layout_panel> + <layout_panel + follows="left|top|right" + height="18" + left="0" + top="0" + auto_resize="false" + width="315"> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="Owner" + width="75"> + Owner: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="18" + layout="topleft" + left_pad="2" + valign="center" + name="OwnerText" + use_ellipses="true" + top_delta="-2" + width="188"> + </text> + </layout_panel> + <layout_panel + follows="all" + height="18" + layout="topleft" + left="0" + top="5" + width="313" + visible="false" + auto_resize="false" + name="group_panel"> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="Group" + width="75"> + Group: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="18" + layout="topleft" + left_pad="2" + valign="center" + name="GroupText" + use_ellipses="true" + top_delta="-2" + width="188"> + </text> + </layout_panel> + <layout_panel + follows="all" + height="75" + layout="topleft" + left="0" + top="5" + width="313" + auto_resize="false" + visible="true" + name="perm panel"> + <button + follows="bottom|left" + height="23" + label="Allow" + layout="topleft" + name="allow_btn" + width="94" + top_pad="3" + left="10" + enabled="false"/> + <button + follows="bottom|left" + height="23" + label="Forget" + layout="topleft" + name="forget_btn" + width="94" + top_pad="-23" + left_pad="3" + enabled="false"/> + <button + follows="bottom|left" + height="23" + label="Block" + layout="topleft" + name="block_btn" + width="94" + top_pad="-23" + left_pad="3" + enabled="false"/> + <text + type="string" + halign="center" + length="1" + follows="left|top|right" + height="16" + layout="topleft" + left="10" + name="privileged" + visible="false" + width="288"> + This experience is enabled for all residents. + </text> + <button + follows="bottom|left" + height="23" + label="Report Abuse" + layout="topleft" + name="report_btn" + width="94" + top_pad="3" + left="107" + enabled="true"/> + </layout_panel> + </layout_stack> + </panel> + </scroll_container> + </panel> + <panel + background_visible="true" + follows="all" + layout="topleft" + height="540" + left="0" + top="0" + width="348" + name="edit_panel_experience_info"> + <scroll_container + color="DkGray2" + follows="all" + height="520" + layout="topleft" + left="9" + name="edit_xp_scroll" + opaque="true" + top_pad="10" + width="330"> + <panel + bg_alpha_color="DkGray2" + follows="top|left|right" + height="590" + layout="topleft" + left="0" + name="edit_scrolling_panel" + top="0" + width="310"> + <texture_picker + enabled="true" + fallback_image="default_land_picture.j2c" + follows="left|top" + height="197" + layout="topleft" + left="10" + name="edit_logo" + top="10" + width="290" /> + <text + follows="left|top|right" + height="14" + layout="topleft" + left="10" + name="edit_experience_title_label" + use_ellipses="true" + value="Name:" + right="-10"/> + <line_editor + follows="left|top|right" + height="19" + layout="topleft" + left="10" + name="edit_experience_title" + max_length_bytes="63" + text_color="black" + right="-10"/> + <text + follows="left|top|right" + height="14" + layout="topleft" + left="10" + top_pad="10" + name="edit_experience_desc_label" + use_ellipses="true" + value="Description:" + right="-10"/> + <text_editor + follows="left|top|right" + height="57" + layout="topleft" + left="11" + name="edit_experience_description" + max_length="2048" + text_color="black" + right="-11" + word_wrap="true"/> + <button + top_pad="10" + left="10" + width="125" + height="23" + name="Group_btn" + label="Group" + /> + <text + type="string" + length="1" + follows="left|top|right" + height="14" + layout="topleft" + left_pad="10" + top_pad="-18" + use_ellipses="true" + name="edit_GroupText" + right="-10" /> + <text + top_pad="10" + type="string" + length="1" + follows="left|top|right" + height="16" + layout="topleft" + left="10" + name="edit_ContentRating" + right="-10"> + Rating: + </text> + <icons_combo_box + follows="right|top" + height="20" + label="Moderate" + layout="topleft" + right="-10" + top_pad="-19" + tool_tip="Increasing the maturity rating on an experience will reset permission for all residents which have allowed the experience." + name="edit_ContentRatingText" + width="105"> + <icons_combo_box.drop_down_button + image_overlay="Parcel_M_Light" + image_overlay_alignment="left" + imgoverlay_label_space="3" + pad_left="3"/> + <icons_combo_box.item + label="Adult" + name="Adult" + value="42"> + <item.columns + halign="center" + type="icon" + value="Parcel_R_Light" + width="20"/> + </icons_combo_box.item> + <icons_combo_box.item + label="Moderate" + name="Mature" + value="21"> + <item.columns + halign="center" + type="icon" + value="Parcel_M_Light" + width="20"/> + </icons_combo_box.item> + <icons_combo_box.item + label="General" + name="PG" + value="13"> + <item.columns + halign="center" + type="icon" + value="Parcel_PG_Light" + width="20"/> + </icons_combo_box.item> + </icons_combo_box> + <text + type="string" + length="1" + follows="left|top" + height="14" + layout="topleft" + left="10" + top_pad="10" + name="edit_Location" + right="90"> + Location: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="14" + layout="topleft" + left_pad="5" + top_pad="-14" + use_ellipses="true" + name="edit_LocationTextText" + right="-10" /> + <button + left="10" + width="125" + height="23" + name="location_btn" + label="Set to Current" + /> + <button + top_pad="-23" + follows="top|right" + right="-10" + width="125" + name="clear_btn" + label="Clear Location"/> + <text + type="string" + length="1" + follows="left|top|right" + height="14" + top_pad="10" + layout="topleft" + left="10" + right="-10"> + Marketplace store: + </text> + <line_editor + type="string" + length="1" + follows="left|top|right" + height="19" + layout="topleft" + left="10" + max_length_bytes="255" + valign="center" + name="edit_marketplace" + right="-10"/> + <check_box width="140" + height="21" + left="10" + layout="topleft" + follows="top|left" + tool_tip="" + label="Enable Experience" + name="edit_enable_btn"/> + <check_box width="125" + height="21" + top_pad="-21" + right="-10" + visible="false" + layout="topleft" + follows="top|left|right" + label="Hide In Search" + name="edit_private_btn"/> + <text + follows="left|top|right" + height="25" + layout="topleft" + left="10" + top_pad="10" + name="changes" + use_ellipses="true" + word_wrap="true" + value="Experience changes may take several minutes to be seen on all regions." + right="-10"/> + <button + follows="top|left" + height="23" + label="Back" + layout="topleft" + name="cancel_btn" + width="125" + top_pad="15" + left="10" + visible="true"/> + <button + follows="top|right" + height="23" + label="Save" + layout="topleft" + name="save_btn" + top_pad="-23" + width="125" + right="-10" + visible="true"/> + </panel> + </scroll_container> + </panel> + </tab_container> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_experiences.xml b/indra/newview/skins/default/xui/en/floater_experiences.xml new file mode 100644 index 0000000000..70e7507907 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_experiences.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> + +<floater + can_close="true" + can_resize="true" + height="400" + width="500" + min_height="300" + min_width="500" + layout="topleft" + help_topic="floater_experiences" + name="floater_experiences" + save_rect="true" + single_instance="true" + reuse_instance="false" + bg_opaque_color="0 0.5 0 0.3" + title="EXPERIENCES"> + <tab_container + top="3" + left="3" + layout="topleft" + right="-3" + follows="all" + height="394" + name="xp_tabs"> + </tab_container> + +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml index 5cd7cd196d..40b47dc1d7 100755 --- a/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml @@ -1,71 +1,114 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <floater - legacy_header_height="18" - bevel_style="none" - border_style="line" - can_resize="true" - height="580" - layout="topleft" - min_height="271" - min_width="290" - name="script ed float" - help_topic="script_ed_float" - save_rect="true" - title="SCRIPT: NEW SCRIPT" - width="508"> - <floater.string - name="not_allowed"> - You can not view or edit this script, since it has been set as "no copy". You need full permissions to view or edit a script inside an object. - </floater.string> - <floater.string - name="script_running"> - Running - </floater.string> - <floater.string - name="Title"> - SCRIPT: [NAME] - </floater.string> - <panel - bevel_style="none" + legacy_header_height="18" + bevel_style="none" + border_style="line" + can_resize="true" + height="582" + layout="topleft" + min_height="271" + min_width="328" + name="script ed float" + help_topic="script_ed_float" + save_rect="true" + title="SCRIPT: NEW SCRIPT" + width="508"> + <floater.string + name="not_allowed"> + You can not view or edit this script, since it has been set as "no copy". You need full permissions to view or edit a script inside an object. + </floater.string> + <floater.string + name="script_running"> + Running + </floater.string> + <floater.string + name="Title"> + SCRIPT: [NAME] + </floater.string> + <floater.string + name="experience_enabled"> + Uncheck to remove the current experience + </floater.string> + <floater.string + name="no_experiences"> + You are not authorized for any experiences + </floater.string> + <floater.string + name="add_experiences"> + Select to add an experience + </floater.string> + <floater.string + name="loading"> + Loading... + </floater.string> + <panel + bevel_style="none" - border_style="line" - follows="left|top|right|bottom" - height="522" - layout="topleft" - left="10" - name="script ed panel" - top="20" - width="497" /> - <button - follows="left|bottom" - height="23" - label="Reset" - label_selected="Reset" - layout="topleft" - name="Reset" - left="10" - width="85" /> - <check_box + border_style="line" + follows="left|top|right|bottom" + height="499" + layout="topleft" + left="10" + name="script ed panel" + top="16" + width="501" /> + <button + follows="left|bottom" + height="23" + label="Reset" + label_selected="Reset" + layout="topleft" + name="Reset" + left="10" + width="85" /> + <check_box left_delta="90" top_delta="3" - enabled="false" - follows="left|bottom" - font="SansSerif" - height="18" - initial_value="true" - label="Running" - layout="topleft" - name="running" - width="205" /> - <check_box + enabled="false" + follows="left|bottom" + font="SansSerif" + height="18" + initial_value="true" + label="Running" + layout="topleft" + name="running" + width="205" /> + <check_box left_delta="140" - enabled="true" - follows="left|bottom" - font="SansSerif" - height="18" - initial_value="true" - label="Mono" - layout="topleft" - name="mono" - width="100" /> + enabled="true" + follows="left|bottom" + font="SansSerif" + height="18" + initial_value="true" + label="Mono" + layout="topleft" + name="mono" + width="100" /> + <check_box width="130" + height="21" + enabled="false" + left="9" + top_pad="10" + layout="topleft" + follows="bottom|left" + label="Use Experience:" + name="enable_xp"/> + <combo_box + label="" + top_pad="-21" + left="149" + right="467" + layout="topleft" + follows="left|bottom|right" + visible="false" + name="Experiences..."/> + <button label=">" + name="view_profile" + height="23" + width="23" + right="496" + layout="topleft" + top_pad="-23" + follows="right" + visible="false"/> </floater> diff --git a/indra/newview/skins/default/xui/en/menu_url_experience.xml b/indra/newview/skins/default/xui/en/menu_url_experience.xml new file mode 100644 index 0000000000..f4d50e1603 --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_url_experience.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<context_menu + layout="topleft" + name="Url Popup"> + <menu_item_call + label="Copy SLurl to clipboard" + layout="topleft" + name="url_copy"> + <menu_item_call.on_click + function="Url.CopyUrl" /> + </menu_item_call> +</context_menu> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index c8fcda9858..e226a88b30 100755 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -66,6 +66,13 @@ function="Floater.ToggleOrBringToFront" parameter="picks" /> </menu_item_call> + <menu_item_call + label="Experiences..." + name="Experiences"> + <menu_item_call.on_click + function="Floater.ToggleOrBringToFront" + parameter="experiences"/> + </menu_item_call> <menu_item_separator/> <menu_item_call label="Camera Controls..." diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 772d81c448..c910f2eae8 100755 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -4410,6 +4410,90 @@ Remove estate manager for this estate only or for [ALL_ESTATES]? <notification icon="alert.tga" + label="Select estate" + name="EstateAllowedExperienceAdd" + type="alert"> + Add to allowed list for this estate only or for [ALL_ESTATES]? + <tag>confirm</tag> + <usetemplate + canceltext="Cancel" + name="yesnocancelbuttons" + notext="All Estates" + yestext="This Estate"/> + </notification> + + <notification + icon="alert.tga" + label="Select estate" + name="EstateAllowedExperienceRemove" + type="alert"> + Remove from allowed list for this estate only or for [ALL_ESTATES]? + <tag>confirm</tag> + <usetemplate + canceltext="Cancel" + name="yesnocancelbuttons" + notext="All Estates" + yestext="This Estate"/> + </notification> + + <notification + icon="alert.tga" + label="Select estate" + name="EstateBlockedExperienceAdd" + type="alert"> + Add to blocked list for this estate only or for [ALL_ESTATES]? + <tag>confirm</tag> + <usetemplate + canceltext="Cancel" + name="yesnocancelbuttons" + notext="All Estates" + yestext="This Estate"/> + </notification> + + <notification + icon="alert.tga" + label="Select estate" + name="EstateBlockedExperienceRemove" + type="alert"> + Remove from blocked list for this estate only or for [ALL_ESTATES]? + <tag>confirm</tag> + <usetemplate + canceltext="Cancel" + name="yesnocancelbuttons" + notext="All Estates" + yestext="This Estate"/> + </notification> + + <notification + icon="alert.tga" + label="Select estate" + name="EstateTrustedExperienceAdd" + type="alert"> + Add to trusted list for this estate only or for [ALL_ESTATES]? + <tag>confirm</tag> + <usetemplate + canceltext="Cancel" + name="yesnocancelbuttons" + notext="All Estates" + yestext="This Estate"/> + </notification> + + <notification + icon="alert.tga" + label="Select estate" + name="EstateTrustedExperienceRemove" + type="alert"> + Remove from trusted list for this estate only or for [ALL_ESTATES]? + <tag>confirm</tag> + <usetemplate + canceltext="Cancel" + name="yesnocancelbuttons" + notext="All Estates" + yestext="This Estate"/> + </notification> + + <notification + icon="alert.tga" label="Confirm Kick" name="EstateKickUser" type="alert"> @@ -7072,6 +7156,152 @@ Is this OK? </notification> <notification + icon="alertmodal.tga" + name="ExperienceAcquireFailed" + type="alertmodal"> +Unable to acquire a new experience: + [ERROR_MESSAGE] + <tag>fail</tag> + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification + icon="notify.tga" + name="NotInGroupExperienceProfileMessage" + persist="false" + type="notify"> + A change to the experience group was ignored because the owner is not a member of the selected group. + </notification> + + <notification + icon="notify.tga" + name="UneditableExperienceProfileMessage" + persist="false" + type="notify"> + The uneditable field '[field]' was ignored when updating the experience profile. + </notification> + + <notification + icon="notify.tga" + name="RestrictedToOwnerExperienceProfileMessage" + persist="false" + type="notify"> + Ignored changes to the field '[field]' which can only be set by the experience owner. + </notification> + + <notification + icon="notify.tga" + name="MaturityRatingExceedsOwnerExperienceProfileMessage" + persist="false" + type="notify"> + You may not set the maturity rating of an experience higher than that of the owner. + </notification> + + <notification + icon="notify.tga" + name="RestrictedTermExperienceProfileMessage" + persist="false" + type="notify"> + The following terms prevented the update of the experience profile name and/or description: [extra_info] + </notification> + + <notification + icon="notify.tga" + name="TeleportedHomeExperienceRemoved" + persist="false" + type="notify"> + You have been teleported from the region [region_name] for removing the experience secondlife:///app/experience/[public_id]/profile and are no longer permitted in the region. + <form name="form"> + <ignore name="ignore" + text="Kicked from region for removing an experience"/> + </form> + </notification> + + <notification + icon="notify.tga" + name="TrustedExperienceEntry" + persist="false" + type="notify"> + You have been allowed into the region [region_name] by participating in the trusted experience secondlife:///app/experience/[public_id]/profile removing this experience may kick you from the region. + <form name="form"> + <ignore name="ignore" + text="Allowed into a region by an experience"/> + </form> + </notification> + + <notification + icon="notify.tga" + name="TrustedExperiencesAvailable" + persist="false" + type="notify"> +You do not have access to this destination. You may be allowed into the region by Accepting an experience below: + +[EXPERIENCE_LIST] + </notification> + + + <notification + icon="notify.tga" + name="ExperienceEvent" + persist="false" + type="notifytip"> + An object was allowed to [EventType] by the secondlife:///app/experience/[public_id]/profile experience. + Owner: secondlife:///app/agent/[OwnerID]/inspect + Object Name: [ObjectName] + Parcel Name: [ParcelName] + </notification> + + <notification + icon="notify.tga" + name="ExperienceEventAttachment" + persist="false" + type="notifytip"> + An attachment was allowed to [EventType] by the secondlife:///app/experience/[public_id]/profile experience. + Owner: secondlife:///app/agent/[OwnerID]/inspect + </notification> + + <notification + icon="notify.tga" + name="ScriptQuestionExperience" + persist="false" + type="notify"> +'<nolink>[OBJECTNAME]</nolink>', an object owned by '[NAME]', requests your participation in the [GRID_WIDE]experience: + +[EXPERIENCE] + +Once permission is granted you will not see this message again for this experience unless it is revoked from the experience profile. + +Scripts associated with this experience will be able to do the following on regions where the experience is active: + +[QUESTIONS]Is this OK? + + <unique combine="combine_with_new"> + <context>experience</context> + </unique> + <tag>confirm</tag> + <form name="form"> + <button + index="3" + name="BlockExperience" + text="Block Experience"/> + <button + index="2" + name="Mute" + text="Block Object"/> + <button + index="0" + name="Yes" + text="Yes"/> + <button + index="1" + name="No" + text="No"/> + </form> + </notification> + + <notification icon="notify.tga" name="ScriptQuestionCaution" priority="critical" diff --git a/indra/newview/skins/default/xui/en/panel_experience_info.xml b/indra/newview/skins/default/xui/en/panel_experience_info.xml new file mode 100644 index 0000000000..70d8a4fc64 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_experience_info.xml @@ -0,0 +1,377 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + background_visible="true" + follows="all" + height="570" + layout="topleft" + left="5" + min_height="350" + top="5" + width="348" + name="panel_experience_info"> + <text + follows="top|left|right" + font="SansSerifHugeBold" + height="26" + layout="topleft" + left_pad="4" + name="title" + text_color="White" + top="2" + value="Experience Profile" + use_ellipses="true" + left="3" + right="-3"/> + <scroll_container + color="DkGray2" + follows="all" + height="532" + layout="topleft" + left="9" + name="xp_scroll" + opaque="true" + top_pad="10" + width="330"> + <panel + bg_alpha_color="DkGray2" + follows="top|left" + height="480" + layout="topleft" + left="0" + min_height="480" + name="scrolling_panel" + top="0" + width="315" + min_width="315"> + <layout_stack + follows="all" + height="480" + layout="topleft" + left="0" + top="0" + orientation="vertical" + width="315"> + <layout_panel + follows="all" + height="197" + layout="topleft" + left="0" + top="0" + auto_resize="false" + visible="true" + width="315" + name="image_panel"> + <texture_picker + enabled="false" + fallback_image="default_land_picture.j2c" + follows="left|top" + height="197" + layout="topleft" + left="10" + name="logo" + top="10" + width="290" /> + </layout_panel> + <layout_panel + follows="all" + height="19" + layout="topleft" + left="0" + top="5" + width="313" + auto_resize="false" + > + <text + follows="left|top|right" + font="SansSerifLarge" + height="14" + layout="topleft" + left="10" + name="experience_title" + text_color="white" + top="0" + use_ellipses="true" + value="Kyle's Superhero RPG" + width="288"/> + </layout_panel> + <layout_panel + follows="" + height="50" + layout="topleft" + left="0" + top="0" + auto_resize="false" + width="315" + name="description panel"> + <expandable_text + follows="left|top|right" + font="SansSerif" + height="50" + layout="topleft" + left="7" + name="experience_description" + top="0" + value="" + width="293"/> + </layout_panel> + <layout_panel + follows="all" + height="69" + layout="topleft" + left="0" + top="5" + width="313" + visible="true" + auto_resize="false" + name="location panel" + > + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="Location" + width="290"> + Location: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="18" + layout="topleft" + left="10" + valign="center" + name="LocationTextText" + width="288"> + someplace + </text> + <button + follows="bottom|left" + height="23" + label="Teleport" + layout="topleft" + name="teleport_btn" + width="151" + left="10"/> + <button + follows="bottom|left" + height="23" + label="Map" + layout="topleft" + name="map_btn" + top_pad="-23" + width="101" + left_pad="5"/> + </layout_panel> + <layout_panel + follows="all" + height="53" + layout="topleft" + left="0" + top="5" + width="313" + visible="true" + auto_resize="false" + name="marketplace panel" + + > + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="Location" + width="290"> + Marketplace store: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="18" + layout="topleft" + left="10" + valign="center" + name="LocationTextText" + width="288"> + someplace + </text> + </layout_panel> + <layout_panel + follows="left|top|right" + height="69" + left="0" + top="0" + auto_resize="false" + width="315" + > + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="ContentRating" + width="100"> + Rating: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="18" + layout="topleft" + left_pad="2" + valign="center" + name="ContentRatingText" + top_delta="-2" + width="188"> + Adult + </text> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="Owner" + width="100"> + Owner: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="18" + layout="topleft" + left_pad="2" + valign="center" + name="OwnerText" + top_delta="-2" + width="188"> + Kyle + </text> + <button + follows="bottom|left" + height="23" + label="Edit" + layout="topleft" + name="edit_btn" + top_pad="0" + width="151" + left="10"/> + <!-- + + + <expandable_text + allow_scroll="false" + bg_visible="false" + follows="left|top|right" + h_pad="0" + height="35" + width="280" + layout="topleft" + font="SansSerifBig" + font.style="BOLD" + left="10" + top_pad="10" + name="pick_name" + read_only="false" + text_color="white" + v_pad="0" + use_ellipses="true" + value="It's an experience" /> + + <text + follows="left|top" + height="16" + layout="topleft" + left="10" + top_pad="10" + value="Maturity level:" + width="130" /> + <icon + follows="top|left" + height="16" + image_name="unknown" + layout="topleft" + left_pad="10" + name="maturity_icon" + top_pad="-18" + width="18" /> + <text + follows="top|left" + height="16" + layout="topleft" + left_pad="5" + name="maturity_value" + top_pad="-14" + value="unknown" + width="118" /> + <panel + follows="left|top|right" + name="location_panel" + left="10" + width="280" + visible="false" + top_pad="10" + height="42"> + <text + follows="left|top|right" + height="16" + layout="topleft" + left="00" + top="0" + value="Location:" + width="280" /> + + <text + follows="left|top|right" + height="16" + layout="topleft" + left="0" + top_pad="10" + value="Location:" + width="280" /> + </panel> + + <panel + follows="left|top|right" + name="location_panel" + left="10" + width="280" + top_pad="10" + height="42"> + <text + follows="left|top|right" + height="16" + layout="topleft" + left="00" + top="0" + value="Location:" + width="280" /> + + <text + follows="left|top|right" + height="16" + layout="topleft" + left="0" + top_pad="10" + value="Location:" + width="280" /> + </panel> + +--> + </layout_panel> + </layout_stack> + </panel> + </scroll_container> + +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_experience_list_editor.xml b/indra/newview/skins/default/xui/en/panel_experience_list_editor.xml new file mode 100644 index 0000000000..128c721992 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_experience_list_editor.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + layout="topleft" + left="0" + top="0" + width="300" + height="100" + min_height="100" + follows="all" + border="true" + name="experince_list_editor"> + <panel.string + name="loading"> + loading... + </panel.string> + <panel.string + name="panel_allowed"> + Allowed Experiences: + </panel.string> + <panel.string + name="panel_blocked"> + Blocked Experiences: + </panel.string> + <panel.string + name="panel_trusted"> + Trusted Experiences: + </panel.string> + <panel.string + name="no_results"> + (empty) + </panel.string> + <text + layout="topleft" + name="text_name" + left="3" + right="120" + height="12" + follows="left|top"> + Experience List + </text> + <text + layout="topleft" + name="text_count" + left="125" + top_pad="-12" + right="-1" + height="12" + follows="top|left"> + </text> + <scroll_list + draw_heading="false" + left="3" + width="225" + height="77" + follows="all" + name="experience_list"> + <columns + width="225" + user_resize="false" + name="experience_name" + label="Name"/> + </scroll_list> + <button + layout="topleft" + follows="top|right" + top_pad="-77" + left_pad="3" + width="65" + name="btn_add" + label="Add..."/> + <button + layout="topleft" + follows="top|right" + width="64" + name="btn_remove" + label="Remove"/> + <button + layout="topleft" + follows="top|right" + width="64" + name="btn_profile" + label="Profile..."/> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_experience_list_item.xml b/indra/newview/skins/default/xui/en/panel_experience_list_item.xml new file mode 100644 index 0000000000..a9777c51e7 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_experience_list_item.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> + +<panel + layout="topleft" + top="100" + left="5" + width="100" + height="19" + label="Experiences" + follows="top|right|left"> + <text + left="3" + top="3" + height="16" + width="177" + follows="all" + name="experience_name" + > + Dummy Name + </text> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_experience_log.xml b/indra/newview/skins/default/xui/en/panel_experience_log.xml new file mode 100644 index 0000000000..6869a135d8 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_experience_log.xml @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> + +<panel + layout="topleft" + top="3" + left="3" + width="500" + height="300" + label="EVENTS" + bg_opaque_color="0 0.5 0 0.3" + follows="all"> + <string + name="no_events" + value="No events."/> + <string + name="loading" + value="loading..."/> + <layout_stack + layout="topleft" + top="0" + left="4" + right="-1" + bottom="-4" + orientation="vertical" + follows="all"> + <layout_panel + layout="topleft" + top="0" + left="1" + right="-1" + height="250" + follows="all"> + <scroll_list + draw_heading="true" + left="1" + right="-86" + height="250" + follows="all" + name="experience_log_list"> + <columns + width="100" + user_resize="true" + name="time" + label="Time"/> + <columns + width="100" + user_resize="true" + name="event" + label="Event"/> + <columns + width="100" + user_resize="true" + name="experience_name" + label="Experience"/> + <columns + width="100" + user_resize="true" + name="object_name" + label="Object"/> + </scroll_list> + + <button + layout="topleft" + follows="top|right" + name="btn_notify" + label="Notify" + top_pad="-225" + left_pad="5" + right="-1" + enabled="false"/> + + <button + layout="topleft" + follows="top|right" + name="btn_profile_xp" + label="Profile" + top_pad="5" + right="-1" + enabled="false"/> + + <button + layout="topleft" + follows="top|right" + name="btn_report_xp" + label="Report" + top_pad="5" + right="-1" + enabled="false"/> + </layout_panel> + <layout_panel + layout="topleft" + top="0" + left="1" + right="-1" + height="30" + min_height="30" + follows="all" + name="button_panel" + visible="true"> + <check_box + top="0" + follows="top|left" + height="26" + label="Notify All Events Days" + width="140" + name="notify_all" /> + + <spinner + top="5" + control_name="LogDays" + decimal_digits="0" + follows="left|top" + height="23" + increment="1" + initial_value="7" + label_width="30" + layout="topleft" + left_pad="5" + max_val="14" + min_val="0" + name="logsizespinner" + width="40" /> + <button + top="5" + left="280" + layout="topleft" + follows="top|left" + name="btn_clear" + label="Clear" + width="80" + enabled="false"/> + <button + top="5" + layout="topleft" + left_pad="5" + width="20" + follows="top|left" + name="btn_prev" + label="<" + enabled="false"/> + <button + top="5" + layout="topleft" + follows="top|left" + name="btn_next" + label=">" + left_pad="5" + width="20" + enabled="false"/> + </layout_panel> + </layout_stack> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_experience_search.xml b/indra/newview/skins/default/xui/en/panel_experience_search.xml new file mode 100644 index 0000000000..99ad3e7fe3 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_experience_search.xml @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + layout="topleft" + top="18" + left="0" + width="350" + height="330" + label="SEARCH" + follows="all"> + + <string + name="not_found"> + '[TEXT]' not found + </string> + <string + name="no_results"> + No results + </string> + <string + name="searching"> + Searching... + </string> + <string + name="loading"> + Loading... + </string> + <string + name="maturity_icon_general"> + "Parcel_PG_Light" + </string> + <string + name="maturity_icon_moderate"> + "Parcel_M_Light" + </string> + <string + name="maturity_icon_adult"> + "Parcel_R_Light" + </string> + <panel + follows="all" + left="5" + right="-3" + bottom="-2" + top="0" + name="search_panel"> + <text + left="0" + follows="top|left|right" + right="-1"> + Enter part of the name: + </text> + <line_editor + left="0" + follows="left|top|right" + name="edit" + height="18" + right="-60"/> + <button label="Go" + follows="top|right" + top_pad="-18" + left_pad="2" + right="-1" + height="18" + name="find"/> + <text + top_pad="6" + left="0" + follows="top|left" + width="180"> + Max Content Rating: + </text> + <icons_combo_box + follows="left|top" + height="20" + label="Moderate" + layout="topleft" + name="maturity" + top_pad="-13" + left_pad="0" + right="-1"> + <icons_combo_box.drop_down_button + image_overlay="Parcel_M_Light" + image_overlay_alignment="left" + imgoverlay_label_space="3" + pad_left="3"/> + <icons_combo_box.item + label="Adult" + name="Adult" + value="42"> + <item.columns + halign="center" + type="icon" + value="Parcel_R_Light" + width="20"/> + </icons_combo_box.item> + <icons_combo_box.item + label="Moderate" + name="Mature" + value="21"> + <item.columns + halign="center" + type="icon" + value="Parcel_M_Light" + width="20"/> + </icons_combo_box.item> + <icons_combo_box.item + label="General" + name="PG" + value="13"> + <item.columns + halign="center" + type="icon" + value="Parcel_PG_Light" + width="20"/> + </icons_combo_box.item> + </icons_combo_box> + <scroll_list + draw_heading="true" + left="0" + right="-1" + height="239" + top_pad="4" + follows="all" + column_padding="5" + name="search_results"> + <columns + halign="center" + width="24" + name="maturity" + label=""/> + <columns + user_resize="true" + name="experience_name" + label="Name"/> + <columns + user_resize="true" + name="owner" + label="Owner"/> + </scroll_list> + + <button + follows="left|bottom" + height="23" + label="OK" + label_selected="OK" + name="ok_btn" + top_pad="3" + left="0" + width="95" /> + <button + follows="left|bottom" + height="23" + label="Cancel" + name="cancel_btn" + width="95" + left_pad="3" /> + <button + follows="left|bottom" + height="23" + label="View Profile" + name="profile_btn" + width="95" + left_pad="3" /> + <button + follows="left|bottom" + height="23" + label="<" + name="left_btn" + width="23" + left_pad="3" + enabled="false"/> + <button + follows="left|bottom" + height="23" + label=">" + name="right_btn" + width="23" + left_pad="3" + enabled="false" /> + + </panel> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_experiences.xml b/indra/newview/skins/default/xui/en/panel_experiences.xml new file mode 100644 index 0000000000..7691e684c0 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_experiences.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> + +<panel + layout="topleft" + top="3" + left="3" + width="200" + height="300" + label="Experiences" + bg_opaque_color="0 0.5 0 0.3" + follows="all"> + <string + name="loading_experiences" + value="Loading experiences..."/> + <string + name="no_experiences" + value="No experiences."/> + <string + name="acquire" + value="Acquire an Experience" /> + <layout_stack + layout="topleft" + top="0" + left="4" + right="-1" + bottom="-4" + orientation="vertical" + follows="all"> + <layout_panel + layout="topleft" + top="0" + left="1" + right="-1" + height="250" + follows="all"> + <flat_list_view + name="experiences_list" + layout="topleft" + top="0" + left="1" + right="-1" + bottom="-1" + follows="all"> + </flat_list_view> + </layout_panel> + <layout_panel + layout="topleft" + top="0" + left="1" + right="-1" + height="24" + min_height="24" + follows="all" + name="button_panel" + visible="false"> + <button + layout="topleft" + left="2" + top="0" + follows="top|left" + name="btn_action" + enabled="true"/> + </layout_panel> + </layout_stack> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_group_info_sidetray.xml b/indra/newview/skins/default/xui/en/panel_group_info_sidetray.xml index b3326d8da6..95312edfb9 100755 --- a/indra/newview/skins/default/xui/en/panel_group_info_sidetray.xml +++ b/indra/newview/skins/default/xui/en/panel_group_info_sidetray.xml @@ -176,6 +176,22 @@ background_visible="true" name="group_land_tab_panel" top="0" /> </accordion_tab> + <accordion_tab + expanded="false" + layout="topleft" + name="group_experiences_tab" + title="Experiences" + fit_panel="false"> + <panel + border="false" + class="panel_group_experiences" + filename="panel_experiences.xml" + follows="left|top|right" + layout="topleft" + left="0" + name="group_experiences_tab_panel" + top="0" /> + </accordion_tab> </accordion> </layout_panel> </layout_stack> diff --git a/indra/newview/skins/default/xui/en/panel_region_experiences.xml b/indra/newview/skins/default/xui/en/panel_region_experiences.xml new file mode 100644 index 0000000000..ea57bdd164 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_region_experiences.xml @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + border="true" + follows="all" + height="320" + help_topic="panel_region_experience_tab" + label="Experiences" + name="Experiences" + layout="topleft" + left="0" + top="320" + width="480"> + <panel.string + name="trusted_estate_text"> +Any Experience may be Trusted. + +Trusted Experiences have permission to run on this estate. + +Additionally, if the estate does not allow public access, Residents participating in any Trusted Experience may enter the estate and can remain as long as they are in the Experience. + </panel.string> + <panel.string + name="allowed_estate_text"> +Only Experiences that are not Grid-Wide may be Allowed. + +Allowed Experiences have permission to run on this estate. + </panel.string> + <panel.string + name="blocked_estate_text"> +Only Grid-Wide Experiences may be Blocked. + +Blocked Experiences may not run on this estate. + </panel.string> + <panel.string + name="estate_caption"> + Changes to settings on this tab will affect all regions in the estate. + </panel.string> + <panel.string + name="allowed_parcel_text">Only Experiences that are not Grid-Wide may be Allowed. + +Allowed Experiences have permission to run on this parcel if they are not Blocked by the estate. + </panel.string> + <panel.string + name="blocked_parcel_text">Only Grid-Wide Experiences may be Blocked. + +Blocked Experiences may not run on this parcel. + </panel.string> + <panel.string + name="parcel_caption"> + </panel.string> + <text + type="string" + length="1" + follows="left|top" + height="32" + layout="topleft" + left="10" + name="experiences_help_text" + top="14" + word_wrap="true"/> + <layout_stack + left="5" + layout="topleft" + follows="all" + right="-5" + height="260" + orientation="vertical"> + <layout_panel + follows="all" + height="50" + min_height="50" + width="530" + visible="false" + name="trusted_layout_panel"> + <panel follows="all" + width="330" + name="panel_trusted" + class="panel_experience_list_editor" + filename="panel_experience_list_editor.xml" /> + <text + layout="topleft" + name="trusted_text_help" + length="1" + top="5" + left_pad="5" + width="200" + height="200" + follows="top|right" + word_wrap="true"> + </text> + </layout_panel> + <layout_panel + height="50" + min_height="50" + follows="all" + width="530"> + <panel + width="330" + name="panel_allowed" + class="panel_experience_list_editor" + filename="panel_experience_list_editor.xml"/> + <text + layout="topleft" + name="allowed_text_help" + length="1" + top="5" + left_pad="5" + width="200" + height="200" + follows="top|right" + word_wrap="true"> + </text> + </layout_panel> + <layout_panel + height="50" + min_height="50" + follows="all" + width="530"> + <panel + width="330" + name="panel_blocked" + class="panel_experience_list_editor" + filename="panel_experience_list_editor.xml"/> + <text + layout="topleft" + name="blocked_text_help" + length="1" + top="5" + left_pad="5" + width="200" + height="200" + follows="top|right" + word_wrap="true"> + </text> + </layout_panel> + </layout_stack> +</panel> diff --git a/indra/newview/skins/default/xui/en/panel_script_ed.xml b/indra/newview/skins/default/xui/en/panel_script_ed.xml index eb1b954e61..c56a5e17cd 100755 --- a/indra/newview/skins/default/xui/en/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/en/panel_script_ed.xml @@ -1,75 +1,75 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <panel - bevel_style="none" - border_style="line" - follows="left|top|right|bottom" + bevel_style="none" + border_style="line" + follows="left|top|right|bottom" height="522" - layout="topleft" - left="0" - name="script panel" - width="497"> - <panel.string - name="loading"> - Loading... - </panel.string> - <panel.string - name="can_not_view"> - You can not view or edit this script, since it has been set as "no copy". You need full permissions to view or edit a script inside an object. - </panel.string> - <panel.string - name="public_objects_can_not_run"> - Public Objects cannot run scripts - </panel.string> - <panel.string - name="script_running"> - Running - </panel.string> - <panel.string - name="Title"> - Script: [NAME] - </panel.string> - <panel.string - name="external_editor_not_set"> - Select an editor by setting the environment variable LL_SCRIPT_EDITOR or the ExternalEditor setting. - </panel.string> - <menu_bar - bg_visible="false" - follows="left|top" - height="18" - layout="topleft" - left="0" - mouse_opaque="false" - name="script_menu" - width="476"> - <menu - top="0" - height="62" - label="File" - layout="topleft" - left="0" - mouse_opaque="false" - name="File" - width="138"> - <menu_item_call - label="Save" - layout="topleft" - name="Save" /> - <menu_item_separator - layout="topleft" /> - <menu_item_call - label="Revert All Changes" - layout="topleft" - name="Revert All Changes" /> - <menu_item_separator - layout="topleft" /> - <menu_item_call - label="Load from file..." - layout="topleft" - name="LoadFromFile" /> - <menu_item_call - label="Save to file..." - layout="topleft" - name="SaveToFile" /> + layout="topleft" + left="0" + name="script panel" + width="497"> + <panel.string + name="loading"> + Loading... + </panel.string> + <panel.string + name="can_not_view"> + You can not view or edit this script, since it has been set as "no copy". You need full permissions to view or edit a script inside an object. + </panel.string> + <panel.string + name="public_objects_can_not_run"> + Public Objects cannot run scripts + </panel.string> + <panel.string + name="script_running"> + Running + </panel.string> + <panel.string + name="Title"> + Script: [NAME] + </panel.string> + <panel.string + name="external_editor_not_set"> + Select an editor by setting the environment variable LL_SCRIPT_EDITOR or the ExternalEditor setting. + </panel.string> + <menu_bar + bg_visible="false" + follows="left|top" + height="18" + layout="topleft" + left="0" + mouse_opaque="false" + name="script_menu" + width="476"> + <menu + top="0" + height="62" + label="File" + layout="topleft" + left="0" + mouse_opaque="false" + name="File" + width="138"> + <menu_item_call + label="Save" + layout="topleft" + name="Save" /> + <menu_item_separator + layout="topleft" /> + <menu_item_call + label="Revert All Changes" + layout="topleft" + name="Revert All Changes" /> + <menu_item_separator + layout="topleft" /> + <menu_item_call + label="Load from file..." + layout="topleft" + name="LoadFromFile" /> + <menu_item_call + label="Save to file..." + layout="topleft" + name="SaveToFile" /> <menu_item_separator layout="topleft" /> <menu_item_call @@ -80,95 +80,95 @@ function="Floater.Toggle" parameter="script_colors"/> </menu_item_call> - </menu> - <menu - top="0" - height="198" - label="Edit" - layout="topleft" - mouse_opaque="false" - name="Edit" - width="139"> - <menu_item_call - enabled="false" - label="Undo" - layout="topleft" - name="Undo" /> - <menu_item_call - enabled="false" - label="Redo" - layout="topleft" - name="Redo" /> - <menu_item_separator - layout="topleft" /> - <menu_item_call - enabled="false" - label="Cut" - layout="topleft" - name="Cut" /> - <menu_item_call - enabled="false" - label="Copy" - layout="topleft" - name="Copy" /> - <menu_item_call - enabled="false" - label="Paste" - layout="topleft" - name="Paste" /> - <menu_item_separator - layout="topleft" - name="separator2" /> - <menu_item_call - label="Select All" - layout="topleft" - name="Select All" /> - <menu_item_call - enabled="false" - label="Deselect" - layout="topleft" - name="Deselect" /> - <menu_item_separator - layout="topleft" - name="separator3" /> - <menu_item_call - label="Search / Replace..." - layout="topleft" - name="Search / Replace..." /> + </menu> + <menu + top="0" + height="198" + label="Edit" + layout="topleft" + mouse_opaque="false" + name="Edit" + width="139"> + <menu_item_call + enabled="false" + label="Undo" + layout="topleft" + name="Undo" /> + <menu_item_call + enabled="false" + label="Redo" + layout="topleft" + name="Redo" /> + <menu_item_separator + layout="topleft" /> + <menu_item_call + enabled="false" + label="Cut" + layout="topleft" + name="Cut" /> + <menu_item_call + enabled="false" + label="Copy" + layout="topleft" + name="Copy" /> + <menu_item_call + enabled="false" + label="Paste" + layout="topleft" + name="Paste" /> + <menu_item_separator + layout="topleft" + name="separator2" /> + <menu_item_call + label="Select All" + layout="topleft" + name="Select All" /> + <menu_item_call + enabled="false" + label="Deselect" + layout="topleft" + name="Deselect" /> + <menu_item_separator + layout="topleft" + name="separator3" /> + <menu_item_call + label="Search / Replace..." + layout="topleft" + name="Search / Replace..." /> <menu_item_call label="Go to line..." layout="topleft" name="Go to line..." /> - </menu> - <menu - top="0" - height="34" - label="Help" - layout="topleft" - mouse_opaque="false" - name="Help" - width="112"> - <menu_item_call - label="Help..." - layout="topleft" - name="Help..." /> - <menu_item_call - label="Keyword Help..." - layout="topleft" - name="Keyword Help..." /> - </menu> - </menu_bar> + </menu> + <menu + top="0" + height="34" + label="Help" + layout="topleft" + mouse_opaque="false" + name="Help" + width="112"> + <menu_item_call + label="Help..." + layout="topleft" + name="Help..." /> + <menu_item_call + label="Keyword Help..." + layout="topleft" + name="Keyword Help..." /> + </menu> + </menu_bar> <script_editor - left="0" - type="string" - length="1" - follows="left|top|right|bottom" - font="Monospace" + left="0" + type="string" + length="1" + follows="left|top|right|bottom" + font="Monospace" height="376" - ignore_tab="false" - layout="topleft" + ignore_tab="false" + layout="topleft" max_length="262144" - name="Script Editor" + name="Script Editor" width="487" text_color="ScriptText" default_color="ScriptText" @@ -177,51 +177,51 @@ text_readonly_color="ScriptText" bg_readonly_color="ScriptBackground" cursor_color="ScriptCursorColor" - enable_tooltip_paste="true" + enable_tooltip_paste="true" word_wrap="true" show_context_menu="true"> - Loading... + Loading... </script_editor> - <scroll_list + <scroll_list top_pad="10" left="0" - follows="left|right|bottom" - height="60" - layout="topleft" - name="lsl errors" + follows="left|right|bottom" + height="60" + layout="topleft" + name="lsl errors" width="487" /> - <text - follows="left|bottom" - height="12" - layout="topleft" + <text + follows="left|bottom" + height="12" + layout="topleft" left="0" - name="line_col" - width="128" /> - <combo_box - follows="left|bottom" - height="23" - label="Insert..." - layout="topleft" - name="Insert..." + name="line_col" + width="128" /> + <combo_box + follows="left|bottom" + height="23" + label="Insert..." + layout="topleft" + name="Insert..." width="128" /> - <button - follows="right|bottom" - height="23" - label="Save" - label_selected="Save" - layout="topleft" - top_pad="-35" + <button + follows="right|bottom" + height="23" + label="Save" + label_selected="Save" + layout="topleft" + top_pad="-35" right="487" - name="Save_btn" - width="81" /> - <button - enabled="false" - follows="right|bottom" - height="23" - label="Edit..." - layout="topleft" - top_pad="-23" + name="Save_btn" + width="81" /> + <button + enabled="false" + follows="right|bottom" + height="23" + label="Edit..." + layout="topleft" + top_pad="-23" right="400" - name="Edit_btn" - width="81" /> + name="Edit_btn" + width="81" /> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_script_experience.xml b/indra/newview/skins/default/xui/en/panel_script_experience.xml new file mode 100644 index 0000000000..e798638751 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_script_experience.xml @@ -0,0 +1,97 @@ +<panel + name="script_experience" + title="EXPERIENCE" + width="400" + follows="top|left|right" + top="0" + left="0" + layout="topleft"> + <!-- <floater.string name="EXPERIENCE">EXPERIENCE!!!!</floater.string> + + <floater.string name="Script:">Script:</floater.string> + <floater.string name="Associated with:">Associated with:</floater.string> + <floater.string name="You can contribute:">You can contribute:</floater.string> + <floater.string name="Associate with:">Associate with:</floater.string> + <floater.string name="Yes">Yes</floater.string> + <floater.string name="No">No</floater.string> + <floater.string name="(none)"></floater.string> + <floater.string name="Choose Experience...">Choose Experience...</floater.string> + <floater.string name="You are not a contributor to any experiences.">You are not a contributor to any experiences.</floater.string> + --> + + <button name="Expand Experience" width="200" + height="35" + is_toggle="true" + tab_stop="false" + pad_left="35" + top="0" + left="3" + label="Experience" + halign="left" + handle_right_mouse="false" + follows="top|left|right" + image_unselected="MarketplaceBtn_Off" + image_selected="MarketplaceBtn_Selected"> + </button> + <check_box + follows="top|right" height="25" label="Uses Experience" left="215" width="0" top="0" name="enable_xp" + /> + <layout_stack + follows="top|left|right" + + width="384" + height="140" + name="xp_details" + left="4" + top="45" + orientation="horizontal" + layout="topleft" + visible="false"> + <layout_panel width="120" + height="140"> + <text > + Script: + </text> + <text bottom_delta="25"> + Associated with: + </text> + <text bottom_delta="25"> + You can contribute: + </text> + <text bottom_delta="25"> + Associate with: + </text> + </layout_panel> + + <layout_panel width="250" + height="140"> + <text > + EasySit Animator 1.2.4 + </text> + <text bottom_delta="25" + text_color="HTMLLinkColor" font.style="UNDERLINE"> + Kyle's Superhero RPG + </text> + <text bottom_delta="25"> + Yes + </text> + <combo_box left="0" bottom_delta="33" + label="Choose Experience..." + name="Experiences..." + follows="top|left|right" + /> + </layout_panel> + </layout_stack> + <text + follows="top|left|right" + width="400" + height="15" + bottom_delta="-12" + left="0" + halign="center" + name="No Experiences" + visible="false" + text_color="AlertCautionTextColor"> + You are not a contributor to any experiences. + </text> +</panel> diff --git a/indra/newview/skins/default/xui/en/role_actions.xml b/indra/newview/skins/default/xui/en/role_actions.xml index 1044cbfd2e..c2e7c706d6 100755 --- a/indra/newview/skins/default/xui/en/role_actions.xml +++ b/indra/newview/skins/default/xui/en/role_actions.xml @@ -190,4 +190,16 @@ longdescription="Members in a Role with this Ability can control access and participation in group voice and text chat sessions." name="moderate group chat" value="37" /> </action_set> + <action_set + description="These Abilities include power to modify experiences owned by this group." + name="experience_tools_experience"> + <action description="Experience Admin" + longdescription="Members in a role with this ability can edit the meta-data for an experience." + name="experience admin" + value ="49" /> + <action description="Experience Contributor" + longdescription="Members in a role with this ability can contribute scripts for an experience." + name="experience contributor" + value ="50" /> + </action_set> </role_actions> diff --git a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml index c5dfb703e5..fc3fdbcfa5 100755 --- a/indra/newview/skins/default/xui/en/sidepanel_item_info.xml +++ b/indra/newview/skins/default/xui/en/sidepanel_item_info.xml @@ -1,440 +1,472 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <panel - follows="all" - height="570" - layout="topleft" - name="item properties" - help_topic="item_properties" - title="Item Profile" - width="333"> - <panel.string - name="unknown"> - (unknown) - </panel.string> - <panel.string - name="unknown_multiple"> - (unknown / multiple) - </panel.string> - <panel.string - name="public"> - (public) - </panel.string> - <panel.string - name="you_can"> - You can: - </panel.string> - <panel.string - name="owner_can"> - Owner can: - </panel.string> - <panel.string - name="acquiredDate"> - [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local] - </panel.string> - <panel.string - name="origin_inventory"> - (Inventory) - </panel.string> - <panel.string - name="origin_inworld"> - (Inworld) - </panel.string> - <icon - follows="top|right" - height="18" - image_name="Lock" - layout="topleft" - right="-15" - mouse_opaque="true" - name="IconLocked" - top="8" - width="18" /> - <button - follows="top|left" - height="24" - image_hover_unselected="BackButton_Over" - image_pressed="BackButton_Press" - image_unselected="BackButton_Off" - layout="topleft" - left="12" - name="back_btn" - tab_stop="false" - top="2" - width="30" - use_draw_context_alpha="false" /> - <text - follows="top|left|right" - font="SansSerifHugeBold" - height="26" - layout="topleft" - left_pad="3" - name="title" - text_color="LtGray" - top="2" - use_ellipses="true" - value="Item Profile" - width="275" /> - <text - follows="top|left|right" - height="13" - layout="topleft" - left="45" - name="origin" - text_color="LtGray_50" - use_ellipses="true" - value="(Inventory)" - width="275" /> - <scroll_container - color="DkGray2" - follows="all" - layout="topleft" - left="9" - name="item_profile_scroll" - opaque="true" - height="493" - width="313" - top="45"> - <panel - follows="left|top|right" - height="390" - help_topic="" - label="" - layout="topleft" - left="0" - name="item_profile" - top="0" - width="295"> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left="5" - name="LabelItemNameTitle" - top="10" - width="78"> - Name: - </text> - <line_editor - border_style="line" - border_thickness="1" - follows="left|top|right" - height="20" - layout="topleft" - left_delta="78" - max_length_bytes="63" - name="LabelItemName" - top_delta="0" - width="210" /> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left="5" - name="LabelItemDescTitle" - top_pad="10" - width="78"> - Description: - </text> - <line_editor - border_style="line" - border_thickness="1" - follows="left|top|right" - height="23" - layout="topleft" - left_delta="78" - max_length_bytes="127" - name="LabelItemDesc" - top_delta="-5" - width="210" /> - <text - type="string" - length="1" - follows="left|top" - height="23" - layout="topleft" - left="5" - name="LabelCreatorTitle" - top_pad="10" - width="78"> - Creator: - </text> - <avatar_icon - follows="top|left" - height="20" - default_icon_name="Generic_Person" - layout="topleft" - left_pad="0" - top_delta="-6" - mouse_opaque="true" - width="20" /> - <text - type="string" - follows="left|right|top" - font="SansSerifSmall" - height="15" - layout="topleft" - left_pad="5" - name="LabelCreatorName" - top_delta="6" - use_ellipses="true" - width="165"> - </text> - <button - follows="top|right" - height="16" + follows="all" + height="570" + layout="topleft" + name="item properties" + help_topic="item_properties" + title="Item Profile" + width="333"> + <panel.string + name="loading_experience"> + (loading) + </panel.string> + <panel.string + name="unknown"> + (unknown) + </panel.string> + <panel.string + name="unknown_multiple"> + (unknown / multiple) + </panel.string> + <panel.string + name="public"> + (public) + </panel.string> + <panel.string + name="you_can"> + You can: + </panel.string> + <panel.string + name="owner_can"> + Owner can: + </panel.string> + <panel.string + name="acquiredDate"> + [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local] + </panel.string> + <panel.string + name="origin_inventory"> + (Inventory) + </panel.string> + <panel.string + name="origin_inworld"> + (Inworld) + </panel.string> + <icon + follows="top|right" + height="18" + image_name="Lock" + layout="topleft" + right="-15" + mouse_opaque="true" + name="IconLocked" + top="8" + width="18" /> + <button + follows="top|left" + height="24" + image_hover_unselected="BackButton_Over" + image_pressed="BackButton_Press" + image_unselected="BackButton_Off" + layout="topleft" + left="12" + name="back_btn" + tab_stop="false" + top="2" + width="30" + use_draw_context_alpha="false" /> + <text + follows="top|left|right" + font="SansSerifHugeBold" + height="26" + layout="topleft" + left_pad="3" + name="title" + text_color="LtGray" + top="2" + use_ellipses="true" + value="Item Profile" + width="275" /> + <text + follows="top|left|right" + height="13" + layout="topleft" + left="45" + name="origin" + text_color="LtGray_50" + use_ellipses="true" + value="(Inventory)" + width="275" /> + <scroll_container + color="DkGray2" + follows="all" + layout="topleft" + left="9" + name="item_profile_scroll" + opaque="true" + height="493" + width="313" + top="45"> + <panel + follows="left|top|right" + height="390" + help_topic="" + label="" + layout="topleft" + left="0" + name="item_profile" + top="0" + width="295"> + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left="5" + name="LabelItemNameTitle" + top="10" + width="78"> + Name: + </text> + <line_editor + border_style="line" + border_thickness="1" + follows="left|top|right" + height="20" + layout="topleft" + left_delta="78" + max_length_bytes="63" + name="LabelItemName" + top_delta="0" + width="210" /> + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left="5" + name="LabelItemDescTitle" + top_pad="10" + width="78"> + Description: + </text> + <line_editor + border_style="line" + border_thickness="1" + follows="left|top|right" + height="23" + layout="topleft" + left_delta="78" + max_length_bytes="127" + name="LabelItemDesc" + top_delta="-5" + width="210" /> + <text + type="string" + length="1" + follows="left|top" + height="23" + layout="topleft" + left="5" + name="LabelCreatorTitle" + top_pad="10" + width="78"> + Creator: + </text> + <avatar_icon + follows="top|left" + height="20" + default_icon_name="Generic_Person" + layout="topleft" + left_pad="0" + top_delta="-6" + mouse_opaque="true" + width="20" /> + <text + type="string" + follows="left|right|top" + font="SansSerifSmall" + height="15" + layout="topleft" + left_pad="5" + name="LabelCreatorName" + top_delta="6" + use_ellipses="true" + width="165"> + </text> + <button + follows="top|right" + height="16" + image_selected="Inspector_I" + image_unselected="Inspector_I" + layout="topleft" + right="-5" + name="BtnCreator" + top_delta="-6" + width="16" /> + <text + type="string" + length="1" + follows="left|top" + height="23" + layout="topleft" + left="5" + name="LabelOwnerTitle" + top_pad="10" + width="78"> + Owner: + </text> + <avatar_icon + follows="top|left" + height="20" + default_icon_name="Generic_Person" + layout="topleft" + left_pad="0" + top_delta="-6" + mouse_opaque="true" + width="20" /> + <text + type="string" + follows="left|right|top" + font="SansSerifSmall" + height="15" + layout="topleft" + left_pad="5" + name="LabelOwnerName" + top_delta="6" + use_ellipses="true" + width="165"> + </text> + <button + follows="top|right" + height="16" image_selected="Inspector_I" image_unselected="Inspector_I" + layout="topleft" + right="-5" + name="BtnOwner" + top_delta="-3" + width="16" /> + <text + type="string" + length="1" + follows="left|top" + height="23" + layout="topleft" + left="5" + name="LabelAcquiredTitle" + top_pad="10" + width="78"> + Acquired: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="23" + layout="topleft" + left_delta="78" + name="LabelAcquiredDate" + top_delta="0" + width="210"> + </text> + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left="5" + name="LabelItemExperienceTitle" + top_pad="0" + width="78" + visible="true"> + Experience: + </text> + <text + type="string" + length="1" + follows="left|top|right" + height="10" + layout="topleft" + left_delta="78" + name="LabelItemExperience" + top_delta="0" + width="210" + visible="true" + /> + <panel + border="false" + follows="left|top|right" + layout="topleft" + mouse_opaque="false" + name="perms_inv" + left="0" + top_pad="25" + height="155" + width="313"> + <text + type="string" + length="1" + left="10" + top_pad="13" + text_color="EmphasisColor" + height="15" + follows="left|top|right" + layout="topleft" + name="perm_modify" + width="200"> + You can: + </text> + <check_box + height="18" + label="Modify" + layout="topleft" + left="20" + name="CheckOwnerModify" + top_pad="0" + width="90" /> + <check_box + height="18" + label="Copy" + layout="topleft" + left_pad="0" + name="CheckOwnerCopy" + width="90" /> + <check_box + height="18" + label="Transfer" + layout="topleft" + left_pad="0" + name="CheckOwnerTransfer" + width="106" /> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="AnyoneLabel" + top_pad="8" + width="100"> + Anyone: + </text> + <check_box + height="18" + label="Copy" layout="topleft" - right="-5" - name="BtnCreator" - top_delta="-6" - width="16" /> - <text + left_pad="0" + name="CheckEveryoneCopy" + top_delta="-2" + width="150" /> + <text type="string" length="1" follows="left|top" - height="23" + height="16" layout="topleft" - left="5" - name="LabelOwnerTitle" - top_pad="10" - width="78"> - Owner: - </text> - <avatar_icon - follows="top|left" - height="20" - default_icon_name="Generic_Person" - layout="topleft" - left_pad="0" - top_delta="-6" - mouse_opaque="true" - width="20" /> - <text + left="10" + name="GroupLabel" + top_pad="8" + width="100"> + Group: + </text> + <check_box + height="18" + label="Share" + layout="topleft" + left_pad="0" + top_delta="-2" + name="CheckShareWithGroup" + tool_tip="Allow all members of the set group to share your modify permissions for this object. You must Deed to enable role restrictions." + width="150" /> + <text type="string" - follows="left|right|top" - font="SansSerifSmall" - height="15" - layout="topleft" - left_pad="5" - name="LabelOwnerName" - top_delta="6" - use_ellipses="true" - width="165"> - </text> - <button - follows="top|right" - height="16" - image_selected="Inspector_I" - image_unselected="Inspector_I" - layout="topleft" - right="-5" - name="BtnOwner" - top_delta="-3" - width="16" /> - <text - type="string" - length="1" - follows="left|top" - height="23" - layout="topleft" - left="5" - name="LabelAcquiredTitle" - top_pad="10" - width="78"> - Acquired: - </text> - <text - type="string" - length="1" - follows="left|top|right" - height="23" - layout="topleft" - left_delta="78" - name="LabelAcquiredDate" - top_delta="0" - width="210"> - </text> - <panel - border="false" - follows="left|top|right" - layout="topleft" - mouse_opaque="false" - name="perms_inv" - left="0" - top_pad="25" - height="155" - width="313"> - <text - type="string" - length="1" - left="10" - top_pad="13" - text_color="EmphasisColor" - height="15" - follows="left|top|right" - layout="topleft" - name="perm_modify" - width="200"> - You can: - </text> - <check_box - height="18" - label="Modify" - layout="topleft" - left="20" - name="CheckOwnerModify" - top_pad="0" - width="90" /> - <check_box - height="18" - label="Copy" - layout="topleft" - left_pad="0" - name="CheckOwnerCopy" - width="90" /> - <check_box - height="18" - label="Transfer" - layout="topleft" - left_pad="0" - name="CheckOwnerTransfer" - width="106" /> - <text - type="string" - length="1" - follows="left|top" - height="16" - layout="topleft" - left="10" - name="AnyoneLabel" - top_pad="8" - width="100"> - Anyone: - </text> - <check_box - height="18" - label="Copy" - layout="topleft" - left_pad="0" - name="CheckEveryoneCopy" - top_delta="-2" - width="150" /> - <text - type="string" - length="1" - follows="left|top" - height="16" - layout="topleft" - left="10" - name="GroupLabel" - top_pad="8" - width="100"> - Group: - </text> - <check_box - height="18" - label="Share" - layout="topleft" - left_pad="0" - top_delta="-2" - name="CheckShareWithGroup" - tool_tip="Allow all members of the set group to share your modify permissions for this object. You must Deed to enable role restrictions." - width="150" /> - <text - type="string" - length="1" - follows="left|top" - height="16" - layout="topleft" - left="10" - name="NextOwnerLabel" - top_pad="8" - width="200" - word_wrap="true"> - Next owner: - </text> - <check_box - height="18" - label="Modify" - layout="topleft" - left="20" - top_pad="0" - name="CheckNextOwnerModify" - width="90" /> - <check_box - height="18" - label="Copy" - layout="topleft" - left_pad="0" - name="CheckNextOwnerCopy" - width="90" /> - <check_box - height="18" - label="Transfer" - layout="topleft" - left_pad="0" - name="CheckNextOwnerTransfer" - tool_tip="Next owner can give away or resell this object" - width="106" /> - </panel> + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" + name="NextOwnerLabel" + top_pad="8" + width="200" + word_wrap="true"> + Next owner: + </text> <check_box - height="18" - label="For Sale" - layout="topleft" - left="20" - name="CheckPurchase" - top_pad="20" - width="100" /> - <combo_box - height="23" - left_pad="0" - layout="topleft" - follows="left|top" - name="combobox sale copy" - width="170"> - <combo_box.item - label="Copy" - name="Copy" - value="Copy" /> - <combo_box.item - label="Original" - name="Original" - value="Original" /> - </combo_box> - <spinner - follows="left|top" - decimal_digits="0" - increment="1" - control_name="Edit Cost" - name="Edit Cost" - label="Price: L$" - label_width="75" - left="120" - width="170" - min_val="0" - height="23" - max_val="999999999" - top_pad="10"/> - </panel> - </scroll_container> - <panel - height="30" - layout="topleft" - name="button_panel" - left="5" - top_pad="0" - width="313"> - <button - height="23" - label="Cancel" - layout="topleft" - name="cancel_btn" - right="-1" - width="100" /> - </panel> - </panel> + height="18" + label="Modify" + layout="topleft" + left="20" + top_pad="0" + name="CheckNextOwnerModify" + width="90" /> + <check_box + height="18" + label="Copy" + layout="topleft" + left_pad="0" + name="CheckNextOwnerCopy" + width="90" /> + <check_box + height="18" + label="Transfer" + layout="topleft" + left_pad="0" + name="CheckNextOwnerTransfer" + tool_tip="Next owner can give away or resell this object" + width="106" /> + </panel> + <check_box + height="18" + label="For Sale" + layout="topleft" + left="20" + name="CheckPurchase" + top_pad="20" + width="100" /> + <combo_box + height="23" + left_pad="0" + layout="topleft" + follows="left|top" + name="combobox sale copy" + width="170"> + <combo_box.item + label="Copy" + name="Copy" + value="Copy" /> + <combo_box.item + label="Original" + name="Original" + value="Original" /> + </combo_box> + <spinner + follows="left|top" + decimal_digits="0" + increment="1" + control_name="Edit Cost" + name="Edit Cost" + label="Price: L$" + label_width="75" + left="120" + width="170" + min_val="0" + height="23" + max_val="999999999" + top_pad="10"/> + </panel> + + </scroll_container> + <panel + height="30" + layout="topleft" + name="button_panel" + left="5" + top_pad="0" + width="313" + follows="top|right|left"> + <button + follows="top|right" + height="23" + label="Cancel" + layout="topleft" + name="cancel_btn" + right="-1" + width="100" /> + </panel> +</panel> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 5fbc539a06..e5ed49fd1b 100755 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -488,7 +488,7 @@ Please try logging in again in a minute.</string> <string name="NotConnected">Not Connected</string> <string name="AgentNameSubst">(You)</string> <!-- Substitution for agent name --> - <string name="JoinAnExperience">Join an experience</string> <!-- not used --> + <string name="JoinAnExperience"></string> <!-- intentionally left blank --> <string name="SilentlyManageEstateAccess">Suppress alerts when managing estate access lists</string> <string name="OverrideYourAnimations">Replace your default animations</string> <string name="ScriptReturnObjects">Return objects on your behalf</string> @@ -519,8 +519,6 @@ Please try logging in again in a minute.</string> <string name="load_file_verb">Load</string> <string name="targa_image_files">Targa Images</string> <string name="bitmap_image_files">Bitmap Images</string> - <string name="png_image_files">PNG Images</string> - <string name="save_texture_image_files">Targa or PNG Images</string> <string name="avi_movie_file">AVI Movie File</string> <string name="xaf_animation_file">XAF Anim File</string> <string name="xml_file">XML File</string> @@ -2494,7 +2492,8 @@ The [[MARKETPLACE_CREATE_STORE_URL] Marketplace store] is returning errors. <string name="CompileQueueProblemDownloading">Problem downloading</string> <string name="CompileQueueInsufficientPermDownload">Insufficient permissions to download a script.</string> <string name="CompileQueueInsufficientPermFor">Insufficient permissions for</string> - <string name="CompileQueueUnknownFailure">Unknown failure to download</string> + <string name="CompileQueueUnknownFailure">Unknown failure to download</string> + <string name="CompileNoExperiencePerm">Skipping script [SCRIPT] with Experience [EXPERIENCE].</string> <string name="CompileQueueTitle">Recompilation Progress</string> <string name="CompileQueueStart">recompile</string> <string name="ResetQueueTitle">Reset Progress</string> @@ -4027,6 +4026,34 @@ Try enclosing path to the editor with double quotes. <!-- Spell check settings floater --> <string name="UserDictionary">[User]</string> + <!-- Experience Tools strings --> + <string name="experience_tools_experience">Experience</string> + <string name="ExperienceNameNull">(no experience)</string> + <string name="GRID_WIDE">Grid-wide</string> + <string name="Allowed_Experiences_Tab">ALLOWED</string> + <string name="Blocked_Experiences_Tab">BLOCKED</string> + <string name="Contrib_Experiences_Tab">CONTRIBUTOR</string> + <string name="Admin_Experiences_Tab">ADMIN</string> + <string name="Recent_Experiences_Tab">RECENT</string> + <string name="Owned_Experiences_Tab">OWNED</string> + <string name="ExperiencesCounter">([EXPERIENCES], max [MAXEXPERIENCES])</string> + <string name="ExperiencePermission1">take over your controls</string> + <string name="ExperiencePermission3">trigger animations on your avatar</string> + <string name="ExperiencePermission4">attach to your avatar</string> + <string name="ExperiencePermission9">track your camera</string> + <string name="ExperiencePermission10">control your camera</string> + <string name="ExperiencePermission11">teleport you</string> + <string name="ExperiencePermission12">automatically accept experience permissions</string> + <string name="ExperiencePermissionShortUnknown">perform an unknown operation: [Permission]</string> + <string name="ExperiencePermissionShort1">Take Controls</string> + <string name="ExperiencePermissionShort3">Trigger Animations</string> + <string name="ExperiencePermissionShort4">Attach</string> + <string name="ExperiencePermissionShort9">Track Camera</string> + <string name="ExperiencePermissionShort10">Control Camera</string> + <string name="ExperiencePermissionShort11">Teleport</string> + <string name="ExperiencePermissionShort12">Permission</string> + <string name="ExperiencePermissionShortUnknown">Unknown: [Permission]</string> + <!-- Conversation log messages --> <string name="logging_calls_disabled_log_empty"> Conversations are not being logged. To begin keeping a log, choose "Save: Log only" or "Save: Log and transcripts" under Preferences > Chat. diff --git a/indra/tools/vstool/DispatchUtility.cs b/indra/tools/vstool/DispatchUtility.cs new file mode 100644 index 0000000000..6056ac55a1 --- /dev/null +++ b/indra/tools/vstool/DispatchUtility.cs @@ -0,0 +1,271 @@ +#region Using Directives
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Reflection;
+using System.Security.Permissions;
+
+#endregion
+
+namespace TestDispatchUtility
+{
+ /// <summary>
+ /// Provides helper methods for working with COM IDispatch objects that have a registered type library.
+ /// </summary>
+ public static class DispatchUtility
+ {
+ #region Private Constants
+
+ private const int S_OK = 0; //From WinError.h
+ private const int LOCALE_SYSTEM_DEFAULT = 2 << 10; //From WinNT.h == 2048 == 0x800
+
+ #endregion
+
+ #region Public Methods
+
+ /// <summary>
+ /// Gets whether the specified object implements IDispatch.
+ /// </summary>
+ /// <param name="obj">An object to check.</param>
+ /// <returns>True if the object implements IDispatch. False otherwise.</returns>
+ public static bool ImplementsIDispatch(object obj)
+ {
+ bool result = obj is IDispatchInfo;
+ return result;
+ }
+
+ /// <summary>
+ /// Gets a Type that can be used with reflection.
+ /// </summary>
+ /// <param name="obj">An object that implements IDispatch.</param>
+ /// <param name="throwIfNotFound">Whether an exception should be thrown if a Type can't be obtained.</param>
+ /// <returns>A .NET Type that can be used with reflection.</returns>
+ /// <exception cref="InvalidCastException">If <paramref name="obj"/> doesn't implement IDispatch.</exception>
+ [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
+ public static Type GetType(object obj, bool throwIfNotFound)
+ {
+ RequireReference(obj, "obj");
+ Type result = GetType((IDispatchInfo)obj, throwIfNotFound);
+ return result;
+ }
+
+ /// <summary>
+ /// Tries to get the DISPID for the requested member name.
+ /// </summary>
+ /// <param name="obj">An object that implements IDispatch.</param>
+ /// <param name="name">The name of a member to lookup.</param>
+ /// <param name="dispId">If the method returns true, this holds the DISPID on output.
+ /// If the method returns false, this value should be ignored.</param>
+ /// <returns>True if the member was found and resolved to a DISPID. False otherwise.</returns>
+ /// <exception cref="InvalidCastException">If <paramref name="obj"/> doesn't implement IDispatch.</exception>
+ [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
+ public static bool TryGetDispId(object obj, string name, out int dispId)
+ {
+ RequireReference(obj, "obj");
+ bool result = TryGetDispId((IDispatchInfo)obj, name, out dispId);
+ return result;
+ }
+
+ /// <summary>
+ /// Invokes a member by DISPID.
+ /// </summary>
+ /// <param name="obj">An object that implements IDispatch.</param>
+ /// <param name="dispId">The DISPID of a member. This can be obtained using
+ /// <see cref="TryGetDispId(object, string, out int)"/>.</param>
+ /// <param name="args">The arguments to pass to the member.</param>
+ /// <returns>The member's return value.</returns>
+ /// <remarks>
+ /// This can invoke a method or a property get accessor.
+ /// </remarks>
+ public static object Invoke(object obj, int dispId, object[] args)
+ {
+ string memberName = "[DispId=" + dispId + "]";
+ object result = Invoke(obj, memberName, args);
+ return result;
+ }
+
+ /// <summary>
+ /// Invokes a member by name.
+ /// </summary>
+ /// <param name="obj">An object.</param>
+ /// <param name="memberName">The name of the member to invoke.</param>
+ /// <param name="args">The arguments to pass to the member.</param>
+ /// <returns>The member's return value.</returns>
+ /// <remarks>
+ /// This can invoke a method or a property get accessor.
+ /// </remarks>
+ public static object Invoke(object obj, string memberName, object[] args)
+ {
+ RequireReference(obj, "obj");
+ Type type = obj.GetType();
+ object result = type.InvokeMember(memberName, BindingFlags.InvokeMethod | BindingFlags.GetProperty,
+ null, obj, args, null);
+ return result;
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ /// <summary>
+ /// Requires that the value is non-null.
+ /// </summary>
+ /// <typeparam name="T">The type of the value.</typeparam>
+ /// <param name="value">The value to check.</param>
+ /// <param name="name">The name of the value.</param>
+ private static void RequireReference<T>(T value, string name) where T : class
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(name);
+ }
+ }
+
+ /// <summary>
+ /// Gets a Type that can be used with reflection.
+ /// </summary>
+ /// <param name="dispatch">An object that implements IDispatch.</param>
+ /// <param name="throwIfNotFound">Whether an exception should be thrown if a Type can't be obtained.</param>
+ /// <returns>A .NET Type that can be used with reflection.</returns>
+ private static Type GetType(IDispatchInfo dispatch, bool throwIfNotFound)
+ {
+ RequireReference(dispatch, "dispatch");
+
+ Type result = null;
+ int typeInfoCount;
+ int hr = dispatch.GetTypeInfoCount(out typeInfoCount);
+ if (hr == S_OK && typeInfoCount > 0)
+ {
+ // Type info isn't usually culture-aware for IDispatch, so we might as well pass
+ // the default locale instead of looking up the current thread's LCID each time
+ // (via CultureInfo.CurrentCulture.LCID).
+ dispatch.GetTypeInfo(0, LOCALE_SYSTEM_DEFAULT, out result);
+ }
+
+ if (result == null && throwIfNotFound)
+ {
+ // If the GetTypeInfoCount called failed, throw an exception for that.
+ Marshal.ThrowExceptionForHR(hr);
+
+ // Otherwise, throw the same exception that Type.GetType would throw.
+ throw new TypeLoadException();
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Tries to get the DISPID for the requested member name.
+ /// </summary>
+ /// <param name="dispatch">An object that implements IDispatch.</param>
+ /// <param name="name">The name of a member to lookup.</param>
+ /// <param name="dispId">If the method returns true, this holds the DISPID on output.
+ /// If the method returns false, this value should be ignored.</param>
+ /// <returns>True if the member was found and resolved to a DISPID. False otherwise.</returns>
+ private static bool TryGetDispId(IDispatchInfo dispatch, string name, out int dispId)
+ {
+ RequireReference(dispatch, "dispatch");
+ RequireReference(name, "name");
+
+ bool result = false;
+
+ // Members names aren't usually culture-aware for IDispatch, so we might as well
+ // pass the default locale instead of looking up the current thread's LCID each time
+ // (via CultureInfo.CurrentCulture.LCID).
+ Guid iidNull = Guid.Empty;
+ int hr = dispatch.GetDispId(ref iidNull, ref name, 1, LOCALE_SYSTEM_DEFAULT, out dispId);
+
+ const int DISP_E_UNKNOWNNAME = unchecked((int)0x80020006); //From WinError.h
+ const int DISPID_UNKNOWN = -1; //From OAIdl.idl
+ if (hr == S_OK)
+ {
+ result = true;
+ }
+ else if (hr == DISP_E_UNKNOWNNAME && dispId == DISPID_UNKNOWN)
+ {
+ // This is the only supported "error" case because it means IDispatch
+ // is saying it doesn't know the member we asked about.
+ result = false;
+ }
+ else
+ {
+ // The other documented result codes are all errors.
+ Marshal.ThrowExceptionForHR(hr);
+ }
+
+ return result;
+ }
+
+ #endregion
+
+ #region Private Interfaces
+
+ /// <summary>
+ /// A partial declaration of IDispatch used to lookup Type information and DISPIDs.
+ /// </summary>
+ /// <remarks>
+ /// This interface only declares the first three methods of IDispatch. It omits the
+ /// fourth method (Invoke) because there are already plenty of ways to do dynamic
+ /// invocation in .NET. But the first three methods provide dynamic type metadata
+ /// discovery, which .NET doesn't provide normally if you have a System.__ComObject
+ /// RCW instead of a strongly-typed RCW.
+ /// <para/>
+ /// Note: The original declaration of IDispatch is in OAIdl.idl.
+ /// </remarks>
+ [ComImport]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ [Guid("00020400-0000-0000-C000-000000000046")]
+ private interface IDispatchInfo
+ {
+ /// <summary>
+ /// Gets the number of Types that the object provides (0 or 1).
+ /// </summary>
+ /// <param name="typeInfoCount">Returns 0 or 1 for the number of Types provided by <see cref="GetTypeInfo"/>.</param>
+ /// <remarks>
+ /// http://msdn.microsoft.com/en-us/library/da876d53-cb8a-465c-a43e-c0eb272e2a12(VS.85)
+ /// </remarks>
+ [PreserveSig]
+ int GetTypeInfoCount(out int typeInfoCount);
+
+ /// <summary>
+ /// Gets the Type information for an object if <see cref="GetTypeInfoCount"/> returned 1.
+ /// </summary>
+ /// <param name="typeInfoIndex">Must be 0.</param>
+ /// <param name="lcid">Typically, LOCALE_SYSTEM_DEFAULT (2048).</param>
+ /// <param name="typeInfo">Returns the object's Type information.</param>
+ /// <remarks>
+ /// http://msdn.microsoft.com/en-us/library/cc1ec9aa-6c40-4e70-819c-a7c6dd6b8c99(VS.85)
+ /// </remarks>
+ void GetTypeInfo(int typeInfoIndex, int lcid, [MarshalAs(UnmanagedType.CustomMarshaler,
+ MarshalTypeRef = typeof(System.Runtime.InteropServices.CustomMarshalers.TypeToTypeInfoMarshaler))] out Type typeInfo);
+
+ /// <summary>
+ /// Gets the DISPID of the specified member name.
+ /// </summary>
+ /// <param name="riid">Must be IID_NULL. Pass a copy of Guid.Empty.</param>
+ /// <param name="name">The name of the member to look up.</param>
+ /// <param name="nameCount">Must be 1.</param>
+ /// <param name="lcid">Typically, LOCALE_SYSTEM_DEFAULT (2048).</param>
+ /// <param name="dispId">If a member with the requested <paramref name="name"/>
+ /// is found, this returns its DISPID and the method's return value is 0.
+ /// If the method returns a non-zero value, then this parameter's output value is
+ /// undefined.</param>
+ /// <returns>Zero for success. Non-zero for failure.</returns>
+ /// <remarks>
+ /// http://msdn.microsoft.com/en-us/library/6f6cf233-3481-436e-8d6a-51f93bf91619(VS.85)
+ /// </remarks>
+ [PreserveSig]
+ int GetDispId(ref Guid riid, ref string name, int nameCount, int lcid, out int dispId);
+
+ // NOTE: The real IDispatch also has an Invoke method next, but we don't need it.
+ // We can invoke methods using .NET's Type.InvokeMember method with the special
+ // [DISPID=n] syntax for member "names", or we can get a .NET Type using GetTypeInfo
+ // and invoke methods on that through reflection.
+ // Type.InvokeMember: http://msdn.microsoft.com/en-us/library/de3dhzwy.aspx
+ }
+
+ #endregion
+ }
+}
diff --git a/indra/tools/vstool/app.config b/indra/tools/vstool/app.config new file mode 100644 index 0000000000..8494f728ff --- /dev/null +++ b/indra/tools/vstool/app.config @@ -0,0 +1,3 @@ +<?xml version="1.0"?>
+<configuration>
+<startup><supportedRuntime version="v2.0.50727"/></startup></configuration>
|