diff options
30 files changed, 2134 insertions, 228 deletions
@@ -1,6 +1,5 @@ syntax: glob - # WinMerge temp files *.bak # Compiled python bytecode diff --git a/indra/llcommon/roles_constants.h b/indra/llcommon/roles_constants.h index effd15ea72..65ec290200 100755 --- a/indra/llcommon/roles_constants.h +++ b/indra/llcommon/roles_constants.h @@ -53,7 +53,7 @@ enum LLRoleChangeType // KNOWN HOLES: use these for any single bit powers you need // bit 0x1 << 46 -// bit 0x1 << 49 and above +// bit 0x1 << 51 and above // These powers were removed to make group roles simpler // bit 0x1 << 41 (GP_ACCOUNTING_VIEW) @@ -146,6 +146,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 + const U64 GP_DEFAULT_MEMBER = GP_ACCOUNTING_ACCOUNTABLE | GP_LAND_ALLOW_SET_HOME | GP_NOTICES_RECEIVE diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index d193e367eb..5740584edb 100755 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -38,6 +38,7 @@ set(llmessage_SOURCE_FILES llcurl.cpp lldatapacker.cpp lldispatcher.cpp + llexperiencecache.cpp llfiltersd2xmlrpc.cpp llhost.cpp llhttpassetstorage.cpp @@ -128,6 +129,7 @@ set(llmessage_HEADER_FILES lldbstrings.h lldispatcher.h lleventflags.h + llexperiencecache.h llfiltersd2xmlrpc.h llfollowcamparams.h llhost.h diff --git a/indra/llmessage/llexperiencecache.cpp b/indra/llmessage/llexperiencecache.cpp new file mode 100644 index 0000000000..219e68b51c --- /dev/null +++ b/indra/llmessage/llexperiencecache.cpp @@ -0,0 +1,650 @@ +/** + * @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> PrivateKeyMap; + PrivateKeyMap experinceKeyMap; + + void mapPrivateKeys(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()); + } + + if(row.has(OWNER_ID)) + { + sPendingQueue.erase(row[OWNER_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) + { + mapPrivateKeys(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_INFOS("ExperienceCache") << "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 completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + mHeaders = content; + } + + virtual void result(const LLSD& content) + { + LLSD experiences = content["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_INFOS("ExperienceCache") << "Received result for " << public_key + << " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL ; + + processExperience(public_key, row); + } + + LLSD error_ids = content["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["DoesNotExist"]=true; + + processExperience(id, exp); + LL_INFOS("ExperienceCache") << "Error result for " << id << LL_ENDL ; + } + + LL_INFOS("ExperienceCache") << sCache.size() << " cached experiences" << LL_ENDL; + } + + virtual void error(U32 status, const std::string& reason) + { + LL_WARNS("ExperienceCache") << "Request failed "<<status<<" "<<reason<< 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(status); + + + // 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; + exp[EXPIRES]=retry_timestamp; + exp[EXPERIENCE_ID] = it->first; + exp["key_type"] = it->second; + exp["uuid"] = it->first; + exp["error"] = (LLSD::Integer)status; + exp[PROPERTIES]=PROPERTY_INVALID; + 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 = mHeaders["retry-after"]; + if (retry_after.isDefined()) + { + // We only support the delta-seconds type + S32 delta_seconds = retry_after.asInteger(); + if (delta_seconds > 0) + { + // ...valid delta-seconds + return F64(delta_seconds); + } + } + + // If no Retry-After, look for Cache-Control max-age + F64 expires = 0.0; + if (LLExperienceCache::expirationFromCacheControl(mHeaders, &expires)) + { + return expires; + } + + // No information in header, make a guess + if (status == 503) + { + // ...service unavailable, retry soon + const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min + return 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; + LLSD mHeaders; + }; + + void requestExperiences() + { + if(sAskQueue.empty() || sLookupURL.empty()) + return; + + F64 now = LLFrameTimer::getTotalSeconds(); + + const U32 EXP_URL_SEND_THRESHOLD = 3000; + + + std::ostringstream ostr; + + ask_queue_t keys; + + ostr << sLookupURL; + + char arg='?'; + + int request_count = 0; + for(ask_queue_t::const_iterator it = sAskQueue.begin() ; it != sAskQueue.end() && request_count < sMaximumLookups; ++it) + { + const LLUUID& key = it->first; + const std::string& key_type = it->second; + + ostr << arg << key_type << '=' << key.asString() ; + + keys[key]=key_type; + request_count++; + + sPendingQueue[key] = now; + + arg='&'; + + if(ostr.tellp() > EXP_URL_SEND_THRESHOLD) + { + LL_INFOS("ExperienceCache") << " query: " << ostr.str() << LL_ENDL; + LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys)); + ostr.clear(); + ostr.str(sLookupURL); + arg='?'; + keys.clear(); + } + } + + if(ostr.tellp() > sLookupURL.size()) + { + LL_INFOS("ExperienceCache") << " query: " << ostr.str() << LL_ENDL; + LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys)); + } + + sAskQueue.clear(); + } + + 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)) + { + LLUUID id = exp[EXPERIENCE_ID].asUUID(); + S32 properties = PROPERTY_INVALID; + if(exp.has(PROPERTIES)) + { + properties = exp[PROPERTIES].asInteger(); + } + if(id.notNull() && ((properties & PROPERTY_INVALID) == 0)) + { + fetch(id, true); + } + else + { + sCache.erase(cur); + } + } + } + } + } + + + bool fetch( const LLUUID& key, bool refresh/* = true*/ ) + { + if(!key.isNull() && !isRequestPending(key) && (refresh || sCache.find(key)==sCache.end())) + { + LL_INFOS("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)) + { + sCache[experience_data[EXPERIENCE_ID].asUUID()]=experience_data; + } + else + { + LL_WARNS("ExperienceCache") << ": Ignoring cache insert of experience which is missing " << EXPERIENCE_ID << LL_ENDL; + } + } + + bool get( const LLUUID& key, LLSD& experience_data ) + { + if(key.isNull()) return false; + cache_t::const_iterator it = sCache.find(key); + + if (it != sCache.end()) + { + experience_data = it->second; + return true; + } + + fetch(key); + + return false; + } + + + 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::mapPrivateKeys( 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)) + { + experinceKeyMap[(*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; + + + PrivateKeyMap::const_iterator it=experinceKeyMap.find(private_key); + if(it == experinceKeyMap.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..fb00ea31f0 --- /dev/null +++ b/indra/llmessage/llexperiencecache.h @@ -0,0 +1,94 @@ +/** + * @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 EXPERIENCE_ID = "public_id"; + const std::string OWNER_ID = "owner_id"; + const std::string NAME = "name"; + const std::string PROPERTIES = "properties"; + const std::string EXPIRES = "expiration"; + const std::string DESCRIPTION = "description"; + + // 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; + + + const static F64 DEFAULT_EXPIRATION = 600.0; + + // 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(LLSD& experience_data); + bool get(const LLUUID& key, LLSD& experience_data); + + // If name information is in cache, callback will be called immediately. + void get(const LLUUID& key, callback_slot_t slot); + + const cache_t& getCached(); + + LLUUID getExperienceId(const LLUUID& private_key, bool null_if_not_found=false); + +}; + +#endif // LL_LLEXPERIENCECACHE_H diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 89add3e21f..7d361e4f6a 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -226,6 +226,7 @@ set(viewer_SOURCE_FILES llfloatereditwater.cpp llfloaterenvironmentsettings.cpp llfloaterevent.cpp + llfloaterexperiences.cpp llfloaterfonttest.cpp llfloatergesture.cpp llfloatergodtools.cpp @@ -395,6 +396,7 @@ set(viewer_SOURCE_FILES llpanelclassified.cpp llpanelcontents.cpp llpaneleditwearable.cpp + llpanelexperiences.cpp llpanelface.cpp llpanelgenerictip.cpp llpanelgroup.cpp @@ -809,6 +811,7 @@ set(viewer_HEADER_FILES llfloatereditwater.h llfloaterenvironmentsettings.h llfloaterevent.h + llfloaterexperiences.h llfloaterfonttest.h llfloatergesture.h llfloatergodtools.h @@ -971,6 +974,7 @@ set(viewer_HEADER_FILES llpanelclassified.h llpanelcontents.h llpaneleditwearable.h + llpanelexperiences.h llpanelface.h llpanelgenerictip.h llpanelgroup.h diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index f92274dbbd..912a5e9b7f 100755 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -100,6 +100,7 @@ // Linden library includes #include "llavatarnamecache.h" #include "lldiriterator.h" +#include "llexperiencecache.h" #include "llimagej2c.h" #include "llmemory.h" #include "llprimitive.h" @@ -4409,7 +4410,7 @@ void LLAppViewer::loadNameCache() } void LLAppViewer::saveNameCache() - { +{ // display names cache std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml"); @@ -4417,7 +4418,7 @@ void LLAppViewer::saveNameCache() if(name_cache_stream.is_open()) { LLAvatarNameCache::exportFile(name_cache_stream); -} + } if (!gCacheName) return; @@ -4430,6 +4431,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. @@ -4629,7 +4656,7 @@ void LLAppViewer::idle() // floating throughout the various object lists. // idleNameCache(); - + idleExperienceCache(); idleNetwork(); @@ -5049,6 +5076,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 // @@ -5211,6 +5254,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 cd91ae8b2b..260eb7352e 100755 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -117,6 +117,10 @@ public: void loadNameCache(); void saveNameCache(); + void loadExperienceCache(); + void saveExperienceCache(); + + void removeMarkerFile(bool leave_logout_marker = false); // LLAppViewer testing helpers. @@ -222,6 +226,7 @@ private: void idle(); void idleShutdown(); // update avatar SLID and display name caches + void idleExperienceCache(); void idleNameCache(); void idleNetwork(); diff --git a/indra/newview/llfloaterexperiences.cpp b/indra/newview/llfloaterexperiences.cpp new file mode 100644 index 0000000000..b862b41bba --- /dev/null +++ b/indra/newview/llfloaterexperiences.cpp @@ -0,0 +1,14 @@ +#include "llviewerprecompiledheaders.h" + +#include "llpanelexperiences.h" +#include "llfloaterexperiences.h" + +LLFloaterExperiences::LLFloaterExperiences(const LLSD& data) + :LLFloater(data) +{ +} + +BOOL LLFloaterExperiences::postBuild() +{ + return TRUE; +} diff --git a/indra/newview/llfloaterexperiences.h b/indra/newview/llfloaterexperiences.h new file mode 100644 index 0000000000..1e5f216f8d --- /dev/null +++ b/indra/newview/llfloaterexperiences.h @@ -0,0 +1,45 @@ +/** + * @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 LLFloaterExperiences : + public LLFloater +{ +public: + LLFloaterExperiences(const LLSD& data); + +protected: + /*virtual*/ BOOL postBuild(); + +private: + +}; + +#endif //LL_LLFLOATEREXPERIENCES_H diff --git a/indra/newview/llfloaterscriptexperience.cpp b/indra/newview/llfloaterscriptexperience.cpp new file mode 100644 index 0000000000..497522e821 --- /dev/null +++ b/indra/newview/llfloaterscriptexperience.cpp @@ -0,0 +1,13 @@ +#include "llviewerprecompiledheaders.h" + +#include "llfloaterscriptexperience.h" + +LLFloaterScriptExperience::LLFloaterScriptExperience(const LLSD& data) + :LLFloater(data) +{ +} + +BOOL LLFloaterScriptExperience::postBuild() +{ + return TRUE; +} diff --git a/indra/newview/llfloaterscriptexperience.h b/indra/newview/llfloaterscriptexperience.h new file mode 100644 index 0000000000..c79ff78131 --- /dev/null +++ b/indra/newview/llfloaterscriptexperience.h @@ -0,0 +1,45 @@ +/** + * @file llfloaterscriptexperience.h + * @brief LLFloaterScriptExperience 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_LLFLOATERSCRIPTEXPERIENCE_H +#define LL_LLFLOATERSCRIPTEXPERIENCE_H + +#include "llfloater.h" + +class LLFloaterScriptExperience : + public LLFloater +{ +public: + LLFloaterScriptExperience(const LLSD& data); + +protected: + /*virtual*/ BOOL postBuild(); + +private: + +}; + +#endif //LL_LLFLOATERSCRIPTEXPERIENCE_H diff --git a/indra/newview/llpanelexperiences.cpp b/indra/newview/llpanelexperiences.cpp new file mode 100644 index 0000000000..617ceef615 --- /dev/null +++ b/indra/newview/llpanelexperiences.cpp @@ -0,0 +1,302 @@ +#include "llviewerprecompiledheaders.h" + + +#include "llpanelprofile.h" +#include "lluictrlfactory.h" +#include "llexperiencecache.h" +#include "llagent.h" + +#include "llpanelexperiences.h" + + +static LLRegisterPanelClassWrapper<LLPanelExperiences> register_experiences_panel("experiences_panel"); + + +LLPanelExperiences::LLPanelExperiences( ) + : mExperiencesList(NULL), + mExperiencesAccTab(NULL), + mProfilePanel(NULL), + mPanelExperienceInfo(NULL), + mNoExperiences(false) +{ + +} + +void* LLPanelExperiences::create( void* data ) +{ + return new LLPanelExperiences(); +} + +void ExperienceResult(LLHandle<LLPanelExperiences> panel, const LLSD& experience) +{ + LLPanelExperiences* experiencePanel = panel.get(); + if(experiencePanel) + { + experiencePanel->addExperienceInfo(experience); + } +} + +class LLExperienceListResponder : public LLHTTPClient::Responder +{ +public: + LLExperienceListResponder(const LLHandle<LLPanelExperiences>& parent):mParent(parent) + { + } + + LLHandle<LLPanelExperiences> mParent; + + virtual void result(const LLSD& content) + { + if(mParent.isDead()) + return; + + LLSD experiences = content["experiences"]; + LLSD::array_const_iterator it = experiences.beginArray(); + for( /**/ ; it != experiences.endArray(); ++it) + { + LLUUID public_key = it->asUUID(); + + LLExperienceCache::get(public_key, boost::bind(ExperienceResult, mParent, _1)); + } + } +}; + +void LLPanelExperiences::addExperienceInfo(const LLSD& experience) +{ + LLExperienceItem* item = new LLExperienceItem(); + if(experience.has(LLExperienceCache::NAME)) + { + item->setExperienceName(experience[LLExperienceCache::NAME].asString()); + } + else if(experience.has("error")) + { + item->setExperienceName(experience["error"].asString()); + } + + if(experience.has(LLExperienceCache::DESCRIPTION)) + { + item->setExperienceDescription(experience[LLExperienceCache::DESCRIPTION].asString()); + } + + mExperiencesList->addItem(item); + +} + + +BOOL LLPanelExperiences::postBuild( void ) +{ + mExperiencesList = getChild<LLFlatListView>("experiences_list"); + if(hasString("no_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("no_experiences")); + } + + + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string lookup_url=region->getCapability("GetExperiences"); + if(!lookup_url.empty()) + { + LLHTTPClient::get(lookup_url, new LLExperienceListResponder(getDerivedHandle<LLPanelExperiences>())); + } + } + + mExperiencesAccTab = getChild<LLAccordionCtrlTab>("tab_experiences"); + mExperiencesAccTab->setDropDownStateChangedCallback(boost::bind(&LLPanelExperiences::onAccordionStateChanged, this, mExperiencesAccTab)); + mExperiencesAccTab->setDisplayChildren(true); + + return TRUE; +} + +void LLPanelExperiences::onOpen( const LLSD& key ) +{ + LLPanel::onOpen(key); +} + +void LLPanelExperiences::onClosePanel() +{ + if (mPanelExperienceInfo) + { + onPanelExperienceClose(mPanelExperienceInfo); + } +} + +void LLPanelExperiences::updateData() +{ + if(isDirty()) + { + mNoExperiences = false; + } +} + +LLExperienceItem* LLPanelExperiences::getSelectedExperienceItem() +{ + LLPanel* selected_item = mExperiencesList->getSelectedItem(); + if (!selected_item) return NULL; + + return dynamic_cast<LLExperienceItem*>(selected_item); +} + +void LLPanelExperiences::setProfilePanel( LLPanelProfile* profile_panel ) +{ + mProfilePanel = profile_panel; +} + +void LLPanelExperiences::onListCommit( const LLFlatListView* f_list ) +{ + if(f_list == mExperiencesList) + { + mExperiencesList->resetSelection(true); + } + else + { + llwarns << "Unknown list" << llendl; + } + + //updateButtons(); +} + +void LLPanelExperiences::onAccordionStateChanged( const LLAccordionCtrlTab* acc_tab ) +{ + if(!mExperiencesAccTab->getDisplayChildren()) + { + mExperiencesList->resetSelection(true); + } + +} + +void LLPanelExperiences::openExperienceInfo() +{ + LLSD selected_value = mExperiencesList->getSelectedValue(); + if(selected_value.isUndefined()) + { + return; + } + + LLExperienceItem* experience = (LLExperienceItem*)mExperiencesList->getSelectedItem(); + + createExperienceInfoPanel(); + + LLSD params; + params["experience_name"] = experience->getExperienceName(); + params["experience_desc"] = experience->getExperienceDescription(); + + getProfilePanel()->openPanel(mPanelExperienceInfo, params); + +} + + +void LLPanelExperiences::createExperienceInfoPanel() +{ + if(!mPanelExperienceInfo) + { + mPanelExperienceInfo = LLPanelExperienceInfo::create(); + mPanelExperienceInfo->setExitCallback(boost::bind(&LLPanelExperiences::onPanelExperienceClose, this, mPanelExperienceInfo)); + mPanelExperienceInfo->setVisible(FALSE); + } +} + +void LLPanelExperiences::onPanelExperienceClose( LLPanel* panel ) +{ + getProfilePanel()->closePanel(panel); +} + +LLPanelProfile* LLPanelExperiences::getProfilePanel() +{ + llassert_always(NULL != mProfilePanel); + + return mProfilePanel; +} + + + + + + + + + + + +LLExperienceItem::LLExperienceItem() +{ + buildFromFile("panel_experience_info.xml"); +} + +void LLExperienceItem::init( LLSD* experience_data ) +{ + if(experience_data) + { + setExperienceDescription(experience_data->has(LLExperienceCache::DESCRIPTION)?(*experience_data)[LLExperienceCache::DESCRIPTION].asString() : std::string()); + setExperienceName(experience_data->has(LLExperienceCache::NAME)?(*experience_data)[LLExperienceCache::NAME].asString() : std::string()); + } +} + +void LLExperienceItem::setExperienceDescription( const std::string& val ) +{ + mExperienceDescription = val; + getChild<LLUICtrl>("experience_desc")->setValue(val); +} + +void LLExperienceItem::setExperienceName( const std::string& val ) +{ + mExperienceName = val; + getChild<LLUICtrl>("experience_name")->setValue(val); +} + +BOOL LLExperienceItem::postBuild() +{ + return TRUE; +} + +void LLExperienceItem::update() +{ + +} + +void LLExperienceItem::processProperties( void* data, EAvatarProcessorType type ) +{ + +} + +LLExperienceItem::~LLExperienceItem() +{ + +} + + +void LLPanelExperienceInfo::setExperienceName( const std::string& name ) +{ + getChild<LLUICtrl>("experience_name")->setValue(name); +} + +void LLPanelExperienceInfo::setExperienceDesc( const std::string& desc ) +{ + getChild<LLUICtrl>("experience_desc")->setValue(desc); +} + +void LLPanelExperienceInfo::onOpen( const LLSD& key ) +{ + setExperienceName(key["experience_name"]); + setExperienceDesc(key["experience_desc"]); + + /* + LLAvatarPropertiesProcessor::getInstance()->addObserver( + getAvatarId(), this); + LLAvatarPropertiesProcessor::getInstance()->sendPickInfoRequest( + getAvatarId(), getPickId()); + */ +} + +LLPanelExperienceInfo* LLPanelExperienceInfo::create() +{ + LLPanelExperienceInfo* panel = new LLPanelExperienceInfo(); + panel->buildFromFile("panel_experience_info.xml"); + return panel; +} + +void LLPanelExperienceInfo::setExitCallback( const commit_callback_t& cb ) +{ + getChild<LLButton>("back_btn")->setClickedCallback(cb); +} diff --git a/indra/newview/llpanelexperiences.h b/indra/newview/llpanelexperiences.h new file mode 100644 index 0000000000..1fe3f6ae1d --- /dev/null +++ b/indra/newview/llpanelexperiences.h @@ -0,0 +1,119 @@ +/** + * @file llpanelpicks.h + * @brief LLPanelPicks 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$ + */ + +#ifndef LL_LLPANELEXPERIENCES_H +#define LL_LLPANELEXPERIENCES_H + +#include "llaccordionctrltab.h" +#include "llflatlistview.h" +#include "llpanelavatar.h" + +class LLExperienceItem; +class LLPanelProfile; + +class LLPanelExperienceInfo + : public LLPanel +{ +public: + static LLPanelExperienceInfo* create(); + + void onOpen(const LLSD& key); + void setExperienceName( const std::string& name ); + void setExperienceDesc( const std::string& desc ); + + + virtual void setExitCallback(const commit_callback_t& cb); +}; + + +class LLPanelExperiences + : public LLPanel /*LLPanelProfileTab*/ +{ +public: + LLPanelExperiences(); + + static void* create(void* data); + + /*virtual*/ BOOL postBuild(void); + + /*virtual*/ void onOpen(const LLSD& key); + + /*virtual*/ void onClosePanel(); + + void updateData(); + + LLExperienceItem* getSelectedExperienceItem(); + + void setProfilePanel(LLPanelProfile* profile_panel); + void addExperienceInfo(const LLSD& experience); +protected: + + void onListCommit(const LLFlatListView* f_list); + void onAccordionStateChanged(const LLAccordionCtrlTab* acc_tab); + + + void openExperienceInfo(); + void createExperienceInfoPanel(); + void onPanelExperienceClose(LLPanel* panel); + LLPanelProfile* getProfilePanel(); +private: + LLFlatListView* mExperiencesList; + LLAccordionCtrlTab* mExperiencesAccTab; + LLPanelProfile* mProfilePanel; + LLPanelExperienceInfo* mPanelExperienceInfo; + bool mNoExperiences; +}; + + +class LLExperienceItem + : public LLPanel + //, public LLAvatarPropertiesObserver +{ +public: + LLExperienceItem(); + ~LLExperienceItem(); + + void init(LLSD* experience_data); + /*virtual*/ BOOL postBuild(); + void update(); + + /*virtual*/ void processProperties(void* data, EAvatarProcessorType type); + + void setCreatorID(const LLUUID& val) { mCreatorID = val; } + void setExperienceDescription(const std::string& val); + void setExperienceName(const std::string& val); + + const LLUUID& getCreatorID() const { return mCreatorID; } + const std::string& getExperienceName() const { return mExperienceName; } + const std::string& getExperienceDescription() const { return mExperienceDescription; } + +protected: + LLUUID mCreatorID; + + std::string mExperienceName; + std::string mExperienceDescription; +}; +#endif // LL_LLPANELEXPERIENCES_H diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 968a912ea2..bd8f65e767 100755 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -86,6 +86,7 @@ #include "lltrans.h" #include "llviewercontrol.h" #include "llappviewer.h" +#include "llexperiencecache.h" const std::string HELLO_LSL = "default\n" @@ -118,6 +119,57 @@ static bool have_script_upload_cap(LLUUID& object_id) return object && (! object->getRegion()->getCapability("UpdateScriptTask").empty()); } +class ExperienceAssociationResponder : public LLHTTPClient::Responder +{ +public: + ExperienceAssociationResponder(const LLUUID& parent):mParent(parent) + { + } + + LLUUID mParent; + + virtual void result(const LLSD& content) + { + + LLLiveLSLEditor* scriptCore = LLFloaterReg::findTypedInstance<LLLiveLSLEditor>("preview_scriptedit", mParent); + + if(!scriptCore) + return; + + LLUUID id; + if(content.has("experience")) + { + id=content["experience"].asUUID(); + } + scriptCore->setAssociatedExperience(id); + } + + virtual void error(U32 status, const std::string& reason) + { + llinfos << "Failed to look up associated script: " << status << ": " << reason << llendl; + } + +}; + +class ExperienceResponder : public LLHTTPClient::Responder +{ +public: + ExperienceResponder(const LLHandle<LLScriptEdCore>& parent):mParent(parent) + { + } + + LLHandle<LLScriptEdCore> mParent; + + virtual void result(const LLSD& content) + { + LLScriptEdCore* scriptCore = mParent.get(); + if(!scriptCore) + return; + + scriptCore->setExperienceIds(content["experience_ids"]); + } +}; + /// --------------------------------------------------------------------------- /// LLLiveLSLFile /// --------------------------------------------------------------------------- @@ -374,12 +426,50 @@ LLScriptEdCore::~LLScriptEdCore() delete mLiveFile; } +void LLScriptEdCore::experienceChanged() +{ + if(mAssociatedExperience != mExperiences->getSelectedValue().asUUID()) + { + enableSave(TRUE); + getChildView("Save_btn")->setEnabled(TRUE); + mAssociatedExperience = mExperiences->getSelectedValue().asUUID(); + } +} + +void LLScriptEdCore::onToggleExperience( LLUICtrl *ui, void* userdata ) +{ + LLScriptEdCore* self = (LLScriptEdCore*)userdata; + + LLUUID id; + if(self->mExperienceEnabled->get()) + { + if(self->mAssociatedExperience.isNull()) + { + id=self->mExperienceIds.beginArray()->asUUID(); + } + } + + if(id != self->mAssociatedExperience) + { + self->enableSave(TRUE); + } + self->setAssociatedExperience(id); +} + BOOL LLScriptEdCore::postBuild() { mErrorList = getChild<LLScrollListCtrl>("lsl errors"); mFunctions = getChild<LLComboBox>( "Insert..."); + mExperiences = getChild<LLComboBox>("Experiences..."); + mExperiences->setCommitCallback(boost::bind(&LLScriptEdCore::experienceChanged, this)); + + mExperienceEnabled = getChild<LLCheckBoxCtrl>("enable_xp"); + + childSetCommitCallback("enable_xp", onToggleExperience, this); + + childSetCommitCallback("Insert...", &LLScriptEdCore::onBtnInsertFunction, this); mEditor = getChild<LLViewerTextEditor>("Script Editor"); @@ -389,6 +479,10 @@ BOOL LLScriptEdCore::postBuild() childSetAction("Edit_btn", boost::bind(&LLScriptEdCore::openInExternalEditor, this)); initMenu(); + + + + requestExperiences(); std::vector<std::string> funcs; @@ -1191,6 +1285,107 @@ bool LLScriptEdCore::enableLoadFromFileMenu(void* userdata) return (self && self->mEditor) ? self->mEditor->canLoadOrSaveToFile() : FALSE; } +LLUUID LLScriptEdCore::getAssociatedExperience()const +{ + return mAssociatedExperience; +} + +void LLScriptEdCore::setExperienceIds( const LLSD& experience_ids ) +{ + mExperienceIds=experience_ids; + updateExperiencePanel(); +} + + +void LLScriptEdCore::updateExperiencePanel() +{ + BOOL editable = mEditor->getEnabled(); + + if(mAssociatedExperience.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(editable); + mExperienceEnabled->set(TRUE); + mExperiences->setVisible(TRUE); + getChild<LLButton>("view_profile")->setVisible(TRUE); + buildExperienceList(); + } +} + +void LLScriptEdCore::addExperienceInfo(const LLSD& experience, BOOL enabled) +{ + LLUUID id = experience[LLExperienceCache::EXPERIENCE_ID].asUUID(); + EAddPosition position = (id == mAssociatedExperience)?ADD_TOP:ADD_BOTTOM; + LLScrollListItem* item=mExperiences->add(experience[LLExperienceCache::NAME], id, position ); + if(!enabled) + { + item->setEnabled(FALSE); + } +} + +void LLScriptEdCore::buildExperienceList() +{ + mExperiences->clear(); + bool foundAssociated=false; + for(LLSD::array_const_iterator it = mExperienceIds.beginArray(); it != mExperienceIds.endArray(); ++it) + { + LLUUID id = it->asUUID(); + foundAssociated |= (id == mAssociatedExperience); + LLExperienceCache::get(id, boost::bind(&LLScriptEdCore::addExperienceInfo, this, _1, TRUE)); + } + + if(!foundAssociated ) + { + LLExperienceCache::get(mAssociatedExperience, boost::bind(&LLScriptEdCore::addExperienceInfo, this, _1, FALSE)); + } + +} + + +void LLScriptEdCore::setAssociatedExperience( const LLUUID& experience_id ) +{ + mAssociatedExperience = experience_id; + updateExperiencePanel(); +} + + + +void LLScriptEdCore::requestExperiences() +{ + if (!mEditor->getEnabled()) + { + 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<LLScriptEdCore>())); + } + } +} + + + /// --------------------------------------------------------------------------- /// LLScriptEdContainer /// --------------------------------------------------------------------------- @@ -1860,7 +2055,7 @@ void LLLiveLSLEditor::loadAsset() LLHost host(object->getRegion()->getIP(), object->getRegion()->getPort()); gMessageSystem->sendReliable(host); - */ + */ } } else @@ -1893,11 +2088,15 @@ void LLLiveLSLEditor::onLoadComplete(LLVFS *vfs, const LLUUID& asset_id, lldebugs << "LLLiveLSLEditor::onLoadComplete: got uuid " << asset_id << llendl; LLUUID* xored_id = (LLUUID*)user_data; - + + LLLiveLSLEditor* instance = LLFloaterReg::findTypedInstance<LLLiveLSLEditor>("preview_scriptedit", *xored_id); if(instance ) { + instance->fetchAssociatedExperience(asset_id); + + if( LL_ERR_NOERR == status ) { instance->loadScriptText(vfs, asset_id, type); @@ -2146,8 +2345,8 @@ void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) mPendingUploads++; 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) { @@ -2155,11 +2354,7 @@ void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) } } -void LLLiveLSLEditor::uploadAssetViaCaps(const std::string& url, - const std::string& filename, - const LLUUID& task_id, - const LLUUID& item_id, - BOOL is_running) +void LLLiveLSLEditor::uploadAssetViaCaps( const std::string& url, const std::string& filename, const LLUUID& task_id, const LLUUID& item_id, BOOL is_running, const LLUUID& experience_public_id ) { llinfos << "Update Task Inventory via capability " << url << llendl; LLSD body; @@ -2167,6 +2362,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)); } @@ -2347,6 +2543,25 @@ void LLLiveLSLEditor::onSaveBytecodeComplete(const LLUUID& asset_uuid, void* use delete data; } +void LLLiveLSLEditor::fetchAssociatedExperience(const LLUUID& asset_id) +{ + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string lookup_url=region->getCapability("GetMetadata"); + if(!lookup_url.empty()) + { + LLSD request; + request["asset-id"]=asset_id; + LLSD fields; + fields.append("experience"); + request["fields"] = fields; + LLHTTPClient::post(lookup_url, request, new ExperienceAssociationResponder(getKey())); + } + } +} + + BOOL LLLiveLSLEditor::canClose() { return (mScriptEd->canClose()); @@ -2418,3 +2633,11 @@ BOOL LLLiveLSLEditor::monoChecked() const } return FALSE; } + +void LLLiveLSLEditor::setAssociatedExperience( const LLUUID& experience_id ) +{ + if(mScriptEd) + { + mScriptEd->setAssociatedExperience(experience_id); + } +} diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 7563cecd9d..9578396f91 100755 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -100,12 +100,20 @@ public: static void onBtnInsertSample(void*); static void onBtnInsertFunction(LLUICtrl*, void*); static void onBtnLoadFromFile(void*); - static void onBtnSaveToFile(void*); + static void onBtnSaveToFile(void*); + static void onToggleExperience(LLUICtrl *ui, void* userdata); static bool enableSaveToFileMenu(void* userdata); static bool enableLoadFromFileMenu(void* userdata); virtual bool hasAccelerators() const { return true; } + void addExperienceInfo( const LLSD& experience, BOOL enabled ); + void setExperienceIds(const LLSD& experience_ids); + void buildExperienceList(); + LLUUID getAssociatedExperience()const; + + void setAssociatedExperience( const LLUUID& experience_id ); + void updateExperiencePanel(); private: void onBtnHelp(); @@ -120,6 +128,10 @@ private: void enableSave(BOOL b) {mEnableSave = b;} + void requestExperiences(); + void experienceChanged(); + void addAssociatedExperience(const LLSD& experience); + protected: void deleteBridges(); void setHelpPage(const std::string& help_string); @@ -133,8 +145,10 @@ private: void (*mLoadCallback)(void* userdata); void (*mSaveCallback)(void* userdata, BOOL close_after_save); void (*mSearchReplaceCallback) (void* userdata); - void* mUserdata; + void* mUserdata; LLComboBox *mFunctions; + LLComboBox *mExperiences; + LLCheckBoxCtrl *mExperienceEnabled; BOOL mForceClose; LLPanel* mCodePanel; LLScrollListCtrl* mErrorList; @@ -146,6 +160,8 @@ private: BOOL mEnableSave; BOOL mHasScriptData; LLLiveLSLFile* mLiveFile; + LLUUID mAssociatedExperience; + LLSD mExperienceIds; LLScriptEdContainer* mContainer; // parent view }; @@ -227,7 +243,9 @@ public: /*virtual*/ BOOL postBuild(); - void setIsNew() { mIsNew = TRUE; } + void setIsNew() { mIsNew = TRUE; } + void setAssociatedExperience( const LLUUID& experience_id ); + void fetchAssociatedExperience(const LLUUID& asset_id); private: virtual BOOL canClose(); @@ -237,11 +255,7 @@ private: virtual void loadAsset(); void loadAsset(BOOL is_new); /*virtual*/ void saveIfNeeded(bool sync = true); - void uploadAssetViaCaps(const std::string& url, - const std::string& filename, - const LLUUID& task_id, - const LLUUID& item_id, - BOOL is_running); + void uploadAssetViaCaps(const std::string& url, const std::string& filename, const LLUUID& task_id, const LLUUID& item_id, BOOL is_running, const LLUUID& experience_public_id); void uploadAssetLegacy(const std::string& filename, LLViewerObject* object, const LLTransactionID& tid, @@ -282,7 +296,7 @@ private: S32 mPendingUploads; BOOL getIsModifiable() const { return mIsModifiable; } // Evaluated on load assert - + LLCheckBoxCtrl* mMonoCheckbox; BOOL mIsModifiable; }; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index cff3a7e02a..3f8f5e282a 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" @@ -1401,6 +1402,9 @@ bool idle_startup() LLStartUp::initNameCache(); display_startup(); + LLStartUp::initExperienceCache(); + display_startup(); + // update the voice settings *after* gCacheName initialization // so that we can construct voice UI that relies on the name cache LLVoiceClient::getInstance()->updateSettings(); @@ -2814,6 +2818,13 @@ void LLStartUp::initNameCache() LLAvatarNameCache::setUseDisplayNames(gSavedSettings.getBOOL("UseDisplayNames")); } + +void LLStartUp::initExperienceCache() +{ + LLAppViewer::instance()->loadExperienceCache(); + LLExperienceCache::initClass(); +} + void LLStartUp::cleanupNameCache() { LLAvatarNameCache::cleanupClass(); @@ -3510,3 +3521,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 760e38890b..00e03bcda6 100755 --- a/indra/newview/llstartup.h +++ b/indra/newview/llstartup.h @@ -91,6 +91,7 @@ public: static void fontInit(); static void initNameCache(); + static void initExperienceCache(); static void cleanupNameCache(); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index c6b28b9e5e..c6296a20d7 100755 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -58,6 +58,7 @@ #include "llfloatereditsky.h" #include "llfloatereditwater.h" #include "llfloaterenvironmentsettings.h" +#include "llfloaterexperiences.h" #include "llfloaterevent.h" #include "llfloaterdestinations.h" #include "llfloaterfonttest.h" @@ -207,7 +208,8 @@ void LLViewerFloaterReg::registerFloaters() 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("experiences", "floater_experiences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterExperiences>); + 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/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 8422708add..adc346529e 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1596,7 +1596,11 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) } capabilityNames.append("GetDisplayNames"); - capabilityNames.append("GetMesh"); + capabilityNames.append("GetExperiences"); + capabilityNames.append("GetExperienceInfo"); + capabilityNames.append("GetCreatorExperiences"); + capabilityNames.append("GetMesh"); + 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 1a050800b4..15d5dd63c7 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 @@ -2037,7 +2038,7 @@ void LLVOAvatar::idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time) idleUpdateBelowWater(); // wind effect uses this idleUpdateWindEffect(); } - + idleUpdateNameTag( root_pos_last ); idleUpdateRenderCost(); } 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..57541c8b35 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_experiences.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> + +<floater + positioning="cascading" + can_close="true" + can_resize="true" + height="400" + help_topic="sidebar_experiences" + min_height="300" + min_width="300" + layout="topleft" + name="floater_experiences" + save_rect="false" + single_instance="true" + reuse_instance="false" + title="EXPERIENCES" + width="400"> + <panel + top="3" + left="3" + layout="topleft" + right="-3" + follows="all" + height="300" + class="experiences_panel" + filename="panel_experiences.xml" + > + </panel> +</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..562936ed80 100755 --- a/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml @@ -7,7 +7,7 @@ height="580" layout="topleft" min_height="271" - min_width="290" + min_width="328" name="script ed float" help_topic="script_ed_float" save_rect="true" diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index b01c3067ff..752734d7bd 100755 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -53,6 +53,14 @@ parameter="" /> </menu_item_call> <menu_item_call + label="Experiences..." + name="Experiences" + shortcut="control|E"> + <menu_item_call.on_click + function="Floater.ToggleOrBringToFront" + parameter="experiences"/> + </menu_item_call> + <menu_item_call label="Places..." name="Places"> <menu_item_call.on_click 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..47f366d857 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_experience_info.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + bg_opaque_color="DkGray2" + background_visible="true" + background_opaque="true" + fit_parent="true" + follows="all" + height="120" + label="Experiences" + layout="topleft" + left="0" + name="panel_experience_info" + top_pad="0"> + <text + follows="top|left|right" + font="SansSerifHugeBold" + height="26" + layout="topleft" + left_pad="4" + name="title" + text_color="White" + top="2" + value="Experience Info" + use_ellipses="true" + right="-3"/> + <text + follows="top|left|right" + font="SansSerifBig" + height="20" + layout="topleft" + left_pad="4" + name="name_label" + text_color="White" + left="8" + top_delta="28" + value="Name" + use_ellipses="true" + right="-3" /> + <text + follows="top|left|right" + font="SansSerif" + height="20" + layout="topleft" + left_pad="8" + name="experience_name" + text_color="White" + left="16" + top_delta="22" + value="[loading...]" + use_ellipses="true" + right="-3" /> + <text + follows="top|left|right" + font="SansSerifBig" + height="20" + left="8" + layout="topleft" + left_pad="4" + name="desc_label" + text_color="White" + top_delta="22" + value="Description" + use_ellipses="true" + right="-3" /> + <expandable_text + follows="top|left|right" + font="SansSerif" + height="20" + layout="topleft" + left_pad="8" + name="experience_desc" + text_color="White" + left="16" + top_delta="22" + value="[loading...]" + use_ellipses="true" + right="-3" + word_wrap="true" /> +</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..47a3005aae --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_experiences.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> + +<panel + layout="topleft" + top="3" + left="3" + right="-3" + bottom="-3" + label="Experiences" + follows="all"> + <string + name="no_experiences" + value="No experiences."/> + + <accordion + fit_parent="true" + layout="topleft" + top="0" + left="3" + right="-3" + bottom="-3" + single_expansion="true" + follows="all"> + <accordion_tab + name="tab_experiences" + layout="topleft" + top="0" + left="0" + right="-3" + title="Experiences" + follows="all"> + <flat_list_view + name="experiences_list" + layout="topleft" + top="0" + left="0" + follows="all"/> + </accordion_tab> + </accordion> +</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 765b07ed8b..18a8ddc1bf 100755 --- a/indra/newview/skins/default/xui/en/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/en/panel_script_ed.xml @@ -1,207 +1,248 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <panel - 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" /> - </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="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> - <text_editor + bevel_style="none" + border_style="line" + follows="left|top|right|bottom" + height="515" + 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> + <panel.string + name="experience_enabled"> + Uncheck to remove the current experience + </panel.string> + <panel.string + name="no_experiences"> + You are not authorized for any experiences + </panel.string> + <panel.string + name="add_experiences"> + Select to add an experience + </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> + <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="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> + <text_editor left="0" - type="string" - length="1" - follows="left|top|right|bottom" - font="Monospace" - height="376" - ignore_tab="false" - layout="topleft" - max_length="65536" - name="Script Editor" - text_readonly_color="DkGray" - width="487" - show_line_numbers="true" - enable_tooltip_paste="true" - word_wrap="true"> - Loading... - </text_editor> - <scroll_list + type="string" + length="1" + follows="left|top|right|bottom" + font="Monospace" + height="346" + ignore_tab="false" + layout="topleft" + max_length="65536" + name="Script Editor" + text_readonly_color="DkGray" + width="482" + show_line_numbers="true" + enable_tooltip_paste="true" + word_wrap="true"> + Loading... + </text_editor> + <scroll_list top_pad="10" left="0" - follows="left|right|bottom" - height="60" - layout="topleft" - name="lsl errors" - width="487" /> - <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..." - width="128" /> - <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" - right="400" - name="Edit_btn" - width="81" /> + follows="left|right|bottom" + height="60" + layout="topleft" + name="lsl errors" + width="482" /> + <text + follows="left|bottom" + height="12" + layout="topleft" + left="11" + name="line_col" + width="128" /> + <combo_box + follows="left|bottom" + height="23" + label="Insert..." + layout="topleft" + name="Insert..." + width="128" + left="0"/> + <button + follows="right|bottom" + height="23" + label="Save" + label_selected="Save" + layout="topleft" + top_pad="-35" + right="482" + name="Save_btn" + width="81" /> + <button + enabled="false" + follows="right|bottom" + height="23" + label="Edit..." + layout="topleft" + top_pad="-23" + right="396" + name="Edit_btn" + width="81" /> + <check_box width="130" + height="21" + enabled="false" + left="0" + top="490" + layout="topleft" + follows="bottom|left" + label="Use Experience:" + name="enable_xp"/> + <combo_box + label="" + top_pad="-23" + left="135" + right="454" + layout="topleft" + follows="left|bottom|right" + visible="false" + name="Experiences..."/> + <button label=">" + name="view_profile" + height="23" + width="23" + right="482" + layout="topleft" + top_pad="-23" + follows="right" + visible="false"/> + </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 89aef57cca..65acc14b6a 100755 --- a/indra/newview/skins/default/xui/en/role_actions.xml +++ b/indra/newview/skins/default/xui/en/role_actions.xml @@ -187,4 +187,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 Creator" + longdescription="Members in a role with this ability can create scripts for an experience." + name="experience creator" + value ="50" /> + </action_set> </role_actions> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index f7b33b0a4a..6fe847df50 100755 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3917,6 +3917,10 @@ 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> + + <!-- 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. |