diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 473498833e8..5fe88703375 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -19,6 +19,7 @@ Issue Link: Please ensure the following before requesting review: - [ ] I have provided a clear title and detailed description for this pull request. +- [ ] If useful, I have included media such as screenshots and video to show off my changes. - [ ] The PR is linked to a relevant issue with sufficient context. - [ ] I have tested the changes locally and verified they work as intended. - [ ] All new and existing tests pass. diff --git a/.github/workflows/qatest.yaml b/.github/workflows/qatest.yaml index 5d8894a3f49..4e10900441b 100644 --- a/.github/workflows/qatest.yaml +++ b/.github/workflows/qatest.yaml @@ -46,11 +46,10 @@ jobs: runner: qa-dan-asus artifact: Windows-installer install-path: 'C:\viewer-automation-main' - # Commented out until mac runner is available - # - os: mac - # runner: qa-mac-atlas - # artifact: Mac-installer - # install-path: '$HOME/Documents/viewer-automation' + - os: mac + runner: qa-mac-atlas + artifact: macOS-installer + install-path: '$HOME/Documents/viewer-automation' fail-fast: false runs-on: [self-hosted, "${{ matrix.runner }}"] @@ -498,11 +497,13 @@ jobs: mkdir -p "$MOUNT_POINT" # Mount the DMG - hdiutil attach "${{ env.INSTALLER_PATH }}" -mountpoint "$MOUNT_POINT" -nobrowse + hdiutil attach "$INSTALLER_PATH" -mountpoint "$MOUNT_POINT" -nobrowse echo "✅ DMG mounted at $MOUNT_POINT" - # Find the app in the mounted DMG + echo "Installing application to default location from DMG..." + + # Find the .app bundle in the DMG APP_PATH=$(find "$MOUNT_POINT" -name "*.app" -type d | head -1) if [ -z "$APP_PATH" ]; then @@ -510,18 +511,40 @@ jobs: exit 1 fi - echo "Installing application to Applications folder..." + APP_NAME=$(basename "$APP_PATH") + DEST_PATH="/Applications/$APP_NAME" + + # Handle existing installation + if [ -d "$DEST_PATH" ]; then + echo "Found existing installation at: $DEST_PATH" + echo "Moving existing installation to trash..." + + # Move to trash instead of force removing + TRASH_PATH="$HOME/.Trash/$(date +%Y%m%d_%H%M%S)_$APP_NAME" + mv "$DEST_PATH" "$TRASH_PATH" || { + echo "⚠️ Could not move to trash, trying direct removal..." + rm -rf "$DEST_PATH" || { + echo "❌ Could not remove existing installation" + echo "Please manually remove: $DEST_PATH" + exit 1 + } + } + + echo "✅ Existing installation handled successfully" + fi - # Copy the app to the Applications folder (or specified install path) - cp -R "$APP_PATH" "${{ matrix.install-path }}" + # Copy the .app to /Applications + echo "Copying app from: $APP_PATH" + echo "To destination: /Applications/" + cp -R "$APP_PATH" /Applications/ # Verify the app was copied successfully - if [ ! -d "${{ matrix.install-path }}/$(basename "$APP_PATH")" ]; then - echo "❌ Error: Failed to install application to ${{ matrix.install-path }}!" + if [ ! -d "$DEST_PATH" ]; then + echo "❌ Error: Failed to install application to /Applications!" exit 1 fi - echo "✅ Application installed successfully to ${{ matrix.install-path }}" + echo "✅ Application installed successfully to /Applications" # Save mount point for cleanup echo "MOUNT_POINT=$MOUNT_POINT" >> $GITHUB_ENV diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db2225c9fd9..9f38432ff73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,6 +8,7 @@ changes. ## Table of contents - [Communication](#communication) +- [What to work on](#what-to-work-on) - [Reporting bugs and requesting features](#reporting-bugs-and-requesting-features) - [Contributing pull requests](#contributing-pull-requests) @@ -35,6 +36,16 @@ developer-to-developer or support. discussion between viewer maintainers. - Our [discord channel](https://discord.com/channels/677442248157167619/1357059883400167585) is available for real-time discussion. +## What to work on + +If you're looking for ways to contribute code, here are some ways to get involved: + +- Explore existing issues on the [GitHub issue tracker](https://github.com/secondlife/viewer/issues) to find known problems, bugs, or enhancement discussions. +- File new issues if you’ve discovered a bug or have a specific idea to propose. If your idea is user-facing, consider submitting it through feedback.secondlife.com so it can reach a broader audience and be prioritized by Linden Lab staff. +- Look for the [help wanted](https://github.com/secondlife/viewer/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22) label. These are tasks the core maintainers have specifically identified as good candidates for community help. +- Talk to maintainers before starting significant work. Even if an issue exists, discussing your approach first ensures alignment with ongoing efforts and increases the likelihood your pull request will be accepted. + +Collaboration is essential. We encourage contributors to work closely with the Second Life engineering team and other developers to keep work consistent and maintainable. ## Reporting bugs and requesting features diff --git a/autobuild.xml b/autobuild.xml index 777d98640f9..f792bac7895 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2332,59 +2332,33 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors threejs + copyright + Copyright © 2010-2021 three.js authors + license + MIT + license_file + LICENSES/THREEJS_LICENSE.txt + name + threejs platforms - darwin64 - - archive - - hash - cfed00d8ea7265c035c2d86a234b28efb0b23756 - hash_algorithm - sha1 - url - https://github.com/secondlife/3p-three_js/releases/download/v0.132.2-b8f6746/threejs-0.132.2-darwin64-b8f6746.tar.zst - - name - darwin64 - - linux64 - - archive - - hash - 9de1295b157c9913c28be81ff933c73493ecc132 - hash_algorithm - sha1 - url - https://github.com/secondlife/3p-three_js/releases/download/v0.132.2-b8f6746/threejs-0.132.2-linux64-b8f6746.tar.zst - - - windows64 + common archive hash - 4141710fccbd1ea2b3b53d00e189bdfa2ee9d441 + 982c0fa427458082ea9e3cb9603904210732b64e hash_algorithm sha1 url - https://github.com/secondlife/3p-three_js/releases/download/v0.132.2-b8f6746/threejs-0.132.2-windows64-b8f6746.tar.zst + https://github.com/secondlife/3p-three_js/releases/download/v0.132.2-5da28d9/threejs-0.132.2-common-8454371083.tar.zst name - windows64 + common - license - MIT - license_file - LICENSES/THREEJS_LICENSE.txt - copyright - Copyright © 2010-2021 three.js authors version 0.132.2 - name - threejs tinygltf @@ -2896,6 +2870,64 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors version 1.0.9-5e8947c + discord_sdk + + platforms + + windows64 + + archive + + creds + github + hash + e11571bf76b27d15c244069988ae372eaa5afae9 + hash_algorithm + sha1 + url + https://api.github.com/repos/secondlife/3p-discord-sdk/releases/assets/279333720 + + name + windows64 + + darwin64 + + archive + + creds + github + hash + dc21df8b051c425163acf3eff8f06e32f407c9e0 + hash_algorithm + sha1 + url + https://api.github.com/repos/secondlife/3p-discord-sdk/releases/assets/279333706 + + name + darwin64 + + + license + discord_sdk + license_file + LICENSES/discord_sdk.txt + copyright + Discord Inc. + version + 1.4.9649.16733550144 + name + discord_sdk + vcs_branch + main + vcs_revision + ef5c7c4a490ceac2df2b2f046788b1daf1bbb392 + vcs_url + https://github.com/secondlife/3p-discord-sdk + canonical_repo + https://github.com/secondlife/3p-discord-sdk + description + Discord Social SDK + package_description diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index cc217b0563b..0a00ccbb5b8 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -20,6 +20,7 @@ set(cmake_SOURCE_FILES Copy3rdPartyLibs.cmake DBusGlib.cmake DeploySharedLibs.cmake + Discord.cmake DragDrop.cmake EXPAT.cmake FindAutobuild.cmake diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 6ac00fd131d..0153e69d5b8 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -6,6 +6,9 @@ include(CMakeCopyIfDifferent) include(Linking) +if (USE_DISCORD) + include(Discord) +endif () include(OPENAL) # When we copy our dependent libraries, we almost always want to copy them to @@ -75,6 +78,10 @@ if(WINDOWS) endif(ADDRESS_SIZE EQUAL 32) endif (USE_BUGSPLAT) + if (TARGET ll::discord_sdk) + list(APPEND release_files discord_partner_sdk.dll) + endif () + if (TARGET ll::openal) list(APPEND release_files openal32.dll alut.dll) endif () @@ -180,6 +187,10 @@ elseif(DARWIN) ) endif() + if (TARGET ll::discord_sdk) + list(APPEND release_files libdiscord_partner_sdk.dylib) + endif () + if (TARGET ll::openal) list(APPEND release_files libalut.dylib libopenal.dylib) endif () diff --git a/indra/cmake/Discord.cmake b/indra/cmake/Discord.cmake new file mode 100644 index 00000000000..95cfaacf5b4 --- /dev/null +++ b/indra/cmake/Discord.cmake @@ -0,0 +1,11 @@ +include(Prebuilt) + +include_guard() + +add_library(ll::discord_sdk INTERFACE IMPORTED) +target_compile_definitions(ll::discord_sdk INTERFACE LL_DISCORD=1) + +use_prebuilt_binary(discord_sdk) + +target_include_directories(ll::discord_sdk SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/discord_sdk) +target_link_libraries(ll::discord_sdk INTERFACE discord_partner_sdk) diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h index 0046c5da7ea..079bcd132a6 100644 --- a/indra/llcharacter/llcharacter.h +++ b/indra/llcharacter/llcharacter.h @@ -119,6 +119,8 @@ class LLCharacter virtual void addDebugText( const std::string& text ) = 0; + virtual std::string getDebugName() const { return getID().asString(); } + virtual const LLUUID& getID() const = 0; //------------------------------------------------------------------------- // End Interface diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index c09cf7abd27..9672a3262be 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -29,6 +29,7 @@ #include "llassettype.h" #include "lldictionary.h" #include "llmemory.h" +#include "llsd.h" #include "llsingleton.h" ///---------------------------------------------------------------------------- @@ -246,3 +247,19 @@ bool LLAssetType::lookupIsAssetIDKnowable(EType asset_type) } return false; } + +LLSD LLAssetType::getTypeNames() +{ + LLSD type_names; + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + for (S32 type = AT_TEXTURE; type < AT_COUNT; ++type) + { + const AssetEntry *entry = dict->lookup((LLAssetType::EType) type); + // skip llassettype_bad_lookup + if (entry) + { + type_names.append(entry->mTypeName); + } + } + return type_names; +} diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index 547c3f4329b..17177d81c3c 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -165,6 +165,8 @@ class LL_COMMON_API LLAssetType static bool lookupIsAssetFetchByIDAllowed(EType asset_type); // the asset allows direct download static bool lookupIsAssetIDKnowable(EType asset_type); // asset data can be known by the viewer + static LLSD getTypeNames(); + static const std::string BADLOOKUP; protected: diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp index a783e18e491..2e94651083b 100644 --- a/indra/llcommon/llprocessor.cpp +++ b/indra/llcommon/llprocessor.cpp @@ -638,6 +638,14 @@ class LLProcessorInfoDarwinImpl : public LLProcessorInfoImpl { getCPUIDInfo(); uint64_t frequency = getSysctlInt64("hw.cpufrequency"); + if (!frequency) + { + auto tbfrequency = getSysctlInt64("hw.tbfrequency"); + struct clockinfo clockrate; + auto clockrate_len = sizeof(clockrate); + if (!sysctlbyname("kern.clockrate", &clockrate, &clockrate_len, NULL, 0)) + frequency = tbfrequency * clockrate.hz; + } setInfo(eFrequency, (F64)frequency / (F64)1000000); } diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index c31030c5ea2..497c0ad3eb7 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -553,6 +553,45 @@ LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter } // namespace llsd +/***************************************************************************** +* LLSDParam> +*****************************************************************************/ +// Given an LLSD array, return a const std::vector&, where T is a type +// supported by LLSDParam. Bonus: if the LLSD value is actually a scalar, +// return a single-element vector containing the converted value. +template +class LLSDParam>: public LLSDParamBase +{ +public: + LLSDParam(const LLSD& array) + { + // treat undefined "array" as empty vector + if (array.isDefined()) + { + // what if it's a scalar? + if (! array.isArray()) + { + v.push_back(LLSDParam(array)); + } + else // really is an array + { + // reserve space for the array entries + v.reserve(array.size()); + for (const auto& item : llsd::inArray(array)) + { + v.push_back(LLSDParam(item)); + } + } + } + } + + operator const std::vector&() const { return v; } + +private: + std::vector v; +}; + + /***************************************************************************** * toArray(), toMap() *****************************************************************************/ diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h index 0eb1891754c..ac4f415f3ed 100644 --- a/indra/llcommon/threadpool.h +++ b/indra/llcommon/threadpool.h @@ -55,7 +55,7 @@ namespace LL * ThreadPool listens for application shutdown messages on the "LLApp" * LLEventPump. Call close() to shut down this ThreadPool early. */ - virtual void close(); + void close(); std::string getName() const { return mName; } size_t getWidth() const { return mThreads.size(); } @@ -122,7 +122,7 @@ namespace LL size_t threads=1, size_t capacity=1024*1024, bool auto_shutdown = true): - ThreadPoolBase(name, threads, new queue_t(name, capacity), auto_shutdown) + ThreadPoolBase(name, threads, new queue_t(name, capacity, false), auto_shutdown) {} ~ThreadPoolUsing() override {} diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index ea4feec58cb..7efaebd569e 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -21,6 +21,7 @@ #include "llcoros.h" #include LLCOROS_MUTEX_HEADER #include "llerror.h" +#include "llevents.h" #include "llexception.h" #include "stringize.h" @@ -30,11 +31,38 @@ using Lock = LLCoros::LockType; /***************************************************************************** * WorkQueueBase *****************************************************************************/ -LL::WorkQueueBase::WorkQueueBase(const std::string& name): - super(makeName(name)) +LL::WorkQueueBase::WorkQueueBase(const std::string& name, bool auto_shutdown) + : super(makeName(name)) { - // TODO: register for "LLApp" events so we can implicitly close() on - // viewer shutdown. + if (auto_shutdown) + { + // Register for "LLApp" events so we can implicitly close() on viewer shutdown + std::string listener_name = "WorkQueue:" + getKey(); + LLEventPumps::instance().obtain("LLApp").listen( + listener_name, + [this](const LLSD& stat) + { + std::string status(stat["status"]); + if (status != "running") + { + // Viewer is shutting down, close this queue + LL_DEBUGS("WorkQueue") << getKey() << " closing on app shutdown" << LL_ENDL; + close(); + } + return false; + }); + + // Store the listener name so we can unregister in the destructor + mListenerName = listener_name; + } +} + +LL::WorkQueueBase::~WorkQueueBase() +{ + if (!mListenerName.empty() && !LLEventPumps::wasDeleted()) + { + LLEventPumps::instance().obtain("LLApp").stopListening(mListenerName); + } } void LL::WorkQueueBase::runUntilClose() @@ -220,8 +248,8 @@ void LL::WorkQueueBase::checkCoroutine(const std::string& method) /***************************************************************************** * WorkQueue *****************************************************************************/ -LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity): - super(name), +LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity, bool auto_shutdown): + super(name, auto_shutdown), mQueue(capacity) { } @@ -269,8 +297,8 @@ bool LL::WorkQueue::tryPop_(Work& work) /***************************************************************************** * WorkSchedule *****************************************************************************/ -LL::WorkSchedule::WorkSchedule(const std::string& name, size_t capacity): - super(name), +LL::WorkSchedule::WorkSchedule(const std::string& name, size_t capacity, bool auto_shutdown): + super(name, auto_shutdown), mQueue(capacity) { } diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 9d7bbfbf7ae..735ad38a26d 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -51,7 +51,9 @@ namespace LL * You may omit the WorkQueueBase name, in which case a unique name is * synthesized; for practical purposes that makes it anonymous. */ - WorkQueueBase(const std::string& name); + WorkQueueBase(const std::string& name, bool auto_shutdown); + + virtual ~WorkQueueBase(); /** * Since the point of WorkQueue is to pass work to some other worker @@ -197,6 +199,9 @@ namespace LL private: virtual Work pop_() = 0; virtual bool tryPop_(Work&) = 0; + + // Name used for the LLApp event listener (empty if not registered) + std::string mListenerName; }; /***************************************************************************** @@ -212,7 +217,7 @@ namespace LL * You may omit the WorkQueue name, in which case a unique name is * synthesized; for practical purposes that makes it anonymous. */ - WorkQueue(const std::string& name = std::string(), size_t capacity=1024); + WorkQueue(const std::string& name = std::string(), size_t capacity=1024, bool auto_shutdown = true); /** * Since the point of WorkQueue is to pass work to some other worker @@ -282,7 +287,7 @@ namespace LL * You may omit the WorkSchedule name, in which case a unique name is * synthesized; for practical purposes that makes it anonymous. */ - WorkSchedule(const std::string& name = std::string(), size_t capacity=1024); + WorkSchedule(const std::string& name = std::string(), size_t capacity=1024, bool auto_shutdown = true); /** * Since the point of WorkSchedule is to pass work to some other worker diff --git a/indra/llimage/llimagebmp.cpp b/indra/llimage/llimagebmp.cpp index 2a328675c2e..c8f99380eae 100644 --- a/indra/llimage/llimagebmp.cpp +++ b/indra/llimage/llimagebmp.cpp @@ -558,6 +558,12 @@ bool LLImageBMP::encode(const LLImageRaw* raw_image, F32 encode_time) LL_INFOS() << "Dropping alpha information during BMP encoding" << LL_ENDL; } + if (raw_image->isBufferInvalid()) + { + setLastError("Invalid input, no buffer"); + return false; + } + setSize(raw_image->getWidth(), raw_image->getHeight(), dst_components); U8 magic[14]; diff --git a/indra/llimage/llimagedxt.cpp b/indra/llimage/llimagedxt.cpp index 6b960f9077e..c3fd0c5aa87 100644 --- a/indra/llimage/llimagedxt.cpp +++ b/indra/llimage/llimagedxt.cpp @@ -329,6 +329,12 @@ bool LLImageDXT::encodeDXT(const LLImageRaw* raw_image, F32 time, bool explicit_ { llassert_always(raw_image); + if (raw_image->isBufferInvalid()) + { + setLastError("Invalid input, no buffer"); + return false; + } + S32 ncomponents = raw_image->getComponents(); EFileFormat format; switch (ncomponents) diff --git a/indra/llimage/llimagejpeg.cpp b/indra/llimage/llimagejpeg.cpp index 0e7ec365d43..effd33b410a 100644 --- a/indra/llimage/llimagejpeg.cpp +++ b/indra/llimage/llimagejpeg.cpp @@ -491,6 +491,12 @@ bool LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time ) resetLastError(); + if (raw_image->isBufferInvalid()) + { + setLastError("Invalid input, no buffer"); + return false; + } + LLImageDataSharedLock lockIn(raw_image); LLImageDataLock lockOut(this); diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index c56e94aaa41..431f5aa0016 100644 --- a/indra/llimagej2coj/llimagej2coj.cpp +++ b/indra/llimagej2coj/llimagej2coj.cpp @@ -897,6 +897,12 @@ bool LLImageJ2COJ::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decod bool LLImageJ2COJ::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time, bool reversible) { + if (raw_image.isBufferInvalid()) + { + base.setLastError("Invalid input, no buffer"); + return false; + } + JPEG2KEncode encode(comment_text, reversible); bool encoded = encode.encode(raw_image, base); if (!encoded) diff --git a/indra/llinventory/llfoldertype.cpp b/indra/llinventory/llfoldertype.cpp index 7e1be17ecc8..670405e9b55 100644 --- a/indra/llinventory/llfoldertype.cpp +++ b/indra/llinventory/llfoldertype.cpp @@ -29,6 +29,7 @@ #include "llfoldertype.h" #include "lldictionary.h" #include "llmemory.h" +#include "llsd.h" #include "llsingleton.h" ///---------------------------------------------------------------------------- @@ -220,3 +221,21 @@ const std::string &LLFolderType::badLookup() static const std::string sBadLookup = "llfoldertype_bad_lookup"; return sBadLookup; } + +LLSD LLFolderType::getTypeNames() +{ + LLSD type_names; + for (S32 type = FT_TEXTURE; type < FT_COUNT; ++type) + { + if (lookupIsEnsembleType((LLFolderType::EType)type)) + continue; + + const FolderEntry* entry = LLFolderDictionary::getInstance()->lookup((LLFolderType::EType)type); + // skip llfoldertype_bad_lookup + if (entry) + { + type_names.append(entry->mName); + } + } + return type_names; +} diff --git a/indra/llinventory/llfoldertype.h b/indra/llinventory/llfoldertype.h index 46a1b92a967..dd12693f660 100644 --- a/indra/llinventory/llfoldertype.h +++ b/indra/llinventory/llfoldertype.h @@ -115,6 +115,8 @@ class LL_COMMON_API LLFolderType static const std::string& badLookup(); // error string when a lookup fails + static LLSD getTypeNames(); + protected: LLFolderType() {} ~LLFolderType() {} diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp index 075abf95363..3defad8f3b0 100644 --- a/indra/llinventory/llinventory.cpp +++ b/indra/llinventory/llinventory.cpp @@ -46,6 +46,7 @@ static const std::string INV_ITEM_ID_LABEL("item_id"); static const std::string INV_FOLDER_ID_LABEL("cat_id"); static const std::string INV_PARENT_ID_LABEL("parent_id"); static const std::string INV_THUMBNAIL_LABEL("thumbnail"); +static const std::string INV_FAVORITE_LABEL("favorite"); static const std::string INV_THUMBNAIL_ID_LABEL("thumbnail_id"); static const std::string INV_ASSET_TYPE_LABEL("type"); static const std::string INV_PREFERRED_TYPE_LABEL("preferred_type"); @@ -59,6 +60,7 @@ static const std::string INV_LINKED_ID_LABEL("linked_id"); static const std::string INV_SALE_INFO_LABEL("sale_info"); static const std::string INV_FLAGS_LABEL("flags"); static const std::string INV_CREATION_DATE_LABEL("created_at"); +static const std::string INV_TOGGLED_LABEL("toggled"); // key used by agent-inventory-service static const std::string INV_ASSET_TYPE_LABEL_WS("type_default"); @@ -82,14 +84,16 @@ LLInventoryObject::LLInventoryObject(const LLUUID& uuid, mParentUUID(parent_uuid), mType(type), mName(name), - mCreationDate(0) + mCreationDate(0), + mFavorite(false) { correctInventoryName(mName); } LLInventoryObject::LLInventoryObject() : mType(LLAssetType::AT_NONE), - mCreationDate(0) + mCreationDate(0), + mFavorite(false) { } @@ -104,6 +108,7 @@ void LLInventoryObject::copyObject(const LLInventoryObject* other) mType = other->mType; mName = other->mName; mThumbnailUUID = other->mThumbnailUUID; + mFavorite = other->mFavorite; } const LLUUID& LLInventoryObject::getUUID() const @@ -121,6 +126,11 @@ const LLUUID& LLInventoryObject::getThumbnailUUID() const return mThumbnailUUID; } +bool LLInventoryObject::getIsFavorite() const +{ + return mFavorite; +} + const std::string& LLInventoryObject::getName() const { return mName; @@ -175,6 +185,11 @@ void LLInventoryObject::setThumbnailUUID(const LLUUID& thumbnail_uuid) mThumbnailUUID = thumbnail_uuid; } +void LLInventoryObject::setFavorite(bool favorite) +{ + mFavorite = favorite; +} + void LLInventoryObject::setType(LLAssetType::EType type) { mType = type; @@ -247,6 +262,23 @@ bool LLInventoryObject::importLegacyStream(std::istream& input_stream) { setThumbnailUUID(LLUUID::null); } + + if (metadata.has("favorite")) + { + const LLSD& favorite = metadata["favorite"]; + if (favorite.has("toggled")) + { + setFavorite(favorite["toggled"].asBoolean()); + } + else + { + setFavorite(false); + } + } + else + { + setFavorite(false); + } } else if(0 == strcmp("name", keyword)) { @@ -735,6 +767,23 @@ bool LLInventoryItem::importLegacyStream(std::istream& input_stream) { setThumbnailUUID(LLUUID::null); } + + if (metadata.has("favorite")) + { + const LLSD& favorite = metadata["favorite"]; + if (favorite.has("toggled")) + { + setFavorite(favorite["toggled"].asBoolean()); + } + else + { + setFavorite(false); + } + } + else + { + setFavorite(false); + } } else if(0 == strcmp("inv_type", keyword)) { @@ -879,7 +928,7 @@ bool LLInventoryItem::exportLegacyStream(std::ostream& output_stream, bool inclu LLSD LLInventoryItem::asLLSD() const { - LLSD sd = LLSD(); + LLSD sd; asLLSD(sd); return sd; } @@ -888,13 +937,18 @@ void LLInventoryItem::asLLSD( LLSD& sd ) const { sd[INV_ITEM_ID_LABEL] = mUUID; sd[INV_PARENT_ID_LABEL] = mParentUUID; - sd[INV_PERMISSIONS_LABEL] = ll_create_sd_from_permissions(mPermissions); + ll_fill_sd_from_permissions(sd[INV_PERMISSIONS_LABEL], mPermissions); if (mThumbnailUUID.notNull()) { sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); } + if (mFavorite) + { + sd[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite); + } + U32 mask = mPermissions.getMaskBase(); if(((mask & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) || (mAssetUUID.isNull())) @@ -909,19 +963,22 @@ void LLInventoryItem::asLLSD( LLSD& sd ) const cipher.encrypt(shadow_id.mData, UUID_BYTES); sd[INV_SHADOW_ID_LABEL] = shadow_id; } - sd[INV_ASSET_TYPE_LABEL] = LLAssetType::lookup(mType); - sd[INV_INVENTORY_TYPE_LABEL] = mInventoryType; + sd[INV_ASSET_TYPE_LABEL] = std::string(LLAssetType::lookup(mType)); const std::string inv_type_str = LLInventoryType::lookup(mInventoryType); if(!inv_type_str.empty()) { sd[INV_INVENTORY_TYPE_LABEL] = inv_type_str; } + else + { + sd[INV_INVENTORY_TYPE_LABEL] = (LLSD::Integer)mInventoryType; + } //sd[INV_FLAGS_LABEL] = (S32)mFlags; sd[INV_FLAGS_LABEL] = ll_sd_from_U32(mFlags); - sd[INV_SALE_INFO_LABEL] = mSaleInfo.asLLSD(); + mSaleInfo.asLLSD(sd[INV_SALE_INFO_LABEL]); sd[INV_NAME_LABEL] = mName; sd[INV_DESC_LABEL] = mDescription; - sd[INV_CREATION_DATE_LABEL] = (S32) mCreationDate; + sd[INV_CREATION_DATE_LABEL] = (LLSD::Integer)mCreationDate; } bool LLInventoryItem::fromLLSD(const LLSD& sd, bool is_new) @@ -937,6 +994,8 @@ bool LLInventoryItem::fromLLSD(const LLSD& sd, bool is_new) // TODO - figure out if this should be moved into the noclobber fields above mThumbnailUUID.setNull(); + mFavorite = false; + mPermissions.init(LLUUID::null, LLUUID::null, LLUUID::null, LLUUID::null); // iterate as map to avoid making unnecessary temp copies of everything LLSD::map_const_iterator i, end; @@ -982,9 +1041,20 @@ bool LLInventoryItem::fromLLSD(const LLSD& sd, bool is_new) continue; } + if (i->first == INV_FAVORITE_LABEL) + { + const LLSD& favorite_map = i->second; + const std::string w = INV_TOGGLED_LABEL; + if (favorite_map.has(w)) + { + mFavorite = favorite_map[w].asBoolean(); + } + continue; + } + if (i->first == INV_PERMISSIONS_LABEL) { - mPermissions = ll_permissions_from_sd(i->second); + mPermissions.importLLSD(i->second); continue; } @@ -1177,6 +1247,11 @@ LLSD LLInventoryCategory::asLLSD() const sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); } + if (mFavorite) + { + sd[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite); + } + return sd; } @@ -1188,11 +1263,17 @@ LLSD LLInventoryCategory::asAISCreateCatLLSD() const S8 type = static_cast(mPreferredType); sd[INV_ASSET_TYPE_LABEL_WS] = type; sd[INV_NAME_LABEL] = mName; + if (mThumbnailUUID.notNull()) { sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); } + if (mFavorite) + { + sd[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite); + } + return sd; } @@ -1240,6 +1321,17 @@ bool LLInventoryCategory::fromLLSD(const LLSD& sd) mThumbnailUUID = sd[w]; } } + mFavorite = false; + w = INV_FAVORITE_LABEL; + if (sd.has(w)) + { + const LLSD& favorite_map = sd[w]; + w = INV_TOGGLED_LABEL; + if (favorite_map.has(w)) + { + mFavorite = favorite_map[w].asBoolean(); + } + } w = INV_ASSET_TYPE_LABEL; if (sd.has(w)) { @@ -1362,6 +1454,23 @@ bool LLInventoryCategory::importLegacyStream(std::istream& input_stream) { setThumbnailUUID(LLUUID::null); } + + if (metadata.has("favorite")) + { + const LLSD& favorite = metadata["favorite"]; + if (favorite.has("toggled")) + { + setFavorite(favorite["toggled"].asBoolean()); + } + else + { + setFavorite(false); + } + } + else + { + setFavorite(false); + } } else { @@ -1396,12 +1505,11 @@ bool LLInventoryCategory::exportLegacyStream(std::ostream& output_stream, bool) return true; } -LLSD LLInventoryCategory::exportLLSD() const +void LLInventoryCategory::exportLLSD(LLSD& cat_data) const { - LLSD cat_data; cat_data[INV_FOLDER_ID_LABEL] = mUUID; cat_data[INV_PARENT_ID_LABEL] = mParentUUID; - cat_data[INV_ASSET_TYPE_LABEL] = LLAssetType::lookup(mType); + cat_data[INV_ASSET_TYPE_LABEL] = std::string(LLAssetType::lookup(mType)); cat_data[INV_PREFERRED_TYPE_LABEL] = LLFolderType::lookup(mPreferredType); cat_data[INV_NAME_LABEL] = mName; @@ -1409,49 +1517,76 @@ LLSD LLInventoryCategory::exportLLSD() const { cat_data[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); } + if (mFavorite) + { + cat_data[INV_FAVORITE_LABEL] = LLSD().with(INV_TOGGLED_LABEL, mFavorite); + } +} - return cat_data; +bool LLInventoryCategory::importLLSDMap(const LLSD& cat_data) +{ + LLSD::map_const_iterator i, end; + end = cat_data.endMap(); + for ( i = cat_data.beginMap(); i != end; ++i) + { + importLLSD(i->first, i->second); + } + return true; } -bool LLInventoryCategory::importLLSD(const LLSD& cat_data) +bool LLInventoryCategory::importLLSD(const std::string& label, const LLSD& value) { - if (cat_data.has(INV_FOLDER_ID_LABEL)) + if (label == INV_FOLDER_ID_LABEL) { - setUUID(cat_data[INV_FOLDER_ID_LABEL].asUUID()); + setUUID(value.asUUID()); + return true; } - if (cat_data.has(INV_PARENT_ID_LABEL)) + else if (label == INV_PARENT_ID_LABEL) { - setParent(cat_data[INV_PARENT_ID_LABEL].asUUID()); + setParent(value.asUUID()); + return true; } - if (cat_data.has(INV_ASSET_TYPE_LABEL)) + else if (label == INV_ASSET_TYPE_LABEL) { - setType(LLAssetType::lookup(cat_data[INV_ASSET_TYPE_LABEL].asString())); + setType(LLAssetType::lookup(value.asString())); + return true; } - if (cat_data.has(INV_PREFERRED_TYPE_LABEL)) + else if (label == INV_PREFERRED_TYPE_LABEL) { - setPreferredType(LLFolderType::lookup(cat_data[INV_PREFERRED_TYPE_LABEL].asString())); + setPreferredType(LLFolderType::lookup(value.asString())); + return true; } - if (cat_data.has(INV_THUMBNAIL_LABEL)) + else if (label == INV_THUMBNAIL_LABEL) { LLUUID thumbnail_uuid; - const LLSD &thumbnail_data = cat_data[INV_THUMBNAIL_LABEL]; - if (thumbnail_data.has(INV_ASSET_ID_LABEL)) + if (value.has(INV_ASSET_ID_LABEL)) { - thumbnail_uuid = thumbnail_data[INV_ASSET_ID_LABEL].asUUID(); + thumbnail_uuid = value[INV_ASSET_ID_LABEL].asUUID(); } setThumbnailUUID(thumbnail_uuid); + return true; + } + if (label == INV_FAVORITE_LABEL) + { + bool favorite = false; + if (value.has(INV_TOGGLED_LABEL)) + { + favorite = value[INV_TOGGLED_LABEL].asBoolean(); + } + setFavorite(favorite); } - if (cat_data.has(INV_NAME_LABEL)) + else if (label == INV_NAME_LABEL) { - mName = cat_data[INV_NAME_LABEL].asString(); + mName = value.asString(); LLStringUtil::replaceNonstandardASCII(mName, ' '); LLStringUtil::replaceChar(mName, '|', ' '); + return true; } - - return true; + return false; } + ///---------------------------------------------------------------------------- -/// Local function definitions +/// Local function definitions for testing purposes ///---------------------------------------------------------------------------- LLSD ll_create_sd_from_inventory_item(LLPointer item) diff --git a/indra/llinventory/llinventory.h b/indra/llinventory/llinventory.h index e63f2deba7a..181c46226c5 100644 --- a/indra/llinventory/llinventory.h +++ b/indra/llinventory/llinventory.h @@ -71,6 +71,7 @@ class LLInventoryObject : public LLRefCount virtual const LLUUID& getLinkedUUID() const; // inventoryID that this item points to, else this item's inventoryID const LLUUID& getParentUUID() const; virtual const LLUUID& getThumbnailUUID() const; + virtual bool getIsFavorite() const; virtual const std::string& getName() const; virtual LLAssetType::EType getType() const; LLAssetType::EType getActualType() const; // bypasses indirection for linked items @@ -86,6 +87,7 @@ class LLInventoryObject : public LLRefCount virtual void rename(const std::string& new_name); void setParent(const LLUUID& new_parent); virtual void setThumbnailUUID(const LLUUID& thumbnail_uuid); + virtual void setFavorite(bool favorite); void setType(LLAssetType::EType type); virtual void setCreationDate(time_t creation_date_utc); // only stored for items @@ -111,6 +113,7 @@ class LLInventoryObject : public LLRefCount LLUUID mUUID; LLUUID mParentUUID; // Parent category. Root categories have LLUUID::NULL. LLUUID mThumbnailUUID; + bool mFavorite; LLAssetType::EType mType; std::string mName; time_t mCreationDate; // seconds from 1/1/1970, UTC @@ -270,8 +273,9 @@ class LLInventoryCategory : public LLInventoryObject virtual bool importLegacyStream(std::istream& input_stream); virtual bool exportLegacyStream(std::ostream& output_stream, bool include_asset_key = true) const; - LLSD exportLLSD() const; - bool importLLSD(const LLSD& cat_data); + virtual void exportLLSD(LLSD& sd) const; + bool importLLSDMap(const LLSD& cat_data); + virtual bool importLLSD(const std::string& label, const LLSD& value); //-------------------------------------------------------------------- // Member Variables //-------------------------------------------------------------------- @@ -285,6 +289,7 @@ class LLInventoryCategory : public LLInventoryObject // // These functions convert between structured data and an inventory // item, appropriate for serialization. +// Not up to date (no favorites, nor thumbnails), for testing purposes //----------------------------------------------------------------------------- LLSD ll_create_sd_from_inventory_item(LLPointer item); LLSD ll_create_sd_from_inventory_category(LLPointer cat); diff --git a/indra/llinventory/llpermissions.cpp b/indra/llinventory/llpermissions.cpp index c8963881df8..ebf7445c65c 100644 --- a/indra/llinventory/llpermissions.cpp +++ b/indra/llinventory/llpermissions.cpp @@ -704,6 +704,79 @@ bool LLPermissions::exportLegacyStream(std::ostream& output_stream) const return true; } +static const std::string PERM_CREATOR_ID_LABEL("creator_id"); +static const std::string PERM_OWNER_ID_LABEL("owner_id"); +static const std::string PERM_LAST_OWNER_ID_LABEL("last_owner_id"); +static const std::string PERM_GROUP_ID_LABEL("group_id"); +static const std::string PERM_IS_OWNER_GROUP_LABEL("is_owner_group"); +static const std::string PERM_BASE_MASK_LABEL("base_mask"); +static const std::string PERM_OWNER_MASK_LABEL("owner_mask"); +static const std::string PERM_GROUP_MASK_LABEL("group_mask"); +static const std::string PERM_EVERYONE_MASK_LABEL("everyone_mask"); +static const std::string PERM_NEXT_OWNER_MASK_LABEL("next_owner_mask"); + +void LLPermissions::importLLSD(const LLSD& sd_perm) +{ + LLSD::map_const_iterator i, end; + end = sd_perm.endMap(); + for (i = sd_perm.beginMap(); i != end; ++i) + { + const std::string& label = i->first; + if (label == PERM_CREATOR_ID_LABEL) + { + mCreator = i->second.asUUID(); + continue; + } + if (label == PERM_OWNER_ID_LABEL) + { + mOwner = i->second.asUUID(); + continue; + } + if (label == PERM_LAST_OWNER_ID_LABEL) + { + mLastOwner = i->second.asUUID(); + continue; + } + if (label == PERM_GROUP_ID_LABEL) + { + mGroup = i->second.asUUID(); + continue; + } + if (label == PERM_BASE_MASK_LABEL) + { + PermissionMask mask = i->second.asInteger(); + mMaskBase = mask; + continue; + } + if (label == PERM_OWNER_MASK_LABEL) + { + PermissionMask mask = i->second.asInteger(); + mMaskOwner = mask; + continue; + } + if (label == PERM_EVERYONE_MASK_LABEL) + { + PermissionMask mask = i->second.asInteger(); + mMaskEveryone = mask; + continue; + } + if (label == PERM_GROUP_MASK_LABEL) + { + PermissionMask mask = i->second.asInteger(); + mMaskGroup = mask; + continue; + } + if (label == PERM_NEXT_OWNER_MASK_LABEL) + { + PermissionMask mask = i->second.asInteger(); + mMaskNextOwner = mask; + continue; + } + } + + fix(); +} + bool LLPermissions::operator==(const LLPermissions &rhs) const { return @@ -998,55 +1071,30 @@ std::string mask_to_string(U32 mask) ///---------------------------------------------------------------------------- /// exported functions ///---------------------------------------------------------------------------- -static const std::string PERM_CREATOR_ID_LABEL("creator_id"); -static const std::string PERM_OWNER_ID_LABEL("owner_id"); -static const std::string PERM_LAST_OWNER_ID_LABEL("last_owner_id"); -static const std::string PERM_GROUP_ID_LABEL("group_id"); -static const std::string PERM_IS_OWNER_GROUP_LABEL("is_owner_group"); -static const std::string PERM_BASE_MASK_LABEL("base_mask"); -static const std::string PERM_OWNER_MASK_LABEL("owner_mask"); -static const std::string PERM_GROUP_MASK_LABEL("group_mask"); -static const std::string PERM_EVERYONE_MASK_LABEL("everyone_mask"); -static const std::string PERM_NEXT_OWNER_MASK_LABEL("next_owner_mask"); LLSD ll_create_sd_from_permissions(const LLPermissions& perm) { LLSD rv; + ll_fill_sd_from_permissions(rv, perm); + return rv; +} +void ll_fill_sd_from_permissions(LLSD& rv, const LLPermissions& perm) +{ rv[PERM_CREATOR_ID_LABEL] = perm.getCreator(); rv[PERM_OWNER_ID_LABEL] = perm.getOwner(); rv[PERM_LAST_OWNER_ID_LABEL] = perm.getLastOwner(); rv[PERM_GROUP_ID_LABEL] = perm.getGroup(); rv[PERM_IS_OWNER_GROUP_LABEL] = perm.isGroupOwned(); - rv[PERM_BASE_MASK_LABEL] = (S32)perm.getMaskBase(); - rv[PERM_OWNER_MASK_LABEL] = (S32)perm.getMaskOwner(); - rv[PERM_GROUP_MASK_LABEL] = (S32)perm.getMaskGroup(); - rv[PERM_EVERYONE_MASK_LABEL] = (S32)perm.getMaskEveryone(); - rv[PERM_NEXT_OWNER_MASK_LABEL] = (S32)perm.getMaskNextOwner(); - return rv; + rv[PERM_BASE_MASK_LABEL] = (LLSD::Integer)perm.getMaskBase(); + rv[PERM_OWNER_MASK_LABEL] = (LLSD::Integer)perm.getMaskOwner(); + rv[PERM_GROUP_MASK_LABEL] = (LLSD::Integer)perm.getMaskGroup(); + rv[PERM_EVERYONE_MASK_LABEL] = (LLSD::Integer)perm.getMaskEveryone(); + rv[PERM_NEXT_OWNER_MASK_LABEL] = (LLSD::Integer)perm.getMaskNextOwner(); } LLPermissions ll_permissions_from_sd(const LLSD& sd_perm) { LLPermissions rv; - rv.init( - sd_perm[PERM_CREATOR_ID_LABEL].asUUID(), - sd_perm[PERM_OWNER_ID_LABEL].asUUID(), - sd_perm[PERM_LAST_OWNER_ID_LABEL].asUUID(), - sd_perm[PERM_GROUP_ID_LABEL].asUUID()); - - // We do a cast to U32 here since LLSD does not attempt to - // represent unsigned ints. - PermissionMask mask; - mask = (U32)(sd_perm[PERM_BASE_MASK_LABEL].asInteger()); - rv.setMaskBase(mask); - mask = (U32)(sd_perm[PERM_OWNER_MASK_LABEL].asInteger()); - rv.setMaskOwner(mask); - mask = (U32)(sd_perm[PERM_EVERYONE_MASK_LABEL].asInteger()); - rv.setMaskEveryone(mask); - mask = (U32)(sd_perm[PERM_GROUP_MASK_LABEL].asInteger()); - rv.setMaskGroup(mask); - mask = (U32)(sd_perm[PERM_NEXT_OWNER_MASK_LABEL].asInteger()); - rv.setMaskNext(mask); - rv.fix(); + rv.importLLSD(sd_perm); return rv; } diff --git a/indra/llinventory/llpermissions.h b/indra/llinventory/llpermissions.h index a68abcfa34b..82cdc03727e 100644 --- a/indra/llinventory/llpermissions.h +++ b/indra/llinventory/llpermissions.h @@ -299,6 +299,8 @@ class LLPermissions bool importLegacyStream(std::istream& input_stream); bool exportLegacyStream(std::ostream& output_stream) const; + void importLLSD(const LLSD& sd_perm); + bool operator==(const LLPermissions &rhs) const; bool operator!=(const LLPermissions &rhs) const; @@ -435,6 +437,7 @@ class LLAggregatePermissions // like 'creator_id', 'owner_id', etc, with the value copied from the // permission object. LLSD ll_create_sd_from_permissions(const LLPermissions& perm); +void ll_fill_sd_from_permissions(LLSD& rv, const LLPermissions& perm); LLPermissions ll_permissions_from_sd(const LLSD& sd_perm); #endif diff --git a/indra/llinventory/llsaleinfo.cpp b/indra/llinventory/llsaleinfo.cpp index 35bbc1dbb18..b4d64bb4fb6 100644 --- a/indra/llinventory/llsaleinfo.cpp +++ b/indra/llinventory/llsaleinfo.cpp @@ -90,15 +90,20 @@ bool LLSaleInfo::exportLegacyStream(std::ostream& output_stream) const LLSD LLSaleInfo::asLLSD() const { LLSD sd; + asLLSD(sd); + return sd; +} + +void LLSaleInfo::asLLSD(LLSD& sd) const +{ const char* type = lookup(mSaleType); if (!type) { LL_WARNS_ONCE() << "Unknown sale type: " << mSaleType << LL_ENDL; type = lookup(LLSaleInfo::FS_NOT); } - sd["sale_type"] = type; + sd["sale_type"] = std::string(type); sd["sale_price"] = mSalePrice; - return sd; } bool LLSaleInfo::fromLLSD(const LLSD& sd, bool& has_perm_mask, U32& perm_mask) diff --git a/indra/llinventory/llsaleinfo.h b/indra/llinventory/llsaleinfo.h index 44eb8416419..7186e8ab498 100644 --- a/indra/llinventory/llsaleinfo.h +++ b/indra/llinventory/llsaleinfo.h @@ -86,6 +86,7 @@ class LLSaleInfo bool exportLegacyStream(std::ostream& output_stream) const; LLSD asLLSD() const; + void asLLSD(LLSD &sd) const; operator LLSD() const { return asLLSD(); } bool fromLLSD(const LLSD& sd, bool& has_perm_mask, U32& perm_mask); bool importLegacyStream(std::istream& input_stream, bool& has_perm_mask, U32& perm_mask); diff --git a/indra/llinventory/tests/inventorymisc_test.cpp b/indra/llinventory/tests/inventorymisc_test.cpp index 9779cb8fbc7..f11a4c3bf71 100644 --- a/indra/llinventory/tests/inventorymisc_test.cpp +++ b/indra/llinventory/tests/inventorymisc_test.cpp @@ -39,6 +39,34 @@ #pragma warning(disable: 4702) #endif +void set_random_inventory_metadata(LLInventoryObject* obj) +{ + S32 extra = rand() % 4; + switch (extra) + { + case 0: + { + LLUUID thumbnail_id; + thumbnail_id.generate(); + obj->setThumbnailUUID(thumbnail_id); + break; + } + case 1: + obj->setFavorite(true); + break; + case 2: + { + LLUUID thumbnail_id; + thumbnail_id.generate(); + obj->setThumbnailUUID(thumbnail_id); + obj->setFavorite(true); + break; + } + default: + break; + } +} + LLPointer create_random_inventory_item() { LLUUID item_id; @@ -75,6 +103,7 @@ LLPointer create_random_inventory_item() sale_info, flags, creation); + set_random_inventory_metadata(item); return item; } @@ -90,6 +119,7 @@ LLPointer create_random_inventory_cat() parent_id, LLFolderType::FT_NONE, std::string("Sample category")); + set_random_inventory_metadata(cat); return cat; } @@ -290,6 +320,7 @@ namespace tut src->setCreationDate(new_creation); // test a save/load cycle to LLSD and back again + // Note: ll_create_sd_from_inventory_item does not support metadata LLSD sd = ll_create_sd_from_inventory_item(src); LLPointer dst = new LLInventoryItem; bool successful_parse = dst->fromLLSD(sd); @@ -329,7 +360,9 @@ namespace tut } LLPointer src1 = create_random_inventory_item(); - fileXML << LLSDOStreamer(src1->asLLSD()) << std::endl; + LLSD sd; + src1->asLLSD(sd); + fileXML << LLSDOStreamer(sd) << std::endl; fileXML.close(); @@ -364,13 +397,13 @@ namespace tut ensure_equals("8.name::getName() failed", src1->getName(), src2->getName()); ensure_equals("9.description::getDescription() failed", src1->getDescription(), src2->getDescription()); ensure_equals("10.creation::getCreationDate() failed", src1->getCreationDate(), src2->getCreationDate()); - + ensure_equals("13.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); + ensure_equals("14.favorites::getIsFavorite() failed", src1->getIsFavorite(), src2->getIsFavorite()); } template<> template<> void inventory_object::test<8>() { - LLPointer src1 = create_random_inventory_item(); std::ostringstream ostream; @@ -390,8 +423,8 @@ namespace tut ensure_equals("8.name::getName() failed", src1->getName(), src2->getName()); ensure_equals("9.description::getDescription() failed", src1->getDescription(), src2->getDescription()); ensure_equals("10.creation::getCreationDate() failed", src1->getCreationDate(), src2->getCreationDate()); - - + ensure_equals("11.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); + ensure_equals("12.favorites::getIsFavorite() failed", false, src2->getIsFavorite()); // not supposed to carry over } template<> template<> @@ -421,6 +454,8 @@ namespace tut ensure_equals("10.name::getName() failed", src1->getName(), src2->getName()); ensure_equals("11.description::getDescription() failed", src1->getDescription(), src2->getDescription()); ensure_equals("12.creation::getCreationDate() failed", src1->getCreationDate(), src2->getCreationDate()); + ensure_equals("13.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); + ensure_equals("14.favorites::getIsFavorite() failed", src1->getIsFavorite(), src2->getIsFavorite()); } //******class LLInventoryCategory*******// @@ -458,7 +493,9 @@ namespace tut } LLPointer src1 = create_random_inventory_cat(); - fileXML << LLSDOStreamer(src1->exportLLSD()) << std::endl; + LLSD sd; + src1->exportLLSD(sd); + fileXML << LLSDOStreamer(sd) << std::endl; fileXML.close(); llifstream file(filename.c_str()); @@ -481,13 +518,15 @@ namespace tut file.close(); LLPointer src2 = new LLInventoryCategory(); - src2->importLLSD(s_item); + src2->importLLSDMap(s_item); ensure_equals("1.item id::getUUID() failed", src1->getUUID(), src2->getUUID()); ensure_equals("2.parent::getParentUUID() failed", src1->getParentUUID(), src2->getParentUUID()); ensure_equals("3.type::getType() failed", src1->getType(), src2->getType()); ensure_equals("4.preferred type::getPreferredType() failed", src1->getPreferredType(), src2->getPreferredType()); ensure_equals("5.name::getName() failed", src1->getName(), src2->getName()); + ensure_equals("6.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); + ensure_equals("7.favorites::getIsFavorite() failed", src1->getIsFavorite(), src2->getIsFavorite()); } template<> template<> @@ -507,6 +546,7 @@ namespace tut ensure_equals("3.type::getType() failed", src1->getType(), src2->getType()); ensure_equals("4.preferred type::getPreferredType() failed", src1->getPreferredType(), src2->getPreferredType()); ensure_equals("5.name::getName() failed", src1->getName(), src2->getName()); - + ensure_equals("13.thumbnails::getThumbnailUUID() failed", src1->getThumbnailUUID(), src2->getThumbnailUUID()); + ensure_equals("14.favorites::getIsFavorite() failed", false, src2->getIsFavorite()); // currently not supposed to carry over } } diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index 0d1f2b3006d..b824fd83851 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -662,6 +662,12 @@ bool LLImageJ2CKDU::encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, co bool vflip = true; bool hflip = false; + if (raw_image.isBufferInvalid()) + { + base.setLastError("Invalid input, no buffer"); + return false; + } + try { // Set up input image files diff --git a/indra/llmath/llcoordframe.cpp b/indra/llmath/llcoordframe.cpp index 15c9f6ff3f5..b42541c7c09 100644 --- a/indra/llmath/llcoordframe.cpp +++ b/indra/llmath/llcoordframe.cpp @@ -328,28 +328,30 @@ void LLCoordFrame::rotate(const LLMatrix3 &rotation_matrix) } +// Rotate 2 normalized orthogonal vectors in direction from `source` to `target` +static void rotate2(LLVector3& source, LLVector3& target, F32 angle) +{ + F32 sx = source[VX], sy = source[VY], sz = source[VZ]; + F32 tx = target[VX], ty = target[VY], tz = target[VZ]; + F32 c = cosf(angle), s = sinf(angle); + + source.set(sx * c + tx * s, sy * c + ty * s, sz * c + tz * s); + target.set(tx * c - sx * s, ty * c - sy * s, tz * c - sz * s); +} + void LLCoordFrame::roll(F32 angle) { - LLQuaternion q(angle, mXAxis); - LLMatrix3 rotation_matrix(q); - rotate(rotation_matrix); - CHECK_FINITE_OBJ(); + rotate2(mYAxis, mZAxis, angle); } void LLCoordFrame::pitch(F32 angle) { - LLQuaternion q(angle, mYAxis); - LLMatrix3 rotation_matrix(q); - rotate(rotation_matrix); - CHECK_FINITE_OBJ(); + rotate2(mZAxis, mXAxis, angle); } void LLCoordFrame::yaw(F32 angle) { - LLQuaternion q(angle, mZAxis); - LLMatrix3 rotation_matrix(q); - rotate(rotation_matrix); - CHECK_FINITE_OBJ(); + rotate2(mXAxis, mYAxis, angle); } // get*() routines diff --git a/indra/llmath/llmath.h b/indra/llmath/llmath.h index 7f51de78202..891f0ffc4c1 100644 --- a/indra/llmath/llmath.h +++ b/indra/llmath/llmath.h @@ -65,7 +65,7 @@ constexpr F32 DEG_TO_RAD = 0.017453292519943295769236907684886f; constexpr F32 RAD_TO_DEG = 57.295779513082320876798154814105f; constexpr F32 F_APPROXIMATELY_ZERO = 0.00001f; constexpr F32 F_LN10 = 2.3025850929940456840179914546844f; -constexpr F32 OO_LN10 = 0.43429448190325182765112891891661; +constexpr F32 OO_LN10 = 0.43429448190325182765112891891661f; constexpr F32 F_LN2 = 0.69314718056f; constexpr F32 OO_LN2 = 1.4426950408889634073599246810019f; diff --git a/indra/llmath/llquaternion.cpp b/indra/llmath/llquaternion.cpp index 1ab3a73d799..3a6748bdb3e 100644 --- a/indra/llmath/llquaternion.cpp +++ b/indra/llmath/llquaternion.cpp @@ -57,7 +57,7 @@ LLQuaternion::LLQuaternion(const LLMatrix3 &mat) LLQuaternion::LLQuaternion(F32 angle, const LLVector4 &vec) { - F32 mag = sqrtf(vec.mV[VX] * vec.mV[VX] + vec.mV[VY] * vec.mV[VY] + vec.mV[VZ] * vec.mV[VZ]); + F32 mag = vec.length(); if (mag > FP_MAG_THRESHOLD) { angle *= 0.5; @@ -76,7 +76,7 @@ LLQuaternion::LLQuaternion(F32 angle, const LLVector4 &vec) LLQuaternion::LLQuaternion(F32 angle, const LLVector3 &vec) { - F32 mag = sqrtf(vec.mV[VX] * vec.mV[VX] + vec.mV[VY] * vec.mV[VY] + vec.mV[VZ] * vec.mV[VZ]); + F32 mag = vec.length(); if (mag > FP_MAG_THRESHOLD) { angle *= 0.5; diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 1e7dfd18f20..7c602536186 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -5711,7 +5711,15 @@ bool LLVolumeFace::cacheOptimize(bool gen_tangents) S32 vert_count = 0; if (!data.p.empty()) { - vert_count = static_cast(meshopt_generateVertexRemapMulti(&remap[0], nullptr, data.p.size(), data.p.size(), mos, stream_count)); + try + { + vert_count = static_cast(meshopt_generateVertexRemapMulti(&remap[0], nullptr, data.p.size(), data.p.size(), mos, stream_count)); + } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("LLCoros") << "Failed to allocate memory for VertexRemap: " << (S32)data.p.size() << LL_ENDL; + } } if (vert_count < 65535 && vert_count != 0) diff --git a/indra/llmath/v2math.cpp b/indra/llmath/v2math.cpp index 59e6d947cac..175f08df882 100644 --- a/indra/llmath/v2math.cpp +++ b/indra/llmath/v2math.cpp @@ -66,6 +66,13 @@ F32 angle_between(const LLVector2& a, const LLVector2& b) return angle; } +F32 signed_angle_between(const LLVector2& a, const LLVector2& b) +{ + F32 angle = angle_between(a, b); + F32 rhombus_square = a[VX] * b[VY] - b[VX] * a[VY]; + return rhombus_square < 0 ? -angle : angle; +} + bool are_parallel(const LLVector2& a, const LLVector2& b, F32 epsilon) { LLVector2 an = a; diff --git a/indra/llmath/v2math.h b/indra/llmath/v2math.h index 18ad02a411d..6b9d37535b2 100644 --- a/indra/llmath/v2math.h +++ b/indra/llmath/v2math.h @@ -107,13 +107,14 @@ class LLVector2 friend LLVector2 operator-(const LLVector2 &a); // Return vector -a - friend std::ostream& operator<<(std::ostream& s, const LLVector2 &a); // Stream a + friend std::ostream& operator<<(std::ostream& s, const LLVector2 &a); // Stream a }; // Non-member functions F32 angle_between(const LLVector2& a, const LLVector2& b); // Returns angle (radians) between a and b +F32 signed_angle_between(const LLVector2& a, const LLVector2& b); // Returns signed angle (radians) between a and b bool are_parallel(const LLVector2& a, const LLVector2& b, F32 epsilon = F_APPROXIMATELY_ZERO); // Returns true if a and b are very close to parallel F32 dist_vec(const LLVector2& a, const LLVector2& b); // Returns distance between a and b F32 dist_vec_squared(const LLVector2& a, const LLVector2& b);// Returns distance squared between a and b @@ -124,26 +125,22 @@ LLVector2 lerp(const LLVector2& a, const LLVector2& b, F32 u); // Returns a vect inline LLVector2::LLVector2() { - mV[VX] = 0.f; - mV[VY] = 0.f; + clear(); } inline LLVector2::LLVector2(F32 x, F32 y) { - mV[VX] = x; - mV[VY] = y; + set(x, y); } inline LLVector2::LLVector2(const F32 *vec) { - mV[VX] = vec[VX]; - mV[VY] = vec[VY]; + set(vec); } inline LLVector2::LLVector2(const LLVector3 &vec) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; + set(vec.mV); } inline LLVector2::LLVector2(const LLSD &sd) @@ -155,28 +152,24 @@ inline LLVector2::LLVector2(const LLSD &sd) inline void LLVector2::clear() { - mV[VX] = 0.f; - mV[VY] = 0.f; + mV[VX] = mV[VY] = 0.f; } inline void LLVector2::setZero() { - mV[VX] = 0.f; - mV[VY] = 0.f; + clear(); } // deprecated inline void LLVector2::clearVec() { - mV[VX] = 0.f; - mV[VY] = 0.f; + clear(); } // deprecated inline void LLVector2::zeroVec() { - mV[VX] = 0.f; - mV[VY] = 0.f; + clear(); } inline void LLVector2::set(F32 x, F32 y) @@ -187,36 +180,31 @@ inline void LLVector2::set(F32 x, F32 y) inline void LLVector2::set(const LLVector2 &vec) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; + set(vec.mV); } inline void LLVector2::set(const F32 *vec) { - mV[VX] = vec[VX]; - mV[VY] = vec[VY]; + set(vec[VX], vec[VY]); } // deprecated inline void LLVector2::setVec(F32 x, F32 y) { - mV[VX] = x; - mV[VY] = y; + set(x, y); } // deprecated inline void LLVector2::setVec(const LLVector2 &vec) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; + set(vec); } // deprecated inline void LLVector2::setVec(const F32 *vec) { - mV[VX] = vec[VX]; - mV[VY] = vec[VY]; + set(vec); } @@ -224,7 +212,7 @@ inline void LLVector2::setVec(const F32 *vec) inline F32 LLVector2::length() const { - return sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY]); + return sqrt(lengthSquared()); } inline F32 LLVector2::lengthSquared() const @@ -234,61 +222,42 @@ inline F32 LLVector2::lengthSquared() const inline F32 LLVector2::normalize() { - F32 mag = sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY]); - F32 oomag; + F32 mag = length(); if (mag > FP_MAG_THRESHOLD) { - oomag = 1.f/mag; - mV[VX] *= oomag; - mV[VY] *= oomag; + *this /= mag; } else { - mV[VX] = 0.f; - mV[VY] = 0.f; + clear(); mag = 0; } - return (mag); + return mag; } // checker inline bool LLVector2::isFinite() const { - return (llfinite(mV[VX]) && llfinite(mV[VY])); + return llfinite(mV[VX]) && llfinite(mV[VY]); } // deprecated inline F32 LLVector2::magVec() const { - return sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY]); + return length(); } // deprecated inline F32 LLVector2::magVecSquared() const { - return mV[VX]*mV[VX] + mV[VY]*mV[VY]; + return lengthSquared(); } // deprecated inline F32 LLVector2::normVec() { - F32 mag = sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY]); - F32 oomag; - - if (mag > FP_MAG_THRESHOLD) - { - oomag = 1.f/mag; - mV[VX] *= oomag; - mV[VY] *= oomag; - } - else - { - mV[VX] = 0.f; - mV[VY] = 0.f; - mag = 0; - } - return (mag); + return normalize(); } inline const LLVector2& LLVector2::scaleVec(const LLVector2& vec) @@ -301,11 +270,7 @@ inline const LLVector2& LLVector2::scaleVec(const LLVector2& vec) inline bool LLVector2::isNull() const { - if (F_APPROXIMATELY_ZERO > mV[VX]*mV[VX] + mV[VY]*mV[VY]) - { - return true; - } - return false; + return F_APPROXIMATELY_ZERO > mV[VX]*mV[VX] + mV[VY]*mV[VY]; } @@ -405,10 +370,7 @@ inline const LLVector2& operator*=(LLVector2& a, F32 k) inline const LLVector2& operator/=(LLVector2& a, F32 k) { - F32 t = 1.f / k; - a.mV[VX] *= t; - a.mV[VY] *= t; - return a; + return a *= 1.f / k; } inline LLVector2 operator-(const LLVector2& a) diff --git a/indra/llmath/v3math.h b/indra/llmath/v3math.h index 551c7df6c9a..098ca5218c9 100644 --- a/indra/llmath/v3math.h +++ b/indra/llmath/v3math.h @@ -112,24 +112,24 @@ class LLVector3 const LLVector3& setVec(const LLVector4 &vec); // deprecated const LLVector3& setVec(const LLVector3d &vec); // deprecated - F32 length() const; // Returns magnitude of LLVector3 - F32 lengthSquared() const; // Returns magnitude squared of LLVector3 - F32 magVec() const; // deprecated - F32 magVecSquared() const; // deprecated + F32 length() const; // Returns magnitude of LLVector3 + F32 lengthSquared() const; // Returns magnitude squared of LLVector3 + F32 magVec() const; // deprecated + F32 magVecSquared() const; // deprecated - inline F32 normalize(); // Normalizes and returns the magnitude of LLVector3 - inline F32 normVec(); // deprecated + inline F32 normalize(); // Normalizes and returns the magnitude of LLVector3 + inline F32 normVec(); // deprecated - inline bool inRange( F32 min, F32 max ) const; // Returns true if all values of the vector are between min and max + inline bool inRange(F32 min, F32 max) const; // Returns true if all values of the vector are between min and max - const LLVector3& rotVec(F32 angle, const LLVector3 &vec); // Rotates about vec by angle radians - const LLVector3& rotVec(F32 angle, F32 x, F32 y, F32 z); // Rotates about x,y,z by angle radians - const LLVector3& rotVec(const LLMatrix3 &mat); // Rotates by LLMatrix4 mat - const LLVector3& rotVec(const LLQuaternion &q); // Rotates by LLQuaternion q - const LLVector3& transVec(const LLMatrix4& mat); // Transforms by LLMatrix4 mat (mat * v) + const LLVector3& rotVec(F32 angle, const LLVector3 &vec); // Rotates about vec by angle radians + const LLVector3& rotVec(F32 angle, F32 x, F32 y, F32 z); // Rotates about x,y,z by angle radians + const LLVector3& rotVec(const LLMatrix3 &mat); // Rotates by LLMatrix4 mat + const LLVector3& rotVec(const LLQuaternion &q); // Rotates by LLQuaternion q + const LLVector3& transVec(const LLMatrix4& mat); // Transforms by LLMatrix4 mat (mat * v) - const LLVector3& scaleVec(const LLVector3& vec); // scales per component by vec - LLVector3 scaledVec(const LLVector3& vec) const; // get a copy of this vector scaled by vec + const LLVector3& scaleVec(const LLVector3& vec); // scales per component by vec + LLVector3 scaledVec(const LLVector3& vec) const; // get a copy of this vector scaled by vec bool isNull() const; // Returns true if vector has a _very_small_ length bool isExactlyZero() const { return !mV[VX] && !mV[VY] && !mV[VZ]; } @@ -183,23 +183,17 @@ bool box_valid_and_non_zero(const LLVector3* box); inline LLVector3::LLVector3() { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; + clear(); } inline LLVector3::LLVector3(const F32 x, const F32 y, const F32 z) { - mV[VX] = x; - mV[VY] = y; - mV[VZ] = z; + set(x, y, z); } inline LLVector3::LLVector3(const F32 *vec) { - mV[VX] = vec[VX]; - mV[VY] = vec[VY]; - mV[VZ] = vec[VZ]; + set(vec); } inline LLVector3::LLVector3(const glm::vec3& vec) @@ -230,7 +224,7 @@ inline LLVector3::LLVector3(const LLVector3 ©) // checker inline bool LLVector3::isFinite() const { - return (llfinite(mV[VX]) && llfinite(mV[VY]) && llfinite(mV[VZ])); + return llfinite(mV[VX]) && llfinite(mV[VY]) && llfinite(mV[VZ]); } @@ -238,30 +232,22 @@ inline bool LLVector3::isFinite() const inline void LLVector3::clear() { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; + set(0.f, 0.f, 0.f); } inline void LLVector3::setZero() { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; + clear(); } inline void LLVector3::clearVec() { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; + clear(); } inline void LLVector3::zeroVec() { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; + clear(); } inline void LLVector3::set(F32 x, F32 y, F32 z) @@ -273,16 +259,12 @@ inline void LLVector3::set(F32 x, F32 y, F32 z) inline void LLVector3::set(const LLVector3& vec) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; - mV[VZ] = vec.mV[VZ]; + set(vec.mV[VX], vec.mV[VY], vec.mV[VZ]); } inline void LLVector3::set(const F32* vec) { - mV[VX] = vec[VX]; - mV[VY] = vec[VY]; - mV[VZ] = vec[VZ]; + set(vec[VX], vec[VY], vec[VZ]); } inline void LLVector3::set(const glm::vec4& vec) @@ -302,95 +284,66 @@ inline void LLVector3::set(const glm::vec3& vec) // deprecated inline void LLVector3::setVec(F32 x, F32 y, F32 z) { - mV[VX] = x; - mV[VY] = y; - mV[VZ] = z; + set(x, y, z); } // deprecated inline void LLVector3::setVec(const LLVector3& vec) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; - mV[VZ] = vec.mV[VZ]; + set(vec); } // deprecated inline void LLVector3::setVec(const F32* vec) { - mV[VX] = vec[0]; - mV[VY] = vec[1]; - mV[VZ] = vec[2]; + set(vec); } inline F32 LLVector3::normalize() { F32 mag = (F32) sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); - F32 oomag; if (mag > FP_MAG_THRESHOLD) { - oomag = 1.f/mag; - mV[VX] *= oomag; - mV[VY] *= oomag; - mV[VZ] *= oomag; + *this /= mag; } else { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; + clear(); mag = 0; } - return (mag); + return mag; } // deprecated inline F32 LLVector3::normVec() { - F32 mag = sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); - F32 oomag; - - if (mag > FP_MAG_THRESHOLD) - { - oomag = 1.f/mag; - mV[VX] *= oomag; - mV[VY] *= oomag; - mV[VZ] *= oomag; - } - else - { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; - mag = 0; - } - return (mag); + return normalize(); } // LLVector3 Magnitude and Normalization Functions -inline F32 LLVector3::length() const +inline F32 LLVector3::length() const { - return sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); + return sqrt(lengthSquared()); } -inline F32 LLVector3::lengthSquared() const +inline F32 LLVector3::lengthSquared() const { return mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]; } -inline F32 LLVector3::magVec() const +inline F32 LLVector3::magVec() const { - return sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); + return length(); } -inline F32 LLVector3::magVecSquared() const +inline F32 LLVector3::magVecSquared() const { - return mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]; + return lengthSquared(); } -inline bool LLVector3::inRange( F32 min, F32 max ) const +inline bool LLVector3::inRange(F32 min, F32 max) const { return mV[VX] >= min && mV[VX] <= max && mV[VY] >= min && mV[VY] <= max && @@ -416,7 +369,7 @@ inline F32 operator*(const LLVector3& a, const LLVector3& b) inline LLVector3 operator%(const LLVector3& a, const LLVector3& b) { - return LLVector3( a.mV[VY]*b.mV[VZ] - b.mV[VY]*a.mV[VZ], a.mV[VZ]*b.mV[VX] - b.mV[VZ]*a.mV[VX], a.mV[VX]*b.mV[VY] - b.mV[VX]*a.mV[VY] ); + return LLVector3(a.mV[VY]*b.mV[VZ] - b.mV[VY]*a.mV[VZ], a.mV[VZ]*b.mV[VX] - b.mV[VZ]*a.mV[VX], a.mV[VX]*b.mV[VY] - b.mV[VX]*a.mV[VY]); } inline LLVector3 operator/(const LLVector3& a, F32 k) @@ -476,7 +429,7 @@ inline const LLVector3& operator-=(LLVector3& a, const LLVector3& b) inline const LLVector3& operator%=(LLVector3& a, const LLVector3& b) { - LLVector3 ret( a.mV[VY]*b.mV[VZ] - b.mV[VY]*a.mV[VZ], a.mV[VZ]*b.mV[VX] - b.mV[VZ]*a.mV[VX], a.mV[VX]*b.mV[VY] - b.mV[VX]*a.mV[VY]); + LLVector3 ret(a.mV[VY]*b.mV[VZ] - b.mV[VY]*a.mV[VZ], a.mV[VZ]*b.mV[VX] - b.mV[VZ]*a.mV[VX], a.mV[VX]*b.mV[VY] - b.mV[VX]*a.mV[VY]); a = ret; return a; } @@ -499,9 +452,7 @@ inline const LLVector3& operator*=(LLVector3& a, const LLVector3& b) inline const LLVector3& operator/=(LLVector3& a, F32 k) { - a.mV[VX] /= k; - a.mV[VY] /= k; - a.mV[VZ] /= k; + a *= 1.f / k; return a; } @@ -526,7 +477,7 @@ inline F32 dist_vec(const LLVector3& a, const LLVector3& b) F32 x = a.mV[VX] - b.mV[VX]; F32 y = a.mV[VY] - b.mV[VY]; F32 z = a.mV[VZ] - b.mV[VZ]; - return sqrt( x*x + y*y + z*z ); + return sqrt(x*x + y*y + z*z); } inline F32 dist_vec_squared(const LLVector3& a, const LLVector3& b) @@ -551,10 +502,7 @@ inline LLVector3 projected_vec(const LLVector3& a, const LLVector3& b) { return ((a * b) / bb) * b; } - else - { - return b.zero; - } + return b.zero; } inline LLVector3 inverse_projected_vec(const LLVector3& a, const LLVector3& b) @@ -591,11 +539,7 @@ inline LLVector3 lerp(const LLVector3& a, const LLVector3& b, F32 u) inline bool LLVector3::isNull() const { - if ( F_APPROXIMATELY_ZERO > mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ] ) - { - return true; - } - return false; + return F_APPROXIMATELY_ZERO > mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]; } inline void update_min_max(LLVector3& min, LLVector3& max, const LLVector3& pos) @@ -636,7 +580,7 @@ inline F32 angle_between(const LLVector3& a, const LLVector3& b) ab = 0.0f; // get rid of negative zero } LLVector3 c = a % b; // crossproduct - return atan2f(sqrtf(c * c), ab); // return the angle + return atan2f(c.length(), ab); // return the angle } inline bool are_parallel(const LLVector3& a, const LLVector3& b, F32 epsilon) @@ -646,7 +590,7 @@ inline bool are_parallel(const LLVector3& a, const LLVector3& b, F32 epsilon) an.normalize(); bn.normalize(); F32 dot = an * bn; - if ( (1.0f - fabs(dot)) < epsilon) + if (1.0f - fabs(dot) < epsilon) { return true; } diff --git a/indra/llmath/v4math.h b/indra/llmath/v4math.h index 37492e7f98b..1cbd0d9a783 100644 --- a/indra/llmath/v4math.h +++ b/indra/llmath/v4math.h @@ -107,9 +107,9 @@ class LLVector4 F32 lengthSquared() const; // Returns magnitude squared of LLVector4 F32 normalize(); // Normalizes and returns the magnitude of LLVector4 - F32 magVec() const; // deprecated - F32 magVecSquared() const; // deprecated - F32 normVec(); // deprecated + F32 magVec() const; // deprecated + F32 magVecSquared() const; // deprecated + F32 normVec(); // deprecated // Sets all values to absolute value of their original values // Returns true if data changed @@ -118,8 +118,8 @@ class LLVector4 bool isExactlyClear() const { return (mV[VW] == 1.0f) && !mV[VX] && !mV[VY] && !mV[VZ]; } bool isExactlyZero() const { return !mV[VW] && !mV[VX] && !mV[VY] && !mV[VZ]; } - const LLVector4& rotVec(const LLMatrix4 &mat); // Rotates by MAT4 mat - const LLVector4& rotVec(const LLQuaternion &q); // Rotates by QUAT q + const LLVector4& rotVec(const LLMatrix4 &mat); // Rotates by MAT4 mat + const LLVector4& rotVec(const LLQuaternion &q); // Rotates by QUAT q const LLVector4& scaleVec(const LLVector4& vec); // Scales component-wise by vec @@ -159,34 +159,22 @@ LLVector4 lerp(const LLVector4 &a, const LLVector4 &b, F32 u); // Returns a vect inline LLVector4::LLVector4(void) { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; - mV[VW] = 1.f; + clear(); } inline LLVector4::LLVector4(F32 x, F32 y, F32 z) { - mV[VX] = x; - mV[VY] = y; - mV[VZ] = z; - mV[VW] = 1.f; + set(x, y, z, 1.f); } inline LLVector4::LLVector4(F32 x, F32 y, F32 z, F32 w) { - mV[VX] = x; - mV[VY] = y; - mV[VZ] = z; - mV[VW] = w; + set(x, y, z, w); } inline LLVector4::LLVector4(const F32 *vec) { - mV[VX] = vec[VX]; - mV[VY] = vec[VY]; - mV[VZ] = vec[VZ]; - mV[VW] = vec[VW]; + set(vec); } inline LLVector4::LLVector4(const F64 *vec) @@ -215,18 +203,12 @@ inline LLVector4::LLVector4(const LLVector2 &vec, F32 z, F32 w) inline LLVector4::LLVector4(const LLVector3 &vec) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; - mV[VZ] = vec.mV[VZ]; - mV[VW] = 1.f; + set(vec, 1.f); } inline LLVector4::LLVector4(const LLVector3 &vec, F32 w) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; - mV[VZ] = vec.mV[VZ]; - mV[VW] = w; + set(vec, w); } inline LLVector4::LLVector4(const LLSD &sd) @@ -252,43 +234,31 @@ inline LLVector4::LLVector4(const glm::vec4& vec) inline bool LLVector4::isFinite() const { - return (llfinite(mV[VX]) && llfinite(mV[VY]) && llfinite(mV[VZ]) && llfinite(mV[VW])); + return llfinite(mV[VX]) && llfinite(mV[VY]) && llfinite(mV[VZ]) && llfinite(mV[VW]); } // Clear and Assignment Functions inline void LLVector4::clear() { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; - mV[VW] = 1.f; + set(0.f, 0.f, 0.f, 1.f); } // deprecated inline void LLVector4::clearVec() { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; - mV[VW] = 1.f; + clear(); } // deprecated inline void LLVector4::zeroVec() { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; - mV[VW] = 0.f; + set(0.f, 0.f, 0.f, 0.f); } inline void LLVector4::set(F32 x, F32 y, F32 z) { - mV[VX] = x; - mV[VY] = y; - mV[VZ] = z; - mV[VW] = 1.f; + set(x, y, z, 1.f); } inline void LLVector4::set(F32 x, F32 y, F32 z, F32 w) @@ -301,10 +271,7 @@ inline void LLVector4::set(F32 x, F32 y, F32 z, F32 w) inline void LLVector4::set(const LLVector4& vec) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; - mV[VZ] = vec.mV[VZ]; - mV[VW] = vec.mV[VW]; + set(vec.mV); } inline void LLVector4::set(const LLVector3& vec, F32 w) @@ -322,7 +289,6 @@ inline void LLVector4::set(const F32* vec) mV[VZ] = vec[VZ]; mV[VW] = vec[VW]; } - inline void LLVector4::set(const glm::vec4& vec) { mV[VX] = vec.x; @@ -342,68 +308,53 @@ inline void LLVector4::set(const glm::vec3& vec, F32 w) // deprecated inline void LLVector4::setVec(F32 x, F32 y, F32 z) { - mV[VX] = x; - mV[VY] = y; - mV[VZ] = z; - mV[VW] = 1.f; + set(x, y, z); } // deprecated inline void LLVector4::setVec(F32 x, F32 y, F32 z, F32 w) { - mV[VX] = x; - mV[VY] = y; - mV[VZ] = z; - mV[VW] = w; + set(x, y, z, w); } // deprecated inline void LLVector4::setVec(const LLVector4& vec) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; - mV[VZ] = vec.mV[VZ]; - mV[VW] = vec.mV[VW]; + set(vec); } // deprecated inline void LLVector4::setVec(const LLVector3& vec, F32 w) { - mV[VX] = vec.mV[VX]; - mV[VY] = vec.mV[VY]; - mV[VZ] = vec.mV[VZ]; - mV[VW] = w; + set(vec, w); } // deprecated inline void LLVector4::setVec(const F32* vec) { - mV[VX] = vec[VX]; - mV[VY] = vec[VY]; - mV[VZ] = vec[VZ]; - mV[VW] = vec[VW]; + set(vec); } // LLVector4 Magnitude and Normalization Functions -inline F32 LLVector4::length() const +inline F32 LLVector4::length() const { - return sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); + return sqrt(lengthSquared()); } -inline F32 LLVector4::lengthSquared() const +inline F32 LLVector4::lengthSquared() const { return mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]; } -inline F32 LLVector4::magVec() const +inline F32 LLVector4::magVec() const { - return sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); + return length(); } -inline F32 LLVector4::magVecSquared() const +inline F32 LLVector4::magVecSquared() const { - return mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]; + return lengthSquared(); } // LLVector4 Operators @@ -422,7 +373,7 @@ inline LLVector4 operator-(const LLVector4& a, const LLVector4& b) inline F32 operator*(const LLVector4& a, const LLVector4& b) { - return (a.mV[VX]*b.mV[VX] + a.mV[VY]*b.mV[VY] + a.mV[VZ]*b.mV[VZ]); + return a.mV[VX]*b.mV[VX] + a.mV[VY]*b.mV[VY] + a.mV[VZ]*b.mV[VZ]; } inline LLVector4 operator%(const LLVector4& a, const LLVector4& b) @@ -495,11 +446,7 @@ inline const LLVector4& operator*=(LLVector4& a, F32 k) inline const LLVector4& operator/=(LLVector4& a, F32 k) { - F32 t = 1.f / k; - a.mV[VX] *= t; - a.mV[VY] *= t; - a.mV[VZ] *= t; - return a; + return a *= 1.f / k; } inline LLVector4 operator-(const LLVector4& a) @@ -517,16 +464,16 @@ inline LLVector4::operator glm::vec4() const return glm::make_vec4(mV); } -inline F32 dist_vec(const LLVector4& a, const LLVector4& b) +inline F32 dist_vec(const LLVector4& a, const LLVector4& b) { LLVector4 vec = a - b; - return (vec.length()); + return vec.length(); } -inline F32 dist_vec_squared(const LLVector4& a, const LLVector4& b) +inline F32 dist_vec_squared(const LLVector4& a, const LLVector4& b) { LLVector4 vec = a - b; - return (vec.lengthSquared()); + return vec.lengthSquared(); } inline LLVector4 lerp(const LLVector4& a, const LLVector4& b, F32 u) @@ -538,17 +485,13 @@ inline LLVector4 lerp(const LLVector4& a, const LLVector4& b, F32 u) a.mV[VW] + (b.mV[VW] - a.mV[VW]) * u); } -inline F32 LLVector4::normalize() +inline F32 LLVector4::normalize() { F32 mag = sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); - F32 oomag; if (mag > FP_MAG_THRESHOLD) { - oomag = 1.f/mag; - mV[VX] *= oomag; - mV[VY] *= oomag; - mV[VZ] *= oomag; + *this /= mag; } else { @@ -557,30 +500,13 @@ inline F32 LLVector4::normalize() mV[VZ] = 0.f; mag = 0.f; } - return (mag); + return mag; } // deprecated -inline F32 LLVector4::normVec() +inline F32 LLVector4::normVec() { - F32 mag = sqrt(mV[VX]*mV[VX] + mV[VY]*mV[VY] + mV[VZ]*mV[VZ]); - F32 oomag; - - if (mag > FP_MAG_THRESHOLD) - { - oomag = 1.f/mag; - mV[VX] *= oomag; - mV[VY] *= oomag; - mV[VZ] *= oomag; - } - else - { - mV[VX] = 0.f; - mV[VY] = 0.f; - mV[VZ] = 0.f; - mag = 0.f; - } - return (mag); + return normalize(); } // Because apparently some parts of the viewer use this for color info. diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index a33f25307e3..00ef79ce7fd 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -66,7 +66,12 @@ LLModel::~LLModel() { if (mDecompID >= 0) { - LLConvexDecomposition::getInstance()->deleteDecomposition(mDecompID); + // can be null on shutdown + LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); + if (decomp) + { + decomp->deleteDecomposition(mDecompID); + } } mPhysics.mMesh.clear(); } diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h index 3fdcf9f7f2b..bb0b8ce04fd 100644 --- a/indra/llui/llaccordionctrltab.h +++ b/indra/llui/llaccordionctrltab.h @@ -140,7 +140,7 @@ class LLAccordionCtrlTab : public LLUICtrl S32 notify(const LLSD& info); bool notifyChildren(const LLSD& info); - void draw(); + virtual void draw(); void storeOpenCloseState(); void restoreOpenCloseState(); diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index 8178bada423..dfe0a71b745 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -69,7 +69,7 @@ const LLRect& LLFlatListView::getItemsRect() const bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null*/, EAddPosition pos /*= ADD_BOTTOM*/,bool rearrange /*= true*/) { if (!item) return false; - if (value.isUndefined()) return false; + if (value.isUndefined()) return false; // item stays an orphan?!!! //force uniqueness of items, easiest check but unreliable if (item->getParent() == mItemsPanel) return false; @@ -1346,9 +1346,17 @@ bool LLFlatListViewEx::getForceShowingUnmatchedItems() const return mForceShowingUnmatchedItems; } -void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show) +void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show, bool notify_parent) +{ + if (mForceShowingUnmatchedItems != show) { mForceShowingUnmatchedItems = show; + if (!mFilterSubString.empty()) + { + updateNoItemsMessage(mFilterSubString); + filterItems(false, true); + } + } } void LLFlatListViewEx::setFilterSubString(const std::string& filter_str, bool notify_parent) @@ -1416,6 +1424,7 @@ void LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) if (visibility_changed && notify_parent) { + rearrangeItems(); notifyParentItemsRectChanged(); } } diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 62712311835..1f22360a8a8 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -484,7 +484,11 @@ class LLFlatListViewEx : public LLFlatListView bool getForceShowingUnmatchedItems() const; - void setForceShowingUnmatchedItems(bool show); + /** + * Sets filtered out items to stay visible. Can result in rect changes, + * so can notify_parent if rect changes + */ + void setForceShowingUnmatchedItems(bool show, bool notify_parent); /** * Sets up new filter string and filters the list. diff --git a/indra/llui/llfolderview.h b/indra/llui/llfolderview.h index bdce9dec54b..368a86ea849 100644 --- a/indra/llui/llfolderview.h +++ b/indra/llui/llfolderview.h @@ -221,6 +221,7 @@ class LLFolderView : public LLFolderViewFolder, public LLEditMenuHandler void scrollToShowSelection(); void scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect); void setScrollContainer( LLScrollContainer* parent ) { mScrollContainer = parent; } + LLScrollContainer* getScrollContainer() { return mScrollContainer; } LLRect getVisibleRect(); bool search(LLFolderViewItem* first_item, const std::string &search_string, bool backward); diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 6d0cfcba954..9ca77dbe46b 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -31,11 +31,12 @@ #include "llfolderviewitem.h" #include "llfolderview.h" #include "llfolderviewmodel.h" -#include "llpanel.h" #include "llcallbacklist.h" #include "llcriticaldamp.h" #include "llclipboard.h" #include "llfocusmgr.h" // gFocusMgr +#include "llnotificationsutil.h" +#include "llpanel.h" #include "lltrans.h" #include "llwindow.h" @@ -60,7 +61,11 @@ LLUIColor LLFolderViewItem::sSearchStatusColor; S32 LLFolderViewItem::sTopPad = 0; LLUIImagePtr LLFolderViewItem::sFolderArrowImg; LLUIImagePtr LLFolderViewItem::sSelectionImg; +LLUIImagePtr LLFolderViewItem::sFavoriteImg; +LLUIImagePtr LLFolderViewItem::sFavoriteContentImg; LLFontGL* LLFolderViewItem::sSuffixFont = nullptr; +LLUIColor LLFolderViewItem::sFavoriteColor; +bool LLFolderViewItem::sColorSetInitialized = false; // only integers can be initialized in header const F32 LLFolderViewItem::FOLDER_CLOSE_TIME_CONSTANT = 0.02f; @@ -68,6 +73,9 @@ const F32 LLFolderViewItem::FOLDER_OPEN_TIME_CONSTANT = 0.03f; const LLColor4U DEFAULT_WHITE(255, 255, 255); +constexpr S32 FAVORITE_IMAGE_SIZE = 14; +constexpr S32 FAVORITE_IMAGE_PAD = 3; + //static LLFontGL* LLFolderViewItem::getLabelFontForStyle(U8 style) @@ -102,6 +110,8 @@ void LLFolderViewItem::initClass() sTopPad = default_params.item_top_pad; sFolderArrowImg = default_params.folder_arrow_image; sSelectionImg = default_params.selection_image; + sFavoriteImg = default_params.favorite_image; + sFavoriteContentImg = default_params.favorite_content_image; sSuffixFont = getLabelFontForStyle(LLFontGL::NORMAL); sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); @@ -121,6 +131,8 @@ void LLFolderViewItem::cleanupClass() sFonts.clear(); sFolderArrowImg = nullptr; sSelectionImg = nullptr; + sFavoriteImg = nullptr; + sFavoriteContentImg = nullptr; sSuffixFont = nullptr; } @@ -129,13 +141,15 @@ void LLFolderViewItem::cleanupClass() LLFolderViewItem::Params::Params() : root(), listener(), + favorite_image("favorite_image"), + favorite_content_image("favorite_content_image"), folder_arrow_image("folder_arrow_image"), folder_indentation("folder_indentation"), selection_image("selection_image"), item_height("item_height"), item_top_pad("item_top_pad"), creation_date(), - allow_wear("allow_wear", true), + marketplace_item("marketplace_item", false), allow_drop("allow_drop", true), font_color("font_color"), font_highlight_color("font_highlight_color"), @@ -155,6 +169,8 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) : LLView(p), mLabelWidth(0), mLabelWidthDirty(false), + mIsFavorite(false), + mHasFavorites(false), mSuffixNeedsRefresh(false), mLabelPaddingRight(DEFAULT_LABEL_PADDING_RIGHT), mParentFolder( NULL ), @@ -175,7 +191,7 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mRoot(p.root), mViewModelItem(p.listener), mIsMouseOverTitle(false), - mAllowWear(p.allow_wear), + mMarketplaceItem(p.marketplace_item), mAllowDrop(p.allow_drop), mFontColor(p.font_color), mFontHighlightColor(p.font_highlight_color), @@ -189,6 +205,21 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mMaxFolderItemOverlap(p.max_folder_item_overlap), mDoubleClickOverride(p.double_click_override) { + if (!sColorSetInitialized) + { + sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE); + sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE); + sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE); + sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE); + sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE); + sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); + sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); + sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); + sFavoriteColor = LLUIColorTable::instance().getColor("InventoryFavoriteColor", DEFAULT_WHITE); + sColorSetInitialized = true; + } + if (mViewModelItem) { mViewModelItem->setFolderViewItem(this); @@ -211,6 +242,7 @@ bool LLFolderViewItem::postBuild() // getDisplayName() is expensive (due to internal getLabelSuffix() and name building) // it also sets search strings so it requires a filter reset mLabel = utf8str_to_wstring(vmi->getDisplayName()); + mIsFavorite = vmi->isFavorite() && !vmi->isItemInTrash(); setToolTip(vmi->getName()); // Dirty the filter flag of the model from the view (CHUI-849) @@ -325,6 +357,7 @@ void LLFolderViewItem::refresh() mLabel = utf8str_to_wstring(vmi.getDisplayName()); mLabelFontBuffer.reset(); + mIsFavorite = vmi.isFavorite() && !vmi.isItemInTrash(); setToolTip(vmi.getName()); // icons are slightly expensive to get, can be optimized // see LLInventoryIcon::getIcon() @@ -359,6 +392,8 @@ void LLFolderViewItem::refreshSuffix() mIconOpen = vmi->getIconOpen(); mIconOverlay = vmi->getIconOverlay(); + mIsFavorite = vmi->isFavorite() && !vmi->isItemInTrash(); + if (mRoot->useLabelSuffix()) { // Very Expensive! @@ -428,6 +463,10 @@ S32 LLFolderViewItem::arrange( S32* width, S32* height ) } mLabelWidth = getLabelXPos() + getLabelFontForStyle(mLabelStyle)->getWidth(mLabel.c_str()) + getLabelFontForStyle(LLFontGL::NORMAL)->getWidth(mLabelSuffix.c_str()) + mLabelPaddingRight; mLabelWidthDirty = false; + if (mIsFavorite) + { + mLabelWidth += FAVORITE_IMAGE_SIZE + FAVORITE_IMAGE_PAD; + } } *width = llmax(*width, mLabelWidth); @@ -554,10 +593,15 @@ void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags) void LLFolderViewItem::openItem( void ) { - if (mAllowWear || !getViewModelItem()->isItemWearable()) + if (!mMarketplaceItem || !getViewModelItem()->isItemWearable()) { getViewModelItem()->openItem(); } + else if (mMarketplaceItem) + { + // Wearing an object from any listing, active or not, is verbotten + LLNotificationsUtil::add("AlertMerchantListingCannotWear"); + } } void LLFolderViewItem::rename(const std::string& new_name) @@ -771,6 +815,45 @@ void LLFolderViewItem::drawOpenFolderArrow() } } +void LLFolderViewItem::drawFavoriteIcon() +{ + static LLUICachedControl draw_star("InventoryFavoritesUseStar", true); + static LLUICachedControl draw_hollow_star("InventoryFavoritesUseHollowStar", true); + + LLUIImage* favorite_image = nullptr; + if (draw_star && mIsFavorite) + { + favorite_image = sFavoriteImg; + } + else if (draw_hollow_star && mHasFavorites && !isOpen()) + { + favorite_image = sFavoriteContentImg; + } + + if (favorite_image) + { + S32 x_offset = 0; + LLScrollContainer* scroll = mRoot->getScrollContainer(); + if (scroll) + { + S32 width = scroll->getVisibleContentRect().getWidth(); + S32 offset = scroll->getDocPosHorizontal(); + x_offset = width + offset; + } + else + { + x_offset = getRect().getWidth(); + } + gl_draw_scaled_image( + x_offset - FAVORITE_IMAGE_SIZE - FAVORITE_IMAGE_PAD, + getRect().getHeight() - mItemHeight + FAVORITE_IMAGE_PAD, + FAVORITE_IMAGE_SIZE, + FAVORITE_IMAGE_SIZE, + favorite_image->getImage(), + sFgColor); + } +} + /*virtual*/ bool LLFolderViewItem::isHighlightAllowed() { return mIsSelected; @@ -928,6 +1011,7 @@ void LLFolderViewItem::draw() { drawOpenFolderArrow(); } + drawFavoriteIcon(); drawHighlight(show_context, filled, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); @@ -999,7 +1083,20 @@ void LLFolderViewItem::draw() } } - LLColor4 color = (mIsSelected && filled) ? mFontHighlightColor : mFontColor; + static LLUICachedControl highlight_color("InventoryFavoritesColorText", true); + LLColor4 color; + if (mIsSelected && filled) + { + color = mFontHighlightColor; + } + else if (mIsFavorite && highlight_color) + { + color = sFavoriteColor; + } + else + { + color = mFontColor; + } if (isFadeItem()) { @@ -1093,7 +1190,8 @@ LLFolderViewFolder::LLFolderViewFolder( const LLFolderViewItem::Params& p ): mIsFolderComplete(false), // folder might have children that are not loaded yet. mAreChildrenInited(false), // folder might have children that are not built yet. mLastArrangeGeneration( -1 ), - mLastCalculatedWidth(0) + mLastCalculatedWidth(0), + mFavoritesDirtyFlags(0) { } @@ -1119,6 +1217,11 @@ LLFolderViewFolder::~LLFolderViewFolder( void ) // The LLView base class takes care of object destruction. make sure that we // don't have mouse or keyboard focus gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + + if (mFavoritesDirtyFlags) + { + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, this); + } } // addToFolder() returns true if it succeeds. false otherwise @@ -1762,6 +1865,140 @@ bool LLFolderViewFolder::isMovable() return true; } +void LLFolderViewFolder::updateHasFavorites(bool new_childs_value) +{ + if (mFavoritesDirtyFlags == 0) + { + gIdleCallbacks.addFunction(&LLFolderViewFolder::onIdleUpdateFavorites, this); + } + if (new_childs_value) + { + mFavoritesDirtyFlags |= FAVORITE_ADDED; + } + else + { + mFavoritesDirtyFlags |= FAVORITE_REMOVED; + } +} + +void LLFolderViewFolder::onIdleUpdateFavorites(void* data) +{ + LLFolderViewFolder* self = reinterpret_cast(data); + if (self->mFavoritesDirtyFlags == 0) + { + // already processed either on previous run or by a different callback + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, self); + return; + } + + if (self->getViewModelItem()->isItemInTrash()) + { + // do not display favorite-stars in trash + self->mFavoritesDirtyFlags = 0; + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, self); + return; + } + + if (self->mFavoritesDirtyFlags == FAVORITE_ADDED) + { + if (!self->mHasFavorites) + { + // propagate up, exclude root + LLFolderViewFolder* parent = self; + while (parent + && (!parent->hasFavorites() || parent->mFavoritesDirtyFlags) + && !parent->getViewModelItem()->isAgentInventoryRoot()) + { + parent->setHasFavorites(true); + if (parent->mFavoritesDirtyFlags) + { + // Parent will remove onIdleUpdateFavorites later, don't remove now, + // We are inside gIdleCallbacks. Removing 'self' callback is safe, + // but removing 'parent' can invalidate following iterator + parent->mFavoritesDirtyFlags = 0; + } + parent = parent->getParentFolder(); + } + } + else + { + // already up to date + self->mFavoritesDirtyFlags = 0; + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, self); + } + } + else if (self->mFavoritesDirtyFlags > FAVORITE_ADDED) + { + // full check + LLFolderViewFolder* parent = self; + while (parent && !parent->getViewModelItem()->isAgentInventoryRoot()) + { + bool has_favorites = false; + for (items_t::iterator iter = parent->mItems.begin(); + iter != parent->mItems.end();) + { + items_t::iterator iit = iter++; + if ((*iit)->isFavorite()) + { + has_favorites = true; + break; + } + } + + for (folders_t::iterator iter = parent->mFolders.begin(); + iter != parent->mFolders.end() && !has_favorites;) + { + folders_t::iterator fit = iter++; + if ((*fit)->isFavorite() || (*fit)->hasFavorites()) + { + has_favorites = true; + break; + } + } + + if (!has_favorites) + { + if (parent->hasFavorites()) + { + parent->setHasFavorites(false); + } + else + { + // Nothing changed + break; + } + } + else + { + // propagate up, exclude root + while (parent + && (!parent->hasFavorites() || parent->mFavoritesDirtyFlags) + && !parent->getViewModelItem()->isAgentInventoryRoot()) + { + parent->setHasFavorites(true); + if (parent->mFavoritesDirtyFlags) + { + // Parent will remove onIdleUpdateFavorites later, don't remove now, + // We are inside gIdleCallbacks. Removing 'self' callback is safe, + // but removing 'parent' can invalidate following iterator + parent->mFavoritesDirtyFlags = 0; + } + parent = parent->getParentFolder(); + } + break; + } + if (parent->mFavoritesDirtyFlags) + { + // Parent will remove onIdleUpdateFavorites later, don't remove now. + // We are inside gIdleCallbacks. Removing 'self' callback is safe, + // but removing 'parent' can invalidate following iterator + parent->mFavoritesDirtyFlags = 0; + } + parent = parent->getParentFolder(); + } + } +} + bool LLFolderViewFolder::isRemovable() { diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index 2ee018a90af..c9b003b892f 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -50,7 +50,9 @@ class LLFolderViewItem : public LLView public: struct Params : public LLInitParam::Block { - Optional folder_arrow_image, + Optional favorite_image, + favorite_content_image, + folder_arrow_image, selection_image; Mandatory root; Mandatory listener; @@ -60,7 +62,7 @@ class LLFolderViewItem : public LLView item_top_pad; Optional creation_date; - Optional allow_wear; + Optional marketplace_item; Optional allow_drop; Optional font_color; @@ -93,6 +95,8 @@ class LLFolderViewItem : public LLView LLWString mLabel; S32 mLabelWidth; bool mLabelWidthDirty; + bool mIsFavorite; + bool mHasFavorites; S32 mLabelPaddingRight; LLFolderViewFolder* mParentFolder; LLPointer mViewModelItem; @@ -122,7 +126,7 @@ class LLFolderViewItem : public LLView mIsCurSelection, mDragAndDropTarget, mIsMouseOverTitle, - mAllowWear, + mMarketplaceItem, mAllowDrop, mSingleFolderMode, mDoubleClickOverride, @@ -133,6 +137,7 @@ class LLFolderViewItem : public LLView LLUIColor mFontColor; LLUIColor mFontHighlightColor; + static bool sColorSetInitialized; // For now assuming all colors are the same in derived classes. static LLUIColor sFgColor; @@ -145,6 +150,8 @@ class LLFolderViewItem : public LLView static LLUIColor sFilterTextColor; static LLUIColor sSuffixColor; static LLUIColor sSearchStatusColor; + static LLUIColor sFavoriteColor; + // this is an internal method used for adding items to folders. A // no-op at this level, but reimplemented in derived classes. @@ -208,6 +215,8 @@ class LLFolderViewItem : public LLView // Returns true is this object and all of its children can be moved virtual bool isMovable(); + bool isFavorite() const { return mIsFavorite; } + // destroys this item recursively virtual void destroyView(); @@ -298,6 +307,7 @@ class LLFolderViewItem : public LLView // virtual void handleDropped(); virtual void draw(); void drawOpenFolderArrow(); + void drawFavoriteIcon(); void drawHighlight(bool showContent, bool hasKeyboardFocus, const LLUIColor& selectColor, const LLUIColor& flashColor, const LLUIColor& outlineColor, const LLUIColor& mouseOverColor); void drawLabel(const LLFontGL* font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x); virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, @@ -311,6 +321,8 @@ class LLFolderViewItem : public LLView static S32 sTopPad; static LLUIImagePtr sFolderArrowImg; static LLUIImagePtr sSelectionImg; + static LLUIImagePtr sFavoriteImg; + static LLUIImagePtr sFavoriteContentImg; static LLFontGL* sSuffixFont; LLFontVertexBuffer mLabelFontBuffer; @@ -400,6 +412,18 @@ class LLFolderViewFolder : public LLFolderViewItem // Returns true is this object and all of its children can be moved virtual bool isMovable(); + bool isFavorite() const { return mIsFavorite; } + bool hasFavorites() const { return mHasFavorites; } + void setHasFavorites(bool val) { mHasFavorites = val; } + void updateHasFavorites(bool new_childs_value); +private: + static void onIdleUpdateFavorites(void* data); + + constexpr static S32 FAVORITE_ADDED = 1; + constexpr static S32 FAVORITE_REMOVED = 2; + S32 mFavoritesDirtyFlags { 0 }; +public: + // destroys this folder, and all children virtual void destroyView(); void destroyRoot(); diff --git a/indra/llui/llfolderviewmodel.h b/indra/llui/llfolderviewmodel.h index 9372818ca57..2865b789b9f 100644 --- a/indra/llui/llfolderviewmodel.h +++ b/indra/llui/llfolderviewmodel.h @@ -162,6 +162,7 @@ class LLFolderViewModelItem : public LLRefCount virtual void navigateToFolder(bool new_window = false, bool change_mode = false) = 0; + virtual bool isFavorite() const = 0; virtual bool isItemWearable() const { return false; } virtual bool isItemRenameable() const = 0; @@ -170,7 +171,8 @@ class LLFolderViewModelItem : public LLRefCount virtual bool isItemMovable( void ) const = 0; // Can be moved to another folder virtual void move( LLFolderViewModelItem* parent_listener ) = 0; - virtual bool isItemRemovable( bool check_worn = true ) const = 0; // Can be destroyed + virtual bool isItemRemovable( bool check_worn = true) const = 0; // Can be destroyed + virtual bool isItemInTrash(void) const = 0; virtual bool removeItem() = 0; virtual void removeBatch(std::vector& batch) = 0; @@ -183,6 +185,9 @@ class LLFolderViewModelItem : public LLRefCount virtual void pasteFromClipboard() = 0; virtual void pasteLinkFromClipboard() = 0; + virtual bool isAgentInventory() const = 0; + virtual bool isAgentInventoryRoot() const = 0; + virtual void buildContextMenu(LLMenuGL& menu, U32 flags) = 0; virtual bool potentiallyVisible() = 0; // is the item definitely visible or we haven't made up our minds yet? @@ -219,6 +224,7 @@ class LLFolderViewModelItem : public LLRefCount virtual S32 getSortVersion() = 0; virtual void setSortVersion(S32 version) = 0; virtual void setParent(LLFolderViewModelItem* parent) = 0; + virtual const LLFolderViewModelItem* getParent() = 0; virtual bool hasParent() = 0; protected: @@ -249,14 +255,14 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem mChildren.clear(); } - void requestSort() { mSortVersion = -1; } - S32 getSortVersion() { return mSortVersion; } - void setSortVersion(S32 version) { mSortVersion = version;} + void requestSort() override { mSortVersion = -1; } + S32 getSortVersion() override { return mSortVersion; } + void setSortVersion(S32 version) override { mSortVersion = version;} - S32 getLastFilterGeneration() const { return mLastFilterGeneration; } + S32 getLastFilterGeneration() const override { return mLastFilterGeneration; } S32 getLastFolderFilterGeneration() const { return mLastFolderFilterGeneration; } - S32 getMarkedDirtyGeneration() const { return mMarkedDirtyGeneration; } - void dirtyFilter() + S32 getMarkedDirtyGeneration() const override { return mMarkedDirtyGeneration; } + void dirtyFilter() override { if(mMarkedDirtyGeneration < 0) { @@ -271,7 +277,7 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem mParent->dirtyFilter(); } } - void dirtyDescendantsFilter() + void dirtyDescendantsFilter() override { mMostFilteredDescendantGeneration = -1; if (mParent) @@ -279,13 +285,13 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem mParent->dirtyDescendantsFilter(); } } - bool hasFilterStringMatch(); - std::string::size_type getFilterStringOffset(); - std::string::size_type getFilterStringSize(); + bool hasFilterStringMatch() override; + std::string::size_type getFilterStringOffset() override; + std::string::size_type getFilterStringSize() override; - typedef std::list child_list_t; + typedef std::list > child_list_t; - virtual void addChild(LLFolderViewModelItem* child) + virtual void addChild(LLFolderViewModelItem* child) override { mChildren.push_back(child); child->setParent(this); @@ -293,15 +299,15 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem requestSort(); } - virtual void removeChild(LLFolderViewModelItem* child) + virtual void removeChild(LLFolderViewModelItem* child) override final { - mChildren.remove(child); child->setParent(NULL); + mChildren.remove(child); dirtyDescendantsFilter(); dirtyFilter(); } - virtual void clearChildren() + virtual void clearChildren() override { // We are working with models that belong to views as LLPointers, clean the list, let poiters handle the rest std::for_each(mChildren.begin(), mChildren.end(), [](LLFolderViewModelItem* c) {c->setParent(NULL); }); @@ -314,7 +320,7 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem child_list_t::const_iterator getChildrenEnd() const { return mChildren.end(); } child_list_t::size_type getChildrenCount() const { return mChildren.size(); } - void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) + void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) override { mPassedFilter = passed; mLastFilterGeneration = filter_generation; @@ -323,20 +329,20 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem mMarkedDirtyGeneration = -1; } - void setPassedFolderFilter(bool passed, S32 filter_generation) + void setPassedFolderFilter(bool passed, S32 filter_generation) override { mPassedFolderFilter = passed; mLastFolderFilterGeneration = filter_generation; } - virtual bool potentiallyVisible() + virtual bool potentiallyVisible() override { return passedFilter() // we've passed the filter || (getLastFilterGeneration() < mRootViewModel.getFilter().getFirstSuccessGeneration()) // or we don't know yet || descendantsPassedFilter(); } - virtual bool passedFilter(S32 filter_generation = -1) + virtual bool passedFilter(S32 filter_generation = -1) override { if (filter_generation < 0) { @@ -347,7 +353,7 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem return passed_folder_filter && (passed_filter || descendantsPassedFilter(filter_generation)); } - virtual bool descendantsPassedFilter(S32 filter_generation = -1) + virtual bool descendantsPassedFilter(S32 filter_generation = -1) override { if (filter_generation < 0) { @@ -356,10 +362,10 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem return mMostFilteredDescendantGeneration >= filter_generation; } - protected: - virtual void setParent(LLFolderViewModelItem* parent) { mParent = parent; } - virtual bool hasParent() { return mParent != NULL; } + virtual void setParent(LLFolderViewModelItem* parent) override final { mParent = parent; } + virtual const LLFolderViewModelItem* getParent() override { return mParent; }; + virtual bool hasParent() override { return mParent != NULL; } S32 mSortVersion; bool mPassedFilter; @@ -376,7 +382,7 @@ class LLFolderViewModelItemCommon : public LLFolderViewModelItem LLFolderViewModelItem* mParent; LLFolderViewModelInterface& mRootViewModel; - void setFolderViewItem(LLFolderViewItem* folder_view_item) { mFolderViewItem = folder_view_item;} + void setFolderViewItem(LLFolderViewItem* folder_view_item) override { mFolderViewItem = folder_view_item;} LLFolderViewItem* mFolderViewItem; }; @@ -390,15 +396,15 @@ class LLFolderViewModelCommon : public LLFolderViewModelInterface mFolderView(NULL) {} - virtual void requestSortAll() + virtual void requestSortAll() override { // sort everything mTargetSortVersion++; } - virtual std::string getStatusText(bool is_empty_folder = false); - virtual void filter(); + virtual std::string getStatusText(bool is_empty_folder = false) override; + virtual void filter() override; - void setFolderView(LLFolderView* folder_view) { mFolderView = folder_view;} + void setFolderView(LLFolderView* folder_view) override { mFolderView = folder_view;} protected: bool needsSort(class LLFolderViewModelItem* item); @@ -428,14 +434,14 @@ class LLFolderViewModel : public LLFolderViewModelCommon virtual const SortType& getSorter() const { return *mSorter; } virtual void setSorter(const SortType& sorter) { mSorter.reset(new SortType(sorter)); requestSortAll(); } - virtual FilterType& getFilter() { return *mFilter; } - virtual const FilterType& getFilter() const { return *mFilter; } + virtual FilterType& getFilter() override { return *mFilter; } + virtual const FilterType& getFilter() const override { return *mFilter; } virtual void setFilter(const FilterType& filter) { mFilter.reset(new FilterType(filter)); } // By default, we assume the content is available. If a network fetch mechanism is implemented for the model, // this method needs to be overloaded and return the relevant fetch status. - virtual bool contentsReady() { return true; } - virtual bool isFolderComplete(LLFolderViewFolder* folder) { return true; } + virtual bool contentsReady() override { return true; } + virtual bool isFolderComplete(LLFolderViewFolder* folder) override { return true; } struct ViewModelCompare { @@ -456,7 +462,7 @@ class LLFolderViewModel : public LLFolderViewModelCommon const SortType& mSorter; }; - void sort(LLFolderViewFolder* folder) + void sort(LLFolderViewFolder* folder) override { if (needsSort(folder->getViewModelItem())) { diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index c11b42a3480..2ca2454040f 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -46,6 +46,7 @@ #include "llfocusmgr.h" #include "llcoord.h" #include "llwindow.h" +#include "llemojihelper.h" #include "llcriticaldamp.h" #include "lluictrlfactory.h" @@ -1411,6 +1412,7 @@ void LLMenuItemBranchDownGL::openMenu( void ) } else { + LLEmojiHelper::instance().hideHelper(nullptr, true); if (branch->getTornOff()) { LLFloater * branch_parent = dynamic_cast(branch->getParent()); diff --git a/indra/llui/llmodaldialog.cpp b/indra/llui/llmodaldialog.cpp index c5c31f72520..225ff607ad5 100644 --- a/indra/llui/llmodaldialog.cpp +++ b/indra/llui/llmodaldialog.cpp @@ -28,6 +28,7 @@ #include "llmodaldialog.h" +#include "llemojihelper.h" #include "llfocusmgr.h" #include "v4color.h" #include "v2math.h" @@ -35,6 +36,7 @@ #include "llwindow.h" #include "llkeyboard.h" #include "llmenugl.h" + // static std::list LLModalDialog::sModalStack; @@ -98,7 +100,7 @@ void LLModalDialog::onOpen(const LLSD& key) { if (mModal) { - // If Modal, Hide the active modal dialog + // If Modal, hide the active modal dialog if (!sModalStack.empty()) { LLModalDialog* front = sModalStack.front(); @@ -155,6 +157,12 @@ void LLModalDialog::setVisible( bool visible ) { if( visible ) { + // Hide all menus currently shown + LLMenuGL::sMenuContainer->hideMenus(); + + // Hide EmojiPicker if it is shown + LLEmojiHelper::instance().hideHelper(nullptr, true); + // This is a modal dialog. It sucks up all mouse and keyboard operations. gFocusMgr.setMouseCapture( this ); @@ -301,7 +309,6 @@ void LLModalDialog::centerOnScreen() centerWithin(LLRect(0, 0, ll_round(window_size.mV[VX]), ll_round(window_size.mV[VY]))); } - // static void LLModalDialog::onAppFocusLost() { @@ -333,6 +340,7 @@ void LLModalDialog::onAppFocusGained() } } +// static void LLModalDialog::shutdownModals() { // This method is only for use during app shutdown. ~LLModalDialog() diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 778b253c3c3..382847d68f8 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1069,6 +1069,14 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) { + S32 text_length = (S32)getLength(); + if (pos >= text_length || pos < 0) + { + return 0; // nothing to remove + } + // Clamp length to not go past the end of the text + length = std::min(length, text_length - pos); + beforeValueChange(); segment_set_t::iterator seg_iter = getSegIterContaining(pos); while(seg_iter != mSegments.end()) @@ -2228,6 +2236,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url)); registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, url)); registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url)); + registrar.add("Url.ShowParcelOnMap", boost::bind(&LLUrlAction::showParcelOnMap, url)); registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url)); registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url)); diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index cfe729be06e..58b785fcddc 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -274,7 +274,9 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : mShowChatMentionPicker(false), mEnableTooltipPaste(p.enable_tooltip_paste), mPassDelete(false), - mKeepSelectionOnReturn(false) + mKeepSelectionOnReturn(false), + mSelectAllOnFocusReceived(false), + mSelectedOnFocusReceived(false) { mSourceID.generate(); @@ -398,6 +400,7 @@ void LLTextEditor::selectNext(const std::string& search_text_in, bool case_insen setCursorPos(loc); mIsSelecting = true; + mSelectedOnFocusReceived = false; mSelectionEnd = mCursorPos; mSelectionStart = llmin((S32)getLength(), (S32)(mCursorPos + search_text.size())); } @@ -677,6 +680,13 @@ bool LLTextEditor::canSelectAll() const return true; } +//virtual +void LLTextEditor::deselect() +{ + LLTextBase::deselect(); + mSelectedOnFocusReceived = false; +} + // virtual void LLTextEditor::selectAll() { @@ -694,6 +704,11 @@ void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_p endSelection(); } +void LLTextEditor::setSelectAllOnFocusReceived(bool b) +{ + mSelectAllOnFocusReceived = b; +} + void LLTextEditor::insertEmoji(llwchar emoji) { LL_INFOS() << "LLTextEditor::insertEmoji(" << wchar_utf8_preview(emoji) << ")" << LL_ENDL; @@ -795,8 +810,16 @@ bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) // Delay cursor flashing resetCursorBlink(); + mSelectedOnFocusReceived = false; if (handled && !gFocusMgr.getMouseCapture()) { + if (!mask && mSelectAllOnFocusReceived) + { + mIsSelecting = false; + mSelectionStart = getLength(); + mSelectionEnd = 0; + mSelectedOnFocusReceived = true; + } gFocusMgr.setMouseCapture( this ); } return handled; @@ -2200,6 +2223,11 @@ void LLTextEditor::focusLostHelper() gEditMenuHandler = NULL; } + if (mSelectedOnFocusReceived) + { + deselect(); + } + if (mCommitOnFocusLost) { onCommit(); diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 882bb145df4..cdfcbcdd636 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -144,8 +144,10 @@ class LLTextEditor : virtual bool canDoDelete() const; virtual void selectAll(); virtual bool canSelectAll() const; + virtual void deselect(); void selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_pos); + void setSelectAllOnFocusReceived(bool b); virtual bool canLoadOrSaveToFile(); @@ -336,6 +338,8 @@ class LLTextEditor : bool mEnableTooltipPaste; bool mPassDelete; bool mKeepSelectionOnReturn; // disabling of removing selected text after pressing of Enter + bool mSelectAllOnFocusReceived; + bool mSelectedOnFocusReceived; LLUUID mSourceID; diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp index b6b450c2a16..fdae22cfa10 100644 --- a/indra/llui/llurlaction.cpp +++ b/indra/llui/llurlaction.cpp @@ -30,6 +30,7 @@ #include "llview.h" #include "llwindow.h" #include "llurlregistry.h" +#include "v3dmath.h" // global state for the callback functions @@ -128,6 +129,23 @@ void LLUrlAction::showLocationOnMap(std::string url) } } +void LLUrlAction::showParcelOnMap(std::string url) +{ + LLSD path_array = LLURI(url).pathArray(); + auto path_parts = path_array.size(); + + if (path_parts < 3) // no parcel id + { + LL_WARNS() << "Global coordinates are missing in url: [" << url << "]" << LL_ENDL; + return; + } + + LLVector3d parcel_pos = LLUrlEntryParcel::getParcelPos(LLUUID(LLURI::unescape(path_array[2]))); + std::ostringstream pos; + pos << parcel_pos.mdV[VX] << '/' << parcel_pos.mdV[VY] << '/' << parcel_pos.mdV[VZ]; + executeSLURL("secondlife:///app/worldmap_global/" + pos.str()); +} + void LLUrlAction::copyURLToClipboard(std::string url) { LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(url)); @@ -142,6 +160,16 @@ void LLUrlAction::copyLabelToClipboard(std::string url) } } +std::string LLUrlAction::getURLLabel(std::string url) +{ + LLUrlMatch match; + if (LLUrlRegistry::instance().findUrl(url, match)) + { + return match.getLabel(); + } + return ""; +} + void LLUrlAction::showProfile(std::string url) { // Get id from 'secondlife:///app/{cmd}/{id}/{action}' diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h index ac9741a7ada..8b7ab0e7c5e 100644 --- a/indra/llui/llurlaction.h +++ b/indra/llui/llurlaction.h @@ -63,6 +63,8 @@ class LLUrlAction /// if the Url specifies an SL location, show it on a map static void showLocationOnMap(std::string url); + static void showParcelOnMap(std::string url); + /// perform the appropriate action for left-clicking on a Url static void clickAction(std::string url, bool trusted_content); @@ -72,6 +74,8 @@ class LLUrlAction /// copy a Url to the clipboard static void copyURLToClipboard(std::string url); + static std::string getURLLabel(std::string url); + /// if the Url specifies an SL command in the form like 'app/{cmd}/{id}/*', show its profile static void showProfile(std::string url); static std::string getUserID(std::string url); diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index bcd13b7f0b3..95603d7ed53 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -41,8 +41,7 @@ #include "lluicolortable.h" #include "message.h" #include "llexperiencecache.h" - -#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" +#include "v3dmath.h" // Utility functions std::string localize_slapp_label(const std::string& url, const std::string& full_name); @@ -1084,6 +1083,7 @@ LLUUID LLUrlEntryParcel::sSessionID(LLUUID::null); LLHost LLUrlEntryParcel::sRegionHost; bool LLUrlEntryParcel::sDisconnected(false); std::set LLUrlEntryParcel::sParcelInfoObservers; +std::map LLUrlEntryParcel::sParcelPos; /// /// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., @@ -1176,6 +1176,20 @@ void LLUrlEntryParcel::processParcelInfo(const LLParcelData& parcel_data) url_entry->onParcelInfoReceived(parcel_data.parcel_id.asString(), label); } } + if (sParcelPos.find(parcel_data.parcel_id) == sParcelPos.end()) + { + sParcelPos[parcel_data.parcel_id] = LLVector3d(parcel_data.global_x, parcel_data.global_y, parcel_data.global_z); + } +} + +// static +LLVector3d LLUrlEntryParcel::getParcelPos(const LLUUID& parcel_id) +{ + if (sParcelPos.find(parcel_id) != sParcelPos.end()) + { + return sParcelPos[parcel_id]; + } + return LLVector3d(); } // diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 6e7d2fc80f2..efb5081103d 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -41,6 +41,9 @@ #include class LLAvatarName; +class LLVector3d; + +#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" typedef boost::signals2::signal sParcelInfoObservers; + static std::map sParcelPos; }; /// diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp index a306600f85b..20951ff8167 100644 --- a/indra/llwebrtc/llwebrtc.cpp +++ b/indra/llwebrtc/llwebrtc.cpp @@ -670,7 +670,10 @@ LLWebRTCPeerConnectionInterface *LLWebRTCImpl::newPeerConnection() peerConnection->init(this); mPeerConnections.emplace_back(peerConnection); - peerConnection->enableSenderTracks(!mMute); + // Should it really start disabled? + // Seems like something doesn't get the memo and senders need to be reset later + // to remove the voice indicator from taskbar + peerConnection->enableSenderTracks(false); if (mPeerConnections.empty()) { setRecording(true); @@ -704,7 +707,7 @@ void LLWebRTCImpl::freePeerConnection(LLWebRTCPeerConnectionInterface* peer_conn LLWebRTCPeerConnectionImpl::LLWebRTCPeerConnectionImpl() : mWebRTCImpl(nullptr), mPeerConnection(nullptr), - mMute(true), + mMute(MUTE_INITIAL), mAnswerReceived(false) { } @@ -739,6 +742,19 @@ void LLWebRTCPeerConnectionImpl::terminate() } } + // to remove 'Secondlife is recording' icon from taskbar + // if user was speaking + auto senders = mPeerConnection->GetSenders(); + for (auto& sender : senders) + { + auto track = sender->track(); + if (track) + { + track->set_enabled(false); + } + } + mPeerConnection->SetAudioRecording(false); + mPeerConnection->Close(); if (mLocalStream) { @@ -828,6 +844,7 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti audioOptions.auto_gain_control = true; audioOptions.echo_cancellation = true; audioOptions.noise_suppression = true; + audioOptions.init_recording_on_send = false; mLocalStream = mPeerConnectionFactory->CreateLocalMediaStream("SLStream"); @@ -887,6 +904,7 @@ void LLWebRTCPeerConnectionImpl::enableSenderTracks(bool enable) // set_enabled shouldn't be done on the worker thread. if (mPeerConnection) { + mPeerConnection->SetAudioRecording(enable); auto senders = mPeerConnection->GetSenders(); for (auto &sender : senders) { @@ -932,12 +950,23 @@ void LLWebRTCPeerConnectionImpl::AnswerAvailable(const std::string &sdp) void LLWebRTCPeerConnectionImpl::setMute(bool mute) { - mMute = mute; + EMicMuteState new_state = mute ? MUTE_MUTED : MUTE_UNMUTED; + if (new_state == mMute) + { + return; // no change + } + bool force_reset = mMute == MUTE_INITIAL && mute; + bool enable = !mute; + mMute = new_state; + mWebRTCImpl->PostSignalingTask( - [this]() + [this, force_reset, enable]() { if (mPeerConnection) { + // SetAudioRecording must be called before enabling/disabling tracks. + mPeerConnection->SetAudioRecording(enable); + auto senders = mPeerConnection->GetSenders(); RTC_LOG(LS_INFO) << __FUNCTION__ << (mMute ? "disabling" : "enabling") << " streams count " << senders.size(); @@ -946,7 +975,14 @@ void LLWebRTCPeerConnectionImpl::setMute(bool mute) auto track = sender->track(); if (track) { - track->set_enabled(!mMute); + if (force_reset) + { + // Force notify observers + // Was it disabled too early? + // Without this microphone icon in Win's taskbar will stay + track->set_enabled(true); + } + track->set_enabled(enable); } } } @@ -955,7 +991,17 @@ void LLWebRTCPeerConnectionImpl::setMute(bool mute) void LLWebRTCPeerConnectionImpl::resetMute() { - setMute(mMute); + switch(mMute) + { + case MUTE_MUTED: + setMute(true); + break; + case MUTE_UNMUTED: + setMute(false); + break; + default: + break; + } } void LLWebRTCPeerConnectionImpl::setReceiveVolume(float volume) diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h index b93a1fdb019..b6294dbd4a5 100644 --- a/indra/llwebrtc/llwebrtc_impl.h +++ b/indra/llwebrtc/llwebrtc_impl.h @@ -417,7 +417,12 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface, rtc::scoped_refptr mPeerConnectionFactory; - bool mMute; + typedef enum { + MUTE_INITIAL, + MUTE_MUTED, + MUTE_UNMUTED, + } EMicMuteState; + EMicMuteState mMute; // signaling std::vector mSignalingObserverList; diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index af53b6fb3f3..b127c9b48fb 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -76,6 +76,11 @@ #pragma comment(lib, "dxguid.lib") // needed for llurlentry test to build on some systems #pragma comment(lib, "dinput8") +#pragma comment(lib, "UxTheme.lib") +#pragma comment(lib, "Dwmapi.lib") +#include +#include // needed for DwmSetWindowAttribute to set window theme + const S32 MAX_MESSAGE_PER_UPDATE = 20; const S32 BITS_PER_PIXEL = 32; const S32 MAX_NUM_RESOLUTIONS = 32; @@ -85,6 +90,10 @@ const F32 ICON_FLASH_TIME = 0.5f; #define USER_DEFAULT_SCREEN_DPI 96 // Win7 #endif +#ifndef WM_DWMCOLORIZATIONCOLORCHANGED +#define WM_DWMCOLORIZATIONCOLORCHANGED 0x0320 +#endif + // Claim a couple unused GetMessage() message IDs const UINT WM_DUMMY_(WM_USER + 0x0017); const UINT WM_POST_FUNCTION_(WM_USER + 0x0018); @@ -104,6 +113,7 @@ static std::thread::id sMainThreadId; LPWSTR gIconResource = IDI_APPLICATION; +LPWSTR gIconSmallResource = IDI_APPLICATION; LPDIRECTINPUT8 gDirectInput8; LLW32MsgCallback gAsyncMsgCallback = NULL; @@ -137,6 +147,17 @@ typedef HRESULT(STDAPICALLTYPE *GetDpiForMonitorType)( _Out_ UINT *dpiX, _Out_ UINT *dpiY); +typedef enum PREFERRED_APP_MODE +{ + DEFAULT, + ALLOW_DARK, + FORCE_DARK, + FORCE_LIGHT, + MAX +} PREFERRED_APP_MODE; + +typedef PREFERRED_APP_MODE(WINAPI* fnSetPreferredAppMode)(PREFERRED_APP_MODE mode); + // // LLWindowWin32 // @@ -350,10 +371,14 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool LLWindowWin32Thread(); void run() override; - void close() override; - // closes queue, wakes thread, waits until thread closes - void wakeAndDestroy(); + // Detroys handles and window + // Either post to or call from window thread + void destroyWindow(); + + // Closes queue, wakes thread, waits until thread closes. + // Call from main thread + bool wakeAndDestroy(); void glReady() { @@ -410,6 +435,7 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool // until after some graphics setup. See SL-20177. -Cosmic,2023-09-18 bool mGLReady = false; bool mGotGLBuffer = false; + LLAtomicBool mDeleteOnExit = false; }; @@ -507,6 +533,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks, mFSAASamples = fsaa_samples; mIconResource = gIconResource; + mIconSmallResource = gIconSmallResource; mOverrideAspectRatio = 0.f; mNativeAspectRatio = 0.f; mInputProcessingPaused = false; @@ -839,6 +866,8 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks, // Initialize (boot strap) the Language text input management, // based on the system's (or user's) default settings. allowLanguageTextInput(NULL, false); + updateWindowTheme(); + setCustomIcon(); } @@ -850,6 +879,7 @@ LLWindowWin32::~LLWindowWin32() } delete mDragDrop; + mDragDrop = NULL; delete [] mWindowTitle; mWindowTitle = NULL; @@ -861,6 +891,7 @@ LLWindowWin32::~LLWindowWin32() mWindowClassName = NULL; delete mWindowThread; + mWindowThread = NULL; } void LLWindowWin32::show() @@ -969,7 +1000,7 @@ void LLWindowWin32::close() // Restore gamma to the system values. restoreGamma(); - LL_DEBUGS("Window") << "Destroying Window" << LL_ENDL; + LL_INFOS("Window") << "Cleanup and destruction of Window Thread" << LL_ENDL; if (sWindowHandleForMessageBox == mWindowHandle) { @@ -979,7 +1010,11 @@ void LLWindowWin32::close() mhDC = NULL; mWindowHandle = NULL; - mWindowThread->wakeAndDestroy(); + if (mWindowThread->wakeAndDestroy()) + { + // thread will delete itselfs once done + mWindowThread = NULL; + } } bool LLWindowWin32::isValid() @@ -3007,6 +3042,17 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ WINDOW_IMP_POST(window_imp->mMouseVanish = true); } } + // Check if theme-related settings changed + else if (l_param && (wcscmp((LPCWSTR)l_param, L"ImmersiveColorSet") == 0)) + { + WINDOW_IMP_POST(window_imp->updateWindowTheme()); + } + } + break; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + { + WINDOW_IMP_POST(window_imp->updateWindowTheme()); } break; @@ -3099,10 +3145,14 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ break; } } - else + else // (NULL == window_imp) { - // (NULL == window_imp) LL_DEBUGS("Window") << "No window implementation to handle message with, message code: " << U32(u_msg) << LL_ENDL; + if (u_msg == WM_DESTROY) + { + PostQuitMessage(0); // Posts WM_QUIT with an exit code of 0 + return 0; + } } // pass unhandled messages down to Windows @@ -4572,25 +4622,11 @@ std::vector LLWindowWin32::getDynamicFallbackFontList() #endif // LL_WINDOWS inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread() - : LL::ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE, true /*should be false, temporary workaround for SL-18721*/) + : LL::ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE, false) { LL::ThreadPool::start(); } -void LLWindowWin32::LLWindowWin32Thread::close() -{ - if (!mQueue->isClosed()) - { - LL_WARNS() << "Closing window thread without using destroy_window_handler" << LL_ENDL; - LL::ThreadPool::close(); - - // Workaround for SL-18721 in case window closes too early and abruptly - LLSplashScreen::show(); - LLSplashScreen::update("..."); // will be updated later - } -} - - /** * LogChange is to log changes in status while trying to avoid spamming the * log with repeated messages, especially in a tight loop. It refuses to log @@ -4814,108 +4850,102 @@ void LLWindowWin32::LLWindowWin32Thread::run() } #endif } + + destroyWindow(); + + if (mDeleteOnExit) + { + delete this; + } +} + +void LLWindowWin32::LLWindowWin32Thread::destroyWindow() +{ + if (mWindowHandleThrd != NULL && IsWindow(mWindowHandleThrd)) + { + if (mhDCThrd) + { + if (!ReleaseDC(mWindowHandleThrd, mhDCThrd)) + { + LL_WARNS("Window") << "Release of ghDC failed!" << LL_ENDL; + } + mhDCThrd = NULL; + } + + // This causes WM_DESTROY to be sent *immediately* + if (!destroy_window_handler(mWindowHandleThrd)) + { + LL_WARNS("Window") << "Failed to destroy Window! " << std::hex << GetLastError() << LL_ENDL; + } + } + else + { + // Something killed the window while we were busy destroying gl or handle somehow got broken + LL_WARNS("Window") << "Failed to destroy Window, invalid handle!" << LL_ENDL; + } + mWindowHandleThrd = NULL; + mhDCThrd = NULL; } -void LLWindowWin32::LLWindowWin32Thread::wakeAndDestroy() +bool LLWindowWin32::LLWindowWin32Thread::wakeAndDestroy() { if (mQueue->isClosed()) { - LL_WARNS() << "Tried to close Queue. Win32 thread Queue already closed." << LL_ENDL; - return; + LL_WARNS("Window") << "Tried to close Queue. Win32 thread Queue already closed." << LL_ENDL; + return false; } - // Make sure we don't leave a blank toolbar button. - // Also hiding window now prevents user from suspending it - // via some action (like dragging it around) - ShowWindow(mWindowHandleThrd, SW_HIDE); + // Stop checking budget + mGLReady = false; - // Schedule destruction + // Capture current handle before we lose it HWND old_handle = mWindowHandleThrd; - post([this]() - { - if (IsWindow(mWindowHandleThrd)) - { - if (mhDCThrd) - { - if (!ReleaseDC(mWindowHandleThrd, mhDCThrd)) - { - LL_WARNS("Window") << "Release of ghDC failed!" << LL_ENDL; - } - mhDCThrd = NULL; - } - - // This causes WM_DESTROY to be sent *immediately* - if (!destroy_window_handler(mWindowHandleThrd)) - { - LL_WARNS("Window") << "Failed to destroy Window! " << std::hex << GetLastError() << LL_ENDL; - } - } - else - { - // Something killed the window while we were busy destroying gl or handle somehow got broken - LL_WARNS("Window") << "Failed to destroy Window, invalid handle!" << LL_ENDL; - } - mWindowHandleThrd = NULL; - mhDCThrd = NULL; - mGLReady = false; - }); - - LL_DEBUGS("Window") << "Closing window's pool queue" << LL_ENDL; - mQueue->close(); - // Post a nonsense user message to wake up the thread in - // case it is waiting for a getMessage() + // Clear the user data to prevent callbacks from finding us if (old_handle) { - WPARAM wparam{ 0xB0B0 }; - LL_DEBUGS("Window") << "PostMessage(" << std::hex << old_handle - << ", " << WM_DUMMY_ - << ", " << wparam << ")" << std::dec << LL_ENDL; - PostMessage(old_handle, WM_DUMMY_, wparam, 0x1337); + SetWindowLongPtr(old_handle, GWLP_USERDATA, NULL); } - // There are cases where window will refuse to close, - // can't wait forever on join, check state instead - LLTimer timeout; - timeout.setTimerExpirySec(2.0); - while (!getQueue().done() && !timeout.hasExpired() && mWindowHandleThrd) - { - ms_sleep(100); - } + // Signal thread to clean up when done + mDeleteOnExit = true; - if (getQueue().done() || mWindowHandleThrd == NULL) + LL_INFOS("Window") << "Detaching window's thread" << LL_ENDL; + // Cleanly detach threads instead of joining them to avoid blocking the main thread + // This is acceptable since the thread will self-delete with mDeleteOnExit + // Doing it before close() to make sure thread doesn't die before or mid detach. + for (auto& pair : mThreads) { - // Window is closed, started closing or is cleaning up - // now wait for our single thread to die. - if (mWindowHandleThrd) - { - LL_INFOS("Window") << "Window is closing, waiting on pool's thread to join, time since post: " << timeout.getElapsedSeconds() << "s" << LL_ENDL; - } - else - { - LL_DEBUGS("Window") << "Waiting on pool's thread, time since post: " << timeout.getElapsedSeconds() << "s" << LL_ENDL; + try { + // Only detach if the thread is joinable + if (pair.second.joinable()) + { + pair.second.detach(); + } } - for (auto& pair : mThreads) - { - pair.second.join(); + catch (const std::system_error& e) { + LL_WARNS("Window") << "Exception detaching thread: " << e.what() << LL_ENDL; } } - else + + // Close the queue. + LL_INFOS("Window") << "Closing window's pool queue" << LL_ENDL; + mQueue->close(); + + // Wake up the thread if it's stuck in GetMessage() + if (old_handle) { - // Something suspended window thread, can't afford to wait forever - // so kill thread instead - // Ex: This can happen if user starts dragging window arround (if it - // was visible) or a modal notification pops up - LL_WARNS("Window") << "Window is frozen, couldn't perform clean exit" << LL_ENDL; + WPARAM wparam{ 0xB0B0 }; + LL_DEBUGS("Window") << "PostMessage(" << std::hex << old_handle + << ", " << WM_DUMMY_ + << ", " << wparam << ")" << std::dec << LL_ENDL; - for (auto& pair : mThreads) - { - // very unsafe - TerminateThread(pair.second.native_handle(), 0); - pair.second.detach(); - } + // Use PostMessage to signal thread to wake up + PostMessage(old_handle, WM_DUMMY_, wparam, 0x1337); } - LL_DEBUGS("Window") << "thread pool shutdown complete" << LL_ENDL; + + LL_INFOS("Window") << "Thread pool shutdown complete" << LL_ENDL; + return true; } void LLWindowWin32::post(const std::function& func) @@ -4962,3 +4992,69 @@ void LLWindowWin32::updateWindowRect() }); } } + +bool LLWindowWin32::isSystemAppDarkMode() +{ + HKEY hKey; + DWORD dwValue = 1; // Default to light theme + DWORD dwSize = sizeof(DWORD); + + // Check registry for system theme preference + LSTATUS ret_code = + RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey); + if (ERROR_SUCCESS == ret_code) + { + if (RegQueryValueExW(hKey, L"AppsUseLightTheme", NULL, NULL, (LPBYTE)&dwValue, &dwSize) != ERROR_SUCCESS) + { + // If AppsUseLightTheme is not found, check SystemUsesLightTheme + dwSize = sizeof(DWORD); + RegQueryValueExW(hKey, L"SystemUsesLightTheme", NULL, NULL, (LPBYTE)&dwValue, &dwSize); + } + RegCloseKey(hKey); + } + + // Return true if dark mode + return dwValue == 0; +} + +void LLWindowWin32::updateWindowTheme() +{ + bool use_dark_mode = isSystemAppDarkMode(); + if (use_dark_mode == mCurrentDarkMode) + { + return; + } + mCurrentDarkMode = use_dark_mode; + + HMODULE hUxTheme = LoadLibraryExW(L"uxtheme.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (hUxTheme) + { + auto SetPreferredAppMode = (fnSetPreferredAppMode)GetProcAddress(hUxTheme, "SetPreferredAppMode"); + if (SetPreferredAppMode) + { + SetPreferredAppMode(use_dark_mode ? ALLOW_DARK : FORCE_LIGHT); + } + FreeLibrary(hUxTheme); + } + BOOL dark_mode(use_dark_mode); + DwmSetWindowAttribute(mWindowHandle, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark_mode, sizeof(dark_mode)); + + LL_INFOS("Window") << "Viewer window theme is set to " << (use_dark_mode ? "dark" : "light") << " mode" << LL_ENDL; +} + +void LLWindowWin32::setCustomIcon() +{ + if (mWindowHandle) + { + HICON hDefaultIcon = LoadIcon(mhInstance, mIconResource); + HICON hSmallIcon = LoadIcon(mhInstance, mIconSmallResource); + mWindowThread->post([=]() + { + SendMessage(mWindowHandle, WM_SETICON, ICON_BIG, (LPARAM)hDefaultIcon); + SendMessage(mWindowHandle, WM_SETICON, ICON_SMALL, (LPARAM)hSmallIcon); + + SetClassLongPtr(mWindowHandle, GCLP_HICON, (LONG_PTR)hDefaultIcon); + SetClassLongPtr(mWindowHandle, GCLP_HICONSM, (LONG_PTR)hSmallIcon); + }); + } +} diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index 561f07d3880..7196706f871 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -214,6 +214,7 @@ class LLWindowWin32 : public LLWindow bool mCustomGammaSet; LPWSTR mIconResource; + LPWSTR mIconSmallResource; bool mInputProcessingPaused; // The following variables are for Language Text Input control. @@ -246,6 +247,11 @@ class LLWindowWin32 : public LLWindow RECT mRect; RECT mClientRect; + void updateWindowTheme(); + bool isSystemAppDarkMode(); + void setCustomIcon(); + bool mCurrentDarkMode { false }; + struct LLWindowWin32Thread; LLWindowWin32Thread* mWindowThread = nullptr; LLThreadSafeQueue> mFunctionQueue; @@ -281,6 +287,7 @@ class LLSplashScreenWin32 : public LLSplashScreen extern LLW32MsgCallback gAsyncMsgCallback; extern LPWSTR gIconResource; +extern LPWSTR gIconSmallResource; S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 type); diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp index bb590ebd76f..562a30e8d17 100644 --- a/indra/llxml/llcontrol.cpp +++ b/indra/llxml/llcontrol.cpp @@ -157,6 +157,9 @@ LLControlVariable::LLControlVariable(const std::string& name, eControlType type, { if ((persist != PERSIST_NO) && mComment.empty()) { + // File isn't actually missing, but something is wrong with it + // so the main point is to warn user to reinstall + LLError::LLUserWarningMsg::showMissingFiles(); LL_ERRS() << "Must supply a comment for control " << mName << LL_ENDL; } //Push back versus setValue'ing here, since we don't want to call a signal yet diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 481db3d1083..5274986ff03 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -15,6 +15,9 @@ include(CMakeCopyIfDifferent) include(CubemapToEquirectangularJS) include(DBusGlib) include(DragDrop) +if (USE_DISCORD) + include(Discord) +endif () include(EXPAT) include(Hunspell) include(JPEGEncoderBasic) @@ -339,6 +342,7 @@ set(viewer_SOURCE_FILES llhudeffectpointat.cpp llhudeffecttrail.cpp llhudeffectblob.cpp + llhudeffectresetskeleton.cpp llhudicon.cpp llhudmanager.cpp llhudnametag.cpp @@ -364,6 +368,7 @@ set(viewer_SOURCE_FILES llinventorygallerymenu.cpp llinventoryicon.cpp llinventoryitemslist.cpp + llinventorylistener.cpp llinventorylistitem.cpp llinventorymodel.cpp llinventorymodelbackgroundfetch.cpp @@ -1013,6 +1018,7 @@ set(viewer_HEADER_FILES llhudeffectpointat.h llhudeffecttrail.h llhudeffectblob.h + llhudeffectresetskeleton.h llhudicon.h llhudmanager.h llhudnametag.h @@ -1037,6 +1043,7 @@ set(viewer_HEADER_FILES llinventorygallerymenu.h llinventoryicon.h llinventoryitemslist.h + llinventorylistener.h llinventorylistitem.h llinventorymodel.h llinventorymodelbackgroundfetch.h @@ -1568,6 +1575,7 @@ if (WINDOWS) res-sdl/ll_icon.BMP res/ll_icon.BMP res/ll_icon.ico + res/ll_icon_small.ico res/resource.h res/toolpickobject.cur res/toolpickobject2.cur @@ -1783,6 +1791,12 @@ if (WINDOWS) ) endif (ADDRESS_SIZE EQUAL 64) + if (TARGET ll::discord_sdk) + list(APPEND COPY_INPUT_DEPENDENCIES + ${SHARED_LIB_STAGING_DIR}/discord_partner_sdk.dll + ) + endif () + if (TARGET ll::openal) list(APPEND COPY_INPUT_DEPENDENCIES ${SHARED_LIB_STAGING_DIR}/OpenAL32.dll @@ -1799,6 +1813,7 @@ if (WINDOWS) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -1837,6 +1852,7 @@ if (WINDOWS) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -1901,6 +1917,7 @@ if (WINDOWS) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -1996,6 +2013,10 @@ target_link_libraries(${VIEWER_BINARY_NAME} ll::openxr ) +if (USE_DISCORD) + target_link_libraries(${VIEWER_BINARY_NAME} ll::discord_sdk ) +endif () + if( TARGET ll::intel_memops ) target_link_libraries(${VIEWER_BINARY_NAME} ll::intel_memops ) endif() @@ -2052,6 +2073,7 @@ if (LINUX) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -2080,6 +2102,7 @@ if (LINUX) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -2156,6 +2179,7 @@ if (DARWIN) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} @@ -2191,6 +2215,7 @@ if (DARWIN) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" --build=${CMAKE_CURRENT_BINARY_DIR} diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 0ee843cc604..b26a34e4705 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.2.0 +7.2.1 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 5b5551dbe0e..f4158188ada 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -102,7 +102,7 @@ Type S32 Value - 300.0 + 300 AckCollectTime @@ -1150,6 +1150,39 @@ Value 1 + EnableDiscord + + Comment + When set, connect to Discord to enable Rich Presence + Persist + 1 + Type + Boolean + Value + 0 + + ShowDiscordActivityDetails + + Comment + When set, show avatar name on Discord Rich Presence + Persist + 1 + Type + Boolean + Value + 0 + + ShowDiscordActivityState + + Comment + When set, show location on Discord Rich Presence + Persist + 1 + Type + Boolean + Value + 0 + EnableDiskCacheDebugInfo Comment @@ -1164,13 +1197,13 @@ DiskCachePercentOfTotal Comment - The percent of total cache size (defined by CacheSize) to use for the disk cache + The percent of total cache size (defined by CacheSize) to use for the disk cache (ex: asset storage, excludes textures) Persist 1 Type F32 Value - 40.0 + 35.0 DiskCacheDirName @@ -1214,7 +1247,7 @@ Type U32 Value - 4096 + 6144 CacheValidateCounter @@ -1875,6 +1908,17 @@ Value 0 + DebugSelectionLODs + + Comment + Force selection to show specific LOD, -1 for off, 0 - lowest, 4 - high. + Persist + 0 + Type + S32 + Value + -1 + AnimatedObjectsAllowLeftClick Comment @@ -9606,6 +9650,17 @@ Value 1 + ObscureBalanceInStatusBar + + Comment + If true, balance will be shows as '*' + Persist + 1 + Type + Boolean + Value + 0 + RenderUIBuffer Comment @@ -13753,7 +13808,7 @@ FullScreen Comment - run a fullscreen session + Run a fullscreen session. MacOS not supported Persist 1 Type @@ -14251,10 +14306,32 @@ Value 1 - OutfitGallerySortByName + OutfitGallerySortOrder + + Comment + Gallery sorting: 0 - sort outfits by name, 1 - images frst, 2 - favorites first + Persist + 1 + Type + S32 + Value + 0 + + OutfitListSortOrder + + Comment + How outfit list in Avatar's floater is sorted. 0 - by name 1 - favorites to top + Persist + 1 + Type + S32 + Value + 0 + + OutfitListFilterFullList Comment - Always sort outfits by name in Outfit Gallery + 0 - show only matches. 1 - show all items in outfit as long as outfit or item inside matches. Persist 1 Type @@ -16135,6 +16212,50 @@ Value 0 + InventoryFavoritesUseStar + + Comment + Show star near favorited items in inventory + Persist + 1 + Type + Boolean + Value + 1 + + InventoryFavoritesUseHollowStar + + Comment + Show star near folders that contain favorites + Persist + 1 + Type + Boolean + Value + 1 + + InventoryFavoritesColorText + + Comment + render favorite items using InventoryFavoriteText as color + Persist + 1 + Type + Boolean + Value + 1 + + InventoryAddAttachmentBehavior + + Comment + Defines behavior when hitting return on an inventory item + Persist + 1 + Type + Boolean + Value + 0 + StatsReportMaxDuration Comment @@ -16245,5 +16366,49 @@ Value 31 + EnableSelectionHints + + Comment + Whether or not to send editing hints to animate the arm when editing an object. + Persist + 1 + Type + Boolean + Value + 1 + + EnableLookAtTarget + + Comment + Whether or not to animate the avatar head and send look at targets when moving the cursor or focusing on objects + Persist + 1 + Type + Boolean + Value + 1 + + LimitLookAtTarget + + Comment + Whether or not to clamp the look at targets around the avatar head before sending + Persist + 1 + Type + Boolean + Value + 0 + + LimitLookAtTargetDistance + + Comment + Distance to limit look at target to + Persist + 1 + Type + F32 + Value + 2 + diff --git a/indra/newview/app_settings/settings_per_account.xml b/indra/newview/app_settings/settings_per_account.xml index 6b788dd78f5..99c43acd497 100644 --- a/indra/newview/app_settings/settings_per_account.xml +++ b/indra/newview/app_settings/settings_per_account.xml @@ -329,7 +329,7 @@ KeepConversationLogTranscripts Comment - Keep a conversation log and transcripts + Keep a conversation log and transcripts 2 - both, 1 - logs, 0 - none Persist 1 Type diff --git a/indra/newview/gltf/buffer_util.h b/indra/newview/gltf/buffer_util.h index be36c5e90ba..c231443a9e4 100644 --- a/indra/newview/gltf/buffer_util.h +++ b/indra/newview/gltf/buffer_util.h @@ -146,6 +146,12 @@ namespace LL dst.set(src[0], src[1]); } + template<> + inline void copyVec3(F32* src, LLVector2& dst) + { + dst.set(src[0], src[1]); + } + template<> inline void copyVec3(F32* src, vec3& dst) { @@ -375,12 +381,18 @@ namespace LL template inline void copy(Asset& asset, Accessor& accessor, LLStrider& dst) { - if (accessor.mBufferView == INVALID_INDEX) + if (accessor.mBufferView == INVALID_INDEX + || accessor.mBufferView >= asset.mBufferViews.size()) { LL_WARNS("GLTF") << "Invalid buffer" << LL_ENDL; return; } const BufferView& bufferView = asset.mBufferViews[accessor.mBufferView]; + if (bufferView.mBuffer >= asset.mBuffers.size()) + { + LL_WARNS("GLTF") << "Invalid buffer view" << LL_ENDL; + return; + } const Buffer& buffer = asset.mBuffers[bufferView.mBuffer]; const U8* src = buffer.mData.data() + bufferView.mByteOffset + accessor.mByteOffset; diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index 3cb5e9a0d7c..ac452b38a0b 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -220,6 +220,7 @@ void GLTFSceneManager::uploadSelection() LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"), expected_upload_cost, + LLUUID::null, false, finish, failure)); @@ -283,6 +284,7 @@ void GLTFSceneManager::uploadSelection() LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"), expected_upload_cost, + LLUUID::null, false, finish, failure)); @@ -559,6 +561,7 @@ void GLTFSceneManager::update() LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"), expected_upload_cost, + LLUUID::null, false, finish, failure)); diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 039bd4da2ad..085155714ad 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -121,8 +121,8 @@ const F32 MIN_FIDGET_TIME = 8.f; // seconds const F32 MAX_FIDGET_TIME = 20.f; // seconds const S32 UI_FEATURE_VERSION = 1; -// For version 1: 1 - inventory, 2 - gltf -const S32 UI_FEATURE_FLAGS = 3; +// For version 1, flag holds: 1 - inventory thumbnails, 2 - gltf, 4 - inventory favorites +const S32 UI_FEATURE_FLAGS = 7; // The agent instance. LLAgent gAgent; @@ -223,7 +223,6 @@ class LLTeleportRequestViaLocation : public LLTeleportRequest LLVector3d mPosGlobal; }; - class LLTeleportRequestViaLocationLookAt : public LLTeleportRequestViaLocation { public: @@ -604,7 +603,7 @@ void LLAgent::getFeatureVersionAndFlags(S32& version, S32& flags) if (feature_version.isInteger()) { version = feature_version.asInteger(); - flags = 1; // inventory flag + flags = 3; // show 'favorites' notification } else if (feature_version.isMap()) { @@ -630,13 +629,8 @@ void LLAgent::showLatestFeatureNotification(const std::string key) if (key == "inventory") { - // Notify user about new thumbnail support - flag = 1; - } - - if (key == "gltf") - { - flag = 2; + // Notify user about new favorites support + flag = 4; } if ((flags & flag) == 0) @@ -843,7 +837,6 @@ void LLAgent::movePitch(F32 mag) } } - // Does this parcel allow you to fly? bool LLAgent::canFly() { @@ -923,7 +916,6 @@ void LLAgent::setFlying(bool fly, bool fail_sound) LLFloaterMove::setFlyingMode(fly); } - // UI based mechanism of setting fly state //----------------------------------------------------------------------------- // toggleFlying() @@ -1002,7 +994,6 @@ void LLAgent::capabilityReceivedCallback(const LLUUID ®ion_id, LLViewerRegion } } - //----------------------------------------------------------------------------- // setRegion() //----------------------------------------------------------------------------- @@ -1108,7 +1099,6 @@ void LLAgent::setRegion(LLViewerRegion *regionp) mRegionChangedSignal(); } - //----------------------------------------------------------------------------- // getRegion() //----------------------------------------------------------------------------- @@ -1117,7 +1107,6 @@ LLViewerRegion *LLAgent::getRegion() const return mRegionp; } - LLHost LLAgent::getRegionHost() const { if (mRegionp) @@ -1148,7 +1137,6 @@ bool LLAgent::inPrelude() return mRegionp && mRegionp->isPrelude(); } - std::string LLAgent::getRegionCapability(const std::string &name) { if (!mRegionp) @@ -1157,7 +1145,6 @@ std::string LLAgent::getRegionCapability(const std::string &name) return mRegionp->getCapability(name); } - //----------------------------------------------------------------------------- // canManageEstate() //----------------------------------------------------------------------------- @@ -1185,7 +1172,6 @@ void LLAgent::sendMessage() gMessageSystem->sendMessage(mRegionp->getHost()); } - //----------------------------------------------------------------------------- // sendReliableMessage() //----------------------------------------------------------------------------- @@ -1219,7 +1205,6 @@ LLVector3 LLAgent::getVelocity() const } } - //----------------------------------------------------------------------------- // setPositionAgent() //----------------------------------------------------------------------------- @@ -1293,7 +1278,6 @@ const LLVector3 &LLAgent::getPositionAgent() } } - return mFrameAgent.getOrigin(); } @@ -1302,7 +1286,6 @@ boost::signals2::connection LLAgent::whenPositionChanged(position_signal_t::slot return mOnPositionChanged.connect(fn); } - //----------------------------------------------------------------------------- // getRegionsVisited() //----------------------------------------------------------------------------- @@ -1319,7 +1302,6 @@ F64 LLAgent::getDistanceTraveled() const return mDistanceTraveled; } - //----------------------------------------------------------------------------- // getPosAgentFromGlobal() //----------------------------------------------------------------------------- @@ -1330,7 +1312,6 @@ LLVector3 LLAgent::getPosAgentFromGlobal(const LLVector3d &pos_global) const return pos_agent; } - //----------------------------------------------------------------------------- // getPosGlobalFromAgent() //----------------------------------------------------------------------------- @@ -1346,7 +1327,6 @@ void LLAgent::sitDown() setControlFlags(AGENT_CONTROL_SIT_ON_GROUND); } - //----------------------------------------------------------------------------- // resetAxes() //----------------------------------------------------------------------------- @@ -1355,7 +1335,6 @@ void LLAgent::resetAxes() mFrameAgent.resetAxes(); } - // Copied from LLCamera::setOriginAndLookAt // Look_at must be unit vector //----------------------------------------------------------------------------- @@ -1384,7 +1363,6 @@ void LLAgent::resetAxes(const LLVector3 &look_at) mFrameAgent.setAxes(look_at, left, up); } - //----------------------------------------------------------------------------- // rotate() //----------------------------------------------------------------------------- @@ -1393,7 +1371,6 @@ void LLAgent::rotate(F32 angle, const LLVector3 &axis) mFrameAgent.rotate(angle, axis); } - //----------------------------------------------------------------------------- // rotate() //----------------------------------------------------------------------------- @@ -1402,7 +1379,6 @@ void LLAgent::rotate(F32 angle, F32 x, F32 y, F32 z) mFrameAgent.rotate(angle, x, y, z); } - //----------------------------------------------------------------------------- // rotate() //----------------------------------------------------------------------------- @@ -1411,7 +1387,6 @@ void LLAgent::rotate(const LLMatrix3 &matrix) mFrameAgent.rotate(matrix); } - //----------------------------------------------------------------------------- // rotate() //----------------------------------------------------------------------------- @@ -1420,7 +1395,6 @@ void LLAgent::rotate(const LLQuaternion &quaternion) mFrameAgent.rotate(quaternion); } - //----------------------------------------------------------------------------- // getReferenceUpVector() //----------------------------------------------------------------------------- @@ -1449,7 +1423,6 @@ LLVector3 LLAgent::getReferenceUpVector() return up_vector; } - // Radians, positive is forward into ground //----------------------------------------------------------------------------- // pitch() @@ -1493,7 +1466,6 @@ void LLAgent::pitch(F32 angle) } } - //----------------------------------------------------------------------------- // roll() //----------------------------------------------------------------------------- @@ -1502,7 +1474,6 @@ void LLAgent::roll(F32 angle) mFrameAgent.roll(angle); } - //----------------------------------------------------------------------------- // yaw() //----------------------------------------------------------------------------- @@ -1514,7 +1485,6 @@ void LLAgent::yaw(F32 angle) } } - // Returns a quat that represents the rotation of the agent in the absolute frame //----------------------------------------------------------------------------- // getQuat() @@ -1540,7 +1510,6 @@ void LLAgent::setControlFlags(U32 mask) mControlFlags |= mask; } - //----------------------------------------------------------------------------- // clearControlFlags() //----------------------------------------------------------------------------- @@ -1628,7 +1597,6 @@ bool LLAgent::isDoNotDisturb() const return mIsDoNotDisturb; } - //----------------------------------------------------------------------------- // startAutoPilotGlobal() //----------------------------------------------------------------------------- @@ -1734,7 +1702,6 @@ void LLAgent::startAutoPilotGlobal( mAutoPilotNoProgressFrameCount = 0; } - //----------------------------------------------------------------------------- // setAutoPilotTargetGlobal //----------------------------------------------------------------------------- @@ -1788,7 +1755,6 @@ void LLAgent::startFollowPilot(const LLUUID &leader_id, bool allow_flying, F32 s allow_flying); } - //----------------------------------------------------------------------------- // stopAutoPilot() //----------------------------------------------------------------------------- @@ -1830,7 +1796,6 @@ void LLAgent::stopAutoPilot(bool user_cancel) } } - // Returns necessary agent pitch and yaw changes, radians. //----------------------------------------------------------------------------- // autoPilot() @@ -2019,7 +1984,6 @@ void LLAgent::autoPilot(F32 *delta_yaw) } } - //----------------------------------------------------------------------------- // propagate() //----------------------------------------------------------------------------- @@ -2040,18 +2004,19 @@ void LLAgent::propagate(const F32 dt) } // handle rotation based on keyboard levels - constexpr F32 YAW_RATE = 90.f * DEG_TO_RAD; // radians per second - F32 angle = YAW_RATE * gAgentCamera.getYawKey() * dt; - if (fabs(angle) > 0.0f) + if (fabs(dt) > 1e-6) { - yaw(angle); - } + if (fabs(gAgentCamera.getYawKey()) > 1e-6) + { + static const F32 YAW_RATE = 90.f * DEG_TO_RAD; // radians per second + yaw(YAW_RATE * gAgentCamera.getYawKey() * dt); + } - constexpr F32 PITCH_RATE = 90.f * DEG_TO_RAD; // radians per second - angle = PITCH_RATE * gAgentCamera.getPitchKey() * dt; - if (fabs(angle) > 0.0f) - { - pitch(angle); + if (fabs(gAgentCamera.getPitchKey()) > 1e-6) + { + static const F32 PITCH_RATE = 90.f * DEG_TO_RAD; // radians per second + pitch(PITCH_RATE * gAgentCamera.getPitchKey() * dt); + } } // handle auto-land behavior @@ -2213,7 +2178,6 @@ void LLAgent::clearRenderState(U8 clearstate) mRenderState &= ~clearstate; } - //----------------------------------------------------------------------------- // getRenderState() //----------------------------------------------------------------------------- @@ -2255,6 +2219,7 @@ void LLAgent::endAnimationUpdateUI() { return; } + if (gAgentCamera.getCameraMode() == gAgentCamera.getLastCameraMode()) { // We're already done endAnimationUpdateUI for this transition. @@ -2320,7 +2285,6 @@ void LLAgent::endAnimationUpdateUI() mViewsPushed = false; } - gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); if( gMorphView ) { @@ -2952,7 +2916,6 @@ void LLAgent::sendMaturityPreferenceToServer(U8 pPreferredMaturity) } } - void LLAgent::processMaturityPreferenceFromServer(const LLSD &result, U8 perferredMaturity) { U8 maturity = SIM_ACCESS_MIN; @@ -3022,7 +2985,6 @@ void LLAgent::changeInterestListMode(const std::string &new_mode) } } - bool LLAgent::requestPostCapability(const std::string &capName, LLSD &postData, httpCallback_t cbSuccess, httpCallback_t cbFailure) { if (getRegion()) @@ -3349,7 +3311,6 @@ void LLAgent::sendAnimationStateReset() sendReliableMessage(); } - // Send a message to the region to revoke sepecified permissions on ALL scripts in the region // If the target is an object in the region, permissions in scripts on that object are cleared. // If it is the region ID, all scripts clear the permissions for this agent @@ -3469,14 +3430,11 @@ void LLAgent::initOriginGlobal(const LLVector3d &origin_global) bool LLAgent::leftButtonGrabbed() const { - if (gAgentCamera.cameraMouselook()) - { - return mControlsTakenCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0; - } - else - { - return mControlsTakenCount[CONTROL_LBUTTON_DOWN_INDEX] > 0; - } + const bool camera_mouse_look = gAgentCamera.cameraMouselook(); + return (!camera_mouse_look && mControlsTakenCount[CONTROL_LBUTTON_DOWN_INDEX] > 0) + || (camera_mouse_look && mControlsTakenCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0) + || (!camera_mouse_look && mControlsTakenPassedOnCount[CONTROL_LBUTTON_DOWN_INDEX] > 0) + || (camera_mouse_look && mControlsTakenPassedOnCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0); } bool LLAgent::rotateGrabbed() const @@ -4282,7 +4240,6 @@ void LLAgent::onCapabilitiesReceivedAfterTeleport() check_merchant_status(); } - void LLAgent::teleportRequest( const U64& region_handle, const LLVector3& pos_local, @@ -4396,7 +4353,6 @@ void LLAgent::doTeleportViaLure(const LLUUID& lure_id, bool godlike) } } - // James Cook, July 28, 2005 void LLAgent::teleportCancel() { @@ -4521,7 +4477,6 @@ LLAgent::ETeleportState LLAgent::getTeleportState() const TELEPORT_NONE : mTeleportState; } - void LLAgent::setTeleportState(ETeleportState state) { if (mTeleportRequest && (state != TELEPORT_NONE) && (mTeleportRequest->getStatus() == LLTeleportRequest::kFailed)) @@ -4566,7 +4521,6 @@ void LLAgent::setTeleportState(ETeleportState state) } } - void LLAgent::stopCurrentAnimations() { LL_DEBUGS("Avatar") << "Stopping current animations" << LL_ENDL; @@ -4681,7 +4635,6 @@ void LLAgent::stopFidget() gAgent.sendAnimationRequests(anims, ANIM_REQUEST_STOP); } - void LLAgent::requestEnterGodMode() { LLMessageSystem* msg = gMessageSystem; @@ -4802,7 +4755,6 @@ void LLAgent::sendAgentUpdateUserInfo(const std::string& directory_visibility) } } - void LLAgent::updateAgentUserInfoCoro(std::string capurl, std::string directory_visibility) { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index d4767e18afb..339656089c8 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -1462,13 +1462,12 @@ void LLAgentCamera::updateCamera() // LL_INFOS() << "Current FOV Zoom: " << mCameraCurrentFOVZoomFactor << " Target FOV Zoom: " << mCameraFOVZoomFactor << " Object penetration: " << mFocusObjectDist << LL_ENDL; LLVector3 focus_agent = gAgent.getPosAgentFromGlobal(mFocusGlobal); + LLVector3 position_agent = gAgent.getPosAgentFromGlobal(camera_pos_global); - mCameraPositionAgent = gAgent.getPosAgentFromGlobal(camera_pos_global); + // Try to move the camera - // Move the camera - - LLViewerCamera::getInstance()->updateCameraLocation(mCameraPositionAgent, mCameraUpVector, focus_agent); - //LLViewerCamera::getInstance()->updateCameraLocation(mCameraPositionAgent, camera_skyward, focus_agent); + if (!LLViewerCamera::getInstance()->updateCameraLocation(position_agent, mCameraUpVector, focus_agent)) + return; // Change FOV LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / (1.f + mCameraCurrentFOVZoomFactor)); @@ -1476,7 +1475,7 @@ void LLAgentCamera::updateCamera() // follow camera when in customize mode if (cameraCustomizeAvatar()) { - setLookAt(LOOKAT_TARGET_FOCUS, NULL, mCameraPositionAgent); + setLookAt(LOOKAT_TARGET_FOCUS, NULL, position_agent); } // update the travel distance stat @@ -1495,8 +1494,8 @@ void LLAgentCamera::updateCamera() LLVector3 head_pos = gAgentAvatarp->mHeadp->getWorldPosition() + LLVector3(0.08f, 0.f, 0.05f) * gAgentAvatarp->mHeadp->getWorldRotation() + LLVector3(0.1f, 0.f, 0.f) * gAgentAvatarp->mPelvisp->getWorldRotation(); - LLVector3 diff = mCameraPositionAgent - head_pos; - diff = diff * ~gAgentAvatarp->mRoot->getWorldRotation(); + LLVector3 diff = position_agent - head_pos; + diff *= ~gAgentAvatarp->mRoot->getWorldRotation(); LLJoint* torso_joint = gAgentAvatarp->mTorsop; LLJoint* chest_joint = gAgentAvatarp->mChestp; @@ -2256,7 +2255,8 @@ void LLAgentCamera::changeCameraToFollow(bool animate) mCameraMode = CAMERA_MODE_FOLLOW; // bang-in the current focus, position, and up vector of the follow cam - mFollowCam.reset(mCameraPositionAgent, LLViewerCamera::getInstance()->getPointOfInterest(), LLVector3::z_axis); + const LLViewerCamera& camera = LLViewerCamera::instance(); + mFollowCam.reset(camera.getOrigin(), camera.getPointOfInterest(), LLVector3::z_axis); if (gBasicToolset) { diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h index 52571f3c55f..d277fd6158d 100644 --- a/indra/newview/llagentcamera.h +++ b/indra/newview/llagentcamera.h @@ -112,6 +112,7 @@ class LLAgentCamera //-------------------------------------------------------------------- public: void switchCameraPreset(ECameraPreset preset); + ECameraPreset getCameraPreset() const { return mCameraPreset; } /** Determines default camera offset depending on the current camera preset */ LLVector3 getCameraOffsetInitial(); /** Determines default focus offset depending on the current camera preset */ @@ -138,13 +139,14 @@ class LLAgentCamera //-------------------------------------------------------------------- public: LLVector3d getCameraPositionGlobal() const; - const LLVector3 &getCameraPositionAgent() const; + const LLVector3& getCameraPositionAgent() const; LLVector3d calcCameraPositionTargetGlobal(bool *hit_limit = NULL); // Calculate the camera position target F32 getCameraMinOffGround(); // Minimum height off ground for this mode, meters void setCameraCollidePlane(const LLVector4 &plane) { mCameraCollidePlane = plane; } bool calcCameraMinDistance(F32 &obj_min_distance); - F32 getCurrentCameraBuildOffset() { return (F32)mCameraFocusOffset.length(); } + F32 getCurrentCameraBuildOffset() const { return (F32)mCameraFocusOffset.length(); } void clearCameraLag() { mCameraLag.clearVec(); } + const LLVector3& getCameraUpVector() const { return mCameraUpVector; } private: LLVector3 getAvatarRootPosition(); @@ -154,7 +156,6 @@ class LLAgentCamera F32 mCameraCurrentFOVZoomFactor; // Interpolated fov zoom LLVector4 mCameraCollidePlane; // Colliding plane for camera F32 mCameraZoomFraction; // Mousewheel driven fraction of zoom - LLVector3 mCameraPositionAgent; // Camera position in agent coordinates LLVector3 mCameraVirtualPositionAgent; // Camera virtual position (target) before performing FOV zoom LLVector3d mCameraSmoothingLastPositionGlobal; LLVector3d mCameraSmoothingLastPositionAgent; @@ -278,7 +279,7 @@ class LLAgentCamera F32 getAgentHUDTargetZoom(); void resetCameraZoomFraction(); - F32 getCurrentCameraZoomFraction() { return mCameraZoomFraction; } + F32 getCurrentCameraZoomFraction() const { return mCameraZoomFraction; } //-------------------------------------------------------------------- // Pan diff --git a/indra/newview/llagentpilot.cpp b/indra/newview/llagentpilot.cpp index 0b5198bbd3e..8b18b7d5a2a 100644 --- a/indra/newview/llagentpilot.cpp +++ b/indra/newview/llagentpilot.cpp @@ -322,9 +322,7 @@ void LLAgentPilot::moveCamera() LLViewerCamera::getInstance()->setView(view); LLViewerCamera::getInstance()->setOrigin(origin); - LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); - LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); - LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); + LLViewerCamera::getInstance()->setAxes(mat); } } diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index cd4222dddf9..25f5cbd78fc 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -1094,12 +1094,12 @@ void LLAgentWearables::setWearableOutfit(const LLInventoryItem::item_array_t& it { gAgentAvatarp->setCompositeUpdatesEnabled(true); - // If we have not yet declouded, we may want to use + // If we have not yet loaded core parts, we may want to use // baked texture UUIDs sent from the first objectUpdate message - // don't overwrite these. If we have already declouded, we've saved - // these ids as the last known good textures and can invalidate without - // re-clouding. - if (!gAgentAvatarp->getIsCloud()) + // don't overwrite these. If we have parts already, we've saved + // these texture ids as the last known good textures and can + // invalidate without having to recloud avatar. + if (!gAgentAvatarp->getHasMissingParts()) { gAgentAvatarp->invalidateAll(); } diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index 11c5ffecb61..5114ac8a081 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -839,7 +839,7 @@ void AISAPI::onUpdateReceived(const LLSD& update, COMMAND_TYPE type, const LLSD& if ( (type == UPDATECATEGORY || type == UPDATEITEM) && gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) { - dump_sequential_xml(gAgentAvatarp->getFullname() + "_ais_update", update); + dump_sequential_xml(gAgentAvatarp->getDebugName() + "_ais_update", update); } AISUpdate ais_update(update, type, request_body); diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index e9d455ae53a..6fa23727b1c 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -856,7 +856,7 @@ void LLWearableHoldingPattern::checkMissingWearables() // was requested but none was found, create a default asset as a replacement. // In all other cases, don't do anything. // For critical types (shape/hair/skin/eyes), this will keep the avatar as a cloud - // due to logic in LLVOAvatarSelf::getIsCloud(). + // due to logic in LLVOAvatarSelf::getHasMissingParts(). // For non-critical types (tatoo, socks, etc.) the wearable will just be missing. (requested_by_type[type] > 0) && ((type == LLWearableType::WT_PANTS) || (type == LLWearableType::WT_SHIRT) || (type == LLWearableType::WT_SKIRT))) @@ -2045,7 +2045,7 @@ bool LLAppearanceMgr::getCanReplaceCOF(const LLUUID& outfit_cat_id) } // Moved from LLWearableList::ContextMenu for wider utility. -bool LLAppearanceMgr::canAddWearables(const uuid_vec_t& item_ids) const +bool LLAppearanceMgr::canAddWearables(const uuid_vec_t& item_ids, bool warn_on_type_mismatch) const { // TODO: investigate wearables may not be loaded at this point EXT-8231 @@ -2075,7 +2075,10 @@ bool LLAppearanceMgr::canAddWearables(const uuid_vec_t& item_ids) const } else { + if (warn_on_type_mismatch) + { LL_WARNS() << "Unexpected wearable type" << LL_ENDL; + } return false; } } @@ -2266,7 +2269,7 @@ void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append) } if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) { - dump_sequential_xml(gAgentAvatarp->getFullname() + "_slam_request", contents); + dump_sequential_xml(gAgentAvatarp->getDebugName() + "_slam_request", contents); } slam_inventory_folder(getCOF(), contents, link_waiter); @@ -3959,7 +3962,7 @@ void LLAppearanceMgr::serverAppearanceUpdateCoro(LLCoreHttpUtil::HttpCoroutineAd LL_DEBUGS("Avatar") << "succeeded" << LL_ENDL; if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) { - dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_ok", result); + dump_sequential_xml(gAgentAvatarp->getDebugName() + "_appearance_request_ok", result); } } while (bRetry); @@ -3968,7 +3971,7 @@ void LLAppearanceMgr::serverAppearanceUpdateCoro(LLCoreHttpUtil::HttpCoroutineAd /*static*/ void LLAppearanceMgr::debugAppearanceUpdateCOF(const LLSD& content) { - dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_error", content); + dump_sequential_xml(gAgentAvatarp->getDebugName() + "_appearance_request_error", content); LL_INFOS("Avatar") << "AIS COF, version received: " << content["expected"].asInteger() << " ================================= " << LL_ENDL; diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index bc7dc9506bb..0a41a91750c 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -103,7 +103,7 @@ class LLAppearanceMgr: public LLSingleton bool getCanReplaceCOF(const LLUUID& outfit_cat_id); // Can we add all referenced items to the avatar? - bool canAddWearables(const uuid_vec_t& item_ids) const; + bool canAddWearables(const uuid_vec_t& item_ids, bool warn_on_type_mismatch = true) const; // Copy all items in a category. void shallowCopyCategoryContents(const LLUUID& src_id, const LLUUID& dst_id, diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 35fdc18839d..66dadf75450 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -268,6 +268,16 @@ using namespace LL; #include "glib.h" #endif // (LL_LINUX) && LL_GTK +#ifdef LL_DISCORD +#define DISCORDPP_IMPLEMENTATION +#include +static std::shared_ptr gDiscordClient; +static uint64_t gDiscordTimestampsStart; +static std::string gDiscordActivityDetails; +static int32_t gDiscordPartyCurrentSize; +static int32_t gDiscordPartyMaxSize; +#endif + static LLAppViewerListener sAppViewerListener(LLAppViewer::instance); ////// Windows-specific includes to the bottom - nasty defines in these pollute the preprocessor @@ -443,13 +453,28 @@ static bool app_metrics_qa_mode = false; void idle_afk_check() { + // Don't check AFK status during startup states + if (LLStartUp::getStartupState() < STATE_STARTED) + { + return; + } + // check idle timers F32 current_idle = gAwayTriggerTimer.getElapsedTimeF32(); static LLCachedControl afk_timeout(gSavedSettings, "AFKTimeout", 300); - if (afk_timeout() && (current_idle > (F32)afk_timeout()) && !gAgent.getAFK()) + if (afk_timeout() && (current_idle > afk_timeout())) { - LL_INFOS("IdleAway") << "Idle more than " << afk_timeout << " seconds: automatically changing to Away status" << LL_ENDL; - gAgent.setAFK(); + if (!gAgent.getAFK()) + { + LL_INFOS("IdleAway") << "Idle more than " << afk_timeout << " seconds: automatically changing to Away status" << LL_ENDL; + gAgent.setAFK(); + } + else + { + // Refresh timer so that random one click or hover won't clear the status. + // But expanding the window still should lift afk status + gAwayTimer.reset(); + } } } @@ -1319,6 +1344,13 @@ bool LLAppViewer::frame() bool LLAppViewer::doFrame() { +#ifdef LL_DISCORD + { + LL_PROFILE_ZONE_NAMED("discord_callbacks"); + discordpp::RunCallbacks(); + } +#endif + LL_RECORD_BLOCK_TIME(FTM_FRAME); { // and now adjust the visuals from previous frame. @@ -1835,36 +1867,6 @@ bool LLAppViewer::cleanup() // Clean up before GL is shut down because we might be holding on to objects with texture references LLSelectMgr::cleanupGlobals(); - LL_INFOS() << "Shutting down OpenGL" << LL_ENDL; - - // Shut down OpenGL - if( gViewerWindow) - { - gViewerWindow->shutdownGL(); - - // Destroy window, and make sure we're not fullscreen - // This may generate window reshape and activation events. - // Therefore must do this before destroying the message system. - delete gViewerWindow; - gViewerWindow = NULL; - LL_INFOS() << "ViewerWindow deleted" << LL_ENDL; - } - - LLSplashScreen::show(); - LLSplashScreen::update(LLTrans::getString("ShuttingDown")); - - LL_INFOS() << "Cleaning up Keyboard & Joystick" << LL_ENDL; - - // viewer UI relies on keyboard so keep it aound until viewer UI isa gone - delete gKeyboard; - gKeyboard = NULL; - - if (LLViewerJoystick::instanceExists()) - { - // Turn off Space Navigator and similar devices - LLViewerJoystick::getInstance()->terminate(); - } - LL_INFOS() << "Cleaning up Objects" << LL_ENDL; LLViewerObject::cleanupVOClasses(); @@ -2025,6 +2027,36 @@ bool LLAppViewer::cleanup() sTextureFetch->shutDownTextureCacheThread() ; LLLFSThread::sLocal->shutdown(); + LL_INFOS() << "Shutting down OpenGL" << LL_ENDL; + + // Shut down OpenGL + if (gViewerWindow) + { + gViewerWindow->shutdownGL(); + + // Destroy window, and make sure we're not fullscreen + // This may generate window reshape and activation events. + // Therefore must do this before destroying the message system. + delete gViewerWindow; + gViewerWindow = NULL; + LL_INFOS() << "ViewerWindow deleted" << LL_ENDL; + } + + LLSplashScreen::show(); + LLSplashScreen::update(LLTrans::getString("ShuttingDown")); + + LL_INFOS() << "Cleaning up Keyboard & Joystick" << LL_ENDL; + + // viewer UI relies on keyboard so keep it aound until viewer UI isa gone + delete gKeyboard; + gKeyboard = NULL; + + if (LLViewerJoystick::instanceExists()) + { + // Turn off Space Navigator and similar devices + LLViewerJoystick::getInstance()->terminate(); + } + LL_INFOS() << "Shutting down message system" << LL_ENDL; end_messaging_system(); @@ -3093,7 +3125,15 @@ bool LLAppViewer::initWindow() .height(gSavedSettings.getU32("WindowHeight")) .min_width(gSavedSettings.getU32("MinWindowWidth")) .min_height(gSavedSettings.getU32("MinWindowHeight")) +#ifdef LL_DARWIN + // Setting it to true causes black screen with no UI displayed. + // Given that it's a DEBUG settings and application goes fullscreen + // on mac simply by expanding it, it was decided to not support/use + // this setting on mac. + .fullscreen(false) +#else // LL_DARWIN .fullscreen(gSavedSettings.getBOOL("FullScreen")) +#endif .ignore_pixel_depth(ignorePixelDepth) .first_run(mIsFirstRun); @@ -4279,8 +4319,8 @@ bool LLAppViewer::initCache() const std::string cache_dir_name = gSavedSettings.getString("DiskCacheDirName"); const U32 MB = 1024 * 1024; - const uintmax_t MIN_CACHE_SIZE = 256 * MB; - const uintmax_t MAX_CACHE_SIZE = 9984ll * MB; + const uintmax_t MIN_CACHE_SIZE = 896 * MB; + const uintmax_t MAX_CACHE_SIZE = 32768ll * MB; const uintmax_t setting_cache_total_size = uintmax_t(gSavedSettings.getU32("CacheSize")) * MB; const uintmax_t cache_total_size = llclamp(setting_cache_total_size, MIN_CACHE_SIZE, MAX_CACHE_SIZE); const F64 disk_cache_percent = gSavedSettings.getF32("DiskCachePercentOfTotal"); @@ -5881,3 +5921,180 @@ void LLAppViewer::metricsSend(bool enable_reporting) gViewerAssetStats->restart(); } +#ifdef LL_DISCORD + +void LLAppViewer::initDiscordSocial() +{ + gDiscordPartyCurrentSize = 1; + gDiscordPartyMaxSize = 0; + gDiscordTimestampsStart = time(nullptr); + gDiscordClient = std::make_shared(); + gDiscordClient->SetStatusChangedCallback([](discordpp::Client::Status status, discordpp::Client::Error, int32_t) { + if (status == discordpp::Client::Status::Ready) + { + updateDiscordActivity(); + } + }); + if (gSavedSettings.getBOOL("EnableDiscord")) + { + auto credential = gSecAPIHandler->loadCredential("Discord"); + if (credential.notNull()) + { + gDiscordClient->UpdateToken(discordpp::AuthorizationTokenType::Bearer, credential->getAuthenticator()["token"].asString(), [](discordpp::ClientResult result) { + if (result.Successful()) + gDiscordClient->Connect(); + else + LL_WARNS("Discord") << result.Error() << LL_ENDL; + }); + } + else + { + LL_WARNS("Discord") << "Integration was enabled, but no credentials. Disabling integration." << LL_ENDL; + gSavedSettings.setBOOL("EnableDiscord", false); + } + } +} + +void LLAppViewer::toggleDiscordIntegration(const LLSD& value) +{ + static const uint64_t APPLICATION_ID = 1394782217405862001; + if (value.asBoolean()) + { + discordpp::AuthorizationArgs args{}; + args.SetClientId(APPLICATION_ID); + args.SetScopes(discordpp::Client::GetDefaultPresenceScopes()); + auto codeVerifier = gDiscordClient->CreateAuthorizationCodeVerifier(); + args.SetCodeChallenge(codeVerifier.Challenge()); + gDiscordClient->Authorize(args, [codeVerifier](auto result, auto code, auto redirectUri) { + if (result.Successful()) + { + gDiscordClient->GetToken(APPLICATION_ID, code, codeVerifier.Verifier(), redirectUri, [](discordpp::ClientResult result, std::string accessToken, std::string, discordpp::AuthorizationTokenType, int32_t, std::string) { + if (result.Successful()) + { + gDiscordClient->UpdateToken(discordpp::AuthorizationTokenType::Bearer, accessToken, [accessToken](discordpp::ClientResult result) { + if (result.Successful()) + { + LLSD authenticator = LLSD::emptyMap(); + authenticator["token"] = accessToken; + gSecAPIHandler->saveCredential(gSecAPIHandler->createCredential("Discord", LLSD::emptyMap(), authenticator), true); + gDiscordClient->Connect(); + } + else + { + LL_WARNS("Discord") << result.Error() << LL_ENDL; + } + }); + } + else + { + LL_WARNS("Discord") << result.Error() << LL_ENDL; + } + }); + } + else + { + LL_WARNS("Discord") << result.Error() << LL_ENDL; + gSavedSettings.setBOOL("EnableDiscord", false); + } + }); + } + else + { + gDiscordClient->Disconnect(); + auto credential = gSecAPIHandler->loadCredential("Discord"); + if (credential.notNull()) + { + gDiscordClient->RevokeToken(APPLICATION_ID, credential->getAuthenticator()["token"].asString(), [](discordpp::ClientResult result) { + if (result.Successful()) + LL_INFOS("Discord") << "Access token successfully revoked." << LL_ENDL; + else + LL_WARNS("Discord") << "No access token to revoke." << LL_ENDL; + }); + auto cred = new LLCredential("Discord"); + gSecAPIHandler->deleteCredential(cred); + } + else + { + LL_WARNS("Discord") << "Credentials are already nonexistent." << LL_ENDL; + } + } +} + +void LLAppViewer::updateDiscordActivity() +{ + LL_PROFILE_ZONE_SCOPED; + discordpp::Activity activity; + activity.SetType(discordpp::ActivityTypes::Playing); + discordpp::ActivityTimestamps timestamps; + timestamps.SetStart(gDiscordTimestampsStart); + activity.SetTimestamps(timestamps); + + if (gAgent.getID() == LLUUID::null) + { + gDiscordClient->UpdateRichPresence(activity, [](discordpp::ClientResult) {}); + return; + } + + static LLCachedControl show_details(gSavedSettings, "ShowDiscordActivityDetails", false); + if (show_details) + { + if (gDiscordActivityDetails.empty()) + { + LLAvatarName av_name; + LLAvatarNameCache::get(gAgent.getID(), &av_name); + gDiscordActivityDetails = av_name.getUserName(); + auto displayName = av_name.getDisplayName(); + if (gDiscordActivityDetails != displayName) + gDiscordActivityDetails = displayName + " (" + gDiscordActivityDetails + ")"; + } + activity.SetDetails(gDiscordActivityDetails); + } + + static LLCachedControl show_state(gSavedSettings, "ShowDiscordActivityState", false); + if (show_state) + { + auto agent_pos_region = gAgent.getPositionAgent(); + S32 pos_x = S32(agent_pos_region.mV[VX] + 0.5f); + S32 pos_y = S32(agent_pos_region.mV[VY] + 0.5f); + S32 pos_z = S32(agent_pos_region.mV[VZ] + 0.5f); + F32 velocity_mag_sq = gAgent.getVelocity().magVecSquared(); + const F32 FLY_CUTOFF = 6.f; + const F32 FLY_CUTOFF_SQ = FLY_CUTOFF * FLY_CUTOFF; + const F32 WALK_CUTOFF = 1.5f; + const F32 WALK_CUTOFF_SQ = WALK_CUTOFF * WALK_CUTOFF; + if (velocity_mag_sq > FLY_CUTOFF_SQ) + { + pos_x -= pos_x % 4; + pos_y -= pos_y % 4; + } + else if (velocity_mag_sq > WALK_CUTOFF_SQ) + { + pos_x -= pos_x % 2; + pos_y -= pos_y % 2; + } + auto location = llformat("%s (%d, %d, %d)", gAgent.getRegion()->getName().c_str(), pos_x, pos_y, pos_z); + activity.SetState(location); + + discordpp::ActivityParty party; + party.SetId(location); + party.SetCurrentSize(gDiscordPartyCurrentSize); + party.SetMaxSize(gDiscordPartyMaxSize); + activity.SetParty(party); + } + + gDiscordClient->UpdateRichPresence(activity, [](discordpp::ClientResult) {}); +} + +void LLAppViewer::updateDiscordPartyCurrentSize(int32_t size) +{ + gDiscordPartyCurrentSize = size; + updateDiscordActivity(); +} + +void LLAppViewer::updateDiscordPartyMaxSize(int32_t size) +{ + gDiscordPartyMaxSize = size; + updateDiscordActivity(); +} + +#endif diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index bafe9526595..14e96afe94a 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -251,6 +251,14 @@ class LLAppViewer : public LLApp // Note: mQuitRequested can be aborted by user. void outOfMemorySoftQuit(); +#ifdef LL_DISCORD + static void initDiscordSocial(); + static void toggleDiscordIntegration(const LLSD& value); + static void updateDiscordActivity(); + static void updateDiscordPartyCurrentSize(int32_t size); + static void updateDiscordPartyMaxSize(int32_t size); +#endif + protected: virtual bool initWindow(); // Initialize the viewer's window. virtual void initLoggingAndGetLastDuration(); // Initialize log files, logging system diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 6274933586b..8477bd30441 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -448,6 +448,7 @@ int APIENTRY WINMAIN(HINSTANCE hInstance, // *FIX: global gIconResource = MAKEINTRESOURCE(IDI_LL_ICON); + gIconSmallResource = MAKEINTRESOURCE(IDI_LL_ICON_SMALL); LLAppViewerWin32* viewer_app_ptr = new LLAppViewerWin32(ll_convert_wide_to_string(pCmdLine).c_str()); diff --git a/indra/newview/llautoreplace.cpp b/indra/newview/llautoreplace.cpp index f200ca8e31e..1ea2899ba41 100644 --- a/indra/newview/llautoreplace.cpp +++ b/indra/newview/llautoreplace.cpp @@ -536,11 +536,12 @@ LLAutoReplaceSettings::AddListResult LLAutoReplaceSettings::replaceList(const LL S32 search_index; LLSD targetList; // The following is working around the fact that LLSD arrays containing maps also seem to have undefined entries... see LLSD-30 - for ( search_index = 0, targetList = mLists[0]; + for ( search_index = 0; !listFound && search_index < mLists.size(); - search_index += 1, targetList = mLists[search_index] + search_index += 1 ) { + targetList = mLists[search_index]; if ( targetList.isMap() ) { if ( listNameMatches( targetList, listName) ) diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index a48e22bc731..a966253c2c8 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -936,7 +936,7 @@ class LLChatHistoryHeader: public LLPanel menu->setItemEnabled("Voice Call", false); menu->setItemEnabled("Chat History", false); menu->setItemEnabled("Invite Group", false); - menu->setItemEnabled("Zoom In", false); + menu->setItemEnabled("Zoom In", true); menu->setItemEnabled("Share", false); menu->setItemEnabled("Pay", false); menu->setItemEnabled("Block Unblock", false); diff --git a/indra/newview/llcontrolavatar.cpp b/indra/newview/llcontrolavatar.cpp index 92012418569..660fb1b41ac 100644 --- a/indra/newview/llcontrolavatar.cpp +++ b/indra/newview/llcontrolavatar.cpp @@ -129,14 +129,14 @@ void LLControlAvatar::getNewConstraintFixups(LLVector3& new_pos_fixup, F32& new_ { LLVector3 pos_box_offset = point_to_box_offset(vol_pos, unshift_extents); F32 offset_dist = pos_box_offset.length(); - if (offset_dist > MAX_LEGAL_OFFSET && offset_dist > 0.f) + if (offset_dist > max_legal_offset && offset_dist > 0.f) { - F32 target_dist = (offset_dist - MAX_LEGAL_OFFSET); + F32 target_dist = (offset_dist - max_legal_offset); new_pos_fixup = (target_dist/offset_dist)*pos_box_offset; } if (new_pos_fixup != mPositionConstraintFixup) { - LL_DEBUGS("ConstraintFix") << getFullname() << " pos fix, offset_dist " << offset_dist << " pos fixup " + LL_DEBUGS("ConstraintFix") << getDebugName() << " pos fix, offset_dist " << offset_dist << " pos fixup " << new_pos_fixup << " was " << mPositionConstraintFixup << LL_ENDL; LL_DEBUGS("ConstraintFix") << "vol_pos " << vol_pos << LL_ENDL; LL_DEBUGS("ConstraintFix") << "extents " << extents[0] << " " << extents[1] << LL_ENDL; @@ -144,11 +144,11 @@ void LLControlAvatar::getNewConstraintFixups(LLVector3& new_pos_fixup, F32& new_ } } - if (box_size/mScaleConstraintFixup > MAX_LEGAL_SIZE) + if (box_size/mScaleConstraintFixup > max_legal_size) { - new_scale_fixup = mScaleConstraintFixup* MAX_LEGAL_SIZE /box_size; - LL_DEBUGS("ConstraintFix") << getFullname() << " scale fix, box_size " << box_size << " fixup " - << mScaleConstraintFixup << " max legal " << MAX_LEGAL_SIZE + new_scale_fixup = mScaleConstraintFixup*max_legal_size/box_size; + LL_DEBUGS("ConstraintFix") << getDebugName() << " scale fix, box_size " << box_size << " fixup " + << mScaleConstraintFixup << " max legal " << max_legal_size << " -> new scale " << new_scale_fixup << LL_ENDL; } } @@ -231,7 +231,7 @@ void LLControlAvatar::matchVolumeTransform() const LLMeshSkinInfo* skin_info = mRootVolp->getSkinInfo(); if (skin_info) { - LL_DEBUGS("BindShape") << getFullname() << " bind shape " << skin_info->mBindShapeMatrix << LL_ENDL; + LL_DEBUGS("BindShape") << getDebugName() << " bind shape " << skin_info->mBindShapeMatrix << LL_ENDL; bind_rot = LLSkinningUtil::getUnscaledQuaternion(LLMatrix4(skin_info->mBindShapeMatrix)); } #endif diff --git a/indra/newview/llconversationlog.cpp b/indra/newview/llconversationlog.cpp index e6a720e734b..cea68c1779a 100644 --- a/indra/newview/llconversationlog.cpp +++ b/indra/newview/llconversationlog.cpp @@ -663,7 +663,7 @@ void LLConversationLog::onClearLogResponse(const LLSD& notification, const LLSD& { mConversations.clear(); notifyObservers(); - cache(); + saveToFile(getFileName()); deleteBackupLogs(); } } diff --git a/indra/newview/llconversationmodel.cpp b/indra/newview/llconversationmodel.cpp index 50040556669..bb1daf4ec18 100644 --- a/indra/newview/llconversationmodel.cpp +++ b/indra/newview/llconversationmodel.cpp @@ -300,7 +300,7 @@ void LLConversationItemSession::updateName(LLConversationItemParticipant* partic for (auto itemp : mChildren) { - LLConversationItem* current_participant = dynamic_cast(itemp); + LLConversationItem* current_participant = dynamic_cast(itemp.get()); // Add the avatar uuid to the list (except if it's the own agent uuid) if (current_participant->getUUID() != gAgentID) { @@ -329,6 +329,7 @@ void LLConversationItemSession::updateName(LLConversationItemParticipant* partic void LLConversationItemSession::removeParticipant(LLConversationItemParticipant* participant) { + LLPointer holder(participant); removeChild(participant); mNeedsRefresh = true; updateName(participant); @@ -360,15 +361,10 @@ void LLConversationItemSession::clearAndDeparentModels() for (child_list_t::iterator it = mChildren.begin(); it != mChildren.end();) { LLFolderViewModelItem* child = *it; - if (child->getNumRefs() == 0) + // Note that model might still be assigned to some view/widget + // and have a different parent + if (child->getParent() == this) { - // LLConversationItemParticipant can be created but not assigned to any view, - // it was waiting for an "add_participant" event to be processed - delete child; - } - else - { - // Model is still assigned to some view/widget child->setParent(NULL); } it = mChildren.erase(it); @@ -383,7 +379,7 @@ LLConversationItemParticipant* LLConversationItemSession::findParticipant(const child_list_t::iterator iter; for (iter = mChildren.begin(); iter != mChildren.end(); iter++) { - participant = dynamic_cast(*iter); + participant = dynamic_cast((*iter).get()); if (participant && participant->hasSameValue(participant_id)) { break; @@ -493,7 +489,7 @@ const bool LLConversationItemSession::getTime(F64& time) const child_list_t::const_iterator iter; for (iter = mChildren.begin(); iter != mChildren.end(); iter++) { - participant = dynamic_cast(*iter); + participant = dynamic_cast((*iter).get()); F64 participant_time; if (participant && participant->getTime(participant_time)) { @@ -517,7 +513,7 @@ void LLConversationItemSession::dumpDebugData(bool dump_children) { for (child_list_t::iterator iter = mChildren.begin(); iter != mChildren.end(); iter++) { - LLConversationItemParticipant* participant = dynamic_cast(*iter); + LLConversationItemParticipant* participant = dynamic_cast((*iter).get()); if (participant) { participant->dumpDebugData(); diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index c1e48c63a94..d5486b9f4a6 100644 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -79,6 +79,9 @@ class LLConversationItem : public LLFolderViewModelItemCommon virtual LLPointer getOpenIcon() const { return getIcon(); } virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } virtual std::string getLabelSuffix() const { return LLStringUtil::null; } + virtual bool isFavorite() const { return false; } + virtual bool isAgentInventory() const { return false; } + virtual bool isAgentInventoryRoot() const { return false; } virtual bool isItemRenameable() const { return true; } virtual bool renameItem(const std::string& new_name) { mName = new_name; mNeedsRefresh = true; return true; } virtual bool isItemMovable( void ) const { return false; } diff --git a/indra/newview/llfloaterautoreplacesettings.cpp b/indra/newview/llfloaterautoreplacesettings.cpp index 99f24e161ee..d93bd624f5f 100644 --- a/indra/newview/llfloaterautoreplacesettings.cpp +++ b/indra/newview/llfloaterautoreplacesettings.cpp @@ -422,7 +422,13 @@ bool LLFloaterAutoReplaceSettings::callbackNewListName(const LLSD& notification, LLSD newList = notification["payload"]["list"]; - if ( response.has("listname") && response["listname"].isString() ) + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 1) // Must also match RenameAutoReplaceList + { + // user cancelled + return false; + } + else if (response.has("listname") && response["listname"].isString() ) { std::string newName = response["listname"].asString(); LLAutoReplaceSettings::setListName(newList, newName); @@ -508,12 +514,53 @@ bool LLFloaterAutoReplaceSettings::callbackListNameConflict(const LLSD& notifica return false; } +bool LLFloaterAutoReplaceSettings::callbackRemoveList(const LLSD& notification, const LLSD& response) +{ + std::string listName = notification["payload"]["list"]; + + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch (option) + { + case 1: + if (mSettings.removeReplacementList(listName)) + { + LL_INFOS("AutoReplace") << "deleted list '" << listName << "'" << LL_ENDL; + mReplacementsList->deleteSelectedItems(); // remove from the scrolling list + mSelectedListName.clear(); + updateListNames(); + updateListNamesControls(); + updateReplacementsList(); + } + break; + case 0: + break; + + default: + LL_ERRS("AutoReplace") << "invalid selected option " << option << LL_ENDL; + } + + return false; +} + void LLFloaterAutoReplaceSettings::onDeleteList() { std::string listName = mListNames->getSelectedValue().asString(); if ( ! listName.empty() ) { - if ( mSettings.removeReplacementList(listName) ) + const LLSD* mappings = mSettings.getListEntries(mSelectedListName); + if (mappings->size() > 0) + { + LLSD payload; + payload["list"] = listName; + + LLSD args; + args["MAP_SIZE"] = llformat("%d",mappings->size()); + args["LIST_NAME"] = listName; + + LLNotificationsUtil::add("RemoveAutoReplaceList", args, payload, + boost::bind(&LLFloaterAutoReplaceSettings::callbackRemoveList, this, _1, _2)); + } + else if ( mSettings.removeReplacementList(listName) ) { LL_INFOS("AutoReplace")<<"deleted list '"<deleteSelectedItems(); // remove from the scrolling list diff --git a/indra/newview/llfloaterautoreplacesettings.h b/indra/newview/llfloaterautoreplacesettings.h index 94a7c00c150..1a8068ab7c1 100644 --- a/indra/newview/llfloaterautoreplacesettings.h +++ b/indra/newview/llfloaterautoreplacesettings.h @@ -105,6 +105,8 @@ class LLFloaterAutoReplaceSettings : public LLFloater bool callbackNewListName(const LLSD& notification, const LLSD& response); /// called from the RenameAutoReplaceList notification dialog bool callbackListNameConflict(const LLSD& notification, const LLSD& response); + /// called from the RemoveAutoReplaceList notification dialog + bool callbackRemoveList(const LLSD& notification, const LLSD& response); bool selectedListIsFirst(); bool selectedListIsLast(); diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index 5ee93be061a..392079efe43 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -118,8 +118,8 @@ std::string STATUS[] = //----------------------------------------------------------------------------- // LLFloaterBvhPreview() //----------------------------------------------------------------------------- -LLFloaterBvhPreview::LLFloaterBvhPreview(const std::string& filename) : - LLFloaterNameDesc(filename) +LLFloaterBvhPreview::LLFloaterBvhPreview(const LLSD& args) : + LLFloaterNameDesc(args) { mLastMouseX = 0; mLastMouseY = 0; @@ -1028,7 +1028,8 @@ void LLFloaterBvhPreview::onBtnOK(void* userdata) LLFloaterPerms::getNextOwnerPerms("Uploads"), LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost)); + expected_upload_cost, + floaterp->mDestinationFolderId)); upload_new_resource(assetUploadInfo); } diff --git a/indra/newview/llfloaterbvhpreview.h b/indra/newview/llfloaterbvhpreview.h index bb69ab65eff..1eb7f686fd1 100644 --- a/indra/newview/llfloaterbvhpreview.h +++ b/indra/newview/llfloaterbvhpreview.h @@ -70,7 +70,7 @@ class LLPreviewAnimation : public LLViewerDynamicTexture class LLFloaterBvhPreview : public LLFloaterNameDesc { public: - LLFloaterBvhPreview(const std::string& filename); + LLFloaterBvhPreview(const LLSD& args); virtual ~LLFloaterBvhPreview(); bool postBuild(); diff --git a/indra/newview/llfloaterchatmentionpicker.cpp b/indra/newview/llfloaterchatmentionpicker.cpp index 1cfed122a95..a3eb2863750 100644 --- a/indra/newview/llfloaterchatmentionpicker.cpp +++ b/indra/newview/llfloaterchatmentionpicker.cpp @@ -88,7 +88,7 @@ uuid_vec_t LLFloaterChatMentionPicker::getParticipantIds() LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); while (current_participant_model != end_participant_model) { - LLConversationItem* participant_model = dynamic_cast(*current_participant_model); + LLConversationItem* participant_model = dynamic_cast((*current_participant_model).get()); if (participant_model) { avatar_ids.push_back(participant_model->getUUID()); diff --git a/indra/newview/llfloatereditenvironmentbase.h b/indra/newview/llfloatereditenvironmentbase.h index 37fda5d33e3..41192f3d30d 100644 --- a/indra/newview/llfloatereditenvironmentbase.h +++ b/indra/newview/llfloatereditenvironmentbase.h @@ -133,7 +133,8 @@ class LLSettingsEditPanel : public LLPanel LLSettingsEditPanel() : LLPanel(), mIsDirty(false), - mOnDirtyChanged() + mOnDirtyChanged(), + mCanEdit(false) {} private: diff --git a/indra/newview/llfloaterimagepreview.cpp b/indra/newview/llfloaterimagepreview.cpp index 989e1d8d04f..550c3adc270 100644 --- a/indra/newview/llfloaterimagepreview.cpp +++ b/indra/newview/llfloaterimagepreview.cpp @@ -32,6 +32,7 @@ #include "llimagetga.h" #include "llimagejpeg.h" #include "llimagepng.h" +#include "llimagej2c.h" #include "llagent.h" #include "llagentbenefits.h" @@ -43,6 +44,10 @@ #include "llrender.h" #include "llface.h" #include "llfocusmgr.h" +#include "llfilesystem.h" +#include "llfloaterperms.h" +#include "llnotificationsutil.h" +#include "llstatusbar.h" // can_afford_transaction() #include "lltextbox.h" #include "lltoolmgr.h" #include "llui.h" @@ -52,6 +57,7 @@ #include "llvoavatar.h" #include "pipeline.h" #include "lluictrlfactory.h" +#include "llviewermenufile.h" // upload_new_resource() #include "llviewershadermgr.h" #include "llviewertexturelist.h" #include "llstring.h" @@ -72,8 +78,8 @@ const S32 PREVIEW_TEXTURE_HEIGHT = 320; //----------------------------------------------------------------------------- // LLFloaterImagePreview() //----------------------------------------------------------------------------- -LLFloaterImagePreview::LLFloaterImagePreview(const std::string& filename) : - LLFloaterNameDesc(filename), +LLFloaterImagePreview::LLFloaterImagePreview(const LLSD& args) : + LLFloaterNameDesc(args), mAvatarPreview(NULL), mSculptedPreview(NULL), @@ -140,7 +146,7 @@ bool LLFloaterImagePreview::postBuild() } } - getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this)); + getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterImagePreview::onBtnOK, this)); return true; } @@ -243,6 +249,61 @@ void LLFloaterImagePreview::clearAllPreviewTextures() } } +//----------------------------------------------------------------------------- +// onBtnOK() +//----------------------------------------------------------------------------- +void LLFloaterImagePreview::onBtnOK() +{ + getChildView("ok_btn")->setEnabled(false); // don't allow inadvertent extra uploads + + S32 expected_upload_cost = getExpectedUploadCost(); + if (can_afford_transaction(expected_upload_cost)) + { + LL_INFOS() << "saving texture: " << mRawImagep->getWidth() << "x" << mRawImagep->getHeight() << LL_ENDL; + // gen a new uuid for this asset + LLTransactionID tid; + tid.generate(); + LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); + + LLPointer formatted = new LLImageJ2C; + + if (formatted->encode(mRawImagep, 0.0f)) + { + LLFileSystem fmt_file(new_asset_id, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); + fmt_file.write(formatted->getData(), formatted->getDataSize()); + + LLResourceUploadInfo::ptr_t assetUploadInfo(new LLResourceUploadInfo( + tid, LLAssetType::AT_TEXTURE, + getChild("name_form")->getValue().asString(), + getChild("description_form")->getValue().asString(), + 0, + LLFolderType::FT_NONE, LLInventoryType::IT_NONE, + LLFloaterPerms::getNextOwnerPerms("Uploads"), + LLFloaterPerms::getGroupPerms("Uploads"), + LLFloaterPerms::getEveryonePerms("Uploads"), + expected_upload_cost + )); + + upload_new_resource(assetUploadInfo); + } + else + { + LLSD args; + args["REASON"] = LLImage::getLastThreadError(); + LLNotificationsUtil::add("ErrorEncodingImage", args); + LL_WARNS() << "Error encoding image" << LL_ENDL; + } + } + else + { + LLSD args; + args["COST"] = llformat("%d", expected_upload_cost); + LLNotificationsUtil::add("ErrorCannotAffordUpload", args); + } + + closeFloater(false); +} + //----------------------------------------------------------------------------- // draw() //----------------------------------------------------------------------------- @@ -364,16 +425,15 @@ bool LLFloaterImagePreview::loadImage(const std::string& src_filename) return false; } - S32 max_width = gSavedSettings.getS32("max_texture_dimension_X"); - S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y"); + // raw image is limited to 256MB so need at least some upper limit that fits into that + constexpr S32 MAX_IMAGE_AREA = 8096 * 8096; - if ((image_info.getWidth() > max_width) || (image_info.getHeight() > max_height)) + if (image_info.getWidth() * image_info.getHeight() > MAX_IMAGE_AREA) { LLStringUtil::format_map_t args; - args["WIDTH"] = llformat("%d", max_width); - args["HEIGHT"] = llformat("%d", max_height); + args["PIXELS"] = llformat("%dM", (S32)(MAX_IMAGE_AREA / 1000000)); - mImageLoadError = LLTrans::getString("texture_load_dimensions_error", args); + mImageLoadError = LLTrans::getString("texture_load_area_error", args); return false; } @@ -399,6 +459,46 @@ bool LLFloaterImagePreview::loadImage(const std::string& src_filename) image->setLastError("Image files with less than 3 or more than 4 components are not supported."); return false; } + // Downscale images to fit the max_texture_dimensions_* + S32 max_width = gSavedSettings.getS32("max_texture_dimension_X"); + S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y"); + + S32 orig_width = raw_image->getWidth(); + S32 orig_height = raw_image->getHeight(); + + if (orig_width > max_width || orig_height > max_height) + { + // Calculate scale factors + F32 width_scale = (F32)max_width / (F32)orig_width; + F32 height_scale = (F32)max_height / (F32)orig_height; + F32 scale = llmin(width_scale, height_scale); + + // Calculate new dimensions, preserving aspect ratio + S32 new_width = LLImageRaw::contractDimToPowerOfTwo( + llclamp((S32)llroundf(orig_width * scale), 4, max_width) + ); + S32 new_height = LLImageRaw::contractDimToPowerOfTwo( + llclamp((S32)llroundf(orig_height * scale), 4, max_height) + ); + + if (!raw_image->scale(new_width, new_height)) + { + LL_WARNS() << "Failed to scale image from " + << orig_width << "x" << orig_height + << " to " << new_width << "x" << new_height << LL_ENDL; + return false; + } + + // Inform the resident about the resized image + LLSD subs; + subs["[ORIGINAL_WIDTH]"] = orig_width; + subs["[ORIGINAL_HEIGHT]"] = orig_height; + subs["[NEW_WIDTH]"] = new_width; + subs["[NEW_HEIGHT]"] = new_height; + subs["[MAX_WIDTH]"] = max_width; + subs["[MAX_HEIGHT]"] = max_height; + LLNotificationsUtil::add("ImageUploadResized", subs); + } raw_image->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); mRawImagep = raw_image; diff --git a/indra/newview/llfloaterimagepreview.h b/indra/newview/llfloaterimagepreview.h index ed395722def..5e5f4932c23 100644 --- a/indra/newview/llfloaterimagepreview.h +++ b/indra/newview/llfloaterimagepreview.h @@ -110,7 +110,7 @@ class LLImagePreviewAvatar : public LLViewerDynamicTexture class LLFloaterImagePreview : public LLFloaterNameDesc { public: - LLFloaterImagePreview(const std::string& filename); + LLFloaterImagePreview(const LLSD& args); virtual ~LLFloaterImagePreview(); bool postBuild() override; @@ -126,6 +126,8 @@ class LLFloaterImagePreview : public LLFloaterNameDesc void clearAllPreviewTextures(); + void onBtnOK(); + protected: static void onPreviewTypeCommit(LLUICtrl*,void*); void draw() override; diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 72d4d30dcf2..d821d9a4a56 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -460,7 +460,7 @@ void LLFloaterIMContainer::processParticipantsStyleUpdate() LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = session_model->getChildrenEnd(); while (current_participant_model != end_participant_model) { - LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); + LLConversationItemParticipant* participant_model = dynamic_cast((*current_participant_model).get()); if (participant_model) { // Get the avatar name for this participant id from the cache and update the model @@ -511,7 +511,7 @@ void LLFloaterIMContainer::idleUpdate() bool can_ban = haveAbilityToBan(); while (current_participant_model != end_participant_model) { - LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); + LLConversationItemParticipant* participant_model = dynamic_cast((*current_participant_model).get()); if (participant_model) { participant_model->setModeratorOptionsVisible(is_moderator); @@ -1540,6 +1540,10 @@ bool LLFloaterIMContainer::enableContextMenuItem(const std::string& item, uuid_v // Beyond that point, if only the user agent is selected, everything is disabled if (is_single_select && (single_id == gAgentID)) { + if ("can_zoom_in" == item) + { + return true; + } if (is_moderator_option) { return enableModerateContextMenuItem(item, true); @@ -1874,7 +1878,7 @@ LLConversationItem* LLFloaterIMContainer::addConversationListItem(const LLUUID& LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); while (current_participant_model != end_participant_model) { - LLConversationItem* participant_model = dynamic_cast(*current_participant_model); + LLConversationItem* participant_model = dynamic_cast((*current_participant_model).get()); LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); participant_view->addToFolder(widget); current_participant_model++; diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 7c3d2b511ba..e03422780aa 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -107,26 +107,6 @@ LLFloaterIMSessionTab::~LLFloaterIMSessionTab() delete mRefreshTimer; LLIMMgr::instance().removeSessionObserver(this); mEmojiCloseConn.disconnect(); - - LLFloaterIMContainer* im_container = LLFloaterIMContainer::findInstance(); - if (im_container) - { - LLParticipantList* session = dynamic_cast(im_container->getSessionModel(mSessionID)); - if (session) - { - for (const conversations_widgets_map::value_type& widget_pair : mConversationsWidgets) - { - LLFolderViewItem* widget = widget_pair.second; - LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); - if (item_vmi && item_vmi->getNumRefs() == 1) - { - // This is the last pointer, remove participant from session - // before participant gets deleted on destroyView. - session->removeChild(item_vmi); - } - } - } - } } // static @@ -730,7 +710,7 @@ void LLFloaterIMSessionTab::buildConversationViewParticipant() LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); while (current_participant_model != end_participant_model) { - LLConversationItem* participant_model = dynamic_cast(*current_participant_model); + LLConversationItem* participant_model = dynamic_cast((*current_participant_model).get()); if (participant_model) { addConversationViewParticipant(participant_model); @@ -774,27 +754,6 @@ void LLFloaterIMSessionTab::removeConversationViewParticipant(const LLUUID& part LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); if (widget) { - LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); - if (item_vmi && item_vmi->getNumRefs() == 1) - { - // This is the last pointer, remove participant from session - // before participant gets deleted on destroyView. - // - // Floater (widget) and participant's view can simultaneously - // co-own the model, in which case view is responsible for - // the deletion and floater is free to clear and recreate - // the list, yet there are cases where only widget owns - // the pointer so it should do the cleanup. - // See "add_participant". - // - // Todo: If it keeps causing issues turn participants - // into LLPointers in the session - LLParticipantList* session = getParticipantList(); - if (session) - { - session->removeChild(item_vmi); - } - } widget->destroyView(); } mConversationsWidgets.erase(participant_id); @@ -860,7 +819,7 @@ void LLFloaterIMSessionTab::refreshConversation() LLIMSpeakerMgr *speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); while (current_participant_model != end_participant_model) { - LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); + LLConversationItemParticipant* participant_model = dynamic_cast((*current_participant_model).get()); if (speaker_mgr && participant_model) { LLSpeaker *participant_speaker = speaker_mgr->findSpeaker(participant_model->getUUID()); diff --git a/indra/newview/llfloaterinventorysettings.cpp b/indra/newview/llfloaterinventorysettings.cpp index e5ee69f240c..aab632bcb84 100644 --- a/indra/newview/llfloaterinventorysettings.cpp +++ b/indra/newview/llfloaterinventorysettings.cpp @@ -28,9 +28,14 @@ #include "llfloaterinventorysettings.h" +#include "llcolorswatch.h" +#include "llviewercontrol.h" + LLFloaterInventorySettings::LLFloaterInventorySettings(const LLSD& key) : LLFloater(key) { + mCommitCallbackRegistrar.add("ScriptPref.applyUIColor", boost::bind(&LLFloaterInventorySettings::applyUIColor, this, _1, _2)); + mCommitCallbackRegistrar.add("ScriptPref.getUIColor", boost::bind(&LLFloaterInventorySettings::getUIColor, this, _1, _2)); } LLFloaterInventorySettings::~LLFloaterInventorySettings() @@ -39,6 +44,29 @@ LLFloaterInventorySettings::~LLFloaterInventorySettings() bool LLFloaterInventorySettings::postBuild() { getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloater::closeFloater, this, false)); + + getChild("favorites_color")->setCommitCallback(boost::bind(&LLFloaterInventorySettings::updateColorSwatch, this)); + + bool enable_color = gSavedSettings.getBOOL("InventoryFavoritesColorText"); + getChild("favorites_swatch")->setEnabled(enable_color); + return true; } +void LLFloaterInventorySettings::updateColorSwatch() +{ + bool val = getChild("favorites_color")->getValue(); + getChild("favorites_swatch")->setEnabled(val); +} + +void LLFloaterInventorySettings::applyUIColor(LLUICtrl* ctrl, const LLSD& param) +{ + LLUIColorTable::instance().setColor(param.asString(), LLColor4(ctrl->getValue())); +} + +void LLFloaterInventorySettings::getUIColor(LLUICtrl* ctrl, const LLSD& param) +{ + LLColorSwatchCtrl* color_swatch = (LLColorSwatchCtrl*)ctrl; + color_swatch->setOriginal(LLUIColorTable::instance().getColor(param.asString())); +} + diff --git a/indra/newview/llfloaterinventorysettings.h b/indra/newview/llfloaterinventorysettings.h index 3fe3a001b9a..c27d5d2e1b6 100644 --- a/indra/newview/llfloaterinventorysettings.h +++ b/indra/newview/llfloaterinventorysettings.h @@ -40,6 +40,11 @@ class LLFloaterInventorySettings private: LLFloaterInventorySettings(const LLSD& key); ~LLFloaterInventorySettings(); + + void updateColorSwatch(); + + void applyUIColor(LLUICtrl* ctrl, const LLSD& param); + void getUIColor(LLUICtrl* ctrl, const LLSD& param); }; #endif diff --git a/indra/newview/llfloatermediasettings.cpp b/indra/newview/llfloatermediasettings.cpp index 2496887c9de..81eab52e6c1 100644 --- a/indra/newview/llfloatermediasettings.cpp +++ b/indra/newview/llfloatermediasettings.cpp @@ -180,8 +180,15 @@ void LLFloaterMediaSettings::onClose(bool app_quitting) //////////////////////////////////////////////////////////////////////////////// //static -void LLFloaterMediaSettings::initValues( const LLSD& media_settings, bool editable ) +void LLFloaterMediaSettings::initValues( const LLSD& media_settings, bool editable, bool has_media_info, bool multiple_media, bool multiple_valid_media) { + if (!sInstance) + { + return; + } + sInstance->mIdenticalHasMediaInfo = has_media_info; + sInstance->mMultipleMedia = multiple_media; + sInstance->mMultipleValidMedia = multiple_valid_media; if (sInstance->hasFocus()) return; // Clear values diff --git a/indra/newview/llfloatermediasettings.h b/indra/newview/llfloatermediasettings.h index 38730ddc982..7ed7ab246fc 100644 --- a/indra/newview/llfloatermediasettings.h +++ b/indra/newview/llfloatermediasettings.h @@ -48,7 +48,7 @@ class LLFloaterMediaSettings : static LLFloaterMediaSettings* getInstance(); static bool instanceExists(); static void apply(); - static void initValues( const LLSD& media_settings , bool editable); + static void initValues( const LLSD& media_settings , bool editable, bool has_media_info, bool multiple_media, bool multiple_valid_media); static void clearValues( bool editable); LLPanelMediaSettingsSecurity* getPanelSecurity(){return mPanelMediaSettingsSecurity;}; diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 332a98031f5..f76f39222b9 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -165,7 +165,7 @@ bool LLFloaterModelPreview::postBuild() for (S32 lod = 0; lod <= LLModel::LOD_HIGH; ++lod) { LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[lod]); - lod_source_combo->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLoDSourceCommit, this, lod, true)); + lod_source_combo->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLoDSourceCommit, this, lod)); lod_source_combo->setCurrentByIndex(mLODMode[lod]); getChild("lod_browse_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onBrowseLOD, this, lod)); @@ -350,14 +350,14 @@ void LLFloaterModelPreview::initModelPreview() } //static -bool LLFloaterModelPreview::showModelPreview() +void LLFloaterModelPreview::showModelPreview(const LLUUID& dest_folder) { LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)LLFloaterReg::getInstance("upload_model"); if (fmp && !fmp->isModelLoading()) { + fmp->setUploadDestination(dest_folder); fmp->loadHighLodModel(); } - return true; } void LLFloaterModelPreview::onUploadOptionChecked(LLUICtrl* ctrl) @@ -503,10 +503,13 @@ void LLFloaterModelPreview::onClickCalculateBtn() mUploadModelUrl.clear(); mModelPhysicsFee.clear(); - gMeshRepo.uploadModel(mModelPreview->mUploadData, mModelPreview->mPreviewScale, + lod_sources_map_t lod_sources; + fillLODSourceStatistics(lod_sources); + + gMeshRepo.uploadModel(mModelPreview->mUploadData, lod_sources, mModelPreview->mPreviewScale, childGetValue("upload_textures").asBoolean(), upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, - mUploadModelUrl, false, + mUploadModelUrl, mDestinationFolderId, false, getWholeModelFeeObserverHandle()); toggleCalculateButton(false); @@ -766,7 +769,7 @@ void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit) LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[i]); if (lod_source_combo->getCurrentIndex() == LLModelPreview::USE_LOD_ABOVE) { - onLoDSourceCommit(i, false); + onLoDSourceCommit(i); } else { @@ -1087,9 +1090,7 @@ void LLFloaterModelPreview::onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata) } else if (which_mode == cube_mode) { - std::string path = gDirUtilp->getAppRODataDir(); - gDirUtilp->append(path, "cube.dae"); - sInstance->loadModel(LLModel::LOD_PHYSICS, path); + sInstance->loadModel(LLModel::LOD_PHYSICS, getBoundingBoxCubePath()); } LLModelPreview *model_preview = sInstance->mModelPreview; @@ -1317,8 +1318,91 @@ void LLFloaterModelPreview::createSmoothComboBox(LLComboBox* combo_box, float mi std::string label = (++ilabel == SMOOTH_VALUES_NUMBER) ? "10 (max)" : llformat("%.1d", ilabel); combo_box->add(label, value, ADD_BOTTOM, true); } +} + +std::string get_source_file_format(const std::string& filename) +{ + const std::string extension = gDirUtilp->getExtension(filename); + if (extension == "gltf" + || extension == "glb") + { + return "gltf"; + } + else if (extension == "dae") + { + return "dae"; + } + else if (extension == "slm") + { + return "slm"; + } + else + { + return "unknown file"; + } +} + +std::string LLFloaterModelPreview::getBoundingBoxCubePath() +{ + std::string path = gDirUtilp->getAppRODataDir(); + gDirUtilp->append(path, "cube.dae"); + return path; +} +void LLFloaterModelPreview::fillLODSourceStatistics(LLFloaterModelPreview::lod_sources_map_t& lod_sources) const +{ + lod_sources.clear(); + // This doesn't nessesarily reflect the actual source of meshes, just user choices, + // some meshes could have been matched from different lods, but should be good + // enough for statistics. + for (S32 lod = 0; lod <= LLModel::LOD_HIGH; ++lod) + { + const std::string &lod_string = lod_name[lod]; + if (mLODMode[lod] == LLModelPreview::USE_LOD_ABOVE) + { + lod_sources[lod_string] = "lod above"; + } + else if (mLODMode[lod] == LLModelPreview::MESH_OPTIMIZER_AUTO + || mLODMode[lod] == LLModelPreview::MESH_OPTIMIZER_PRECISE + || mLODMode[lod] == LLModelPreview::MESH_OPTIMIZER_SLOPPY) + { + lod_sources[lod_string] = "generated"; + } + else if (mLODMode[lod] == LLModelPreview::LOD_FROM_FILE) + { + const std::string& file = mModelPreview->mLODFile[lod]; + lod_sources[lod_string] = get_source_file_format(file); + } + else + { + lod_sources[lod_string] = "unknown source"; + } + } + if (mModelPreview->mLODFile[LLModel::LOD_PHYSICS].empty()) + { + if (mModelPreview->mPhysicsSearchLOD >= 0 && mModelPreview->mPhysicsSearchLOD <= 3) + { + lod_sources["physics"] = lod_name[mModelPreview->mPhysicsSearchLOD]; + } + else + { + lod_sources["physics"] = "none"; + } + } + else + { + const std::string& file = mModelPreview->mLODFile[LLModel::LOD_PHYSICS]; + const std::string cube = getBoundingBoxCubePath(); + if (cube != file) // check for "cube.dae" + { + lod_sources["physics"] = get_source_file_format(file); + } + else + { + lod_sources["physics"] = "bounding box"; + } + } } //----------------------------------------------------------------------------- @@ -1656,10 +1740,13 @@ void LLFloaterModelPreview::onUpload(void* user_data) mp->mModelPreview->saveUploadData(upload_skinweights, upload_joint_positions, lock_scale_if_joint_position); } - gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, mp->mModelPreview->mPreviewScale, + lod_sources_map_t lod_sources; + mp->fillLODSourceStatistics(lod_sources); + + gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, lod_sources, mp->mModelPreview->mPreviewScale, mp->childGetValue("upload_textures").asBoolean(), upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, - mp->mUploadModelUrl, + mp->mUploadModelUrl, mp->mDestinationFolderId, true, LLHandle(), mp->getWholeModelUploadObserverHandle()); } @@ -1760,7 +1847,7 @@ void LLFloaterModelPreview::toggleCalculateButton(bool visible) } } -void LLFloaterModelPreview::onLoDSourceCommit(S32 lod, bool refresh_ui) +void LLFloaterModelPreview::onLoDSourceCommit(S32 lod) { mModelPreview->updateLodControls(lod); @@ -1773,12 +1860,10 @@ void LLFloaterModelPreview::onLoDSourceCommit(S32 lod, bool refresh_ui) // rebuild LoD to update triangle counts onLODParamCommit(lod, true); } - else if (refresh_ui && index == LLModelPreview::USE_LOD_ABOVE) + if (index == LLModelPreview::USE_LOD_ABOVE) { - // Update mUploadData for updateStatusMessages - mModelPreview->rebuildUploadData(); - // Update UI with new triangle values - mModelPreview->updateStatusMessages(); + // refresh to pick triangle counts + mModelPreview->mDirty = true; } } diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h index 5d23dc8d8e7..20e5b2666a7 100644 --- a/indra/newview/llfloatermodelpreview.h +++ b/indra/newview/llfloatermodelpreview.h @@ -73,7 +73,8 @@ class LLFloaterModelPreview : public LLFloaterModelUploadBase /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); void initModelPreview(); - static bool showModelPreview(); + void setUploadDestination(const LLUUID& dest_folder) { mDestinationFolderId = dest_folder; } + static void showModelPreview(const LLUUID& dest_folder = LLUUID::null); bool handleMouseDown(S32 x, S32 y, MASK mask); bool handleMouseUp(S32 x, S32 y, MASK mask); @@ -164,9 +165,6 @@ class LLFloaterModelPreview : public LLFloaterModelUploadBase static void onPhysicsBrowse(LLUICtrl* ctrl, void* userdata); static void onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata); - static void onPhysicsOptimize(LLUICtrl* ctrl, void* userdata); - static void onPhysicsDecomposeBack(LLUICtrl* ctrl, void* userdata); - static void onPhysicsSimplifyBack(LLUICtrl* ctrl, void* userdata); void draw(); @@ -208,7 +206,7 @@ class LLFloaterModelPreview : public LLFloaterModelUploadBase void onClickCalculateBtn(); void onJointListSelection(); - void onLoDSourceCommit(S32 lod, bool refresh_ui); + void onLoDSourceCommit(S32 lod); void modelUpdated(bool calculate_visible); @@ -225,6 +223,11 @@ class LLFloaterModelPreview : public LLFloaterModelUploadBase void createSmoothComboBox(LLComboBox* combo_box, float min, float max); + static std::string getBoundingBoxCubePath(); + typedef std::map lod_sources_map_t; + void fillLODSourceStatistics(lod_sources_map_t& lod_sources) const; + + LLUUID mDestinationFolderId; LLButton* mUploadBtn; LLButton* mCalculateBtn; LLViewerTextEditor* mUploadLogText; diff --git a/indra/newview/llfloatermyenvironment.cpp b/indra/newview/llfloatermyenvironment.cpp index 891e16a8ef0..c0405c106ec 100644 --- a/indra/newview/llfloatermyenvironment.cpp +++ b/indra/newview/llfloatermyenvironment.cpp @@ -38,7 +38,9 @@ #include "llcheckboxctrl.h" #include "llviewerinventory.h" #include "llenvironment.h" +#include "llnotificationsutil.h" #include "llparcel.h" +#include "lltrans.h" #include "llviewerparcelmgr.h" //========================================================================= @@ -223,16 +225,13 @@ void LLFloaterMyEnvironment::onFilterEdit(const std::string& search_string) mInventoryList->setFilterSubString(search_string); } -void LLFloaterMyEnvironment::onDeleteSelected() +void LLFloaterMyEnvironment::onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, uuid_vec_t item_ids) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) { - uuid_vec_t selected; - - getSelectedIds(selected); - if (selected.empty()) - return; - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - for (const LLUUID& itemid: selected) + for (const LLUUID& itemid : item_ids) { LLInventoryItem* inv_item = gInventory.getItem(itemid); @@ -253,6 +252,27 @@ void LLFloaterMyEnvironment::onDeleteSelected() } gInventory.notifyObservers(); } +} + +void LLFloaterMyEnvironment::onDeleteSelected() +{ + uuid_vec_t selected; + + getSelectedIds(selected); + if (selected.empty()) + return; + + LLSD args; + args["QUESTION"] = LLTrans::getString(selected.size() > 1 ? "DeleteItems" : "DeleteItem"); + LLNotificationsUtil::add( + "DeleteItems", + args, + LLSD(), + [this, selected](const LLSD& notification, const LLSD& response) + { + onItemsRemovalConfirmation(notification, response, selected); + }); +} void LLFloaterMyEnvironment::onDoCreate(const LLSD &data) @@ -318,13 +338,13 @@ bool LLFloaterMyEnvironment::canAction(const std::string &context) if (context == PARAMETER_EDIT) { - return (selected.size() == 1) && isSettingSelected(selected.front()); + return (selected.size() == 1) && isSettingId(selected.front()); } else if (context == PARAMETER_COPY) { for (std::vector::iterator it = selected.begin(); it != selected.end(); it++) { - if(!isSettingSelected(*it)) + if(!isSettingId(*it)) { return false; } @@ -342,7 +362,7 @@ bool LLFloaterMyEnvironment::canAction(const std::string &context) LLClipboard::instance().pasteFromClipboard(ids); for (std::vector::iterator it = ids.begin(); it != ids.end(); it++) { - if (!isSettingSelected(*it)) + if (!isSettingId(*it)) { return false; } @@ -351,7 +371,7 @@ bool LLFloaterMyEnvironment::canAction(const std::string &context) } else if (context == PARAMETER_COPYUUID) { - return (selected.size() == 1) && isSettingSelected(selected.front()); + return (selected.size() == 1) && isSettingId(selected.front()); } return false; @@ -367,16 +387,42 @@ bool LLFloaterMyEnvironment::canApply(const std::string &context) if (context == PARAMETER_REGION) { - return LLEnvironment::instance().canAgentUpdateRegionEnvironment(); + return isSettingId(selected.front()) && LLEnvironment::instance().canAgentUpdateRegionEnvironment(); } else if (context == PARAMETER_PARCEL) { - return LLEnvironment::instance().canAgentUpdateParcelEnvironment(); + return isSettingId(selected.front()) && LLEnvironment::instance().canAgentUpdateParcelEnvironment(); } - else + else if (context == PARAMETER_LOCAL) { - return (context == PARAMETER_LOCAL); + return isSettingId(selected.front()); } + + return false; +} + +bool can_delete(const LLUUID& id) +{ + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (id == trash_id || gInventory.isObjectDescendentOf(id, trash_id)) + { + return false; + } + + LLViewerInventoryCategory* cat = gInventory.getCategory(id); + if (cat) + { + if (!get_is_category_removable(&gInventory, id)) + { + return false; + } + } + else if (!get_is_item_removable(&gInventory, id, false)) + { + return false; + } + + return true; } //------------------------------------------------------------------------- @@ -389,7 +435,14 @@ void LLFloaterMyEnvironment::refreshButtonStates() getChild(BUTTON_GEAR)->setEnabled(settings_ok); getChild(BUTTON_NEWSETTINGS)->setEnabled(true); - getChild(BUTTON_DELETE)->setEnabled(settings_ok && !selected.empty()); + + bool enable_delete = false; + if(settings_ok && !selected.empty()) + { + enable_delete = can_delete(selected.front()); + } + + getChild(BUTTON_DELETE)->setEnabled(enable_delete); } //------------------------------------------------------------------------- @@ -438,7 +491,7 @@ LLUUID LLFloaterMyEnvironment::findItemByAssetId(LLUUID asset_id, bool copyable_ return LLUUID::null; } -bool LLFloaterMyEnvironment::isSettingSelected(LLUUID item_id) +bool LLFloaterMyEnvironment::isSettingId(const LLUUID& item_id) { LLInventoryItem* itemp = gInventory.getItem(item_id); diff --git a/indra/newview/llfloatermyenvironment.h b/indra/newview/llfloatermyenvironment.h index 8e81b8e5e23..c5d521d2073 100644 --- a/indra/newview/llfloatermyenvironment.h +++ b/indra/newview/llfloatermyenvironment.h @@ -60,6 +60,7 @@ class LLFloaterMyEnvironment void onFilterCheckChange(); void onFilterEdit(const std::string& search_string); void onSelectionChange(); + void onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, uuid_vec_t item_ids); void onDeleteSelected(); void onDoCreate(const LLSD &data); void onDoApply(const std::string &context); @@ -69,7 +70,7 @@ class LLFloaterMyEnvironment void getSelectedIds(uuid_vec_t& ids) const; void refreshButtonStates(); - bool isSettingSelected(LLUUID item_id); + static bool isSettingId(const LLUUID &item_id); static LLUUID findItemByAssetId(LLUUID asset_id, bool copyable_only, bool ignore_library); }; diff --git a/indra/newview/llfloaternamedesc.cpp b/indra/newview/llfloaternamedesc.cpp index 01c50d89c51..569b41cfa9b 100644 --- a/indra/newview/llfloaternamedesc.cpp +++ b/indra/newview/llfloaternamedesc.cpp @@ -62,11 +62,20 @@ const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE; //----------------------------------------------------------------------------- // LLFloaterNameDesc() //----------------------------------------------------------------------------- -LLFloaterNameDesc::LLFloaterNameDesc(const LLSD& filename ) - : LLFloater(filename), - mIsAudio(false) +LLFloaterNameDesc::LLFloaterNameDesc(const LLSD& args) + : LLFloater(args) + , mIsAudio(false) + , mIsText(false) { - mFilenameAndPath = filename.asString(); + if (args.isString()) + { + mFilenameAndPath = args.asString(); + } + else + { + mFilenameAndPath = args["filename"].asString(); + mDestinationFolderId = args["dest"].asUUID(); + } mFilename = gDirUtilp->getBaseFileName(mFilenameAndPath, false); } @@ -203,7 +212,8 @@ void LLFloaterNameDesc::onBtnOK( ) LLFloaterPerms::getNextOwnerPerms("Uploads"), LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost)); + expected_upload_cost, + mDestinationFolderId)); upload_new_resource(uploadInfo, callback, nruserdata); } @@ -230,8 +240,8 @@ void LLFloaterNameDesc::onBtnCancel() // LLFloaterSoundPreview() //----------------------------------------------------------------------------- -LLFloaterSoundPreview::LLFloaterSoundPreview(const LLSD& filename ) - : LLFloaterNameDesc(filename) +LLFloaterSoundPreview::LLFloaterSoundPreview(const LLSD& args ) + : LLFloaterNameDesc(args) { mIsAudio = true; } @@ -251,8 +261,8 @@ bool LLFloaterSoundPreview::postBuild() // LLFloaterAnimPreview() //----------------------------------------------------------------------------- -LLFloaterAnimPreview::LLFloaterAnimPreview(const LLSD& filename ) - : LLFloaterNameDesc(filename) +LLFloaterAnimPreview::LLFloaterAnimPreview(const LLSD& args ) + : LLFloaterNameDesc(args) { } @@ -270,8 +280,8 @@ bool LLFloaterAnimPreview::postBuild() // LLFloaterScriptPreview() //----------------------------------------------------------------------------- -LLFloaterScriptPreview::LLFloaterScriptPreview(const LLSD& filename ) - : LLFloaterNameDesc(filename) +LLFloaterScriptPreview::LLFloaterScriptPreview(const LLSD& args ) + : LLFloaterNameDesc(args) { mIsText = true; } diff --git a/indra/newview/llfloaternamedesc.h b/indra/newview/llfloaternamedesc.h index aa5571ccc0c..8c8ec49a8ec 100644 --- a/indra/newview/llfloaternamedesc.h +++ b/indra/newview/llfloaternamedesc.h @@ -39,7 +39,7 @@ class LLRadioGroup; class LLFloaterNameDesc : public LLFloater { public: - LLFloaterNameDesc(const LLSD& filename); + LLFloaterNameDesc(const LLSD& args); virtual ~LLFloaterNameDesc(); bool postBuild() override; @@ -58,6 +58,7 @@ class LLFloaterNameDesc : public LLFloater std::string mFilenameAndPath; std::string mFilename; + LLUUID mDestinationFolderId; }; class LLFloaterSoundPreview : public LLFloaterNameDesc diff --git a/indra/newview/llfloaterobjectweights.cpp b/indra/newview/llfloaterobjectweights.cpp index 26b7304b9ab..fa491a4b279 100644 --- a/indra/newview/llfloaterobjectweights.cpp +++ b/indra/newview/llfloaterobjectweights.cpp @@ -36,6 +36,14 @@ #include "llviewerparcelmgr.h" #include "llviewerregion.h" +static const std::string lod_strings[4] = +{ + "lowest_lod", + "low_lod", + "medium_lod", + "high_lod", +}; + // virtual bool LLCrossParcelFunctor::apply(LLViewerObject* obj) { @@ -75,7 +83,10 @@ LLFloaterObjectWeights::LLFloaterObjectWeights(const LLSD& key) mSelectedOnLand(NULL), mRezzedOnLand(NULL), mRemainingCapacity(NULL), - mTotalCapacity(NULL) + mTotalCapacity(NULL), + mLodLevel(nullptr), + mTrianglesShown(nullptr), + mPixelArea(nullptr) { } @@ -99,6 +110,10 @@ bool LLFloaterObjectWeights::postBuild() mRemainingCapacity = getChild("remaining_capacity"); mTotalCapacity = getChild("total_capacity"); + mLodLevel = getChild("lod_level"); + mTrianglesShown = getChild("triangles_shown"); + mPixelArea = getChild("pixel_area"); + return true; } @@ -135,6 +150,69 @@ void LLFloaterObjectWeights::setErrorStatus(S32 status, const std::string& reaso toggleWeightsLoadingIndicators(false); } +void LLFloaterObjectWeights::draw() +{ + // Normally it's a bad idea to set text and visibility inside draw + // since it can cause rect updates go to different, already drawn elements, + // but floater is very simple and these elements are supposed to be isolated + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + if (selection->isEmpty()) + { + const std::string text = getString("nothing_selected"); + mLodLevel->setText(text); + mTrianglesShown->setText(text); + mPixelArea->setText(text); + + toggleRenderLoadingIndicators(false); + } + else + { + S32 object_lod = -1; + bool multiple_lods = false; + S32 total_tris = 0; + F32 pixel_area = 0; + for (LLObjectSelection::valid_root_iterator iter = selection->valid_root_begin(); + iter != selection->valid_root_end(); ++iter) + { + LLViewerObject* object = (*iter)->getObject(); + S32 lod = object->getLOD(); + if (object_lod < 0) + { + object_lod = lod; + } + else if (object_lod != lod) + { + multiple_lods = true; + } + + if (object->isRootEdit()) + { + total_tris += object->recursiveGetTriangleCount(); + pixel_area += object->getPixelArea(); + } + } + + if (multiple_lods) + { + mLodLevel->setText(getString("multiple_lods")); + toggleRenderLoadingIndicators(false); + } + else if (object_lod < 0) + { + // nodes are waiting for data + toggleRenderLoadingIndicators(true); + } + else + { + mLodLevel->setText(getString(lod_strings[object_lod])); + toggleRenderLoadingIndicators(false); + } + mTrianglesShown->setText(llformat("%d", total_tris)); + mPixelArea->setText(llformat("%d", pixel_area)); + } + LLFloater::draw(); +} + void LLFloaterObjectWeights::updateLandImpacts(const LLParcel* parcel) { if (!parcel || LLSelectMgr::getInstance()->getSelection()->isEmpty()) @@ -252,6 +330,17 @@ void LLFloaterObjectWeights::toggleLandImpactsLoadingIndicators(bool visible) mTotalCapacity->setVisible(!visible); } +void LLFloaterObjectWeights::toggleRenderLoadingIndicators(bool visible) +{ + childSetVisible("lod_level_loading_indicator", visible); + childSetVisible("triangles_shown_loading_indicator", visible); + childSetVisible("pixel_area_loading_indicator", visible); + + mLodLevel->setVisible(!visible); + mTrianglesShown->setVisible(!visible); + mPixelArea->setVisible(!visible); +} + void LLFloaterObjectWeights::updateIfNothingSelected() { const std::string text = getString("nothing_selected"); @@ -269,6 +358,11 @@ void LLFloaterObjectWeights::updateIfNothingSelected() mRemainingCapacity->setText(text); mTotalCapacity->setText(text); + mLodLevel->setText(text); + mTrianglesShown->setText(text); + mPixelArea->setText(text); + toggleWeightsLoadingIndicators(false); toggleLandImpactsLoadingIndicators(false); + toggleRenderLoadingIndicators(false); } diff --git a/indra/newview/llfloaterobjectweights.h b/indra/newview/llfloaterobjectweights.h index 3b999f6b9bb..bda625564ba 100644 --- a/indra/newview/llfloaterobjectweights.h +++ b/indra/newview/llfloaterobjectweights.h @@ -58,21 +58,24 @@ class LLFloaterObjectWeights : public LLFloater, LLAccountingCostObserver LLFloaterObjectWeights(const LLSD& key); ~LLFloaterObjectWeights(); - /*virtual*/ bool postBuild(); + bool postBuild() override; - /*virtual*/ void onOpen(const LLSD& key); + void onOpen(const LLSD& key) override; - /*virtual*/ void onWeightsUpdate(const SelectionCost& selection_cost); - /*virtual*/ void setErrorStatus(S32 status, const std::string& reason); + void onWeightsUpdate(const SelectionCost& selection_cost) override; + void setErrorStatus(S32 status, const std::string& reason) override; + + void draw() override; void updateLandImpacts(const LLParcel* parcel); - void refresh(); + void refresh() override; private: - /*virtual*/ void generateTransactionID(); + void generateTransactionID() override; void toggleWeightsLoadingIndicators(bool visible); void toggleLandImpactsLoadingIndicators(bool visible); + void toggleRenderLoadingIndicators(bool visible); void updateIfNothingSelected(); @@ -88,6 +91,10 @@ class LLFloaterObjectWeights : public LLFloater, LLAccountingCostObserver LLTextBox *mRezzedOnLand; LLTextBox *mRemainingCapacity; LLTextBox *mTotalCapacity; + + LLTextBox *mLodLevel; + LLTextBox *mTrianglesShown; + LLTextBox *mPixelArea; }; #endif //LL_LLFLOATEROBJECTWEIGHTS_H diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index fdac390e8a0..9fb9c6346e8 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -366,6 +366,11 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) mCommitCallbackRegistrar.add("Pref.ClearLog", boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance())); mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", boost::bind(&LLFloaterPreference::onDeleteTranscripts, this)); mCommitCallbackRegistrar.add("UpdateFilter", boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); // Hook up for filtering +#ifdef LL_DISCORD + gSavedSettings.getControl("EnableDiscord")->getCommitSignal()->connect(boost::bind(&LLAppViewer::toggleDiscordIntegration, _2)); + gSavedSettings.getControl("ShowDiscordActivityDetails")->getCommitSignal()->connect(boost::bind(&LLAppViewer::updateDiscordActivity)); + gSavedSettings.getControl("ShowDiscordActivityState")->getCommitSignal()->connect(boost::bind(&LLAppViewer::updateDiscordActivity)); +#endif } void LLFloaterPreference::processProperties( void* pData, EAvatarProcessorType type ) @@ -523,6 +528,11 @@ bool LLFloaterPreference::postBuild() getChild("language_combobox")->add("System default", LLSD("default"), ADD_TOP, true); } +#ifndef LL_DISCORD + LLPanel* panel = getChild("privacy_preferences_discord"); + getChild("privacy_tab_container")->removeTabPanel(panel); +#endif + return true; } @@ -1968,7 +1978,21 @@ void LLFloaterPreference::selectChatPanel() void LLFloaterPreference::changed() { + if (LLConversationLog::instance().getIsLoggingEnabled()) + { getChild("clear_log")->setEnabled(LLConversationLog::instance().getConversations().size() > 0); + } + else + { + // onClearLog clears list, then notifies changed() and only then clears file, + // so check presence of conversations before checking file, file will cleared later. + llstat st; + bool has_logs = LLConversationLog::instance().getConversations().size() > 0 + && LLFile::stat(LLConversationLog::instance().getFileName(), &st) == 0 + && S_ISREG(st.st_mode) + && st.st_size > 0; + getChild("clear_log")->setEnabled(has_logs); + } // set 'enable' property for 'Delete transcripts...' button updateDeleteTranscriptsButton(); diff --git a/indra/newview/llfloatersettingsdebug.cpp b/indra/newview/llfloatersettingsdebug.cpp index 8cc01f6dc67..01108b5cfab 100644 --- a/indra/newview/llfloatersettingsdebug.cpp +++ b/indra/newview/llfloatersettingsdebug.cpp @@ -207,14 +207,14 @@ void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp) mSettingNameText->setToolTip(controlp->getName()); mComment->setVisible(true); - std::string old_text = mComment->getText(); std::string new_text = controlp->getComment(); // Don't setText if not nessesary, it will reset scroll // This is a debug UI that reads from xml, there might // be use cases where comment changes, but not the name - if (old_text != new_text) + if (mOldText != new_text) { mComment->setText(controlp->getComment()); + mOldText = new_text; } mValSpinner1->setMaxValue(F32_MAX); @@ -467,6 +467,7 @@ void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp) } default: mComment->setText(std::string("unknown")); + mOldText = "unknown"; break; } } diff --git a/indra/newview/llfloatersettingsdebug.h b/indra/newview/llfloatersettingsdebug.h index b813cf4a748..8781cd3b679 100644 --- a/indra/newview/llfloatersettingsdebug.h +++ b/indra/newview/llfloatersettingsdebug.h @@ -82,6 +82,7 @@ class LLFloaterSettingsDebug final LLColorSwatchCtrl* mColorSwatch = nullptr; std::string mSearchFilter; + std::string mOldText; }; #endif //LLFLOATERDEBUGSETTINGS_H diff --git a/indra/newview/llfloaterwebcontent.cpp b/indra/newview/llfloaterwebcontent.cpp index e1b6df6072a..3ff84ac9b7e 100644 --- a/indra/newview/llfloaterwebcontent.cpp +++ b/indra/newview/llfloaterwebcontent.cpp @@ -130,7 +130,7 @@ void LLFloaterWebContent::initializeURLHistory() for(; iter_history != end_history; ++iter_history) { std::string url = (*iter_history).asString(); - if(! url.empty()) + if(! url.empty() && url_list) url_list->addSimpleElement(url); } } diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index 6e6eaa3a204..03979edbc10 100755 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -169,6 +169,52 @@ class LLWorldMapHandler : public LLCommandHandler }; LLWorldMapHandler gWorldMapHandler; +// handle secondlife:///app/worldmap_global/{GLOBAL_COORDS} URLs +class LLWorldMapGlobalHandler : public LLCommandHandler +{ +public: + LLWorldMapGlobalHandler() : LLCommandHandler("worldmap_global", UNTRUSTED_THROTTLE) + {} + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + // NAV_TYPE_EXTERNAL will be throttled + return true; + } + + return false; + } + + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if (params.size() < 3) + { + LL_WARNS() << "Correct global coordinates are not provided." << LL_ENDL; + return true; + } + + LLVector3d parcel_global_pos = LLVector3d(params[0].asInteger(), params[1].asInteger(), params[2].asInteger()); + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + if (!parcel_global_pos.isExactlyZero() && worldmap_instance) + { + worldmap_instance->trackLocation(parcel_global_pos); + LLFloaterReg::showInstance("world_map", "center"); + } + return true; + } +}; +LLWorldMapGlobalHandler gWorldMapGlobalHandler; + // SocialMap handler secondlife:///app/maptrackavatar/id class LLMapTrackAvatarHandler : public LLCommandHandler { diff --git a/indra/newview/llfolderviewmodelinventory.cpp b/indra/newview/llfolderviewmodelinventory.cpp index c668d414d3d..a0621bb0157 100644 --- a/indra/newview/llfolderviewmodelinventory.cpp +++ b/indra/newview/llfolderviewmodelinventory.cpp @@ -68,9 +68,10 @@ void LLFolderViewModelInventory::sort( LLFolderViewFolder* folder ) if (!folder->areChildrenInited() || !needsSort(folder->getViewModelItem())) return; - LLFolderViewModelItemInventory* modelp = static_cast(folder->getViewModelItem()); - if (modelp->getUUID().isNull()) return; + LLFolderViewModelItemInventory* sort_modelp = static_cast(folder->getViewModelItem()); + if (!sort_modelp->canSortContent()) return; + bool has_favorites = false; for (std::list::iterator it = folder->getFoldersBegin(), end_it = folder->getFoldersEnd(); it != end_it; ++it) @@ -79,11 +80,14 @@ void LLFolderViewModelInventory::sort( LLFolderViewFolder* folder ) LLFolderViewFolder* child_folderp = *it; sort(child_folderp); + LLFolderViewModelItemInventory* modelp = static_cast(child_folderp->getViewModelItem()); + has_favorites |= child_folderp->isFavorite() || child_folderp->hasFavorites(); + if (child_folderp->getFoldersCount() > 0) { - time_t most_recent_folder_time = - static_cast((*child_folderp->getFoldersBegin())->getViewModelItem())->getCreationDate(); - LLFolderViewModelItemInventory* modelp = static_cast(child_folderp->getViewModelItem()); + LLFolderViewModelItemInventory* folderp = static_cast((*child_folderp->getFoldersBegin())->getViewModelItem()); + time_t most_recent_folder_time = folderp->getCreationDate(); + if (most_recent_folder_time > modelp->getCreationDate()) { modelp->setCreationDate(most_recent_folder_time); @@ -91,16 +95,26 @@ void LLFolderViewModelInventory::sort( LLFolderViewFolder* folder ) } if (child_folderp->getItemsCount() > 0) { - time_t most_recent_item_time = - static_cast((*child_folderp->getItemsBegin())->getViewModelItem())->getCreationDate(); + LLFolderViewModelItemInventory* itemp = static_cast((*child_folderp->getItemsBegin())->getViewModelItem()); + time_t most_recent_item_time = itemp->getCreationDate(); - LLFolderViewModelItemInventory* modelp = static_cast(child_folderp->getViewModelItem()); if (most_recent_item_time > modelp->getCreationDate()) { modelp->setCreationDate(most_recent_item_time); } } } + for (std::list::const_iterator it = folder->getItemsBegin(), end_it = folder->getItemsEnd(); + it != end_it && !has_favorites; + ++it) + { + LLFolderViewItem* child_itemp = *it; + has_favorites |= child_itemp->isFavorite(); + } + if (has_favorites) + { + folder->updateHasFavorites(true); + } base_t::sort(folder); } diff --git a/indra/newview/llfolderviewmodelinventory.h b/indra/newview/llfolderviewmodelinventory.h index 48b4ee5fd97..74645a19e06 100644 --- a/indra/newview/llfolderviewmodelinventory.h +++ b/indra/newview/llfolderviewmodelinventory.h @@ -48,6 +48,7 @@ class LLFolderViewModelItemInventory virtual bool isItemInTrash( void) const { return false; } // TODO: make into pure virtual. virtual bool isItemInOutfits() const { return false; } virtual bool isAgentInventory() const { return false; } + virtual bool isAgentInventoryRoot() const { return false; } virtual bool isUpToDate() const = 0; virtual void addChild(LLFolderViewModelItem* child); virtual bool hasChildren() const = 0; @@ -58,6 +59,7 @@ class LLFolderViewModelItemInventory virtual EInventorySortGroup getSortGroup() const = 0; virtual LLInventoryObject* getInventoryObject() const = 0; virtual void requestSort(); + virtual bool canSortContent() const { return getUUID().notNull(); } virtual void setPassedFilter(bool filtered, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0); virtual bool filter( LLFolderViewFilter& filter); virtual bool filterChildItem( LLFolderViewModelItem* item, LLFolderViewFilter& filter); diff --git a/indra/newview/llgltffolderitem.h b/indra/newview/llgltffolderitem.h index 89d90c81cc2..40a4c6fef12 100644 --- a/indra/newview/llgltffolderitem.h +++ b/indra/newview/llgltffolderitem.h @@ -114,6 +114,11 @@ class LLGLTFFolderItem : public LLFolderViewModelItemCommon EType getType() const { return mItemType; } S32 getItemId() const { return mItemId; } + bool isFavorite() const override { return false; } + bool isItemInTrash() const override { return false; } + bool isAgentInventory() const override { return false; } + bool isAgentInventoryRoot() const override { return false; } + private: LLUIImagePtr pIcon; std::string mName; diff --git a/indra/newview/llhudeffectlookat.cpp b/indra/newview/llhudeffectlookat.cpp index d0d2ee191a9..776d2dd31e9 100644 --- a/indra/newview/llhudeffectlookat.cpp +++ b/indra/newview/llhudeffectlookat.cpp @@ -37,6 +37,7 @@ #include "lldrawable.h" #include "llviewerobjectlist.h" #include "llviewercontrol.h" +#include "llvoavatarself.h" #include "llrendersphere.h" #include "llselectmgr.h" #include "llglheaders.h" @@ -397,6 +398,21 @@ bool LLHUDEffectLookAt::setLookAt(ELookAtType target_type, LLViewerObject *objec return false; } + static LLCachedControl enable_lookat_hints(gSavedSettings, "EnableLookAtTarget", true); + if (!enable_lookat_hints) + { + // Clear the effect so it doesn't linger around if it gets disabled + if (mTargetType != LOOKAT_TARGET_IDLE) + { + mTargetObject = gAgentAvatarp; + mTargetType = LOOKAT_TARGET_IDLE; + mTargetOffsetGlobal.set(2.f, 0.f, 0.f); + setDuration(3.f); + setNeedsSendToSim(true); + } + return false; + } + if (target_type >= LOOKAT_NUM_TARGETS) { LL_WARNS() << "Bad target_type " << (int)target_type << " - ignoring." << LL_ENDL; @@ -409,6 +425,29 @@ bool LLHUDEffectLookAt::setLookAt(ELookAtType target_type, LLViewerObject *objec return false; } + static LLCachedControl limit_lookat_hints(gSavedSettings, "LimitLookAtTarget", true); + // Don't affect the look at if object is gAgentAvatarp (cursor head follow) + if (limit_lookat_hints && object != gAgentAvatarp) + { + // If it is a object + if (object) + { + position += object->getRenderPosition(); + object = NULL; + } + + LLVector3 agentHeadPosition = gAgentAvatarp->mHeadp->getWorldPosition(); + float dist = (float)dist_vec(agentHeadPosition, position); + + static LLCachedControl limit_lookat_hints_distance(gSavedSettings, "LimitLookAtTargetDistance", 2.0f); + if (dist > limit_lookat_hints_distance) + { + LLVector3 headOffset = position - agentHeadPosition; + headOffset *= limit_lookat_hints_distance / dist; + position.setVec(agentHeadPosition + headOffset); + } + } + F32 current_time = mTimer.getElapsedTimeF32(); // type of lookat behavior or target object has changed diff --git a/indra/newview/llhudeffectpointat.cpp b/indra/newview/llhudeffectpointat.cpp index eeb38cd6aa5..c600010f6be 100644 --- a/indra/newview/llhudeffectpointat.cpp +++ b/indra/newview/llhudeffectpointat.cpp @@ -34,6 +34,7 @@ #include "llagent.h" #include "llagentcamera.h" #include "lldrawable.h" +#include "llviewercontrol.h" #include "llviewerobjectlist.h" #include "llvoavatar.h" #include "message.h" @@ -226,6 +227,19 @@ bool LLHUDEffectPointAt::setPointAt(EPointAtType target_type, LLViewerObject *ob return false; } + static LLCachedControl enable_selection_hints(gSavedSettings, "EnableSelectionHints", true); + if (!enable_selection_hints) + { + // Clear the effect so it doesn't linger around if it gets disabled + if (mTargetType != POINTAT_TARGET_NONE) + { + clearPointAtTarget(); + setDuration(1.f); + setNeedsSendToSim(true); + } + return false; + } + if (target_type >= POINTAT_NUM_TARGETS) { LL_WARNS() << "Bad target_type " << (int)target_type << " - ignoring." << LL_ENDL; diff --git a/indra/newview/llhudeffectresetskeleton.cpp b/indra/newview/llhudeffectresetskeleton.cpp new file mode 100644 index 00000000000..31065a3e762 --- /dev/null +++ b/indra/newview/llhudeffectresetskeleton.cpp @@ -0,0 +1,211 @@ +/** + * @file llhudeffectresetskeleton.cpp + * @brief LLHUDEffectResetSkeleton class implementation + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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 "llhudeffectresetskeleton.h" + +#include "llagent.h" +#include "llviewerobjectlist.h" +#include "llvoavatar.h" +#include "message.h" + +// packet layout +const S32 TARGET_OBJECT = 0; // This is to allow for targetting owned animesh +const S32 RESET_ANIMATIONS = 16; //This can also be a flags if needed +const S32 PKT_SIZE = 17; + +//----------------------------------------------------------------------------- +// LLHUDEffectResetSkeleton() +//----------------------------------------------------------------------------- +LLHUDEffectResetSkeleton::LLHUDEffectResetSkeleton(const U8 type) : + LLHUDEffect(type) +{ +} + +//----------------------------------------------------------------------------- +// ~LLHUDEffectResetSkeleton() +//----------------------------------------------------------------------------- +LLHUDEffectResetSkeleton::~LLHUDEffectResetSkeleton() +{ +} + +//----------------------------------------------------------------------------- +// packData() +//----------------------------------------------------------------------------- +void LLHUDEffectResetSkeleton::packData(LLMessageSystem *mesgsys) +{ + // Pack the default data + LLHUDEffect::packData(mesgsys); + + // Pack the type-specific data. Uses a fun packed binary format. Whee! + U8 packed_data[PKT_SIZE]; + memset(packed_data, 0, PKT_SIZE); + + // pack both target object and position + // position interpreted as offset if target object is non-null + if (mTargetObject) + { + htolememcpy(&(packed_data[TARGET_OBJECT]), mTargetObject->mID.mData, MVT_LLUUID, 16); + } + else + { + htolememcpy(&(packed_data[TARGET_OBJECT]), LLUUID::null.mData, MVT_LLUUID, 16); + } + + U8 resetAnimations = (U8)mResetAnimations; + htolememcpy(&(packed_data[RESET_ANIMATIONS]), &resetAnimations, MVT_U8, 1); + + mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, PKT_SIZE); +} + +//----------------------------------------------------------------------------- +// unpackData() +//----------------------------------------------------------------------------- +void LLHUDEffectResetSkeleton::unpackData(LLMessageSystem *mesgsys, S32 blocknum) +{ + LLVector3d new_target; + U8 packed_data[PKT_SIZE]; + + + LLHUDEffect::unpackData(mesgsys, blocknum); + + LLUUID source_id; + mesgsys->getUUIDFast(_PREHASH_Effect, _PREHASH_AgentID, source_id, blocknum); + + LLViewerObject *objp = gObjectList.findObject(source_id); + if (objp && objp->isAvatar()) + { + setSourceObject(objp); + } + else + { + //LL_WARNS() << "Could not find source avatar for ResetSkeleton effect" << LL_ENDL; + return; + } + + S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); + if (size != PKT_SIZE) + { + LL_WARNS() << "ResetSkeleton effect with bad size " << size << LL_ENDL; + return; + } + + mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, PKT_SIZE, blocknum); + + LLUUID target_id; + htolememcpy(target_id.mData, &(packed_data[TARGET_OBJECT]), MVT_LLUUID, 16); + + // The purpose for having a target ID is if we want to reset animesh, or + // other things in the future. + // I implemented this, but due to issues regarding various permission + // checks, I scrapped it for now. --Chaser Zaks + // See https://github.com/secondlife/viewer/pull/1212 for additional info + + if (target_id.isNull()) + { + target_id = source_id; + } + + objp = gObjectList.findObject(target_id); + + if (objp) + { + setTargetObject(objp); + } + + U8 resetAnimations = 0; + htolememcpy(&resetAnimations, &(packed_data[RESET_ANIMATIONS]), MVT_U8, 1); + + // Pre-emptively assume this is going to be flags in the future. + // It isn't needed now, but this will assure that only bit 1 is set + mResetAnimations = resetAnimations & 1; + + update(); +} + +//----------------------------------------------------------------------------- +// setTargetObjectAndOffset() +//----------------------------------------------------------------------------- +void LLHUDEffectResetSkeleton::setTargetObject(LLViewerObject *objp) +{ + mTargetObject = objp; +} + + +//----------------------------------------------------------------------------- +// markDead() +//----------------------------------------------------------------------------- +void LLHUDEffectResetSkeleton::markDead() +{ + LLHUDEffect::markDead(); +} + +void LLHUDEffectResetSkeleton::setSourceObject(LLViewerObject* objectp) +{ + // restrict source objects to avatars + if (objectp && objectp->isAvatar()) + { + LLHUDEffect::setSourceObject(objectp); + } +} + +//----------------------------------------------------------------------------- +// update() +//----------------------------------------------------------------------------- +void LLHUDEffectResetSkeleton::update() +{ + // If the target object is dead, set the target object to NULL + if (mTargetObject.isNull() || mTargetObject->isDead()) + { + markDead(); + return; + } + + if (mSourceObject.isNull() || mSourceObject->isDead()) + { + markDead(); + return; + } + + if (mTargetObject->isAvatar()) + { + // Only the owner of a avatar can reset their skeleton like this + // Also allow reset if we created the effect (Local resetting) + if (mSourceObject->getID() == mTargetObject->getID() || getOriginatedHere()) + { + LLVOAvatar* avatar = mTargetObject->asAvatar(); + avatar->resetSkeleton(mResetAnimations); + } + } + else + { + LL_WARNS() << mSourceObject->getID() << " attempted to reset skeleton on " + << mTargetObject->getID() << ", but it is not a avatar!" << LL_ENDL; + } + + markDead(); +} diff --git a/indra/newview/llhudeffectresetskeleton.h b/indra/newview/llhudeffectresetskeleton.h new file mode 100644 index 00000000000..39a61370548 --- /dev/null +++ b/indra/newview/llhudeffectresetskeleton.h @@ -0,0 +1,59 @@ +/** + * @file llhudeffectresetskeleton.h + * @brief LLHUDEffectResetSkeleton class definition + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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_LLHUDEFFECTRESETSKELETON_H +#define LL_LLHUDEFFECTRESETSKELETON_H + +#include "llhudeffect.h" + +class LLViewerObject; +class LLVOAvatar; + + +class LLHUDEffectResetSkeleton final : public LLHUDEffect +{ +public: + friend class LLHUDObject; + + /*virtual*/ void markDead(); + /*virtual*/ void setSourceObject(LLViewerObject* objectp); + + void setTargetObject(LLViewerObject *objp); + void setResetAnimations(bool enable){ mResetAnimations = enable; }; + +protected: + LLHUDEffectResetSkeleton(const U8 type); + ~LLHUDEffectResetSkeleton(); + + /*virtual*/ void packData(LLMessageSystem *mesgsys); + /*virtual*/ void unpackData(LLMessageSystem *mesgsys, S32 blocknum); + + void update(); +private: + bool mResetAnimations; +}; + +#endif // LL_LLHUDEFFECTRESETSKELETON_H diff --git a/indra/newview/llhudobject.cpp b/indra/newview/llhudobject.cpp index e6fbfbfb38e..04e9e2dff2d 100644 --- a/indra/newview/llhudobject.cpp +++ b/indra/newview/llhudobject.cpp @@ -36,6 +36,7 @@ #include "llhudeffecttrail.h" #include "llhudeffectlookat.h" #include "llhudeffectpointat.h" +#include "llhudeffectresetskeleton.h" #include "llhudnametag.h" #include "llvoicevisualizer.h" @@ -241,6 +242,9 @@ LLHUDEffect *LLHUDObject::addHUDEffect(const U8 type) case LL_HUD_EFFECT_BLOB: hud_objectp = new LLHUDEffectBlob(type); break; + case LL_HUD_EFFECT_RESET_SKELETON: + hud_objectp = new LLHUDEffectResetSkeleton(type); + break; default: LL_WARNS() << "Unknown type of hud effect:" << (U32) type << LL_ENDL; } diff --git a/indra/newview/llhudobject.h b/indra/newview/llhudobject.h index 8c628e3f920..f683f21e969 100644 --- a/indra/newview/llhudobject.h +++ b/indra/newview/llhudobject.h @@ -96,7 +96,8 @@ class LLHUDObject : public LLRefCount LL_HUD_EFFECT_POINTAT, LL_HUD_EFFECT_VOICE_VISUALIZER, // Ventrella LL_HUD_NAME_TAG, - LL_HUD_EFFECT_BLOB + LL_HUD_EFFECT_BLOB, + LL_HUD_EFFECT_RESET_SKELETON }; protected: static void sortObjects(); diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp index 4c02511268f..7cd0171a370 100644 --- a/indra/newview/llimprocessing.cpp +++ b/indra/newview/llimprocessing.cpp @@ -1520,10 +1520,10 @@ void LLIMProcessing::requestOfflineMessages() if (!requested && gMessageSystem && !gDisconnected - && LLMuteList::getInstance()->isLoaded() && isAgentAvatarValid() && gAgent.getRegion() - && gAgent.getRegion()->capabilitiesReceived()) + && gAgent.getRegion()->capabilitiesReceived() + && (LLMuteList::getInstance()->isLoaded() || LLMuteList::getInstance()->getLoadFailed())) { std::string cap_url = gAgent.getRegionCapability("ReadOfflineMsgs"); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index f0f25089fad..c1e80ba4f12 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -1700,6 +1700,8 @@ bool LLIMModel::logToFile(const std::string& file_name, const std::string& from, } else { + // will check KeepConversationLogTranscripts on its own + LLConversationLog::instance().cache(); return false; } } diff --git a/indra/newview/llinspecttexture.cpp b/indra/newview/llinspecttexture.cpp index 24dbe61bad7..9f0d2368266 100644 --- a/indra/newview/llinspecttexture.cpp +++ b/indra/newview/llinspecttexture.cpp @@ -115,7 +115,6 @@ class LLTexturePreviewView : public LLView protected: LLPointer m_Image; - S32 mImageBoostLevel = LLGLTexture::BOOST_NONE; std::string mLoadingText; }; @@ -128,12 +127,8 @@ LLTexturePreviewView::LLTexturePreviewView(const LLView::Params& p) LLTexturePreviewView::~LLTexturePreviewView() { - if (m_Image) - { - m_Image->setBoostLevel(mImageBoostLevel); m_Image = nullptr; } -} void LLTexturePreviewView::draw() { @@ -153,18 +148,18 @@ void LLTexturePreviewView::draw() bool isLoading = (!m_Image->isFullyLoaded()) && (m_Image->getDiscardLevel() > 0); if (isLoading) LLFontGL::getFontSansSerif()->renderUTF8(mLoadingText, 0, rctClient.mLeft + 3, rctClient.mTop - 25, LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); - m_Image->addTextureStats((isLoading) ? MAX_IMAGE_AREA : (F32)(rctClient.getWidth() * rctClient.getHeight())); + + m_Image->setKnownDrawSize(MAX_IMAGE_SIZE, MAX_IMAGE_SIZE); } } void LLTexturePreviewView::setImageFromAssetId(const LLUUID& idAsset) { - m_Image = LLViewerTextureManager::getFetchedTexture(idAsset, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + m_Image = LLViewerTextureManager::getFetchedTexture(idAsset, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_THUMBNAIL); if (m_Image) { - mImageBoostLevel = m_Image->getBoostLevel(); - m_Image->setBoostLevel(LLGLTexture::BOOST_PREVIEW); m_Image->forceToSaveRawImage(0); + m_Image->setKnownDrawSize(MAX_IMAGE_SIZE, MAX_IMAGE_SIZE); if ( (!m_Image->isFullyLoaded()) && (!m_Image->hasFetcher()) ) { if (m_Image->isInFastCacheList()) diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index f9e90491190..1e6c5cf04a8 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -829,6 +829,8 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, { const LLInventoryObject *obj = getInventoryObject(); bool single_folder_root = (mRoot == NULL); + bool is_cof = isCOFFolder(); + bool is_inbox = isInboxFolder(); if (obj) { @@ -843,7 +845,8 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, disabled_items.push_back(std::string("Copy")); } - if (isAgentInventory() && !single_folder_root && !isMarketplaceListingsFolder()) + bool is_agent_inventory = isAgentInventory(); + if (is_agent_inventory && !single_folder_root && !is_cof && !is_inbox) { items.push_back(std::string("New folder from selected")); items.push_back(std::string("Subfolder Separator")); @@ -856,6 +859,19 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, } } + if (isFavorite()) + { + items.push_back(std::string("Remove from Favorites")); + } + else if (is_agent_inventory && !gInventory.isObjectDescendentOf(mUUID, gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH))) + { + items.push_back(std::string("Add to Favorites")); + if (gInventory.getRootFolderID() == mUUID) + { + disabled_items.push_back(std::string("Add to Favorites")); + } + } + if (obj->getIsLinkType()) { items.push_back(std::string("Find Original")); @@ -868,6 +884,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, if (!isItemMovable() || !canMenuCut()) { disabled_items.push_back(std::string("Cut")); + disabled_items.push_back(std::string("New folder from selected")); } } else @@ -877,7 +894,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, items.push_back(std::string("Find Links")); } - if (!isInboxFolder() && !single_folder_root) + if (!is_inbox && !single_folder_root) { items.push_back(std::string("Rename")); if (!isItemRenameable() || ((flags & FIRST_SELECTED_ITEM) == 0)) @@ -917,6 +934,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, if (!isItemMovable() || !canMenuCut()) { disabled_items.push_back(std::string("Cut")); + disabled_items.push_back(std::string("New folder from selected")); } if (canListOnMarketplace() && !isMarketplaceListingsFolder() && !isInboxFolder()) @@ -939,7 +957,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, } // Don't allow items to be pasted directly into the COF or the inbox - if (!isCOFFolder() && !isInboxFolder()) + if (!is_cof && !is_inbox) { items.push_back(std::string("Paste")); } @@ -1333,6 +1351,13 @@ bool LLInvFVBridge::isAgentInventory() const return model->isObjectDescendentOf(mUUID, gInventory.getRootFolderID()); } +bool LLInvFVBridge::isAgentInventoryRoot() const +{ + const LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + return gInventory.getRootFolderID() == mUUID; +} + bool LLInvFVBridge::isCOFFolder() const { return LLAppearanceMgr::instance().getIsInCOF(mUUID); @@ -2280,7 +2305,21 @@ const LLUUID& LLItemBridge::getThumbnailUUID() const return LLUUID::null; } -// virtual +bool LLItemBridge::isFavorite() const +{ + LLViewerInventoryItem* item = NULL; + LLInventoryModel* model = getInventoryModel(); + if (model) + { + item = model->getItem(mUUID); + } + if (item) + { + return get_is_favorite(item); + } + return false; +} + bool LLItemBridge::isItemPermissive() const { if (LLViewerInventoryItem* item = getItem()) @@ -2425,6 +2464,16 @@ const LLUUID& LLFolderBridge::getThumbnailUUID() const return LLUUID::null; } +bool LLFolderBridge::isFavorite() const +{ + LLViewerInventoryCategory* cat = getCategory(); + if (cat) + { + return cat->getIsFavorite(); + } + return false; +} + void LLFolderBridge::update() { // we know we have children but haven't fetched them (doesn't obey filter) @@ -4420,6 +4469,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items } disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("upload_options")); disabled_items.push_back(std::string("upload_def")); disabled_items.push_back(std::string("create_new")); } @@ -4445,6 +4495,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items { disabled_items.push_back(std::string("New Folder")); disabled_items.push_back(std::string("New Listing Folder")); + disabled_items.push_back(std::string("upload_options")); disabled_items.push_back(std::string("upload_def")); disabled_items.push_back(std::string("create_new")); } @@ -4504,6 +4555,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items items.push_back(std::string("Rename")); items.push_back(std::string("thumbnail")); + addInventoryFavoritesMenuOptions(items); addDeleteContextMenuOptions(items, disabled_items); // EXT-4030: disallow deletion of currently worn outfit const LLViewerInventoryItem* base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); @@ -4521,6 +4573,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items EMyOutfitsSubfolderType in_my_outfits = myoutfit_object_subfolder_type(model, mUUID, outfits_id); if (in_my_outfits != MY_OUTFITS_NO) { + // Either an outfit or a subfolder inside MY_OUTFITS if (in_my_outfits == MY_OUTFITS_SUBFOLDER) { // Not inside an outfit, but inside 'my outfits' @@ -4530,6 +4583,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items items.push_back(std::string("Rename")); items.push_back(std::string("thumbnail")); + addInventoryFavoritesMenuOptions(items); addDeleteContextMenuOptions(items, disabled_items); } else @@ -4546,6 +4600,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items } if (!isMarketplaceListingsFolder()) { + items.push_back(std::string("upload_options")); items.push_back(std::string("upload_def")); items.push_back(std::string("create_new")); items.push_back(std::string("New Script")); @@ -4577,6 +4632,8 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items if (model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT) == mUUID) { items.push_back(std::string("Copy outfit list to clipboard")); + addInventoryFavoritesMenuOptions(items); + addOpenFolderMenuOptions(flags, items); } @@ -4835,6 +4892,18 @@ void LLFolderBridge::addOpenFolderMenuOptions(U32 flags, menuentry_vec_t& items) } } +void LLFolderBridge::addInventoryFavoritesMenuOptions(menuentry_vec_t& items) +{ + if (isFavorite()) + { + items.push_back(std::string("Remove from Favorites")); + } + else + { + items.push_back(std::string("Add to Favorites")); + } +} + bool LLFolderBridge::hasChildren() const { LLInventoryModel* model = getInventoryModel(); @@ -7089,12 +7158,13 @@ void LLObjectBridge::performAction(LLInventoryModel* model, std::string action) item = (LLViewerInventoryItem*)gInventory.getItem(object_id); if(item && gInventory.isObjectDescendentOf(object_id, gInventory.getRootFolderID())) { - rez_attachment(item, NULL, true); // Replace if "Wear"ing. + static LLCachedControl replace_item(gSavedSettings, "InventoryAddAttachmentBehavior", false); + rez_attachment(item, NULL, ("attach" == action) ? replace_item() : true); // Replace if "Wear"ing. } else if(item && item->isFinished()) { // must be in library. copy it to our inventory and put it on. - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, (LLViewerJointAttachment*)0)); + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, (LLViewerJointAttachment*)0, true)); copy_inventory_item( gAgent.getID(), item->getPermissions().getOwner(), @@ -7735,6 +7805,15 @@ void LLLinkItemBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { items.push_back(std::string("Properties")); addDeleteContextMenuOptions(items, disabled_items); + + if (isFavorite()) + { + items.push_back(std::string("Remove from Favorites")); + } + else if (isAgentInventory()) + { + items.push_back(std::string("Add to Favorites")); + } } addLinkReplaceMenuOption(items, disabled_items); hide_context_entries(menu, items, disabled_items); @@ -7961,6 +8040,15 @@ void LLLinkFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { items.push_back(std::string("Find Original")); addDeleteContextMenuOptions(items, disabled_items); + + if (isFavorite()) + { + items.push_back(std::string("Remove from Favorites")); + } + else if (isAgentInventory()) + { + items.push_back(std::string("Add to Favorites")); + } } hide_context_entries(menu, items, disabled_items); } @@ -8219,7 +8307,8 @@ void LLObjectBridgeAction::attachOrDetach() } else { - LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, false); // Don't replace if adding. + static LLCachedControl inventory_linking(gSavedSettings, "InventoryAddAttachmentBehavior", false); + LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, inventory_linking()); // Don't replace if adding. } } @@ -8410,6 +8499,7 @@ void LLRecentItemsFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) buildContextMenuOptions(flags, items, disabled_items); items.erase(std::remove(items.begin(), items.end(), std::string("New Folder")), items.end()); + items.erase(std::remove(items.begin(), items.end(), std::string("New folder from selected")), items.end()); hide_context_entries(menu, items, disabled_items); } @@ -8444,6 +8534,51 @@ LLInvFVBridge* LLRecentInventoryBridgeBuilder::createBridge( return new_listener; } +/************************************************************************/ +/* Favorites Inventory Panel related classes */ +/************************************************************************/ +void LLFavoritesFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + // todo: consider things that should be disabled + menuentry_vec_t disabled_items, items; + buildContextMenuOptions(flags, items, disabled_items); + + items.erase(std::remove(items.begin(), items.end(), std::string("New Folder")), items.end()); + items.erase(std::remove(items.begin(), items.end(), std::string("New folder from selected")), items.end()); + + hide_context_entries(menu, items, disabled_items); +} + +LLInvFVBridge* LLFavoritesInventoryBridgeBuilder::createBridge( + LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags /*= 0x00*/) const +{ + LLInvFVBridge* new_listener = NULL; + if (asset_type == LLAssetType::AT_CATEGORY + && actual_asset_type != LLAssetType::AT_LINK_FOLDER) + { + new_listener = new LLFavoritesFolderBridge(inv_type, inventory, root, uuid); + } + else + { + new_listener = LLInventoryFolderViewModelBuilder::createBridge(asset_type, + actual_asset_type, + inv_type, + inventory, + view_model, + root, + uuid, + flags); + } + return new_listener; +} + LLFolderViewGroupedItemBridge::LLFolderViewGroupedItemBridge() { } @@ -8454,7 +8589,7 @@ void LLFolderViewGroupedItemBridge::groupFilterContextMenu(folder_view_item_dequ menuentry_vec_t disabled_items; if (get_selection_item_uuids(selected_items, ids)) { - if (!LLAppearanceMgr::instance().canAddWearables(ids) && canWearSelected(ids)) + if (!LLAppearanceMgr::instance().canAddWearables(ids, false) && canWearSelected(ids)) { disabled_items.push_back(std::string("Wearable And Object Wear")); disabled_items.push_back(std::string("Wearable Add")); diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index b7bdef9b21d..d96adbd1d21 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -86,6 +86,7 @@ class LLInvFVBridge : public LLFolderViewModelItemInventory //-------------------------------------------------------------------- virtual const LLUUID& getUUID() const { return mUUID; } virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null; } + virtual bool isFavorite() const { return false; } virtual void clearDisplayName() { mDisplayName.clear(); } virtual void restoreItem() {} virtual void restoreToWorld() {} @@ -175,6 +176,7 @@ class LLInvFVBridge : public LLFolderViewModelItemInventory bool isLinkedObjectMissing() const; // Is this a linked obj whose baseobj is not in inventory? bool isAgentInventory() const; // false if lost or in the inventory library + bool isAgentInventoryRoot() const; // true if worn by agent bool isCOFFolder() const; // true if COF or descendant of bool isInboxFolder() const; // true if COF or descendant of marketplace inbox @@ -259,6 +261,7 @@ class LLItemBridge : public LLInvFVBridge LLViewerInventoryItem* getItem() const; virtual const LLUUID& getThumbnailUUID() const; + virtual bool isFavorite() const; protected: bool confirmRemoveItem(const LLSD& notification, const LLSD& response); @@ -301,6 +304,7 @@ class LLFolderBridge : public LLInvFVBridge virtual std::string getLabelSuffix() const; virtual LLFontGL::StyleFlags getLabelStyle() const; virtual const LLUUID& getThumbnailUUID() const; + virtual bool isFavorite() const; void setShowDescendantsCount(bool show_count) {mShowDescendantsCount = show_count;} @@ -341,6 +345,7 @@ class LLFolderBridge : public LLInvFVBridge void buildContextMenuOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items); void buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items); void addOpenFolderMenuOptions(U32 flags, menuentry_vec_t& items); + void addInventoryFavoritesMenuOptions(menuentry_vec_t& items); // Inventory favorites, not toolbar favorites //-------------------------------------------------------------------- // Menu callbacks @@ -754,6 +759,46 @@ class LLRecentInventoryBridgeBuilder : public LLInventoryFolderViewModelBuilder U32 flags = 0x00) const; }; +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Favorites Inventory Panel related classes +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +// Overridden version of the Inventory-Folder-View-Bridge for Folders +class LLFavoritesFolderBridge : public LLFolderBridge +{ + friend class LLInvFVBridgeAction; +public: + // Creates context menu for Folders related to Recent Inventory Panel. + // Uses base logic and than removes from visible items "New..." menu items. + LLFavoritesFolderBridge(LLInventoryType::EType type, + LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLFolderBridge(inventory, root, uuid) + { + mInvType = type; + } + /*virtual*/ void buildContextMenu(LLMenuGL& menu, U32 flags); + /*virtual*/ bool canSortContent() const { return true; } +}; + +// Bridge builder to create Inventory-Folder-View-Bridge for Recent Inventory Panel +class LLFavoritesInventoryBridgeBuilder : public LLInventoryFolderViewModelBuilder +{ +public: + LLFavoritesInventoryBridgeBuilder() {} + // Overrides FolderBridge for Recent Inventory Panel. + // It use base functionality for bridges other than FolderBridge. + virtual LLInvFVBridge* createBridge(LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags = 0x00) const; +}; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Marketplace Inventory Panel related classes //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -781,7 +826,7 @@ class LLMarketplaceFolderBridge : public LLFolderBridge void rez_attachment(LLViewerInventoryItem* item, LLViewerJointAttachment* attachment, - bool replace = false); + bool replace); // Move items from an in-world object's "Contents" folder to a specified // folder in agent inventory. diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index 01f2c6c5253..99c2d6e410d 100644 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -64,6 +64,7 @@ LLInventoryFilter::FilterOps::FilterOps(const Params& p) mFilterUUID(p.uuid), mFilterLinks(p.links), mFilterThumbnails(p.thumbnails), + mFilterFavorites(p.favorites), mSearchVisibility(p.search_visibility) { } @@ -159,6 +160,7 @@ bool LLInventoryFilter::check(const LLFolderViewModelItem* item) passed = passed && checkAgainstCreator(listener); passed = passed && checkAgainstSearchVisibility(listener); + passed = passed && checkAgainstFilterFavorites(listener->getUUID()); passed = passed && checkAgainstFilterThumbnails(listener->getUUID()); return passed; @@ -221,6 +223,19 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const return false; } + const LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); + if (cat && cat->getIsFavorite()) + { + if (mFilterOps.mFilterFavorites == FILTER_ONLY_FAVORITES) + { + return true; + } + if (mFilterOps.mFilterFavorites == FILTER_EXCLUDE_FAVORITES) + { + return false; + } + } + // Marketplace folder filtering const U32 filterTypes = mFilterOps.mFilterTypes; const U32 marketplace_filter = FILTERTYPE_MARKETPLACE_ACTIVE | FILTERTYPE_MARKETPLACE_INACTIVE | @@ -273,6 +288,16 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const } } + if (filterTypes & FILTERTYPE_NO_TRASH_ITEMS) + { + const LLUUID trash_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + // If not a descendant of the marketplace listings root, then the nesting depth is -1 by definition + if (gInventory.isObjectDescendentOf(folder_id, trash_uuid)) + { + return false; + } + } + // show folder links LLViewerInventoryItem* item = gInventory.getItem(folder_id); if (item && item->getActualType() == LLAssetType::AT_LINK_FOLDER) @@ -611,6 +636,24 @@ bool LLInventoryFilter::checkAgainstFilterThumbnails(const LLUUID& object_id) co return true; } +bool LLInventoryFilter::checkAgainstFilterFavorites(const LLUUID& object_id) const +{ + const LLInventoryObject* object = gInventory.getObject(object_id); + if (!object) return true; + + + if (mFilterOps.mFilterFavorites != FILTER_INCLUDE_FAVORITES) + { + bool is_favorite = get_is_favorite(object); + if (is_favorite && (mFilterOps.mFilterFavorites == FILTER_EXCLUDE_FAVORITES)) + return false; + if (!is_favorite && (mFilterOps.mFilterFavorites == FILTER_ONLY_FAVORITES)) + return false; + } + + return true; +} + bool LLInventoryFilter::checkAgainstCreator(const LLFolderViewModelItemInventory* listener) const { if (!listener) @@ -811,6 +854,32 @@ void LLInventoryFilter::setFilterThumbnails(U64 filter_thumbnails) mFilterOps.mFilterThumbnails = filter_thumbnails; } +void LLInventoryFilter::setFilterFavorites(U64 filter_favorites) +{ + if (mFilterOps.mFilterFavorites != filter_favorites) + { + if (mFilterOps.mFilterFavorites == FILTER_EXCLUDE_FAVORITES + && filter_favorites == FILTER_ONLY_FAVORITES) + { + setModified(FILTER_RESTART); + } + else if (mFilterOps.mFilterFavorites == FILTER_ONLY_FAVORITES + && filter_favorites == FILTER_EXCLUDE_FAVORITES) + { + setModified(FILTER_RESTART); + } + else if (mFilterOps.mFilterFavorites == FILTER_INCLUDE_FAVORITES) + { + setModified(FILTER_MORE_RESTRICTIVE); + } + else + { + setModified(FILTER_LESS_RESTRICTIVE); + } + } + mFilterOps.mFilterFavorites = filter_favorites; +} + void LLInventoryFilter::setFilterEmptySystemFolders() { mFilterOps.mFilterTypes |= FILTERTYPE_EMPTYFOLDERS; @@ -923,6 +992,11 @@ void LLInventoryFilter::toggleSearchVisibilityLibrary() } } +void LLInventoryFilter::setFilterNoTrashFolder() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_NO_TRASH_ITEMS; +} + void LLInventoryFilter::setFilterNoMarketplaceFolder() { mFilterOps.mFilterTypes |= FILTERTYPE_NO_MARKETPLACE_ITEMS; @@ -1615,6 +1689,11 @@ U64 LLInventoryFilter::getFilterThumbnails() const return mFilterOps.mFilterThumbnails; } +U64 LLInventoryFilter::getFilterFavorites() const +{ + return mFilterOps.mFilterFavorites; +} + bool LLInventoryFilter::hasFilterString() const { return mFilterSubString.size() > 0; diff --git a/indra/newview/llinventoryfilter.h b/indra/newview/llinventoryfilter.h index 612a161ba2a..c0164e04e48 100644 --- a/indra/newview/llinventoryfilter.h +++ b/indra/newview/llinventoryfilter.h @@ -61,6 +61,7 @@ class LLInventoryFilter : public LLFolderViewFilter FILTERTYPE_NO_MARKETPLACE_ITEMS = 0x1 << 10, // pass iff folder is not under the marketplace FILTERTYPE_WORN = 0x1 << 11, // pass if item is worn FILTERTYPE_SETTINGS = 0x1 << 12, // pass if the item is a settings object + FILTERTYPE_NO_TRASH_ITEMS = 0x1 << 13, // pass iff folder is not under the marketplace }; enum EFilterDateDirection @@ -83,6 +84,13 @@ class LLInventoryFilter : public LLFolderViewFilter FILTER_ONLY_THUMBNAILS }; + enum EFilterFavorite + { + FILTER_INCLUDE_FAVORITES, + FILTER_EXCLUDE_FAVORITES, + FILTER_ONLY_FAVORITES + }; + enum ESortOrderType { SO_NAME = 0, // Sort inventory by name @@ -149,6 +157,7 @@ class LLInventoryFilter : public LLFolderViewFilter Optional permissions; Optional creator_type; Optional thumbnails; + Optional favorites; Params() : types("filter_types", FILTERTYPE_OBJECT), @@ -156,6 +165,7 @@ class LLInventoryFilter : public LLFolderViewFilter wearable_types("wearable_types", 0xffffFFFFffffFFFFULL), settings_types("settings_types", 0xffffFFFFffffFFFFULL), thumbnails("thumbnails", FILTER_INCLUDE_THUMBNAILS), + favorites("favorites", FILTER_INCLUDE_FAVORITES), category_types("category_types", 0xffffFFFFffffFFFFULL), links("links", FILTERLINK_INCLUDE_LINKS), search_visibility("search_visibility", 0xFFFFFFFF), @@ -177,6 +187,7 @@ class LLInventoryFilter : public LLFolderViewFilter mFilterWearableTypes, mFilterSettingsTypes, // for _SETTINGS mFilterThumbnails, + mFilterFavorites, mFilterLinks, mFilterCategoryTypes; // For _CATEGORY LLUUID mFilterUUID; // for UUID @@ -220,6 +231,7 @@ class LLInventoryFilter : public LLFolderViewFilter U64 getFilterSettingsTypes() const; U64 getSearchVisibilityTypes() const; U64 getFilterThumbnails() const; + U64 getFilterFavorites() const; bool isFilterObjectTypesWith(LLInventoryType::EType t) const; void setFilterObjectTypes(U64 types); @@ -233,8 +245,10 @@ class LLInventoryFilter : public LLFolderViewFilter void setFilterMarketplaceInactiveFolders(); void setFilterMarketplaceUnassociatedFolders(); void setFilterMarketplaceListingFolders(bool select_only_listing_folders); + void setFilterNoTrashFolder(); void setFilterNoMarketplaceFolder(); void setFilterThumbnails(U64 filter_thumbnails); + void setFilterFavorites(U64 filter_favorites); void updateFilterTypes(U64 types, U64& current_types); void setSearchType(ESearchType type); ESearchType getSearchType() { return mSearchType; } @@ -339,6 +353,7 @@ class LLInventoryFilter : public LLFolderViewFilter LLInventoryFilter& operator =(const LLInventoryFilter& other); bool checkAgainstFilterThumbnails(const LLUUID& object_id) const; + bool checkAgainstFilterFavorites(const LLUUID& object_id) const; private: bool areDateLimitsSet() const; diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index d2ed7168c5a..d1fd82a7f19 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -51,6 +51,7 @@ #include "lldirpicker.h" #include "lldonotdisturbnotificationstorage.h" #include "llfloatermarketplacelistings.h" +#include "llfloatermodelpreview.h" #include "llfloatersidepanelcontainer.h" #include "llfocusmgr.h" #include "llfolderview.h" @@ -62,6 +63,7 @@ #include "llinventorymodel.h" #include "llinventorypanel.h" #include "lllineeditor.h" +#include "llmaterialeditor.h" #include "llmarketplacenotifications.h" #include "llmarketplacefunctions.h" #include "llmenugl.h" @@ -86,6 +88,7 @@ #include "llviewermessage.h" #include "llviewerfoldertype.h" #include "llviewerobjectlist.h" +#include "llviewermenufile.h" #include "llviewerregion.h" #include "llviewerwindow.h" #include "llvoavatarself.h" @@ -2172,21 +2175,10 @@ void validate_marketplacelistings( void change_item_parent(const LLUUID& item_id, const LLUUID& new_parent_id) { - LLInventoryItem* inv_item = gInventory.getItem(item_id); + LLViewerInventoryItem* inv_item = gInventory.getItem(item_id); if (inv_item) { - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate old_folder(inv_item->getParentUUID(), -1); - update.push_back(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - - LLPointer new_item = new LLViewerInventoryItem(inv_item); - new_item->setParent(new_parent_id); - new_item->updateParentOnServer(false); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); + gInventory.changeItemParent(inv_item, new_parent_id, false); } } @@ -2194,17 +2186,17 @@ void move_items_to_folder(const LLUUID& new_cat_uuid, const uuid_vec_t& selected { for (uuid_vec_t::const_iterator it = selected_uuids.begin(); it != selected_uuids.end(); ++it) { - LLInventoryItem* inv_item = gInventory.getItem(*it); + LLViewerInventoryItem* inv_item = gInventory.getItem(*it); if (inv_item) { - change_item_parent(*it, new_cat_uuid); + gInventory.changeItemParent(inv_item, new_cat_uuid, false); } else { - LLInventoryCategory* inv_cat = gInventory.getCategory(*it); + LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); if (inv_cat && !LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) { - gInventory.changeCategoryParent((LLViewerInventoryCategory*)inv_cat, new_cat_uuid, false); + gInventory.changeCategoryParent(inv_cat, new_cat_uuid, false); } } } @@ -2488,6 +2480,143 @@ void ungroup_folder_items(const LLUUID& folder_id) gInventory.notifyObservers(); } +class LLUpdateFavorite : public LLInventoryCallback +{ +public: + LLUpdateFavorite(const LLUUID& inv_item_id) + : mInvItemID(inv_item_id) + {} + /* virtual */ void fire(const LLUUID& inv_item_id) override + { + gInventory.addChangedMask(LLInventoryObserver::UPDATE_FAVORITE, mInvItemID); + + LLInventoryModel::item_array_t items; + LLInventoryModel::cat_array_t cat_array; + LLLinkedItemIDMatches matches(mInvItemID); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cat_array, + items, + LLInventoryModel::INCLUDE_TRASH, + matches); + + std::set link_ids; + for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) + { + LLPointer item = *it; + + gInventory.addChangedMask(LLInventoryObserver::UPDATE_FAVORITE, item->getUUID()); + } + + gInventory.notifyObservers(); + } +private: + LLUUID mInvItemID; +}; + +void favorite_send(LLInventoryObject* obj, const LLUUID& obj_id, bool favorite) +{ + LLSD updates; + if (favorite) + { + updates["favorite"] = LLSD().with("toggled", true); + } + else + { + updates["favorite"] = LLSD(); + } + + LLPointer cb = new LLUpdateFavorite(obj_id); + + LLViewerInventoryCategory* view_folder = dynamic_cast(obj); + if (view_folder) + { + update_inventory_category(obj_id, updates, cb); + } + LLViewerInventoryItem* view_item = dynamic_cast(obj); + if (view_item) + { + update_inventory_item(obj_id, updates, cb); + } +} + +bool get_is_favorite(const LLInventoryObject* object) +{ + if (object->getIsLinkType()) + { + LLInventoryObject* obj = gInventory.getObject(object->getLinkedUUID()); + return obj && obj->getIsFavorite(); + } + + return object->getIsFavorite(); +} + +bool get_is_favorite(const LLUUID& obj_id) +{ + LLInventoryObject* object = gInventory.getObject(obj_id); + if (object && object->getIsLinkType()) + { + LLInventoryObject* obj = gInventory.getObject(object->getLinkedUUID()); + return obj && obj->getIsFavorite(); + } + + return object->getIsFavorite(); +} + +void set_favorite(const LLUUID& obj_id, bool favorite) +{ + LLInventoryObject* obj = gInventory.getObject(obj_id); + + if (obj && obj->getIsLinkType()) + { + if (!favorite && obj->getIsFavorite()) + { + // Links currently aren't supposed to be favorites, + // instead should show state of the original + LL_INFOS("Inventory") << "Recovering proper 'favorites' state of a link " << obj_id << LL_ENDL; + favorite_send(obj, obj_id, false); + } + obj = gInventory.getObject(obj->getLinkedUUID()); + } + + if (obj && obj->getIsFavorite() != favorite) + { + favorite_send(obj, obj->getUUID(), favorite); + } +} + +void toggle_favorite(const LLUUID& obj_id) +{ + LLInventoryObject* obj = gInventory.getObject(obj_id); + if (obj && obj->getIsLinkType()) + { + obj = gInventory.getObject(obj->getLinkedUUID()); + } + + if (obj) + { + favorite_send(obj, obj->getUUID(), !obj->getIsFavorite()); + } +} + +void toggle_favorites(const uuid_vec_t& ids) +{ + if (ids.size() == 0) + { + return; + } + if (ids.size() == 1) + { + toggle_favorite(ids[0]); + return; + } + + bool new_val = !get_is_favorite(ids.front()); + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + set_favorite(*it, new_val); + } +} + std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id) { if (model) @@ -2822,6 +2951,20 @@ bool LLIsTypeWithPermissions::operator()(LLInventoryCategory* cat, LLInventoryIt return false; } +bool LLFavoritesCollector::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + if (item && item->getIsFavorite()) + { + return true; + } + if (cat && cat->getIsFavorite()) + { + return true; + } + return false; +} + bool LLBuddyCollector::operator()(LLInventoryCategory* cat, LLInventoryItem* item) { @@ -3306,7 +3449,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root for (LLInventoryModel::item_array_t::value_type& item : items) { - if (get_is_item_worn(item)) + if (!item->getIsLinkType() && get_is_item_worn(item)) { has_worn = true; LLWearableType::EType type = item->getWearableType(); @@ -3326,7 +3469,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root } } LLViewerInventoryItem* item = gInventory.getItem(obj_id); - if (item && get_is_item_worn(item)) + if (item && !item->getIsLinkType() && get_is_item_worn(item)) { has_worn = true; LLWearableType::EType type = item->getWearableType(); @@ -3532,7 +3675,6 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root } else if ("new_folder_from_selected" == action) { - LLInventoryObject* first_item = gInventory.getObject(*ids.begin()); if (!first_item) { @@ -3576,6 +3718,20 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root ungroup_folder_items(*ids.begin()); } } + else if ("add_to_favorites" == action) + { + for (const LLUUID& id : ids) + { + set_favorite(id, true); + } + } + else if ("remove_from_favorites" == action) + { + for (const LLUUID& id : ids) + { + set_favorite(id, false); + } + } else if ("thumbnail" == action) { if (selected_items.size() > 0) @@ -3686,6 +3842,54 @@ void LLInventoryAction::removeItemFromDND(LLFolderView* root) } } +void LLInventoryAction::fileUploadLocation(const LLUUID& dest_id, const std::string& action) +{ + if (action == "def_model") + { + gSavedPerAccountSettings.setString("ModelUploadFolder", dest_id.asString()); + } + else if (action == "def_texture") + { + gSavedPerAccountSettings.setString("TextureUploadFolder", dest_id.asString()); + } + else if (action == "def_sound") + { + gSavedPerAccountSettings.setString("SoundUploadFolder", dest_id.asString()); + } + else if (action == "def_animation") + { + gSavedPerAccountSettings.setString("AnimationUploadFolder", dest_id.asString()); + } + else if (action == "def_pbr_material") + { + gSavedPerAccountSettings.setString("PBRUploadFolder", dest_id.asString()); + } + else if (action == "upload_texture") + { + LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2, dest_id), LLFilePicker::FFLOAD_IMAGE, false); + } + else if (action == "upload_sound") + { + LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2, dest_id), LLFilePicker::FFLOAD_WAV, false); + } + else if (action == "upload_animation") + { + LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2, dest_id), LLFilePicker::FFLOAD_ANIM, false); + } + else if (action == "upload_model") + { + LLFloaterModelPreview::showModelPreview(dest_id); + } + else if (action == "upload_pbr_material") + { + LLMaterialEditor::importMaterial(dest_id); + } + else if (action == "upload_bulk") + { + LLFilePickerReplyThread::startPicker(boost::bind(&upload_bulk, _1, _2, true, dest_id), LLFilePicker::FFLOAD_ALL, true); + } +} + void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle root) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); @@ -3794,15 +3998,17 @@ void LLInventoryAction::buildMarketplaceFolders(LLFolderView* root) for (; set_iter != selected_items.end(); ++set_iter) { viewModel = dynamic_cast((*set_iter)->getViewModelItem()); - if (!viewModel || !viewModel->getInventoryObject()) continue; - if (gInventory.isObjectDescendentOf(viewModel->getInventoryObject()->getParentUUID(), marketplacelistings_id)) + if (!viewModel) continue; + LLInventoryObject* inv_obj = viewModel->getInventoryObject(); + if (!inv_obj) continue; + if (gInventory.isObjectDescendentOf(inv_obj->getParentUUID(), marketplacelistings_id)) { - const LLUUID &parent_id = viewModel->getInventoryObject()->getParentUUID(); + const LLUUID &parent_id = inv_obj->getParentUUID(); if (parent_id != marketplacelistings_id) { sMarketplaceFolders.push_back(parent_id); } - const LLUUID &curr_id = viewModel->getInventoryObject()->getUUID(); + const LLUUID &curr_id = inv_obj->getUUID(); if (curr_id != marketplacelistings_id) { sMarketplaceFolders.push_back(curr_id); diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index b23f82a189a..ae7bb8770d5 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -118,6 +118,11 @@ bool can_move_to_my_outfits_as_subfolder(LLInventoryModel* model, LLInventoryCat std::string get_localized_folder_name(LLUUID cat_uuid); void new_folder_window(const LLUUID& folder_id); void ungroup_folder_items(const LLUUID& folder_id); +bool get_is_favorite(const LLInventoryObject* object); +bool get_is_favorite(const LLUUID& obj_id); +void set_favorite(const LLUUID& obj_id, bool favorite); +void toggle_favorite(const LLUUID& obj_id); +void toggle_favorites(const uuid_vec_t& ids); std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id); std::string get_searchable_creator_name(LLInventoryModel* model, const LLUUID& item_id); std::string get_searchable_UUID(LLInventoryModel* model, const LLUUID& item_id); @@ -201,7 +206,9 @@ class LLInventoryCollectFunctor { public: virtual ~LLInventoryCollectFunctor(){}; - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) = 0; + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) = 0; + + virtual bool exceedsLimit() { return false; } static bool itemTransferCommonlyAllowed(const LLInventoryItem* item); }; @@ -375,6 +382,18 @@ class LLIsTypeWithPermissions : public LLInventoryCollectFunctor LLUUID mGroupID; }; +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFavoritesCollector +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFavoritesCollector : public LLInventoryCollectFunctor +{ +public: + LLFavoritesCollector() {} + virtual ~LLFavoritesCollector() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLBuddyCollector // @@ -633,6 +652,7 @@ struct LLInventoryAction static void callback_copySelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action); static void onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle root); static void removeItemFromDND(LLFolderView* root); + static void fileUploadLocation(const LLUUID& dest_id, const std::string& action); static void saveMultipleTextures(const std::vector& filenames, std::set selected_items, LLInventoryModel* model); diff --git a/indra/newview/llinventorygallery.cpp b/indra/newview/llinventorygallery.cpp index 43d4edb069e..a4cb6ea65d7 100644 --- a/indra/newview/llinventorygallery.cpp +++ b/indra/newview/llinventorygallery.cpp @@ -636,7 +636,7 @@ void LLInventoryGallery::removeFromLastRow(LLInventoryGalleryItem* item) mItemPanels.pop_back(); } -LLInventoryGalleryItem* LLInventoryGallery::buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn) +LLInventoryGalleryItem* LLInventoryGallery::buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn, bool is_favorite) { LLInventoryGalleryItem::Params giparams; giparams.visible = true; @@ -647,6 +647,7 @@ LLInventoryGalleryItem* LLInventoryGallery::buildGalleryItem(std::string name, L gitem->setUUID(item_id); gitem->setGallery(this); gitem->setType(type, inventory_type, flags, is_link); + gitem->setFavorite(is_favorite); gitem->setLoadImmediately(mLoadThumbnailsImmediately); gitem->setThumbnail(thumbnail_id); gitem->setWorn(is_worn); @@ -939,8 +940,19 @@ bool LLInventoryGallery::updateAddedItem(LLUUID item_id) } bool res = false; - - LLInventoryGalleryItem* item = buildGalleryItem(name, item_id, obj->getType(), thumbnail_id, inventory_type, misc_flags, obj->getCreationDate(), obj->getIsLinkType(), is_worn); + bool is_favorite = get_is_favorite(obj); + + LLInventoryGalleryItem* item = buildGalleryItem( + name, + item_id, + obj->getType(), + thumbnail_id, + inventory_type, + misc_flags, + obj->getCreationDate(), + obj->getIsLinkType(), + is_worn, + is_favorite); mItemMap.insert(LLInventoryGallery::gallery_item_map_t::value_type(item_id, item)); if (mGalleryCreated) { @@ -977,7 +989,7 @@ void LLInventoryGallery::updateRemovedItem(LLUUID item_id) mItemBuildQuery.erase(item_id); } -void LLInventoryGallery::updateChangedItemName(LLUUID item_id, std::string name) +void LLInventoryGallery::updateChangedItemData(LLUUID item_id, std::string name, bool is_favorite) { gallery_item_map_t::iterator iter = mItemMap.find(item_id); if (iter != mItemMap.end()) @@ -986,6 +998,7 @@ void LLInventoryGallery::updateChangedItemName(LLUUID item_id, std::string name) if (item) { item->setItemName(name); + item->setFavorite(is_favorite); } } } @@ -2001,7 +2014,7 @@ void LLInventoryGallery::deleteSelection() for (LLInventoryModel::item_array_t::value_type& item : items) { - if (get_is_item_worn(item)) + if (!item->getIsLinkType() && get_is_item_worn(item)) { has_worn = true; LLWearableType::EType type = item->getWearableType(); @@ -2022,7 +2035,7 @@ void LLInventoryGallery::deleteSelection() } LLViewerInventoryItem* item = gInventory.getItem(id); - if (item && get_is_item_worn(item)) + if (item && !item->getIsLinkType() && get_is_item_worn(item)) { has_worn = true; LLWearableType::EType type = item->getWearableType(); @@ -2335,7 +2348,7 @@ void LLInventoryGallery::refreshList(const LLUUID& category_id) return; } - updateChangedItemName(*items_iter, obj->getName()); + updateChangedItemData(*items_iter, obj->getName(), get_is_favorite(obj)); mNeedsArrange = true; } @@ -2851,6 +2864,14 @@ void LLInventoryGalleryItem::setType(LLAssetType::EType type, LLInventoryType::E getChild("link_overlay")->setVisible(is_link); } +void LLInventoryGalleryItem::setFavorite(bool is_favorite) +{ + getChild("fav_icon")->setVisible(is_favorite); + static const LLUIColor text_color = LLUIColorTable::instance().getColor("LabelTextColor", LLColor4::white); + static const LLUIColor favorite_color = LLUIColorTable::instance().getColor("InventoryFavoriteColor", LLColor4::white); + mNameText->setReadOnlyColor(is_favorite ? favorite_color : text_color); +} + void LLInventoryGalleryItem::setThumbnail(LLUUID id) { mDefaultImage = id.isNull(); diff --git a/indra/newview/llinventorygallery.h b/indra/newview/llinventorygallery.h index 59d08d19ed4..7f53f9998da 100644 --- a/indra/newview/llinventorygallery.h +++ b/indra/newview/llinventorygallery.h @@ -102,7 +102,7 @@ class LLInventoryGallery : public LLPanel, public LLEditMenuHandler void getCurrentCategories(uuid_vec_t& vcur); bool updateAddedItem(LLUUID item_id); // returns true if added item is visible void updateRemovedItem(LLUUID item_id); - void updateChangedItemName(LLUUID item_id, std::string name); + void updateChangedItemData(LLUUID item_id, std::string name, bool is_favorite); void updateItemThumbnail(LLUUID item_id); void updateWornItem(LLUUID item_id, bool is_worn); @@ -227,7 +227,7 @@ class LLInventoryGallery : public LLPanel, public LLEditMenuHandler bool updateRowsIfNeeded(); void updateGalleryWidth(); - LLInventoryGalleryItem* buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn); + LLInventoryGalleryItem* buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn, bool is_favorite); LLInventoryGalleryItem* getItem(const LLUUID& id) const; void buildGalleryPanel(int row_count); @@ -343,6 +343,7 @@ class LLInventoryGalleryItem : public LLPanel void setHidden(bool hidden) {mHidden = hidden;} void setType(LLAssetType::EType type, LLInventoryType::EType inventory_type, U32 flags, bool is_link); + void setFavorite(bool is_favorite); LLAssetType::EType getAssetType() { return mType; } void setThumbnail(LLUUID id); void setGallery(LLInventoryGallery* gallery) { mGallery = gallery; } diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp index ec3e03ee2d1..7212c4dedb4 100644 --- a/indra/newview/llinventorygallerymenu.cpp +++ b/indra/newview/llinventorygallerymenu.cpp @@ -250,6 +250,20 @@ void LLInventoryGalleryContextMenu::doToSelected(const LLSD& userdata) { ungroup_folder_items(mUUIDs.front()); } + else if ("add_to_favorites" == action) + { + for (const LLUUID& id : mUUIDs) + { + set_favorite(id, true); + } + } + else if ("remove_from_favorites" == action) + { + for (const LLUUID& id : mUUIDs) + { + set_favorite(id, false); + } + } else if ("replaceoutfit" == action) { modify_outfit(false, mUUIDs.front(), &gInventory); @@ -472,22 +486,7 @@ void LLInventoryGalleryContextMenu::onRename(const LLSD& notification, const LLS void LLInventoryGalleryContextMenu::fileUploadLocation(const LLSD& userdata) { const std::string param = userdata.asString(); - if (param == "model") - { - gSavedPerAccountSettings.setString("ModelUploadFolder", mUUIDs.front().asString()); - } - else if (param == "texture") - { - gSavedPerAccountSettings.setString("TextureUploadFolder", mUUIDs.front().asString()); - } - else if (param == "sound") - { - gSavedPerAccountSettings.setString("SoundUploadFolder", mUUIDs.front().asString()); - } - else if (param == "animation") - { - gSavedPerAccountSettings.setString("AnimationUploadFolder", mUUIDs.front().asString()); - } + LLInventoryAction::fileUploadLocation(mUUIDs.front(), param); } bool LLInventoryGalleryContextMenu::canSetUploadLocation(const LLSD& userdata) @@ -768,6 +767,7 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men { items.push_back(std::string("New Folder")); } + items.push_back(std::string("upload_options")); items.push_back(std::string("upload_def")); } @@ -787,6 +787,18 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men } } + if (!is_trash && !is_in_trash && gInventory.getRootFolderID() != selected_id) + { + if (get_is_favorite(obj)) + { + items.push_back(std::string("Remove from Favorites")); + } + else + { + items.push_back(std::string("Add to Favorites")); + } + } + items.push_back(std::string("Subfolder Separator")); if (!is_system_folder && !isRootFolder() && !is_outfits) { @@ -832,6 +844,17 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men if(is_agent_inventory) { items.push_back(std::string("Cut")); + if (!is_in_trash) + { + if (get_is_favorite(obj)) + { + items.push_back(std::string("Remove from Favorites")); + } + else + { + items.push_back(std::string("Add to Favorites")); + } + } if (!is_link || !is_cof || !get_is_item_worn(selected_id)) { items.push_back(std::string("Delete")); @@ -968,6 +991,7 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men } disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("upload_options")); disabled_items.push_back(std::string("upload_def")); disabled_items.push_back(std::string("create_new")); } @@ -1018,6 +1042,15 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men disabled_items.push_back(std::string("Marketplace Move")); } } + + if (get_is_favorite(obj)) + { + items.push_back(std::string("Remove from Favorites")); + } + else if (is_agent_inventory) + { + items.push_back(std::string("Add to Favorites")); + } } hide_context_entries(*menu, items, disabled_items); diff --git a/indra/newview/llinventoryitemslist.cpp b/indra/newview/llinventoryitemslist.cpp index 2e0669fc38e..cfa37cc3ee2 100644 --- a/indra/newview/llinventoryitemslist.cpp +++ b/indra/newview/llinventoryitemslist.cpp @@ -115,7 +115,7 @@ void LLInventoryItemsList::doIdle() { if (mRefreshState == REFRESH_COMPLETE) return; - if (isInVisibleChain() || mForceRefresh ) + if (isInVisibleChain() || mForceRefresh || !getFilterSubString().empty()) { refresh(); diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp new file mode 100644 index 00000000000..028483e134d --- /dev/null +++ b/indra/newview/llinventorylistener.cpp @@ -0,0 +1,309 @@ +/** + * @file llinventorylistener.cpp + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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 "llinventorylistener.h" + +#include "llappearancemgr.h" +#include "llinventoryfunctions.h" +#include "lltransutil.h" +#include "llwearableitemslist.h" +#include "stringize.h" + +LLInventoryListener::LLInventoryListener() + : LLEventAPI("LLInventory", + "API for interactions with viewer Inventory items") +{ + add("getItemsInfo", + "Return information about items or folders defined in [\"item_ids\"]:\n" + "reply will contain [\"items\"] and [\"categories\"] result set keys", + &LLInventoryListener::getItemsInfo, + llsd::map("item_ids", LLSD(), "reply", LLSD())); + + add("getFolderTypeNames", + "Return the table of folder type names, contained in [\"names\"]\n", + &LLInventoryListener::getFolderTypeNames, + llsd::map("reply", LLSD())); + + add("getAssetTypeNames", + "Return the table of asset type names, contained in [\"names\"]\n", + &LLInventoryListener::getAssetTypeNames, + llsd::map("reply", LLSD())); + + add("getBasicFolderID", + "Return the UUID of the folder by specified folder type name, for example:\n" + "\"Textures\", \"My outfits\", \"Sounds\" and other basic folders which have associated type", + &LLInventoryListener::getBasicFolderID, + llsd::map("ft_name", LLSD(), "reply", LLSD())); + + add("getDirectDescendants", + "Return result set keys [\"categories\"] and [\"items\"] for the direct\n" + "descendants of the [\"folder_id\"]", + &LLInventoryListener::getDirectDescendants, + llsd::map("folder_id", LLSD(), "reply", LLSD())); + + add("collectDescendantsIf", + "Return result set keys [\"categories\"] and [\"items\"] for the descendants\n" + "of the [\"folder_id\"], if it passes specified filters:\n" + "[\"name\"] is a substring of object's name,\n" + "[\"desc\"] is a substring of object's description,\n" + "asset [\"type\"] corresponds to the string name of the object's asset type\n" + "[\"limit\"] sets item count limit in result set (default unlimited)\n" + "[\"filter_links\"]: EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default)", + &LLInventoryListener::collectDescendantsIf, + llsd::map("folder_id", LLSD(), "reply", LLSD())); +} + +void add_cat_info(LLEventAPI::Response& response, LLViewerInventoryCategory* cat) +{ + response["categories"].insert(cat->getUUID().asString(), + llsd::map("id", cat->getUUID(), + "name", cat->getName(), + "parent_id", cat->getParentUUID(), + "type", LLFolderType::lookup(cat->getPreferredType()))); + +}; + +void add_item_info(LLEventAPI::Response& response, LLViewerInventoryItem* item) +{ + response["items"].insert(item->getUUID().asString(), + llsd::map("id", item->getUUID(), + "name", item->getName(), + "parent_id", item->getParentUUID(), + "desc", item->getDescription(), + "inv_type", LLInventoryType::lookup(item->getInventoryType()), + "asset_type", LLAssetType::lookup(item->getType()), + "creation_date", LLSD::Integer(item->getCreationDate()), + "asset_id", item->getAssetUUID(), + "is_link", item->getIsLinkType(), + "linked_id", item->getLinkedUUID())); +} + +void add_objects_info(LLEventAPI::Response& response, LLInventoryModel::cat_array_t cat_array, LLInventoryModel::item_array_t item_array) +{ + for (auto& p : item_array) + { + add_item_info(response, p); + } + for (auto& p : cat_array) + { + add_cat_info(response, p); + } +} + +void LLInventoryListener::getItemsInfo(LLSD const &data) +{ + Response response(LLSD(), data); + uuid_vec_t ids = LLSDParam(data["item_ids"]); + for (auto &it : ids) + { + LLViewerInventoryItem* item = gInventory.getItem(it); + if (item) + { + add_item_info(response, item); + } + else + { + LLViewerInventoryCategory *cat = gInventory.getCategory(it); + if (cat) + { + add_cat_info(response, cat); + } + } + } +} + +void LLInventoryListener::getFolderTypeNames(LLSD const &data) +{ + Response response(llsd::map("names", LLFolderType::getTypeNames()), data); +} + +void LLInventoryListener::getAssetTypeNames(LLSD const &data) +{ + Response response(llsd::map("names", LLAssetType::getTypeNames()), data); +} + +void LLInventoryListener::getBasicFolderID(LLSD const &data) +{ + Response response(llsd::map("id", gInventory.findCategoryUUIDForType(LLFolderType::lookup(data["ft_name"].asString()))), data); +} + + +void LLInventoryListener::getDirectDescendants(LLSD const &data) +{ + Response response(LLSD(), data); + LLUUID folder_id(data["folder_id"].asUUID()); + LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); + if (!cat) + { + return response.error(stringize("Folder ", std::quoted(data["folder_id"].asString()), " was not found")); + } + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(folder_id, cats, items); + + add_objects_info(response, *cats, *items); +} + +struct LLFilteredCollector : public LLInventoryCollectFunctor +{ + enum EFilterLink + { + INCLUDE_LINKS, // show links too + EXCLUDE_LINKS, // don't show links + ONLY_LINKS // only show links + }; + + LLFilteredCollector(LLSD const &data); + virtual ~LLFilteredCollector() {} + virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item) override; + virtual bool exceedsLimit() override + { + // mItemLimit == 0 means unlimited + return (mItemLimit && mItemLimit <= mItemCount); + } + + protected: + bool checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item); + bool checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item); + bool checkagainstLinks(LLInventoryCategory *cat, LLInventoryItem *item); + + LLAssetType::EType mType; + std::string mName; + std::string mDesc; + EFilterLink mLinkFilter; + + S32 mItemLimit; + S32 mItemCount; +}; + +void LLInventoryListener::collectDescendantsIf(LLSD const &data) +{ + Response response(LLSD(), data); + LLUUID folder_id(data["folder_id"].asUUID()); + LLViewerInventoryCategory *cat = gInventory.getCategory(folder_id); + if (!cat) + { + return response.error(stringize("Folder ", std::quoted(data["folder_id"].asString()), " was not found")); + } + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + + LLFilteredCollector collector = LLFilteredCollector(data); + + gInventory.collectDescendentsIf(folder_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector); + + add_objects_info(response, cat_array, item_array); +} + +LLFilteredCollector::LLFilteredCollector(LLSD const &data) : + mType(LLAssetType::EType::AT_UNKNOWN), + mLinkFilter(INCLUDE_LINKS), + mItemLimit(0), + mItemCount(0) +{ + + mName = data["name"].asString(); + mDesc = data["desc"].asString(); + + if (data.has("type")) + { + mType = LLAssetType::lookup(data["type"]); + } + if (data.has("filter_links")) + { + if (data["filter_links"] == "EXCLUDE_LINKS") + { + mLinkFilter = EXCLUDE_LINKS; + } + else if (data["filter_links"] == "ONLY_LINKS") + { + mLinkFilter = ONLY_LINKS; + } + } + if (data["limit"].isInteger()) + { + mItemLimit = std::max(data["limit"].asInteger(), 1); + } +} + +bool LLFilteredCollector::operator()(LLInventoryCategory *cat, LLInventoryItem *item) +{ + bool passed = checkagainstType(cat, item); + passed = passed && checkagainstNameDesc(cat, item); + passed = passed && checkagainstLinks(cat, item); + + if (passed) + { + ++mItemCount; + } + return passed; +} + +bool LLFilteredCollector::checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item) +{ + std::string name, desc; + bool passed(true); + if (cat) + { + if (!mDesc.empty()) return false; + name = cat->getName(); + } + if (item) + { + name = item->getName(); + passed = (mDesc.empty() || (item->getDescription().find(mDesc) != std::string::npos)); + } + + return passed && (mName.empty() || name.find(mName) != std::string::npos); +} + +bool LLFilteredCollector::checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item) +{ + if (mType == LLAssetType::AT_UNKNOWN) + { + return true; + } + if (cat && (mType == LLAssetType::AT_CATEGORY)) + { + return true; + } + if (item && item->getType() == mType) + { + return true; + } + return false; +} + +bool LLFilteredCollector::checkagainstLinks(LLInventoryCategory *cat, LLInventoryItem *item) +{ + bool is_link = cat ? cat->getIsLinkType() : item->getIsLinkType(); + if (is_link && (mLinkFilter == EXCLUDE_LINKS)) + return false; + if (!is_link && (mLinkFilter == ONLY_LINKS)) + return false; + return true; +} diff --git a/indra/newview/llinventorylistener.h b/indra/newview/llinventorylistener.h new file mode 100644 index 00000000000..d50397730c7 --- /dev/null +++ b/indra/newview/llinventorylistener.h @@ -0,0 +1,48 @@ +/** + * @file llinventorylistener.h + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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_LLINVENTORYLISTENER_H +#define LL_LLINVENTORYLISTENER_H + +#include "lleventapi.h" +#include "llinventoryfunctions.h" + +class LLInventoryListener : public LLEventAPI +{ +public: + LLInventoryListener(); + +private: + void getItemsInfo(LLSD const &data); + void getFolderTypeNames(LLSD const &data); + void getAssetTypeNames(LLSD const &data); + void getBasicFolderID(LLSD const &data); + void getDirectDescendants(LLSD const &data); + void collectDescendantsIf(LLSD const &data); +}; + +#endif // LL_LLINVENTORYLISTENER_H + diff --git a/indra/newview/llinventorylistitem.cpp b/indra/newview/llinventorylistitem.cpp index e210975a5a4..5fb5b0f23f0 100644 --- a/indra/newview/llinventorylistitem.cpp +++ b/indra/newview/llinventorylistitem.cpp @@ -43,7 +43,19 @@ static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelInventoryListItemBaseParams(&typeid(LLPanelInventoryListItemBase::Params), "inventory_list_item"); -static const S32 WIDGET_SPACING = 3; +constexpr S32 WIDGET_SPACING = 3; +constexpr S32 FAVORITE_IMAGE_SIZE = 14; +constexpr S32 FAVORITE_IMAGE_PAD = 3; + +bool get_is_item_favorite(const LLViewerInventoryItem* inv) +{ + if (inv->getIsLinkType()) + { + LLInventoryObject* obj = gInventory.getObject(inv->getLinkedUUID()); + return obj && obj->getIsFavorite(); + } + return inv->getIsFavorite(); +} LLPanelInventoryListItemBase::Params::Params() : default_style("default_style"), @@ -75,19 +87,30 @@ void LLPanelInventoryListItemBase::draw() LLViewerInventoryItem* inv_item = getItem(); if (inv_item) { - updateItem(inv_item->getName()); + updateItem(inv_item->getName(), get_is_item_favorite(inv_item)); } setNeedsRefresh(false); } + static LLUICachedControl draw_star("InventoryFavoritesUseStar", true); + + LLRect local_rect = getLocalRect(); if (mHovered && mHoverImage) { - mHoverImage->draw(getLocalRect()); + mHoverImage->draw(local_rect); + } + else if (mIsFavorite && draw_star()) + { + + static LLPointer fav_img = LLRender2D::getInstance()->getUIImage("Inv_Favorite_Star_Full"); + gl_draw_scaled_image( + local_rect.getWidth() - FAVORITE_IMAGE_SIZE - FAVORITE_IMAGE_PAD, FAVORITE_IMAGE_PAD, + FAVORITE_IMAGE_SIZE, FAVORITE_IMAGE_SIZE, fav_img->getImage()); } if (mSelected && mSelectedImage) { - mSelectedImage->draw(getLocalRect()); + mSelectedImage->draw(local_rect); } if (mSeparatorVisible && mSeparatorImage) @@ -95,7 +118,7 @@ void LLPanelInventoryListItemBase::draw() // place under bottom of listitem, using image height // item_pad in list using the item should be >= image height // to avoid cropping of top of the next item. - LLRect separator_rect = getLocalRect(); + LLRect separator_rect = local_rect; separator_rect.mTop = separator_rect.mBottom; separator_rect.mBottom -= mSeparatorImage->getHeight(); F32 alpha = getCurrentTransparency(); @@ -107,9 +130,15 @@ void LLPanelInventoryListItemBase::draw() // virtual void LLPanelInventoryListItemBase::updateItem(const std::string& name, + bool favorite, EItemState item_state) { setIconImage(mIconImage); + if (mIsFavorite != favorite) + { + mIsFavorite = favorite; + reshapeMiddleWidgets(); + } setTitle(name, mHighlightedText, item_state); } @@ -164,7 +193,7 @@ bool LLPanelInventoryListItemBase::postBuild() if (inv_item) { mIconImage = LLInventoryIcon::getIcon(inv_item->getType(), inv_item->getInventoryType(), inv_item->getFlags(), false); - updateItem(inv_item->getName()); + updateItem(inv_item->getName(), get_is_item_favorite(inv_item)); } setNeedsRefresh(true); @@ -290,6 +319,7 @@ LLPanelInventoryListItemBase::LLPanelInventoryListItemBase(LLViewerInventoryItem mHovered(false), mSelected(false), mSeparatorVisible(false), + mIsFavorite(false), mHoverImage(params.hover_image), mSelectedImage(params.selected_image), mSeparatorImage(params.separator_image) @@ -392,6 +422,16 @@ void LLPanelInventoryListItemBase::setTitle(const std::string& title, default:; } + if (mIsFavorite) + { + static LLUICachedControl use_color("InventoryFavoritesColorText", true); + if (use_color) + { + static const LLUIColor favorite_color = LLUIColorTable::instance().getColor("InventoryFavoriteColor", LLColor4::white); + style_params.color = favorite_color; + } + } + LLTextUtil::textboxSetHighlightedVal( mTitleCtrl, style_params, @@ -466,6 +506,10 @@ void LLPanelInventoryListItemBase::reshapeMiddleWidgets() S32 name_left = icon_rect.mRight + getWidgetSpacing(); S32 name_right = getLocalRect().getWidth() - mRightWidgetsWidth - getWidgetSpacing(); + if (mIsFavorite) + { + name_right -= FAVORITE_IMAGE_SIZE + FAVORITE_IMAGE_PAD; + } LLRect name_rect(mTitleCtrl->getRect()); name_rect.set(name_left, name_rect.mTop, name_right, name_rect.mBottom); mTitleCtrl->setShape(name_rect); diff --git a/indra/newview/llinventorylistitem.h b/indra/newview/llinventorylistitem.h index 21540a380bf..40a86001a47 100644 --- a/indra/newview/llinventorylistitem.h +++ b/indra/newview/llinventorylistitem.h @@ -167,6 +167,7 @@ class LLPanelInventoryListItemBase : public LLPanel * Called after inventory item was updated, update panel widgets to reflect inventory changes. */ virtual void updateItem(const std::string& name, + bool favorite, EItemState item_state = IS_DEFAULT); void setLeftWidgetsWidth(S32 width) { mLeftWidgetsWidth = width; } @@ -222,8 +223,9 @@ class LLPanelInventoryListItemBase : public LLPanel LLUIImagePtr mSelectedImage; LLUIImagePtr mSeparatorImage; - bool mSelected; - bool mSeparatorVisible; + bool mSelected = false; + bool mSeparatorVisible = false; + bool mIsFavorite = false; // note that any setter needs to update tittle std::string mHighlightedText; diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index f1ca69416b1..2dfc3b014f5 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -78,7 +78,7 @@ // Increment this if the inventory contents change in a non-backwards-compatible way. // For viewer 2, the addition of link items makes a pre-viewer-2 cache incorrect. -const S32 LLInventoryModel::sCurrentInvCacheVersion = 3; +const S32 LLInventoryModel::sCurrentInvCacheVersion = 5; bool LLInventoryModel::sFirstTimeInViewer2 = true; S32 LLInventoryModel::sPendingSystemFolders = 0; @@ -1283,6 +1283,10 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id, { for (auto& cat : *cat_array) { + if (add.exceedsLimit()) + { + break; + } if(add(cat,NULL)) { cats.push_back(cat); @@ -1298,6 +1302,10 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id, { for (auto& item : *item_array) { + if (add.exceedsLimit()) + { + break; + } if(add(NULL, item)) { items.push_back(item); @@ -2809,8 +2817,9 @@ bool LLInventoryModel::loadSkeleton( cached_ids.insert(tcat->getUUID()); // At the moment download does not provide a thumbnail - // uuid, use the one from cache + // uuid or favorite, use values from cache tcat->setThumbnailUUID(cat->getThumbnailUUID()); + tcat->setFavorite(cat->getIsFavorite()); } } @@ -3375,7 +3384,7 @@ bool LLInventoryModel::loadFromFile(const std::string& filename, } LL_INFOS(LOG_INV) << "loading inventory from: (" << filename << ")" << LL_ENDL; - llifstream file(filename.c_str()); + llifstream file(filename.c_str(), std::ifstream::in | std::ifstream::binary); if (!file.is_open()) { @@ -3384,80 +3393,92 @@ bool LLInventoryModel::loadFromFile(const std::string& filename, } is_cache_obsolete = true; // Obsolete until proven current - - //U64 lines_count = 0U; - std::string line; - LLPointer parser = new LLSDNotationParser(); - while (std::getline(file, line)) + U32 value_nbo = 0; + file.read((char*)&value_nbo, sizeof(U32)); + if (file.fail()) { - LLSD s_item; - std::istringstream iss(line); - if (parser->parse(iss, s_item, line.length()) == LLSDParser::PARSE_FAILURE) + LL_WARNS(LOG_INV) << "Failed to read cache version. Unable to load inventory from: " << filename << LL_ENDL; + } + else + { + S32 version = (S32)ntohl(value_nbo); + if (version == sCurrentInvCacheVersion) { - LL_WARNS(LOG_INV)<< "Parsing inventory cache failed" << LL_ENDL; - break; + // Cache is up to date + is_cache_obsolete = false; } - - if (s_item.has("inv_cache_version")) + else { - S32 version = s_item["inv_cache_version"].asInteger(); - if (version == sCurrentInvCacheVersion) - { - // Cache is up to date - is_cache_obsolete = false; - continue; - } - else - { - LL_WARNS(LOG_INV)<< "Inventory cache is out of date" << LL_ENDL; - break; - } + LL_WARNS(LOG_INV) << "Inventory cache is out of date" << LL_ENDL; } - else if (s_item.has("cat_id")) + } + + LLSD inventory; + if (!is_cache_obsolete) + { + LLPointer parser = new LLSDBinaryParser(); + + if (parser->parse(file, inventory, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE) { - if (is_cache_obsolete) - break; + is_cache_obsolete = true; + LL_WARNS(LOG_INV) << "Parsing inventory cache failed" << LL_ENDL; + } + } - LLPointer inv_cat = new LLViewerInventoryCategory(LLUUID::null); - if(inv_cat->importLLSD(s_item)) + if (!is_cache_obsolete) + { + const LLSD& llsd_cats = inventory["categories"]; + if (llsd_cats.isArray()) + { + LLSD::array_const_iterator iter = llsd_cats.beginArray(); + LLSD::array_const_iterator end = llsd_cats.endArray(); + for (; iter != end; ++iter) { - categories.push_back(inv_cat); + LLPointer inv_cat = new LLViewerInventoryCategory(LLUUID::null); + if (inv_cat->importLLSDMap(*iter)) + { + categories.push_back(inv_cat); + } } } - else if (s_item.has("item_id")) - { - if (is_cache_obsolete) - break; - LLPointer inv_item = new LLViewerInventoryItem; - if( inv_item->fromLLSD(s_item) ) + const LLSD& llsd_items = inventory["items"]; + if (llsd_items.isArray()) + { + LLSD::array_const_iterator iter = llsd_items.beginArray(); + LLSD::array_const_iterator end = llsd_items.endArray(); + for (; iter != end; ++iter) { - if(inv_item->getUUID().isNull()) - { - LL_DEBUGS(LOG_INV) << "Ignoring inventory with null item id: " - << inv_item->getName() << LL_ENDL; - } - else + LLPointer inv_item = new LLViewerInventoryItem; + if (inv_item->fromLLSD(*iter)) { - if (inv_item->getType() == LLAssetType::AT_UNKNOWN) + if (inv_item->getUUID().isNull()) { - cats_to_update.insert(inv_item->getParentUUID()); + LL_DEBUGS(LOG_INV) << "Ignoring inventory with null item id: " + << inv_item->getName() << LL_ENDL; } else { - items.push_back(inv_item); + if (inv_item->getType() == LLAssetType::AT_UNKNOWN) + { + cats_to_update.insert(inv_item->getParentUUID()); + } + else + { + items.push_back(inv_item); + } } } + + // TODO(brad) - figure out how to reenable this without breaking everything else + // static constexpr U64 BATCH_SIZE = 512U; + // if ((++lines_count % BATCH_SIZE) == 0) + // { + // // SL-19968 - make sure message system code gets a chance to run every so often + // pump_idle_startup_network(); + // } } } - -// TODO(brad) - figure out how to reenable this without breaking everything else -// static constexpr U64 BATCH_SIZE = 512U; -// if ((++lines_count % BATCH_SIZE) == 0) -// { -// // SL-19968 - make sure message system code gets a chance to run every so often -// pump_idle_startup_network(); -// } } file.close(); @@ -3480,54 +3501,54 @@ bool LLInventoryModel::saveToFile(const std::string& filename, try { - llofstream fileXML(filename.c_str()); - if (!fileXML.is_open()) + llofstream fileSD(filename.c_str(), std::ios_base::out | std::ios_base::binary); + if (!fileSD.is_open()) { LL_WARNS(LOG_INV) << "Failed to open file. Unable to save inventory to: " << filename << LL_ENDL; return false; } - - LLSD cache_ver; - cache_ver["inv_cache_version"] = sCurrentInvCacheVersion; - - if (fileXML.fail()) + U32 value_nbo = htonl(sCurrentInvCacheVersion); + fileSD.write((const char*)(&value_nbo), sizeof(U32)); + if (fileSD.fail()) { - LL_WARNS(LOG_INV) << "Failed to write cache version to file. Unable to save inventory to: " << filename << LL_ENDL; + LL_WARNS(LOG_INV) << "Failed to write cache. Unable to save inventory to: " << filename << LL_ENDL; return false; } - fileXML << LLSDOStreamer(cache_ver) << std::endl; + LLSD inventory; + inventory["categories"] = LLSD::emptyArray(); + LLSD& cat_array = inventory["categories"]; S32 cat_count = 0; for (auto& cat : categories) { if (cat->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) { - fileXML << LLSDOStreamer(cat->exportLLSD()) << std::endl; + LLSD sd; + cat->exportLLSD(sd); + cat_array.append(sd); cat_count++; } - - if (fileXML.fail()) - { - LL_WARNS(LOG_INV) << "Failed to write a folder to file. Unable to save inventory to: " << filename << LL_ENDL; - return false; - } } + inventory["items"] = LLSD::emptyArray(); + LLSD& item_array = inventory["items"]; auto it_count = items.size(); for (auto& item : items) { - fileXML << LLSDOStreamer(item->asLLSD()) << std::endl; - - if (fileXML.fail()) - { - LL_WARNS(LOG_INV) << "Failed to write an item to file. Unable to save inventory to: " << filename << LL_ENDL; - return false; - } + LLSD sd; + item->asLLSD(sd); + item_array.append(sd); + } + fileSD << LLSDOStreamer(inventory) << std::endl; + if (fileSD.fail()) + { + LL_WARNS(LOG_INV) << "Failed to write cache. Unable to save inventory to: " << filename << LL_ENDL; + return false; } - fileXML.flush(); + fileSD.flush(); - fileXML.close(); + fileSD.close(); LL_INFOS(LOG_INV) << "Inventory saved: " << (S32)cat_count << " categories, " << (S32)it_count << " items." << LL_ENDL; } diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index ac791e224e9..ac22be9d5af 100644 --- a/indra/newview/llinventoryobserver.cpp +++ b/indra/newview/llinventoryobserver.cpp @@ -749,6 +749,13 @@ void LLInventoryCategoriesObserver::changed(U32 mask) cat_changed = true; } + bool is_favorite = category->getIsFavorite(); + if (cat_data.mIsFavorite != is_favorite) + { + cat_data.mIsFavorite = is_favorite; + cat_changed = true; + } + // If anything has changed above, fire the callback. if (cat_changed) cat_data.mCallback(); @@ -766,6 +773,7 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t S32 version = LLViewerInventoryCategory::VERSION_UNKNOWN; S32 current_num_known_descendents = LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN; bool can_be_added = true; + bool favorite = false; LLUUID thumbnail_id; LLViewerInventoryCategory* category = gInventory.getCategory(cat_id); @@ -779,6 +787,7 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t // to a category have been made. version = category->getVersion(); thumbnail_id = category->getThumbnailUUID(); + favorite = category->getIsFavorite(); LLInventoryModel::cat_array_t* cats; LLInventoryModel::item_array_t* items; @@ -804,11 +813,11 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t if(init_name_hash) { digest_t item_name_hash = gInventory.hashDirectDescendentNames(cat_id); - mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents,item_name_hash))); + mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, favorite, cb, version, current_num_known_descendents,item_name_hash))); } else { - mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents))); + mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, favorite, cb, version, current_num_known_descendents))); } } @@ -821,25 +830,37 @@ void LLInventoryCategoriesObserver::removeCategory(const LLUUID& cat_id) } LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents) + const LLUUID& cat_id, + const LLUUID& thumbnail_id, + bool is_favorite, + callback_t cb, + S32 version, + S32 num_descendents) : mCatID(cat_id) , mCallback(cb) , mVersion(version) , mDescendentsCount(num_descendents) , mThumbnailId(thumbnail_id) + , mIsFavorite(is_favorite) , mIsNameHashInitialized(false) { } LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash) + const LLUUID& cat_id, + const LLUUID& thumbnail_id, + bool is_favorite, + callback_t cb, S32 version, + S32 num_descendents, + const digest_t& name_hash) : mCatID(cat_id) , mCallback(cb) , mVersion(version) , mDescendentsCount(num_descendents) , mThumbnailId(thumbnail_id) + , mIsFavorite(is_favorite) , mIsNameHashInitialized(true) , mItemNameHash(name_hash) { diff --git a/indra/newview/llinventoryobserver.h b/indra/newview/llinventoryobserver.h index 950b02d3cf1..12d6c445212 100644 --- a/indra/newview/llinventoryobserver.h +++ b/indra/newview/llinventoryobserver.h @@ -60,6 +60,7 @@ class LLInventoryObserver CREATE = 512, // With ADD, item has just been created. // unfortunately a particular message is still associated with some unique semantics. UPDATE_CREATE = 1024, // With ADD, item added via UpdateCreateInventoryItem + UPDATE_FAVORITE = 2048, // With ADD, item added via UpdateCreateInventoryItem ALL = 0xffffffff }; LLInventoryObserver(); @@ -276,12 +277,26 @@ class LLInventoryCategoriesObserver : public LLInventoryObserver typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" struct LLCategoryData { - LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents); - LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash); + LLCategoryData( + const LLUUID& cat_id, + const LLUUID& thumbnail_id, + bool is_favorite, + callback_t cb, + S32 version, + S32 num_descendents); + LLCategoryData( + const LLUUID& cat_id, + const LLUUID& thumbnail_id, + bool is_favorite, + callback_t cb, + S32 version, + S32 num_descendents, + const digest_t& name_hash); callback_t mCallback; S32 mVersion; S32 mDescendentsCount; digest_t mItemNameHash; + bool mIsFavorite; bool mIsNameHashInitialized; LLUUID mCatID; LLUUID mThumbnailId; diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 443a0022fb9..590cbbec4ec 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -55,11 +55,13 @@ #include "llviewerfoldertype.h" #include "llvoavatarself.h" +class LLInventoryFavoritesItemsPanel; class LLInventoryRecentItemsPanel; class LLAssetFilteredInventoryPanel; static LLDefaultChildRegistry::Register r("inventory_panel"); static LLDefaultChildRegistry::Register t_recent_inventory_panel("recent_inventory_panel"); +static LLDefaultChildRegistry::Register t_favorites_inventory_panel("favorites_inventory_panel"); static LLDefaultChildRegistry::Register t_asset_filtered_inv_panel("asset_filtered_inv_panel"); const std::string LLInventoryPanel::DEFAULT_SORT_ORDER = std::string("InventorySortOrder"); @@ -641,6 +643,19 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve } } + if (mask & LLInventoryObserver::UPDATE_FAVORITE) + { + if (view_item) + { + view_item->refresh(); + LLFolderViewFolder* parent = view_item->getParentFolder(); + if (parent) + { + parent->updateHasFavorites(get_is_favorite(model_item)); + } + } + } + // We don't typically care which of these masks the item is actually flagged with, since the masks // may not be accurate (e.g. in the main inventory panel, I move an item from My Inventory into // Landmarks; this is a STRUCTURE change for that panel but is an ADD change for the Landmarks @@ -669,6 +684,16 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve setSelection(item_id, false); } updateFolderLabel(model_item->getParentUUID()); + + if (get_is_favorite(model_item)) + { + LLFolderViewFolder* new_parent = getFolderByID(model_item->getParentUUID()); + if (new_parent) + { + new_parent->updateHasFavorites(true); + } + } + } ////////////////////////////// @@ -682,9 +707,11 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve { LLFolderViewModelItem* old_parent_vmi = old_parent->getViewModelItem(); LLFolderViewModelItemInventory* viewmodel_folder = static_cast(old_parent_vmi); - LLFolderViewFolder* new_parent = (LLFolderViewFolder*)getItemByID(model_item->getParentUUID()); - // Item has been moved. - if (old_parent != new_parent) + LLFolderViewFolder* new_parent = getFolderByID(model_item->getParentUUID()); + + if (old_parent != new_parent // Item has been moved. + && (new_parent != NULL || !isInRootContent(item_id, view_item)) // item is not or shouldn't be in root content + ) { if (new_parent != NULL) { @@ -719,9 +746,21 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve { old_parent_vmi->dirtyDescendantsFilter(); } + + if (view_item->isFavorite()) + { + if (old_parent) + { + old_parent->updateHasFavorites(false); // favorite was removed + } + if (new_parent) + { + new_parent->updateHasFavorites(true); // favorite was added + } } } } + } ////////////////////////////// // REMOVE Operation @@ -731,6 +770,7 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve // Remove the item's UI. LLFolderViewFolder* parent = view_item->getParentFolder(); removeItemID(viewmodel_item->getUUID()); + bool was_favorite = view_item->isFavorite(); view_item->destroyView(); if(parent) { @@ -744,6 +784,10 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve updateFolderLabel(viewmodel_folder->getUUID()); } } + if (was_favorite) + { + parent->updateHasFavorites(false); // favorite was removed + } } } } @@ -754,7 +798,7 @@ void LLInventoryPanel::modelChanged(U32 mask) { LL_PROFILE_ZONE_SCOPED; - if (mViewsInitialized != VIEWS_INITIALIZED) return; + if (mViewsInitialized != VIEWS_INITIALIZED) return; // todo: Store changes if building? const LLInventoryModel* model = getModel(); if (!model) return; @@ -861,7 +905,23 @@ void LLInventoryPanel::idle(void* user_data) bool in_visible_chain = panel->isInVisibleChain(); - if (!panel->mBuildViewsQueue.empty()) + if (!panel->mBuildRootQueue.empty()) + { + const F64 max_time = in_visible_chain ? 0.006f : 0.001f; // 6 ms + F64 curent_time = LLTimer::getTotalSeconds(); + panel->mBuildViewsEndTime = curent_time + max_time; + + while (curent_time < panel->mBuildViewsEndTime + && !panel->mBuildRootQueue.empty()) + { + LLUUID item_id = panel->mBuildRootQueue.back(); + panel->mBuildRootQueue.pop_back(); + panel->findAndInitRootContent(item_id); + + curent_time = LLTimer::getTotalSeconds(); + } + } + else if (!panel->mBuildViewsQueue.empty()) { const F64 max_time = in_visible_chain ? 0.006f : 0.001f; // 6 ms F64 curent_time = LLTimer::getTotalSeconds(); @@ -901,6 +961,11 @@ void LLInventoryPanel::idle(void* user_data) panel->mViewsInitialized = VIEWS_INITIALIZED; } } + // in case panel is empty or only has 'roots' + else if (panel->mViewsInitialized == VIEWS_BUILDING) + { + panel->mViewsInitialized = VIEWS_INITIALIZED; + } // Take into account the fact that the root folder might be invalidated if (panel->mFolderRoot.get()) @@ -943,20 +1008,9 @@ void LLInventoryPanel::initializeViews(F64 max_time) mBuildViewsEndTime = curent_time + max_time; // init everything - LLUUID root_id = getRootFolderID(); - if (root_id.notNull()) - { - buildNewViews(getRootFolderID()); - } - else - { - // Default case: always add "My Inventory" root first, "Library" root second - // If we run out of time, this still should create root folders - buildNewViews(gInventory.getRootFolderID()); // My Inventory - buildNewViews(gInventory.getLibraryRootFolderID()); // Library - } + initRootContent(); - if (mBuildViewsQueue.empty()) + if (mBuildViewsQueue.empty() && mBuildRootQueue.empty()) { mViewsInitialized = VIEWS_INITIALIZED; } @@ -987,6 +1041,22 @@ void LLInventoryPanel::initializeViews(F64 max_time) } } +void LLInventoryPanel::initRootContent() +{ + LLUUID root_id = getRootFolderID(); + if (root_id.notNull()) + { + buildNewViews(getRootFolderID()); + } + else + { + // Default case: always add "My Inventory" root first, "Library" root second + // If we run out of time, this still should create root folders + buildNewViews(gInventory.getRootFolderID()); // My Inventory + buildNewViews(gInventory.getLibraryRootFolderID()); // Library + } +} + LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop) { @@ -1758,26 +1828,8 @@ bool LLInventoryPanel::beginIMSession() void LLInventoryPanel::fileUploadLocation(const LLSD& userdata) { const std::string param = userdata.asString(); - if (param == "model") - { - gSavedPerAccountSettings.setString("ModelUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } - else if (param == "texture") - { - gSavedPerAccountSettings.setString("TextureUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } - else if (param == "sound") - { - gSavedPerAccountSettings.setString("SoundUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } - else if (param == "animation") - { - gSavedPerAccountSettings.setString("AnimationUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } - else if (param == "pbr_material") - { - gSavedPerAccountSettings.setString("PBRUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } + const LLUUID dest = LLFolderBridge::sSelf.get()->getUUID(); + LLInventoryAction::fileUploadLocation(dest, param); } void LLInventoryPanel::openSingleViewInventory(LLUUID folder_id) @@ -2225,6 +2277,297 @@ LLInventoryRecentItemsPanel::LLInventoryRecentItemsPanel( const Params& params) mInvFVBridgeBuilder = &RECENT_ITEMS_BUILDER; } +/************************************************************************/ +/* Favorites Inventory Panel related class */ +/************************************************************************/ +static const LLFavoritesInventoryBridgeBuilder FAVORITES_BUILDER; +class LLInventoryFavoritesItemsPanel : public LLInventoryPanel +{ +public: + struct Params : public LLInitParam::Block + {}; + + void initFromParams(const Params& p) + { + LLInventoryPanel::initFromParams(p); + // turn off trash + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() | (1ULL << LLFolderType::FT_TRASH)); + getFilter().setFilterNoTrashFolder(); + // turn off marketplace for favorites + getFilter().setFilterNoMarketplaceFolder(); + } + + void removeItemID(const LLUUID& id) override; + bool isInRootContent(const LLUUID& id, LLFolderViewItem* view_item) override; + bool hasPredecessorsInRootContent(const LLInventoryObject* model_item) const; + +protected: + LLInventoryFavoritesItemsPanel(const Params&); + friend class LLUICtrlFactory; + + void findAndInitRootContent(const LLUUID& folder_id) override; + void initRootContent() override; + + // removeFavorite removes item from root, does not readd favorited children if present + bool removeFavorite(const LLUUID& id, const LLInventoryObject* model_item); + void itemChanged(const LLUUID& item_id, U32 mask, const LLInventoryObject* model_item) override; + + std::set mRootContentIDs; +}; + +LLInventoryFavoritesItemsPanel::LLInventoryFavoritesItemsPanel(const Params& params) + : LLInventoryPanel(params) +{ + // replace bridge builder to have necessary View bridges. + mInvFVBridgeBuilder = &FAVORITES_BUILDER; +} + +void LLInventoryFavoritesItemsPanel::removeItemID(const LLUUID& id) +{ + std::set::iterator found = mRootContentIDs.find(id); + if (found != mRootContentIDs.end()) + { + mRootContentIDs.erase(found); + // check content for favorites + mBuildRootQueue.emplace_back(id); + } + + LLInventoryPanel::removeItemID(id); +} + +bool LLInventoryFavoritesItemsPanel::isInRootContent(const LLUUID& id, LLFolderViewItem* view_item) +{ + if (!view_item->isFavorite()) + { + return false; + } + + std::set::iterator found = mRootContentIDs.find(id); + return found != mRootContentIDs.end(); +} + +bool LLInventoryFavoritesItemsPanel::hasPredecessorsInRootContent(const LLInventoryObject* obj) const +{ + LLUUID parent_id = obj->getParentUUID(); + while (parent_id.notNull()) + { + if (mRootContentIDs.contains(parent_id)) + { + return true; + } + LLViewerInventoryCategory* cat = mInventory->getCategory(parent_id); + if (cat) + { + parent_id = cat->getParentUUID(); + } + } + return false; +} + +void LLInventoryFavoritesItemsPanel::findAndInitRootContent(const LLUUID& id) +{ + F64 curent_time = LLTimer::getTotalSeconds(); + if (mBuildViewsEndTime < curent_time) + { + mBuildRootQueue.emplace_back(id); + return; + } + LLViewerInventoryCategory::cat_array_t* categories; + LLViewerInventoryItem::item_array_t* items; + mInventory->lockDirectDescendentArrays(id, categories, items); + + if (categories) + { + S32 count = static_cast(categories->size()); + for (S32 i = 0; i < count; ++i) + { + LLViewerInventoryCategory* cat = categories->at(i); + if (cat->getPreferredType() == LLFolderType::FT_TRASH) + { + continue; + } + else if (cat->getIsFavorite()) + { + LLFolderViewItem* folder_view_item = getItemByID(cat->getUUID()); + if (!folder_view_item) + { + const LLUUID& parent_id = cat->getParentUUID(); + mRootContentIDs.emplace(cat->getUUID()); + + buildViewsTree(cat->getUUID(), parent_id, cat, folder_view_item, mFolderRoot.get(), BUILD_TIMELIMIT); + } + } + else + { + findAndInitRootContent(cat->getUUID()); + } + } + } + + if (items) + { + S32 count = static_cast(items->size()); + for (S32 i = 0; i < count; ++i) + { + LLViewerInventoryItem* item = items->at(i); + const LLUUID item_id = item->getUUID(); + if (item->getIsFavorite() && typedViewsFilter(item_id, item)) + { + LLFolderViewItem* folder_view_item = getItemByID(id); + if (!folder_view_item) + { + const LLUUID& parent_id = item->getParentUUID(); + mRootContentIDs.emplace(item_id); + + buildViewsTree(item_id, parent_id, item, folder_view_item, mFolderRoot.get(), BUILD_TIMELIMIT); + } + } + } + } + + mInventory->unlockDirectDescendentArrays(id); +} + +void LLInventoryFavoritesItemsPanel::initRootContent() +{ + findAndInitRootContent(gInventory.getRootFolderID()); // My Inventory +} + +bool LLInventoryFavoritesItemsPanel::removeFavorite(const LLUUID& id, const LLInventoryObject* model_item) +{ + std::set::iterator found = mRootContentIDs.find(id); + if (found == mRootContentIDs.end()) + { + return false; + } + + mRootContentIDs.erase(found); + + // This item is in root's content, remove item's UI. + LLFolderViewItem* view_item = getItemByID(id); + if (view_item) + { + LLFolderViewFolder* parent = view_item->getParentFolder(); + LLFolderViewModelItemInventory* viewmodel_item = static_cast(view_item->getViewModelItem()); + if (viewmodel_item) + { + removeItemID(viewmodel_item->getUUID()); + } + bool was_favorite = view_item->isFavorite(); + view_item->destroyView(); + if (parent) + { + parent->getViewModelItem()->dirtyDescendantsFilter(); + LLFolderViewModelItemInventory* viewmodel_folder = static_cast(parent->getViewModelItem()); + if (viewmodel_folder) + { + updateFolderLabel(viewmodel_folder->getUUID()); + } + if (was_favorite) + { + parent->updateHasFavorites(false); // favorite was removed + } + } + } + + return true; +} + +void LLInventoryFavoritesItemsPanel::itemChanged(const LLUUID& id, U32 mask, const LLInventoryObject* model_item) +{ + LLFolderViewItem* view_item = getItemByID(id); + if (!model_item && !view_item) + { + // remove operation, but item is not in panel already + return; + } + + bool handled = false; + + if (mask & (LLInventoryObserver::UPDATE_FAVORITE | + LLInventoryObserver::STRUCTURE | + LLInventoryObserver::ADD | + LLInventoryObserver::REMOVE)) + { + // specifically exlude links and not get_is_favorite(model_item) + if (model_item && model_item->getIsFavorite()) + { + if (!view_item) + { + const LLViewerInventoryCategory* cat = dynamic_cast(model_item); + if (cat) + { + // New favorite folder + if (cat->getPreferredType() != LLFolderType::FT_TRASH) + { + // If any descendants were in the list, remove them + // Todo: Consider implementing and checking hasFavorites to save on search + LLFavoritesCollector is_favorite; + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendentsIf(id, cat_array, item_array, false, is_favorite); + for (LLInventoryModel::cat_array_t::const_iterator it = cat_array.begin(); it != cat_array.end(); ++it) + { + removeFavorite((*it)->getUUID(), *it); + } + for (LLInventoryModel::item_array_t::const_iterator it = item_array.begin(); it != item_array.end(); ++it) + { + removeFavorite((*it)->getUUID(), *it); + } + + LLFolderViewItem* folder_view_item = getItemByID(cat->getUUID()); + if (!folder_view_item + && !hasPredecessorsInRootContent(model_item)) + { + const LLUUID& parent_id = cat->getParentUUID(); + mRootContentIDs.emplace(cat->getUUID()); + + buildViewsTree(cat->getUUID(), parent_id, cat, folder_view_item, mFolderRoot.get(), BUILD_ONE_FOLDER); + } + } + } + else + { + // New favorite item + if (model_item->getIsFavorite() + && typedViewsFilter(id, model_item) + && !hasPredecessorsInRootContent(model_item)) + { + const LLUUID& parent_id = model_item->getParentUUID(); + mRootContentIDs.emplace(id); + + buildViewsTree(id, parent_id, model_item, NULL, mFolderRoot.get(), BUILD_ONE_FOLDER); + } + } + handled = true; + } + } + else + { + handled = removeFavorite(id, model_item); + if (handled) + { + const LLViewerInventoryCategory* cat = dynamic_cast(model_item); + // Todo: Consider implementing and checking hasFavorites to save on search + if (cat) + { + // re-add any favorited children + mBuildRootQueue.emplace_back(id); + } + } + } + } + + if (!handled + && (!model_item || model_item->getParentUUID().notNull())) // filter out 'My inventory' + { + LLInventoryPanel::itemChanged(id, mask, model_item); + } +} +/************************************************************************/ +/* LLInventorySingleFolderPanel */ +/************************************************************************/ + static LLDefaultChildRegistry::Register t_single_folder_inventory_panel("single_folder_inventory_panel"); LLInventorySingleFolderPanel::LLInventorySingleFolderPanel(const Params& params) diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h index 56909c8d986..473283352ff 100644 --- a/indra/newview/llinventorypanel.h +++ b/indra/newview/llinventorypanel.h @@ -251,7 +251,8 @@ class LLInventoryPanel : public LLPanel bool reset_filter = false); static void setSFViewAndOpenFolder(const LLInventoryPanel* panel, const LLUUID& folder_id); void addItemID(const LLUUID& id, LLFolderViewItem* itemp); - void removeItemID(const LLUUID& id); + virtual void removeItemID(const LLUUID& id); + virtual bool isInRootContent(const LLUUID& id, LLFolderViewItem* view_item) { return false; } LLFolderViewItem* getItemByID(const LLUUID& id); LLFolderViewFolder* getFolderByID(const LLUUID& id); void setSelectionByID(const LLUUID& obj_id, bool take_keyboard_focus); @@ -334,6 +335,8 @@ class LLInventoryPanel : public LLPanel protected: // Builds the UI. Call this once the inventory is usable. void initializeViews(F64 max_time); + virtual void initRootContent(); + virtual void findAndInitRootContent(const LLUUID& root_id) {}; // Specific inventory colors static bool sColorSetInitialized; @@ -371,7 +374,7 @@ class LLInventoryPanel : public LLPanel virtual LLFolderViewItem* createFolderViewItem(LLInvFVBridge * bridge); boost::function& items, bool user_action)> mSelectionCallback; -private: + // buildViewsTree does not include some checks and is meant // for recursive use, use buildNewViews() for first call LLFolderViewItem* buildViewsTree(const LLUUID& id, @@ -394,6 +397,8 @@ class LLInventoryPanel : public LLPanel EViewsInitializationState mViewsInitialized; // Whether views have been generated F64 mBuildViewsEndTime; // Stop building views past this timestamp std::deque mBuildViewsQueue; + std::deque mBuildRootQueue; + }; diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index fbf4a7d1ddc..fa1f650113a 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -32,6 +32,8 @@ #include "lllogchat.h" #include "llregex.h" #include "lltrans.h" +#include "llurlaction.h" +#include "llurlentry.h" #include "llviewercontrol.h" #include "lldiriterator.h" @@ -358,13 +360,29 @@ void LLLogChat::saveHistory(const std::string& filename, return; } + std::string altered_line = line; + + // avoid costly regex calls + if (line.find("/mention") != std::string::npos) + { + static const boost::regex mention_regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/mention", boost::regex::perl | boost::regex::icase); + + // replace mention URL with [@username](URL) + altered_line = boost::regex_replace(line, mention_regex, [](const boost::smatch& match) -> std::string + { + std::string url = match[0].str(); + std::string username = LLUrlAction::getURLLabel(url); + return "[" + username + "](" + url + ")"; + }); + } + LLSD item; if (gSavedPerAccountSettings.getBOOL("LogTimestamp")) item["time"] = LLLogChat::timestamp2LogString(0, gSavedPerAccountSettings.getBOOL("LogTimestampDate")); item["from_id"] = from_id; - item["message"] = line; + item["message"] = altered_line; //adding "Second Life:" for all system messages to make chat log history parsing more reliable if (from.empty() && from_id.isNull()) @@ -432,8 +450,8 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list& m } // If we got here, we managed to stat the file. - // Open the file to read - LLFILE* fptr = LLFile::fopen(log_file_name, "r"); /*Flawfinder: ignore*/ + // Open the file to read in binary mode to prevent interpreting other characters as EOF + LLFILE* fptr = LLFile::fopen(log_file_name, "rb"); /*Flawfinder: ignore*/ if (!fptr) { // Ok, this is strange but not really tragic in the big picture of things LL_WARNS("ChatHistory") << "Unable to read file " << log_file_name << " after stat was successful" << LL_ENDL; @@ -470,6 +488,19 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list& m std::string line(remove_utf8_bom(buffer)); + + // fast heuristic test for a mention URL in a string + // this is used to avoid costly regex calls + if (line.find("/mention)") != std::string::npos) + { + // restore original mention URL from [@username](URL) format + static const boost::regex altered_mention_regex("\\[@([^\\]]+)\\]\\((" APP_HEADER_REGEX "/agent/[\\da-f-]+/mention)\\)", + boost::regex::perl | boost::regex::icase); + + // $2 captures the URL part + line = boost::regex_replace(line, altered_mention_regex, "$2"); + } + //updated 1.23 plain text log format requires a space added before subsequent lines in a multilined message if (' ' == line[0]) { @@ -1150,7 +1181,7 @@ void LLLoadHistoryThread::loadHistory(const std::string& file_name, std::list cb = new LLObjectsMaterialItemCallback(permissions, buffer, name); @@ -1904,7 +1904,11 @@ static void pack_textures( } } -void LLMaterialEditor::uploadMaterialFromModel(const std::string& filename, tinygltf::Model& model_in, S32 index) +void LLMaterialEditor::uploadMaterialFromModel( + const std::string& filename, + tinygltf::Model& model_in, + S32 index, + const LLUUID& dest) { if (index < 0 || !LLMaterialEditor::capabilitiesAvailable()) { @@ -1927,12 +1931,13 @@ void LLMaterialEditor::uploadMaterialFromModel(const std::string& filename, tiny // This uses 'filename' to make sure multiple bulk uploads work // instead of fighting for a single instance. LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor", LLSD().with("filename", filename).with("index", LLSD::Integer(index))); + me->mUploadFolder = dest; me->loadMaterial(model_in, filename, index, false); me->saveIfNeeded(); } -void LLMaterialEditor::loadMaterialFromFile(const std::string& filename, S32 index) +void LLMaterialEditor::loadMaterialFromFile(const std::string& filename, S32 index, const LLUUID& dest_folder) { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; @@ -2432,17 +2437,17 @@ void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notificati return; } - createInventoryItem(str.str(), new_name, std::string(), permissions); + createInventoryItem(str.str(), new_name, std::string(), permissions, LLUUID::null); } -const void upload_bulk(const std::vector& filenames, LLFilePicker::ELoadFilter type, bool allow_2k); +void upload_bulk(const std::vector& filenames, LLFilePicker::ELoadFilter type, bool allow_2k, const LLUUID& dest); void LLMaterialEditor::loadMaterial(const tinygltf::Model &model_in, const std::string &filename, S32 index, bool open_floater) { if (index == model_in.materials.size()) { // bulk upload all the things - upload_bulk({ filename }, LLFilePicker::FFLOAD_MATERIAL, true); + upload_bulk({ filename }, LLFilePicker::FFLOAD_MATERIAL, true, LLUUID::null); return; } @@ -2872,10 +2877,10 @@ void LLMaterialEditor::setFromGltfMetaData(const std::string& filename, const ti } } -void LLMaterialEditor::importMaterial() +void LLMaterialEditor::importMaterial(const LLUUID dest_folder) { LLFilePickerReplyThread::startPicker( - [](const std::vector& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter) + [dest_folder](const std::vector& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter) { if (LLAppViewer::instance()->quitRequested()) { @@ -2885,7 +2890,7 @@ void LLMaterialEditor::importMaterial() { if (filenames.size() > 0) { - LLMaterialEditor::loadMaterialFromFile(filenames[0], -1); + LLMaterialEditor::loadMaterialFromFile(filenames[0], -1, dest_folder); } } catch (std::bad_alloc&) @@ -3573,6 +3578,7 @@ void LLMaterialEditor::saveTexture(LLImageJ2C* img, const std::string& name, con LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"), expected_upload_cost, + mUploadFolder, false, cb, failed_upload)); diff --git a/indra/newview/llmaterialeditor.h b/indra/newview/llmaterialeditor.h index 232467460ea..1abdd7f84c0 100644 --- a/indra/newview/llmaterialeditor.h +++ b/indra/newview/llmaterialeditor.h @@ -94,7 +94,7 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener void setFromGltfMetaData(const std::string& filename, const tinygltf::Model& model, S32 index); // open a file dialog and select a gltf/glb file for import - static void importMaterial(); + static void importMaterial(const LLUUID dest_folder = LLUUID::null); // for live preview, apply current material to currently selected object void applyToSelection(); @@ -105,8 +105,11 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener void loadAsset() override; // @index if -1 and file contains more than one material, // will promt to select specific one - static void uploadMaterialFromModel(const std::string& filename, tinygltf::Model& model, S32 index); - static void loadMaterialFromFile(const std::string& filename, S32 index = -1); + static void uploadMaterialFromModel(const std::string& filename, + tinygltf::Model& model, + S32 index, + const LLUUID& dest_folder_id = LLUUID::null); + static void loadMaterialFromFile(const std::string& filename, S32 index = -1, const LLUUID& dest_folder = LLUUID::null); void onSelectionChanged(); // live overrides selection changes @@ -134,8 +137,6 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener void onClickSave(); - void getGLTFModel(tinygltf::Model& model); - std::string getEncodedAsset(); bool decodeAsset(const std::vector& buffer); @@ -239,7 +240,7 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener static void saveObjectsMaterialAs(const LLGLTFMaterial *render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions, const LLUUID& object_id /* = LLUUID::null */, const LLUUID& item /* = LLUUID::null */); static bool updateInventoryItem(const std::string &buffer, const LLUUID &item_id, const LLUUID &task_id); - static void createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions); + static void createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions, const LLUUID& upload_folder); void setFromGLTFMaterial(LLGLTFMaterial* mat); bool setFromSelection(); @@ -249,6 +250,7 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener friend class LLMaterialFilePicker; LLUUID mAssetID; + LLUUID mUploadFolder; LLTextureCtrl* mBaseColorTextureCtrl; LLTextureCtrl* mMetallicTextureCtrl; diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 142a3dac39f..9e8ed3bb437 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -816,8 +816,12 @@ class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase }; -void log_upload_error(LLCore::HttpStatus status, const LLSD& content, - const char * const stage, const std::string & model_name) +void log_upload_error( + LLCore::HttpStatus status, + const LLSD& content, + const char * const stage, + const std::string & model_name, + const std::vector & texture_filenames) { // Add notification popup. LLSD args; @@ -875,6 +879,20 @@ void log_upload_error(LLCore::HttpStatus status, const LLSD& content, error_num++; } } + + if (err.has("TextureIndex")) + { + S32 texture_index = err["TextureIndex"].asInteger(); + if (texture_index < texture_filenames.size()) + { + args["MESSAGE"] = message + "\n" + texture_filenames[texture_index]; + } + else + { + llassert(false); // figure out why or how texture wasn't in the list + args["MESSAGE"] = message + llformat("\nTexture index: %d", texture_index); + } + } } else { @@ -2362,7 +2380,13 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p LLPointer volume = new LLVolume(mesh_params, LLVolumeLODGroup::getVolumeScaleFromDetail(lod)); if (volume->unpackVolumeFaces(data, data_size)) { - if (volume->getNumFaces() > 0) + // Use LLVolume::getNumVolumeFaces() here and not LLVolume::getNumFaces(), + // because setMeshAssetLoaded() has not yet been called for this volume + // (it is set later in LLMeshRepository::notifyMeshLoaded()), and + // getNumFaces() would return the number of faces in the LLProfile + // instead. HB + S32 num_faces = volume->getNumVolumeFaces(); + if (num_faces > 0) { // if we have a valid SkinInfo, cache per-joint bounding boxes for this LOD LLPointer skin_info = nullptr; @@ -2376,7 +2400,7 @@ EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_p } if (skin_info.notNull() && isAgentAvatarValid()) { - for (S32 i = 0; i < volume->getNumFaces(); ++i) + for (S32 i = 0; i < num_faces; ++i) { // NOTE: no need to lock gAgentAvatarp as the state being checked is not changed after initialization LLVolumeFace& face = volume->getVolumeFace(i); @@ -2545,9 +2569,10 @@ EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_ return MESH_OK; } -LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures, +LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, const LLMeshUploadThread::lod_sources_map_t& sources_list, + LLVector3& scale, bool upload_textures, bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - const std::string & upload_url, bool do_upload, + const std::string & upload_url, LLUUID destination_folder_id, bool do_upload, LLHandle fee_observer, LLHandle upload_observer) : LLThread("mesh upload"), @@ -2555,10 +2580,12 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, mDiscarded(false), mDoUpload(do_upload), mWholeModelUploadURL(upload_url), + mDestinationFolderId(destination_folder_id), mFeeObserverHandle(fee_observer), mUploadObserverHandle(upload_observer) { mInstanceList = data; + mLodSources = sources_list; mUploadTextures = upload_textures; mUploadSkin = upload_skin; mUploadJoints = upload_joints; @@ -2658,6 +2685,8 @@ void dump_llsd_to_file(const LLSD& content, std::string filename) { if (gSavedSettings.getBOOL("MeshUploadLogXML")) { + filename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, + filename); llofstream of(filename.c_str()); LLSDSerialize::toPrettyXML(content,of); } @@ -2671,13 +2700,21 @@ LLSD llsd_from_file(std::string filename) return result; } -void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) +void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, std::vector& texture_list_dest, bool include_textures) { LLSD result; LLSD res; + if (mDestinationFolderId.isNull()) + { result["folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_OBJECT); result["texture_folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); + } + else + { + result["folder_id"] = mDestinationFolderId; + result["texture_folder_id"] = mDestinationFolderId; + } result["asset_type"] = "mesh"; result["inventory_type"] = "object"; result["description"] = "(No Description)"; @@ -2688,6 +2725,12 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) res["mesh_list"] = LLSD::emptyArray(); res["texture_list"] = LLSD::emptyArray(); res["instance_list"] = LLSD::emptyArray(); + LLSD& lod_sources = res["source_format"]; + lod_sources["high"] = 0; + for (auto &source : mLodSources) + { + lod_sources[source.first] = source.second; + } S32 mesh_num = 0; S32 texture_num = 0; @@ -2824,7 +2867,7 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) LLPointer upload_file = LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); - if (!upload_file.isNull() && upload_file->getDataSize()) + if (!upload_file.isNull() && upload_file->getDataSize() && !upload_file->isBufferInvalid()) { texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); } @@ -2838,6 +2881,8 @@ void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) texture_index[texture] = texture_num; std::string str = texture_str.str(); res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end()); + // store indexes for error handling; + texture_list_dest.push_back(material.mDiffuseMapFilename); texture_num++; } @@ -3103,7 +3148,8 @@ void LLMeshUploadThread::doWholeModelUpload() LL_DEBUGS(LOG_MESH) << "Hull generation completed." << LL_ENDL; mModelData = LLSD::emptyMap(); - wholeModelToLLSD(mModelData, true); + mTextureFiles.clear(); + wholeModelToLLSD(mModelData, mTextureFiles, true); LLSD body = mModelData["asset_resources"]; dump_llsd_to_file(body, make_dump_name("whole_model_body_", dump_num)); @@ -3156,7 +3202,8 @@ void LLMeshUploadThread::requestWholeModelFee() generateHulls(); mModelData = LLSD::emptyMap(); - wholeModelToLLSD(mModelData, false); + mTextureFiles.clear(); + wholeModelToLLSD(mModelData, mTextureFiles, false); dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num)); LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, mHttpPolicyClass, @@ -3222,7 +3269,7 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp body["error"] = LLSD::emptyMap(); body["error"]["message"] = reason; body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py - log_upload_error(status, body, "upload", mModelData["name"].asString()); + log_upload_error(status, body, "upload", mModelData["name"].asString(), mTextureFiles); if (observer) { @@ -3257,7 +3304,7 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp else { LL_WARNS(LOG_MESH) << "Upload failed. Not in expected 'complete' state." << LL_ENDL; - log_upload_error(status, body, "upload", mModelData["name"].asString()); + log_upload_error(status, body, "upload", mModelData["name"].asString(), mTextureFiles); if (observer) { @@ -3282,7 +3329,7 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp body["error"] = LLSD::emptyMap(); body["error"]["message"] = reason; body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py - log_upload_error(status, body, "fee", mModelData["name"].asString()); + log_upload_error(status, body, "fee", mModelData["name"].asString(), mTextureFiles); if (observer) { @@ -3315,7 +3362,7 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp else { LL_WARNS(LOG_MESH) << "Fee request failed. Not in expected 'upload' state." << LL_ENDL; - log_upload_error(status, body, "fee", mModelData["name"].asString()); + log_upload_error(status, body, "fee", mModelData["name"].asString(), mTextureFiles); if (observer) { @@ -4989,14 +5036,15 @@ bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) const return iter != mMeshHeader.end(); } -void LLMeshRepository::uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, +void LLMeshRepository::uploadModel(std::vector& data, const std::map &lod_sources, + LLVector3& scale, bool upload_textures, bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - std::string upload_url, bool do_upload, + std::string upload_url, const LLUUID& destination_folder_id, bool do_upload, LLHandle fee_observer, LLHandle upload_observer) { - LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, + LLMeshUploadThread* thread = new LLMeshUploadThread(data, lod_sources, scale, upload_textures, upload_skin, upload_joints, lock_scale_if_joint_position, - upload_url, do_upload, fee_observer, upload_observer); + upload_url, destination_folder_id, do_upload, fee_observer, upload_observer); mUploadWaitList.push_back(thread); } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 4c3901408f8..ab17b921d6c 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -691,6 +691,8 @@ class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler }; typedef std::map, instance_list, LLUploadModelInstanceLess> instance_map; instance_map mInstance; + typedef std::map lod_sources_map_t; + lod_sources_map_t mLodSources; LLMutex* mMutex; S32 mPendingUploads; @@ -705,10 +707,14 @@ class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler LLHost mHost; std::string mWholeModelFeeCapability; std::string mWholeModelUploadURL; + LLUUID mDestinationFolderId; - LLMeshUploadThread(instance_list& data, LLVector3& scale, bool upload_textures, + LLMeshUploadThread(instance_list& data, const lod_sources_map_t& sources_list, + LLVector3& scale, bool upload_textures, bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - const std::string & upload_url, bool do_upload = true, + const std::string & upload_url, + const LLUUID destination_folder_id = LLUUID::null, + bool do_upload = true, LLHandle fee_observer = (LLHandle()), LLHandle upload_observer = (LLHandle())); ~LLMeshUploadThread(); @@ -724,7 +730,7 @@ class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler void doWholeModelUpload(); void requestWholeModelFee(); - void wholeModelToLLSD(LLSD& dest, bool include_textures); + void wholeModelToLLSD(LLSD& dest, std::vector& texture_list_dest, bool include_textures); void decomposeMeshMatrix(LLMatrix4& transformation, LLVector3& result_pos, @@ -745,6 +751,7 @@ class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler bool mDoUpload; // if false only model data will be requested, otherwise the model will be uploaded LLSD mModelData; + std::vector mTextureFiles; // llcorehttp library interface objects. LLCore::HttpStatus mHttpStatus; @@ -865,9 +872,12 @@ class LLMeshRepository bool meshUploadEnabled(); bool meshRezEnabled(); - void uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, + void uploadModel(std::vector& data, const std::map &lod_sources, + LLVector3& scale, bool upload_textures, bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - std::string upload_url, bool do_upload = true, + std::string upload_url, + const LLUUID& destination_folder_id = LLUUID::null, + bool do_upload = true, LLHandle fee_observer= (LLHandle()), LLHandle upload_observer = (LLHandle())); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index d68fc9c02dc..e0649e1d88b 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -2393,7 +2393,7 @@ void LLModelPreview::updateStatusMessages() if (lod != lod_high) { - if (total_submeshes[lod] && total_submeshes[lod] != total_submeshes[lod_high]) + if (total_submeshes[lod] && total_submeshes[lod] > total_submeshes[lod_high]) { //number of submeshes is different message = "mesh_status_submesh_mismatch"; upload_status[lod] = 2; @@ -3395,7 +3395,6 @@ bool LLModelPreview::render() fmp->setViewOptionEnabled("show_skin_weight", show_skin_weight); } } - //if (this) return TRUE; if (upload_skin && !has_skin_weights) { //can't upload skin weights if model has no skin weights diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index 2d51acc063d..f6d635f51f8 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -154,7 +154,8 @@ std::string LLMute::getDisplayType() const // LLMuteList() //----------------------------------------------------------------------------- LLMuteList::LLMuteList() : - mIsLoaded(false) + mLoadState(ML_INITIAL), + mRequestStartTime(0.f) { gGenericDispatcher.addHandler("emptymutelist", &sDispatchEmptyMuteList); @@ -209,6 +210,23 @@ bool LLMuteList::isLinden(const std::string& name) return last_name == "linden"; } +bool LLMuteList::getLoadFailed() const +{ + if (mLoadState == ML_FAILED) + { + return true; + } + if (mLoadState == ML_REQUESTED) + { + constexpr F64 WAIT_SECONDS = 30; + if (mRequestStartTime + WAIT_SECONDS < LLTimer::getTotalSeconds()) + { + return true; + } + } + return false; +} + static LLVOAvatar* find_avatar(const LLUUID& id) { LLViewerObject *obj = gObjectList.findObject(id); @@ -371,11 +389,14 @@ void LLMuteList::updateAdd(const LLMute& mute) msg->addU32("MuteFlags", mute.mFlags); gAgent.sendReliableMessage(); - if (!mIsLoaded) + if (!isLoaded()) { LL_WARNS() << "Added elements to non-initialized block list" << LL_ENDL; } - mIsLoaded = true; // why is this here? -MG + // Based of logs and testing, if file doesn't exist server side, + // viewer will not receive any callback and won't know to set + // ML_LOADED. As a workaround, set it regardless of current state. + mLoadState = ML_LOADED; } @@ -564,6 +585,7 @@ bool LLMuteList::loadFromFile(const std::string& filename) if(!filename.size()) { LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; + mLoadState = ML_FAILED; return false; } @@ -571,6 +593,7 @@ bool LLMuteList::loadFromFile(const std::string& filename) if (!fp) { LL_WARNS() << "Couldn't open mute list " << filename << LL_ENDL; + mLoadState = ML_FAILED; return false; } @@ -730,13 +753,17 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id) if (gDisconnected) { LL_WARNS() << "Trying to request mute list when disconnected!" << LL_ENDL; + mLoadState = ML_FAILED; return; } if (!gAgent.getRegion()) { LL_WARNS() << "No region for agent yet, skipping mute list request!" << LL_ENDL; + mLoadState = ML_FAILED; return; } + mLoadState = ML_REQUESTED; + mRequestStartTime = LLTimer::getElapsedSeconds(); // Double amount of retries due to this request happening during busy stage // Ideally this should be turned into a capability gMessageSystem->sendReliable(gAgent.getRegionHost(), LL_DEFAULT_RELIABLE_RETRIES * 2, true, LL_PING_BASED_TIMEOUT_DUMMY, NULL, NULL); @@ -749,7 +776,7 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id) void LLMuteList::cache(const LLUUID& agent_id) { // Write to disk even if empty. - if(mIsLoaded) + if(isLoaded()) { std::string agent_id_string; std::string filename; @@ -777,6 +804,13 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) msg->getStringFast(_PREHASH_MuteData, _PREHASH_Filename, unclean_filename); std::string filename = LLDir::getScrubbedFileName(unclean_filename); + LLMuteList* mute_list = getInstance(); + mute_list->mLoadState = ML_REQUESTED; + mute_list->mRequestStartTime = LLTimer::getElapsedSeconds(); + + // Todo: Based of logs and testing, there is no callback + // from server if file doesn't exist server side. + // Once server side gets fixed make sure it gets handled right. std::string *local_filename_and_path = new std::string(gDirUtilp->getExpandedFilename( LL_PATH_CACHE, filename )); gXferManager->requestFile(*local_filename_and_path, filename, @@ -809,12 +843,16 @@ void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_ LLMuteList::getInstance()->loadFromFile(*local_filename_and_path); LLFile::remove(*local_filename_and_path); } + else + { + LLMuteList::getInstance()->mLoadState = ML_FAILED; + } delete local_filename_and_path; } void LLMuteList::onAccountNameChanged(const LLUUID& id, const std::string& username) { - if (mIsLoaded) + if (isLoaded()) { LLMute mute(id, username, LLMute::AGENT); mute_set_t::iterator mute_it = mMutes.find(mute); @@ -866,7 +904,7 @@ void LLMuteList::removeObserver(LLMuteListObserver* observer) void LLMuteList::setLoaded() { - mIsLoaded = true; + mLoadState = ML_LOADED; notifyObservers(); } diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index 13d579c61f1..b65fd61fccf 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -74,6 +74,14 @@ class LLMuteList : public LLSingleton LLSINGLETON(LLMuteList); ~LLMuteList(); /*virtual*/ void cleanupSingleton() override; + + enum EMuteListState + { + ML_INITIAL, + ML_REQUESTED, + ML_LOADED, + ML_FAILED, + }; public: // reasons for auto-unmuting a resident enum EAutoReason @@ -107,7 +115,8 @@ class LLMuteList : public LLSingleton static bool isLinden(const std::string& name); - bool isLoaded() const { return mIsLoaded; } + bool isLoaded() const { return mLoadState == ML_LOADED; } + bool getLoadFailed() const; std::vector getMutes() const; @@ -167,7 +176,8 @@ class LLMuteList : public LLSingleton typedef std::set observer_set_t; observer_set_t mObservers; - bool mIsLoaded; + EMuteListState mLoadState; + F64 mRequestStartTime; friend class LLDispatchEmptyMuteList; }; diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp index 98b7d74cd22..3adee9fa165 100644 --- a/indra/newview/lloutfitgallery.cpp +++ b/indra/newview/lloutfitgallery.cpp @@ -87,9 +87,16 @@ LLOutfitGallery::LLOutfitGallery(const LLOutfitGallery::Params& p) mItemsInRow(p.items_in_row), mRowPanWidthFactor(p.row_panel_width_factor), mGalleryWidthFactor(p.gallery_width_factor), - mTextureSelected(NULL) + mTextureSelected(NULL), + mSortMenu(nullptr) { updateGalleryWidth(); + + LLControlVariable* ctrl = gSavedSettings.getControl("InventoryFavoritesColorText"); + if (ctrl) + { + mSavedSettingInvFavColor = ctrl->getSignal()->connect(boost::bind(&LLOutfitGallery::handleInvFavColorChange, this)); + } } LLOutfitGallery::Params::Params() @@ -420,19 +427,32 @@ void LLOutfitGallery::updateRowsIfNeeded() bool compareGalleryItem(LLOutfitGalleryItem* item1, LLOutfitGalleryItem* item2) { - static LLCachedControl outfit_gallery_sort_by_name(gSavedSettings, "OutfitGallerySortByName"); - if(outfit_gallery_sort_by_name || - ((item1->isDefaultImage() && item2->isDefaultImage()) || (!item1->isDefaultImage() && !item2->isDefaultImage()))) - { - std::string name1 = item1->getItemName(); - std::string name2 = item2->getItemName(); - - return (LLStringUtil::compareDict(name1, name2) < 0); - } - else + static LLCachedControl sort_by_name(gSavedSettings, "OutfitGallerySortOrder", 0); + switch (sort_by_name()) { - return item2->isDefaultImage(); + case 2: + // Sort by favorites - favorite items first, then alphabetically + if (item1->isFavorite() != item2->isFavorite()) + { + return item1->isFavorite(); + } + break; + case 1: + // Sort by images - items with non-default images first, then alphabetically + if (item1->isDefaultImage() != item2->isDefaultImage()) + { + return item2->isDefaultImage(); + } + break; + default: + // Sort alphabetically only + break; } + + // Final comparison is always alphabetical by name + std::string name1 = item1->getItemName(); + std::string name2 = item2->getItemName(); + return (LLStringUtil::compareDict(name1, name2) < 0); } void LLOutfitGallery::reArrangeRows(S32 row_diff) @@ -475,6 +495,20 @@ void LLOutfitGallery::updateGalleryWidth() mGalleryWidth = mGalleryWidthFactor * mItemsInRow - mItemHorizontalGap; } +void LLOutfitGallery::handleInvFavColorChange() +{ + for (outfit_map_t::iterator iter = mOutfitMap.begin(); + iter != mOutfitMap.end(); + ++iter) + { + if (!iter->second) continue; + LLOutfitGalleryItem* item = (LLOutfitGalleryItem*)iter->second; + + // refresh font color + item->setOutfitFavorite(item->isFavorite()); + } +} + LLPanel* LLOutfitGallery::addLastRow() { mRowCount++; @@ -626,7 +660,7 @@ void LLOutfitGallery::removeFromLastRow(LLOutfitGalleryItem* item) mItemPanels.pop_back(); } -LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID outfit_id) +LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID outfit_id, bool is_favorite) { LLOutfitGalleryItem::Params giparams; LLOutfitGalleryItem* gitem = LLUICtrlFactory::create(giparams); @@ -635,6 +669,7 @@ LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID gitem->setFollowsLeft(); gitem->setFollowsTop(); gitem->setOutfitName(name); + gitem->setOutfitFavorite(is_favorite); gitem->setUUID(outfit_id); gitem->setGallery(this); return gitem; @@ -797,8 +832,7 @@ void LLOutfitGallery::updateAddedCategory(LLUUID cat_id) return; } - std::string name = cat->getName(); - LLOutfitGalleryItem* item = buildGalleryItem(name, cat_id); + LLOutfitGalleryItem* item = buildGalleryItem(cat->getName(), cat_id, cat->getIsFavorite()); mOutfitMap.insert(LLOutfitGallery::outfit_map_value_t(cat_id, item)); item->setRightMouseDownCallback(boost::bind(&LLOutfitListBase::outfitRightClickCallBack, this, _1, _2, _3, cat_id)); @@ -861,6 +895,7 @@ void LLOutfitGallery::updateChangedCategoryName(LLViewerInventoryCategory *cat, if (item) { item->setOutfitName(name); + item->setOutfitFavorite(cat->getIsFavorite()); } } } @@ -941,6 +976,10 @@ LLOutfitListGearMenuBase* LLOutfitGallery::createGearMenu() static LLDefaultChildRegistry::Register r("outfit_gallery_item"); +bool LLOutfitGalleryItem::sColorSetInitialized = false; +LLUIColor LLOutfitGalleryItem::sDefaultTextColor; +LLUIColor LLOutfitGalleryItem::sDefaultFavoriteColor; + LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p) : LLPanel(p), mGallery(nullptr), @@ -952,6 +991,12 @@ LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p) mUUID(LLUUID()) { buildFromFile("panel_outfit_gallery_item.xml"); + if (!sColorSetInitialized) + { + sDefaultTextColor = LLUIColorTable::instance().getColor("White", LLColor4::white); + sDefaultFavoriteColor = LLUIColorTable::instance().getColor("InventoryFavoriteColor", LLColor4::white); + sColorSetInitialized = true; + } } LLOutfitGalleryItem::~LLOutfitGalleryItem() @@ -998,6 +1043,19 @@ void LLOutfitGalleryItem::draw() gl_draw_scaled_image(interior.mLeft - 1, interior.mBottom, interior.getWidth(), interior.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); } + static LLUICachedControl draw_star("InventoryFavoritesUseStar", true); + if(mFavorite && draw_star()) + { + const S32 HPAD = 3; + const S32 VPAD = 6; // includes padding for text and for the image + const S32 image_size = 14; + static LLPointer fav_img = LLRender2D::getInstance()->getUIImage("Inv_Favorite_Star_Full"); + + const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + gl_draw_scaled_image( + border.getWidth() - image_size - HPAD, image_size + VPAD + mOutfitNameText->getRect().getHeight(), + image_size, image_size, fav_img->getImage(), UI_VERTEX_COLOR % alpha); + } } void LLOutfitGalleryItem::setOutfitName(std::string name) @@ -1007,18 +1065,28 @@ void LLOutfitGalleryItem::setOutfitName(std::string name) mOutfitName = name; } +void LLOutfitGalleryItem::setOutfitFavorite(bool is_favorite) +{ + mFavorite = is_favorite; + + static LLCachedControl use_color(gSavedSettings, "InventoryFavoritesColorText"); + mOutfitNameText->setReadOnlyColor((mFavorite && use_color()) ? sDefaultFavoriteColor : sDefaultTextColor); +} + void LLOutfitGalleryItem::setOutfitWorn(bool value) { mWorn = value; LLStringUtil::format_map_t worn_string_args; std::string worn_string = getString("worn_string", worn_string_args); - LLUIColor text_color = LLUIColorTable::instance().getColor("White", LLColor4::white); - mOutfitWornText->setReadOnlyColor(text_color); - mOutfitNameText->setReadOnlyColor(text_color); + mOutfitWornText->setReadOnlyColor(sDefaultTextColor.get()); + mOutfitNameText->setReadOnlyColor(sDefaultTextColor.get()); mOutfitWornText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); mOutfitNameText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); mOutfitWornText->setValue(value ? worn_string : ""); mOutfitNameText->setText(mOutfitName); // refresh LLTextViewModel to pick up font changes + + static LLCachedControl use_color(gSavedSettings, "InventoryFavoritesColorText"); + mOutfitNameText->setReadOnlyColor((mFavorite && use_color()) ? sDefaultFavoriteColor : sDefaultTextColor); } void LLOutfitGalleryItem::setSelected(bool value) @@ -1170,6 +1238,7 @@ LLContextMenu* LLOutfitGalleryContextMenu::createMenu() registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id)); registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2)); registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitGalleryContextMenu::onThumbnail, this, selected_id)); + registrar.add("Outfit.Favorite", boost::bind(&LLOutfitGalleryContextMenu::onFavorite, this, selected_id)); registrar.add("Outfit.Save", boost::bind(&LLOutfitGalleryContextMenu::onSave, this, selected_id)); enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitGalleryContextMenu::onEnable, this, _2)); enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitGalleryContextMenu::onVisible, this, _2)); @@ -1208,25 +1277,12 @@ void LLOutfitGalleryGearMenu::onUpdateItemsVisibility() { if (!mMenu) return; bool have_selection = getSelectedOutfitID().notNull(); - mMenu->setItemVisible("expand", false); - mMenu->setItemVisible("collapse", false); mMenu->setItemVisible("thumbnail", have_selection); mMenu->setItemVisible("sepatator3", true); mMenu->setItemVisible("sort_folders_by_name", true); LLOutfitListGearMenuBase::onUpdateItemsVisibility(); } -void LLOutfitGalleryGearMenu::onChangeSortOrder() -{ - bool sort_by_name = !gSavedSettings.getBOOL("OutfitGallerySortByName"); - gSavedSettings.setBOOL("OutfitGallerySortByName", sort_by_name); - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - if (gallery) - { - gallery->reArrangeRows(); - } -} - bool LLOutfitGalleryGearMenu::hasDefaultImage() { LLOutfitGallery* gallery = dynamic_cast(mOutfitList); @@ -1343,6 +1399,15 @@ void LLOutfitGallery::refreshOutfit(const LLUUID& category_id) } } +LLToggleableMenu* LLOutfitGallery::getSortMenu() +{ + if (!mSortMenu) + { + mSortMenu = new LLOutfitGallerySortMenu(this); + } + return mSortMenu->getMenu(); +} + LLUUID LLOutfitGallery::getPhotoAssetId(const LLUUID& outfit_id) { outfit_map_t::iterator outfit_it = mOutfitMap.find(outfit_id); @@ -1358,3 +1423,84 @@ LLUUID LLOutfitGallery::getDefaultPhoto() return LLUUID(); } + +//////////////////// LLOutfitGallerySortMenu //////////////////// + +LLOutfitGallerySortMenu::LLOutfitGallerySortMenu(LLOutfitListBase* parent_panel) + : mPanelHandle(parent_panel->getHandle()) +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add("Sort.OnSort", boost::bind(&LLOutfitGallerySortMenu::onSort, this, _2)); + enable_registrar.add("Sort.OnEnable", boost::bind(&LLOutfitGallerySortMenu::onEnable, this, _2)); + + mMenu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_outfit_gallery_sort.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + llassert(mMenu); +} + + +LLToggleableMenu* LLOutfitGallerySortMenu::getMenu() +{ + return mMenu; +} + +void LLOutfitGallerySortMenu::updateItemsVisibility() +{ + onUpdateItemsVisibility(); +} + +void LLOutfitGallerySortMenu::onUpdateItemsVisibility() +{ + if (!mMenu) return; +} + +bool LLOutfitGallerySortMenu::onEnable(LLSD::String param) +{ + static LLCachedControl sort_order(gSavedSettings, "OutfitGallerySortOrder", 0); + if ("favorites_to_top" == param) + { + return sort_order == 2; + } + else if ("images_to_top" == param) + { + return sort_order == 1; + } + else if ("by_name" == param) + { + return sort_order == 0; + } + + return false; +} + +void LLOutfitGallerySortMenu::onSort(LLSD::String param) +{ + S32 sort_order = gSavedSettings.getS32("OutfitGallerySortOrder"); + S32 new_sort_order = 0; + if ("favorites_to_top" == param) + { + new_sort_order = 2; + } + else if ("images_to_top" == param) + { + new_sort_order = 1; + } + else if ("by_name" == param) + { + new_sort_order = 0; + } + if (sort_order == new_sort_order) + { + new_sort_order = sort_order ? 0 : 1; + } + gSavedSettings.setS32("OutfitGallerySortOrder", new_sort_order); + + LLOutfitGallery* gallery = dynamic_cast(mPanelHandle.get()); + if (gallery) + { + gallery->reArrangeRows(); + } +} + diff --git a/indra/newview/lloutfitgallery.h b/indra/newview/lloutfitgallery.h index a2a5fe26eb0..5801a32a39f 100644 --- a/indra/newview/lloutfitgallery.h +++ b/indra/newview/lloutfitgallery.h @@ -42,11 +42,13 @@ class LLOutfitGalleryItem; class LLOutfitListGearMenuBase; class LLOutfitGalleryGearMenu; class LLOutfitGalleryContextMenu; +class LLOutfitGallerySortMenu; class LLOutfitGallery : public LLOutfitListBase { public: friend class LLOutfitGalleryGearMenu; + friend class LLOutfitGallerySortMenu; friend class LLOutfitGalleryContextMenu; friend class LLUpdateGalleryOnPhotoLinked; @@ -102,10 +104,12 @@ class LLOutfitGallery : public LLOutfitListBase /*virtual*/ bool getHasExpandableFolders() { return false; } + /*virtual*/ void onChangeSortOrder(const LLSD& userdata) {}; void updateMessageVisibility(); bool hasDefaultImage(const LLUUID& outfit_cat_id); void refreshOutfit(const LLUUID& category_id); + virtual LLToggleableMenu* getSortMenu(); protected: /*virtual*/ void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id); @@ -133,8 +137,9 @@ class LLOutfitGallery : public LLOutfitListBase void reArrangeRows(S32 row_diff = 0); void updateRowsIfNeeded(); void updateGalleryWidth(); + void handleInvFavColorChange(); - LLOutfitGalleryItem* buildGalleryItem(std::string name, LLUUID outfit_id); + LLOutfitGalleryItem* buildGalleryItem(std::string name, LLUUID outfit_id, bool is_favorite); LLOutfitGalleryItem* getSelectedItem() const; LLOutfitGalleryItem* getItem(const LLUUID& id) const; @@ -176,6 +181,7 @@ class LLOutfitGallery : public LLOutfitListBase int mGalleryWidthFactor; LLListContextMenu* mOutfitGalleryMenu; + LLOutfitGallerySortMenu* mSortMenu; typedef std::map outfit_map_t; typedef outfit_map_t::value_type outfit_map_value_t; @@ -184,6 +190,11 @@ class LLOutfitGallery : public LLOutfitListBase typedef item_num_map_t::value_type item_numb_map_value_t; item_num_map_t mItemIndexMap; std::map mIndexToItemMap; + + + LLInventoryCategoriesObserver* mOutfitsObserver; + + boost::signals2::connection mSavedSettingInvFavColor; }; class LLOutfitGalleryContextMenu : public LLOutfitContextMenu { @@ -210,8 +221,6 @@ class LLOutfitGalleryGearMenu : public LLOutfitListGearMenuBase protected: /*virtual*/ void onUpdateItemsVisibility(); private: - /*virtual*/ void onChangeSortOrder(); - bool hasDefaultImage(); }; @@ -240,6 +249,7 @@ class LLOutfitGalleryItem : public LLPanel bool setImageAssetId(LLUUID asset_id); LLUUID getImageAssetId(); void setOutfitName(std::string name); + void setOutfitFavorite(bool is_favorite); void setOutfitWorn(bool value); void setSelected(bool value); void setUUID(const LLUUID &outfit_id) {mUUID = outfit_id;} @@ -247,6 +257,7 @@ class LLOutfitGalleryItem : public LLPanel std::string getItemName() {return mOutfitName;} bool isDefaultImage() {return mDefaultImage;} + bool isFavorite() { return mFavorite; } bool isHidden() {return mHidden;} void setHidden(bool hidden) {mHidden = hidden;} @@ -264,7 +275,29 @@ class LLOutfitGalleryItem : public LLPanel bool mWorn; bool mDefaultImage; bool mHidden; + bool mFavorite; std::string mOutfitName; + + static bool sColorSetInitialized; + static LLUIColor sDefaultTextColor; + static LLUIColor sDefaultFavoriteColor; +}; + +class LLOutfitGallerySortMenu +{ +public: + LLOutfitGallerySortMenu(LLOutfitListBase* parent_panel); + + LLToggleableMenu* getMenu(); + void updateItemsVisibility(); + +private: + void onUpdateItemsVisibility(); + bool onEnable(LLSD::String param); + void onSort(LLSD::String param); + + LLToggleableMenu* mMenu; + LLHandle mPanelHandle; }; #endif // LL_LLOUTFITGALLERYCTRL_H diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index df53c66ec15..4e594af432d 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -34,10 +34,13 @@ #include "llaccordionctrl.h" #include "llaccordionctrltab.h" #include "llagentwearables.h" +#include "llaisapi.h" #include "llappearancemgr.h" +#include "llappviewer.h" #include "llfloaterreg.h" #include "llfloatersidepanelcontainer.h" #include "llinspecttexture.h" +#include "llinventorymodelbackgroundfetch.h" #include "llinventoryfunctions.h" #include "llinventorymodel.h" #include "llmenubutton.h" @@ -45,6 +48,7 @@ #include "lloutfitobserver.h" #include "lltoggleablemenu.h" #include "lltransutil.h" +#include "llviewercontrol.h" #include "llviewermenu.h" #include "llvoavatar.h" #include "llvoavatarself.h" @@ -53,14 +57,24 @@ static bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y); static const LLOutfitTabNameComparator OUTFIT_TAB_NAME_COMPARATOR; +static const LLOutfitTabFavComparator OUTFIT_TAB_FAV_COMPARATOR; /*virtual*/ bool LLOutfitTabNameComparator::compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const { - std::string name1 = tab1->getTitle(); - std::string name2 = tab2->getTitle(); + return (LLStringUtil::compareDict(tab1->getTitle(), tab2->getTitle()) < 0); +} + +bool LLOutfitTabFavComparator::compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const +{ + LLOutfitAccordionCtrlTab* taba = (LLOutfitAccordionCtrlTab*)tab1; + LLOutfitAccordionCtrlTab* tabb = (LLOutfitAccordionCtrlTab*)tab2; + if (taba->getFavorite() != tabb->getFavorite()) + { + return taba->getFavorite(); + } - return (LLStringUtil::compareDict(name1, name2) < 0); + return (LLStringUtil::compareDict(tab1->getTitle(), tab2->getTitle()) < 0); } struct outfit_accordion_tab_params : public LLInitParam::Block @@ -80,6 +94,9 @@ const outfit_accordion_tab_params& get_accordion_tab_params() { initialized = true; + LLOutfitAccordionCtrlTab::sFavoriteIcon = LLUI::getUIImage("Inv_Favorite_Star_Full"); + LLOutfitAccordionCtrlTab::sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", LLColor4U(255, 255, 255)); + LLXMLNodePtr xmlNode; if (LLUICtrlFactory::getLayeredXMLNode("outfit_accordion_tab.xml", xmlNode)) { @@ -103,11 +120,20 @@ LLOutfitsList::LLOutfitsList() , mAccordion(NULL) , mListCommands(NULL) , mItemSelected(false) + , mSortMenu(nullptr) { + LLControlVariable* ctrl = gSavedSettings.getControl("InventoryFavoritesColorText"); + if (ctrl) + { + mSavedSettingInvFavColor = ctrl->getSignal()->connect(boost::bind(&LLOutfitsList::handleInvFavColorChange, this)); + } } LLOutfitsList::~LLOutfitsList() { + delete mSortMenu; + mSavedSettingInvFavColor.disconnect(); + mGearMenuConnection.disconnect(); } bool LLOutfitsList::postBuild() @@ -115,9 +141,25 @@ bool LLOutfitsList::postBuild() mAccordion = getChild("outfits_accordion"); mAccordion->setComparator(&OUTFIT_TAB_NAME_COMPARATOR); + initComparator(); + return LLOutfitListBase::postBuild(); } +void LLOutfitsList::initComparator() +{ + S32 mode = gSavedSettings.getS32("OutfitListSortOrder"); + if (mode == 0) + { + mAccordion->setComparator(&OUTFIT_TAB_NAME_COMPARATOR); + } + else + { + mAccordion->setComparator(&OUTFIT_TAB_FAV_COMPARATOR); + } + sortOutfits(); +} + //virtual void LLOutfitsList::onOpen(const LLSD& info) { @@ -165,6 +207,7 @@ void LLOutfitsList::updateAddedCategory(LLUUID cat_id) tab->setName(name); tab->setTitle(name); + tab->setFavorite(cat->getIsFavorite()); // *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated. tab->setDisplayChildren(false); @@ -194,8 +237,9 @@ void LLOutfitsList::updateAddedCategory(LLUUID cat_id) // Setting callback to reset items selection inside outfit on accordion collapsing and expanding (EXT-7875) tab->setDropDownStateChangedCallback(boost::bind(&LLOutfitsList::resetItemSelection, this, list, cat_id)); - // force showing list items that don't match current filter(EXT-7158) - list->setForceShowingUnmatchedItems(true); + // Depending on settings, force showing list items that don't match current filter(EXT-7158) + static LLCachedControl list_filter(gSavedSettings, "OutfitListFilterFullList"); + list->setForceShowingUnmatchedItems(list_filter(), false); // Setting list commit callback to monitor currently selected wearable item. list->setCommitCallback(boost::bind(&LLOutfitsList::onListSelectionChange, this, _1)); @@ -205,12 +249,27 @@ void LLOutfitsList::updateAddedCategory(LLUUID cat_id) list->setRightMouseDownCallback(boost::bind(&LLOutfitsList::onWearableItemsListRightClick, this, _1, _2, _3)); - // Fetch the new outfit contents. - cat->fetch(); - - // Refresh the list of outfit items after fetch(). - // Further list updates will be triggered by the category observer. - list->updateList(cat_id); + if (AISAPI::isAvailable() && LLInventoryModelBackgroundFetch::instance().folderFetchActive()) + { + // for reliability just fetch it whole, linked items included + LLInventoryModelBackgroundFetch::instance().fetchFolderAndLinks(cat_id, [cat_id, list] + { + if (list) + { + list->updateList(cat_id); + list->setForceRefresh(true); + } + }); + } + else + { + // Fetch the new outfit contents. + cat->fetch(); + // Refresh the list of outfit items after fetch(). + // Further list updates will be triggered by the category observer. + list->updateList(cat_id); + list->setForceRefresh(true); + } // If filter is currently applied we store the initial tab state. if (!getFilterSubString().empty()) @@ -260,13 +319,11 @@ void LLOutfitsList::onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id) { if (mOutfitsMap[prev_id]) { - mOutfitsMap[prev_id]->setTitleFontStyle("NORMAL"); - mOutfitsMap[prev_id]->setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor")); + ((LLOutfitAccordionCtrlTab*)mOutfitsMap[prev_id])->setOutfitSelected(false); } if (mOutfitsMap[base_id]) { - mOutfitsMap[base_id]->setTitleFontStyle("BOLD"); - mOutfitsMap[base_id]->setTitleColor(LLUIColorTable::instance().getColor("SelectedOutfitTextColor")); + ((LLOutfitAccordionCtrlTab*)mOutfitsMap[base_id])->setOutfitSelected(true); } } @@ -324,6 +381,11 @@ void LLOutfitsList::onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid) } } +void LLOutfitListBase::onAction(const LLSD& userdata) +{ + performAction(userdata.asString()); +} + // virtual bool LLOutfitListBase::isActionEnabled(const LLSD& userdata) { @@ -436,11 +498,12 @@ void LLOutfitsList::updateChangedCategoryName(LLViewerInventoryCategory *cat, st if (outfits_iter != mOutfitsMap.end()) { // Update tab name with the new category name. - LLAccordionCtrlTab* tab = outfits_iter->second; + LLOutfitAccordionCtrlTab* tab = (LLOutfitAccordionCtrlTab*) outfits_iter->second; if (tab) { tab->setName(name); tab->setTitle(name); + tab->setFavorite(cat->getIsFavorite()); } } } @@ -749,6 +812,75 @@ void LLOutfitsList::onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUI } } +void LLOutfitsList::handleInvFavColorChange() +{ + for (outfits_map_t::iterator iter = mOutfitsMap.begin(); + iter != mOutfitsMap.end(); + ++iter) + { + if (!iter->second) continue; + LLOutfitAccordionCtrlTab* tab = (LLOutfitAccordionCtrlTab*)iter->second; + + // refresh font color + tab->setFavorite(tab->getFavorite()); + } +} + +void LLOutfitsList::onChangeSortOrder(const LLSD& userdata) +{ + std::string sort_data = userdata.asString(); + if (sort_data == "favorites_to_top") + { + // at the moment this is a toggle + S32 val = gSavedSettings.getS32("OutfitListSortOrder"); + gSavedSettings.setS32("OutfitListSortOrder", (val ? 0 : 1)); + + initComparator(); + } + else if (sort_data == "show_entire_outfit") + { + bool new_val = !gSavedSettings.getBOOL("OutfitListFilterFullList"); + gSavedSettings.setBOOL("OutfitListFilterFullList", new_val); + + if (!getFilterSubString().empty()) + { + for (outfits_map_t::value_type& outfit : mOutfitsMap) + { + LLAccordionCtrlTab* tab = outfit.second; + const LLUUID& category_id = outfit.first; + if (!tab) continue; + + LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); + if (list) + { + list->setForceRefresh(true); + list->setForceShowingUnmatchedItems(new_val, tab->getDisplayChildren()); + } + applyFilterToTab(category_id, tab, getFilterSubString()); + } + mAccordion->arrange(); + } + } +} + +LLToggleableMenu* LLOutfitsList::getSortMenu() +{ + if (!mSortMenu) + { + mSortMenu = new LLOutfitListSortMenu(this); + } + return mSortMenu->getMenu(); +} + +void LLOutfitsList::updateMenuItemsVisibility() +{ + if (mSortMenu) + { + mSortMenu->updateItemsVisibility(); + } + LLOutfitListBase::updateMenuItemsVisibility(); +} + LLOutfitListGearMenuBase* LLOutfitsList::createGearMenu() { return new LLOutfitListGearMenu(this); @@ -766,10 +898,10 @@ bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y) LLOutfitListBase::LLOutfitListBase() : LLPanelAppearanceTab() , mIsInitialized(false) + , mGearMenu(nullptr) { mCategoriesObserver = new LLInventoryCategoriesObserver(); mOutfitMenu = new LLOutfitContextMenu(this); - //mGearMenu = createGearMenu(); } LLOutfitListBase::~LLOutfitListBase() @@ -865,6 +997,10 @@ bool LLOutfitListBase::isOutfitFolder(LLViewerInventoryCategory* cat) const void LLOutfitListBase::refreshList(const LLUUID& category_id) { + if (LLAppViewer::instance()->quitRequested()) + { + return; + } bool wasNull = mRefreshListState.CategoryUUID.isNull(); mRefreshListState.CategoryUUID.setNull(); @@ -923,8 +1059,18 @@ void LLOutfitListBase::onIdle(void* userdata) void LLOutfitListBase::onIdleRefreshList() { + if (LLAppViewer::instance()->quitRequested()) + { + mRefreshListState.CategoryUUID.setNull(); + gIdleCallbacks.deleteFunction(onIdle, this); + return; + } if (mRefreshListState.CategoryUUID.isNull()) + { + LL_WARNS() << "Called onIdleRefreshList without id" << LL_ENDL; + gIdleCallbacks.deleteFunction(onIdle, this); return; + } const F64 MAX_TIME = 0.05f; F64 curent_time = LLTimer::getTotalSeconds(); @@ -1066,12 +1212,6 @@ void LLOutfitListBase::ChangeOutfitSelection(LLWearableItemsList* list, const LL bool LLOutfitListBase::postBuild() { - mGearMenu = createGearMenu(); - - LLMenuButton* menu_gear_btn = getChild("options_gear_btn"); - - menu_gear_btn->setMouseDownCallback(boost::bind(&LLOutfitListGearMenuBase::updateItemsVisibility, mGearMenu)); - menu_gear_btn->setMenu(mGearMenu->getMenu()); return true; } @@ -1085,6 +1225,20 @@ void LLOutfitListBase::expandAllFolders() onExpandAllFolders(); } +void LLOutfitListBase::updateMenuItemsVisibility() +{ + mGearMenu->updateItemsVisibility(); +} + +LLToggleableMenu* LLOutfitListBase::getGearMenu() +{ + if (!mGearMenu) + { + mGearMenu = createGearMenu(); + } + return mGearMenu->getMenu(); +}; + void LLOutfitListBase::deselectOutfit(const LLUUID& category_id) { // Reset selection if the outfit is selected. @@ -1111,6 +1265,7 @@ LLContextMenu* LLOutfitContextMenu::createMenu() registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); registrar.add("Outfit.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList)); registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitContextMenu::onThumbnail, this, selected_id)); + registrar.add("Outfit.Favorite", boost::bind(&LLOutfitContextMenu::onFavorite, this, selected_id)); registrar.add("Outfit.Save", boost::bind(&LLOutfitContextMenu::onSave, this, selected_id)); enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitContextMenu::onEnable, this, _2)); @@ -1161,6 +1316,16 @@ bool LLOutfitContextMenu::onVisible(LLSD::String param) { return LLAppearanceMgr::instance().getCanRemoveOutfit(outfit_cat_id); } + else if ("favorites_add" == param) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_cat_id); + return cat && !cat->getIsFavorite(); + } + else if ("favorites_remove" == param) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_cat_id); + return cat && cat->getIsFavorite(); + } return true; } @@ -1185,6 +1350,14 @@ void LLOutfitContextMenu::onThumbnail(const LLUUID &outfit_cat_id) } } +void LLOutfitContextMenu::onFavorite(const LLUUID& outfit_cat_id) +{ + if (outfit_cat_id.notNull()) + { + toggle_favorite(outfit_cat_id); + } +} + void LLOutfitContextMenu::onSave(const LLUUID &outfit_cat_id) { if (outfit_cat_id.notNull()) @@ -1215,14 +1388,13 @@ LLOutfitListGearMenuBase::LLOutfitListGearMenuBase(LLOutfitListBase* olist) registrar.add("Gear.Rename", boost::bind(&LLOutfitListGearMenuBase::onRename, this)); registrar.add("Gear.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList)); registrar.add("Gear.Create", boost::bind(&LLOutfitListGearMenuBase::onCreate, this, _2)); - registrar.add("Gear.Collapse", boost::bind(&LLOutfitListBase::onCollapseAllFolders, mOutfitList)); - registrar.add("Gear.Expand", boost::bind(&LLOutfitListBase::onExpandAllFolders, mOutfitList)); registrar.add("Gear.WearAdd", boost::bind(&LLOutfitListGearMenuBase::onAdd, this)); registrar.add("Gear.Save", boost::bind(&LLOutfitListGearMenuBase::onSave, this)); registrar.add("Gear.Thumbnail", boost::bind(&LLOutfitListGearMenuBase::onThumbnail, this)); - registrar.add("Gear.SortByName", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this)); + registrar.add("Gear.Favorite", boost::bind(&LLOutfitListGearMenuBase::onFavorite, this)); + registrar.add("Gear.SortByImage", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this)); enable_registrar.add("Gear.OnEnable", boost::bind(&LLOutfitListGearMenuBase::onEnable, this, _2)); enable_registrar.add("Gear.OnVisible", boost::bind(&LLOutfitListGearMenuBase::onVisible, this, _2)); @@ -1356,6 +1528,16 @@ bool LLOutfitListGearMenuBase::onVisible(LLSD::String param) { return false; } + else if ("favorites_add" == param) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(selected_outfit_id); + return cat && !cat->getIsFavorite(); + } + else if ("favorites_remove" == param) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(selected_outfit_id); + return cat && cat->getIsFavorite(); + } return true; } @@ -1367,6 +1549,12 @@ void LLOutfitListGearMenuBase::onThumbnail() LLFloaterReg::showInstance("change_item_thumbnail", data); } +void LLOutfitListGearMenuBase::onFavorite() +{ + const LLUUID& selected_outfit_id = getSelectedOutfitID(); + toggle_favorite(selected_outfit_id); +} + void LLOutfitListGearMenuBase::onChangeSortOrder() { @@ -1382,14 +1570,79 @@ LLOutfitListGearMenu::~LLOutfitListGearMenu() void LLOutfitListGearMenu::onUpdateItemsVisibility() { if (!mMenu) return; - mMenu->setItemVisible("expand", true); - mMenu->setItemVisible("collapse", true); mMenu->setItemVisible("thumbnail", getSelectedOutfitID().notNull()); + mMenu->setItemVisible("favorite", getSelectedOutfitID().notNull()); mMenu->setItemVisible("sepatator3", false); mMenu->setItemVisible("sort_folders_by_name", false); LLOutfitListGearMenuBase::onUpdateItemsVisibility(); } +//////////////////// LLOutfitListSortMenu //////////////////// + +LLOutfitListSortMenu::LLOutfitListSortMenu(LLOutfitListBase* parent_panel) + : mPanelHandle(parent_panel->getHandle()) +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add("Sort.Collapse", boost::bind(&LLOutfitListBase::onCollapseAllFolders, parent_panel)); + registrar.add("Sort.Expand", boost::bind(&LLOutfitListBase::onExpandAllFolders, parent_panel)); + registrar.add("Sort.OnSort", boost::bind(&LLOutfitListBase::onChangeSortOrder, parent_panel, _2)); + enable_registrar.add("Sort.OnEnable", boost::bind(&LLOutfitListSortMenu::onEnable, this, _2)); + + mMenu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_outfit_list_sort.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + llassert(mMenu); +} + + +LLToggleableMenu* LLOutfitListSortMenu::getMenu() +{ + return mMenu; +} + +void LLOutfitListSortMenu::updateItemsVisibility() +{ + onUpdateItemsVisibility(); +} + +void LLOutfitListSortMenu::onUpdateItemsVisibility() +{ + if (!mMenu) return; + mMenu->setItemVisible("expand", true); + mMenu->setItemVisible("collapse", true); + mMenu->setItemVisible("sort_favorites_to_top", true); + mMenu->setItemVisible("show_entire_outfit_in_search", true); +} + +bool LLOutfitListSortMenu::onEnable(LLSD::String param) +{ + if ("favorites_to_top" == param) + { + static LLCachedControl sort_order(gSavedSettings, "OutfitListSortOrder", 0); + return sort_order == 1; + } + else if ("show_entire_outfit" == param) + { + static LLCachedControl filter_mode(gSavedSettings, "OutfitListFilterFullList", 0); + return filter_mode; + } + + return false; +} + + +//////////////////// LLOutfitAccordionCtrlTab //////////////////// + +LLUIImage* LLOutfitAccordionCtrlTab::sFavoriteIcon; +LLUIColor LLOutfitAccordionCtrlTab::sFgColor; + +void LLOutfitAccordionCtrlTab::draw() +{ + LLAccordionCtrlTab::draw(); + drawFavoriteIcon(); +} + bool LLOutfitAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask) { if (y >= getLocalRect().getHeight() - getHeaderHeight()) @@ -1415,4 +1668,54 @@ bool LLOutfitAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask) return LLAccordionCtrlTab::handleToolTip(x, y, mask); } + +void LLOutfitAccordionCtrlTab::setFavorite(bool is_favorite) +{ + mIsFavorite = is_favorite; + updateTitleColor(); +} + +void LLOutfitAccordionCtrlTab::setOutfitSelected(bool val) +{ + mIsSelected = val; + setTitleFontStyle(mIsSelected ? "BOLD" : "NORMAL"); + updateTitleColor(); + } + +void LLOutfitAccordionCtrlTab::updateTitleColor() + { + static LLUICachedControl highlight_color("InventoryFavoritesColorText", true); + if (mIsFavorite && highlight_color()) + { + setTitleColor(LLUIColorTable::instance().getColor("InventoryFavoriteColor")); + } + else if (mIsSelected) + { + setTitleColor(LLUIColorTable::instance().getColor("SelectedOutfitTextColor")); + } + else + { + setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor")); + } + } + +void LLOutfitAccordionCtrlTab::drawFavoriteIcon() +{ + if (!mIsFavorite) + { + return; + } + static LLUICachedControl draw_star("InventoryFavoritesUseStar", true); + if (!draw_star) + { + return; + } + + const S32 PAD = 2; + const S32 image_size = 18; + + gl_draw_scaled_image( + getRect().getWidth() - image_size - PAD, getRect().getHeight() - image_size - PAD, + image_size, image_size, sFavoriteIcon->getImage(), sFgColor); +} // EOF diff --git a/indra/newview/lloutfitslist.h b/indra/newview/lloutfitslist.h index fad0e638fb6..0bf5becb055 100644 --- a/indra/newview/lloutfitslist.h +++ b/indra/newview/lloutfitslist.h @@ -41,6 +41,7 @@ class LLAccordionCtrlTab; class LLInventoryCategoriesObserver; class LLOutfitListGearMenuBase; +class LLOutfitListSortMenuBase; class LLWearableItemsList; class LLListContextMenu; @@ -61,6 +62,17 @@ class LLOutfitTabNameComparator : public LLAccordionCtrl::LLTabComparator /*virtual*/ bool compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const; }; +class LLOutfitTabFavComparator : public LLAccordionCtrl::LLTabComparator +{ + LOG_CLASS(LLOutfitTabFavComparator); + +public: + LLOutfitTabFavComparator() {}; + virtual ~LLOutfitTabFavComparator() {}; + + /*virtual*/ bool compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const; +}; + class LLOutfitListBase : public LLPanelAppearanceTab { public: @@ -92,6 +104,7 @@ class LLOutfitListBase : public LLPanelAppearanceTab boost::signals2::connection setSelectionChangeCallback(selection_change_callback_t cb); void outfitRightClickCallBack(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id); + void onAction(const LLSD& userdata); virtual bool isActionEnabled(const LLSD& userdata); virtual void performAction(std::string action); virtual bool hasItemSelected() = 0; @@ -109,6 +122,12 @@ class LLOutfitListBase : public LLPanelAppearanceTab virtual bool getHasExpandableFolders() = 0; + virtual void onChangeSortOrder(const LLSD& userdata) = 0; + + virtual void updateMenuItemsVisibility(); + virtual LLToggleableMenu* getGearMenu(); + virtual bool getTrashMenuVisible() { return true; }; + protected: void observerCallback(const LLUUID& category_id); virtual LLOutfitListGearMenuBase* createGearMenu() = 0; @@ -141,6 +160,7 @@ class LLOutfitListBase : public LLPanelAppearanceTab selection_change_signal_t mSelectionChangeSignal; LLListContextMenu* mOutfitMenu; LLOutfitListGearMenuBase* mGearMenu; + boost::signals2::connection mGearMenuConnection; }; ////////////////////////////////////////////////////////////////////////// @@ -157,7 +177,6 @@ class LLOutfitContextMenu : public LLListContextMenu /* virtual */ LLContextMenu* createMenu(); bool onEnable(LLSD::String param); - bool onVisible(LLSD::String param); static void editOutfit(); @@ -165,6 +184,7 @@ class LLOutfitContextMenu : public LLListContextMenu static void renameOutfit(const LLUUID& outfit_cat_id); void onThumbnail(const LLUUID &outfit_cat_id); + void onFavorite(const LLUUID& outfit_cat_id); void onSave(const LLUUID &outfit_cat_id); private: @@ -184,6 +204,7 @@ class LLOutfitListGearMenuBase protected: virtual void onUpdateItemsVisibility(); virtual void onThumbnail(); + virtual void onFavorite(); virtual void onChangeSortOrder(); const LLUUID& getSelectedOutfitID(); @@ -204,6 +225,23 @@ class LLOutfitListGearMenuBase bool onVisible(LLSD::String param); }; +class LLOutfitListSortMenu +{ +public: + LLOutfitListSortMenu(LLOutfitListBase* parent_panel); + + LLToggleableMenu* getMenu(); + void updateItemsVisibility(); + +private: + void onUpdateItemsVisibility(); + bool onEnable(LLSD::String param); + + LLToggleableMenu* mMenu; + LLHandle mPanelHandle; +}; + + class LLOutfitListGearMenu : public LLOutfitListGearMenuBase { public: @@ -223,8 +261,16 @@ class LLOutfitAccordionCtrlTab : public LLAccordionCtrlTab Params() : cat_id("cat_id") {} }; + virtual void draw(); virtual bool handleToolTip(S32 x, S32 y, MASK mask); + void setFavorite(bool is_favorite); + bool getFavorite() const { return mIsFavorite; } + void setOutfitSelected(bool val); + + static LLUIImage* sFavoriteIcon; + static LLUIColor sFgColor; + protected: LLOutfitAccordionCtrlTab(const LLOutfitAccordionCtrlTab::Params &p) : LLAccordionCtrlTab(p), @@ -232,7 +278,12 @@ class LLOutfitAccordionCtrlTab : public LLAccordionCtrlTab {} friend class LLUICtrlFactory; + void updateTitleColor(); + void drawFavoriteIcon(); + LLUUID mFolderID; + bool mIsFavorite = false; + bool mIsSelected = false; }; /** * @class LLOutfitsList @@ -251,6 +302,7 @@ class LLOutfitsList : public LLOutfitListBase virtual ~LLOutfitsList(); /*virtual*/ bool postBuild(); + void initComparator(); /*virtual*/ void onOpen(const LLSD& info); @@ -289,6 +341,10 @@ class LLOutfitsList : public LLOutfitListBase /*virtual*/ bool getHasExpandableFolders() { return true; } + /*virtual*/ void onChangeSortOrder(const LLSD& userdata); + virtual LLToggleableMenu* getSortMenu(); + void updateMenuItemsVisibility(); + protected: LLOutfitListGearMenuBase* createGearMenu(); @@ -359,6 +415,8 @@ class LLOutfitsList : public LLOutfitListBase static void onOutfitRename(const LLSD& notification, const LLSD& response); + void handleInvFavColorChange(); + //LLInventoryCategoriesObserver* mCategoriesObserver; LLAccordionCtrl* mAccordion; @@ -376,13 +434,15 @@ class LLOutfitsList : public LLOutfitListBase // Used to monitor COF changes for updating items worn state. See EXT-8636. uuid_vec_t mCOFLinkedItems; - //LLOutfitListGearMenu* mGearMenu; + LLOutfitListSortMenu* mSortMenu; //bool mIsInitialized; /** * True if there is a selection inside currently selected outfit */ bool mItemSelected; + + boost::signals2::connection mSavedSettingInvFavColor; }; #endif //LL_LLOUTFITSLIST_H diff --git a/indra/newview/llpanelappearancetab.h b/indra/newview/llpanelappearancetab.h index e4d16582de6..e088c3e6f0f 100644 --- a/indra/newview/llpanelappearancetab.h +++ b/indra/newview/llpanelappearancetab.h @@ -29,6 +29,8 @@ #include "llpanel.h" +class LLToggleableMenu; + class LLPanelAppearanceTab : public LLPanel { public: @@ -47,6 +49,11 @@ class LLPanelAppearanceTab : public LLPanel const std::string& getFilterSubString() { return mFilterSubString; } + virtual void updateMenuItemsVisibility() = 0; + virtual LLToggleableMenu* getGearMenu() = 0; + virtual LLToggleableMenu* getSortMenu() = 0; + virtual bool getTrashMenuVisible() = 0; + protected: /** diff --git a/indra/newview/llpanelemojicomplete.cpp b/indra/newview/llpanelemojicomplete.cpp index 3a6a6a5ec3d..f0555408dda 100644 --- a/indra/newview/llpanelemojicomplete.cpp +++ b/indra/newview/llpanelemojicomplete.cpp @@ -463,6 +463,7 @@ void LLPanelEmojiComplete::updateConstraints() { mEmojiHeight = mRenderRect.getHeight(); mRenderRect.stretch((mRenderRect.getWidth() - static_cast(mVisibleEmojis) * mEmojiWidth) / -2, 0); + mRenderRect.translate(-mRenderRect.mLeft, 0); // Left align emojis to fix hitboxes } updateScrollPos(); diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 25dd37590d5..3ab48f69c81 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -543,10 +543,6 @@ LLPanelFace::~LLPanelFace() void LLPanelFace::onVisibilityChange(bool new_visibility) { - if (new_visibility) - { - gAgent.showLatestFeatureNotification("gltf"); - } LLPanel::onVisibilityChange(new_visibility); } @@ -2209,7 +2205,7 @@ void LLPanelFace::refreshMedia() // check if all faces have media(or, all dont have media) - LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo = selected_objects->getSelectedTEValue(&func, bool_has_media); + bool identical_has_media_info = selected_objects->getSelectedTEValue(&func, bool_has_media); const LLMediaEntry default_media_data; @@ -2231,7 +2227,8 @@ void LLPanelFace::refreshMedia() } func_media_data(default_media_data); LLMediaEntry media_data_get; - LLFloaterMediaSettings::getInstance()->mMultipleMedia = !(selected_objects->getSelectedTEValue(&func_media_data, media_data_get)); + bool multiple_media = !(selected_objects->getSelectedTEValue(&func_media_data, media_data_get)); + bool multiple_valid_media = false; std::string multi_media_info_str = LLTrans::getString("Multiple Media"); std::string media_title = ""; @@ -2240,12 +2237,12 @@ void LLPanelFace::refreshMedia() mAddMedia->setEnabled(editable); // IF all the faces have media (or all dont have media) - if (LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo) + if (identical_has_media_info) { // TODO: get media title and set it. mTitleMediaText->clear(); // if identical is set, all faces are same (whether all empty or has the same media) - if (!(LLFloaterMediaSettings::getInstance()->mMultipleMedia)) + if (!multiple_media) { // Media data is valid if (media_data_get != default_media_data) @@ -2266,9 +2263,9 @@ void LLPanelFace::refreshMedia() else // not all face has media but at least one does. { // seleted faces have not identical value - LLFloaterMediaSettings::getInstance()->mMultipleValidMedia = selected_objects->isMultipleTEValue(&func_media_data, default_media_data); + multiple_valid_media = selected_objects->isMultipleTEValue(&func_media_data, default_media_data); - if (LLFloaterMediaSettings::getInstance()->mMultipleValidMedia) + if (multiple_valid_media) { media_title = multi_media_info_str; } @@ -2305,7 +2302,7 @@ void LLPanelFace::refreshMedia() // load values for media settings updateMediaSettings(); - LLFloaterMediaSettings::initValues(mMediaSettings, editable); + LLFloaterMediaSettings::initValues(mMediaSettings, editable, identical_has_media_info, multiple_media, multiple_valid_media); } void LLPanelFace::unloadMedia() @@ -3378,6 +3375,7 @@ void LLPanelFace::onSelectNormalTexture(const LLSD& data) // TODO: test if there is media on the item and only allow editing if present void LLPanelFace::onClickBtnEditMedia() { + LLFloaterMediaSettings::getInstance(); // make sure floater we are about to open exists before refreshMedia refreshMedia(); LLFloaterReg::showInstance("media_settings"); } @@ -3396,6 +3394,7 @@ void LLPanelFace::onClickBtnAddMedia() // check if multiple faces are selected if (LLSelectMgr::getInstance()->getSelection()->isMultipleTESelected()) { + LLFloaterMediaSettings::getInstance(); // make sure floater we are about to open exists before refreshMedia refreshMedia(); LLNotificationsUtil::add("MultipleFacesSelected", LLSD(), LLSD(), multipleFacesSelectedConfirm); } diff --git a/indra/newview/llpanelgroupcreate.cpp b/indra/newview/llpanelgroupcreate.cpp index 4a370525fff..bc7b5caddfd 100644 --- a/indra/newview/llpanelgroupcreate.cpp +++ b/indra/newview/llpanelgroupcreate.cpp @@ -85,6 +85,7 @@ bool LLPanelGroupCreate::postBuild() mInsignia = getChild("insignia", true); mInsignia->setAllowLocalTexture(false); + mInsignia->setBakeTextureEnabled(false); mInsignia->setCanApplyImmediately(false); return true; diff --git a/indra/newview/llpanelgroupgeneral.cpp b/indra/newview/llpanelgroupgeneral.cpp index ca429ae2f8b..270ca29403b 100644 --- a/indra/newview/llpanelgroupgeneral.cpp +++ b/indra/newview/llpanelgroupgeneral.cpp @@ -197,6 +197,7 @@ void LLPanelGroupGeneral::setupCtrls(LLPanel* panel_group) { mInsignia->setCommitCallback(onCommitAny, this); mInsignia->setAllowLocalTexture(false); + mInsignia->setBakeTextureEnabled(false); } mFounderName = getChild("founder_name"); diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index 44465cbc33c..ad7aa57842f 100644 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -68,6 +68,7 @@ const std::string FILTERS_FILENAME("filters.xml"); const std::string ALL_ITEMS("All Items"); const std::string RECENT_ITEMS("Recent Items"); const std::string WORN_ITEMS("Worn Items"); +const std::string FAVORITES("Favorites"); static LLPanelInjector t_inventory("panel_main_inventory"); @@ -214,6 +215,17 @@ bool LLPanelMainInventory::postBuild() worn_filter.markDefault(); mWornItemsPanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, mWornItemsPanel, _1, _2)); } + + LLInventoryPanel* favorites_panel = getChild(FAVORITES); + if (favorites_panel) + { + favorites_panel->setSortOrder(gSavedSettings.getU32(LLInventoryPanel::DEFAULT_SORT_ORDER)); + LLInventoryFilter& favorites_filter = favorites_panel->getFilter(); + favorites_filter.setEmptyLookupMessage("InventoryNoMatchingFavorites"); + favorites_filter.markDefault(); + favorites_panel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, favorites_panel, _1, _2)); + } + mSearchTypeCombo = getChild("search_type"); if(mSearchTypeCombo) { @@ -578,7 +590,8 @@ void LLPanelMainInventory::doCreate(const LLSD& userdata) } else { - menu_create_inventory_item(getPanel(), NULL, userdata); + selectAllItemsPanel(); + menu_create_inventory_item(mAllItemsPanel, NULL, userdata); } } diff --git a/indra/newview/llpanelobject.cpp b/indra/newview/llpanelobject.cpp index 0a3a2e753a4..23e6a9fbcf5 100644 --- a/indra/newview/llpanelobject.cpp +++ b/indra/newview/llpanelobject.cpp @@ -2267,19 +2267,21 @@ void LLPanelObject::onCopyParams() if (objectp->getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT)) { LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT); - - LLUUID texture_id = sculpt_params->getSculptTexture(); - if (get_can_copy_texture(texture_id)) - { - LL_DEBUGS("FloaterTools") << "Recording texture" << LL_ENDL; - mClipboardParams["sculpt"]["id"] = texture_id; - } - else + if (sculpt_params) { - mClipboardParams["sculpt"]["id"] = SCULPT_DEFAULT_TEXTURE; - } + LLUUID texture_id = sculpt_params->getSculptTexture(); + if (get_can_copy_texture(texture_id)) + { + LL_DEBUGS("FloaterTools") << "Recording texture" << LL_ENDL; + mClipboardParams["sculpt"]["id"] = texture_id; + } + else + { + mClipboardParams["sculpt"]["id"] = SCULPT_DEFAULT_TEXTURE; + } - mClipboardParams["sculpt"]["type"] = sculpt_params->getSculptType(); + mClipboardParams["sculpt"]["type"] = sculpt_params->getSculptType(); + } } } diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index ef7986603b1..f90d6d5b3f6 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -129,6 +129,7 @@ class LLTaskInvFVBridge : public LLFolderViewModelItemInventory virtual void navigateToFolder(bool new_window = false, bool change_mode = false) {} virtual bool isItemRenameable() const; virtual bool renameItem(const std::string& new_name); + virtual bool isFavorite() const { return false; } virtual bool isItemMovable() const; virtual bool isItemRemovable(bool check_worn = true) const; virtual bool removeItem(); @@ -1364,7 +1365,23 @@ bool LLPanelObjectInventory::postBuild() void LLPanelObjectInventory::doToSelected(const LLSD& userdata) { - LLInventoryAction::doToSelected(&gInventory, mFolders, userdata.asString()); + std::string action = userdata.asString(); + if ("rename" == action || "delete" == action) + { + LLViewerObject* objectp = gObjectList.findObject(mTaskUUID); + if (objectp && !objectp->permModify()) + { + LLNotificationsUtil::add("CantModifyContentInNoModTask"); + } + else + { + LLInventoryAction::doToSelected(&gInventory, mFolders, action); + } + } + else + { + LLInventoryAction::doToSelected(&gInventory, mFolders, action); + } } void LLPanelObjectInventory::clearContents() diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index 4cd4afaa5ad..a9e860d2ef0 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -404,7 +404,9 @@ LLPanelOutfitEdit::LLPanelOutfitEdit() mWearableListManager(NULL), mPlusBtn(NULL), mWearablesGearMenuBtn(NULL), - mGearMenuBtn(NULL) + mGearMenuBtn(NULL), + mStatus(NULL), + mCurrentOutfitName(NULL) { mSavedFolderState = new LLSaveFolderState(); mSavedFolderState->setApply(false); diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp index 47c02793a3a..e2e2cf1a615 100644 --- a/indra/newview/llpaneloutfitsinventory.cpp +++ b/indra/newview/llpaneloutfitsinventory.cpp @@ -33,6 +33,7 @@ #include "llfloatersidepanelcontainer.h" #include "llinventoryfunctions.h" #include "llinventorymodelbackgroundfetch.h" +#include "llmenubutton.h" #include "llnotificationsutil.h" #include "lloutfitgallery.h" #include "lloutfitobserver.h" @@ -53,12 +54,17 @@ static const std::string SAVE_BTN("save_btn"); static LLPanelInjector t_inventory("panel_outfits_inventory"); -LLPanelOutfitsInventory::LLPanelOutfitsInventory() : - mMyOutfitsPanel(NULL), - mCurrentOutfitPanel(NULL), - mActivePanel(NULL), - mAppearanceTabs(NULL), - mInitialized(false) +LLPanelOutfitsInventory::LLPanelOutfitsInventory() + : mMyOutfitsPanel(nullptr) + , mCurrentOutfitPanel(nullptr) + , mActivePanel(nullptr) + , mAppearanceTabs(nullptr) + , mInitialized(false) + , mGearMenu(nullptr) + , mSortMenu(nullptr) + , mTrashBtn(nullptr) + , mSortMenuPanel(nullptr) + , mTrashMenuPanel(nullptr) { gAgentWearables.addLoadedCallback(boost::bind(&LLPanelOutfitsInventory::onWearablesLoaded, this)); gAgentWearables.addLoadingStartedCallback(boost::bind(&LLPanelOutfitsInventory::onWearablesLoading, this)); @@ -75,6 +81,9 @@ LLPanelOutfitsInventory::~LLPanelOutfitsInventory() { gSavedSettings.setS32("LastAppearanceTab", mAppearanceTabs->getCurrentPanelIndex()); } + mGearMenuConnection.disconnect(); + mSortMenuConnection.disconnect(); + mTrashMenuConnection.disconnect(); } // virtual @@ -258,6 +267,22 @@ void LLPanelOutfitsInventory::initListCommandsHandlers() mOutfitGalleryPanel->childSetAction("trash_btn", boost::bind(&LLPanelOutfitsInventory::onTrashButtonClick, this)); } +void LLPanelOutfitsInventory::setMenuButtons(LLMenuButton* gear_menu, LLMenuButton* sort_menu, LLButton* trash_btn, LLPanel* sort_menu_panel, LLPanel* trash_menu_panel) +{ + mGearMenu = gear_menu; + mSortMenu = sort_menu; + mTrashBtn = trash_btn; + mSortMenuPanel = sort_menu_panel; + mTrashMenuPanel = trash_menu_panel; + + mGearMenuConnection.disconnect(); + mSortMenuConnection.disconnect(); + mTrashMenuConnection.disconnect(); + mGearMenuConnection = mGearMenu->setMouseDownCallback(boost::bind(&LLPanelOutfitsInventory::onGearMouseDown, this)); + mSortMenuConnection = mSortMenu->setMouseDownCallback(boost::bind(&LLPanelOutfitsInventory::onGearMouseDown, this)); + mTrashMenuConnection = mTrashBtn->setClickedCallback(boost::bind(&LLPanelOutfitsInventory::onTrashButtonClick, this)); +} + void LLPanelOutfitsInventory::updateListCommands() { bool trash_enabled = isActionEnabled("delete"); @@ -284,6 +309,14 @@ void LLPanelOutfitsInventory::onTrashButtonClick() } } +void LLPanelOutfitsInventory::onGearMouseDown() +{ + if (mActivePanel) + { + mActivePanel->updateMenuItemsVisibility(); + } +} + bool LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata) { return mActivePanel && mActivePanel->isActionEnabled(userdata); @@ -320,6 +353,28 @@ void LLPanelOutfitsInventory::onTabChange() mActivePanel->checkFilterSubString(); mActivePanel->onOpen(LLSD()); + if (mGearMenu) + { + mGearMenu->setMenu(mActivePanel->getGearMenu(), LLMenuButton::MP_BOTTOM_LEFT); + } + if (mSortMenu && mSortMenuPanel) + { + LLToggleableMenu* menu = mActivePanel->getSortMenu(); + if (menu) + { + mSortMenu->setMenu(menu, LLMenuButton::MP_BOTTOM_LEFT); + mSortMenuPanel->setVisible(true); + } + else + { + mSortMenuPanel->setVisible(false); + } + } + if (mTrashMenuPanel) + { + mTrashMenuPanel->setVisible(mActivePanel->getTrashMenuVisible()); + } + updateVerbs(); } diff --git a/indra/newview/llpaneloutfitsinventory.h b/indra/newview/llpaneloutfitsinventory.h index e046681e951..29f7eb44f7a 100644 --- a/indra/newview/llpaneloutfitsinventory.h +++ b/indra/newview/llpaneloutfitsinventory.h @@ -30,11 +30,13 @@ #include "llpanel.h" +class LLButton; class LLOutfitGallery; class LLOutfitsList; class LLOutfitListGearMenuBase; class LLPanelAppearanceTab; class LLPanelWearing; +class LLMenuButton; class LLMenuGL; class LLSidepanelAppearance; class LLTabContainer; @@ -63,6 +65,13 @@ class LLPanelOutfitsInventory : public LLPanel bool isCOFPanelActive() const; + void setMenuButtons( + LLMenuButton* gear_menu, + LLMenuButton* sort_menu, + LLButton* trash_btn, + LLPanel* sort_menu_panel, + LLPanel* trash_menu_panel); + protected: void updateVerbs(); @@ -92,20 +101,29 @@ class LLPanelOutfitsInventory : public LLPanel void initListCommandsHandlers(); void updateListCommands(); void onWearButtonClick(); - void showGearMenu(); void onTrashButtonClick(); + void onGearMouseDown(); bool isActionEnabled(const LLSD& userdata); void setWearablesLoading(bool val); void onWearablesLoaded(); void onWearablesLoading(); private: LLPanel* mListCommands; - LLMenuGL* mMenuAdd; LLButton* mWearBtn = nullptr; // List Commands // ////////////////////////////////////////////////////////////////////////////////// bool mInitialized; + + // not owned items + LLMenuButton* mGearMenu; + LLMenuButton* mSortMenu; + LLButton* mTrashBtn; + LLPanel* mSortMenuPanel; + LLPanel* mTrashMenuPanel; + boost::signals2::connection mGearMenuConnection; + boost::signals2::connection mSortMenuConnection; + boost::signals2::connection mTrashMenuConnection; }; #endif //LL_LLPANELOUTFITSINVENTORY_H diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 25672db318a..33599357a3e 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -843,6 +843,10 @@ void LLPanelPeople::updateNearbyList() LLWorld::getInstance()->getAvatars(&mNearbyList->getIDs(), &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); mNearbyList->setDirty(); +#ifdef LL_DISCORD + if (gSavedSettings.getBOOL("EnableDiscord")) + LLAppViewer::updateDiscordPartyMaxSize((S32)mNearbyList->getIDs().size()); +#endif DISTANCE_COMPARATOR.updateAvatarsPositions(positions, mNearbyList->getIDs()); LLActiveSpeakerMgr::instance().update(true); diff --git a/indra/newview/llpanelpeoplemenus.cpp b/indra/newview/llpanelpeoplemenus.cpp index f8a73ddb465..62e726d21d5 100644 --- a/indra/newview/llpanelpeoplemenus.cpp +++ b/indra/newview/llpanelpeoplemenus.cpp @@ -154,11 +154,15 @@ void PeopleContextMenu::buildContextMenu(class LLMenuGL& menu, U32 flags) bool PeopleContextMenu::enableContextMenuItem(const LLSD& userdata) { + std::string item = userdata.asString(); if(gAgent.getID() == mUUIDs.front()) { + if (item == std::string("can_zoom_in")) + { + return true; + } return false; } - std::string item = userdata.asString(); // Note: can_block and can_delete is used only for one person selected menu // so we don't need to go over all uuids. @@ -350,7 +354,10 @@ void PeopleContextMenu::eject() avatar = (LLVOAvatar*) object; } } - if (!avatar) return; + + if (!avatar) + return; + LLSD payload; payload["avatar_id"] = avatar->getID(); std::string fullname = avatar->getFullname(); diff --git a/indra/newview/llpanelpermissions.cpp b/indra/newview/llpanelpermissions.cpp index 0ce9ef7a7a3..cbf5819fda6 100644 --- a/indra/newview/llpanelpermissions.cpp +++ b/indra/newview/llpanelpermissions.cpp @@ -176,7 +176,7 @@ bool LLPanelPermissions::postBuild() childSetCommitCallback("sale type",LLPanelPermissions::onCommitSaleType,this); - childSetCommitCallback("Edit Cost", LLPanelPermissions::onCommitSaleInfo, this); + childSetCommitCallback("Edit Cost", LLPanelPermissions::onCommitSalePrice, this); childSetCommitCallback("checkbox next owner can modify",LLPanelPermissions::onCommitNextOwnerModify,this); childSetCommitCallback("checkbox next owner can copy",LLPanelPermissions::onCommitNextOwnerCopy,this); @@ -781,7 +781,9 @@ void LLPanelPermissions::refresh() if (has_change_sale_ability && (owner_mask_on & PERM_TRANSFER)) { - getChildView("checkbox for sale")->setEnabled(can_transfer || (!can_transfer && num_for_sale)); + bool change_sale_allowed = can_transfer || (!can_transfer && num_for_sale); + getChildView("checkbox for sale")->setEnabled(change_sale_allowed); + getChildView("Edit Cost")->setEnabled(change_sale_allowed && !is_for_sale_mixed); // Set the checkbox to tentative if the prices of each object selected // are not the same. getChild("checkbox for sale")->setTentative( is_for_sale_mixed); @@ -1224,6 +1226,16 @@ void LLPanelPermissions::onCommitSaleType(LLUICtrl*, void* data) self->setAllSaleInfo(); } +void LLPanelPermissions::onCommitSalePrice(LLUICtrl *, void *data) +{ + LLPanelPermissions *self = (LLPanelPermissions *) data; + LLCheckBoxCtrl *checkPurchase = self->getChild("checkbox for sale"); + if (checkPurchase && checkPurchase->get()) + { + self->setAllSaleInfo(); + } +} + void LLPanelPermissions::setAllSaleInfo() { LL_INFOS() << "LLPanelPermissions::setAllSaleInfo()" << LL_ENDL; diff --git a/indra/newview/llpanelpermissions.h b/indra/newview/llpanelpermissions.h index 77129434ed8..12e88361c9b 100644 --- a/indra/newview/llpanelpermissions.h +++ b/indra/newview/llpanelpermissions.h @@ -77,6 +77,7 @@ class LLPanelPermissions : public LLPanel static void onCommitSaleInfo(LLUICtrl* ctrl, void* data); static void onCommitSaleType(LLUICtrl* ctrl, void* data); + static void onCommitSalePrice(LLUICtrl *ctrl, void *data); void setAllSaleInfo(); static void onCommitClickAction(LLUICtrl* ctrl, void*); diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index c81746a48aa..b77ee2f4395 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -2066,6 +2066,7 @@ void LLPanelProfileFirstLife::onChangePhoto() } }); texture_floaterp->setLocalTextureEnabled(false); + texture_floaterp->setBakeTextureEnabled(false); texture_floaterp->setCanApply(false, true, false); parent_floater->addDependentFloater(mFloaterTexturePickerHandle); diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp index c1534c9abd0..3aedde74c69 100644 --- a/indra/newview/llpanelwearing.cpp +++ b/indra/newview/llpanelwearing.cpp @@ -113,6 +113,7 @@ class LLWearingContextMenu : public LLListContextMenu boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); registrar.add("Wearing.Detach", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); + registrar.add("Wearing.Favorite", boost::bind(toggle_favorites, mUUIDs)); LLContextMenu* menu = createFromFile("menu_wearing_tab.xml"); updateMenuItemsVisibility(menu); @@ -125,6 +126,8 @@ class LLWearingContextMenu : public LLListContextMenu bool bp_selected = false; // true if body parts selected bool clothes_selected = false; bool attachments_selected = false; + bool can_favorite = false; + bool can_unfavorite = false; // See what types of wearables are selected. for (uuid_vec_t::const_iterator it = mUUIDs.begin(); it != mUUIDs.end(); ++it) @@ -137,6 +140,9 @@ class LLWearingContextMenu : public LLListContextMenu continue; } + LLUUID linked_id = item->getLinkedUUID(); + LLViewerInventoryItem* linked_item = gInventory.getItem(linked_id); + LLAssetType::EType type = item->getType(); if (type == LLAssetType::AT_CLOTHING) { @@ -150,6 +156,8 @@ class LLWearingContextMenu : public LLListContextMenu { attachments_selected = true; } + can_favorite |= !linked_item->getIsFavorite(); + can_unfavorite |= linked_item->getIsFavorite(); } // Enable/disable some menu items depending on the selection. @@ -166,6 +174,8 @@ class LLWearingContextMenu : public LLListContextMenu menu->setItemVisible("detach", allow_detach); menu->setItemVisible("edit_outfit_separator", show_touch | show_edit | allow_take_off || allow_detach); menu->setItemVisible("show_original", mUUIDs.size() == 1); + menu->setItemVisible("favorites_add", can_favorite); + menu->setItemVisible("favorites_remove", can_unfavorite); } }; @@ -232,6 +242,10 @@ LLPanelWearing::~LLPanelWearing() { mAttachmentsChangedConnection.disconnect(); } + if (mGearMenuConnection.connected()) + { + mGearMenuConnection.disconnect(); + } } bool LLPanelWearing::postBuild() @@ -249,10 +263,6 @@ bool LLPanelWearing::postBuild() mTempItemsList->setFgUnselectedColor(LLColor4::white); mTempItemsList->setRightMouseDownCallback(boost::bind(&LLPanelWearing::onTempAttachmentsListRightClick, this, _1, _2, _3)); - LLMenuButton* menu_gear_btn = getChild("options_gear_btn"); - - menu_gear_btn->setMenu(mGearMenu->getMenu()); - return true; } @@ -560,6 +570,16 @@ void LLPanelWearing::onRemoveAttachment() } } +LLToggleableMenu* LLPanelWearing::getGearMenu() +{ + return mGearMenu->getMenu(); +} + +LLToggleableMenu* LLPanelWearing::getSortMenu() +{ + return NULL; +} + void LLPanelWearing::onRemoveItem() { if (mWearablesTab->isExpanded()) diff --git a/indra/newview/llpanelwearing.h b/indra/newview/llpanelwearing.h index ea0787d0eff..aa80a3fc21b 100644 --- a/indra/newview/llpanelwearing.h +++ b/indra/newview/llpanelwearing.h @@ -84,6 +84,11 @@ class LLPanelWearing : public LLPanelAppearanceTab void onEditAttachment(); void onRemoveAttachment(); + void updateMenuItemsVisibility() {}; + LLToggleableMenu* getGearMenu(); + LLToggleableMenu* getSortMenu(); + bool getTrashMenuVisible() { return false; } + private: void onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); void onTempAttachmentsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); @@ -93,6 +98,7 @@ class LLPanelWearing : public LLPanelAppearanceTab LLWearableItemsList* mCOFItemsList; LLScrollListCtrl* mTempItemsList; LLWearingGearMenu* mGearMenu; + boost::signals2::connection mGearMenuConnection; LLListContextMenu* mContextMenu; LLListContextMenu* mAttachmentsMenu; diff --git a/indra/newview/llreflectionmap.h b/indra/newview/llreflectionmap.h index a8187935508..8da18e09451 100644 --- a/indra/newview/llreflectionmap.h +++ b/indra/newview/llreflectionmap.h @@ -124,7 +124,7 @@ class alignas(16) LLReflectionMap : public LLRefCount LLSpatialGroup* mGroup = nullptr; // viewer object this probe is tracking (if any) - LLPointer mViewerObject = nullptr; + LLPointer mViewerObject; // what priority should this probe have (higher is higher priority) // currently only 0 or 1 diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 8aa3693d60d..01fd5ae63c0 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -252,6 +252,7 @@ LLSelectMgr::LLSelectMgr() LLSelectMgr::~LLSelectMgr() { clearSelections(); + mSlectionLodModChangedConnection.disconnect(); } void LLSelectMgr::clearSelections() @@ -2248,6 +2249,7 @@ void LLSelectMgr::selectionRevertGLTFMaterials() { // Restore base material LLUUID asset_id = nodep->mSavedGLTFMaterialIds[te]; + LLUUID old_asset_id = objectp->getRenderMaterialID(te); // Update material locally objectp->setRenderMaterialID(te, asset_id, false /*wait for LLGLTFMaterialList update*/); @@ -2258,18 +2260,29 @@ void LLSelectMgr::selectionRevertGLTFMaterials() objectp->setTEGLTFMaterialOverride(te, material); } - // Enqueue update to server - if (asset_id.notNull() && material) + if (asset_id.isNull() || !material) + { + //blank override out + LLGLTFMaterialList::queueApply(objectp, te, asset_id); + } + if (old_asset_id != asset_id) { // Restore overrides and base material + // Note: might not work reliably if asset is already there, might + // have a server sided problem where servers applies override + // first then resets it by adding asset, in which case need + // to create a server ticket and chain asset then override + // application. LLGLTFMaterialList::queueApply(objectp, te, asset_id, material); } else { - //blank override out - LLGLTFMaterialList::queueApply(objectp, te, asset_id); + // Enqueue override update to server + // Note: this is suboptimal, better to send asset id as well + // but there seems to be a server problem with queueApply + // that ignores override in some cases + LLGLTFMaterialList::queueModify(objectp, te, material); } - } return true; } diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h index 792a37297ff..11aad3b806f 100644 --- a/indra/newview/llselectmgr.h +++ b/indra/newview/llselectmgr.h @@ -943,6 +943,7 @@ class LLSelectMgr : public LLEditMenuHandler, public LLSimpleton bool mForceSelection; std::vector mPauseRequests; + boost::signals2::connection mSlectionLodModChangedConnection; }; // *DEPRECATED: For callbacks or observers, use diff --git a/indra/newview/llsidepanelappearance.cpp b/indra/newview/llsidepanelappearance.cpp index c618483fc42..1d1b31d2a6b 100644 --- a/indra/newview/llsidepanelappearance.cpp +++ b/indra/newview/llsidepanelappearance.cpp @@ -40,6 +40,7 @@ #include "llfloaterworldmap.h" #include "llfolderviewmodel.h" #include "llloadingindicator.h" +#include "llmenubutton.h" #include "lloutfitobserver.h" #include "llpaneleditwearable.h" #include "llpaneloutfitsinventory.h" @@ -145,6 +146,14 @@ bool LLSidepanelAppearance::postBuild() setWearablesLoading(gAgentWearables.isCOFChangeInProgress()); + + LLMenuButton* menu_gear_btn = getChild("options_gear_btn"); + LLMenuButton* menu_sort_btn = getChild("sorting_menu_btn"); + LLButton* menu_trash_btn = getChild("trash_btn"); + LLPanel* menu_sort_btn_panel = getChild("options_sort_btn_panel"); + LLPanel* menu_trash_btn_panel = getChild("trash_btn_panel"); + mPanelOutfitsInventory->setMenuButtons(menu_gear_btn, menu_sort_btn, menu_trash_btn, menu_sort_btn_panel, menu_trash_btn_panel); + return true; } diff --git a/indra/newview/llsidepaneliteminfo.cpp b/indra/newview/llsidepaneliteminfo.cpp index fccf745a747..3d4b4fb9c13 100644 --- a/indra/newview/llsidepaneliteminfo.cpp +++ b/indra/newview/llsidepaneliteminfo.cpp @@ -56,6 +56,8 @@ #include "llviewerregion.h" +const char* const DEFAULT_DESC = "(No Description)"; + class PropertiesChangedCallback : public LLInventoryCallback { public: @@ -128,6 +130,7 @@ LLSidepanelItemInfo::LLSidepanelItemInfo(const LLPanel::Params& p) , mUpdatePendingId(-1) , mIsDirty(false) /*Not ready*/ , mParentFloater(NULL) + , mLabelItemDesc(NULL) { gInventory.addObserver(this); gIdleCallbacks.addFunction(&LLSidepanelItemInfo::onIdle, (void*)this); @@ -158,10 +161,11 @@ bool LLSidepanelItemInfo::postBuild() mItemTypeIcon = getChild("item_type_icon"); mLabelOwnerName = getChild("LabelOwnerName"); mLabelCreatorName = getChild("LabelCreatorName"); + mLabelItemDesc = getChild("LabelItemDesc"); getChild("LabelItemName")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); getChild("LabelItemName")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitName,this)); - getChild("LabelItemDesc")->setCommitCallback(boost::bind(&LLSidepanelItemInfo:: onCommitDescription, this)); + mLabelItemDesc->setCommitCallback(boost::bind(&LLSidepanelItemInfo:: onCommitDescription, this)); // Thumnail edition mChangeThumbnailBtn->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onEditThumbnail, this)); // acquired date @@ -923,17 +927,22 @@ void LLSidepanelItemInfo::onCommitDescription() LLViewerInventoryItem* item = findItem(); if(!item) return; - LLTextEditor* labelItemDesc = getChild("LabelItemDesc"); - if(!labelItemDesc) + if(!mLabelItemDesc) + { + return; + } + if (!gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)) { return; } - if((item->getDescription() != labelItemDesc->getText()) && - (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE))) + std::string old_desc = item->getDescription(); + std::string new_desc = mLabelItemDesc->getText(); + if(old_desc != new_desc) { + mLabelItemDesc->setSelectAllOnFocusReceived(false); LLPointer new_item = new LLViewerInventoryItem(item); - new_item->setDescription(labelItemDesc->getText()); + new_item->setDescription(new_desc); onCommitChanges(new_item); } } diff --git a/indra/newview/llsidepaneliteminfo.h b/indra/newview/llsidepaneliteminfo.h index 718edc79d64..0895d3360cf 100644 --- a/indra/newview/llsidepaneliteminfo.h +++ b/indra/newview/llsidepaneliteminfo.h @@ -46,6 +46,7 @@ class LLObjectInventoryObserver; class LLViewerObject; class LLPermissions; class LLTextBox; +class LLTextEditor; class LLSidepanelItemInfo : public LLPanel, public LLInventoryObserver { @@ -105,6 +106,7 @@ class LLSidepanelItemInfo : public LLPanel, public LLInventoryObserver LLIconCtrl* mItemTypeIcon; LLTextBox* mLabelOwnerName; LLTextBox* mLabelCreatorName; + LLTextEditor* mLabelItemDesc; // // UI Elements diff --git a/indra/newview/llskinningutil.cpp b/indra/newview/llskinningutil.cpp index 47f58afa00e..223fc2a8f5e 100644 --- a/indra/newview/llskinningutil.cpp +++ b/indra/newview/llskinningutil.cpp @@ -116,8 +116,8 @@ void LLSkinningUtil::scrubInvalidJoints(LLVOAvatar *avatar, LLMeshSkinInfo* skin // needed for handling of any legacy bad data. if (!avatar->getJoint(skin->mJointNames[j])) { - LL_DEBUGS("Avatar") << avatar->getFullname() << " mesh rigged to invalid joint " << skin->mJointNames[j] << LL_ENDL; - LL_WARNS_ONCE("Avatar") << avatar->getFullname() << " mesh rigged to invalid joint" << skin->mJointNames[j] << LL_ENDL; + LL_DEBUGS("Avatar") << avatar->getDebugName() << " mesh rigged to invalid joint " << skin->mJointNames[j] << LL_ENDL; + LL_WARNS_ONCE("Avatar") << avatar->getDebugName() << " mesh rigged to invalid joint" << skin->mJointNames[j] << LL_ENDL; skin->mJointNames[j] = "mPelvis"; skin->mJointNumsInitialized = false; // force update after names change. } diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp index 68b4ab381a5..9b6a87e68d5 100644 --- a/indra/newview/llsnapshotlivepreview.cpp +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -1068,7 +1068,7 @@ void LLSnapshotLivePreview::saveTexture(bool outfit_snapshot, std::string name) tid, LLAssetType::AT_TEXTURE, res_name, res_desc, 0, folder_type, inv_type, PERM_ALL, LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost, !outfit_snapshot)); + expected_upload_cost, LLUUID::null, !outfit_snapshot)); upload_new_resource(assetUploadInfo); diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp index 4956c188fb0..b49c0119ed6 100644 --- a/indra/newview/llspeakers.cpp +++ b/indra/newview/llspeakers.cpp @@ -1026,6 +1026,10 @@ void LLLocalSpeakerMgr::updateSpeakerList() uuid_vec_t avatar_ids; std::vector positions; LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), CHAT_NORMAL_RADIUS); +#ifdef LL_DISCORD + if (gSavedSettings.getBOOL("EnableDiscord")) + LLAppViewer::updateDiscordPartyCurrentSize((S32)avatar_ids.size()); +#endif for(U32 i=0; i("goShop")->setCommitCallback(boost::bind(&LLWeb::loadURL, gSavedSettings.getString("MarketplaceURL"), LLStringUtil::null, LLStringUtil::null)); mBoxBalance = getChild("balance"); - mBoxBalance->setClickedCallback( &LLStatusBar::onClickBalance, this ); + mBoxBalance->setClickedCallback(&LLStatusBar::onClickRefreshBalance, this); + mBoxBalance->setDoubleClickCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onClickToggleBalance(); }); mIconPresetsCamera = getChild( "presets_icon_camera" ); mIconPresetsCamera->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterPresetsCamera, this)); @@ -191,12 +185,14 @@ bool LLStatusBar::postBuild() gSavedSettings.getControl("MuteAudio")->getSignal()->connect(boost::bind(&LLStatusBar::onVolumeChanged, this, _2)); gSavedSettings.getControl("EnableVoiceChat")->getSignal()->connect(boost::bind(&LLStatusBar::onVoiceChanged, this, _2)); + gSavedSettings.getControl("ObscureBalanceInStatusBar")->getSignal()->connect(boost::bind(&LLStatusBar::onObscureBalanceChanged, this, _2)); if (!gSavedSettings.getBOOL("EnableVoiceChat") && LLAppViewer::instance()->isSecondInstance()) { // Indicate that second instance started without sound mBtnVolume->setImageUnselected(LLUI::getUIImage("VoiceMute_Off")); } + mObscureBalance = gSavedSettings.getBOOL("ObscureBalanceInStatusBar"); // Adding Net Stat Graph S32 x = getRect().getWidth() - 2; @@ -319,6 +315,12 @@ void LLStatusBar::refresh() mTextTime->setToolTip (dtStr); } + if (mBalanceClicked && mBalanceClickTimer.getElapsedTimeF32() > 1.f) + { + mBalanceClicked = false; + sendMoneyBalanceRequest(); + } + LLRect r; const S32 MENU_RIGHT = gMenuBarView->getRightmostMenuEdge(); @@ -384,9 +386,17 @@ void LLStatusBar::setBalance(S32 balance) std::string money_str = LLResMgr::getInstance()->getMonetaryString( balance ); LLStringUtil::format_map_t string_args; - string_args["[AMT]"] = llformat("%s", money_str.c_str()); + if (mObscureBalance) + { + string_args["[AMT]"] = "****"; + } + else + { + string_args["[AMT]"] = llformat("%s", money_str.c_str()); + } std::string label_str = getString("buycurrencylabel", string_args); mBoxBalance->setValue(label_str); + mBoxBalance->setToolTipArg(LLStringExplicit("[AMT]"), llformat("%s", money_str.c_str())); updateBalancePanelPosition(); @@ -406,8 +416,6 @@ void LLStatusBar::setBalance(S32 balance) if( balance != mBalance ) { - mBalanceTimer->reset(); - mBalanceTimer->setTimerExpirySec( ICON_TIMER_EXPIRY ); mBalance = balance; } } @@ -459,9 +467,6 @@ void LLStatusBar::setHealth(S32 health) } } } - - mHealthTimer->reset(); - mHealthTimer->setTimerExpirySec( ICON_TIMER_EXPIRY ); } mHealth = health; @@ -621,13 +626,27 @@ static void onClickVolume(void* data) } //static -void LLStatusBar::onClickBalance(void* ) +void LLStatusBar::onClickRefreshBalance(void* data) { - // Force a balance request message: - LLStatusBar::sendMoneyBalanceRequest(); + LLStatusBar* status_bar = (LLStatusBar*)data; + + if (!status_bar->mBalanceClicked) + { + // Schedule a balance request message: + status_bar->mBalanceClicked = true; + status_bar->mBalanceClickTimer.reset(); + } // The refresh of the display (call to setBalance()) will be done by process_money_balance_reply() } +void LLStatusBar::onClickToggleBalance() +{ + mObscureBalance = !mObscureBalance; + gSavedSettings.setBOOL("ObscureBalanceInStatusBar", mObscureBalance); + setBalance(mBalance); + mBalanceClicked = false; // supress click +} + //static void LLStatusBar::onClickMediaToggle(void* data) { @@ -657,6 +676,12 @@ void LLStatusBar::onVoiceChanged(const LLSD& newvalue) refresh(); } +void LLStatusBar::onObscureBalanceChanged(const LLSD& newvalue) +{ + mObscureBalance = newvalue.asBoolean(); + setBalance(mBalance); +} + void LLStatusBar::onUpdateFilterTerm() { LLWString searchValue = utf8str_to_wstring( mFilterEdit->getValue() ); diff --git a/indra/newview/llstatusbar.h b/indra/newview/llstatusbar.h index 45cbda0ef17..a8fc621ff83 100644 --- a/indra/newview/llstatusbar.h +++ b/indra/newview/llstatusbar.h @@ -72,7 +72,8 @@ class LLStatusBar void debitBalance(S32 debit); void creditBalance(S32 credit); - // Request the latest currency balance from the server + // Request the latest currency balance from the server. + // Reply at process_money_balance_reply() static void sendMoneyBalanceRequest(); void setHealth(S32 percent); @@ -102,6 +103,7 @@ class LLStatusBar void onClickBuyCurrency(); void onVolumeChanged(const LLSD& newvalue); void onVoiceChanged(const LLSD& newvalue); + void onObscureBalanceChanged(const LLSD& newvalue); void onMouseEnterPresetsCamera(); void onMouseEnterPresets(); @@ -109,7 +111,8 @@ class LLStatusBar void onMouseEnterNearbyMedia(); static void onClickMediaToggle(void* data); - static void onClickBalance(void* data); + static void onClickRefreshBalance(void* data); + void onClickToggleBalance(); LLSearchEditor *mFilterEdit; LLPanel *mSearchPanel; @@ -135,11 +138,12 @@ class LLStatusBar LLFrameTimer mClockUpdateTimer; S32 mBalance; + bool mBalanceClicked; + bool mObscureBalance; + LLTimer mBalanceClickTimer; S32 mHealth; S32 mSquareMetersCredit; S32 mSquareMetersCommitted; - LLFrameTimer* mBalanceTimer; - LLFrameTimer* mHealthTimer; LLPanelPresetsCameraPulldown* mPanelPresetsCameraPulldown; LLPanelPresetsPulldown* mPanelPresetsPulldown; LLPanelVolumePulldown* mPanelVolumePulldown; diff --git a/indra/newview/llteleporthistorystorage.cpp b/indra/newview/llteleporthistorystorage.cpp index dd7c6aa9e3e..e02f76711c7 100644 --- a/indra/newview/llteleporthistorystorage.cpp +++ b/indra/newview/llteleporthistorystorage.cpp @@ -127,6 +127,12 @@ void LLTeleportHistoryStorage::addItem(const std::string title, const LLVector3d S32 removed_index = -1; if (item_iter != mItems.end()) { + // When teleporting via history it's possible that there can be + // an offset applied to the position, so each new teleport can + // be a meter higher than the last. + // Avoid it by preserving original position. + item.mGlobalPos = item_iter->mGlobalPos; + removed_index = (S32)(item_iter - mItems.begin()); mItems.erase(item_iter); } diff --git a/indra/newview/lltexturecache.cpp b/indra/newview/lltexturecache.cpp index 442c627d07d..1a7ce74ccc6 100644 --- a/indra/newview/lltexturecache.cpp +++ b/indra/newview/lltexturecache.cpp @@ -1354,6 +1354,7 @@ U32 LLTextureCache::openAndReadEntries(std::vector& entries) if (bytes_read < sizeof(Entry)) { LL_WARNS() << "Corrupted header entries, failed at " << idx << " / " << num_entries << LL_ENDL; + closeHeaderEntriesFile(); return 0; } entries.push_back(entry); diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index 20127f5f270..2027e958d53 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -164,7 +164,6 @@ LLFloaterTexturePicker::LLFloaterTexturePicker( mFallbackImage(fallback_image), mDefaultImageAssetID(default_image_asset_id), mBlankImageAssetID(blank_image_asset_id), - mTentative(tentative), mAllowNoTexture(allow_no_texture), mLabel(label), mTentativeLabel(NULL), @@ -186,8 +185,10 @@ LLFloaterTexturePicker::LLFloaterTexturePicker( mSetImageAssetIDCallback(NULL), mOnUpdateImageStatsCallback(NULL), mBakeTextureEnabled(false), + mLocalTextureEnabled(false), mInventoryPickType(pick_type) { + setTentative(tentative); mCanApplyImmediately = can_apply_immediately; buildFromFile("floater_texture_ctrl.xml"); setCanMinimize(false); @@ -199,7 +200,7 @@ LLFloaterTexturePicker::~LLFloaterTexturePicker() void LLFloaterTexturePicker::setImageID(const LLUUID& image_id, bool set_selection /*=true*/) { - if( ((mImageAssetID != image_id) || mTentative) && mActive) + if( ((mImageAssetID != image_id) || getTentative()) && mActive) { mNoCopyTextureSelected = false; mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? @@ -277,6 +278,7 @@ void LLFloaterTexturePicker::setImageIDFromItem(const LLInventoryItem* itemp, bo asset_id = BLANK_MATERIAL_ASSET_ID; } setImageID(asset_id, set_selection); + setTentative(false); } void LLFloaterTexturePicker::setActive( bool active ) @@ -525,6 +527,8 @@ bool LLFloaterTexturePicker::handleKeyHere(KEY key, MASK mask) void LLFloaterTexturePicker::onOpen(const LLSD& key) { if (sLastPickerMode != 0 + && (mLocalTextureEnabled || sLastPickerMode != 1) + && (mBakeTextureEnabled || sLastPickerMode != 2) && mModeSelector->selectByValue(sLastPickerMode)) { changeMode(); @@ -655,7 +659,7 @@ void LLFloaterTexturePicker::draw() bool valid_dims = updateImageStats(); // if we're inactive, gray out "apply immediate" checkbox - mSelectBtn->setEnabled(mActive && mCanApply && valid_dims); + mSelectBtn->setEnabled(mActive && mCanApply && valid_dims && !getTentative()); mPipetteBtn->setEnabled(mActive); mPipetteBtn->setValue(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()); @@ -720,9 +724,9 @@ void LLFloaterTexturePicker::draw() mTentativeLabel->setVisible( false ); } - mDefaultBtn->setEnabled(mImageAssetID != mDefaultImageAssetID || mTentative); - mBlankBtn->setEnabled((mImageAssetID != mBlankImageAssetID && mBlankImageAssetID.notNull()) || mTentative); - mNoneBtn->setEnabled(mAllowNoTexture && (!mImageAssetID.isNull() || mTentative)); + mDefaultBtn->setEnabled(mImageAssetID != mDefaultImageAssetID || getTentative()); + mBlankBtn->setEnabled((mImageAssetID != mBlankImageAssetID && mBlankImageAssetID.notNull()) || getTentative()); + mNoneBtn->setEnabled(mAllowNoTexture && (!mImageAssetID.isNull() || getTentative())); LLFloater::draw(); @@ -775,7 +779,7 @@ void LLFloaterTexturePicker::draw() } // Draw Tentative Label over the image - if( mTentative && !mViewModel->isDirty() ) + if( getTentative() && !mViewModel->isDirty() ) { mTentativeLabel->setVisible( true ); drawChild(mTentativeLabel); @@ -978,6 +982,7 @@ void LLFloaterTexturePicker::onBtnSetToDefault(void* userdata) if (self->mOwner) { self->setImageID( self->getDefaultImageAssetID() ); + self->setTentative(false); } self->commitIfImmediateSet(); } @@ -988,6 +993,7 @@ void LLFloaterTexturePicker::onBtnBlank(void* userdata) LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; self->setCanApply(true, true); self->setImageID( self->getBlankImageAssetID() ); + self->setTentative(false); self->commitIfImmediateSet(); } @@ -998,21 +1004,10 @@ void LLFloaterTexturePicker::onBtnNone(void* userdata) LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; self->setCanApply(true, true); self->setImageID( LLUUID::null ); + self->setTentative(false); self->commitIfImmediateSet(); } -/* -// static -void LLFloaterTexturePicker::onBtnRevert(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - self->setImageID( self->mOriginalImageAssetID ); - // TODO: Change this to tell the owner to cancel. It needs to be - // smart enough to restore multi-texture selections. - self->mOwner->onFloaterCommit(); - self->mViewModel->resetDirty(); -}*/ - // static void LLFloaterTexturePicker::onBtnCancel(void* userdata) { @@ -1219,6 +1214,7 @@ void LLFloaterTexturePicker::onLocalScrollCommit(LLUICtrl* ctrl, void* userdata) if (self->mSetImageAssetIDCallback) { self->mSetImageAssetIDCallback(inworld_id); + self->setTentative(false); } if (self->childGetValue("apply_immediate_check").asBoolean()) @@ -1297,6 +1293,7 @@ void LLFloaterTexturePicker::onBakeTextureSelect(LLUICtrl* ctrl, void *user_data } self->setImageID(imageID); + self->setTentative(false); self->mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? if (!self->mPreviewSettingChanged) @@ -1317,7 +1314,7 @@ void LLFloaterTexturePicker::onBakeTextureSelect(LLUICtrl* ctrl, void *user_data void LLFloaterTexturePicker::setCanApply(bool can_preview, bool can_apply, bool inworld_image) { - mSelectBtn->setEnabled(can_apply); + mSelectBtn->setEnabled(can_apply && !getTentative()); // will be updated on draw getChildRef("preview_disabled").setVisible(!can_preview && inworld_image); getChildRef("apply_immediate_check").setVisible(can_preview); @@ -1491,7 +1488,13 @@ void LLFloaterTexturePicker::refreshInventoryFilter() void LLFloaterTexturePicker::setLocalTextureEnabled(bool enabled) { + mLocalTextureEnabled = enabled; mModeSelector->setEnabledByValue(1, enabled); + if (!enabled && (mModeSelector->getValue().asInteger() == 2)) + { + mModeSelector->selectByValue(0); + onModeSelect(0, this); + } } void LLFloaterTexturePicker::setBakeTextureEnabled(bool enabled) @@ -1618,6 +1621,7 @@ void LLFloaterTexturePicker::onTextureSelect( const LLTextureEntry& te ) else { setImageID(te.getID()); + setTentative(false); } mNoCopyTextureSelected = false; @@ -1831,6 +1835,17 @@ void LLTextureCtrl::clear() setImageAssetID(LLUUID::null); } +void LLTextureCtrl::setTentative(bool tentative) +{ + LLFloater* floaterp = mFloaterHandle.get(); + + if (floaterp) + { + floaterp->setTentative(tentative); + } + LLUICtrl::setTentative(tentative); +} + void LLTextureCtrl::setLabel(const std::string& label) { mLabel = label; @@ -1878,11 +1893,9 @@ void LLTextureCtrl::showPicker(bool take_focus) if (texture_floaterp) { texture_floaterp->setOnFloaterCommitCallback(boost::bind(&LLTextureCtrl::onFloaterCommit, this, _1, _2, _3, _4, _5)); - } - if (texture_floaterp) - { texture_floaterp->setSetImageAssetIDCallback(boost::bind(&LLTextureCtrl::setImageAssetID, this, _1)); + texture_floaterp->setLocalTextureEnabled(mAllowLocalTexture); texture_floaterp->setBakeTextureEnabled(mBakeTextureEnabled && mInventoryPickType != PICK_MATERIAL); } @@ -1892,12 +1905,6 @@ void LLTextureCtrl::showPicker(bool take_focus) floaterp->openFloater(); } - LLFloaterTexturePicker* picker_floater = dynamic_cast(floaterp); - if (picker_floater) - { - picker_floater->setLocalTextureEnabled(mAllowLocalTexture); - } - if (take_focus) { floaterp->setFocus(true); @@ -2077,7 +2084,17 @@ void LLTextureCtrl::setOnTextureSelectedCallback(texture_selected_callback cb) } } -void LLTextureCtrl::setImageAssetName(const std::string& name) +void LLTextureCtrl::setAllowLocalTexture(bool b) +{ + mAllowLocalTexture = b; + LLFloaterTexturePicker* picker_floater = dynamic_cast(mFloaterHandle.get()); + if (picker_floater) + { + picker_floater->setLocalTextureEnabled(mAllowLocalTexture); + } +} + +void LLTextureCtrl::setImageAssetName(const std::string& name) { LLPointer imagep = LLUI::getUIImage(name); if(imagep) @@ -2102,6 +2119,7 @@ void LLTextureCtrl::setImageAssetID( const LLUUID& asset_id ) if( floaterp && getEnabled() ) { floaterp->setImageID( asset_id ); + floaterp->setTentative(getTentative()); floaterp->resetDirty(); } } diff --git a/indra/newview/lltexturectrl.h b/indra/newview/lltexturectrl.h index df5e763139b..79957431b73 100644 --- a/indra/newview/lltexturectrl.h +++ b/indra/newview/lltexturectrl.h @@ -167,6 +167,8 @@ class LLTextureCtrl // LLUICtrl interface void clear() override; + void setTentative(bool b) override; + // Takes a UUID, wraps get/setImageAssetID void setValue(const LLSD& value) override; LLSD getValue() const override; @@ -181,7 +183,7 @@ class LLTextureCtrl void setAllowNoTexture( bool b ) { mAllowNoTexture = b; } bool getAllowNoTexture() const { return mAllowNoTexture; } - void setAllowLocalTexture(bool b) { mAllowLocalTexture = b; } + void setAllowLocalTexture(bool b); bool getAllowLocalTexture() const { return mAllowLocalTexture; } const LLUUID& getImageItemID() { return mImageItemID; } @@ -405,7 +407,6 @@ class LLFloaterTexturePicker : public LLFloater LLUIImagePtr mFallbackImage; // What to show if currently selected texture is null. LLUUID mDefaultImageAssetID; LLUUID mBlankImageAssetID; - bool mTentative; bool mAllowNoTexture; LLUUID mSpecialCurrentImageAssetID; // Used when the asset id has no corresponding texture in the user's inventory. LLUUID mOriginalImageAssetID; @@ -456,6 +457,7 @@ class LLFloaterTexturePicker : public LLFloater set_on_update_image_stats_callback mOnUpdateImageStatsCallback; bool mBakeTextureEnabled; + bool mLocalTextureEnabled; static S32 sLastPickerMode; }; diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 087761cbd0c..cc187a1f989 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -2484,7 +2484,7 @@ LLTextureFetch::~LLTextureFetch() } S32 LLTextureFetch::createRequest(FTType f_type, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, - S32 w, S32 h, S32 c, S32 desired_discard, bool needs_aux, bool can_use_http) + S32 w, S32 h, S32 c, S32 desired_discard, bool needs_aux, bool can_use_http) { LL_PROFILE_ZONE_SCOPED; if (mDebugPause) @@ -2496,13 +2496,13 @@ S32 LLTextureFetch::createRequest(FTType f_type, const std::string& url, const L { LL_DEBUGS("Avatar") << " requesting " << id << " " << w << "x" << h << " discard " << desired_discard << " type " << f_type << LL_ENDL; } - LLTextureFetchWorker* worker = getWorker(id) ; + LLTextureFetchWorker* worker = getWorker(id); if (worker) { if (worker->mHost != host) { LL_WARNS(LOG_TXT) << "LLTextureFetch::createRequest " << id << " called with multiple hosts: " - << host << " != " << worker->mHost << LL_ENDL; + << host << " != " << worker->mHost << LL_ENDL; removeRequest(worker, true); worker = NULL; return CREATE_REQUEST_ERROR_MHOSTS; @@ -2538,7 +2538,7 @@ S32 LLTextureFetch::createRequest(FTType f_type, const std::string& url, const L // we really do get it.) desired_size = MAX_IMAGE_DATA_SIZE; } - else if (w*h*c > 0) + else if (w * h * c > 0) { // If the requester knows the dimensions of the image, // this will calculate how much data we need without having to parse the header @@ -2596,14 +2596,15 @@ S32 LLTextureFetch::createRequest(FTType f_type, const std::string& url, const L worker->lockWorkMutex(); // +Mw worker->mActiveCount++; worker->mNeedsAux = needs_aux; - worker->setCanUseHTTP(can_use_http) ; + worker->setCanUseHTTP(can_use_http); worker->unlockWorkMutex(); // -Mw } LL_DEBUGS(LOG_TXT) << "REQUESTED: " << id << " f_type " << fttype_to_string(f_type) - << " Discard: " << desired_discard << " size " << desired_size << LL_ENDL; + << " Discard: " << desired_discard << " size " << desired_size << LL_ENDL; return desired_discard; } + // Threads: T* // // protected diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index 8ab90896dcb..851d6c11a0b 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -85,8 +85,9 @@ class LLTextureFetch : public LLWorkerThread }; // Threads: T* (but Tmain mostly) + // returns discard on success, fail code otherwise S32 createRequest(FTType f_type, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, - S32 w, S32 h, S32 c, S32 discard, bool needs_aux, bool can_use_http); + S32 w, S32 h, S32 c, S32 discard, bool needs_aux, bool can_use_http); // Requests that a fetch operation be deleted from the queue. // If @cancel is true, also stops any I/O operations pending. diff --git a/indra/newview/llthumbnailctrl.cpp b/indra/newview/llthumbnailctrl.cpp index ae21d3e733f..b077262f062 100644 --- a/indra/newview/llthumbnailctrl.cpp +++ b/indra/newview/llthumbnailctrl.cpp @@ -111,7 +111,9 @@ void LLThumbnailCtrl::draw() gl_draw_scaled_image( draw_rect.mLeft, draw_rect.mBottom, draw_rect.getWidth(), draw_rect.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); - mTexturep->setKnownDrawSize(draw_rect.getWidth(), draw_rect.getHeight()); + // Thumbnails are usually 256x256 or smaller, either report that or + // some high value to get image with higher priority + mTexturep->setKnownDrawSize(MAX_IMAGE_SIZE, MAX_IMAGE_SIZE); } else if( mImagep.notNull() ) { @@ -238,12 +240,8 @@ void LLThumbnailCtrl::initImage() { // Should it support baked textures? mTexturep = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, MIPMAP_YES, LLGLTexture::BOOST_THUMBNAIL); - mTexturep->forceToSaveRawImage(0); - - S32 desired_draw_width = MAX_IMAGE_SIZE; - S32 desired_draw_height = MAX_IMAGE_SIZE; - mTexturep->setKnownDrawSize(desired_draw_width, desired_draw_height); + mTexturep->setKnownDrawSize(MAX_IMAGE_SIZE, MAX_IMAGE_SIZE); } } else if (tvalue.isString()) diff --git a/indra/newview/lltoast.cpp b/indra/newview/lltoast.cpp index 84854a79d4e..0f871dc1bb5 100644 --- a/indra/newview/lltoast.cpp +++ b/indra/newview/lltoast.cpp @@ -421,7 +421,7 @@ void LLToast::setVisible(bool show) if(mHideBtn) mHideBtn->setVisible(show); } - LLFloater::setVisible(show); + LLModalDialog::setVisible(show); if (mPanel && !mPanel->isDead() && mWrapperPanel diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp index ff4fcc2b0b6..b4a5955be30 100644 --- a/indra/newview/lltooldraganddrop.cpp +++ b/indra/newview/lltooldraganddrop.cpp @@ -1125,28 +1125,33 @@ void set_texture_to_material(LLViewerObject* hit_obj, case LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR: default: { - material->setBaseColorId(asset_id); + material->setBaseColorId(asset_id, true); } break; case LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS: { - material->setOcclusionRoughnessMetallicId(asset_id); + material->setOcclusionRoughnessMetallicId(asset_id, true); } break; case LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE: { - material->setEmissiveId(asset_id); + material->setEmissiveId(asset_id, true); } break; case LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL: { - material->setNormalId(asset_id); + material->setNormalId(asset_id, true); } break; } + // Update viewer side, needed for updating mSavedGLTFOverrideMaterials. + // Also for parity, we are immediately setting textures and materials, + // so we should immediate set overrides to. + hit_obj->setTEGLTFMaterialOverride(hit_face, material); + // update server LLGLTFMaterialList::queueModify(hit_obj, hit_face, material); } @@ -2156,7 +2161,7 @@ EAcceptance LLToolDragAndDrop::dad3dRezAttachmentFromInv( { if(mSource == SOURCE_LIBRARY) { - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, (LLViewerJointAttachment*)0)); + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, (LLViewerJointAttachment*)0, false)); copy_inventory_item( gAgent.getID(), item->getPermissions().getOwner(), @@ -2167,7 +2172,7 @@ EAcceptance LLToolDragAndDrop::dad3dRezAttachmentFromInv( } else { - rez_attachment(item, 0); + rez_attachment(item, 0, false); } } return ACCEPT_YES_SINGLE; diff --git a/indra/newview/lltoolpie.cpp b/indra/newview/lltoolpie.cpp index 0fd9faab350..0a69be528f8 100644 --- a/indra/newview/lltoolpie.cpp +++ b/indra/newview/lltoolpie.cpp @@ -1549,7 +1549,13 @@ bool LLToolPie::shouldAllowFirstMediaInteraction(const LLPickInfo& pick, bool mo } // Further object detail required beyond this point - LLPermissions* perms = LLSelectMgr::getInstance()->getHoverNode()->mPermissions; + LLSelectNode* hover_node = LLSelectMgr::instance().getHoverNode(); + if (hover_node == nullptr) + { + LL_WARNS() << "No Hover node" << LL_ENDL; + return false; + } + LLPermissions* perms = hover_node->mPermissions; if(perms == nullptr) { LL_WARNS() << "LLSelectMgr::getInstance()->getHoverNode()->mPermissions is NULL" << LL_ENDL; diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 7ef2c8d6979..7d5386110d4 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -62,7 +62,8 @@ LLResourceUploadInfo::LLResourceUploadInfo(LLTransactionID transactId, LLAssetType::EType assetType, std::string name, std::string description, S32 compressionInfo, LLFolderType::EType destinationType, LLInventoryType::EType inventoryType, U32 nextOWnerPerms, - U32 groupPerms, U32 everyonePerms, S32 expectedCost, bool showInventory) : + U32 groupPerms, U32 everyonePerms, S32 expectedCost, + const LLUUID& destFolderId, bool showInventory) : mTransactionId(transactId), mAssetType(assetType), mName(name), @@ -75,7 +76,7 @@ LLResourceUploadInfo::LLResourceUploadInfo(LLTransactionID transactId, mEveryonePerms(everyonePerms), mExpectedUploadCost(expectedCost), mShowInventory(showInventory), - mFolderId(LLUUID::null), + mFolderId(destFolderId), mItemId(LLUUID::null), mAssetId(LLAssetID::null) { } @@ -84,7 +85,8 @@ LLResourceUploadInfo::LLResourceUploadInfo(LLTransactionID transactId, LLResourceUploadInfo::LLResourceUploadInfo(std::string name, std::string description, S32 compressionInfo, LLFolderType::EType destinationType, LLInventoryType::EType inventoryType, - U32 nextOWnerPerms, U32 groupPerms, U32 everyonePerms, S32 expectedCost, bool showInventory) : + U32 nextOWnerPerms, U32 groupPerms, U32 everyonePerms, S32 expectedCost, + const LLUUID& destFolderId, bool showInventory) : mName(name), mDescription(description), mCompressionInfo(compressionInfo), @@ -97,7 +99,7 @@ LLResourceUploadInfo::LLResourceUploadInfo(std::string name, mShowInventory(showInventory), mTransactionId(), mAssetType(LLAssetType::AT_NONE), - mFolderId(LLUUID::null), + mFolderId(destFolderId), mItemId(LLUUID::null), mAssetId(LLAssetID::null) { @@ -306,10 +308,10 @@ void LLResourceUploadInfo::assignDefaults() } else { - mFolderId = gInventory.findUserDefinedCategoryUUIDForType( - (mDestinationFolderType == LLFolderType::FT_NONE) ? - (LLFolderType::EType)mAssetType : mDestinationFolderType); -} + mFolderId = gInventory.findUserDefinedCategoryUUIDForType( + (mDestinationFolderType == LLFolderType::FT_NONE) ? + (LLFolderType::EType)mAssetType : mDestinationFolderType); + } } std::string LLResourceUploadInfo::getDisplayName() const @@ -366,10 +368,12 @@ LLNewFileResourceUploadInfo::LLNewFileResourceUploadInfo( U32 groupPerms, U32 everyonePerms, S32 expectedCost, + const LLUUID& destFolderId, bool show_inventory) : LLResourceUploadInfo(name, description, compressionInfo, destinationType, inventoryType, - nextOWnerPerms, groupPerms, everyonePerms, expectedCost, show_inventory), + nextOWnerPerms, groupPerms, everyonePerms, expectedCost, + destFolderId, show_inventory), mFileName(fileName), mMaxImageSize(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT) { @@ -580,12 +584,13 @@ LLNewBufferedResourceUploadInfo::LLNewBufferedResourceUploadInfo( U32 groupPerms, U32 everyonePerms, S32 expectedCost, + const LLUUID& destFolderId, bool show_inventory, uploadFinish_f finish, uploadFailure_f failure) : LLResourceUploadInfo(name, description, compressionInfo, destinationType, inventoryType, - nextOWnerPerms, groupPerms, everyonePerms, expectedCost, show_inventory) + nextOWnerPerms, groupPerms, everyonePerms, expectedCost, destFolderId, show_inventory) , mBuffer(buffer) , mFinishFn(finish) , mFailureFn(failure) diff --git a/indra/newview/llviewerassetupload.h b/indra/newview/llviewerassetupload.h index 365436ede0a..c627e9dbb80 100644 --- a/indra/newview/llviewerassetupload.h +++ b/indra/newview/llviewerassetupload.h @@ -54,6 +54,7 @@ class LLResourceUploadInfo U32 groupPerms, U32 everyonePerms, S32 expectedCost, + const LLUUID &destFolderId = LLUUID::null, bool showInventory = true); virtual ~LLResourceUploadInfo() @@ -104,6 +105,7 @@ class LLResourceUploadInfo U32 groupPerms, U32 everyonePerms, S32 expectedCost, + const LLUUID& destFolderId = LLUUID::null, bool showInventory = true); LLResourceUploadInfo( @@ -155,6 +157,7 @@ class LLNewFileResourceUploadInfo : public LLResourceUploadInfo U32 groupPerms, U32 everyonePerms, S32 expectedCost, + const LLUUID &destFolderId = LLUUID::null, bool show_inventory = true); virtual LLSD prepareUpload(); @@ -193,6 +196,7 @@ class LLNewBufferedResourceUploadInfo : public LLResourceUploadInfo U32 groupPerms, U32 everyonePerms, S32 expectedCost, + const LLUUID& destFolderId, // use null for default bool show_inventory, uploadFinish_f finish, uploadFailure_f failure); @@ -219,6 +223,7 @@ class LLBufferedAssetUploadInfo : public LLResourceUploadInfo typedef std::function taskUploadFinish_f; typedef std::function uploadFailed_f; + // destFolderId is the folder to put the new item in, leave null for default LLBufferedAssetUploadInfo(LLUUID itemId, LLAssetType::EType assetType, std::string buffer, invnUploadFinish_f finish, uploadFailed_f failed); LLBufferedAssetUploadInfo(LLUUID itemId, LLPointer image, invnUploadFinish_f finish); LLBufferedAssetUploadInfo(LLUUID taskId, LLUUID itemId, LLAssetType::EType assetType, std::string buffer, taskUploadFinish_f finish, uploadFailed_f failed); diff --git a/indra/newview/llviewerattachmenu.cpp b/indra/newview/llviewerattachmenu.cpp index f2557e299cc..9828ab1fdff 100644 --- a/indra/newview/llviewerattachmenu.cpp +++ b/indra/newview/llviewerattachmenu.cpp @@ -116,12 +116,12 @@ void LLViewerAttachMenu::attachObjects(const uuid_vec_t& items, const std::strin LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getLinkedItem(id); if(item && gInventory.isObjectDescendentOf(id, gInventory.getRootFolderID())) { - rez_attachment(item, attachmentp); // don't replace if called from an "Attach To..." menu + rez_attachment(item, attachmentp, false); // don't replace if called from an "Attach To..." menu } else if(item && item->isFinished()) { // must be in library. copy it to our inventory and put it on. - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, attachmentp)); + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, attachmentp, false)); copy_inventory_item(gAgent.getID(), item->getPermissions().getOwner(), item->getUUID(), diff --git a/indra/newview/llviewercamera.cpp b/indra/newview/llviewercamera.cpp index 89c28ee2f9a..511293a8089 100644 --- a/indra/newview/llviewercamera.cpp +++ b/indra/newview/llviewercamera.cpp @@ -83,18 +83,16 @@ LLViewerCamera::LLViewerCamera() : LLCamera() } } -void LLViewerCamera::updateCameraLocation(const LLVector3 ¢er, const LLVector3 &up_direction, const LLVector3 &point_of_interest) +bool LLViewerCamera::updateCameraLocation(const LLVector3 ¢er, const LLVector3 &up_direction, const LLVector3 &point_of_interest) { // do not update if avatar didn't move if (!LLViewerJoystick::getInstance()->getCameraNeedsUpdate()) { - return; + return true; } - LLVector3 last_position; - LLVector3 last_axis; - last_position = getOrigin(); - last_axis = getAtAxis(); + LLVector3 last_position = getOrigin(); + LLVector3 last_axis = getAtAxis(); mLastPointOfInterest = point_of_interest; @@ -104,30 +102,49 @@ void LLViewerCamera::updateCameraLocation(const LLVector3 ¢er, const LLVecto regp = gAgent.getRegion(); } - F32 water_height = (NULL != regp) ? regp->getWaterHeight() : 0.f; + F32 water_height = regp ? regp->getWaterHeight() : 0.f; LLVector3 origin = center; + // Move origin[VZ] far enough (up or down) from the water surface + static const F32 MIN_DIST_TO_WATER = 0.2f; + F32& zpos = origin.mV[VZ]; + if (zpos < water_height + MIN_DIST_TO_WATER) { - if (origin.mV[2] > water_height) + if (zpos >= water_height) { - origin.mV[2] = llmax(origin.mV[2], water_height + 0.20f); + zpos = water_height + MIN_DIST_TO_WATER; } - else + else if (zpos > water_height - MIN_DIST_TO_WATER) { - origin.mV[2] = llmin(origin.mV[2], water_height - 0.20f); + zpos = water_height - MIN_DIST_TO_WATER; } } - setOriginAndLookAt(origin, up_direction, point_of_interest); + LLVector3 at(point_of_interest - origin); + at.normalize(); + if (at.isNull() || !at.isFinite()) + return false; + + LLVector3 left(up_direction % at); + left.normalize(); + if (left.isNull() || !left.isFinite()) + return false; + + LLVector3 up = at % left; + up.normalize(); + if (up.isNull() || !up.isFinite()) + return false; + + setOrigin(origin); + setAxes(at, left, up); mVelocityDir = origin - last_position ; F32 dpos = mVelocityDir.normVec() ; LLQuaternion rotation; rotation.shortestArc(last_axis, getAtAxis()); - F32 x, y, z; - F32 drot; + F32 drot, x, y, z; rotation.getAngleAxis(&drot, &x, &y, &z); add(sVelocityStat, dpos); @@ -140,6 +157,8 @@ void LLViewerCamera::updateCameraLocation(const LLVector3 ¢er, const LLVecto // update pixel meter ratio using default fov, not modified one mPixelMeterRatio = (F32)(getViewHeightInPixels()/ (2.f*tanf(mCameraFOVDefault*0.5f))); // update screen pixel area + + return true; mScreenPixelArea =(S32)((F32)getViewHeightInPixels() * ((F32)getViewHeightInPixels() * getAspect())); } @@ -147,7 +166,6 @@ const LLMatrix4 &LLViewerCamera::getProjection() const { calcProjection(getFar()); return mProjectionMatrix; - } const LLMatrix4 &LLViewerCamera::getModelview() const @@ -160,13 +178,12 @@ const LLMatrix4 &LLViewerCamera::getModelview() const void LLViewerCamera::calcProjection(const F32 far_distance) const { - F32 fov_y, z_far, z_near, aspect, f; - fov_y = getView(); - z_far = far_distance; - z_near = getNear(); - aspect = getAspect(); + F32 fov_y = getView(); + F32 z_far = far_distance; + F32 z_near = getNear(); + F32 aspect = getAspect(); - f = 1/tan(fov_y*0.5f); + F32 f = 1 / tan(fov_y * 0.5f); mProjectionMatrix.setZero(); mProjectionMatrix.mMatrix[0][0] = f/aspect; @@ -272,9 +289,9 @@ void LLViewerCamera::updateFrustumPlanes(LLCamera& camera, bool ortho, bool zfli } void LLViewerCamera::setPerspective(bool for_selection, - S32 x, S32 y_from_bot, S32 width, S32 height, - bool limit_select_distance, - F32 z_near, F32 z_far) + S32 x, S32 y_from_bot, S32 width, S32 height, + bool limit_select_distance, + F32 z_near, F32 z_far) { F32 fov_y, aspect; fov_y = getView(); @@ -336,7 +353,7 @@ void LLViewerCamera::setPerspective(bool for_selection, { float offset = mZoomFactor - 1.f; int pos_y = mZoomSubregion / llceil(mZoomFactor); - int pos_x = mZoomSubregion - (pos_y*llceil(mZoomFactor)); + int pos_x = mZoomSubregion - (pos_y * llceil(mZoomFactor)); glm::mat4 translate; translate = glm::translate(glm::vec3(offset - (F32)pos_x * 2.f, offset - (F32)pos_y * 2.f, 0.f)); @@ -349,7 +366,7 @@ void LLViewerCamera::setPerspective(bool for_selection, calcProjection(z_far); // Update the projection matrix cache - proj_mat *= glm::perspective(fov_y,aspect,z_near,z_far); + proj_mat *= glm::perspective(fov_y, aspect, z_near, z_far); gGL.loadMatrix(glm::value_ptr(proj_mat)); @@ -357,7 +374,7 @@ void LLViewerCamera::setPerspective(bool for_selection, gGL.matrixMode(LLRender::MM_MODELVIEW); - glm::mat4 modelview(glm::make_mat4((GLfloat*) OGL_TO_CFR_ROTATION)); + glm::mat4 modelview(glm::make_mat4((GLfloat*)OGL_TO_CFR_ROTATION)); GLfloat ogl_matrix[16]; @@ -373,9 +390,9 @@ void LLViewerCamera::setPerspective(bool for_selection, // however, it is also unused (the GL matricies are used for selection, (see LLCamera::sphereInFrustum())) and so i'm not // comfortable hacking on it. calculateFrustumPlanesFromWindow((F32)(x - width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, - (F32)(y_from_bot - height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f, - (F32)(x + width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, - (F32)(y_from_bot + height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f); + (F32)(y_from_bot - height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f, + (F32)(x + width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, + (F32)(y_from_bot + height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f); } @@ -389,7 +406,6 @@ void LLViewerCamera::setPerspective(bool for_selection, updateFrustumPlanes(*this); } - // Uses the last GL matrices set in set_perspective to project a point from // screen coordinates to the agent's region. void LLViewerCamera::projectScreenToPosAgent(const S32 screen_x, const S32 screen_y, LLVector3* pos_agent) const diff --git a/indra/newview/llviewercamera.h b/indra/newview/llviewercamera.h index 91d26f09f27..cb0994be0a8 100644 --- a/indra/newview/llviewercamera.h +++ b/indra/newview/llviewercamera.h @@ -60,7 +60,7 @@ class alignas(16) LLViewerCamera : public LLCamera, public LLSimpleton* getVelocityStat() {return &sVelocityStat; } static LLTrace::CountStatHandle<>* getAngularVelocityStat() {return &sAngularVelocityStat; } - F32 getCosHalfFov() {return mCosHalfCameraFOV;} - F32 getAverageSpeed() {return mAverageSpeed ;} - F32 getAverageAngularSpeed() {return mAverageAngularSpeed;} void getPixelVectors(const LLVector3 &pos_agent, LLVector3 &up, LLVector3 &right); LLVector3 roundToPixel(const LLVector3 &pos_agent); @@ -88,21 +88,21 @@ class alignas(16) LLViewerCamera : public LLCamera, public LLSimpletongetPreferredType()) - && (updates.size() != 1 || !updates.has("thumbnail"))) + && (updates.size() != 1 + || !(updates.has("thumbnail") || updates.has("favorite")))) { LLNotificationsUtil::add("CannotModifyProtectedCategories"); return; diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h index 21a66062539..0dfbf0cceda 100644 --- a/indra/newview/llviewerinventory.h +++ b/indra/newview/llviewerinventory.h @@ -232,8 +232,8 @@ class LLViewerInventoryCategory : public LLInventoryCategory // How many descendents do we currently have information for in the InventoryModel? S32 getViewerDescendentCount() const; - LLSD exportLLSD() const; - bool importLLSD(const LLSD& cat_data); + virtual void exportLLSD(LLSD &sd) const; + virtual bool importLLSD(const std::string& label, const LLSD& value); void determineFolderType(); void changeType(LLFolderType::EType new_folder_type); @@ -264,7 +264,7 @@ class LLInventoryCallback : public LLRefCount class LLViewerJointAttachment; -void rez_attachment_cb(const LLUUID& inv_item, LLViewerJointAttachment *attachmentp); +void rez_attachment_cb(const LLUUID& inv_item, LLViewerJointAttachment *attachmentp, bool replace); void activate_gesture_cb(const LLUUID& inv_item); diff --git a/indra/newview/llviewerjoystick.cpp b/indra/newview/llviewerjoystick.cpp index 7543fb37439..9d45ea24b9a 100644 --- a/indra/newview/llviewerjoystick.cpp +++ b/indra/newview/llviewerjoystick.cpp @@ -881,6 +881,10 @@ void LLViewerJoystick::moveObjects(bool reset) { gAgent.clearAFK(); } + else + { + gAwayTriggerTimer.reset(); + } if (sDelta[0] || sDelta[1] || sDelta[2]) { @@ -1055,6 +1059,10 @@ void LLViewerJoystick::moveAvatar(bool reset) { gAgent.clearAFK(); } + else + { + gAwayTriggerTimer.reset(); + } setCameraNeedsUpdate(true); } @@ -1267,10 +1275,17 @@ void LLViewerJoystick::moveFlycam(bool reset) } // Clear AFK state if moved beyond the deadzone - if (!is_zero && gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) + if (!is_zero) + { + if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) { gAgent.clearAFK(); } + else + { + gAwayTriggerTimer.reset(); + } + } sFlycamPosition += LLVector3(sDelta) * sFlycamRotation; @@ -1331,6 +1346,10 @@ bool LLViewerJoystick::toggleFlycam() { gAgent.clearAFK(); } + else + { + gAwayTriggerTimer.reset(); + } mOverrideCamera = !mOverrideCamera; if (mOverrideCamera) diff --git a/indra/newview/llviewermedia_streamingaudio.cpp b/indra/newview/llviewermedia_streamingaudio.cpp index b68ffbe1a23..4a1c433f8e3 100644 --- a/indra/newview/llviewermedia_streamingaudio.cpp +++ b/indra/newview/llviewermedia_streamingaudio.cpp @@ -60,13 +60,26 @@ void LLStreamingAudio_MediaPlugins::start(const std::string& url) if(!mMediaPlugin) return; - if (!url.empty()) { + if (!url.empty()) + { LL_INFOS() << "Starting internet stream: " << url << LL_ENDL; - mURL = url; - mMediaPlugin->loadURI ( url ); + + mURL = url; // keep original url here for comparison purposes + std::string snt_url = url; + LLStringUtil::trim(snt_url); + size_t pos = snt_url.find(' '); + if (pos != std::string::npos) + { + // fmod permited having names after the url and people were using it. + // People label their streams this way, ignore the 'label'. + snt_url = snt_url.substr(0, pos); + } + mMediaPlugin->loadURI(snt_url); mMediaPlugin->start(); LL_INFOS() << "Playing stream..." << LL_ENDL; - } else { + } + else + { LL_INFOS() << "setting stream to NULL"<< LL_ENDL; mURL.clear(); mMediaPlugin->stop(); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 44157d2d2da..d66da27bc71 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -85,6 +85,7 @@ #include "lltoolface.h" #include "llhints.h" #include "llhudeffecttrail.h" +#include "llhudeffectresetskeleton.h" #include "llhudmanager.h" #include "llimview.h" #include "llinventorybridge.h" @@ -1847,7 +1848,6 @@ class LLAdvancedAppearanceToXML : public view_listener_t { bool handleEvent(const LLSD& userdata) { - std::string emptyname; LLViewerObject *obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); LLVOAvatar *avatar = NULL; if (obj) @@ -1874,7 +1874,7 @@ class LLAdvancedAppearanceToXML : public view_listener_t } if (avatar) { - avatar->dumpArchetypeXML(emptyname); + avatar->dumpArchetypeXML(LLStringUtil::null); } return true; } @@ -5644,6 +5644,38 @@ class LLToolsEnablePathfindingRebakeRegion : public view_listener_t } }; +class LLToolsCheckSelectionLODMode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string param = userdata.asString(); + static LLCachedControl debug_selection_lods(gSavedSettings, "DebugSelectionLODs", 0); + if ("default" == param) + { + return debug_selection_lods() < 0; + } + else if ("high" == param) + { + return debug_selection_lods() == 3; + } + else if ("medium" == param) + { + return debug_selection_lods() == 2; + } + else if ("low" == param) + { + return debug_selection_lods() == 1; + } + else if ("lowest" == param) + { + return debug_selection_lods() == 0; + } + + return false; + } +}; + + // Round the position of all root objects to the grid class LLToolsSnapObjectXY : public view_listener_t { @@ -6619,8 +6651,18 @@ class LLAvatarResetSkeleton : public view_listener_t { if (LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject())) { + if(avatar->getID() == gAgentID) + { + LLHUDEffectResetSkeleton* effectp = (LLHUDEffectResetSkeleton*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_RESET_SKELETON, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject((LLViewerObject*)avatar); + effectp->setResetAnimations(false); + } + else + { avatar->resetSkeleton(false); } + } return true; } }; @@ -6643,8 +6685,18 @@ class LLAvatarResetSkeletonAndAnimations : public view_listener_t { if (LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject())) { + if(avatar->getID() == gAgentID) + { + LLHUDEffectResetSkeleton* effectp = (LLHUDEffectResetSkeleton*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_RESET_SKELETON, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject((LLViewerObject*)avatar); + effectp->setResetAnimations(true); + } + else + { avatar->resetSkeleton(true); } + } return true; } }; @@ -6671,11 +6723,24 @@ class LLAvatarResetSelfSkeletonAndAnimations : public view_listener_t { if (LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject())) { - avatar->resetSkeleton(true); + if(avatar->getID() == gAgentID) + { + LLHUDEffectResetSkeleton* effectp = (LLHUDEffectResetSkeleton*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_RESET_SKELETON, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject((LLViewerObject*)avatar); + effectp->setResetAnimations(true); + } + else + { + avatar->resetSkeleton(true); + } } else { - gAgentAvatarp->resetSkeleton(true); + LLHUDEffectResetSkeleton* effectp = (LLHUDEffectResetSkeleton*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_RESET_SKELETON, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject(gAgentAvatarp); + effectp->setResetAnimations(true); } return true; } @@ -8468,6 +8533,36 @@ class LLToolsSelectBySurrounding : public view_listener_t } }; +class LLToolsSelectionLODMode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string param = userdata.asString(); + if ("default" == param) + { + gSavedSettings.setS32("DebugSelectionLODs", -1); + } + else if ("high" == param) + { + gSavedSettings.setS32("DebugSelectionLODs", 3); + } + else if ("medium" == param) + { + gSavedSettings.setS32("DebugSelectionLODs", 2); + } + else if ("low" == param) + { + gSavedSettings.setS32("DebugSelectionLODs", 1); + } + else if ("lowest" == param) + { + gSavedSettings.setS32("DebugSelectionLODs", 0); + } + + return true; + } +}; + class LLToolsShowHiddenSelection : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -9707,6 +9802,7 @@ void initialize_menus() view_listener_t::addMenu(new LLToolsSelectInvisibleObjects(), "Tools.SelectInvisibleObjects"); view_listener_t::addMenu(new LLToolsSelectReflectionProbes(), "Tools.SelectReflectionProbes"); view_listener_t::addMenu(new LLToolsSelectBySurrounding(), "Tools.SelectBySurrounding"); + view_listener_t::addMenu(new LLToolsSelectionLODMode(), "Tools.SelectionLODMode"); view_listener_t::addMenu(new LLToolsShowHiddenSelection(), "Tools.ShowHiddenSelection"); view_listener_t::addMenu(new LLToolsShowSelectionLightRadius(), "Tools.ShowSelectionLightRadius"); view_listener_t::addMenu(new LLToolsEditLinkedParts(), "Tools.EditLinkedParts"); @@ -9739,6 +9835,7 @@ void initialize_menus() view_listener_t::addMenu(new LLToolsEnablePathfindingView(), "Tools.EnablePathfindingView"); view_listener_t::addMenu(new LLToolsDoPathfindingRebakeRegion(), "Tools.DoPathfindingRebakeRegion"); view_listener_t::addMenu(new LLToolsEnablePathfindingRebakeRegion(), "Tools.EnablePathfindingRebakeRegion"); + view_listener_t::addMenu(new LLToolsCheckSelectionLODMode(), "Tools.ToolsCheckSelectionLODMode"); // Help menu // most items use the ShowFloater method diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 84195997c36..36954780610 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -69,6 +69,7 @@ #include "llviewerassetupload.h" // linden libraries +#include "llfilesystem.h" #include "llnotificationsutil.h" #include "llsdserialize.h" #include "llsdutil.h" @@ -478,13 +479,19 @@ const bool check_file_extension(const std::string& filename, LLFilePicker::ELoad return true; } -const void upload_single_file(const std::vector& filenames, LLFilePicker::ELoadFilter type) +void upload_single_file( + const std::vector& filenames, + LLFilePicker::ELoadFilter type, + const LLUUID& dest) { std::string filename = filenames[0]; if (!check_file_extension(filename, type)) return; if (!filename.empty()) { + LLSD args; + args["filename"] = filename; + args["dest"] = dest; if (type == LLFilePicker::FFLOAD_WAV) { // pre-qualify wavs to make sure the format is acceptable @@ -499,12 +506,12 @@ const void upload_single_file(const std::vector& filenames, LLFileP } else { - LLFloaterReg::showInstance("upload_sound", LLSD(filename)); + LLFloaterReg::showInstance("upload_sound", args); } } if (type == LLFilePicker::FFLOAD_IMAGE) { - LLFloaterReg::showInstance("upload_image", LLSD(filename)); + LLFloaterReg::showInstance("upload_image", args); } if (type == LLFilePicker::FFLOAD_ANIM) { @@ -512,11 +519,11 @@ const void upload_single_file(const std::vector& filenames, LLFileP LLStringUtil::toLower(filename_lc); if (filename_lc.rfind(".anim") != std::string::npos) { - LLFloaterReg::showInstance("upload_anim_anim", LLSD(filename)); + LLFloaterReg::showInstance("upload_anim_anim", args); } else { - LLFloaterReg::showInstance("upload_anim_bvh", LLSD(filename)); + LLFloaterReg::showInstance("upload_anim_bvh", args); } } } @@ -544,16 +551,9 @@ void do_bulk_upload(std::vector filenames, bool allow_2k) if (LLResourceUploadInfo::findAssetTypeAndCodecOfExtension(ext, asset_type, codec)) { bool resource_upload = false; - if (asset_type == LLAssetType::AT_TEXTURE && allow_2k) + if (asset_type == LLAssetType::AT_TEXTURE) { - LLPointer image_frmted = LLImageFormatted::createFromType(codec); - if (gDirUtilp->fileExists(filename) && image_frmted && image_frmted->load(filename)) - { - S32 biased_width = LLImageRaw::biasedDimToPowerOfTwo(image_frmted->getWidth(), LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); - S32 biased_height = LLImageRaw::biasedDimToPowerOfTwo(image_frmted->getHeight(), LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); - expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(biased_width, biased_height); - resource_upload = true; - } + resource_upload = true; } else if (LLAgentBenefitsMgr::current().findUploadCost(asset_type, expected_upload_cost)) { @@ -562,23 +562,115 @@ void do_bulk_upload(std::vector filenames, bool allow_2k) if (resource_upload) { - LLNewFileResourceUploadInfo* info_p = new LLNewFileResourceUploadInfo( - filename, - asset_name, - asset_name, 0, - LLFolderType::FT_NONE, LLInventoryType::IT_NONE, - LLFloaterPerms::getNextOwnerPerms("Uploads"), - LLFloaterPerms::getGroupPerms("Uploads"), - LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost); - - if (!allow_2k) + if (asset_type == LLAssetType::AT_TEXTURE) { - info_p->setMaxImageSize(1024); - } - LLResourceUploadInfo::ptr_t uploadInfo(info_p); + std::string exten = gDirUtilp->getExtension(filename); + U32 codec = LLImageBase::getCodecFromExtension(exten); + + // Load the image + LLPointer image = LLImageFormatted::createFromType(codec); + if (image.isNull()) + { + LL_WARNS() << "Failed to create image container for " << filename << LL_ENDL; + continue; + } + if (!image->load(filename)) + { + LL_WARNS() << "Failed to load image: " << filename << LL_ENDL; + continue; + } + // Decompress or expand it in a raw image structure + LLPointer raw_image = new LLImageRaw; + if (!image->decode(raw_image, 0.0f)) + { + LL_WARNS() << "Failed to decode image: " << filename << LL_ENDL; + continue; + } + // Check the image constraints + if ((image->getComponents() != 3) && (image->getComponents() != 4)) + { + LL_WARNS() << "Attempted to upload a texture that has " << image->getComponents() + << " components, but only 3 (RGB) or 4 (RGBA) are allowed." << LL_ENDL; + continue; + } + // Downscale images to fit the max_texture_dimensions_*, or 1024 if allow_2k is false + S32 max_width = allow_2k ? gSavedSettings.getS32("max_texture_dimension_X") : 1024; + S32 max_height = allow_2k ? gSavedSettings.getS32("max_texture_dimension_Y") : 1024; + + S32 orig_width = raw_image->getWidth(); + S32 orig_height = raw_image->getHeight(); + + if (orig_width > max_width || orig_height > max_height) + { + // Calculate scale factors + F32 width_scale = (F32)max_width / (F32)orig_width; + F32 height_scale = (F32)max_height / (F32)orig_height; + F32 scale = llmin(width_scale, height_scale); + + // Calculate new dimensions, preserving aspect ratio + S32 new_width = LLImageRaw::contractDimToPowerOfTwo(llclamp((S32)llroundf(orig_width * scale), 4, max_width)); + S32 new_height = LLImageRaw::contractDimToPowerOfTwo(llclamp((S32)llroundf(orig_height * scale), 4, max_height)); - upload_new_resource(uploadInfo); + if (!raw_image->scale(new_width, new_height)) + { + LL_WARNS() << "Failed to scale image from " << orig_width << "x" << orig_height << " to " << new_width << "x" + << new_height << LL_ENDL; + continue; + } + + // Inform the resident about the resized image + LLSD subs; + subs["[ORIGINAL_WIDTH]"] = orig_width; + subs["[ORIGINAL_HEIGHT]"] = orig_height; + subs["[NEW_WIDTH]"] = new_width; + subs["[NEW_HEIGHT]"] = new_height; + subs["[MAX_WIDTH]"] = max_width; + subs["[MAX_HEIGHT]"] = max_height; + LLNotificationsUtil::add("ImageUploadResized", subs); + } + + raw_image->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + + LLTransactionID tid; + tid.generate(); + LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); + + LLPointer formatted = new LLImageJ2C; + + if (formatted->encode(raw_image, 0.0f)) + { + LLFileSystem fmt_file(new_asset_id, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); + fmt_file.write(formatted->getData(), formatted->getDataSize()); + + LLResourceUploadInfo::ptr_t assetUploadInfo(new LLResourceUploadInfo( + tid, LLAssetType::AT_TEXTURE, + asset_name, + asset_name, 0, + LLFolderType::FT_NONE, LLInventoryType::IT_NONE, + LLFloaterPerms::getNextOwnerPerms("Uploads"), + LLFloaterPerms::getGroupPerms("Uploads"), + LLFloaterPerms::getEveryonePerms("Uploads"), + LLAgentBenefitsMgr::current().getTextureUploadCost(raw_image->getWidth(), raw_image->getHeight()) + )); + + upload_new_resource(assetUploadInfo); + } + } + else + { + LLNewFileResourceUploadInfo* info_p = new LLNewFileResourceUploadInfo( + filename, + asset_name, + asset_name, 0, + LLFolderType::FT_NONE, LLInventoryType::IT_NONE, + LLFloaterPerms::getNextOwnerPerms("Uploads"), + LLFloaterPerms::getGroupPerms("Uploads"), + LLFloaterPerms::getEveryonePerms("Uploads"), + expected_upload_cost); + LLResourceUploadInfo::ptr_t uploadInfo(info_p); + + upload_new_resource(uploadInfo); + } } } @@ -647,8 +739,31 @@ bool get_bulk_upload_expected_cost( LLPointer image_frmted = LLImageFormatted::createFromType(codec); if (gDirUtilp->fileExists(filename) && image_frmted && image_frmted->load(filename)) { - S32 biased_width = LLImageRaw::biasedDimToPowerOfTwo(image_frmted->getWidth(), LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); - S32 biased_height = LLImageRaw::biasedDimToPowerOfTwo(image_frmted->getHeight(), LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + S32 biased_width, biased_height; + + S32 max_width = allow_2k ? gSavedSettings.getS32("max_texture_dimension_X") : 1024; + S32 max_height = allow_2k ? gSavedSettings.getS32("max_texture_dimension_Y") : 1024; + + S32 orig_width = image_frmted->getWidth(); + S32 orig_height = image_frmted->getHeight(); + + if (orig_width > max_width || orig_height > max_height) + { + // Calculate scale factors + F32 width_scale = (F32)max_width / (F32)orig_width; + F32 height_scale = (F32)max_height / (F32)orig_height; + F32 scale = llmin(width_scale, height_scale); + + // Calculate new dimensions, preserving aspect ratio + biased_width = LLImageRaw::contractDimToPowerOfTwo(llclamp((S32)llroundf(orig_width * scale), 4, max_width)); + biased_height = LLImageRaw::contractDimToPowerOfTwo(llclamp((S32)llroundf(orig_height * scale), 4, max_height)); + } + else + { + biased_width = LLImageRaw::biasedDimToPowerOfTwo(orig_width, LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + biased_height = LLImageRaw::biasedDimToPowerOfTwo(orig_height, LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + } + total_cost += LLAgentBenefitsMgr::current().getTextureUploadCost(biased_width, biased_height); S32 area = biased_width * biased_height; if (area >= LLAgentBenefits::MIN_2K_TEXTURE_AREA) @@ -709,7 +824,7 @@ bool get_bulk_upload_expected_cost( return file_count > 0; } -const void upload_bulk(const std::vector& filtered_filenames, bool allow_2k) +void upload_bulk(const std::vector& filtered_filenames, bool allow_2k, const LLUUID& dest) { S32 expected_upload_cost; S32 expected_upload_count; @@ -721,6 +836,7 @@ const void upload_bulk(const std::vector& filtered_filenames, bool key["upload_cost"] = expected_upload_cost; key["upload_count"] = expected_upload_count; key["has_2k_textures"] = (textures_2k_count > 0); + key["dest"] = dest; LLSD array; for (const std::string& str : filtered_filenames) @@ -754,7 +870,7 @@ const void upload_bulk(const std::vector& filtered_filenames, bool } -const void upload_bulk(const std::vector& filenames, LLFilePicker::ELoadFilter type, bool allow_2k) +void upload_bulk(const std::vector& filenames, LLFilePicker::ELoadFilter type, bool allow_2k, const LLUUID& dest) { // TODO: // Check user balance for entire cost @@ -776,7 +892,7 @@ const void upload_bulk(const std::vector& filenames, LLFilePicker:: filtered_filenames.push_back(filename); } } - upload_bulk(filtered_filenames, allow_2k); + upload_bulk(filtered_filenames, allow_2k, dest); } class LLFileUploadImage : public view_listener_t @@ -787,7 +903,7 @@ class LLFileUploadImage : public view_listener_t { gAgentCamera.changeCameraToDefault(); } - LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2), LLFilePicker::FFLOAD_IMAGE, false); + LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2, LLUUID::null), LLFilePicker::FFLOAD_IMAGE, false); return true; } }; @@ -818,7 +934,7 @@ class LLFileUploadSound : public view_listener_t { gAgentCamera.changeCameraToDefault(); } - LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2), LLFilePicker::FFLOAD_WAV, false); + LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2, LLUUID::null), LLFilePicker::FFLOAD_WAV, false); return true; } }; @@ -831,7 +947,7 @@ class LLFileUploadAnim : public view_listener_t { gAgentCamera.changeCameraToDefault(); } - LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2), LLFilePicker::FFLOAD_ANIM, false); + LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2, LLUUID::null), LLFilePicker::FFLOAD_ANIM, false); return true; } }; @@ -844,7 +960,7 @@ class LLFileUploadBulk : public view_listener_t { gAgentCamera.changeCameraToDefault(); } - LLFilePickerReplyThread::startPicker(boost::bind(&upload_bulk, _1, _2, true), LLFilePicker::FFLOAD_ALL, true); + LLFilePickerReplyThread::startPicker(boost::bind(&upload_bulk, _1, _2, true, LLUUID::null), LLFilePicker::FFLOAD_ALL, true); return true; } }; @@ -1134,7 +1250,7 @@ LLUUID upload_new_resource( name, desc, compression_info, destination_folder_type, inv_type, next_owner_perms, group_perms, everyone_perms, - expected_upload_cost, show_inventory)); + expected_upload_cost, LLUUID::null, show_inventory)); upload_new_resource(uploadInfo, callback, userdata); return LLUUID::null; diff --git a/indra/newview/llviewermenufile.h b/indra/newview/llviewermenufile.h index d99f9dc4c6d..8f7df48a2e8 100644 --- a/indra/newview/llviewermenufile.h +++ b/indra/newview/llviewermenufile.h @@ -74,6 +74,17 @@ bool get_bulk_upload_expected_cost( void do_bulk_upload(std::vector filenames, bool allow_2k); +void upload_single_file( + const std::vector& filenames, + LLFilePicker::ELoadFilter type, + const LLUUID& dest); + +void upload_bulk( + const std::vector& filenames, + LLFilePicker::ELoadFilter type, + bool allow_2k, + const LLUUID& dest); + //consider moving all file pickers below to more suitable place class LLFilePickerThread : public LLThread { //multi-threaded file picker (runs system specific file picker in background and calls "notify" from main thread) diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 1501ba41c20..7d39cc60597 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -3052,6 +3052,11 @@ void process_agent_movement_complete(LLMessageSystem* msg, void**) } } +#ifdef LL_DISCORD + if (gSavedSettings.getBOOL("EnableDiscord")) + LLAppViewer::updateDiscordActivity(); +#endif + if ( LLTracker::isTracking(NULL) ) { // Check distance to beacon, if < 5m, remove beacon @@ -3189,6 +3194,7 @@ void send_agent_update(bool force_send, bool send_reliable) static F64 last_send_time = 0.0; static U32 last_control_flags = 0; + static bool control_flags_follow_up = false; static U8 last_render_state = 0; static U8 last_flags = AU_FLAGS_NONE; static LLQuaternion last_body_rot, @@ -3266,6 +3272,20 @@ void send_agent_update(bool force_send, bool send_reliable) break; } + // example: + // user taps crouch (control_flags 4128), viewer sends 4128 then immediately 0 + // server starts crouching motion but does not stop it, only once viewer sends 0 + // second time will server stop the motion. follow_up exists to make sure all + // states like 'crouch' motion are properly cleared server side. + // + // P.S. Server probably shouldn't require a reminder to stop a motion, + // but at the moment it does. + if (control_flags_follow_up) + { + send_update = true; + break; + } + // check translation constexpr F32 TRANSLATE_THRESHOLD = 0.01f; if ((last_camera_pos_agent - camera_pos_agent).magVec() > TRANSLATE_THRESHOLD) @@ -3399,6 +3419,7 @@ void send_agent_update(bool force_send, bool send_reliable) // remember last update data last_send_time = now; + control_flags_follow_up = last_control_flags != control_flags; last_control_flags = control_flags; last_render_state = render_state; last_flags = flags; @@ -3648,7 +3669,7 @@ void process_kill_object(LLMessageSystem *mesgsys, void **user_data) gObjectList.killObject(objectp); } - if(delete_object) + if(delete_object && regionp) { regionp->killCacheEntry(local_id); } diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 8d90187e91f..9e77b40a453 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -4184,8 +4184,11 @@ void LLViewerObject::boostTexturePriority(bool boost_children /* = true */) if (isSculpted() && !isMesh()) { LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); - LLUUID sculpt_id = sculpt_params->getSculptTexture(); - LLViewerTextureManager::getFetchedTexture(sculpt_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)->setBoostLevel(LLGLTexture::BOOST_SELECTED); + if (sculpt_params) + { + LLUUID sculpt_id = sculpt_params->getSculptTexture(); + LLViewerTextureManager::getFetchedTexture(sculpt_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)->setBoostLevel(LLGLTexture::BOOST_SELECTED); + } } if (boost_children) diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index a4f308bbf9c..d39d4662053 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -234,6 +234,8 @@ LLTrace::SampleStatHandle FRAMETIME_JITTER_EVENTS("frametimeevents", "Numbe FRAMETIME_JITTER_EVENTS_LAST_MINUTE("frametimeeventslastmin", "Number of frametime events in the last minute."); LLTrace::SampleStatHandle NOTRMALIZED_FRAMETIME_JITTER_SESSION("normalizedframetimejitter", "Normalized frametime jitter over the session."); +LLTrace::SampleStatHandle NFTV("nftv", "Normalized frametime variation."); +LLTrace::SampleStatHandle NORMALIZED_FRAMTIME_JITTER_PERIOD("normalizedframetimejitterperiod", "Normalized frametime jitter over the last 5 seconds."); LLTrace::EventStatHandle > AGENT_POSITION_SNAP("agentpositionsnap", "agent position corrections"); @@ -322,6 +324,8 @@ void LLViewerStats::updateFrameStats(const F64Seconds time_diff) sample(LLStatViewer::FRAMETIME_JITTER_CUMULATIVE, mTotalFrametimeJitter); sample(LLStatViewer::NOTRMALIZED_FRAMETIME_JITTER_SESSION, mTotalFrametimeJitter / mTotalTime); + mLastNoramlizedSessionJitter = mTotalFrametimeJitter / mTotalTime; + static LLCachedControl frameTimeEventThreshold(gSavedSettings, "StatsFrametimeEventThreshold", 0.1f); if (time_diff - mLastTimeDiff > mLastTimeDiff * frameTimeEventThreshold()) @@ -360,6 +364,27 @@ void LLViewerStats::updateFrameStats(const F64Seconds time_diff) sample(LLStatViewer::FRAMETIME_JITTER_99TH, ninety_ninth_percentile); sample(LLStatViewer::FRAMETIME_JITTER_95TH, ninety_fifth_percentile); + F64 averageFrameTime = 0; + for (const auto& frame_time : mFrameTimes) + { + averageFrameTime += frame_time.value(); + } + averageFrameTime /= mFrameTimes.size(); + + sample(LLStatViewer::NFTV, frame_time_stddev / averageFrameTime); + mLastNormalizedFrametimeVariance = frame_time_stddev / averageFrameTime; + + // Add up all of the jitter values. + F64 totalJitter = 0; + for (const auto& frame_jitter : mFrameTimesJitter) + { + totalJitter += frame_jitter.value(); + } + + mLastNormalizedPeriodJitter = totalJitter / mLastFrameTimeSample; + + sample(LLStatViewer::NORMALIZED_FRAMTIME_JITTER_PERIOD, mLastNormalizedPeriodJitter); + mFrameTimes.clear(); mFrameTimesJitter.clear(); mLastFrameTimeSample = F64Seconds(0); @@ -648,6 +673,11 @@ void send_viewer_stats(bool include_preferences) // send fps only for time app spends in foreground agent["fps"] = (F32)gForegroundFrameCount / gForegroundTime.getElapsedTimeF32(); + + agent["normalized_session_jitter"] = LLViewerStats::instance().getLastNormalizedSessionJitter(); + agent["normalized_frametime_variance"] = LLViewerStats::instance().getLastNormalizedFrametimeVariance(); + agent["normalized_period_jitter"] = LLViewerStats::instance().getLastNormalizedPeriodJitter(); + agent["version"] = LLVersionInfo::instance().getChannelAndVersion(); std::string language = LLUI::getLanguage(); agent["language"] = language; diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index 63fb7d4a176..011269d7ee6 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -271,6 +271,10 @@ class LLViewerStats : public LLSingleton LLTrace::Recording& getRecording() { return mRecording; } const LLTrace::Recording& getRecording() const { return mRecording; } + F64 getLastNormalizedSessionJitter() const { return mLastNoramlizedSessionJitter; } + F64 getLastNormalizedFrametimeVariance() const { return mLastNormalizedFrametimeVariance; } + F64 getLastNormalizedPeriodJitter() const { return mLastNormalizedPeriodJitter; } + private: LLTrace::Recording mRecording; @@ -286,6 +290,11 @@ class LLViewerStats : public LLSingleton F64Seconds mTimeSinceLastEventSample; std::vector mFrameTimes; // used for frame time stats std::vector mFrameTimesJitter; // used for frame time jitter stats + + F64 mLastNoramlizedSessionJitter; // used for frame time jitter stats + F64 mLastNormalizedFrametimeVariance; // Used when submitting jitter stats + F64 mLastNormalizedPeriodJitter; + }; static const F32 SEND_STATS_PERIOD = 300.0f; diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 4a9dd1c1b64..5ba0a86e683 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -1107,6 +1107,7 @@ void LLViewerFetchedTexture::init(bool firstinit) mOrigHeight = 0; mHasAux = false; mNeedsAux = false; + mLastWorkerDiscardLevel = -1; mRequestedDiscardLevel = -1; mRequestedDownloadPriority = 0.f; mFullyLoaded = false; @@ -1259,12 +1260,11 @@ void LLViewerFetchedTexture::loadFromFastCache() if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) { - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; - if (mRawImage && (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)) + if (mRawImage && (mRawImage->getWidth() > DEFAULT_THUMBNAIL_DIMENSIONS || mRawImage->getHeight() > DEFAULT_THUMBNAIL_DIMENSIONS)) { - // scale oversized icon, no need to give more work to gl - mRawImage->scale(expected_width, expected_height); + // Scale oversized thumbnail + // thumbnails aren't supposed to go over DEFAULT_THUMBNAIL_DIMENSIONS + mRawImage->scale(DEFAULT_THUMBNAIL_DIMENSIONS, DEFAULT_THUMBNAIL_DIMENSIONS); } } @@ -1957,9 +1957,9 @@ bool LLViewerFetchedTexture::processFetchResults(S32& desired_discard, S32 curre bool LLViewerFetchedTexture::updateFetch() { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - static LLCachedControl textures_decode_disabled(gSavedSettings,"TextureDecodeDisabled", false); + static LLCachedControl textures_decode_disabled(gSavedSettings, "TextureDecodeDisabled", false); - if(textures_decode_disabled) // don't fetch the surface textures in wireframe mode + if (textures_decode_disabled) // don't fetch the surface textures in wireframe mode { return false; } @@ -1994,7 +1994,7 @@ bool LLViewerFetchedTexture::updateFetch() LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - callback pending"); return false; // process any raw image data in callbacks before replacing } - if(mInFastCacheList) + if (mInFastCacheList) { LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - in fast cache"); return false; @@ -2019,7 +2019,7 @@ bool LLViewerFetchedTexture::updateFetch() if (mAuxRawImage.notNull()) sAuxCount--; // keep in mind that fetcher still might need raw image, don't modify original bool finished = LLAppViewer::getTextureFetch()->getRequestFinished(getID(), fetch_discard, mFetchState, mRawImage, mAuxRawImage, - mLastHttpGetStatus); + mLastHttpGetStatus); if (mRawImage.notNull()) sRawCount++; if (mAuxRawImage.notNull()) { @@ -2035,7 +2035,7 @@ bool LLViewerFetchedTexture::updateFetch() else { mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, - mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); + mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); } if (!processFetchResults(desired_discard, current_discard, fetch_discard, decode_priority)) @@ -2046,7 +2046,7 @@ bool LLViewerFetchedTexture::updateFetch() if (mIsFetching) { static const F32 MAX_HOLD_TIME = 5.0f; //seconds to wait before canceling fecthing if decode_priority is 0.f. - if(decode_priority > 0.0f || mStopFetchingTimer.getElapsedTimeF32() > MAX_HOLD_TIME) + if (decode_priority > 0.0f || mStopFetchingTimer.getElapsedTimeF32() > MAX_HOLD_TIME) { mStopFetchingTimer.reset(); LLAppViewer::getTextureFetch()->updateRequestPriority(mID, decode_priority); @@ -2062,7 +2062,7 @@ bool LLViewerFetchedTexture::updateFetch() LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - priority <= 0"); make_request = false; } - else if(mDesiredDiscardLevel > getMaxDiscardLevel()) + else if (mDesiredDiscardLevel > getMaxDiscardLevel()) { LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - desired > max"); make_request = false; @@ -2103,7 +2103,7 @@ bool LLViewerFetchedTexture::updateFetch() if (make_request) { LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - make request"); - S32 w=0, h=0, c=0; + S32 w = 0, h = 0, c = 0; if (getDiscardLevel() >= 0) { w = mGLTexturep->getWidth(0); @@ -2121,18 +2121,19 @@ bool LLViewerFetchedTexture::updateFetch() S32 fetch_request_response = -1; S32 worker_discard = -1; fetch_request_response = LLAppViewer::getTextureFetch()->createRequest(mFTType, mUrl, getID(), getTargetHost(), decode_priority, - w, h, c, desired_discard, needsAux(), mCanUseHTTP); + w, h, c, desired_discard, needsAux(), mCanUseHTTP); if (fetch_request_response >= 0) // positive values and 0 are discard values { LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - request created"); mHasFetcher = true; mIsFetching = true; + mLastWorkerDiscardLevel = worker_discard; // in some cases createRequest can modify discard, as an example // bake textures are always at discard 0 mRequestedDiscardLevel = llmin(desired_discard, fetch_request_response); mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, - mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); + mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); } else if (fetch_request_response == LLTextureFetch::CREATE_REQUEST_ERROR_TRANSITION) { @@ -2146,7 +2147,7 @@ bool LLViewerFetchedTexture::updateFetch() S32 decoded_discard; bool decoded; S32 fetch_state = LLAppViewer::getTextureFetch()->getLastFetchState(mID, desired_discard, decoded_discard, decoded); - if (fetch_state > 1 && decoded && decoded_discard >=0 && decoded_discard <= desired_discard) + if (fetch_state > 1 && decoded && decoded_discard >= 0 && decoded_discard <= desired_discard) { // worker actually has the image if (mRawImage.notNull()) sRawCount--; @@ -2770,11 +2771,9 @@ void LLViewerFetchedTexture::saveRawImage() } else if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) { - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; - if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height) + if (mRawImage->getWidth() > DEFAULT_THUMBNAIL_DIMENSIONS || mRawImage->getHeight() > DEFAULT_THUMBNAIL_DIMENSIONS) { - mSavedRawImage = new LLImageRaw(expected_width, expected_height, mRawImage->getComponents()); + mSavedRawImage = new LLImageRaw(DEFAULT_THUMBNAIL_DIMENSIONS, DEFAULT_THUMBNAIL_DIMENSIONS, mRawImage->getComponents()); mSavedRawImage->copyScaled(mRawImage); } else diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index e1582c74bd9..f9311d85cbf 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -444,6 +444,7 @@ class LLViewerFetchedTexture : public LLViewerTexture bool mKnownDrawSizeChanged ; std::string mUrl; + S32 mLastWorkerDiscardLevel; S32 mRequestedDiscardLevel; F32 mRequestedDownloadPriority; S32 mFetchState; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 6aade15d231..b0408b73ada 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1424,11 +1424,17 @@ void LLViewerWindow::handleMouseMove(LLWindow *window, LLCoordGL pos, MASK mask mWindow->showCursorFromMouseMove(); - if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME - && !gDisconnected) + if (!gDisconnected) + { + if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) { gAgent.clearAFK(); } + else + { + gAwayTriggerTimer.reset(); + } + } } void LLViewerWindow::handleMouseDragged(LLWindow *window, LLCoordGL pos, MASK mask) @@ -1547,6 +1553,10 @@ bool LLViewerWindow::handleTranslatedKeyDown(KEY key, MASK mask, bool repeated) { gAgent.clearAFK(); } + else + { + gAwayTriggerTimer.reset(); + } // *NOTE: We want to interpret KEY_RETURN later when it arrives as // a Unicode char, not as a keydown. Otherwise when client frame @@ -3015,7 +3025,8 @@ bool LLViewerWindow::handleKey(KEY key, MASK mask) { if ((focusedFloaterName == "nearby_chat") || (focusedFloaterName == "im_container") || (focusedFloaterName == "impanel")) { - if (gSavedSettings.getBOOL("ArrowKeysAlwaysMove")) + LLCachedControl key_move(gSavedSettings, "ArrowKeysAlwaysMove"); + if (key_move()) { // let Control-Up and Control-Down through for chat line history, if (!(key == KEY_UP && mask == MASK_CONTROL) @@ -3029,10 +3040,9 @@ bool LLViewerWindow::handleKey(KEY key, MASK mask) case KEY_RIGHT: case KEY_UP: case KEY_DOWN: - case KEY_PAGE_UP: - case KEY_PAGE_DOWN: - case KEY_HOME: - case KEY_END: + case KEY_PAGE_UP: //jump + case KEY_PAGE_DOWN: // down + case KEY_HOME: // toggle fly // when chatbar is empty or ArrowKeysAlwaysMove set, // pass arrow keys on to avatar... return false; diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index dcba891f9fe..903d6a035f1 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -575,7 +575,7 @@ class LLPelvisFixMotion : // joint states to be animated //------------------------------------------------------------------------- LLPointer mPelvisState; - LLCharacter* mCharacter; + LLCharacter* mCharacter; }; /** @@ -678,11 +678,13 @@ LLVOAvatar::LLVOAvatar(const LLUUID& id, mVisuallyMuteSetting(AV_RENDER_NORMALLY), mMutedAVColor(LLColor4::white /* used for "uninitialize" */), mFirstFullyVisible(true), + mWaitingForMeshes(false), mFirstDecloudTime(-1.f), mFullyLoaded(false), mPreviousFullyLoaded(false), mFullyLoadedInitialized(false), mLastCloudAttachmentCount(-1), + mFullyLoadedFrameCounter(0), mVisualComplexity(VISUAL_COMPLEXITY_UNKNOWN), mLoadedCallbacksPaused(false), mLoadedCallbackTextures(0), @@ -776,11 +778,9 @@ std::string LLVOAvatar::avString() const { return " " + getFullname() + " "; } - else - { - std::string viz_string = LLVOAvatar::rezStatusToString(getRezzedStatus()); - return " Avatar '" + getFullname() + "' " + viz_string + " "; - } + + std::string status = LLVOAvatar::rezStatusToString(getRezzedStatus()); + return " Avatar '" + getDebugName() + "' " + status + " "; } void LLVOAvatar::debugAvatarRezTime(std::string notification_name, std::string comment) @@ -803,10 +803,10 @@ void LLVOAvatar::debugAvatarRezTime(std::string notification_name, std::string c if (gSavedSettings.getBOOL("DebugAvatarRezTime")) { LLSD args; - args["EXISTENCE"] = llformat("%d",(U32)mDebugExistenceTimer.getElapsedTimeF32()); - args["TIME"] = llformat("%d",(U32)mRuthDebugTimer.getElapsedTimeF32()); + args["EXISTENCE"] = llformat("%d", (U32)mDebugExistenceTimer.getElapsedTimeF32()); + args["TIME"] = llformat("%d", (U32)mRuthDebugTimer.getElapsedTimeF32()); args["NAME"] = getFullname(); - LLNotificationsUtil::add(notification_name,args); + LLNotificationsUtil::add(notification_name, args); } } @@ -819,14 +819,14 @@ LLVOAvatar::~LLVOAvatar() if (!mFullyLoaded) { - debugAvatarRezTime("AvatarRezLeftCloudNotification","left after ruth seconds as cloud"); + debugAvatarRezTime("AvatarRezLeftCloudNotification", "left after ruth seconds as cloud"); } else { - debugAvatarRezTime("AvatarRezLeftNotification","left sometime after declouding"); + debugAvatarRezTime("AvatarRezLeftNotification", "left sometime after declouding"); } - if(mTuned) + if (mTuned) { LLPerfStats::tunedAvatars--; mTuned = false; @@ -920,12 +920,12 @@ bool LLVOAvatar::isFullyTextured() const bool LLVOAvatar::hasGray() const { - return !getIsCloud() && !isFullyTextured(); + return !getHasMissingParts() && !isFullyTextured(); } S32 LLVOAvatar::getRezzedStatus() const { - if (getIsCloud()) return 0; + if (getHasMissingParts()) return 0; bool textured = isFullyTextured(); bool all_baked_loaded = allBakedTexturesCompletelyDownloaded(); if (textured && all_baked_loaded && getAttachmentCount() == mSimAttachments.size()) return 4; @@ -968,34 +968,49 @@ bool LLVOAvatar::areAllNearbyInstancesBaked(S32& grey_avatars) ++grey_avatars; } } - return !grey_avatars; + return grey_avatars == 0; } // static -void LLVOAvatar::getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars) +void LLVOAvatar::getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars, S32& pending_meshes, S32& control_avatars) { counts.clear(); counts.resize(5); avg_cloud_time = 0; cloud_avatars = 0; + pending_meshes = 0; + control_avatars = 0; S32 count_avg = 0; for (LLCharacter* character : LLCharacter::sInstances) { - if (LLVOAvatar* inst = (LLVOAvatar*)character) + LLVOAvatar* inst = (LLVOAvatar*)character; + if (inst && !inst->isUIAvatar() && !inst->isSelf()) { - S32 rez_status = inst->getRezzedStatus(); - counts[rez_status]++; - F32 time = inst->getFirstDecloudTime(); - if (time >= 0) + if (inst->isControlAvatar()) { - avg_cloud_time+=time; - count_avg++; + control_avatars++; } - if (!inst->isFullyLoaded() || time < 0) + else { - // still renders as cloud - cloud_avatars++; + S32 rez_status = inst->getRezzedStatus(); + counts[rez_status]++; + F32 time = inst->getFirstDecloudTime(); + if (time >= 0) + { + avg_cloud_time += time; + count_avg++; + } + if (!inst->isFullyLoaded() || time < 0) + { + // still renders as cloud + cloud_avatars++; + if (rez_status >= 4 + && inst->mWaitingForMeshes) + { + pending_meshes++; + } + } } } } @@ -1012,7 +1027,7 @@ std::string LLVOAvatar::rezStatusToString(S32 rez_status) switch (rez_status) { case 0: - return "cloud"; + return "missing parts"; case 1: return "gray"; case 2: @@ -2967,7 +2982,7 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update) LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; if (LLVOAvatar::sJointDebug) { - LL_INFOS() << getFullname() << ": joint touches: " << LLJoint::sNumTouches << " updates: " << LLJoint::sNumUpdates << LL_ENDL; + LL_INFOS() << getDebugName() << ": joint touches: " << LLJoint::sNumTouches << " updates: " << LLJoint::sNumUpdates << LL_ENDL; } LLJoint::sNumUpdates = 0; @@ -3187,17 +3202,17 @@ F32 LLVOAvatar::calcMorphAmount() const void LLVOAvatar::idleUpdateLipSync(bool voice_enabled) { // Use the Lipsync_Ooh and Lipsync_Aah morphs for lip sync - if ( voice_enabled + if (voice_enabled && mLastRezzedStatus > 0 // no point updating lip-sync for clouds && LLVoiceVisualizer::getLipSyncEnabled() - && LLVoiceClient::getInstance()->getIsSpeaking( mID ) ) + && LLVoiceClient::getInstance()->getIsSpeaking(mID)) { F32 ooh_morph_amount = 0.0f; F32 aah_morph_amount = 0.0f; mVoiceVisualizer->lipSyncOohAah( ooh_morph_amount, aah_morph_amount ); - if( mOohMorph ) + if (mOohMorph) { F32 ooh_weight = mOohMorph->getMinWeight() + ooh_morph_amount * (mOohMorph->getMaxWeight() - mOohMorph->getMinWeight()); @@ -3205,7 +3220,7 @@ void LLVOAvatar::idleUpdateLipSync(bool voice_enabled) mOohMorph->setWeight( ooh_weight); } - if( mAahMorph ) + if (mAahMorph) { F32 aah_weight = mAahMorph->getMinWeight() + aah_morph_amount * (mAahMorph->getMaxWeight() - mAahMorph->getMinWeight()); @@ -3474,7 +3489,7 @@ void LLVOAvatar::idleUpdateNameTagText(bool new_name) is_muted = isInMuteList(); } bool is_friend = isBuddy(); - bool is_cloud = getIsCloud(); + bool is_cloud = getHasMissingParts(); if (is_appearance != mNameAppearance) { @@ -4292,8 +4307,8 @@ void LLVOAvatar::computeUpdatePeriod() { const LLVector4a* ext = mDrawable->getSpatialExtents(); LLVector4a size; - size.setSub(ext[1],ext[0]); - F32 mag = size.getLength3().getF32()*0.5f; + size.setSub(ext[1], ext[0]); + F32 mag = size.getLength3().getF32() * 0.5f; const S32 UPDATE_RATE_SLOW = 64; const S32 UPDATE_RATE_MED = 48; @@ -4303,14 +4318,14 @@ void LLVOAvatar::computeUpdatePeriod() { // visually muted avatars update at lowest rate mUpdatePeriod = UPDATE_RATE_SLOW; } - else if (! shouldImpostor() - || mDrawable->mDistanceWRTCamera < 1.f + mag) + else if (!shouldImpostor() + || mDrawable->mDistanceWRTCamera < 1.f + mag) { // first 25% of max visible avatars are not impostored // also, don't impostor avatars whose bounding box may be penetrating the // impostor camera near clip plane mUpdatePeriod = 1; } - else if ( shouldImpostor(4.0) ) + else if (shouldImpostor(4.0)) { //background avatars are REALLY slow updating impostors mUpdatePeriod = UPDATE_RATE_SLOW; } @@ -4319,7 +4334,7 @@ void LLVOAvatar::computeUpdatePeriod() // Don't update cloud avatars too often mUpdatePeriod = UPDATE_RATE_SLOW; } - else if ( shouldImpostor(3.0) ) + else if (shouldImpostor(3.0)) { //back 25% of max visible avatars are slow updating impostors mUpdatePeriod = UPDATE_RATE_MED; } @@ -6168,8 +6183,11 @@ void LLVOAvatar::resetAnimations() flushAllMotions(); } -// Override selectively based on avatar sex and whether we're using new -// animations. +//----------------------------------------------------------------------------- +// remapMotionID() +// Override selectively based on avatar sex and whether we're using new animations. +//----------------------------------------------------------------------------- +// virtual LLUUID LLVOAvatar::remapMotionID(const LLUUID& id) { static LLCachedControl use_new_walk_run(gSavedSettings, "UseNewWalkRun"); @@ -6219,7 +6237,6 @@ LLUUID LLVOAvatar::remapMotionID(const LLUUID& id) } return result; - } //----------------------------------------------------------------------------- @@ -6227,6 +6244,7 @@ LLUUID LLVOAvatar::remapMotionID(const LLUUID& id) // id is the asset if of the animation to start // time_offset is the offset into the animation at which to start playing //----------------------------------------------------------------------------- +// virtual bool LLVOAvatar::startMotion(const LLUUID& id, F32 time_offset) { LL_DEBUGS("Motion") << "motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; @@ -6249,6 +6267,7 @@ bool LLVOAvatar::startMotion(const LLUUID& id, F32 time_offset) //----------------------------------------------------------------------------- // stopMotion() //----------------------------------------------------------------------------- +// virtual bool LLVOAvatar::stopMotion(const LLUUID& id, bool stop_immediate) { LL_DEBUGS("Motion") << "Motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; @@ -6288,15 +6307,30 @@ void LLVOAvatar::stopMotionFromSource(const LLUUID& source_id) //----------------------------------------------------------------------------- // addDebugText() //----------------------------------------------------------------------------- +// virtual void LLVOAvatar::addDebugText(const std::string& text) { mDebugText.append(1, '\n'); mDebugText.append(text); } +//----------------------------------------------------------------------------- +// getDebugName() +//----------------------------------------------------------------------------- +// virtual +std::string LLVOAvatar::getDebugName() const +{ +#if LL_RELEASE_WITH_DEBUG_INFO + return getFullname(); +#else + return getID().asString(); +#endif // LL_RELEASE_WITH_DEBUG_INFO +} + //----------------------------------------------------------------------------- // getID() //----------------------------------------------------------------------------- +// virtual const LLUUID& LLVOAvatar::getID() const { return mID; @@ -6306,6 +6340,7 @@ const LLUUID& LLVOAvatar::getID() const // getJoint() //----------------------------------------------------------------------------- // RN: avatar joints are multi-rooted to include screen-based attachments +// virtual LLJoint* LLVOAvatar::getJoint(std::string_view name) { joint_map_t::iterator iter = mJointMap.find(name); @@ -6431,6 +6466,7 @@ bool LLVOAvatar::jointIsRiggedTo(const LLJoint *joint) const void LLVOAvatar::clearAttachmentOverrides() { + for (S32 i=0; i meshes_seen; @@ -6584,11 +6622,11 @@ void LLVOAvatar::updateAttachmentOverrides() } } pelvis_fixups = mPelvisFixups; - //dumpArchetypeXML(getFullname() + "_paranoid_updated"); + //dumpArchetypeXML(getDebugName() + "_paranoid_updated"); // Rebuild and compare rebuildAttachmentOverrides(); - //dumpArchetypeXML(getFullname() + "_paranoid_rebuilt"); + //dumpArchetypeXML(getDebugName() + "_paranoid_rebuilt"); bool mismatched = false; for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) { @@ -6817,22 +6855,22 @@ void LLVOAvatar::showAttachmentOverrides(bool verbose) const { std::stringstream ss; std::copy(pos_names.begin(), pos_names.end(), std::ostream_iterator(ss, ",")); - LL_INFOS() << getFullname() << " attachment positions defined for joints: " << ss.str() << "\n" << LL_ENDL; + LL_INFOS() << avString() << " attachment positions defined for joints: " << ss.str() << "\n" << LL_ENDL; } else { - LL_DEBUGS("Avatar") << getFullname() << " no attachment positions defined for any joints" << "\n" << LL_ENDL; + LL_DEBUGS("Avatar") << avString() << " no attachment positions defined for any joints" << "\n" << LL_ENDL; } if (scale_names.size()) { std::stringstream ss; std::copy(scale_names.begin(), scale_names.end(), std::ostream_iterator(ss, ",")); - LL_INFOS() << getFullname() << " attachment scales defined for joints: " << ss.str() << "\n" << LL_ENDL; + LL_INFOS() << getDebugName() << " attachment scales defined for joints: " << ss.str() << "\n" << LL_ENDL; } else { - LL_INFOS() << getFullname() << " no attachment scales defined for any joints" << "\n" << LL_ENDL; + LL_INFOS() << getDebugName() << " no attachment scales defined for any joints" << "\n" << LL_ENDL; } if (!verbose) @@ -8201,7 +8239,7 @@ bool LLVOAvatar::isVisible() const } // Determine if we have enough avatar data to render -bool LLVOAvatar::getIsCloud() const +bool LLVOAvatar::getHasMissingParts() const { if (mIsDummy) { @@ -8219,7 +8257,7 @@ bool LLVOAvatar::getIsCloud() const void LLVOAvatar::updateRezzedStatusTimers(S32 rez_status) { // State machine for rezzed status. Statuses are -1 on startup, 0 - // = cloud, 1 = gray, 2 = downloading, 3 = waiting for attachments, 4 = full. + // Statuses are -1 on startup, 0 = cloud, 1 = gray, 2 = downloading, 3 = waiting for attachments, 4 = full. // Purpose is to collect time data for each it takes avatar to reach // various loading landmarks: gray, textured (partial), textured fully. @@ -8239,7 +8277,7 @@ void LLVOAvatar::updateRezzedStatusTimers(S32 rez_status) if (rez_status < mLastRezzedStatus) { // load level has decreased. start phase timers for higher load levels. - for (S32 i = rez_status+1; i <= mLastRezzedStatus; i++) + for (S32 i = rez_status + 1; i <= mLastRezzedStatus; i++) { startPhase("load_" + LLVOAvatar::rezStatusToString(i)); } @@ -8247,7 +8285,7 @@ void LLVOAvatar::updateRezzedStatusTimers(S32 rez_status) else if (rez_status > mLastRezzedStatus) { // load level has increased. stop phase timers for lower and equal load levels. - for (S32 i = llmax(mLastRezzedStatus+1,1); i <= rez_status; i++) + for (S32 i = llmax(mLastRezzedStatus + 1, 1); i <= rez_status; i++) { stopPhase("load_" + LLVOAvatar::rezStatusToString(i)); stopPhase("first_load_" + LLVOAvatar::rezStatusToString(i), false); @@ -8408,8 +8446,12 @@ bool LLVOAvatar::updateIsFullyLoaded() || (mLoadedCallbackTextures < mCallbackTextureList.size() && mLastTexCallbackAddedTime.getElapsedTimeF32() < MAX_TEXTURE_WAIT_TIME_SEC) || !mPendingAttachment.empty() || (rez_status < 3 && !isFullyBaked()) - || hasPendingAttachedMeshes() ); + if (!loading) + { + mWaitingForMeshes = hasPendingAttachedMeshes(); + loading = mWaitingForMeshes; + } // compare amount of attachments to one reported by simulator if (!isSelf() && mLastCloudAttachmentCount < mSimAttachments.size() && mSimAttachments.size() > 0) @@ -8488,19 +8530,19 @@ bool LLVOAvatar::processFullyLoadedChange(bool loading) F32 first_use_delay = FIRST_APPEARANCE_CLOUD_MIN_DELAY; if (!isSelf() && loading) { - // Note that textures can causes 60s delay on thier own - // so this delay might end up on top of textures' delay - first_use_delay = llclamp( - mFirstAppearanceMessageTimer.getElapsedTimeF32(), - FIRST_APPEARANCE_CLOUD_MIN_DELAY, - FIRST_APPEARANCE_CLOUD_MAX_DELAY); + // Note that textures can causes 60s delay on thier own + // so this delay might end up on top of textures' delay + first_use_delay = llclamp( + mFirstAppearanceMessageTimer.getElapsedTimeF32(), + FIRST_APPEARANCE_CLOUD_MIN_DELAY, + FIRST_APPEARANCE_CLOUD_MAX_DELAY); - if (shouldImpostor()) - { - // Impostors are less of a priority, - // let them stay cloud longer - first_use_delay *= FIRST_APPEARANCE_CLOUD_IMPOSTOR_MODIFIER; - } + if (shouldImpostor()) + { + // Impostors are less of a priority, + // let them stay cloud longer + first_use_delay *= FIRST_APPEARANCE_CLOUD_IMPOSTOR_MODIFIER; + } } mFullyLoaded = (mFullyLoadedTimer.getElapsedTimeF32() > first_use_delay); } @@ -8517,12 +8559,12 @@ bool LLVOAvatar::processFullyLoadedChange(bool loading) // did our loading state "change" from last call? // FIXME runway - why are we updating every 30 calls even if nothing has changed? // This causes updateLOD() to run every 30 frames, among other things. + bool fully_loaded_changed = (mFullyLoaded != mPreviousFullyLoaded); const S32 UPDATE_RATE = 30; bool changed = ((mFullyLoaded != mPreviousFullyLoaded) || // if the value is different from the previous call - (!mFullyLoadedInitialized) || // if we've never been called before - (mFullyLoadedFrameCounter % UPDATE_RATE == 0)); // every now and then issue a change - bool fully_loaded_changed = (mFullyLoaded != mPreviousFullyLoaded); + (!mFullyLoadedInitialized) || // if we've never been called before + (mFullyLoadedFrameCounter % UPDATE_RATE == 0)); // every now and then issue a change mPreviousFullyLoaded = mFullyLoaded; mFullyLoadedInitialized = true; @@ -8540,6 +8582,7 @@ bool LLVOAvatar::processFullyLoadedChange(bool loading) mNeedsImpostorUpdate = true; mLastImpostorUpdateReason = 6; } + return changed; } @@ -9434,12 +9477,12 @@ void dump_visual_param(apr_file_t* file, LLVisualParam* viewer_param, F32 value) void LLVOAvatar::dumpAppearanceMsgParams( const std::string& dump_prefix, const LLAppearanceMessageContents& contents) { - std::string outfilename = get_sequential_numbered_file_name(dump_prefix,".xml"); + std::string outfilename = get_sequential_numbered_file_name(dump_prefix, ".xml"); const std::vector& params_for_dump = contents.mParamWeights; const LLTEContents& tec = contents.mTEContents; LLAPRFile outfile; - std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename); + std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, outfilename); outfile.open(fullpath, LL_APR_WB ); apr_file_t* file = outfile.getFileHandle(); if (!file) @@ -9652,7 +9695,7 @@ void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys ) static LLCachedControl enable_verbose_dumps(gSavedSettings, "DebugAvatarAppearanceMessage"); static LLCachedControl block_avatar_appearance_messages(gSavedSettings, "BlockAvatarAppearanceMessages"); - std::string dump_prefix = getFullname() + "_" + (isSelf()?"s":"o") + "_"; + std::string dump_prefix = getDebugName() + (isSelf() ? "_s_" : "_o_"); if (block_avatar_appearance_messages) { LL_WARNS() << "Blocking AvatarAppearance message" << LL_ENDL; @@ -10286,17 +10329,13 @@ void LLVOAvatar::dumpArchetypeXML(const std::string& prefix, bool group_by_weara std::string outprefix(prefix); if (outprefix.empty()) { - outprefix = getFullname() + (isSelf()?"_s":"_o"); + outprefix = getDebugName() + (isSelf() ? "_s" : "_o"); } - if (outprefix.empty()) - { - outprefix = std::string("new_archetype"); - } - std::string outfilename = get_sequential_numbered_file_name(outprefix,".xml"); + std::string outfilename = get_sequential_numbered_file_name(outprefix, ".xml"); LLAPRFile outfile; LLWearableType *wr_inst = LLWearableType::getInstance(); - std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename); + std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, outfilename); if (APR_SUCCESS == outfile.open(fullpath, LL_APR_WB )) { apr_file_t* file = outfile.getFileHandle(); @@ -10782,7 +10821,7 @@ void LLVOAvatar::updateRiggingInfo() { LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - LL_DEBUGS("RigSpammish") << getFullname() << " updating rig tab" << LL_ENDL; + LL_DEBUGS("RigSpammish") << getDebugName() << " updating rig tab" << LL_ENDL; // use a local static for scratch space to avoid reallocation here static std::vector volumes; @@ -11763,16 +11802,24 @@ void LLVOAvatar::readProfileQuery(S32 retries) } else - { // wait until next frame - LLUUID id = getID(); + { + // wait until next frame + const LLUUID id = getID(); - LL::WorkQueue::getInstance("mainloop")->post([id, retries] { - LLVOAvatar* avatar = (LLVOAvatar*) gObjectList.findObject(id); - if(avatar) + LL::WorkQueue::getInstance("mainloop")->post([id, retries] + { + LLViewerObject* object = gObjectList.findObject(id); + if (object + && !object->isDead() + && object->isAvatar()) // probably excessive, pcode isn't supposed to change { - avatar->readProfileQuery(retries); + LLVOAvatar* avatar = (LLVOAvatar*)object; + if (avatar) + { + avatar->readProfileQuery(retries); + } } - }); + }); } } diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 9eb8d3f880e..1e563c48694 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -233,6 +233,7 @@ class LLVOAvatar : virtual void onActiveOverrideMeshesChanged(); /*virtual*/ const LLUUID& getID() const; + /*virtual*/ std::string getDebugName() const; /*virtual*/ void addDebugText(const std::string& text); /*virtual*/ F32 getTimeDilation(); /*virtual*/ void getGround(const LLVector3 &inPos, LLVector3 &outPos, LLVector3 &outNorm); @@ -330,16 +331,16 @@ class LLVOAvatar : // avatar render cost - U32 getVisualComplexity() { return mVisualComplexity; }; + U32 getVisualComplexity() { return mVisualComplexity; }; // surface area calculation - F32 getAttachmentSurfaceArea() { return mAttachmentSurfaceArea; }; + F32 getAttachmentSurfaceArea() { return mAttachmentSurfaceArea; }; - U32 getReportedVisualComplexity() { return mReportedVisualComplexity; }; // Numbers as reported by the SL server - void setReportedVisualComplexity(U32 value) { mReportedVisualComplexity = value; }; + U32 getReportedVisualComplexity() { return mReportedVisualComplexity; }; // Numbers as reported by the SL server + void setReportedVisualComplexity(U32 value) { mReportedVisualComplexity = value; }; - S32 getUpdatePeriod() { return mUpdatePeriod; }; - const LLColor4 & getMutedAVColor() { return mMutedAVColor; }; + S32 getUpdatePeriod() { return mUpdatePeriod; }; + const LLColor4 & getMutedAVColor() { return mMutedAVColor; }; static void updateImpostorRendering(U32 newMaxNonImpostorsValue); void idleUpdateBelowWater(); @@ -400,15 +401,14 @@ class LLVOAvatar : bool isTooComplex() const; bool visualParamWeightsAreDefault(); - virtual bool getIsCloud() const; + virtual bool getHasMissingParts() const; bool isFullyTextured() const; bool hasGray() const; - S32 getRezzedStatus() const; // 0 = cloud, 1 = gray, 2 = textured, 3 = textured and fully downloaded. + S32 getRezzedStatus() const; // 0 = cloud, 1 = gray, 2 = textured, 3 = waiting for attachments, 4 = full. void updateRezzedStatusTimers(S32 status); S32 mLastRezzedStatus; - void startPhase(const std::string& phase_name); void stopPhase(const std::string& phase_name, bool err_check = true); void clearPhases(); @@ -427,6 +427,7 @@ class LLVOAvatar : private: bool mFirstFullyVisible; + bool mWaitingForMeshes; F32 mFirstDecloudTime; LLFrameTimer mFirstAppearanceMessageTimer; @@ -723,7 +724,7 @@ class LLVOAvatar : bool isFullyBaked(); static bool areAllNearbyInstancesBaked(S32& grey_avatars); - static void getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars); + static void getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars, S32& pending_meshes, S32& control_avatars); static std::string rezStatusToString(S32 status); //-------------------------------------------------------------------- @@ -742,7 +743,7 @@ class LLVOAvatar : LLViewerTexLayerSet* getTexLayerSet(const U32 index) const { return dynamic_cast(mBakedTextureDatas[index].mTexLayerSet); } - LLLoadedCallbackEntry::source_callback_list_t mCallbackTextureList ; + LLLoadedCallbackEntry::source_callback_list_t mCallbackTextureList; bool mLoadedCallbacksPaused; S32 mLoadedCallbackTextures; // count of 'loaded' baked textures, filled from mCallbackTextureList LLFrameTimer mLastTexCallbackAddedTime; diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index ebba9ba291b..653c7e82ebe 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -1927,7 +1927,7 @@ void LLVOAvatarSelf::dumpTotalLocalTextureByteCount() LL_INFOS() << "Total Avatar LocTex GL:" << (gl_bytes/1024) << "KB" << LL_ENDL; } -bool LLVOAvatarSelf::getIsCloud() const +bool LLVOAvatarSelf::getHasMissingParts() const { // Let people know why they're clouded without spamming them into oblivion. bool do_warn = false; @@ -2237,14 +2237,18 @@ void LLVOAvatarSelf::appearanceChangeMetricsCoro(std::string url) std::vector rez_counts; F32 avg_time; S32 total_cloud_avatars; - LLVOAvatar::getNearbyRezzedStats(rez_counts, avg_time, total_cloud_avatars); + S32 waiting_for_meshes; + S32 control_avatars; + LLVOAvatar::getNearbyRezzedStats(rez_counts, avg_time, total_cloud_avatars, waiting_for_meshes, control_avatars); for (S32 rez_stat = 0; rez_stat < rez_counts.size(); ++rez_stat) { std::string rez_status_name = LLVOAvatar::rezStatusToString(rez_stat); msg["nearby"][rez_status_name] = rez_counts[rez_stat]; } + msg["nearby"]["waiting_for_meshes"] = waiting_for_meshes; msg["nearby"]["avg_decloud_time"] = avg_time; msg["nearby"]["cloud_total"] = total_cloud_avatars; + msg["nearby"]["animeshes"] = control_avatars; // std::vector bucket_fields("timer_name","is_self","grid_x","grid_y","is_using_server_bake"); std::vector by_fields; @@ -2826,6 +2830,12 @@ void LLVOAvatarSelf::setHoverOffset(const LLVector3& hover_offset, bool send_upd //------------------------------------------------------------------------ bool LLVOAvatarSelf::needsRenderBeam() { + static LLCachedControl enable_selection_hints(gSavedSettings, "EnableSelectionHints", true); + if (!enable_selection_hints) + { + return false; + } + LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); bool is_touching_or_grabbing = (tool == LLToolGrab::getInstance() && LLToolGrab::getInstance()->isEditing()); diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index f7cd974ab0e..45985b2a803 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -129,7 +129,7 @@ class LLVOAvatarSelf : // Loading state //-------------------------------------------------------------------- public: - /*virtual*/ bool getIsCloud() const; + /*virtual*/ bool getHasMissingParts() const; //-------------------------------------------------------------------- // Region state diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp index 27c105c8d60..501828eee8e 100644 --- a/indra/newview/llvocache.cpp +++ b/indra/newview/llvocache.cpp @@ -1875,11 +1875,11 @@ void LLVOCache::removeGenericExtrasForHandle(U64 handle) } // NOTE: when removing the extras, we must also remove the objects so the simulator will send us a full upddate with the valid overrides - auto* entry = mHandleEntryMap[handle]; - if (entry) + handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle); + if (iter != mHandleEntryMap.end()) { - LL_WARNS("GLTF", "VOCache") << "Removing generic extras for handle " << entry->mHandle << "Filename: " << getObjectCacheExtrasFilename(handle) << LL_ENDL; - removeEntry(entry); + LL_WARNS("GLTF", "VOCache") << "Removing generic extras for handle " << handle << "Filename: " << getObjectCacheExtrasFilename(handle) << LL_ENDL; + removeEntry(iter->second); } else { diff --git a/indra/newview/llvoicecallhandler.cpp b/indra/newview/llvoicecallhandler.cpp index 25b0e69436c..d2c947fef20 100644 --- a/indra/newview/llvoicecallhandler.cpp +++ b/indra/newview/llvoicecallhandler.cpp @@ -38,6 +38,11 @@ class LLVoiceCallAvatarHandler : public LLCommandHandler { } + virtual bool canHandleUntrusted(const LLSD ¶ms, const LLSD &query_map, LLMediaCtrl *web, const std::string &nav_type) + { + return (nav_type == NAV_TYPE_CLICKED || nav_type == NAV_TYPE_EXTERNAL); + } + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) { //Make sure we have some parameters diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp index cf128f381a0..b3ac28eb7a6 100644 --- a/indra/newview/llvoicechannel.cpp +++ b/indra/newview/llvoicechannel.cpp @@ -369,6 +369,12 @@ void LLVoiceChannel::resume() { if (sSuspendedVoiceChannel) { + if (sSuspendedVoiceChannel->callStarted()) + { + // should have channel data already, restart + sSuspendedVoiceChannel->setState(STATE_READY); + } + // won't do anything if call is already started sSuspendedVoiceChannel->activate(); } else diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 9835a69e4e4..eae71ca454f 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -52,6 +52,7 @@ #include "llcachename.h" #include "llimview.h" // for LLIMMgr #include "llworld.h" +#include "llviewerregion.h" #include "llparcel.h" #include "llviewerparcelmgr.h" #include "llfirstuse.h" @@ -336,35 +337,37 @@ void LLWebRTCVoiceClient::updateSettings() LL_PROFILE_ZONE_SCOPED_CATEGORY_VOICE; setVoiceEnabled(LLVoiceClient::getInstance()->voiceEnabled()); - static LLCachedControl sVoiceEarLocation(gSavedSettings, "VoiceEarLocation"); - setEarLocation(sVoiceEarLocation); - - static LLCachedControl sInputDevice(gSavedSettings, "VoiceInputAudioDevice"); - setCaptureDevice(sInputDevice); + if (mVoiceEnabled) + { + static LLCachedControl sVoiceEarLocation(gSavedSettings, "VoiceEarLocation"); + setEarLocation(sVoiceEarLocation); - static LLCachedControl sOutputDevice(gSavedSettings, "VoiceOutputAudioDevice"); - setRenderDevice(sOutputDevice); + static LLCachedControl sInputDevice(gSavedSettings, "VoiceInputAudioDevice"); + setCaptureDevice(sInputDevice); - LL_INFOS("Voice") << "Input device: " << std::quoted(sInputDevice()) << ", output device: " << std::quoted(sOutputDevice()) << LL_ENDL; + static LLCachedControl sOutputDevice(gSavedSettings, "VoiceOutputAudioDevice"); + setRenderDevice(sOutputDevice); - static LLCachedControl sMicLevel(gSavedSettings, "AudioLevelMic"); - setMicGain(sMicLevel); + LL_INFOS("Voice") << "Input device: " << std::quoted(sInputDevice()) << ", output device: " << std::quoted(sOutputDevice()) << LL_ENDL; - llwebrtc::LLWebRTCDeviceInterface::AudioConfig config; + static LLCachedControl sMicLevel(gSavedSettings, "AudioLevelMic"); + setMicGain(sMicLevel); - static LLCachedControl sEchoCancellation(gSavedSettings, "VoiceEchoCancellation", true); - config.mEchoCancellation = sEchoCancellation; + llwebrtc::LLWebRTCDeviceInterface::AudioConfig config; - static LLCachedControl sAGC(gSavedSettings, "VoiceAutomaticGainControl", true); - config.mAGC = sAGC; + static LLCachedControl sEchoCancellation(gSavedSettings, "VoiceEchoCancellation", true); + config.mEchoCancellation = sEchoCancellation; - static LLCachedControl sNoiseSuppressionLevel(gSavedSettings, - "VoiceNoiseSuppressionLevel", - llwebrtc::LLWebRTCDeviceInterface::AudioConfig::ENoiseSuppressionLevel::NOISE_SUPPRESSION_LEVEL_VERY_HIGH); - config.mNoiseSuppressionLevel = (llwebrtc::LLWebRTCDeviceInterface::AudioConfig::ENoiseSuppressionLevel) (U32)sNoiseSuppressionLevel; + static LLCachedControl sAGC(gSavedSettings, "VoiceAutomaticGainControl", true); + config.mAGC = sAGC; - mWebRTCDeviceInterface->setAudioConfig(config); + static LLCachedControl sNoiseSuppressionLevel(gSavedSettings, + "VoiceNoiseSuppressionLevel", + llwebrtc::LLWebRTCDeviceInterface::AudioConfig::ENoiseSuppressionLevel::NOISE_SUPPRESSION_LEVEL_VERY_HIGH); + config.mNoiseSuppressionLevel = (llwebrtc::LLWebRTCDeviceInterface::AudioConfig::ENoiseSuppressionLevel)(U32)sNoiseSuppressionLevel; + mWebRTCDeviceInterface->setAudioConfig(config); + } } // Observers @@ -2002,6 +2005,33 @@ bool LLWebRTCVoiceClient::sessionState::processConnectionStates() return !mWebRTCConnections.empty(); } +// Helper function to check if a region supports WebRTC voice +bool LLWebRTCVoiceClient::estateSessionState::isRegionWebRTCEnabled(const LLUUID& regionID) +{ + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromID(regionID); + if (!region) + { + LL_WARNS("Voice") << "Could not find region " << regionID + << " for voice server type validation" << LL_ENDL; + return false; + } + + LLSD simulatorFeatures; + region->getSimulatorFeatures(simulatorFeatures); + + bool isWebRTCEnabled = simulatorFeatures.has("VoiceServerType") && + simulatorFeatures["VoiceServerType"].asString() == "webrtc"; + + if (!isWebRTCEnabled) + { + LL_DEBUGS("Voice") << "Region " << regionID << " VoiceServerType is not 'webrtc' (got: " + << (simulatorFeatures.has("VoiceServerType") ? simulatorFeatures["VoiceServerType"].asString() : "none") << ")" + << LL_ENDL; + } + + return isWebRTCEnabled; +} + // processing of spatial voice connection states requires special handling. // as neighboring regions need to be started up or shut down depending // on our location. @@ -2026,6 +2056,13 @@ bool LLWebRTCVoiceClient::estateSessionState::processConnectionStates() // shut down connections to neighbors that are too far away. spatialConnection.get()->shutDown(); } + else if (!isRegionWebRTCEnabled(regionID)) + { + // shut down connections to neighbors that no longer support WebRTC voice. + LL_DEBUGS("Voice") << "Shutting down connection to neighbor region " << regionID + << " - no longer supports WebRTC voice" << LL_ENDL; + spatialConnection.get()->shutDown(); + } if (!spatialConnection.get()->isShuttingDown()) { neighbor_ids.erase(regionID); @@ -2035,11 +2072,20 @@ bool LLWebRTCVoiceClient::estateSessionState::processConnectionStates() // add new connections for new neighbors for (auto &neighbor : neighbor_ids) { - connectionPtr_t connection(new LLVoiceWebRTCSpatialConnection(neighbor, INVALID_PARCEL_ID, mChannelID)); + // Only connect if the region supports WebRTC voice server type + if (isRegionWebRTCEnabled(neighbor)) + { + connectionPtr_t connection(new LLVoiceWebRTCSpatialConnection(neighbor, INVALID_PARCEL_ID, mChannelID)); - mWebRTCConnections.push_back(connection); - connection->setMuteMic(mMuted); - connection->setSpeakerVolume(mSpeakerVolume); + mWebRTCConnections.push_back(connection); + connection->setMuteMic(mMuted); // mute will be set for primary connection when that connection comes up + connection->setSpeakerVolume(mSpeakerVolume); + } + else + { + LL_DEBUGS("Voice") << "Skipping neighbor region " << neighbor + << " - does not support WebRTC voice" << LL_ENDL; + } } } return LLWebRTCVoiceClient::sessionState::processConnectionStates(); @@ -2389,6 +2435,7 @@ void LLVoiceWebRTCConnection::OnAudioEstablished(llwebrtc::LLWebRTCAudioInterfac } LL_DEBUGS("Voice") << "On AudioEstablished." << LL_ENDL; mWebRTCAudioInterface = audio_interface; + mWebRTCAudioInterface->setMute(true); // mute will be set appropriately later when we finish setting up. setVoiceConnectionState(VOICE_STATE_SESSION_ESTABLISHED); }); } @@ -2748,7 +2795,12 @@ bool LLVoiceWebRTCConnection::connectionStateMachine() } // update the peer connection with the various characteristics of // this connection. - mWebRTCAudioInterface->setMute(mMuted); + // For spatial this connection will come up as muted, but will be set to the appropriate + // value later on when we determine the regions we connect to. + if (!isSpatial()) + { + mWebRTCAudioInterface->setMute(mMuted); + } mWebRTCAudioInterface->setReceiveVolume(mSpeakerVolume); LLWebRTCVoiceClient::getInstance()->OnConnectionEstablished(mChannelID, mRegionID); setVoiceConnectionState(VOICE_STATE_WAIT_FOR_DATA_CHANNEL); @@ -2793,6 +2845,10 @@ bool LLVoiceWebRTCConnection::connectionStateMachine() if (primary != mPrimary) { mPrimary = primary; + if (mWebRTCAudioInterface) + { + mWebRTCAudioInterface->setMute(mMuted || !mPrimary); + } sendJoin(); } } @@ -3095,10 +3151,7 @@ LLVoiceWebRTCSpatialConnection::LLVoiceWebRTCSpatialConnection(const LLUUID ® LLVoiceWebRTCConnection(regionID, channelID), mParcelLocalID(parcelLocalID) { - if (gAgent.getRegion()) - { - mPrimary = (regionID == gAgent.getRegion()->getRegionID()); - } + mPrimary = false; // will be set to primary after connection established } LLVoiceWebRTCSpatialConnection::~LLVoiceWebRTCSpatialConnection() diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h index ff82d2739da..71347f206a5 100644 --- a/indra/newview/llvoicewebrtc.h +++ b/indra/newview/llvoicewebrtc.h @@ -351,6 +351,9 @@ class LLWebRTCVoiceClient : public LLSingleton, bool isSpatial() override { return true; } bool isEstate() override { return true; } bool isCallbackPossible() override { return false; } + + private: + bool isRegionWebRTCEnabled(const LLUUID& regionID); }; class parcelSessionState : public sessionState diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index b96df0c79f2..80fc486a103 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -352,8 +352,11 @@ U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys, if (isSculpted()) { LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); - sculpt_id = sculpt_params->getSculptTexture(); - sculpt_type = sculpt_params->getSculptType(); + if (sculpt_params) + { + sculpt_id = sculpt_params->getSculptTexture(); + sculpt_type = sculpt_params->getSculptType(); + } LL_DEBUGS("ObjectUpdate") << "uuid " << mID << " set sculpt_id " << sculpt_id << LL_ENDL; } @@ -1191,12 +1194,15 @@ void LLVOVolume::updateSculptTexture() if (isSculpted() && !isMesh()) { LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); - LLUUID id = sculpt_params->getSculptTexture(); - if (id.notNull()) + if (sculpt_params) { - mSculptTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_SCULPTED, LLViewerTexture::LOD_TEXTURE); - mSculptTexture->forceToSaveRawImage(0, F32_MAX); - mSculptTexture->setKnownDrawSize(256, 256); + LLUUID id = sculpt_params->getSculptTexture(); + if (id.notNull()) + { + mSculptTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_SCULPTED, LLViewerTexture::LOD_TEXTURE); + mSculptTexture->forceToSaveRawImage(0, F32_MAX); + mSculptTexture->setKnownDrawSize(256, 256); + } } mSkinInfoUnavaliable = false; @@ -1484,6 +1490,7 @@ bool LLVOVolume::calcLOD() const LLVector3* box = avatar->getLastAnimExtents(); LLVector3 diag = box[1] - box[0]; radius = diag.magVec() * 0.5f; + LL_DEBUGS("DynamicBox") << avatar->getDebugName() << " diag " << diag << " radius " << radius << LL_ENDL; } else { @@ -1494,6 +1501,7 @@ bool LLVOVolume::calcLOD() const LLVector3* box = avatar->getLastAnimExtents(); LLVector3 diag = box[1] - box[0]; radius = diag.magVec(); // preserve old BinRadius behavior - 2x off + LL_DEBUGS("DynamicBox") << avatar->getDebugName() << " diag " << diag << " radius " << radius << LL_ENDL; } if (distance <= 0.f || radius <= 0.f) { @@ -1558,11 +1566,16 @@ bool LLVOVolume::calcLOD() mLODAdjustedDistance = distance; + static LLCachedControl debug_selection_lods(gSavedSettings, "DebugSelectionLODs", 0); if (isHUDAttachment()) { // HUDs always show at highest detail cur_detail = 3; } + else if (isSelected() && debug_selection_lods() >= 0) + { + cur_detail = llmin(debug_selection_lods(), 3); + } else { cur_detail = computeLODDetail(ll_round(distance, 0.01f), ll_round(radius, 0.01f), lod_factor); @@ -3587,12 +3600,15 @@ bool LLVOVolume::isMesh() const if (isSculpted()) { LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); - U8 sculpt_type = sculpt_params->getSculptType(); - - if ((sculpt_type & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) - // mesh is a mesh + if (sculpt_params) { - return true; + U8 sculpt_type = sculpt_params->getSculptType(); + + if ((sculpt_type & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) + // mesh is a mesh + { + return true; + } } } diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp index c708e804b25..5fb22184c3d 100644 --- a/indra/newview/llwearableitemslist.cpp +++ b/indra/newview/llwearableitemslist.cpp @@ -185,6 +185,7 @@ LLPanelWearableOutfitItem::LLPanelWearableOutfitItem(LLViewerInventoryItem* item // virtual void LLPanelWearableOutfitItem::updateItem(const std::string& name, + bool favorite, EItemState item_state) { std::string search_label = name; @@ -215,7 +216,7 @@ void LLPanelWearableOutfitItem::updateItem(const std::string& name, } } - LLPanelInventoryListItemBase::updateItem(search_label, item_state); + LLPanelInventoryListItemBase::updateItem(search_label, favorite, item_state); } ////////////////////////////////////////////////////////////////////////// @@ -442,6 +443,7 @@ LLPanelAttachmentListItem* LLPanelAttachmentListItem::create(LLViewerInventoryIt } void LLPanelAttachmentListItem::updateItem(const std::string& name, + bool favorite, EItemState item_state) { std::string title_joint = name; @@ -459,7 +461,7 @@ void LLPanelAttachmentListItem::updateItem(const std::string& name, title_joint = title_joint + " (" + trans_name + ")"; } - LLPanelInventoryListItemBase::updateItem(title_joint, item_state); + LLPanelInventoryListItemBase::updateItem(title_joint, favorite, item_state); } ////////////////////////////////////////////////////////////////////////// @@ -487,7 +489,7 @@ bool LLPanelDummyClothingListItem::postBuild() addWidgetToRightSide("btn_add_panel"); setIconImage(LLInventoryIcon::getIcon(LLAssetType::AT_CLOTHING, LLInventoryType::IT_NONE, mWearableType, false)); - updateItem(wearableTypeToString(mWearableType)); + updateItem(wearableTypeToString(mWearableType), false); // Make it look loke clothing item - reserve space for 'delete' button setLeftWidgetsWidth(getChildView("item_icon")->getRect().mLeft); @@ -921,8 +923,8 @@ LLContextMenu* LLWearableItemsList::ContextMenu::createMenu() registrar.add("Wearable.CreateNew", boost::bind(createNewWearable, selected_id)); registrar.add("Wearable.ShowOriginal", boost::bind(show_item_original, selected_id)); registrar.add("Wearable.TakeOffDetach", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); // Register handlers for clothing. registrar.add("Clothing.TakeOff", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); @@ -932,6 +934,7 @@ LLContextMenu* LLWearableItemsList::ContextMenu::createMenu() // Register handlers for attachments. registrar.add("Attachment.Detach", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); + registrar.add("Attachment.Favorite", boost::bind(toggle_favorites, ids)); registrar.add("Attachment.Touch", boost::bind(handle_attachment_touch, selected_id)); registrar.add("Attachment.Profile", boost::bind(show_item_profile, selected_id)); registrar.add("Object.Attach", boost::bind(LLViewerAttachMenu::attachObjects, ids, _2)); @@ -965,6 +968,8 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu U32 n_touchable = 0; // number of touchable items among the selected ones bool can_be_worn = true; + bool can_favorite = false; + bool can_unfavorite = false; for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) { @@ -986,6 +991,12 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu const bool is_editable = get_is_item_editable(id); const bool is_touchable = enable_attachment_touch(id); const bool is_already_worn = gAgentWearables.selfHasWearable(wearable_type); + + LLUUID linked_id = item->getLinkedUUID(); + LLViewerInventoryItem* linked_item = gInventory.getItem(linked_id); + can_favorite |= !linked_item->getIsFavorite(); + can_unfavorite |= linked_item->getIsFavorite(); + if (is_worn) { ++n_worn; @@ -1009,7 +1020,7 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu if (can_be_worn) { - can_be_worn = get_can_item_be_worn(item->getLinkedUUID()); + can_be_worn = get_can_item_be_worn(linked_id); } } // for @@ -1031,6 +1042,8 @@ void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu setMenuItemEnabled(menu, "create_new", LLAppearanceMgr::instance().canAddWearables(ids)); setMenuItemVisible(menu, "show_original", !standalone); setMenuItemEnabled(menu, "show_original", n_items == 1 && n_links == n_items); + setMenuItemVisible(menu, "favorites_add", can_favorite); + setMenuItemVisible(menu, "favorites_remove", can_unfavorite); setMenuItemVisible(menu, "take_off", mask == MASK_CLOTHING && n_worn == n_items); setMenuItemVisible(menu, "detach", mask == MASK_ATTACHMENT && n_worn == n_items); setMenuItemVisible(menu, "take_off_or_detach", mask == (MASK_ATTACHMENT|MASK_CLOTHING)); diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h index 7a5f29020e9..915a5572398 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -95,6 +95,7 @@ class LLPanelWearableOutfitItem : public LLPanelWearableListItem * Updates item name and (worn) suffix. */ /*virtual*/ void updateItem(const std::string& name, + bool favorite, EItemState item_state = IS_DEFAULT); void onAddWearable(); @@ -148,6 +149,7 @@ class LLPanelAttachmentListItem : public LLPanelDeletableWearableListItem /** Set item title. Joint name is added to the title in parenthesis */ /*virtual*/ void updateItem(const std::string& name, + bool favorite, EItemState item_state = IS_DEFAULT); protected: diff --git a/indra/newview/res/ll_icon_small.ico b/indra/newview/res/ll_icon_small.ico new file mode 100644 index 00000000000..a3f6877935c Binary files /dev/null and b/indra/newview/res/ll_icon_small.ico differ diff --git a/indra/newview/res/resource.h b/indra/newview/res/resource.h index e904f4a1a84..1d3289d7848 100644 --- a/indra/newview/res/resource.h +++ b/indra/newview/res/resource.h @@ -30,6 +30,7 @@ #define IDREMOVE 3 #define IDI_LL_ICON 103 #define IDC_GRABHAND 104 +#define IDI_LL_ICON_SMALL 105 #define IDC_CURSOR1 134 #define IDC_CURSOR2 136 #define IDC_CURSOR3 147 diff --git a/indra/newview/res/viewerRes.rc b/indra/newview/res/viewerRes.rc index 4ee26a312a9..dc2ba5f1719 100755 --- a/indra/newview/res/viewerRes.rc +++ b/indra/newview/res/viewerRes.rc @@ -56,6 +56,7 @@ END // remains consistent on all systems. IDI_LL_ICON ICON "ll_icon.ico" IDI_LCD_LL_ICON ICON "icon1.ico" +IDI_LL_ICON_SMALL ICON "ll_icon_small.ico" ///////////////////////////////////////////////////////////////////////////// // diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml index 5142e8ceff8..2ad285eb1ff 100644 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -453,6 +453,9 @@ + diff --git a/indra/newview/skins/default/textures/icons/Icon_Pointer.png b/indra/newview/skins/default/textures/icons/Icon_Pointer.png new file mode 100644 index 00000000000..021942a8aaf Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Icon_Pointer.png differ diff --git a/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Content.png b/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Content.png new file mode 100644 index 00000000000..b71b2022348 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Content.png differ diff --git a/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Full.png b/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Full.png new file mode 100644 index 00000000000..7d55fb5cfe8 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Inv_Favorite_Star_Full.png differ diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 97468c3b2eb..d650e7e791b 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -306,6 +306,8 @@ with the same filename but different name + + @@ -907,4 +909,5 @@ with the same filename but different name + diff --git a/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml b/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml deleted file mode 100644 index 6c3214a76d7..00000000000 --- a/indra/newview/skins/default/xui/en/floater_inventory_item_properties.xml +++ /dev/null @@ -1,421 +0,0 @@ - - - - (unknown) - - - (public) - - - You can: - - - Owner can: - - - [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local] - - - - Name: - - - - Description: - - - - Creator: - - - TestString PleaseIgnore - -