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
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