diff --git a/autobuild.xml b/autobuild.xml index b135c99810..6fa46093e1 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -5,7 +5,7 @@ SDL2 copyright - Copyright (C) 1997-2022 Sam Lantinga + Copyright (C) 1997-2023 Sam Lantinga description Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer. license @@ -21,9 +21,9 @@ archive hash - ba107bef7cdb9c8548d62e749d9af6fd + 535006965531b4a92e4951ee8dbe2745 url - http://3p.firestormviewer.org/SDL2-2.24.1-linux64-222872152.tar.bz2 + http://3p.firestormviewer.org/SDL2-2.28.4-linux64-232750919.tar.bz2 name linux64 @@ -865,9 +865,9 @@ archive hash - bacaa5403be0f1cba52b2250c2c227d9 + 8e92fc9ec2b3be6d2a5387fc283f954f url - http://3p.firestormviewer.org/curl-7.81.0.222121724-linux64-222121724.tar.bz2 + http://3p.firestormviewer.org/curl-8.3.0.232750741-linux64-232750741.tar.bz2 name linux64 @@ -2457,9 +2457,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - b460d5bac2d20b38cb73b54d0ab1b6e3 + 57670eaa6333d9127b8ba88f120e56dd url - http://3p.firestormviewer.org/nghttp2-1.25.0.180841549-linux64-180841549.tar.bz2 + http://3p.firestormviewer.org/nghttp2-1.56.0.232750738-linux64-232750738.tar.bz2 name linux64 @@ -2621,9 +2621,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 8ad6779b81899afb7bd803ac2e2af37a + b329ed955ac63da45957d39f601e85af url - http://3p.firestormviewer.org/open_libndofdev-0.13.222872202-linux64-222872202.tar.bz2 + http://3p.firestormviewer.org/open_libndofdev-0.13.232750923-linux64-232750923.tar.bz2 name linux64 @@ -2785,9 +2785,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - cdd9c6c484542a2e1be99149c4f0e75f + a3b2dbffa28a2b2a63c5d3ce2c12bc85 url - http://3p.firestormviewer.org/openssl-1.1.1l.222121440-linux64-222121440.tar.bz2 + http://3p.firestormviewer.org/openssl-1.1.1w.232750656-linux64-232750656.tar.bz2 name linux64 diff --git a/doc/contributions.txt b/doc/contributions.txt index d7a6b53e6b..10b635d138 100755 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -239,6 +239,7 @@ Ansariel Hiller SL-15227 SL-15398 SL-18432 + SL-19140 SL-4126 Aralara Rajal Arare Chantilly @@ -900,6 +901,7 @@ Kitty Barnett STORM-2149 MAINT-7581 MAINT-7081 + SL-18988 Kolor Fall Komiko Okamoto Korvel Noh diff --git a/indra/llappearance/CMakeLists.txt b/indra/llappearance/CMakeLists.txt index 75c0e276eb..c3be8bc78e 100644 --- a/indra/llappearance/CMakeLists.txt +++ b/indra/llappearance/CMakeLists.txt @@ -21,7 +21,6 @@ set(llappearance_SOURCE_FILES lltexglobalcolor.cpp lltexlayer.cpp lltexlayerparams.cpp - lltexturemanagerbridge.cpp llwearable.cpp llwearabledata.cpp llwearabletype.cpp @@ -44,7 +43,6 @@ set(llappearance_HEADER_FILES lltexglobalcolor.h lltexlayer.h lltexlayerparams.h - lltexturemanagerbridge.h llwearable.h llwearabledata.h llwearabletype.h diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 93d0a035da..9f23ce5317 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -55,7 +55,7 @@ void LLCallbackList::addFunction( callback_t func, void *data) // only add one callback per func/data pair // - if (containsFunction(func)) + if (containsFunction(func, data)) { return; } diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index c70d2a44d7..ca4a40458c 100644 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -272,10 +272,15 @@ void HttpOpRequest::visitNotifier(HttpRequest * request) HttpResponse::TransferStats::ptr_t stats = HttpResponse::TransferStats::ptr_t(new HttpResponse::TransferStats); +#if LL_LINUX + curl_easy_getinfo(mCurlHandle, CURLINFO_SIZE_DOWNLOAD_T, &stats->mSizeDownload); + curl_easy_getinfo(mCurlHandle, CURLINFO_TOTAL_TIME, &stats->mTotalTime); + curl_easy_getinfo(mCurlHandle, CURLINFO_SPEED_DOWNLOAD_T, &stats->mSpeedDownload); +#else curl_easy_getinfo(mCurlHandle, CURLINFO_SIZE_DOWNLOAD, &stats->mSizeDownload); curl_easy_getinfo(mCurlHandle, CURLINFO_TOTAL_TIME, &stats->mTotalTime); curl_easy_getinfo(mCurlHandle, CURLINFO_SPEED_DOWNLOAD, &stats->mSpeedDownload); - +#endif response->setTransferStats(stats); mUserHandler->onCompleted(this->getHandle(), response); diff --git a/indra/llcorehttp/httpresponse.h b/indra/llcorehttp/httpresponse.h index b834085e5c..1403e1c64f 100644 --- a/indra/llcorehttp/httpresponse.h +++ b/indra/llcorehttp/httpresponse.h @@ -75,9 +75,16 @@ public: typedef boost::shared_ptr ptr_t; TransferStats() : mSizeDownload(0.0), mTotalTime(0.0), mSpeedDownload(0.0) {} + +#if LL_LINUX + curl_off_t mSizeDownload; + curl_off_t mSpeedDownload; + F64 mTotalTime; +#else F64 mSizeDownload; F64 mTotalTime; F64 mSpeedDownload; +#endif }; diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp index 3565927821..bc63dfd493 100644 --- a/indra/llinventory/llinventory.cpp +++ b/indra/llinventory/llinventory.cpp @@ -40,9 +40,12 @@ ///---------------------------------------------------------------------------- /// Exported functions ///---------------------------------------------------------------------------- +// FIXME D567 - what's the point of these, especially if we don't even use them consistently? 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_THUMBNAIL_ID_LABEL("thumbnail_id"); static const std::string INV_ASSET_TYPE_LABEL("type"); static const std::string INV_PREFERRED_TYPE_LABEL("preferred_type"); static const std::string INV_INVENTORY_TYPE_LABEL("inv_type"); @@ -99,6 +102,7 @@ void LLInventoryObject::copyObject(const LLInventoryObject* other) mParentUUID = other->mParentUUID; mType = other->mType; mName = other->mName; + mThumbnailUUID = other->mThumbnailUUID; } const LLUUID& LLInventoryObject::getUUID() const @@ -111,6 +115,11 @@ const LLUUID& LLInventoryObject::getParentUUID() const return mParentUUID; } +const LLUUID& LLInventoryObject::getThumbnailUUID() const +{ + return mThumbnailUUID; +} + const std::string& LLInventoryObject::getName() const { return mName; @@ -160,6 +169,11 @@ void LLInventoryObject::setParent(const LLUUID& new_parent) mParentUUID = new_parent; } +void LLInventoryObject::setThumbnailUUID(const LLUUID& thumbnail_uuid) +{ + mThumbnailUUID = thumbnail_uuid; +} + void LLInventoryObject::setType(LLAssetType::EType type) { mType = type; @@ -201,6 +215,26 @@ BOOL LLInventoryObject::importLegacyStream(std::istream& input_stream) { mType = LLAssetType::lookup(valuestr); } + else if (0 == strcmp("metadata", keyword)) + { + LLSD metadata(valuestr); + if (metadata.has("thumbnail")) + { + const LLSD& thumbnail = metadata["thumbnail"]; + if (thumbnail.has("asset_id")) + { + setThumbnailUUID(thumbnail["asset_id"].asUUID()); + } + else + { + setThumbnailUUID(LLUUID::null); + } + } + else + { + setThumbnailUUID(LLUUID::null); + } + } else if(0 == strcmp("name", keyword)) { //strcpy(valuestr, buffer + strlen(keyword) + 3); @@ -336,6 +370,7 @@ void LLInventoryItem::copyItem(const LLInventoryItem* other) copyObject(other); mPermissions = other->mPermissions; mAssetUUID = other->mAssetUUID; + mThumbnailUUID = other->mThumbnailUUID; mDescription = other->mDescription; mSaleInfo = other->mSaleInfo; mInventoryType = other->mInventoryType; @@ -400,6 +435,7 @@ U32 LLInventoryItem::getCRC32() const //LL_DEBUGS() << "8 crc: " << std::hex << crc << std::dec << LL_ENDL; crc += (U32)mCreationDate; //LL_DEBUGS() << "9 crc: " << std::hex << crc << std::dec << LL_ENDL; + crc += mThumbnailUUID.getCRC32(); return crc; } @@ -733,6 +769,26 @@ BOOL LLInventoryItem::importLegacyStream(std::istream& input_stream) { mType = LLAssetType::lookup(valuestr); } + else if (0 == strcmp("metadata", keyword)) + { + LLSD metadata(valuestr); + if (metadata.has("thumbnail")) + { + const LLSD& thumbnail = metadata["thumbnail"]; + if (thumbnail.has("asset_id")) + { + setThumbnailUUID(thumbnail["asset_id"].asUUID()); + } + else + { + setThumbnailUUID(LLUUID::null); + } + } + else + { + setThumbnailUUID(LLUUID::null); + } + } else if(0 == strcmp("inv_type", keyword)) { mInventoryType = LLInventoryType::lookup(std::string(valuestr)); @@ -822,6 +878,13 @@ BOOL LLInventoryItem::exportLegacyStream(std::ostream& output_stream, BOOL inclu output_stream << "\t\tparent_id\t" << uuid_str << "\n"; mPermissions.exportLegacyStream(output_stream); + if (mThumbnailUUID.notNull()) + { + LLSD metadata; + metadata["thumbnail"] = LLSD().with("asset_id", mThumbnailUUID); + output_stream << "\t\tmetadata\t" << metadata << "|\n"; + } + // Check for permissions to see the asset id, and if so write it // out as an asset id. Otherwise, apply our cheesy encryption. if(include_asset_key) @@ -875,6 +938,11 @@ void LLInventoryItem::asLLSD( LLSD& sd ) const sd[INV_PARENT_ID_LABEL] = mParentUUID; sd[INV_PERMISSIONS_LABEL] = ll_create_sd_from_permissions(mPermissions); + if (mThumbnailUUID.notNull()) + { + sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); + } + U32 mask = mPermissions.getMaskBase(); if(((mask & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) || (mAssetUUID.isNull())) @@ -926,6 +994,35 @@ bool LLInventoryItem::fromLLSD(const LLSD& sd, bool is_new) { mParentUUID = sd[w]; } + mThumbnailUUID.setNull(); + w = INV_THUMBNAIL_LABEL; + if (sd.has(w)) + { + const LLSD &thumbnail_map = sd[w]; + w = INV_ASSET_ID_LABEL; + if (thumbnail_map.has(w)) + { + mThumbnailUUID = thumbnail_map[w]; + } + /* Example: + asset_id + acc0ec86 - 17f2 - 4b92 - ab41 - 6718b1f755f7 + perms + 8 + service + 3 + version + 1 + */ + } + else + { + w = INV_THUMBNAIL_ID_LABEL; + if (sd.has(w)) + { + mThumbnailUUID = sd[w].asUUID(); + } + } w = INV_PERMISSIONS_LABEL; if (sd.has(w)) { @@ -1050,135 +1147,6 @@ fail: } -// Deleted LLInventoryItem::exportFileXML() and LLInventoryItem::importXML() -// because I can't find any non-test code references to it. 2009-05-04 JC - -S32 LLInventoryItem::packBinaryBucket(U8* bin_bucket, LLPermissions* perm_override) const -{ - // Figure out which permissions to use. - LLPermissions perm; - if (perm_override) - { - // Use the permissions override. - perm = *perm_override; - } - else - { - // Use the current permissions. - perm = getPermissions(); - } - - // describe the inventory item - char* buffer = (char*) bin_bucket; - std::string creator_id_str; - - perm.getCreator().toString(creator_id_str); - std::string owner_id_str; - perm.getOwner().toString(owner_id_str); - std::string last_owner_id_str; - perm.getLastOwner().toString(last_owner_id_str); - std::string group_id_str; - perm.getGroup().toString(group_id_str); - std::string asset_id_str; - getAssetUUID().toString(asset_id_str); - S32 size = sprintf(buffer, /* Flawfinder: ignore */ - "%d|%d|%s|%s|%s|%s|%s|%x|%x|%x|%x|%x|%s|%s|%d|%d|%x", - getType(), - getInventoryType(), - getName().c_str(), - creator_id_str.c_str(), - owner_id_str.c_str(), - last_owner_id_str.c_str(), - group_id_str.c_str(), - perm.getMaskBase(), - perm.getMaskOwner(), - perm.getMaskGroup(), - perm.getMaskEveryone(), - perm.getMaskNextOwner(), - asset_id_str.c_str(), - getDescription().c_str(), - getSaleInfo().getSaleType(), - getSaleInfo().getSalePrice(), - getFlags()) + 1; - - return size; -} - -void LLInventoryItem::unpackBinaryBucket(U8* bin_bucket, S32 bin_bucket_size) -{ - // Early exit on an empty binary bucket. - if (bin_bucket_size <= 1) return; - - if (NULL == bin_bucket) - { - LL_ERRS() << "unpackBinaryBucket failed. bin_bucket is NULL." << LL_ENDL; - return; - } - - // Convert the bin_bucket into a string. - std::vector item_buffer(bin_bucket_size+1); - memcpy(&item_buffer[0], bin_bucket, bin_bucket_size); /* Flawfinder: ignore */ - item_buffer[bin_bucket_size] = '\0'; - std::string str(&item_buffer[0]); - - LL_DEBUGS() << "item buffer: " << str << LL_ENDL; - - // Tokenize the string. - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("|", "", boost::keep_empty_tokens); - tokenizer tokens(str, sep); - tokenizer::iterator iter = tokens.begin(); - - // Extract all values. - LLUUID item_id; - item_id.generate(); - setUUID(item_id); - - LLAssetType::EType type; - type = (LLAssetType::EType)(atoi((*(iter++)).c_str())); - setType( type ); - - LLInventoryType::EType inv_type; - inv_type = (LLInventoryType::EType)(atoi((*(iter++)).c_str())); - setInventoryType( inv_type ); - - std::string name((*(iter++)).c_str()); - rename( name ); - - LLUUID creator_id((*(iter++)).c_str()); - LLUUID owner_id((*(iter++)).c_str()); - LLUUID last_owner_id((*(iter++)).c_str()); - LLUUID group_id((*(iter++)).c_str()); - PermissionMask mask_base = strtoul((*(iter++)).c_str(), NULL, 16); - PermissionMask mask_owner = strtoul((*(iter++)).c_str(), NULL, 16); - PermissionMask mask_group = strtoul((*(iter++)).c_str(), NULL, 16); - PermissionMask mask_every = strtoul((*(iter++)).c_str(), NULL, 16); - PermissionMask mask_next = strtoul((*(iter++)).c_str(), NULL, 16); - LLPermissions perm; - perm.init(creator_id, owner_id, last_owner_id, group_id); - perm.initMasks(mask_base, mask_owner, mask_group, mask_every, mask_next); - setPermissions(perm); - //LL_DEBUGS() << "perm: " << perm << LL_ENDL; - - LLUUID asset_id((*(iter++)).c_str()); - setAssetUUID(asset_id); - - std::string desc((*(iter++)).c_str()); - setDescription(desc); - - LLSaleInfo::EForSale sale_type; - sale_type = (LLSaleInfo::EForSale)(atoi((*(iter++)).c_str())); - S32 price = atoi((*(iter++)).c_str()); - LLSaleInfo sale_info(sale_type, price); - setSaleInfo(sale_info); - - U32 flags = strtoul((*(iter++)).c_str(), NULL, 16); - setFlags(flags); - - time_t now = time(NULL); - setCreationDate(now); -} - ///---------------------------------------------------------------------------- /// Class LLInventoryCategory ///---------------------------------------------------------------------------- @@ -1228,11 +1196,32 @@ void LLInventoryCategory::setPreferredType(LLFolderType::EType type) LLSD LLInventoryCategory::asLLSD() const { LLSD sd = LLSD(); - sd["item_id"] = mUUID; - sd["parent_id"] = mParentUUID; + sd[INV_ITEM_ID_LABEL] = mUUID; + sd[INV_PARENT_ID_LABEL] = mParentUUID; S8 type = static_cast(mPreferredType); - sd["type"] = type; - sd["name"] = mName; + sd[INV_ASSET_TYPE_LABEL] = type; + sd[INV_NAME_LABEL] = mName; + + if (mThumbnailUUID.notNull()) + { + sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); + } + + return sd; +} + +LLSD LLInventoryCategory::asAISCreateCatLLSD() const +{ + LLSD sd = LLSD(); + sd[INV_FOLDER_ID_LABEL_WS] = mUUID; + sd[INV_PARENT_ID_LABEL] = mParentUUID; + 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); + } return sd; } @@ -1262,6 +1251,25 @@ bool LLInventoryCategory::fromLLSD(const LLSD& sd) { mParentUUID = sd[w]; } + mThumbnailUUID.setNull(); + w = INV_THUMBNAIL_LABEL; + if (sd.has(w)) + { + const LLSD &thumbnail_map = sd[w]; + w = INV_ASSET_ID_LABEL; + if (thumbnail_map.has(w)) + { + mThumbnailUUID = thumbnail_map[w]; + } + } + else + { + w = INV_THUMBNAIL_ID_LABEL; + if (sd.has(w)) + { + mThumbnailUUID = sd[w]; + } + } w = INV_ASSET_TYPE_LABEL; if (sd.has(w)) { @@ -1353,6 +1361,26 @@ BOOL LLInventoryCategory::importLegacyStream(std::istream& input_stream) LLStringUtil::replaceNonstandardASCII(mName, ' '); LLStringUtil::replaceChar(mName, '|', ' '); } + else if (0 == strcmp("metadata", keyword)) + { + LLSD metadata(valuestr); + if (metadata.has("thumbnail")) + { + const LLSD& thumbnail = metadata["thumbnail"]; + if (thumbnail.has("asset_id")) + { + setThumbnailUUID(thumbnail["asset_id"].asUUID()); + } + else + { + setThumbnailUUID(LLUUID::null); + } + } + else + { + setThumbnailUUID(LLUUID::null); + } + } else { LL_WARNS() << "unknown keyword '" << keyword @@ -1373,6 +1401,12 @@ BOOL LLInventoryCategory::exportLegacyStream(std::ostream& output_stream, BOOL) output_stream << "\t\ttype\t" << LLAssetType::lookup(mType) << "\n"; output_stream << "\t\tpref_type\t" << LLFolderType::lookup(mPreferredType) << "\n"; output_stream << "\t\tname\t" << mName.c_str() << "|\n"; + if (mThumbnailUUID.notNull()) + { + LLSD metadata; + metadata["thumbnail"] = LLSD().with("asset_id", mThumbnailUUID); + output_stream << "\t\tmetadata\t" << metadata << "|\n"; + } output_stream << "\t}\n"; return TRUE; } @@ -1386,6 +1420,11 @@ LLSD LLInventoryCategory::exportLLSD() const cat_data[INV_PREFERRED_TYPE_LABEL] = LLFolderType::lookup(mPreferredType); cat_data[INV_NAME_LABEL] = mName; + if (mThumbnailUUID.notNull()) + { + cat_data[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); + } + return cat_data; } @@ -1407,6 +1446,16 @@ bool LLInventoryCategory::importLLSD(const LLSD& cat_data) { setPreferredType(LLFolderType::lookup(cat_data[INV_PREFERRED_TYPE_LABEL].asString())); } + if (cat_data.has(INV_THUMBNAIL_LABEL)) + { + LLUUID thumbnail_uuid; + const LLSD &thumbnail_data = cat_data[INV_THUMBNAIL_LABEL]; + if (thumbnail_data.has(INV_ASSET_ID_LABEL)) + { + thumbnail_uuid = thumbnail_data[INV_ASSET_ID_LABEL].asUUID(); + } + setThumbnailUUID(thumbnail_uuid); + } if (cat_data.has(INV_NAME_LABEL)) { mName = cat_data[INV_NAME_LABEL].asString(); diff --git a/indra/llinventory/llinventory.h b/indra/llinventory/llinventory.h index 7d9f9704f1..6d4535af27 100644 --- a/indra/llinventory/llinventory.h +++ b/indra/llinventory/llinventory.h @@ -70,6 +70,7 @@ public: virtual const LLUUID& getUUID() const; // inventoryID that this item points to 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 const std::string& getName() const; virtual LLAssetType::EType getType() const; LLAssetType::EType getActualType() const; // bypasses indirection for linked items @@ -84,6 +85,7 @@ public: void setUUID(const LLUUID& new_uuid); virtual void rename(const std::string& new_name); void setParent(const LLUUID& new_parent); + virtual void setThumbnailUUID(const LLUUID& thumbnail_uuid); void setType(LLAssetType::EType type); virtual void setCreationDate(time_t creation_date_utc); // only stored for items @@ -108,6 +110,7 @@ public: protected: LLUUID mUUID; LLUUID mParentUUID; // Parent category. Root categories have LLUUID::NULL. + LLUUID mThumbnailUUID; LLAssetType::EType mType; std::string mName; time_t mCreationDate; // seconds from 1/1/1970, UTC @@ -203,9 +206,6 @@ public: // Helper Functions //-------------------------------------------------------------------- public: - // Pack all information needed to reconstruct this item into the given binary bucket. - S32 packBinaryBucket(U8* bin_bucket, LLPermissions* perm_override = NULL) const; - void unpackBinaryBucket(U8* bin_bucket, S32 bin_bucket_size); LLSD asLLSD() const; void asLLSD( LLSD& sd ) const; bool fromLLSD(const LLSD& sd, bool is_new = true); @@ -253,6 +253,7 @@ public: LLFolderType::EType getPreferredType() const; void setPreferredType(LLFolderType::EType type); LLSD asLLSD() const; + LLSD asAISCreateCatLLSD() const; bool fromLLSD(const LLSD& sd); //-------------------------------------------------------------------- diff --git a/indra/llinventory/tests/inventorymisc_test.cpp b/indra/llinventory/tests/inventorymisc_test.cpp index e8b063bffe..039fa938dd 100644 --- a/indra/llinventory/tests/inventorymisc_test.cpp +++ b/indra/llinventory/tests/inventorymisc_test.cpp @@ -400,27 +400,7 @@ namespace tut // Deleted LLInventoryItem::exportFileXML() and LLInventoryItem::importXML() // because I can't find any non-test code references to it. 2009-05-04 JC } - - template<> template<> - void inventory_object::test<10>() - { - LLPointer src1 = create_random_inventory_item(); - U8* bin_bucket = new U8[300]; - S32 bin_bucket_size = src1->packBinaryBucket(bin_bucket, NULL); - LLPointer src2 = new LLInventoryItem(); - src2->unpackBinaryBucket(bin_bucket, bin_bucket_size); - - ensure_equals("1.sale price::getSalePrice() failed price", src1->getSaleInfo().getSalePrice(), src2->getSaleInfo().getSalePrice()); - ensure_equals("2.sale type::getSaleType() failed type", src1->getSaleInfo().getSaleType(), src2->getSaleInfo().getSaleType()); - ensure_equals("3.type::getType() failed", src1->getType(), src2->getType()); - ensure_equals("4.inventory type::getInventoryType() failed type", src1->getInventoryType(), src2->getInventoryType()); - ensure_equals("5.name::getName() failed", src1->getName(), src2->getName()); - ensure_equals("6.description::getDescription() failed", src1->getDescription(), src2->getDescription()); - ensure_equals("7.flags::getFlags() failed", src1->getFlags(), src2->getFlags()); - - } - template<> template<> void inventory_object::test<11>() { diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp index 0d03c5f241..2bf65cdf25 100644 --- a/indra/llmessage/llcoproceduremanager.cpp +++ b/indra/llmessage/llcoproceduremanager.cpp @@ -364,7 +364,26 @@ LLUUID LLCoprocedurePool::enqueueCoprocedure(const std::string &name, LLCoproced { LLUUID id(LLUUID::generateNewID()); - LL_DEBUGS("CoProcMgr") << "Coprocedure(" << name << ") enqueuing with id=" << id.asString() << " in pool \"" << mPoolName << "\" at " << mPending << LL_ENDL; + if (mPoolName == "AIS") + { + // Fetch is going to be spammy. + LL_DEBUGS("CoProcMgr", "Inventory") << "Coprocedure(" << name << ") enqueuing with id=" << id.asString() << " in pool \"" << mPoolName + << "\" at " + << mPending << LL_ENDL; + + if (mPending >= (LLCoprocedureManager::DEFAULT_QUEUE_SIZE - 1)) + { + // If it's all used up (not supposed to happen, + // fetched should cap it), we are going to crash + LL_WARNS("CoProcMgr", "Inventory") << "About to run out of queue space for Coprocedure(" << name + << ") enqueuing with id=" << id.asString() << " Already pending:" << mPending << LL_ENDL; + } + } + else + { + LL_DEBUGS("CoProcMgr") << "Coprocedure(" << name << ") enqueuing with id=" << id.asString() << " in pool \"" << mPoolName << "\" at " + << mPending << LL_ENDL; + } auto pushed = mPendingCoprocs->try_push(boost::make_shared(name, id, proc)); if (pushed == boost::fibers::channel_op_status::success) { diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp index a9d03c425c..533ec40e07 100644 --- a/indra/llmessage/message.cpp +++ b/indra/llmessage/message.cpp @@ -3413,6 +3413,7 @@ typedef std::map BuilderMap; void LLMessageSystem::newMessageFast(const char *name) { + //LL_DEBUGS("Messaging") << "creating new message: " << name << LL_ENDL; LLMessageConfig::Flavor message_flavor = LLMessageConfig::getMessageFlavor(name); LLMessageConfig::Flavor server_flavor = diff --git a/indra/llrender/CMakeLists.txt b/indra/llrender/CMakeLists.txt index 544804ad97..0d0375c060 100644 --- a/indra/llrender/CMakeLists.txt +++ b/indra/llrender/CMakeLists.txt @@ -29,6 +29,7 @@ set(llrender_SOURCE_FILES llrendertarget.cpp llshadermgr.cpp lltexture.cpp + lltexturemanagerbridge.cpp lluiimage.cpp llvertexbuffer.cpp llglcommonfunc.cpp @@ -58,6 +59,7 @@ set(llrender_HEADER_FILES llrendersphere.h llshadermgr.h lltexture.h + lltexturemanagerbridge.h lluiimage.h lluiimage.inl llvertexbuffer.h diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index b169e27fa5..6854f9a8e6 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -1083,6 +1083,20 @@ LLFontGL* LLFontGL::getFontSansSerifSmall() return fontp; } +//static +LLFontGL* LLFontGL::getFontSansSerifSmallBold() +{ + static LLFontGL* fontp = getFont(LLFontDescriptor("SansSerif","Small",BOLD)); + return fontp; +} + +//static +LLFontGL* LLFontGL::getFontSansSerifSmallItalic() +{ + static LLFontGL* fontp = getFont(LLFontDescriptor("SansSerif","Small",ITALIC)); + return fontp; +} + //static LLFontGL* LLFontGL::getFontSansSerif() { diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h index edff8ebc5f..ce52850c05 100644 --- a/indra/llrender/llfontgl.h +++ b/indra/llrender/llfontgl.h @@ -189,6 +189,8 @@ public: static LLFontGL* getFontMonospace(); static LLFontGL* getFontSansSerifSmall(); + static LLFontGL* getFontSansSerifSmallBold(); + static LLFontGL* getFontSansSerifSmallItalic(); static LLFontGL* getFontSansSerif(); static LLFontGL* getFontSansSerifBig(); static LLFontGL* getFontSansSerifHuge(); diff --git a/indra/llappearance/lltexturemanagerbridge.cpp b/indra/llrender/lltexturemanagerbridge.cpp similarity index 100% rename from indra/llappearance/lltexturemanagerbridge.cpp rename to indra/llrender/lltexturemanagerbridge.cpp diff --git a/indra/llappearance/lltexturemanagerbridge.h b/indra/llrender/lltexturemanagerbridge.h similarity index 97% rename from indra/llappearance/lltexturemanagerbridge.h rename to indra/llrender/lltexturemanagerbridge.h index 101704b162..f61433ea4d 100644 --- a/indra/llappearance/lltexturemanagerbridge.h +++ b/indra/llrender/lltexturemanagerbridge.h @@ -27,7 +27,6 @@ #ifndef LL_TEXTUREMANAGERBRIDGE_H #define LL_TEXTUREMANAGERBRIDGE_H -#include "llavatarappearancedefines.h" #include "llpointer.h" #include "llgltexture.h" diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index 5961fde360..0d9a34ef9c 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -217,7 +217,8 @@ LLButton::LLButton(const LLButton::Params& p) } // Hack to make sure there is space for at least one character - if (getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(std::string(" "))) + if (getRect().mRight >= 0 && getRect().getWidth() > 0 && + getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(std::string(" "))) { // Use old defaults mLeftHPad = llbutton_orig_h_pad; diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index 960df92fc0..763b1d6919 100644 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -193,7 +193,9 @@ LLFolderView::LLFolderView(const Params& p) mStatusTextBox(NULL), mShowItemLinkOverlays(p.show_item_link_overlays), mViewModel(p.view_model), - mGroupedItemModel(p.grouped_item_model) + mGroupedItemModel(p.grouped_item_model), + mForceArrange(false), + mSingleFolderMode(false) { LLPanel* panel = p.parent_panel; mParentPanel = panel->getHandle(); @@ -613,6 +615,7 @@ void LLFolderView::clearSelection() } mSelectedItems.clear(); + mNeedsScroll = false; } std::set LLFolderView::getSelectionList() const @@ -685,7 +688,7 @@ void LLFolderView::draw() } else if (mShowEmptyMessage) { - mStatusTextBox->setValue(getFolderViewModel()->getStatusText()); + mStatusTextBox->setValue(getFolderViewModel()->getStatusText(mItems.empty() && mFolders.empty())); mStatusTextBox->setVisible( TRUE ); // firstly reshape message textbox with current size. This is necessary to @@ -713,12 +716,16 @@ void LLFolderView::draw() } } - if (mRenameItem && mRenamer && mRenamer->getVisible() && !getVisibleRect().overlaps(mRenamer->getRect())) - { - // renamer is not connected to the item we are renaming in any form so manage it manually - // TODO: consider stopping on any scroll action instead of when out of visible area - finishRenamingItem(); - } + if (mRenameItem + && mRenamer + && mRenamer->getVisible() + && !getVisibleRect().overlaps(mRenamer->getRect())) + { + // renamer is not connected to the item we are renaming in any form so manage it manually + // TODO: consider stopping on any scroll action instead of when out of visible area + LL_DEBUGS("Inventory") << "Renamer out of bounds, hiding" << LL_ENDL; + finishRenamingItem(); + } // skip over LLFolderViewFolder::draw since we don't want the folder icon, label, // and arrow for the root folder @@ -852,9 +859,12 @@ void LLFolderView::autoOpenItem( LLFolderViewFolder* item ) mAutoOpenItems.push(item); item->setOpen(TRUE); + if(!item->isSingleFolderMode()) + { LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); LLRect constraint_rect(0,content_rect.getHeight(), content_rect.getWidth(), 0); scrollToShowItem(item, constraint_rect); + } } void LLFolderView::closeAutoOpenedFolders() @@ -1075,6 +1085,8 @@ void LLFolderView::paste() // public rename functionality - can only start the process void LLFolderView::startRenamingSelectedItem( void ) { + LL_DEBUGS("Inventory") << "Starting inventory renamer" << LL_ENDL; + // make sure selection is visible scrollToShowSelection(); @@ -1310,6 +1322,11 @@ BOOL LLFolderView::handleKeyHere( KEY key, MASK mask ) if(mSelectedItems.size()) { LLFolderViewItem* last_selected = getCurSelectedItem(); + if(last_selected && last_selected->isSingleFolderMode()) + { + handled = FALSE; + break; + } LLFolderViewItem* parent_folder = last_selected->getParentFolder(); if (!last_selected->isOpen() && parent_folder && parent_folder->getParentFolder()) { @@ -1552,9 +1569,19 @@ BOOL LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask ) mCallbackRegistrar->popScope(); } } + + BOOL item_clicked = FALSE; + for (selected_items_t::iterator item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) + { + item_clicked |= (*item_it)->getRect().pointInRect(x, y); + } + if(!item_clicked && mSingleFolderMode) + { + clearSelection(); + } bool hide_folder_menu = mSuppressFolderMenu && isFolderSelected(); - if (menu && (handled - && ( count > 0 && (hasVisibleChildren()) )) && // show menu only if selected items are visible + if (menu && (mSingleFolderMode || (handled + && ( count > 0 && (hasVisibleChildren()) ))) && // show menu only if selected items are visible !hide_folder_menu) { if (mCallbackRegistrar) @@ -1626,6 +1653,22 @@ BOOL LLFolderView::handleHover( S32 x, S32 y, MASK mask ) return LLView::handleHover( x, y, mask ); } +LLFolderViewItem* LLFolderView::getHoveredItem() const +{ + return dynamic_cast(mHoveredItem.get()); +} + +void LLFolderView::setHoveredItem(LLFolderViewItem* itemp) +{ + if (mHoveredItem.get() != itemp) + { + if (itemp) + mHoveredItem = itemp->getHandle(); + else + mHoveredItem.markDead(); + } +} + BOOL LLFolderView::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, @@ -1810,7 +1853,7 @@ void LLFolderView::update() mNeedsAutoSelect = FALSE; } - BOOL is_visible = isInVisibleChain(); + BOOL is_visible = isInVisibleChain() || mForceArrange; //Puts folders/items in proper positions // arrange() takes the model filter flag into account and call sort() if necessary (CHUI-849) @@ -1911,13 +1954,28 @@ void LLFolderView::update() } } - if (mSignalSelectCallback) - { - //RN: we use keyboard focus as a proxy for user-explicit actions - BOOL take_keyboard_focus = (mSignalSelectCallback == SIGNAL_KEYBOARD_FOCUS); - mSelectSignal(mSelectedItems, take_keyboard_focus); - } - mSignalSelectCallback = FALSE; + if (mSelectedItems.size()) + { + LLFolderViewItem* item = mSelectedItems.back(); + // If the goal is to show renamer, don't callback untill + // item is visible or is no longer being scrolled to. + // Otherwise renamer will be instantly closed + // Todo: consider moving renamer out of selection callback + if (!mNeedsAutoRename || !mNeedsScroll || item->getVisible()) + { + if (mSignalSelectCallback) + { + //RN: we use keyboard focus as a proxy for user-explicit actions + BOOL take_keyboard_focus = (mSignalSelectCallback == SIGNAL_KEYBOARD_FOCUS); + mSelectSignal(mSelectedItems, take_keyboard_focus); + } + mSignalSelectCallback = FALSE; + } + } + else + { + mSignalSelectCallback = FALSE; + } } void LLFolderView::dumpSelectionInformation() @@ -1980,6 +2038,11 @@ void LLFolderView::updateMenuOptions(LLMenuGL* menu) flags = multi_select_flag; } + if(mSingleFolderMode && (mSelectedItems.size() == 0)) + { + buildContextMenu(*menu, flags); + } + // This adds a check for restrictions based on the entire // selection set - for example, any one wearable may not push you // over the limit, but all wearables together still might. @@ -2136,7 +2199,7 @@ LLFolderViewItem* LLFolderView::getNextUnselectedItem() return new_selection; } -S32 LLFolderView::getItemHeight() +S32 LLFolderView::getItemHeight() const { if(!hasVisibleChildren()) { diff --git a/indra/llui/llfolderview.h b/indra/llui/llfolderview.h index e6d6d76ad9..5970334ede 100644 --- a/indra/llui/llfolderview.h +++ b/indra/llui/llfolderview.h @@ -127,6 +127,9 @@ public: bool getAllowMultiSelect() { return mAllowMultiSelect; } bool getAllowDrag() { return mAllowDrag; } + void setSingleFolderMode(bool is_single_mode) { mSingleFolderMode = is_single_mode; } + bool isSingleFolderMode() { return mSingleFolderMode; } + // Close all folders in the view void closeAllFolders(); void openTopLevelFolders(); @@ -136,7 +139,7 @@ public: // Find width and height of this object and its children. Also // makes sure that this view and its children are the right size. virtual S32 arrange( S32* width, S32* height ); - virtual S32 getItemHeight(); + virtual S32 getItemHeight() const; void arrangeAll() { mArrangeGeneration++; } S32 getArrangeGeneration() { return mArrangeGeneration; } @@ -144,6 +147,10 @@ public: // applies filters to control visibility of items virtual void filter( LLFolderViewFilter& filter); + void clearHoveredItem() { setHoveredItem(nullptr); } + LLFolderViewItem* getHoveredItem() const; + void setHoveredItem(LLFolderViewItem* itemp); + // Get the last selected item virtual LLFolderViewItem* getCurSelectedItem( void ); selected_items_t& getSelectedItems( void ); @@ -242,11 +249,15 @@ public: void setCallbackRegistrar(LLUICtrl::CommitCallbackRegistry::ScopedRegistrar* registrar) { mCallbackRegistrar = registrar; } void setEnableRegistrar(LLUICtrl::EnableCallbackRegistry::ScopedRegistrar* registrar) { mEnableRegistrar = registrar; } + void setForceArrange(bool force) { mForceArrange = force; } + LLPanel* getParentPanel() { return mParentPanel.get(); } // DEBUG only void dumpSelectionInformation(); virtual S32 notify(const LLSD& info) ; + + void setShowEmptyMessage(bool show_msg) { mShowEmptyMessage = show_msg; } bool useLabelSuffix() { return mUseLabelSuffix; } virtual void updateMenu(); @@ -280,6 +291,7 @@ protected: LLHandle mPopupMenuHandle; std::string mMenuFileName; + LLHandle mHoveredItem; selected_items_t mSelectedItems; bool mKeyboardSelection, mAllowMultiSelect, @@ -296,7 +308,8 @@ protected: mShowItemLinkOverlays, mShowSelectionContext, mShowSingleSelection, - mSuppressFolderMenu; + mSuppressFolderMenu, + mSingleFolderMode; // Renaming variables and methods LLFolderViewItem* mRenameItem; // The item currently being renamed @@ -339,6 +352,8 @@ protected: LLUICtrl::CommitCallbackRegistry::ScopedRegistrar* mCallbackRegistrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar* mEnableRegistrar; + + bool mForceArrange; public: static F32 sAutoOpenTime; diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index a064002d1d..4ed91b263e 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -34,6 +34,7 @@ #include "llfolderview.h" #include "llfolderviewmodel.h" #include "llpanel.h" +#include "llcallbacklist.h" #include "llcriticaldamp.h" #include "llclipboard.h" #include "llfocusmgr.h" // gFocusMgr @@ -117,6 +118,8 @@ LLFolderViewItem::Params::Params() icon_width("icon_width", 0), text_pad("text_pad", 0), text_pad_right("text_pad_right", 0), + single_folder_mode("single_folder_mode", false), + double_click_override("double_click_override", false), arrow_size("arrow_size", 0), max_folder_item_overlap("max_folder_item_overlap", 0), // Inventory specials @@ -158,10 +161,12 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mTextPad(p.text_pad), mTextPadRight(p.text_pad_right), mArrowSize(p.arrow_size), + mSingleFolderMode(p.single_folder_mode), mMaxFolderItemOverlap(p.max_folder_item_overlap), - // Inventory specials - mForInventory(p.for_inventory), - mItemTopPad(p.item_top_pad) + mDoubleClickOverride(p.double_click_override), + // Inventory specials + mForInventory(p.for_inventory), + mItemTopPad(p.item_top_pad) { if (!sColorSetInitialized) { @@ -177,7 +182,7 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE); sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); // Fix misleading color name - //sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE); + //sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemSuffixColor", DEFAULT_WHITE); // sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); @@ -433,7 +438,7 @@ S32 LLFolderViewItem::arrange( S32* width, S32* height ) // it is purely visual, so it is fine to do at our laisure refreshSuffix(); } - mLabelWidth = getLabelXPos() + getLabelFontForStyle(mLabelStyle)->getWidth(mLabel) + getLabelFontForStyle(mLabelStyle)->getWidth(mLabelSuffix) + mLabelPaddingRight; + mLabelWidth = getLabelXPos() + getLabelFontForStyle(mLabelStyle)->getWidth(mLabel) + getLabelFontForStyle(LLFontGL::NORMAL)->getWidth(mLabelSuffix) + mLabelPaddingRight; mLabelWidthDirty = false; } @@ -450,7 +455,7 @@ S32 LLFolderViewItem::arrange( S32* width, S32* height ) return *height; } -S32 LLFolderViewItem::getItemHeight() +S32 LLFolderViewItem::getItemHeight() const { return mItemHeight; } @@ -690,11 +695,14 @@ BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask ) getWindow()->setCursor(UI_CURSOR_NOLOCKED); // [/SL:KB] + root->clearHoveredItem(); return TRUE; } else { - getRoot()->setShowSelectionContext(FALSE); + LLFolderView* pRoot = getRoot(); + pRoot->setHoveredItem(this); + pRoot->setShowSelectionContext(FALSE); getWindow()->setCursor(UI_CURSOR_ARROW); // let parent handle this then... return FALSE; @@ -749,6 +757,13 @@ BOOL LLFolderViewItem::handleMouseUp( S32 x, S32 y, MASK mask ) void LLFolderViewItem::onMouseLeave(S32 x, S32 y, MASK mask) { mIsMouseOverTitle = false; + + // NOTE: LLViewerWindow::updateUI() calls "enter" before "leave"; if the mouse moved to another item, we can't just outright clear it + LLFolderView* pRoot = getRoot(); + if (this == pRoot->getHoveredItem()) + { + pRoot->clearHoveredItem(); + } } BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, @@ -959,7 +974,10 @@ void LLFolderViewItem::draw() getViewModelItem()->update(); - drawOpenFolderArrow(default_params, sFgColor); + if(!mSingleFolderMode) + { + drawOpenFolderArrow(default_params, sFgColor); + } drawHighlight(show_context, filled, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); @@ -995,16 +1013,43 @@ void LLFolderViewItem::draw() F32 text_left = (F32)getLabelXPos(); std::string combined_string = mLabel + mLabelSuffix; + const LLFontGL* suffix_font = getLabelFontForStyle(LLFontGL::NORMAL); + S32 filter_offset = mViewModelItem->getFilterStringOffset(); if (filter_string_length > 0) { - S32 left = ll_round(text_left) + font->getWidth(combined_string, 0, mViewModelItem->getFilterStringOffset()) - 2; + S32 bottom = llfloor(getRect().getHeight() - font->getLineHeight() - 3 - TOP_PAD); + S32 top = getRect().getHeight() - TOP_PAD; + if(mLabelSuffix.empty() || (font == suffix_font)) + { + S32 left = ll_round(text_left) + font->getWidth(combined_string, 0, mViewModelItem->getFilterStringOffset()) - 2; S32 right = left + font->getWidth(combined_string, mViewModelItem->getFilterStringOffset(), filter_string_length) + 2; - S32 bottom = llfloor(getRect().getHeight() - font->getLineHeight() - 3 - TOP_PAD); - S32 top = getRect().getHeight() - TOP_PAD; LLUIImage* box_image = default_params.selection_image; LLRect box_rect(left, top, right, bottom); box_image->draw(box_rect, sFilterBGColor); + } + else + { + S32 label_filter_length = llmin((S32)mLabel.size() - filter_offset, (S32)filter_string_length); + if(label_filter_length > 0) + { + S32 left = ll_round(text_left) + font->getWidthF32(mLabel, 0, llmin(filter_offset, (S32)mLabel.size())) - 2; + S32 right = left + font->getWidthF32(mLabel, filter_offset, label_filter_length) + 2; + LLUIImage* box_image = default_params.selection_image; + LLRect box_rect(left, top, right, bottom); + box_image->draw(box_rect, sFilterBGColor); + } + S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length; + if(suffix_filter_length > 0) + { + S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); + S32 left = ll_round(text_left) + font->getWidthF32(mLabel, 0, mLabel.size()) + suffix_font->getWidthF32(mLabelSuffix, 0, suffix_offset) - 2; + S32 right = left + suffix_font->getWidthF32(mLabelSuffix, suffix_offset, suffix_filter_length) + 2; + LLUIImage* box_image = default_params.selection_image; + LLRect box_rect(left, top, right, bottom); + box_image->draw(box_rect, sFilterBGColor); + } + } } LLColor4 color = (mIsSelected && filled) ? mFontHighlightColor : mFontColor; @@ -1041,7 +1086,7 @@ void LLFolderViewItem::draw() // if (!mLabelSuffix.empty()) { - font->renderUTF8( mLabelSuffix, 0, right_x, y, isFadeItem() ? color : (LLColor4)sSuffixColor, + suffix_font->renderUTF8( mLabelSuffix, 0, right_x, y, isFadeItem() ? color : (LLColor4)sSuffixColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, &right_x, FALSE ); } @@ -1051,12 +1096,35 @@ void LLFolderViewItem::draw() // if (filter_string_length > 0) { - S32 filter_offset = mViewModelItem->getFilterStringOffset(); - F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string, filter_offset, filter_string_length); - F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; - font->renderUTF8( combined_string, filter_offset, match_string_left, yy, + if(mLabelSuffix.empty() || (font == suffix_font)) + { + F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string, filter_offset, filter_string_length); + F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + font->renderUTF8( combined_string, filter_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, filter_string_length, S32_MAX, &right_x, FALSE ); + } + else + { + S32 label_filter_length = llmin((S32)mLabel.size() - filter_offset, (S32)filter_string_length); + if(label_filter_length > 0) + { + F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, filter_offset + label_filter_length) - font->getWidthF32(mLabel, filter_offset, label_filter_length); + F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + font->renderUTF8( mLabel, filter_offset, match_string_left, yy, + sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, label_filter_length, S32_MAX, &right_x, FALSE ); + } + + S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length; + if(suffix_filter_length > 0) + { + S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); + F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, mLabel.size()) + suffix_font->getWidthF32(mLabelSuffix, 0, suffix_offset + suffix_filter_length) - suffix_font->getWidthF32(mLabelSuffix, suffix_offset, suffix_filter_length); + F32 yy = (F32)getRect().getHeight() - suffix_font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + suffix_font->renderUTF8( mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, suffix_filter_length, S32_MAX, &right_x, FALSE ); + } + } + } //Gilbert Linden 9-20-2012: Although this should be legal, removing it because it causes the mLabelSuffix rendering to @@ -1391,7 +1459,7 @@ BOOL LLFolderViewFolder::setSelection(LLFolderViewItem* selection, BOOL openitem child_selected = TRUE; } } - if(openitem && child_selected) + if(openitem && child_selected && !mSingleFolderMode) { setOpenArrangeRecursively(TRUE); } @@ -1816,6 +1884,11 @@ BOOL LLFolderViewFolder::isRemovable() return TRUE; } +void LLFolderViewFolder::destroyRoot() +{ + delete this; +} + // this is an internal method used for adding items to folders. void LLFolderViewFolder::addItem(LLFolderViewItem* item) { @@ -1884,7 +1957,19 @@ void LLFolderViewFolder::toggleOpen() // Force a folder open or closed void LLFolderViewFolder::setOpen(BOOL openitem) { - setOpenArrangeRecursively(openitem); + if(mSingleFolderMode) + { + // navigateToFolder can destroy this view + // delay it in case setOpen was called from click or key processing + doOnIdleOneTime([this]() + { + getViewModelItem()->navigateToFolder(); + }); + } + else + { + setOpenArrangeRecursively(openitem); + } } void LLFolderViewFolder::setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse) @@ -2087,7 +2172,8 @@ BOOL LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask ) } if( !handled ) { - if(mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad) + if((mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad) + && !mSingleFolderMode) { toggleOpen(); handled = TRUE; @@ -2105,12 +2191,45 @@ BOOL LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask ) BOOL LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; + if(mSingleFolderMode) + { + static LLUICachedControl double_click_new_window("SingleModeDoubleClickOpenWindow", false); + if (double_click_new_window) + { + getViewModelItem()->navigateToFolder(true); + } + else + { + // navigating is going to destroy views and change children + // delay it untill handleDoubleClick processing is complete + doOnIdleOneTime([this]() + { + getViewModelItem()->navigateToFolder(false); + }); + } + return TRUE; + } + if( isOpen() ) { handled = childrenHandleDoubleClick( x, y, mask ) != NULL; } if( !handled ) { + if(mDoubleClickOverride) + { + static LLUICachedControl double_click_action("MultiModeDoubleClickFolder", false); + if (double_click_action == 1) + { + getViewModelItem()->navigateToFolder(true); + return TRUE; + } + if (double_click_action == 2) + { + getViewModelItem()->navigateToFolder(false, true); + return TRUE; + } + } if(mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad) { // don't select when user double-clicks plus sign diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index d2d7e3c36c..20009c531f 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -72,6 +72,8 @@ public: text_pad_right, arrow_size, max_folder_item_overlap; + Optional single_folder_mode, + double_click_override; // Inventory specials Optional for_inventory; @@ -125,6 +127,8 @@ protected: mIsMouseOverTitle, mAllowWear, mAllowDrop, + mSingleFolderMode, + mDoubleClickOverride, mSelectPending, mIsItemCut; @@ -184,7 +188,7 @@ public: // Finds width and height of this object and it's children. Also // makes sure that this view and it's children are the right size. virtual S32 arrange( S32* width, S32* height ); - virtual S32 getItemHeight(); + virtual S32 getItemHeight() const; virtual S32 getLabelXPos(); S32 getIconPad(); S32 getTextPad(); @@ -223,9 +227,9 @@ public: void setIsCurSelection(BOOL select) { mIsCurSelection = select; } - BOOL getIsCurSelection() { return mIsCurSelection; } + BOOL getIsCurSelection() const { return mIsCurSelection; } - BOOL hasVisibleChildren() { return mHasVisibleChildren; } + BOOL hasVisibleChildren() const { return mHasVisibleChildren; } // true if object can't have children virtual bool isFolderComplete() { return true; } @@ -274,7 +278,7 @@ public: virtual LLFolderView* getRoot(); virtual const LLFolderView* getRoot() const; BOOL isDescendantOf( const LLFolderViewFolder* potential_ancestor ); - S32 getIndentation() { return mIndentation; } + S32 getIndentation() const { return mIndentation; } virtual BOOL passedFilter(S32 filter_generation = -1); virtual BOOL isPotentiallyVisible(S32 filter_generation = -1); @@ -287,6 +291,8 @@ public: // Does not need filter update virtual void refreshSuffix(); + bool isSingleFolderMode() { return mSingleFolderMode; } + // LLView functionality virtual BOOL handleRightMouseDown( S32 x, S32 y, MASK mask ); virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ); @@ -402,6 +408,7 @@ public: // destroys this folder, and all children virtual void destroyView(); + void destroyRoot(); // whether known children are fully loaded (arrange sets to true) virtual bool isFolderComplete() { return mIsFolderComplete; } diff --git a/indra/llui/llfolderviewmodel.cpp b/indra/llui/llfolderviewmodel.cpp index 93122503d1..f217b743a0 100644 --- a/indra/llui/llfolderviewmodel.cpp +++ b/indra/llui/llfolderviewmodel.cpp @@ -34,7 +34,7 @@ bool LLFolderViewModelCommon::needsSort(LLFolderViewModelItem* item) return item->getSortVersion() < mTargetSortVersion; } -std::string LLFolderViewModelCommon::getStatusText() +std::string LLFolderViewModelCommon::getStatusText(bool is_empty_folder) { if (!contentsReady() || mFolderView->getViewModelItem()->getLastFilterGeneration() < getFilter().getCurrentGeneration()) { @@ -42,7 +42,7 @@ std::string LLFolderViewModelCommon::getStatusText() } else { - return getFilter().getEmptyLookupMessage(); + return getFilter().getEmptyLookupMessage(is_empty_folder); } } diff --git a/indra/llui/llfolderviewmodel.h b/indra/llui/llfolderviewmodel.h index 31d28a5257..6080eea330 100644 --- a/indra/llui/llfolderviewmodel.h +++ b/indra/llui/llfolderviewmodel.h @@ -69,7 +69,7 @@ public: virtual bool checkFolder(const LLFolderViewModelItem* folder) const = 0; virtual void setEmptyLookupMessage(const std::string& message) = 0; - virtual std::string getEmptyLookupMessage() const = 0; + virtual std::string getEmptyLookupMessage(bool is_empty_folder = false) const = 0; virtual bool showAllResults() const = 0; @@ -125,7 +125,7 @@ public: virtual void setFolderView(LLFolderView* folder_view) = 0; virtual LLFolderViewFilter& getFilter() = 0; virtual const LLFolderViewFilter& getFilter() const = 0; - virtual std::string getStatusText() = 0; + virtual std::string getStatusText(bool is_empty_folder = false) = 0; virtual bool startDrag(std::vector& items) = 0; }; @@ -160,6 +160,8 @@ public: virtual void openItem( void ) = 0; virtual void closeItem( void ) = 0; virtual void selectItem(void) = 0; + + virtual void navigateToFolder(bool new_window = false, bool change_mode = false) = 0; virtual BOOL isItemWearable() const { return FALSE; } @@ -405,7 +407,7 @@ public: // sort everything mTargetSortVersion++; } - virtual std::string getStatusText(); + virtual std::string getStatusText(bool is_empty_folder = false); virtual void filter(); void setFolderView(LLFolderView* folder_view) { mFolderView = folder_view;} diff --git a/indra/llui/lliconctrl.h b/indra/llui/lliconctrl.h index 5d6c544571..e983d63a01 100644 --- a/indra/llui/lliconctrl.h +++ b/indra/llui/lliconctrl.h @@ -39,7 +39,9 @@ class LLUICtrlFactory; // Classes // -// +// Class for diplaying named UI textures +// Do not use for displaying textures from network, +// UI textures are stored permanently! class LLIconCtrl : public LLUICtrl { diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp index 67bab51bfd..fb24c08528 100644 --- a/indra/llui/lllayoutstack.cpp +++ b/indra/llui/lllayoutstack.cpp @@ -224,7 +224,8 @@ LLLayoutStack::Params::Params() drag_handle_first_indent("drag_handle_first_indent", 0), drag_handle_second_indent("drag_handle_second_indent", 0), drag_handle_thickness("drag_handle_thickness", 5), - drag_handle_shift("drag_handle_shift", 2) + drag_handle_shift("drag_handle_shift", 2), + drag_handle_color("drag_handle_color", LLUIColorTable::instance().getColor("ResizebarBody")) { addSynonym(border_size, "drag_handle_gap"); } @@ -245,7 +246,8 @@ LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& p) mDragHandleFirstIndent(p.drag_handle_first_indent), mDragHandleSecondIndent(p.drag_handle_second_indent), mDragHandleThickness(p.drag_handle_thickness), - mDragHandleShift(p.drag_handle_shift) + mDragHandleShift(p.drag_handle_shift), + mDragHandleColor(p.drag_handle_color()) { // Set up settings control to save sizes if not already present if (mSaveSizes) @@ -591,6 +593,15 @@ void LLLayoutStack::updateLayout() mNeedsLayout = continue_animating; } // end LLLayoutStack::updateLayout +void LLLayoutStack::setPanelSpacing(S32 val) +{ + if (mPanelSpacing != val) + { + mPanelSpacing = val; + mNeedsLayout = true; + } +} + LLLayoutPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) const { if (!panelp) return NULL; @@ -644,7 +655,7 @@ void LLLayoutStack::createResizeBar(LLLayoutPanel* panelp) resize_bar_bg_panel_p.follows.flags = FOLLOWS_ALL; resize_bar_bg_panel_p.tab_stop = false; resize_bar_bg_panel_p.background_visible = true; - resize_bar_bg_panel_p.bg_alpha_color = LLUIColorTable::instance().getColor("ResizebarBody"); + resize_bar_bg_panel_p.bg_alpha_color = mDragHandleColor; resize_bar_bg_panel_p.has_border = true; resize_bar_bg_panel_p.border.border_thickness = 1; resize_bar_bg_panel_p.border.highlight_light_color = LLUIColorTable::instance().getColor("ResizebarBorderLight"); diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h index 142712b5f5..f56cf216eb 100644 --- a/indra/llui/lllayoutstack.h +++ b/indra/llui/lllayoutstack.h @@ -59,6 +59,8 @@ public: Optional drag_handle_thickness; Optional drag_handle_shift; + Optional drag_handle_color; + // Add size save control. Caveat: contained panels and widgets need to use // relative sizing, like right="-1" instead of width="XYZ" to get resized // properly when a saved size is being restored. -Zi @@ -94,6 +96,7 @@ public: void updateLayout(); S32 getPanelSpacing() const { return mPanelSpacing; } + void setPanelSpacing(S32 val); static void updateClass(); @@ -136,6 +139,7 @@ private: S32 mDragHandleSecondIndent; S32 mDragHandleThickness; S32 mDragHandleShift; + LLUIColor mDragHandleColor; // Save sizes of the layout stack panels const bool mSaveSizes; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 13cdd3eda9..e2ce3c2cb1 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -594,13 +594,13 @@ void LLMenuItemGL::onVisibilityChange(BOOL new_visibility) // // This class represents a separator. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -LLMenuItemSeparatorGL::Params::Params() -{ -} - LLMenuItemSeparatorGL::LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p) : LLMenuItemGL( p ) { + if (p.on_visible.isProvided()) + { + mVisibleSignal.connect(initEnableCallback(p.on_visible)); + } } //virtual @@ -617,6 +617,15 @@ void LLMenuItemSeparatorGL::draw( void ) gl_line_2d( PAD, y, getRect().getWidth() - PAD, y ); } +void LLMenuItemSeparatorGL::buildDrawLabel( void ) +{ + if (mVisibleSignal.num_slots() > 0) + { + bool visible = mVisibleSignal(this, LLSD()); + setVisible(visible); + } +} + BOOL LLMenuItemSeparatorGL::handleMouseDown(S32 x, S32 y, MASK mask) { LLMenuGL* parent_menu = getMenu(); diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 9caf356183..dc4ec80094 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -235,7 +235,9 @@ class LLMenuItemSeparatorGL : public LLMenuItemGL public: struct Params : public LLInitParam::Block { - Params(); + Optional on_visible; + Params() : on_visible("on_visible") + {} }; LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p = LLMenuItemSeparatorGL::Params()); @@ -244,7 +246,12 @@ public: /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); + virtual void buildDrawLabel(); + /*virtual*/ U32 getNominalHeight( void ) const; + +private: + enable_signal_t mVisibleSignal; }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp index fde6de4921..735e2d529e 100644 --- a/indra/llui/llscrollbar.cpp +++ b/indra/llui/llscrollbar.cpp @@ -188,12 +188,12 @@ void LLScrollbar::setPageSize( S32 page_size ) } } -BOOL LLScrollbar::isAtBeginning() +bool LLScrollbar::isAtBeginning() const { return mDocPos == 0; } -BOOL LLScrollbar::isAtEnd() +bool LLScrollbar::isAtEnd() const { return mDocPos == getDocPosMax(); } @@ -591,7 +591,12 @@ void LLScrollbar::setValue(const LLSD& value) BOOL LLScrollbar::handleKeyHere(KEY key, MASK mask) { - BOOL handled = FALSE; + if (getDocPosMax() == 0 && !getVisible()) + { + return FALSE; + } + + BOOL handled = FALSE; switch( key ) { diff --git a/indra/llui/llscrollbar.h b/indra/llui/llscrollbar.h index 5f2f490d81..9be9d22db8 100644 --- a/indra/llui/llscrollbar.h +++ b/indra/llui/llscrollbar.h @@ -105,8 +105,8 @@ public: bool setDocPos( S32 pos, BOOL update_thumb = TRUE ); S32 getDocPos() const { return mDocPos; } - BOOL isAtBeginning(); - BOOL isAtEnd(); + bool isAtBeginning() const; + bool isAtEnd() const; // Setting both at once. void setDocParams( S32 size, S32 pos ); diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index 3db38bbfac..ad32f7186c 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -105,8 +105,8 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p) mBorder = LLUICtrlFactory::create (params); LLView::addChild( mBorder ); - mInnerRect.set( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - mInnerRect.stretch( -getBorderWidth() ); + mInnerRect = getLocalRect(); + mInnerRect.stretch( -getBorderWidth() ); LLRect vertical_scroll_rect = mInnerRect; vertical_scroll_rect.mLeft = vertical_scroll_rect.mRight - scrollbar_size; @@ -124,8 +124,9 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p) mScrollbar[VERTICAL] = LLUICtrlFactory::create (sbparams); LLView::addChild( mScrollbar[VERTICAL] ); - LLRect horizontal_scroll_rect = mInnerRect; - horizontal_scroll_rect.mTop = horizontal_scroll_rect.mBottom + scrollbar_size; + LLRect horizontal_scroll_rect; + horizontal_scroll_rect.mTop = scrollbar_size; + horizontal_scroll_rect.mRight = mInnerRect.getWidth(); sbparams.name("scrollable horizontal"); sbparams.rect(horizontal_scroll_rect); sbparams.orientation(LLScrollbar::HORIZONTAL); @@ -134,7 +135,7 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p) sbparams.page_size(mInnerRect.getWidth()); sbparams.step_size(VERTICAL_MULTIPLE); sbparams.visible(false); - sbparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT); + sbparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_BOTTOM); sbparams.change_callback(p.scroll_callback); mScrollbar[HORIZONTAL] = LLUICtrlFactory::create (sbparams); LLView::addChild( mScrollbar[HORIZONTAL] ); diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h index f19d02120f..613bb2ca2f 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -99,8 +99,10 @@ public: void pageDown(S32 overlap = 0); void goToTop(); void goToBottom(); - bool isAtTop() { return mScrollbar[VERTICAL]->isAtBeginning(); } - bool isAtBottom() { return mScrollbar[VERTICAL]->isAtEnd(); } + bool isAtTop() const { return mScrollbar[VERTICAL]->isAtBeginning(); } + bool isAtBottom() const { return mScrollbar[VERTICAL]->isAtEnd(); } + S32 getDocPosVertical() const { return mScrollbar[VERTICAL]->getDocPos(); } + S32 getDocPosHorizontal() const { return mScrollbar[HORIZONTAL]->getDocPos(); } S32 getBorderWidth() const; // Scrollbar accessor LLScrollbar* getScrollbar(SCROLL_ORIENTATION orientation) { return mScrollbar[orientation]; } diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index ea965c83ee..b01e730f92 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -655,6 +655,7 @@ BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) pActiveTabBtn->setFocus(TRUE); } // [/SL:KB] + mMouseDownTimer.start(); } } if (handled) { @@ -703,7 +704,11 @@ BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask ) handled = LLPanel::handleHover(x, y, mask); } - commitHoveredButton(x, y); + F32 drag_delay = 0.25f; // filter out clicks from dragging + if (mMouseDownTimer.getElapsedTimeF32() > drag_delay) + { + commitHoveredButton(x, y); + } return handled; } @@ -749,6 +754,7 @@ BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) } commitHoveredButton(x, y); + mMouseDownTimer.stop(); LLPanel* cur_panel = getCurrentPanel(); if (hasMouseCapture()) { @@ -1132,7 +1138,7 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) } else { - //Scip tab button space if they are invisible(EXT - 576) + // Skip tab button space if tabs are invisible (EXT-576) tab_panel_top = getRect().getHeight(); tab_panel_bottom = LLPANEL_BORDER_WIDTH; } @@ -1147,9 +1153,9 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) } else { - tab_panel_rect = LLRect(LLPANEL_BORDER_WIDTH, + tab_panel_rect = LLRect(LLPANEL_BORDER_WIDTH * 3, tab_panel_top, - getRect().getWidth()-LLPANEL_BORDER_WIDTH, + getRect().getWidth() - LLPANEL_BORDER_WIDTH * 2, tab_panel_bottom ); } child->setFollowsAll(); @@ -1241,7 +1247,7 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) p.follows.flags = p.follows.flags() | FOLLOWS_TOP; } else - { + { p.name("htab_"+std::string(child->getName())); p.visible(false); p.image_unselected(tab_img); diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h index c52d915457..2907f994e3 100644 --- a/indra/llui/lltabcontainer.h +++ b/indra/llui/lltabcontainer.h @@ -353,6 +353,7 @@ private: LLUIColor mTabsFlashingColor; S32 mTabIconCtrlPad; bool mUseTabEllipses; + LLFrameTimer mMouseDownTimer; }; #endif // LL_TABCONTAINER_H diff --git a/indra/llui/lltooltip.cpp b/indra/llui/lltooltip.cpp index 92f7251220..b286912f17 100644 --- a/indra/llui/lltooltip.cpp +++ b/indra/llui/lltooltip.cpp @@ -163,6 +163,7 @@ LLToolTip::LLToolTip(const LLToolTip::Params& p) : LLPanel(p), mHasClickCallback(p.click_callback.isProvided()), mPadding(p.padding), + mMaxWidth(p.max_width), mTextBox(NULL), mInfoButton(NULL), mPlayMediaButton(NULL), @@ -272,7 +273,7 @@ void LLToolTip::initFromParams(const LLToolTip::Params& p) // do this *after* we've had our size set in LLPanel::initFromParams(); const S32 REALLY_LARGE_HEIGHT = 10000; - mTextBox->reshape(p.max_width, REALLY_LARGE_HEIGHT); + mTextBox->reshape(mMaxWidth, REALLY_LARGE_HEIGHT); if (p.styled_message.isProvided()) { @@ -288,16 +289,19 @@ void LLToolTip::initFromParams(const LLToolTip::Params& p) mTextBox->setText(p.message()); } - S32 text_width = llmin(p.max_width(), mTextBox->getTextPixelWidth() + 1); + updateTextBox(); + snapToChildren(); +} + +void LLToolTip::updateTextBox() +{ + S32 text_width = llmin(mMaxWidth, mTextBox->getTextPixelWidth() + 1); S32 text_height = mTextBox->getTextPixelHeight(); mTextBox->reshape(text_width, text_height); - if (mInfoButton) - { - LLRect text_rect = mTextBox->getRect(); - LLRect icon_rect = mInfoButton->getRect(); - mTextBox->translate(0, icon_rect.getCenterY() - text_rect.getCenterY()); - } - +} + +void LLToolTip::snapToChildren() +{ // reshape tooltip panel to fit text box LLRect tooltip_rect = calcBoundingRect(); tooltip_rect.mTop += mPadding; @@ -305,7 +309,14 @@ void LLToolTip::initFromParams(const LLToolTip::Params& p) tooltip_rect.mBottom = 0; tooltip_rect.mLeft = 0; - mTextBox->reshape(mTextBox->getRect().getWidth(), llmax(mTextBox->getRect().getHeight(), tooltip_rect.getHeight() - 2 * mPadding)); + if (mInfoButton) + { + mTextBox->reshape(mTextBox->getRect().getWidth(), llmax(mTextBox->getRect().getHeight(), tooltip_rect.getHeight() - 2 * mPadding)); + + LLRect text_rect = mTextBox->getRect(); + LLRect icon_rect = mInfoButton->getRect(); + mInfoButton->translate(0, text_rect.getCenterY() - icon_rect.getCenterY()); + } setShape(tooltip_rect); } @@ -428,7 +439,10 @@ void LLToolTipMgr::createToolTip(const LLToolTip::Params& params) } tooltip_params.rect = LLRect (0, 1, 1, 0); - mToolTip = LLUICtrlFactory::create (tooltip_params); + if (tooltip_params.create_callback.isProvided()) + mToolTip = tooltip_params.create_callback()(tooltip_params); + else + mToolTip = LLUICtrlFactory::create (tooltip_params); gToolTipView->addChild(mToolTip); diff --git a/indra/llui/lltooltip.h b/indra/llui/lltooltip.h index 0b1fbe5367..86943625ff 100644 --- a/indra/llui/lltooltip.h +++ b/indra/llui/lltooltip.h @@ -68,6 +68,7 @@ public: struct Params : public LLInitParam::Block { typedef boost::function click_callback_t; + typedef boost::function create_callback_t; Optional message; Multiple styled_message; @@ -84,6 +85,8 @@ public: Optional time_based_media, web_based_media, media_playing; + Optional create_callback; + Optional create_params; Optional click_callback, click_playmedia_callback, click_homepage_callback; @@ -103,11 +106,15 @@ public: bool hasClickCallback(); LLToolTip(const Params& p); - void initFromParams(const LLToolTip::Params& params); + virtual void initFromParams(const LLToolTip::Params& params); void getToolTipMessage(std::string & message); -private: +protected: + void updateTextBox(); + void snapToChildren(); + +protected: class LLTextBox* mTextBox; class LLButton* mInfoButton; class LLButton* mPlayMediaButton; @@ -117,6 +124,7 @@ private: LLFrameTimer mVisibleTimer; bool mHasClickCallback; S32 mPadding; // pixels + S32 mMaxWidth; }; // used for the inspector tooltips which need different background images etc. diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 8c12a33f12..5d012dad83 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -539,6 +539,15 @@ void LLUICtrl::setControlVariable(LLControlVariable* control) } } +void LLUICtrl::removeControlVariable() +{ + if (mControlVariable) + { + mControlConnection.disconnect(); + mControlVariable = NULL; + } +} + //virtual void LLUICtrl::setControlName(const std::string& control_name, LLView *context) { diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index 2706bb4705..52cab89c46 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -184,6 +184,7 @@ public: bool setControlValue(const LLSD& value); void setControlVariable(LLControlVariable* control); virtual void setControlName(const std::string& control, LLView *context = NULL); + void removeControlVariable(); LLControlVariable* getControlVariable() { return mControlVariable; } // Accessors for other ControlVariables diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index a5d6c2345a..fe6533ec88 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -361,7 +361,13 @@ bool LLView::addChild(LLView* child, S32 tab_group) } child->mParentView = this; - updateBoundingRect(); + if (getVisible() && child->getVisible()) + { + // if child isn't visible it won't affect bounding rect + // if current view is not visible it will be recalculated + // on visibility change + updateBoundingRect(); + } mLastTabGroup = tab_group; return true; } @@ -631,6 +637,7 @@ void LLView::deleteAllChildren() delete viewp; mChildList.pop_front(); } + updateBoundingRect(); } void LLView::setAllChildrenEnabled(BOOL b) @@ -929,6 +936,17 @@ LLView* LLView::childFromPoint(S32 x, S32 y, bool recur) return 0; } +F32 LLView::getTooltipTimeout() +{ + static LLCachedControl tooltip_fast_delay(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipFastDelay", 0.1f); + static LLCachedControl tooltip_delay(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipDelay", 0.7f); + // allow "scrubbing" over ui by showing next tooltip immediately + // if previous one was still visible + return (F32)(LLToolTipMgr::instance().toolTipVisible() + ? tooltip_fast_delay + : tooltip_delay); +} + BOOL LLView::handleToolTip(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; @@ -944,14 +962,7 @@ BOOL LLView::handleToolTip(S32 x, S32 y, MASK mask) std::string tooltip = getToolTip(); if (!tooltip.empty()) { - static LLCachedControl tooltip_fast_delay(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipFastDelay", 0.1f); - static LLCachedControl tooltip_delay(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipDelay", 0.7f); static LLCachedControl allow_ui_tooltips(*LLUI::getInstance()->mSettingGroups["config"], "BasicUITooltips", true); - // allow "scrubbing" over ui by showing next tooltip immediately - // if previous one was still visible - F32 timeout = LLToolTipMgr::instance().toolTipVisible() - ? tooltip_fast_delay - : tooltip_delay; // Even if we don't show tooltips, consume the event, nothing below should show tooltip if (allow_ui_tooltips) @@ -959,7 +970,7 @@ BOOL LLView::handleToolTip(S32 x, S32 y, MASK mask) LLToolTipMgr::instance().show(LLToolTip::Params() .message(tooltip) .sticky_rect(calcScreenRect()) - .delay_time(timeout)); + .delay_time(getTooltipTimeout())); } handled = TRUE; } diff --git a/indra/llui/llview.h b/indra/llui/llview.h index d23af4a789..fea241c92d 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -243,12 +243,11 @@ public: ECursorType getHoverCursor() { return mHoverCursor; } + static F32 getTooltipTimeout(); // Made this non inline when changing mToolTipMsg from a LLUIString to a char* to reduce memory usage, // (Making a virtual function inline is debatable anyway). - // virtual const std::string getToolTip() const { return mToolTipMsg.getString(); } virtual const std::string getToolTip() const; - // void sendChildToFront(LLView* child); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 265eedee5b..a4cacf136f 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -303,6 +303,7 @@ set(viewer_SOURCE_FILES llfloaterbuyland.cpp llfloatercamera.cpp llfloatercamerapresets.cpp + llfloaterchangeitemthumbnail.cpp llfloaterchatvoicevolume.cpp llfloaterclassified.cpp llfloatercolorpicker.cpp @@ -339,6 +340,7 @@ set(viewer_SOURCE_FILES llfloaterimsession.cpp llfloaterimcontainer.cpp llfloaterinspect.cpp + llfloaterinventorysettings.cpp llfloaterjoystick.cpp llfloaterlagmeter.cpp llfloaterland.cpp @@ -354,12 +356,12 @@ set(viewer_SOURCE_FILES llfloatermyscripts.cpp llfloatermyenvironment.cpp llfloaternamedesc.cpp + llfloaternewfeaturenotification.cpp llfloaternotificationsconsole.cpp llfloaternotificationstabbed.cpp - llfloateroutfitphotopreview.cpp llfloaterobjectweights.cpp llfloateropenobject.cpp - llfloatersimpleoutfitsnapshot.cpp + llfloatersimplesnapshot.cpp llfloaterpathfindingcharacters.cpp llfloaterpathfindingconsole.cpp llfloaterpathfindinglinksets.cpp @@ -375,7 +377,7 @@ set(viewer_SOURCE_FILES llfloaterpreferenceviewadvanced.cpp llfloaterpreviewtrash.cpp llfloaterprofiletexture.cpp - llfloaterproperties.cpp + llfloaterproperties.cpp # Keep legacy properties floater llfloaterregiondebugconsole.cpp llfloaterregioninfo.cpp llfloaterreporter.cpp @@ -449,10 +451,13 @@ set(viewer_SOURCE_FILES llinspectgroup.cpp llinspectobject.cpp llinspectremoteobject.cpp + llinspecttexture.cpp llinspecttoast.cpp llinventorybridge.cpp llinventoryfilter.cpp llinventoryfunctions.cpp + llinventorygallery.cpp + llinventorygallerymenu.cpp llinventoryicon.cpp llinventoryitemslist.cpp llinventorylistitem.cpp @@ -678,6 +683,7 @@ set(viewer_SOURCE_FILES lltextureinfodetails.cpp lltexturestats.cpp lltextureview.cpp + llthumbnailctrl.cpp lltoast.cpp lltoastalertpanel.cpp lltoastgroupnotifypanel.cpp @@ -1083,6 +1089,7 @@ set(viewer_HEADER_FILES llfloaterbuycurrencyhtml.h llfloaterbuyland.h llfloatercamerapresets.h + llfloaterchangeitemthumbnail.h llfloatercamera.h llfloaterchatvoicevolume.h llfloaterclassified.h @@ -1123,6 +1130,7 @@ set(viewer_HEADER_FILES llfloaterimsession.h llfloaterimcontainer.h llfloaterinspect.h + llfloaterinventorysettings.h llfloaterjoystick.h llfloaterlagmeter.h llfloaterland.h @@ -1138,12 +1146,12 @@ set(viewer_HEADER_FILES llfloatermyscripts.h llfloatermyenvironment.h llfloaternamedesc.h + llfloaternewfeaturenotification.h llfloaternotificationsconsole.h llfloaternotificationstabbed.h - llfloateroutfitphotopreview.h llfloaterobjectweights.h llfloateropenobject.h - llfloatersimpleoutfitsnapshot.h + llfloatersimplesnapshot.h llfloaterpathfindingcharacters.h llfloaterpathfindingconsole.h llfloaterpathfindinglinksets.h @@ -1159,7 +1167,7 @@ set(viewer_HEADER_FILES llfloaterpreferenceviewadvanced.h llfloaterpreviewtrash.h llfloaterprofiletexture.h - llfloaterproperties.h + llfloaterproperties.h # Keep legacy properties floater llfloaterregiondebugconsole.h llfloaterregioninfo.h llfloaterreporter.h @@ -1231,10 +1239,13 @@ set(viewer_HEADER_FILES llinspectgroup.h llinspectobject.h llinspectremoteobject.h + llinspecttexture.h llinspecttoast.h llinventorybridge.h llinventoryfilter.h llinventoryfunctions.h + llinventorygallery.h + llinventorygallerymenu.h llinventoryicon.h llinventoryitemslist.h llinventorylistitem.h @@ -1451,6 +1462,7 @@ set(viewer_HEADER_FILES lltextureinfodetails.h lltexturestats.h lltextureview.h + llthumbnailctrl.h lltoast.h lltoastalertpanel.h lltoastgroupnotifypanel.h diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index b69c12176f..5235a3e6f4 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -6.6.15 +6.6.16 diff --git a/indra/newview/ao.cpp b/indra/newview/ao.cpp index f7a8f3f7e3..4f5a32fece 100644 --- a/indra/newview/ao.cpp +++ b/indra/newview/ao.cpp @@ -534,11 +534,10 @@ bool FloaterAO::newSetCallback(const LLSD& notification, const LLSD& response) if (option == 0) { - if (AOEngine::instance().addSet(newSetName).notNull()) + return AOEngine::instance().addSet(newSetName, [this](const LLUUID& new_cat_id) { reloading(true); - return true; - } + }); } return false; } diff --git a/indra/newview/aoengine.cpp b/indra/newview/aoengine.cpp index e649c2a879..a726483d6a 100644 --- a/indra/newview/aoengine.cpp +++ b/indra/newview/aoengine.cpp @@ -40,7 +40,6 @@ #include "llnotificationsutil.h" #include "llstring.h" #include "llviewercontrol.h" -#include "llviewerinventory.h" #define ROOT_AO_FOLDER "#AO" #include @@ -978,26 +977,30 @@ void AOEngine::updateSortOrder(AOSet::AOState* state) } } -LLUUID AOEngine::addSet(const std::string& name, bool reload) +bool AOEngine::addSet(const std::string& name, inventory_func_type callback, bool reload) { if (mAOFolder.isNull()) { LL_WARNS("AOEngine") << ROOT_AO_FOLDER << " folder not there yet. Requesting recreation." << LL_ENDL; tick(); - return LLUUID::null; + return false; } BOOL wasProtected = gSavedPerAccountSettings.getBOOL("LockAOFolders"); gSavedPerAccountSettings.setBOOL("LockAOFolders", FALSE); LL_DEBUGS("AOEngine") << "adding set folder " << name << LL_ENDL; - LLUUID newUUID = gInventory.createNewCategory(mAOFolder, LLFolderType::FT_NONE, name); - gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); - - if (reload) + gInventory.createNewCategory(mAOFolder, LLFolderType::FT_NONE, name, [callback, wasProtected, reload, this](const LLUUID &new_cat_id) { - mTimerCollection.enableReloadTimer(true); - } - return newUUID; + gSavedPerAccountSettings.setBOOL("LockAOFolders", wasProtected); + + if (reload) + { + mTimerCollection.enableReloadTimer(true); + } + + callback(new_cat_id); + }); + return true; } bool AOEngine::createAnimationLink(const AOSet* set, AOSet::AOState* state, const LLInventoryItem* item) @@ -2177,8 +2180,51 @@ void AOEngine::processImport(bool from_timer) { if (mImportCategory.isNull()) { - mImportCategory = addSet(mImportSet->getName(), false); - if (mImportCategory.isNull()) + bool success = addSet(mImportSet->getName(), [this, from_timer](const LLUUID& new_cat_id) + { + mImportCategory = new_cat_id; + mImportSet->setInventoryUUID(mImportCategory); + + bool allComplete = true; + for (S32 index = 0; index < AOSet::AOSTATES_MAX; ++index) + { + AOSet::AOState* state = mImportSet->getState(index); + if (state->mAnimations.size()) + { + allComplete = false; + LL_DEBUGS("AOEngine") << "state " << state->mName << " still has animations to link." << LL_ENDL; + + for (S32 animationIndex = state->mAnimations.size() - 1; animationIndex >= 0; --animationIndex) + { + LL_DEBUGS("AOEngine") << "linking animation " << state->mAnimations[animationIndex].mName << LL_ENDL; + if (createAnimationLink(mImportSet, state, gInventory.getItem(state->mAnimations[animationIndex].mInventoryUUID))) + { + LL_DEBUGS("AOEngine") << "link success, size " << state->mAnimations.size() << ", removing animation " + << (*(state->mAnimations.begin() + animationIndex)).mName << " from import state" << LL_ENDL; + state->mAnimations.erase(state->mAnimations.begin() + animationIndex); + LL_DEBUGS("AOEngine") << "deleted, size now: " << state->mAnimations.size() << LL_ENDL; + } + else + { + LLSD args; + args["NAME"] = state->mAnimations[animationIndex].mName; + LLNotificationsUtil::add("AOImportLinkFailed", args); + } + } + } + } + + if (allComplete) + { + mTimerCollection.enableImportTimer(false); + mOldImportSets.push_back(mImportSet); // FIRE-3801; Cannot delete here, or LLInstanceTracker gets upset. Just remember and delete mOldImportSets once we can. + mImportSet = nullptr; + mImportCategory.setNull(); + reload(from_timer); + } + }, false); + + if (!success) { mImportRetryCount++; if (mImportRetryCount == 5) @@ -2199,47 +2245,7 @@ void AOEngine::processImport(bool from_timer) args["NAME"] = mImportSet->getName(); LLNotificationsUtil::add("AOImportRetryCreateSet", args); } - return; } - mImportSet->setInventoryUUID(mImportCategory); - } - - bool allComplete = true; - for (S32 index = 0; index < AOSet::AOSTATES_MAX; ++index) - { - AOSet::AOState* state = mImportSet->getState(index); - if (state->mAnimations.size()) - { - allComplete = false; - LL_DEBUGS("AOEngine") << "state " << state->mName << " still has animations to link." << LL_ENDL; - - for (S32 animationIndex = state->mAnimations.size() - 1; animationIndex >= 0; --animationIndex) - { - LL_DEBUGS("AOEngine") << "linking animation " << state->mAnimations[animationIndex].mName << LL_ENDL; - if (createAnimationLink(mImportSet, state, gInventory.getItem(state->mAnimations[animationIndex].mInventoryUUID))) - { - LL_DEBUGS("AOEngine") << "link success, size "<< state->mAnimations.size() << ", removing animation " - << (*(state->mAnimations.begin() + animationIndex)).mName << " from import state" << LL_ENDL; - state->mAnimations.erase(state->mAnimations.begin() + animationIndex); - LL_DEBUGS("AOEngine") << "deleted, size now: " << state->mAnimations.size() << LL_ENDL; - } - else - { - LLSD args; - args["NAME"] = state->mAnimations[animationIndex].mName; - LLNotificationsUtil::add("AOImportLinkFailed", args); - } - } - } - } - - if (allComplete) - { - mTimerCollection.enableImportTimer(false); - mOldImportSets.push_back(mImportSet); // FIRE-3801; Cannot delete here, or LLInstanceTracker gets upset. Just remember and delete mOldImportSets once we can. - mImportSet = nullptr; - mImportCategory.setNull(); - reload(from_timer); } } diff --git a/indra/newview/aoengine.h b/indra/newview/aoengine.h index a9b5c46686..062786b689 100644 --- a/indra/newview/aoengine.h +++ b/indra/newview/aoengine.h @@ -31,6 +31,7 @@ #include "lleventtimer.h" #include "llextendedstatus.h" #include "llsingleton.h" +#include "llviewerinventory.h" #include class AOTimerCollection @@ -105,7 +106,7 @@ class AOEngine const LLUUID& getAOFolder() const; - LLUUID addSet(const std::string& name, bool reload = true); + bool addSet(const std::string& name, inventory_func_type callback, bool reload = true); bool removeSet(AOSet* set); bool addAnimation(const AOSet* set, AOSet::AOState* state, const LLInventoryItem* item, bool reload = true); diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 552af24806..0898dc69a5 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -6250,17 +6250,6 @@ Boolean Value 0 - - InventoryInboxToggleState - - Comment - Stores the open/closed state of inventory Received items panel - Persist - 1 - Type - Boolean - Value - 0 InventoryLinking @@ -6826,6 +6815,17 @@ Backup 0 + LastUIFeatureVersion + + Comment + UI Feature Version number for tracking feature notification between viewer builds + Persist + 1 + Type + LLSD + Value + + LastName Comment @@ -17690,6 +17690,17 @@ Change of this parameter will affect the layout of buttons in notification toast Backup 0 + BatchSizeAIS3 + + Comment + Amount of folder ais packs into category subset request + Persist + 1 + Type + S32 + Value + 20 + PoolSizeAIS Comment @@ -17697,7 +17708,7 @@ Change of this parameter will affect the layout of buttons in notification toast Type U32 Value - 1 + 20 PoolSizeUpload @@ -22131,6 +22142,17 @@ Change of this parameter will affect the layout of buttons in notification toast Value 0 + FindOriginalOpenWindow + + Comment + Sets the action for 'Find original' and 'Show in Inventory' (0 - shows item in main Inventory, 1 - opens a new single-folder window) + Persist + 1 + Type + Boolean + Value + 0 + StatsReportMaxDuration Comment @@ -22164,6 +22186,29 @@ Change of this parameter will affect the layout of buttons in notification toast Value 0 +MultiModeDoubleClickFolder + + Comment + Sets the action for Double-click on folder in multi-folder view (0 - expands and collapses folder, 1 - opens a new window, 2 – stays in current floater but switches to SFV) + Persist + 1 + Type + U32 + Value + 0 + +SingleModeDoubleClickOpenWindow + + Comment + Sets the action for Double-click on folder in single-folder view (0 - stays in current window, 1 - opens a new window) + Persist + 1 + Type + Boolean + Value + 0 + + FSNetMapPhantomOpacity Comment @@ -24315,7 +24360,7 @@ Change of this parameter will affect the layout of buttons in notification toast Type Boolean Value - 1 + 0 FSBubblesHideConsoleAndToasts diff --git a/indra/newview/app_settings/settings_v3.xml b/indra/newview/app_settings/settings_v3.xml index 6b42c8f47a..fd666af25a 100644 --- a/indra/newview/app_settings/settings_v3.xml +++ b/indra/newview/app_settings/settings_v3.xml @@ -326,18 +326,5 @@ Value 1 - - FSUseLegacyObjectProperties - - Comment - If enabled, the legacy object profile floater will be used when opening object properties. - Persist - 1 - Type - Boolean - Value - 0 - - diff --git a/indra/newview/fsfloaterwearablefavorites.cpp b/indra/newview/fsfloaterwearablefavorites.cpp index 4fa8dd5927..cd632b70d2 100644 --- a/indra/newview/fsfloaterwearablefavorites.cpp +++ b/indra/newview/fsfloaterwearablefavorites.cpp @@ -151,35 +151,42 @@ void FSFloaterWearableFavorites::onOpen(const LLSD& /*info*/) { if (sFolderID.isNull()) { - initCategory(); + initCategory(boost::bind(&FSFloaterWearableFavorites::initialize, this)); } - - LLViewerInventoryCategory* category = gInventory.getCategory(sFolderID); - if (!category) + else { - return; + initialize(); } - - const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - LLViewerInventoryCategory* category_cof = gInventory.getCategory(cof); - if (!category_cof) - { - return; - } - - gInventory.addObserver(mCategoriesObserver); - mCategoriesObserver->addCategory(sFolderID, boost::bind(&FSFloaterWearableFavorites::updateList, this, sFolderID)); - mCategoriesObserver->addCategory(cof, boost::bind(&FSFloaterWearableFavorites::updateList, this, sFolderID)); - category->fetch(); - - mItemsList->setSortOrder((LLWearableItemsList::ESortOrder)gSavedSettings.getU32("FSWearableFavoritesSortOrder")); - updateList(sFolderID); - mItemsList->setDADCallback(boost::bind(&FSFloaterWearableFavorites::onItemDAD, this, _1)); - - mInitialized = true; } } +void FSFloaterWearableFavorites::initialize() +{ + LLViewerInventoryCategory* category = gInventory.getCategory(sFolderID); + if (!category) + { + return; + } + + const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + LLViewerInventoryCategory* category_cof = gInventory.getCategory(cof); + if (!category_cof) + { + return; + } + + gInventory.addObserver(mCategoriesObserver); + mCategoriesObserver->addCategory(sFolderID, boost::bind(&FSFloaterWearableFavorites::updateList, this, sFolderID)); + mCategoriesObserver->addCategory(cof, boost::bind(&FSFloaterWearableFavorites::updateList, this, sFolderID)); + category->fetch(); + + mItemsList->setSortOrder((LLWearableItemsList::ESortOrder)gSavedSettings.getU32("FSWearableFavoritesSortOrder")); + updateList(sFolderID); + mItemsList->setDADCallback(boost::bind(&FSFloaterWearableFavorites::onItemDAD, this, _1)); + + mInitialized = true; +} + //virtual void FSFloaterWearableFavorites::draw() { @@ -224,7 +231,7 @@ std::optional FSFloaterWearableFavorites::getWearableFavoritesFolderID() } // static -void FSFloaterWearableFavorites::initCategory() +void FSFloaterWearableFavorites::initCategory(inventory_func_type callback) { if (!gInventory.isInventoryUsable()) { @@ -235,16 +242,30 @@ void FSFloaterWearableFavorites::initCategory() if (auto fs_favs_id = getWearableFavoritesFolderID(); fs_favs_id.has_value()) { sFolderID = fs_favs_id.value(); + callback(sFolderID); } else { LLUUID fs_root_cat_id = gInventory.findCategoryByName(ROOT_FIRESTORM_FOLDER); if (fs_root_cat_id.isNull()) { - fs_root_cat_id = gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, ROOT_FIRESTORM_FOLDER); + gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, ROOT_FIRESTORM_FOLDER, [callback](const LLUUID& new_cat_id) + { + gInventory.createNewCategory(new_cat_id, LLFolderType::FT_NONE, FS_WEARABLE_FAVORITES_FOLDER, [callback](const LLUUID& new_cat_id) + { + FSFloaterWearableFavorites::sFolderID = new_cat_id; + callback(new_cat_id); + }); + }); + } + else + { + gInventory.createNewCategory(fs_root_cat_id, LLFolderType::FT_NONE, FS_WEARABLE_FAVORITES_FOLDER, [callback](const LLUUID& new_cat_id) + { + FSFloaterWearableFavorites::sFolderID = new_cat_id; + callback(new_cat_id); + }); } - - sFolderID = gInventory.createNewCategory(fs_root_cat_id, LLFolderType::FT_NONE, FS_WEARABLE_FAVORITES_FOLDER); } } diff --git a/indra/newview/fsfloaterwearablefavorites.h b/indra/newview/fsfloaterwearablefavorites.h index 8f09524aa4..e0a5e8f733 100644 --- a/indra/newview/fsfloaterwearablefavorites.h +++ b/indra/newview/fsfloaterwearablefavorites.h @@ -29,6 +29,7 @@ #define FS_FLOATERWEARABLEFAVORITES_H #include "llfloater.h" +#include "llviewerinventory.h" #include "llwearableitemslist.h" #include @@ -79,10 +80,14 @@ public: /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask); /*virtual*/ bool hasAccelerators() const { return true; } - static void initCategory(); + static void initCategory(inventory_func_type callback = no_op_inventory_func); static LLUUID getFavoritesFolder(); + static LLUUID sFolderID; + private: + void initialize(); + void updateList(const LLUUID& folder_id); void onItemDAD(const LLUUID& item_id); @@ -106,8 +111,6 @@ private: LLFilterEditor* mFilterEditor; LLMenuButton* mOptionsButton; LLHandle mOptionsMenuHandle; - - static LLUUID sFolderID; }; #endif // FS_FLOATERWEARABLEFAVORITES_H diff --git a/indra/newview/fslslbridge.cpp b/indra/newview/fslslbridge.cpp index 7e169ace50..e57a92469d 100644 --- a/indra/newview/fslslbridge.cpp +++ b/indra/newview/fslslbridge.cpp @@ -89,6 +89,7 @@ private: FSLSLBridge::FSLSLBridge(): mBridgeCreating(false), mpBridge(nullptr), + mBridgeFolderID(LLUUID::null), mIsFirstCallDone(false), mAllowDetach(false), mFinishCreation(false), @@ -176,7 +177,7 @@ bool FSLSLBridge::lslToViewer(std::string_view message, const LLUUID& fromID, co return false; } std::string_view tag = message.substr(0, tagend + 1); - std::string ourBridge = findFSCategory().asString(); + std::string ourBridge = getBridgeFolder().asString(); // FIRE-962 bool bridgeIsEnabled = gSavedSettings.getBOOL("UseLSLBridge"); @@ -213,7 +214,7 @@ bool FSLSLBridge::lslToViewer(std::string_view message, const LLUUID& fromID, co // If something that looks like our current bridge is attached but failed auth, detach and recreate. - const LLUUID catID = findFSCategory(); + const LLUUID catID = getBridgeFolder(); LLViewerInventoryItem* fsBridge = findInvObject(mCurrentFullName, catID); if (fsBridge && get_is_item_worn(fsBridge->getUUID())) { @@ -251,7 +252,7 @@ bool FSLSLBridge::lslToViewer(std::string_view message, const LLUUID& fromID, co if (!mpBridge) { - LLUUID catID = findFSCategory(); + LLUUID catID = getBridgeFolder(); LLViewerInventoryItem* fsBridge = findInvObject(mCurrentFullName, catID); mpBridge = fsBridge; } @@ -642,7 +643,7 @@ void FSLSLBridge::recreateBridge() //announce yourself report_to_nearby_chat(LLTrans::getString("fsbridge_creating")); - LLUUID catID = findFSCategory(); + LLUUID catID = getBridgeFolder(); FSLSLBridgeInventoryPreCreationCleanupObserver* bridgeInventoryObserver = new FSLSLBridgeInventoryPreCreationCleanupObserver(catID); bridgeInventoryObserver->startFetch(); @@ -663,7 +664,7 @@ void FSLSLBridge::cleanUpPreCreation() LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; NameCollectFunctor namefunctor(mCurrentFullName); - gInventory.collectDescendentsIf(findFSCategory(), cats, items, FALSE, namefunctor); + gInventory.collectDescendentsIf(getBridgeFolder(), cats, items, FALSE, namefunctor); mAllowedDetachables.clear(); for (const auto& item : items) @@ -699,7 +700,7 @@ void FSLSLBridge::finishCleanUpPreCreation() LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; NameCollectFunctor namefunctor(mCurrentFullName); - gInventory.collectDescendentsIf(findFSCategory(), cats, items, FALSE, namefunctor); + gInventory.collectDescendentsIf(getBridgeFolder(), cats, items, FALSE, namefunctor); for (const auto& item : items) { @@ -743,26 +744,26 @@ void FSLSLBridge::initBridge() return; } - LLUUID catID = findFSCategory(); - LLUUID libCatID = findFSBridgeContainerCategory(); + setupFSCategory([this](const LLUUID& bridge_folder_id) + { + LLUUID libCatID = findFSBridgeContainerCategory(); - //check for inventory load - // AH: Use overloaded LLInventoryFetchDescendentsObserver to check for load of - // bridge and bridge rock category before doing anything! - LL_INFOS("FSLSLBridge") << "initBridge called. gInventory.isInventoryUsable = " << (gInventory.isInventoryUsable() ? "true" : "false") << LL_ENDL; - uuid_vec_t cats; - cats.push_back(catID); - cats.push_back(libCatID); - FSLSLBridgeInventoryObserver* bridgeInventoryObserver = new FSLSLBridgeInventoryObserver(cats); - bridgeInventoryObserver->startFetch(); - if (bridgeInventoryObserver->isFinished()) - { - bridgeInventoryObserver->done(); - } - else - { - gInventory.addObserver(bridgeInventoryObserver); - } + //check for inventory load + // AH: Use overloaded LLInventoryFetchDescendentsObserver to check for load of + // bridge and bridge rock category before doing anything! + LL_INFOS("FSLSLBridge") << "initBridge called. gInventory.isInventoryUsable = " << (gInventory.isInventoryUsable() ? "true" : "false") << LL_ENDL; + uuid_vec_t cats{ bridge_folder_id, libCatID }; + FSLSLBridgeInventoryObserver* bridgeInventoryObserver = new FSLSLBridgeInventoryObserver(cats); + bridgeInventoryObserver->startFetch(); + if (bridgeInventoryObserver->isFinished()) + { + bridgeInventoryObserver->done(); + } + else + { + gInventory.addObserver(bridgeInventoryObserver); + } + }); } @@ -786,7 +787,7 @@ void FSLSLBridge::startCreation() LL_INFOS("FSLSLBridge") << "startCreation called. gInventory.isInventoryUsable = " << (gInventory.isInventoryUsable() ? "true" : "false") << LL_ENDL; //if bridge object doesn't exist - create and attach it, update script. - const LLUUID catID = findFSCategory(); + const LLUUID catID = getBridgeFolder(); LLViewerInventoryItem* fsBridge = findInvObject(mCurrentFullName, catID); //detach everything else @@ -836,7 +837,7 @@ void FSLSLBridge::startCreation() void FSLSLBridge::createNewBridge() { //check if user has a bridge - const LLUUID catID = findFSCategory(); + const LLUUID catID = getBridgeFolder(); //attach the Linden rock from the library (will resize as soon as attached) const LLUUID libID = gInventory.getLibraryRootFolderID(); @@ -1193,7 +1194,7 @@ void FSLSLBridge::create_script_inner() return; } - const LLUUID catID = findFSCategory(); + const LLUUID catID = getBridgeFolder(); LLPointer cb = new FSLSLBridgeScriptCallback(); create_inventory_item(gAgentID, @@ -1378,7 +1379,7 @@ std::string FSLSLBridgeScriptCallback::prepUploadFile(std::string &aBuffer) LL_WARNS("FSLSLBridge") << "Invalid bridge script" << LL_ENDL; return std::string(); } - aBuffer.replace(pos, bridgekey.length(), FSLSLBridge::getInstance()->findFSCategory().asString()); + aBuffer.replace(pos, bridgekey.length(), FSLSLBridge::getInstance()->getBridgeFolder().asString()); LLFILE *fpOut = LLFile::fopen(fNew, "wt"); if (!fpOut) @@ -1483,18 +1484,9 @@ bool FSLSLBridge::isItemAttached(const LLUUID& iID) return (isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(iID)); } -LLUUID FSLSLBridge::findFSCategory() +void FSLSLBridge::setupFSCategory(inventory_func_type callback) { - if (!mBridgeFolderID.isNull()) - { - return mBridgeFolderID; - } - - LLUUID fsCatID; - LLUUID bridgeCatID; - - fsCatID = gInventory.findCategoryByName(ROOT_FIRESTORM_FOLDER); - if (!fsCatID.isNull()) + if (LLUUID fsCatID = gInventory.findCategoryByName(ROOT_FIRESTORM_FOLDER); !fsCatID.isNull()) { LLInventoryModel::item_array_t* items; LLInventoryModel::cat_array_t* cats; @@ -1505,25 +1497,64 @@ LLUUID FSLSLBridge::findFSCategory() { if (cat->getName() == FS_BRIDGE_FOLDER) { - bridgeCatID = cat->getUUID(); - break; + mBridgeFolderID = cat->getUUID(); + callback(mBridgeFolderID); + return; + } + } + } + + gInventory.createNewCategory(fsCatID, LLFolderType::FT_NONE, FS_BRIDGE_FOLDER, [this, callback](const LLUUID& new_cat_id) + { + mBridgeFolderID = new_cat_id; + callback(mBridgeFolderID); + }); + } + else + { + gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, ROOT_FIRESTORM_FOLDER, [this, callback](const LLUUID& new_cat_id) + { + gInventory.createNewCategory(new_cat_id, LLFolderType::FT_NONE, FS_BRIDGE_FOLDER, [this, callback](const LLUUID& new_cat_id) + { + mBridgeFolderID = new_cat_id; + callback(mBridgeFolderID); + }); + }); + } +} + +// This used to be the place where the bridge folder was also created before it got moved to setupFSCategory. +// We still need this method because it is used in processAttach / processDetach, which (unfortunately) might be called +// before initBridge is called that sets up the bridge folder. But since apparently a bridge is already attached, +// we can assume the folder already exists and we do not need to create it here anymore. And if something is attached +// to where we attach the bridge and also has the same name as the bridge but is in a different folder, it won't make +// any difference if we return the actual category ID or a null UUID for the check performed in processAttach. +LLUUID FSLSLBridge::findFSCategory() +{ + if (!mBridgeFolderID.isNull()) + { + return mBridgeFolderID; + } + + if (LLUUID fsCatID = gInventory.findCategoryByName(ROOT_FIRESTORM_FOLDER); !fsCatID.isNull()) + { + LLInventoryModel::item_array_t* items; + LLInventoryModel::cat_array_t* cats; + gInventory.getDirectDescendentsOf(fsCatID, cats, items); + if (cats) + { + for (const auto& cat : *cats) + { + if (cat->getName() == FS_BRIDGE_FOLDER) + { + mBridgeFolderID = cat->getUUID(); + return mBridgeFolderID; } } } } - else - { - fsCatID = gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, ROOT_FIRESTORM_FOLDER); - } - if (bridgeCatID.isNull()) - { - bridgeCatID = gInventory.createNewCategory(fsCatID, LLFolderType::FT_NONE, FS_BRIDGE_FOLDER); - } - - mBridgeFolderID = bridgeCatID; - - return mBridgeFolderID; + return LLUUID::null; } LLUUID FSLSLBridge::findFSBridgeContainerCategory() @@ -1613,7 +1644,7 @@ void FSLSLBridge::cleanUpBridgeFolder(const std::string& nameToCleanUp) return; } - LLUUID catID = findFSCategory(); + LLUUID catID = getBridgeFolder(); LLViewerInventoryCategory::cat_array_t cats; LLViewerInventoryItem::item_array_t items; @@ -1665,7 +1696,7 @@ void FSLSLBridge::cleanUpOldVersions() void FSLSLBridge::detachOtherBridges() { - LLUUID catID = findFSCategory(); + LLUUID catID = getBridgeFolder(); LLViewerInventoryCategory::cat_array_t cats; LLViewerInventoryItem::item_array_t items; diff --git a/indra/newview/fslslbridge.h b/indra/newview/fslslbridge.h index 31a0be7df9..630c98a4be 100644 --- a/indra/newview/fslslbridge.h +++ b/indra/newview/fslslbridge.h @@ -124,6 +124,7 @@ private: protected: LLViewerInventoryItem* findInvObject(const std::string& obj_name, const LLUUID& catID); + void setupFSCategory(inventory_func_type callback); LLUUID findFSCategory(); LLUUID findFSBridgeContainerCategory(); diff --git a/indra/newview/fspanellogin.cpp b/indra/newview/fspanellogin.cpp index 4972c0fe38..51fd9da412 100644 --- a/indra/newview/fspanellogin.cpp +++ b/indra/newview/fspanellogin.cpp @@ -1319,7 +1319,21 @@ void FSPanelLogin::onRemoveCallback(const LLSD& notification, const LLSD& respon { gSavedSettings.getControl("UserLoginInfo")->resetToDefault(); } + LLPointer credential = gSecAPIHandler->loadCredential(credName); + + if (size_t arobase = credName.find("@"); arobase != std::string::npos && arobase + 1 < credName.length() && arobase > 1) + { + auto gridname = credName.substr(arobase + 1, credName.length() - arobase - 1); + std::string grid_id = LLGridManager::getInstance()->getGridId(gridname); + if (grid_id.empty()) + { + grid_id = gridname; + } + gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid_id, credential->userID()); // doesn't write + gSecAPIHandler->syncProtectedMap(); + } + gSecAPIHandler->deleteCredential(credential); sInstance->addUsersToCombo(gSavedSettings.getBOOL("ForceShowGrid")); diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 331d684e6d..8b25c8fdb9 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -139,6 +139,11 @@ const F64 CHAT_AGE_FAST_RATE = 3.0; 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 +// Will need to change to 3 once either inventory or gltf releases and cause a conflict +const S32 UI_FEATURE_FLAGS = 1; + // The agent instance. LLAgent gAgent; @@ -408,7 +413,7 @@ LLAgent::LLAgent() : mHideGroupTitle(FALSE), mGroupID(), - mInitialized(FALSE), + mInitialized(false), mListener(), mDoubleTapRunTimer(), @@ -498,7 +503,7 @@ LLAgent::LLAgent() : mNextFidgetTime(0.f), mCurrentFidget(0), - mFirstLogin(FALSE), + mFirstLogin(false), mOutfitChosen(FALSE), mVoiceConnected(false), @@ -591,7 +596,7 @@ void LLAgent::init() mHttpPolicy = app_core_http.getPolicy(LLAppCoreHttp::AP_AGENT); - mInitialized = TRUE; + mInitialized = true; } //----------------------------------------------------------------------------- @@ -646,6 +651,93 @@ void LLAgent::onAppFocusGained() // } } +void LLAgent::setFirstLogin(bool b) +{ + mFirstLogin = b; + + if (mFirstLogin) + { + // Don't notify new users about new features + if (getFeatureVersion() <= UI_FEATURE_VERSION) + { + setFeatureVersion(UI_FEATURE_VERSION, UI_FEATURE_FLAGS); + } + } +} + +void LLAgent::setFeatureVersion(S32 version, S32 flags) +{ + LLSD updated_version; + updated_version["version"] = version; + updated_version["flags"] = flags; + gSavedSettings.setLLSD("LastUIFeatureVersion", updated_version); +} + +S32 LLAgent::getFeatureVersion() +{ + S32 version; + S32 flags; + getFeatureVersionAndFlags(version, flags); + return version; +} + +void LLAgent::getFeatureVersionAndFlags(S32& version, S32& flags) +{ + version = 0; + flags = 0; + LLSD feature_version = gSavedSettings.getLLSD("LastUIFeatureVersion"); + if (feature_version.isInteger()) + { + version = feature_version.asInteger(); + flags = 1; // inventory flag + } + else if (feature_version.isMap()) + { + version = feature_version["version"]; + flags = feature_version["flags"]; + } + else if (!feature_version.isString() && !feature_version.isUndefined()) + { + // is something newer inside? + version = UI_FEATURE_VERSION; + flags = UI_FEATURE_FLAGS; + } +} + +void LLAgent::showLatestFeatureNotification(const std::string key) +{ + S32 version; + S32 flags; // a single release can have multiple new features + getFeatureVersionAndFlags(version, flags); + if (version <= UI_FEATURE_VERSION && (flags & UI_FEATURE_FLAGS) != UI_FEATURE_FLAGS) + { + S32 flag = 0; + + if (key == "inventory") + { + // Notify user about new thumbnail support + flag = 1; + } + + if (key == "gltf") + { + flag = 2; + } + + if ((flags & flag) == 0) + { + // Need to open on top even if called from onOpen, + // do on idle to make sure it's on top + LLSD floater_key(key); + doOnIdleOneTime([floater_key]() + { + LLFloaterReg::showInstance("new_feature_notification", floater_key); + }); + + setFeatureVersion(UI_FEATURE_VERSION, flags | flag); + } + } +} void LLAgent::ageChat() { diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 7da8b15aa8..120506d3b5 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -120,15 +120,20 @@ private: //-------------------------------------------------------------------- public: void onAppFocusGained(); - void setFirstLogin(BOOL b) { mFirstLogin = b; } + void setFirstLogin(bool b); // Return TRUE if the database reported this login as the first for this particular user. - BOOL isFirstLogin() const { return mFirstLogin; } - BOOL isInitialized() const { return mInitialized; } + bool isFirstLogin() const { return mFirstLogin; } + bool isInitialized() const { return mInitialized; } + + void setFeatureVersion(S32 version, S32 flags); + S32 getFeatureVersion(); + void getFeatureVersionAndFlags(S32 &version, S32 &flags); + void showLatestFeatureNotification(const std::string key); public: std::string mMOTD; // Message of the day private: - BOOL mInitialized; - BOOL mFirstLogin; + bool mInitialized; + bool mFirstLogin; boost::shared_ptr mListener; //-------------------------------------------------------------------- diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index a274dc6346..f96e0dc9a6 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -1443,8 +1443,9 @@ void LLAgentWearables::findAttachmentsAddRemoveInfo(LLInventoryModel::item_array } // Build up list of objects to be removed and items currently attached. - for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end();) + LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + LLVOAvatar::attachment_map_t::iterator end = gAgentAvatarp->mAttachmentPoints.end(); + while (iter != end) { LLVOAvatar::attachment_map_t::iterator curiter = iter++; LLViewerJointAttachment* attachment = curiter->second; @@ -1700,7 +1701,7 @@ bool LLAgentWearables::moveWearable(const LLViewerInventoryItem* item, bool clos } // static -void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, const LLUUID& parent_id) +void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, const LLUUID& parent_id, std::function created_cb) { if (type == LLWearableType::WT_INVALID || type == LLWearableType::WT_NONE) return; @@ -1712,7 +1713,7 @@ void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, con LLViewerWearable* wearable = LLWearableList::instance().createNewWearable(type, gAgentAvatarp); LLAssetType::EType asset_type = wearable->getAssetType(); - LLPointer cb; + LLPointer cb; if(wear) { cb = new LLBoostFuncInventoryCallback(wear_and_edit_cb); @@ -1721,6 +1722,10 @@ void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, con { cb = new LLBoostFuncInventoryCallback(wear_cb); } + if (created_cb != NULL) + { + cb->addOnFireFunc(created_cb); + } LLUUID folder_id; diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h index 835fa7242a..c8112cad52 100644 --- a/indra/newview/llagentwearables.h +++ b/indra/newview/llagentwearables.h @@ -150,7 +150,7 @@ protected: //-------------------------------------------------------------------- public: - static void createWearable(LLWearableType::EType type, bool wear = false, const LLUUID& parent_id = LLUUID::null); + static void createWearable(LLWearableType::EType type, bool wear = false, const LLUUID& parent_id = LLUUID::null, std::function created_cb = NULL); static void editWearable(const LLUUID& item_id); bool moveWearable(const LLViewerInventoryItem* item, bool closer_to_body); diff --git a/indra/newview/llagentwearablesfetch.cpp b/indra/newview/llagentwearablesfetch.cpp index 2db58d41a6..d98e00dbc9 100644 --- a/indra/newview/llagentwearablesfetch.cpp +++ b/indra/newview/llagentwearablesfetch.cpp @@ -338,7 +338,7 @@ void LLLibraryOutfitsFetch::folderDone() } mClothingID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); - mLibraryClothingID = gInventory.findLibraryCategoryUUIDForType(LLFolderType::FT_CLOTHING, false); + mLibraryClothingID = gInventory.findLibraryCategoryUUIDForType(LLFolderType::FT_CLOTHING); // If Library->Clothing->Initial Outfits exists, use that. LLNameCategoryCollector matchFolderFunctor("Initial Outfits"); @@ -461,46 +461,50 @@ void LLLibraryOutfitsFetch::libraryDone() gInventory.removeObserver(this); LLPointer copy_waiter = new LLLibraryOutfitsCopyDone(this); - mImportedClothingID = gInventory.createNewCategory(mClothingID, - LLFolderType::FT_NONE, - mImportedClothingName); - // Copy each folder from library into clothing unless it already exists. - for (uuid_vec_t::const_iterator iter = mLibraryClothingFolders.begin(); - iter != mLibraryClothingFolders.end(); - ++iter) - { - const LLUUID& src_folder_id = (*iter); // Library clothing folder ID - const LLViewerInventoryCategory *cat = gInventory.getCategory(src_folder_id); - if (!cat) + gInventory.createNewCategory(mClothingID, LLFolderType::FT_NONE, + mImportedClothingName, [this, copy_waiter](const LLUUID& new_cat_id) { - LL_WARNS() << "Library folder import for uuid:" << src_folder_id << " failed to find folder." << LL_ENDL; - continue; - } + mImportedClothingID = new_cat_id; + // Copy each folder from library into clothing unless it already exists. + for (uuid_vec_t::const_iterator iter = mLibraryClothingFolders.begin(); + iter != mLibraryClothingFolders.end(); + ++iter) + { + const LLUUID& src_folder_id = (*iter); // Library clothing folder ID + const LLViewerInventoryCategory *cat = gInventory.getCategory(src_folder_id); + if (!cat) + { + LL_WARNS() << "Library folder import for uuid:" << src_folder_id << " failed to find folder." << LL_ENDL; + continue; + } - if (!LLAppearanceMgr::getInstance()->getCanMakeFolderIntoOutfit(src_folder_id)) - { - LL_INFOS() << "Skipping non-outfit folder name:" << cat->getName() << LL_ENDL; - continue; - } + if (!LLAppearanceMgr::getInstance()->getCanMakeFolderIntoOutfit(src_folder_id)) + { + LL_INFOS() << "Skipping non-outfit folder name:" << cat->getName() << LL_ENDL; + continue; + } - // Don't copy the category if it already exists. - LLNameCategoryCollector matchFolderFunctor(cat->getName()); - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t wearable_array; - gInventory.collectDescendentsIf(mImportedClothingID, - cat_array, wearable_array, - LLInventoryModel::EXCLUDE_TRASH, - matchFolderFunctor); - if (cat_array.size() > 0) - { - continue; - } + // Don't copy the category if it already exists. + LLNameCategoryCollector matchFolderFunctor(cat->getName()); + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t wearable_array; + gInventory.collectDescendentsIf(mImportedClothingID, + cat_array, wearable_array, + LLInventoryModel::EXCLUDE_TRASH, + matchFolderFunctor); + if (cat_array.size() > 0) + { + continue; + } - LLUUID dst_folder_id = gInventory.createNewCategory(mImportedClothingID, - LLFolderType::FT_NONE, - cat->getName()); - LLAppearanceMgr::getInstance()->shallowCopyCategoryContents(src_folder_id, dst_folder_id, copy_waiter); - } + gInventory.createNewCategory(mImportedClothingID, + LLFolderType::FT_NONE, + cat->getName(), + [src_folder_id, copy_waiter](const LLUUID& new_cat_id) { + LLAppearanceMgr::getInstance()->shallowCopyCategoryContents(src_folder_id, new_cat_id, copy_waiter); + }); + } + }); } void LLLibraryOutfitsFetch::importedFolderFetch() @@ -556,8 +560,6 @@ void LLLibraryOutfitsFetch::contentsDone() { LL_INFOS() << "start" << LL_ENDL; - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t wearable_array; LLPointer order_myoutfits_on_destroy = new LLBoostFuncInventoryCallback(no_op_inventory_func, order_my_outfits_cb); @@ -577,24 +579,26 @@ void LLLibraryOutfitsFetch::contentsDone() if (cat->getName() == LLStartUp::getInitialOutfitName()) continue; // First, make a folder in the My Outfits directory. - LLUUID new_outfit_folder_id = gInventory.createNewCategory(mMyOutfitsID, LLFolderType::FT_OUTFIT, cat->getName()); - - cat_array.clear(); - wearable_array.clear(); - // Collect the contents of each imported clothing folder, so we can create new outfit links for it - gInventory.collectDescendents(folder_id, cat_array, wearable_array, - LLInventoryModel::EXCLUDE_TRASH); - - LLInventoryObject::const_object_list_t item_array; - for (LLInventoryModel::item_array_t::const_iterator wearable_iter = wearable_array.begin(); - wearable_iter != wearable_array.end(); - ++wearable_iter) + gInventory.createNewCategory(mMyOutfitsID, LLFolderType::FT_OUTFIT, cat->getName(), [folder_id, order_myoutfits_on_destroy](const LLUUID&) { - LLConstPointer item = wearable_iter->get(); - item_array.push_back(item); - } + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t wearable_array; - link_inventory_array(LLAppearanceMgr::instance().getCOF(), item_array, order_myoutfits_on_destroy); + // Collect the contents of each imported clothing folder, so we can create new outfit links for it + gInventory.collectDescendents(folder_id, cat_array, wearable_array, + LLInventoryModel::EXCLUDE_TRASH); + + LLInventoryObject::const_object_list_t item_array; + for (LLInventoryModel::item_array_t::const_iterator wearable_iter = wearable_array.begin(); + wearable_iter != wearable_array.end(); + ++wearable_iter) + { + LLConstPointer item = wearable_iter->get(); + item_array.push_back(item); + } + + link_inventory_array(LLAppearanceMgr::instance().getCOF(), item_array, order_myoutfits_on_destroy); + }); } mOutfitsPopulated = true; diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index aa43da9e1e..184481d5f8 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -29,11 +29,15 @@ #include "llaisapi.h" #include "llagent.h" +#include "llappviewer.h" #include "llcallbacklist.h" #include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "llnotificationsutil.h" #include "llsdutil.h" #include "llviewerregion.h" -#include "llinventoryobserver.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" #include "llviewercontrol.h" #include "llviewernetwork.h" @@ -45,11 +49,16 @@ //========================================================================= const std::string AISAPI::INVENTORY_CAP_NAME("InventoryAPIv3"); const std::string AISAPI::LIBRARY_CAP_NAME("LibraryAPIv3"); +const S32 AISAPI::HTTP_TIMEOUT = 180; std::list AISAPI::sPostponedQuery; const S32 MAX_SIMULTANEOUS_COROUTINES = 2048; +// AIS3 allows '*' requests, but in reality those will be cut at some point +// Specify own depth to be able to anticipate it and mark folders as incomplete +const S32 MAX_FOLDER_DEPTH_REQUEST = 50; + //------------------------------------------------------------------------- /*static*/ bool AISAPI::isAvailable() @@ -98,6 +107,10 @@ void AISAPI::CreateInventory(const LLUUID& parentId, const LLSD& newInventory, c if (cap.empty()) { LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } @@ -105,7 +118,7 @@ void AISAPI::CreateInventory(const LLUUID& parentId, const LLSD& newInventory, c tid.generate(); std::string url = cap + std::string("/category/") + parentId.asString() + "?tid=" + tid.asString(); - LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + LL_DEBUGS("Inventory") << "url: " << url << " parentID " << parentId << " newInventory " << newInventory << LL_ENDL; // I may be suffering from golden hammer here, but the first part of this bind // is actually a static cast for &HttpCoroutineAdapter::postAndSuspend so that @@ -129,7 +142,7 @@ void AISAPI::CreateInventory(const LLUUID& parentId, const LLSD& newInventory, c (&LLCoreHttpUtil::HttpCoroutineAdapter::postAndSuspend), _1, _2, _3, _4, _5, _6); LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, postFn, url, parentId, newInventory, callback, COPYINVENTORY)); + _1, postFn, url, parentId, newInventory, callback, CREATEINVENTORY)); EnqueueAISCommand("CreateInventory", proc); } @@ -140,6 +153,10 @@ void AISAPI::SlamFolder(const LLUUID& folderId, const LLSD& newInventory, comple if (cap.empty()) { LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } @@ -175,6 +192,10 @@ void AISAPI::RemoveCategory(const LLUUID &categoryId, completion_t callback) if (cap.empty()) { LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } @@ -208,6 +229,10 @@ void AISAPI::RemoveItem(const LLUUID &itemId, completion_t callback) if (cap.empty()) { LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } @@ -240,6 +265,10 @@ void AISAPI::CopyLibraryCategory(const LLUUID& sourceId, const LLUUID& destId, b if (cap.empty()) { LL_WARNS("Inventory") << "Library cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } @@ -284,6 +313,10 @@ void AISAPI::PurgeDescendents(const LLUUID &categoryId, completion_t callback) if (cap.empty()) { LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } @@ -318,6 +351,10 @@ void AISAPI::UpdateCategory(const LLUUID &categoryId, const LLSD &updates, compl if (cap.empty()) { LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } std::string url = cap + std::string("/category/") + categoryId.asString(); @@ -350,6 +387,10 @@ void AISAPI::UpdateItem(const LLUUID &itemId, const LLSD &updates, completion_t if (cap.empty()) { LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } std::string url = cap + std::string("/item/") + itemId.asString(); @@ -372,6 +413,380 @@ void AISAPI::UpdateItem(const LLUUID &itemId, const LLSD &updates, completion_t EnqueueAISCommand("UpdateItem", proc); } +/*static*/ +void AISAPI::FetchItem(const LLUUID &itemId, ITEM_TYPE type, completion_t callback) +{ + std::string cap; + + cap = (type == INVENTORY) ? getInvCap() : getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/item/") + itemId.asString(); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, itemId, LLSD(), callback, FETCHITEM)); + + EnqueueAISCommand("FetchItem", proc); +} + +/*static*/ +void AISAPI::FetchCategoryChildren(const LLUUID &catId, ITEM_TYPE type, bool recursive, completion_t callback, S32 depth) +{ + std::string cap; + + cap = (type == INVENTORY) ? getInvCap() : getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/") + catId.asString() + "/children"; + + if (recursive) + { + // can specify depth=*, but server side is going to cap requests + // and reject everything 'over the top',. + depth = MAX_FOLDER_DEPTH_REQUEST; + } + else + { + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); + } + + url += "?depth=" + std::to_string(depth); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + // get doesn't use body, can pass additional data + LLSD body; + body["depth"] = depth; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, catId, body, callback, FETCHCATEGORYCHILDREN)); + + EnqueueAISCommand("FetchCategoryChildren", proc); +} + +// some folders can be requested by name, like +// animatn | bodypart | clothing | current | favorite | gesture | inbox | landmark | lsltext +// lstndfnd | my_otfts | notecard | object | outbox | root | snapshot | sound | texture | trash +void AISAPI::FetchCategoryChildren(const std::string &identifier, bool recursive, completion_t callback, S32 depth) +{ + std::string cap; + + cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/") + identifier + "/children"; + + if (recursive) + { + // can specify depth=*, but server side is going to cap requests + // and reject everything 'over the top',. + depth = MAX_FOLDER_DEPTH_REQUEST; + } + else + { + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); + } + + url += "?depth=" + std::to_string(depth); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + // get doesn't use body, can pass additional data + LLSD body; + body["depth"] = depth; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, LLUUID::null, body, callback, FETCHCATEGORYCHILDREN)); + + EnqueueAISCommand("FetchCategoryChildren", proc); +} + +/*static*/ +void AISAPI::FetchCategoryCategories(const LLUUID &catId, ITEM_TYPE type, bool recursive, completion_t callback, S32 depth) +{ + std::string cap; + + cap = (type == INVENTORY) ? getInvCap() : getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/") + catId.asString() + "/categories"; + + if (recursive) + { + // can specify depth=*, but server side is going to cap requests + // and reject everything 'over the top',. + depth = MAX_FOLDER_DEPTH_REQUEST; + } + else + { + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); + } + + url += "?depth=" + std::to_string(depth); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + // get doesn't use body, can pass additional data + LLSD body; + body["depth"] = depth; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, catId, body, callback, FETCHCATEGORYCATEGORIES)); + + EnqueueAISCommand("FetchCategoryCategories", proc); +} + +void AISAPI::FetchCategorySubset(const LLUUID& catId, + const uuid_vec_t specificChildren, + ITEM_TYPE type, + bool recursive, + completion_t callback, + S32 depth) +{ + std::string cap = (type == INVENTORY) ? getInvCap() : getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + if (specificChildren.empty()) + { + LL_WARNS("Inventory") << "Empty request!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + // category/any_folder_id/children?depth=*&children=child_id1,child_id2,child_id3 + std::string url = cap + std::string("/category/") + catId.asString() + "/children"; + + if (recursive) + { + depth = MAX_FOLDER_DEPTH_REQUEST; + } + else + { + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); + } + + uuid_vec_t::const_iterator iter = specificChildren.begin(); + uuid_vec_t::const_iterator end = specificChildren.end(); + + url += "?depth=" + std::to_string(depth) + "&children=" + iter->asString(); + iter++; + + while (iter != end) + { + url += "," + iter->asString(); + iter++; + } + + const S32 MAX_URL_LENGH = 2000; // RFC documentation specifies a maximum length of 2048 + if (url.length() > MAX_URL_LENGH) + { + LL_WARNS("Inventory") << "Request url is too long, url: " << url << LL_ENDL; + } + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + // get doesn't use body, can pass additional data + LLSD body; + body["depth"] = depth; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, catId, body, callback, FETCHCATEGORYSUBSET)); + + EnqueueAISCommand("FetchCategorySubset", proc); +} + +/*static*/ +// Will get COF folder, links in it and items those links point to +void AISAPI::FetchCOF(completion_t callback) +{ + std::string cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/current/links"); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + LLSD body; + // Only cof folder will be full, but cof can contain an outfit + // link with embedded outfit folder for request to parse + body["depth"] = 0; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, LLUUID::null, body, callback, FETCHCOF)); + + EnqueueAISCommand("FetchCOF", proc); +} + +void AISAPI::FetchCategoryLinks(const LLUUID &catId, completion_t callback) +{ + std::string cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/") + catId.asString() + "/links"; + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), + _1, _2, _3, _5, _6); + + LLSD body; + body["depth"] = 0; + LLCoprocedureManager::CoProcedure_t proc( + boost::bind(&AISAPI::InvokeAISCommandCoro, _1, getFn, url, LLUUID::null, body, callback, FETCHCATEGORYLINKS)); + + EnqueueAISCommand("FetchCategoryLinks", proc); +} + +/*static*/ +void AISAPI::FetchOrphans(completion_t callback) +{ + std::string cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/orphans"); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend) , _1 , _2 , _3 , _5 , _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro , + _1 , getFn , url , LLUUID::null , LLSD() , callback , FETCHORPHANS)); + + EnqueueAISCommand("FetchOrphans" , proc); +} + /*static*/ void AISAPI::EnqueueAISCommand(const std::string &procName, LLCoprocedureManager::CoProcedure_t proc) { @@ -422,22 +837,67 @@ void AISAPI::onIdle(void *userdata) } } +/*static*/ +void AISAPI::onUpdateReceived(const LLSD& update, COMMAND_TYPE type, const LLSD& request_body) +{ + LLTimer timer; + if ( (type == UPDATECATEGORY || type == UPDATEITEM) + && gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + dump_sequential_xml(gAgentAvatarp->getFullname() + "_ais_update", update); + } + + AISUpdate ais_update(update, type, request_body); + ais_update.doUpdate(); // execute the updates in the appropriate order. + LL_DEBUGS("Inventory", "AIS3") << "Elapsed processing: " << timer.getElapsedTimeF32() << LL_ENDL; +} + /*static*/ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter, invokationFn_t invoke, std::string url, LLUUID targetId, LLSD body, completion_t callback, COMMAND_TYPE type) { + if (gDisconnected) + { + if (callback) + { + callback(LLUUID::null); + } + return; + } + LLCore::HttpOptions::ptr_t httpOptions(new LLCore::HttpOptions); LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest()); LLCore::HttpHeaders::ptr_t httpHeaders; - httpOptions->setTimeout(LLCoreHttpUtil::HTTP_REQUEST_EXPIRY_SECS); + httpOptions->setTimeout(HTTP_TIMEOUT); - LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + LL_DEBUGS("Inventory") << "Request url: " << url << LL_ENDL; - LLSD result = invoke(httpAdapter, httpRequest, url, body, httpOptions, httpHeaders); - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + LLSD result; + LLSD httpResults; + LLCore::HttpStatus status; + + if (debugLoggingEnabled("Inventory")) + { + LLTimer ais_timer; + ais_timer.start(); + result = invoke(httpAdapter , httpRequest , url , body , httpOptions , httpHeaders); + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + F32MillisecondsImplicit elapsed_time = ais_timer.getElapsedTimeF32(); + + LL_DEBUGS("Inventory") << "Request type: " << (S32)type + << " \nRequest target: " << targetId + << " \nElapsed time since request: " << elapsed_time + << " \nstatus: " << status.toULong() << LL_ENDL; + } + else + { + result = invoke(httpAdapter , httpRequest , url , body , httpOptions , httpHeaders); + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + } if (!status || !result.isMap()) { @@ -479,59 +939,181 @@ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t ht } } } + else if (status == LLCore::HttpStatus(HTTP_FORBIDDEN) /*403*/) + { + if (type == FETCHCATEGORYCHILDREN) + { + if (body.has("depth") && body["depth"].asInteger() == 0) + { + // Can't fetch a single folder with depth 0, folder is too big. + static bool first_call = true; + if (first_call) + { + first_call = false; + LLNotificationsUtil::add("InventoryLimitReachedAISAlert"); + } + else + { + LLNotificationsUtil::add("InventoryLimitReachedAIS"); + } + LL_WARNS("Inventory") << "Fetch failed, content is over limit, url: " << url << LL_ENDL; + } + else + { + // Result was too big, but situation is recoverable by requesting with lower depth + LL_DEBUGS("Inventory") << "Fetch failed, content is over limit, url: " << url << LL_ENDL; + } + } + } LL_WARNS("Inventory") << "Inventory error: " << status.toString() << LL_ENDL; LL_WARNS("Inventory") << ll_pretty_print_sd(result) << LL_ENDL; } - gInventory.onAISUpdateReceived("AISCommand", result); + LL_DEBUGS("Inventory", "AIS3") << "Result: " << result << LL_ENDL; + onUpdateReceived(result, type, body); if (callback && !callback.empty()) - { + { // [SL:KB] - Patch: Appearance-SyncAttach | Checked: Catznip-3.7 - uuid_list_t ids; - switch (type) - { - case COPYLIBRARYCATEGORY: - if (result.has("category_id")) - { - ids.insert(result["category_id"]); - } - break; - case COPYINVENTORY: - { - AISUpdate::parseUUIDArray(result, "_created_items", ids); - AISUpdate::parseUUIDArray(result, "_created_categories", ids); - } - break; - case UPDATECATEGORY: - { - AISUpdate::parseUUIDArray(result, "_updated_categories", ids); - } - break; - default: - break; - } + uuid_list_t ids; + switch (type) + { + case COPYLIBRARYCATEGORY: + case FETCHCATEGORYCATEGORIES: + case FETCHCATEGORYCHILDREN: + case FETCHCATEGORYSUBSET: + case FETCHCATEGORYLINKS: + case FETCHCOF: + if (result.has("category_id")) + { + ids.emplace(result["category_id"]); + } + break; + case FETCHITEM: + if (result.has("linked_id")) + { + ids.emplace(result["linked_id"]); + } + else if (result.has("item_id")) + { + // Error message might contain an item_id!!! + ids.emplace(result["item_id"]); + } + break; + case COPYINVENTORY: + case CREATEINVENTORY: + { + AISUpdate::parseUUIDArray(result, "_created_items", ids); + AISUpdate::parseUUIDArray(result, "_created_categories", ids); + } + break; + case UPDATECATEGORY: + { + AISUpdate::parseUUIDArray(result, "_updated_categories", ids); + } + break; + default: + break; + } + + // Call callback at least once regardless of failure. + if (ids.empty()) + { + ids.emplace(LLUUID::null); + } + + for (const auto& id : ids) + { + callback(id); + } - // If we were feeling daring we'd call LLInventoryCallback::fire for every item but it would take additional work to investigate whether all LLInventoryCallback derived classes - // were designed to handle multiple fire calls (with legacy link creation only one would ever fire per link creation) so we'll be cautious and only call for the first one for now - // (note that the LL code as written below will always call fire once with the NULL UUID for anything but CopyLibraryCategoryCommand so even the above is an improvement) - callback( (!ids.empty()) ? *ids.begin() : LLUUID::null); // [/SL:KB] -// LLUUID id(LLUUID::null); -// -// if (result.has("category_id") && (type == COPYLIBRARYCATEGORY)) -// { -// id = result["category_id"]; -// } -// -// callback(id); + //bool needs_callback = true; + //LLUUID id(LLUUID::null); + + //switch (type) + //{ + //case COPYLIBRARYCATEGORY: + //case FETCHCATEGORYCATEGORIES: + //case FETCHCATEGORYCHILDREN: + //case FETCHCATEGORYSUBSET: + //case FETCHCATEGORYLINKS: + //case FETCHCOF: + // if (result.has("category_id")) + // { + // id = result["category_id"]; + // } + // break; + //case FETCHITEM: + // if (result.has("item_id")) + // { + // // Error message might contain an item_id!!! + // id = result["item_id"]; + // } + // if (result.has("linked_id")) + // { + // id = result["linked_id"]; + // } + // break; + //case CREATEINVENTORY: + // // CREATEINVENTORY can have multiple callbacks + // if (result.has("_created_categories")) + // { + // LLSD& cats = result["_created_categories"]; + // LLSD::array_const_iterator cat_iter; + // for (cat_iter = cats.beginArray(); cat_iter != cats.endArray(); ++cat_iter) + // { + // LLUUID cat_id = *cat_iter; + // callback(cat_id); + // needs_callback = false; + // } + // } + // if (result.has("_created_items")) + // { + // LLSD& items = result["_created_items"]; + // LLSD::array_const_iterator item_iter; + // for (item_iter = items.beginArray(); item_iter != items.endArray(); ++item_iter) + // { + // LLUUID item_id = *item_iter; + // callback(item_id); + // needs_callback = false; + // } + // } + // break; + //default: + // break; + //} + + //if (needs_callback) + //{ + // // Call callback at least once regardless of failure. + // // UPDATEITEM doesn't expect an id + // callback(id); + //} } } //------------------------------------------------------------------------- -AISUpdate::AISUpdate(const LLSD& update) +AISUpdate::AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD& request_body) +: mType(type) { + mFetch = (type == AISAPI::FETCHITEM) + || (type == AISAPI::FETCHCATEGORYCHILDREN) + || (type == AISAPI::FETCHCATEGORYCATEGORIES) + || (type == AISAPI::FETCHCATEGORYSUBSET) + || (type == AISAPI::FETCHCOF) + || (type == AISAPI::FETCHCATEGORYLINKS) + || (type == AISAPI::FETCHORPHANS); + // parse update llsd into stuff to do or parse received items. + mFetchDepth = MAX_FOLDER_DEPTH_REQUEST; + if (mFetch && request_body.has("depth")) + { + mFetchDepth = request_body["depth"].asInteger(); + } + + mTimer.setTimerExpirySec(debugLoggingEnabled("Inventory") ? EXPIRY_SECONDS_DEBUG : EXPIRY_SECONDS_LIVE); + mTimer.start(); parseUpdate(update); } @@ -541,6 +1123,7 @@ void AISUpdate::clearParseResults() mCatDescendentsKnown.clear(); mCatVersionsUpdated.clear(); mItemsCreated.clear(); + mItemsLost.clear(); mItemsUpdated.clear(); mCategoriesCreated.clear(); mCategoriesUpdated.clear(); @@ -549,6 +1132,16 @@ void AISUpdate::clearParseResults() mCategoryIds.clear(); } +void AISUpdate::checkTimeout() +{ + if (mTimer.hasExpired()) + { + llcoro::suspend(); + LLCoros::checkStop(); + mTimer.setTimerExpirySec(debugLoggingEnabled("Inventory") ? EXPIRY_SECONDS_DEBUG : EXPIRY_SECONDS_LIVE); + } +} + void AISUpdate::parseUpdate(const LLSD& update) { clearParseResults(); @@ -636,24 +1229,37 @@ void AISUpdate::parseMeta(const LLSD& update) void AISUpdate::parseContent(const LLSD& update) { - if (update.has("linked_id")) + // Errors from a fetch request might contain id without + // full item or folder. + // Todo: Depending on error we might want to do something, + // like removing a 404 item or refetching parent folder + if (update.has("linked_id") && update.has("parent_id")) { - parseLink(update); + parseLink(update, mFetchDepth); } - else if (update.has("item_id")) + else if (update.has("item_id") && update.has("parent_id")) { parseItem(update); } - if (update.has("category_id")) - { - parseCategory(update); - } + if (mType == AISAPI::FETCHCATEGORYSUBSET) + { + // initial category is incomplete, don't process it, + // go for content instead + if (update.has("_embedded")) + { + parseEmbedded(update["_embedded"], mFetchDepth - 1); + } + } + else if (update.has("category_id") && update.has("parent_id")) + { + parseCategory(update, mFetchDepth); + } else { if (update.has("_embedded")) { - parseEmbedded(update["_embedded"]); + parseEmbedded(update["_embedded"], mFetchDepth); } } } @@ -671,7 +1277,17 @@ void AISUpdate::parseItem(const LLSD& item_map) BOOL rv = new_item->unpackMessage(item_map); if (rv) { - if (curr_item) + if (mFetch) + { + mItemsCreated[item_id] = new_item; + new_item->setComplete(true); + + if (new_item->getParentUUID().isNull()) + { + mItemsLost[item_id] = new_item; + } + } + else if (curr_item) { mItemsUpdated[item_id] = new_item; // This statement is here to cause a new entry with 0 @@ -683,6 +1299,7 @@ void AISUpdate::parseItem(const LLSD& item_map) { mItemsCreated[item_id] = new_item; mCatDescendentDeltas[new_item->getParentUUID()]++; + new_item->setComplete(true); } } else @@ -692,7 +1309,7 @@ void AISUpdate::parseItem(const LLSD& item_map) } } -void AISUpdate::parseLink(const LLSD& link_map) +void AISUpdate::parseLink(const LLSD& link_map, S32 depth) { LLUUID item_id = link_map["item_id"].asUUID(); LLPointer new_link(new LLViewerInventoryItem); @@ -706,7 +1323,24 @@ void AISUpdate::parseLink(const LLSD& link_map) if (rv) { const LLUUID& parent_id = new_link->getParentUUID(); - if (curr_link) + if (mFetch) + { + LLPermissions default_perms; + default_perms.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + default_perms.initMasks(PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE); + new_link->setPermissions(default_perms); + LLSaleInfo default_sale_info; + new_link->setSaleInfo(default_sale_info); + //LL_DEBUGS("Inventory") << "creating link from llsd: " << ll_pretty_print_sd(link_map) << LL_ENDL; + mItemsCreated[item_id] = new_link; + new_link->setComplete(true); + + if (new_link->getParentUUID().isNull()) + { + mItemsLost[item_id] = new_link; + } + } + else if (curr_link) { mItemsUpdated[item_id] = new_link; // This statement is here to cause a new entry with 0 @@ -725,7 +1359,13 @@ void AISUpdate::parseLink(const LLSD& link_map) //LL_DEBUGS("Inventory") << "creating link from llsd: " << ll_pretty_print_sd(link_map) << LL_ENDL; mItemsCreated[item_id] = new_link; mCatDescendentDeltas[parent_id]++; + new_link->setComplete(true); } + + if (link_map.has("_embedded")) + { + parseEmbedded(link_map["_embedded"], depth); + } } else { @@ -735,19 +1375,30 @@ void AISUpdate::parseLink(const LLSD& link_map) } -void AISUpdate::parseCategory(const LLSD& category_map) +void AISUpdate::parseCategory(const LLSD& category_map, S32 depth) { - LLUUID category_id = category_map["category_id"].asUUID(); + LLUUID category_id = category_map["category_id"].asUUID(); + S32 version = LLViewerInventoryCategory::VERSION_UNKNOWN; - // Check descendent count first, as it may be needed - // to populate newly created categories - if (category_map.has("_embedded")) - { - parseDescendentCount(category_id, category_map["_embedded"]); - } + if (category_map.has("version")) + { + version = category_map["version"].asInteger(); + } + + LLViewerInventoryCategory *curr_cat = gInventory.getCategory(category_id); + + if (curr_cat + && curr_cat->getVersion() > LLViewerInventoryCategory::VERSION_UNKNOWN + && curr_cat->getDescendentCount() != LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN + && version > LLViewerInventoryCategory::VERSION_UNKNOWN + && version < curr_cat->getVersion()) + { + LL_WARNS() << "Got stale folder, known: " << curr_cat->getVersion() + << ", received: " << version << LL_ENDL; + return; + } LLPointer new_cat; - LLViewerInventoryCategory *curr_cat = gInventory.getCategory(category_id); if (curr_cat) { // Default to current values where not provided. @@ -767,13 +1418,58 @@ void AISUpdate::parseCategory(const LLSD& category_map) } BOOL rv = new_cat->unpackMessage(category_map); // *NOTE: unpackMessage does not unpack version or descendent count. - //if (category_map.has("version")) - //{ - // mCatVersionsUpdated[category_id] = category_map["version"].asInteger(); - //} if (rv) { - if (curr_cat) + // Check descendent count first, as it may be needed + // to populate newly created categories + if (category_map.has("_embedded")) + { + parseDescendentCount(category_id, new_cat->getPreferredType(), category_map["_embedded"]); + } + + if (mFetch) + { + uuid_int_map_t::const_iterator lookup_it = mCatDescendentsKnown.find(category_id); + if (mCatDescendentsKnown.end() != lookup_it) + { + S32 descendent_count = lookup_it->second; + LL_DEBUGS("Inventory") << "Setting descendents count to " << descendent_count + << " for category " << category_id << LL_ENDL; + new_cat->setDescendentCount(descendent_count); + + // set version only if we are sure this update has full data and embeded items + // since viewer uses version to decide if folder and content still need fetching + if (version > LLViewerInventoryCategory::VERSION_UNKNOWN + && depth >= 0) + { + if (curr_cat && curr_cat->getVersion() > version) + { + LL_WARNS("Inventory") << "Version was " << curr_cat->getVersion() + << ", but fetch returned version " << version + << " for category " << category_id << LL_ENDL; + } + else + { + LL_DEBUGS("Inventory") << "Setting version to " << version + << " for category " << category_id << LL_ENDL; + } + + new_cat->setVersion(version); + } + } + else if (curr_cat + && curr_cat->getVersion() > LLViewerInventoryCategory::VERSION_UNKNOWN + && version > curr_cat->getVersion()) + { + // Potentially should new_cat->setVersion(unknown) here, + // but might be waiting for a callback that would increment + LL_DEBUGS("Inventory") << "Category " << category_id + << " is stale. Known version: " << curr_cat->getVersion() + << " server version: " << version << LL_ENDL; + } + mCategoriesCreated[category_id] = new_cat; + } + else if (curr_cat) { mCategoriesUpdated[category_id] = new_cat; // This statement is here to cause a new entry with 0 @@ -786,20 +1482,22 @@ void AISUpdate::parseCategory(const LLSD& category_map) else { // Set version/descendents for newly created categories. - if (category_map.has("version")) - { - S32 version = category_map["version"].asInteger(); - LL_DEBUGS("Inventory") << "Setting version to " << version - << " for new category " << category_id << LL_ENDL; - new_cat->setVersion(version); - } - uuid_int_map_t::const_iterator lookup_it = mCatDescendentsKnown.find(category_id); - if (mCatDescendentsKnown.end() != lookup_it) - { - S32 descendent_count = lookup_it->second; - LL_DEBUGS("Inventory") << "Setting descendents count to " << descendent_count - << " for new category " << category_id << LL_ENDL; - new_cat->setDescendentCount(descendent_count); + uuid_int_map_t::const_iterator lookup_it = mCatDescendentsKnown.find(category_id); + if (mCatDescendentsKnown.end() != lookup_it) + { + S32 descendent_count = lookup_it->second; + LL_DEBUGS("Inventory") << "Setting descendents count to " << descendent_count + << " for new category " << category_id << LL_ENDL; + new_cat->setDescendentCount(descendent_count); + + // Don't set version unles correct children count is present + if (category_map.has("version")) + { + S32 version = category_map["version"].asInteger(); + LL_DEBUGS("Inventory") << "Setting version to " << version + << " for new category " << category_id << LL_ENDL; + new_cat->setVersion(version); + } } mCategoriesCreated[category_id] = new_cat; mCatDescendentDeltas[new_cat->getParentUUID()]++; @@ -814,28 +1512,35 @@ void AISUpdate::parseCategory(const LLSD& category_map) // Check for more embedded content. if (category_map.has("_embedded")) { - parseEmbedded(category_map["_embedded"]); + parseEmbedded(category_map["_embedded"], depth - 1); } } -void AISUpdate::parseDescendentCount(const LLUUID& category_id, const LLSD& embedded) +void AISUpdate::parseDescendentCount(const LLUUID& category_id, LLFolderType::EType type, const LLSD& embedded) { - // We can only determine true descendent count if this contains all descendent types. - if (embedded.has("categories") && - embedded.has("links") && - embedded.has("items")) - { - mCatDescendentsKnown[category_id] = embedded["categories"].size(); - mCatDescendentsKnown[category_id] += embedded["links"].size(); - mCatDescendentsKnown[category_id] += embedded["items"].size(); - } + // We can only determine true descendent count if this contains all descendent types. + if (embedded.has("categories") && + embedded.has("links") && + embedded.has("items")) + { + mCatDescendentsKnown[category_id] = embedded["categories"].size(); + mCatDescendentsKnown[category_id] += embedded["links"].size(); + mCatDescendentsKnown[category_id] += embedded["items"].size(); + } + else if (mFetch && embedded.has("links") && (type == LLFolderType::FT_CURRENT_OUTFIT || type == LLFolderType::FT_OUTFIT)) + { + // COF and outfits contain links only + mCatDescendentsKnown[category_id] = embedded["links"].size(); + } } -void AISUpdate::parseEmbedded(const LLSD& embedded) +void AISUpdate::parseEmbedded(const LLSD& embedded, S32 depth) { + checkTimeout(); + if (embedded.has("links")) // _embedded in a category { - parseEmbeddedLinks(embedded["links"]); + parseEmbeddedLinks(embedded["links"], depth); } if (embedded.has("items")) // _embedded in a category { @@ -847,11 +1552,11 @@ void AISUpdate::parseEmbedded(const LLSD& embedded) } if (embedded.has("categories")) // _embedded in a category { - parseEmbeddedCategories(embedded["categories"]); + parseEmbeddedCategories(embedded["categories"], depth); } if (embedded.has("category")) // _embedded in a link { - parseEmbeddedCategory(embedded["category"]); + parseEmbeddedCategory(embedded["category"], depth); } } @@ -868,7 +1573,7 @@ void AISUpdate::parseUUIDArray(const LLSD& content, const std::string& name, uui } } -void AISUpdate::parseEmbeddedLinks(const LLSD& links) +void AISUpdate::parseEmbeddedLinks(const LLSD& links, S32 depth) { for(LLSD::map_const_iterator linkit = links.beginMap(), linkend = links.endMap(); @@ -876,13 +1581,13 @@ void AISUpdate::parseEmbeddedLinks(const LLSD& links) { const LLUUID link_id((*linkit).first); const LLSD& link_map = (*linkit).second; - if (mItemIds.end() == mItemIds.find(link_id)) + if (!mFetch && mItemIds.end() == mItemIds.find(link_id)) { LL_DEBUGS("Inventory") << "Ignoring link not in items list " << link_id << LL_ENDL; } else { - parseLink(link_map); + parseLink(link_map, depth); } } } @@ -892,7 +1597,7 @@ void AISUpdate::parseEmbeddedItem(const LLSD& item) // a single item (_embedded in a link) if (item.has("item_id")) { - if (mItemIds.end() != mItemIds.find(item["item_id"].asUUID())) + if (mFetch || mItemIds.end() != mItemIds.find(item["item_id"].asUUID())) { parseItem(item); } @@ -908,7 +1613,7 @@ void AISUpdate::parseEmbeddedItems(const LLSD& items) { const LLUUID item_id((*itemit).first); const LLSD& item_map = (*itemit).second; - if (mItemIds.end() == mItemIds.find(item_id)) + if (!mFetch && mItemIds.end() == mItemIds.find(item_id)) { LL_DEBUGS("Inventory") << "Ignoring item not in items list " << item_id << LL_ENDL; } @@ -919,19 +1624,19 @@ void AISUpdate::parseEmbeddedItems(const LLSD& items) } } -void AISUpdate::parseEmbeddedCategory(const LLSD& category) +void AISUpdate::parseEmbeddedCategory(const LLSD& category, S32 depth) { // a single category (_embedded in a link) if (category.has("category_id")) { - if (mCategoryIds.end() != mCategoryIds.find(category["category_id"].asUUID())) + if (mFetch || mCategoryIds.end() != mCategoryIds.find(category["category_id"].asUUID())) { - parseCategory(category); + parseCategory(category, depth); } } } -void AISUpdate::parseEmbeddedCategories(const LLSD& categories) +void AISUpdate::parseEmbeddedCategories(const LLSD& categories, S32 depth) { // a map of categories (_embedded in a category) for(LLSD::map_const_iterator categoryit = categories.beginMap(), @@ -940,19 +1645,21 @@ void AISUpdate::parseEmbeddedCategories(const LLSD& categories) { const LLUUID category_id((*categoryit).first); const LLSD& category_map = (*categoryit).second; - if (mCategoryIds.end() == mCategoryIds.find(category_id)) + if (!mFetch && mCategoryIds.end() == mCategoryIds.find(category_id)) { LL_DEBUGS("Inventory") << "Ignoring category not in categories list " << category_id << LL_ENDL; } else { - parseCategory(category_map); + parseCategory(category_map, depth); } } } void AISUpdate::doUpdate() { + checkTimeout(); + // Do version/descendant accounting. for (std::map::const_iterator catit = mCatDescendentDeltas.begin(); catit != mCatDescendentDeltas.end(); ++catit) @@ -994,6 +1701,7 @@ void AISUpdate::doUpdate() } // CREATE CATEGORIES + const S32 MAX_UPDATE_BACKLOG = 50; // stall prevention for (deferred_category_map_t::const_iterator create_it = mCategoriesCreated.begin(); create_it != mCategoriesCreated.end(); ++create_it) { @@ -1002,6 +1710,13 @@ void AISUpdate::doUpdate() gInventory.updateCategory(new_category, LLInventoryObserver::CREATE); LL_DEBUGS("Inventory") << "created category " << category_id << LL_ENDL; + + // fetching can receive massive amount of items and folders + if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) + { + gInventory.notifyObservers(); + checkTimeout(); + } } // UPDATE CATEGORIES @@ -1026,6 +1741,24 @@ void AISUpdate::doUpdate() } } + // LOST ITEMS + if (!mItemsLost.empty()) + { + LL_INFOS("Inventory") << "Received " << (S32)mItemsLost.size() << " items without a parent" << LL_ENDL; + const LLUUID lost_uuid(gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); + if (lost_uuid.notNull()) + { + for (deferred_item_map_t::const_iterator lost_it = mItemsLost.begin(); + lost_it != mItemsLost.end(); ++lost_it) + { + LLPointer new_item = lost_it->second; + + new_item->setParent(lost_uuid); + new_item->updateParentOnServer(FALSE); + } + } + } + // CREATE ITEMS for (deferred_item_map_t::const_iterator create_it = mItemsCreated.begin(); create_it != mItemsCreated.end(); ++create_it) @@ -1038,6 +1771,13 @@ void AISUpdate::doUpdate() // case this is create. LL_DEBUGS("Inventory") << "created item " << item_id << LL_ENDL; gInventory.updateItem(new_item, LLInventoryObserver::CREATE); + + // fetching can receive massive amount of items and folders + if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) + { + gInventory.notifyObservers(); + checkTimeout(); + } } // UPDATE ITEMS @@ -1098,6 +1838,8 @@ void AISUpdate::doUpdate() } } + checkTimeout(); + gInventory.notifyObservers(); } diff --git a/indra/newview/llaisapi.h b/indra/newview/llaisapi.h index fbacf418c0..6bf9c80fd6 100644 --- a/indra/newview/llaisapi.h +++ b/indra/newview/llaisapi.h @@ -38,6 +38,12 @@ class AISAPI { public: + static const S32 HTTP_TIMEOUT; + typedef enum { + INVENTORY, + LIBRARY + } ITEM_TYPE; + typedef boost::function completion_t; static bool isAvailable(); @@ -50,9 +56,16 @@ public: static void PurgeDescendents(const LLUUID &categoryId, completion_t callback = completion_t()); static void UpdateCategory(const LLUUID &categoryId, const LLSD &updates, completion_t callback = completion_t()); static void UpdateItem(const LLUUID &itemId, const LLSD &updates, completion_t callback = completion_t()); + static void FetchItem(const LLUUID &itemId, ITEM_TYPE type, completion_t callback = completion_t()); + static void FetchCategoryChildren(const LLUUID &catId, ITEM_TYPE type = AISAPI::ITEM_TYPE::INVENTORY, bool recursive = false, completion_t callback = completion_t(), S32 depth = 0); + static void FetchCategoryChildren(const std::string &identifier, bool recursive = false, completion_t callback = completion_t(), S32 depth = 0); + static void FetchCategoryCategories(const LLUUID &catId, ITEM_TYPE type = AISAPI::ITEM_TYPE::INVENTORY, bool recursive = false, completion_t callback = completion_t(), S32 depth = 0); + static void FetchCategorySubset(const LLUUID& catId, const uuid_vec_t specificChildren, ITEM_TYPE type = AISAPI::ITEM_TYPE::INVENTORY, bool recursive = false, completion_t callback = completion_t(), S32 depth = 0); + static void FetchCOF(completion_t callback = completion_t()); + static void FetchCategoryLinks(const LLUUID &catId, completion_t callback = completion_t()); + static void FetchOrphans(completion_t callback = completion_t() ); static void CopyLibraryCategory(const LLUUID& sourceId, const LLUUID& destId, bool copySubfolders, completion_t callback = completion_t()); -private: typedef enum { COPYINVENTORY, SLAMFOLDER, @@ -61,9 +74,18 @@ private: PURGEDESCENDENTS, UPDATECATEGORY, UPDATEITEM, - COPYLIBRARYCATEGORY + COPYLIBRARYCATEGORY, + CREATEINVENTORY, + FETCHITEM, + FETCHCATEGORYCHILDREN, + FETCHCATEGORYCATEGORIES, + FETCHCATEGORYSUBSET, + FETCHCOF, + FETCHORPHANS, + FETCHCATEGORYLINKS } COMMAND_TYPE; +private: static const std::string INVENTORY_CAP_NAME; static const std::string LIBRARY_CAP_NAME; @@ -72,6 +94,7 @@ private: static void EnqueueAISCommand(const std::string &procName, LLCoprocedureManager::CoProcedure_t proc); static void onIdle(void *userdata); // launches postponed AIS commands + static void onUpdateReceived(const LLSD& update, COMMAND_TYPE type, const LLSD& request_body); static std::string getInvCap(); static std::string getLibCap(); @@ -87,7 +110,7 @@ private: class AISUpdate { public: - AISUpdate(const LLSD& update); + AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD& request_body); void parseUpdate(const LLSD& update); void parseMeta(const LLSD& update); void parseContent(const LLSD& update); @@ -95,19 +118,25 @@ public: static void parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids); // [/SL:KB] // void parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids); - void parseLink(const LLSD& link_map); + void parseLink(const LLSD& link_map, S32 depth); void parseItem(const LLSD& link_map); - void parseCategory(const LLSD& link_map); - void parseDescendentCount(const LLUUID& category_id, const LLSD& embedded); - void parseEmbedded(const LLSD& embedded); - void parseEmbeddedLinks(const LLSD& links); + void parseCategory(const LLSD& link_map, S32 depth); + void parseDescendentCount(const LLUUID& category_id, LLFolderType::EType type, const LLSD& embedded); + void parseEmbedded(const LLSD& embedded, S32 depth); + void parseEmbeddedLinks(const LLSD& links, S32 depth); void parseEmbeddedItems(const LLSD& items); - void parseEmbeddedCategories(const LLSD& categories); + void parseEmbeddedCategories(const LLSD& categories, S32 depth); void parseEmbeddedItem(const LLSD& item); - void parseEmbeddedCategory(const LLSD& category); + void parseEmbeddedCategory(const LLSD& category, S32 depth); void doUpdate(); private: void clearParseResults(); + void checkTimeout(); + + // Debug is very log-heavy, give it more time or it will take forever to process + // Todo: find a way to make throttle static isntead of per-request + const F32 EXPIRY_SECONDS_DEBUG = 1.f; + const F32 EXPIRY_SECONDS_LIVE = 0.008f; typedef std::map uuid_int_map_t; uuid_int_map_t mCatDescendentDeltas; @@ -116,6 +145,7 @@ private: typedef std::map > deferred_item_map_t; deferred_item_map_t mItemsCreated; + deferred_item_map_t mItemsLost; deferred_item_map_t mItemsUpdated; typedef std::map > deferred_category_map_t; deferred_category_map_t mCategoriesCreated; @@ -126,6 +156,10 @@ private: uuid_list_t mObjectsDeletedIds; uuid_list_t mItemIds; uuid_list_t mCategoryIds; + bool mFetch; + S32 mFetchDepth; + LLTimer mTimer; + AISAPI::COMMAND_TYPE mType; }; #endif diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 54c6c49adc..23a2792fdc 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -40,6 +40,7 @@ #include "llgesturemgr.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" +#include "llinventorymodelbackgroundfetch.h" #include "llinventoryobserver.h" #include "llmd5.h" #include "llnotificationsutil.h" @@ -599,6 +600,71 @@ LLUpdateAppearanceAndEditWearableOnDestroy::~LLUpdateAppearanceAndEditWearableOn } } +class LLBrokenLinkObserver : public LLInventoryObserver +{ +public: + LLUUID mUUID; + bool mEnforceItemRestrictions; + bool mEnforceOrdering; + nullary_func_t mPostUpdateFunc; + + LLBrokenLinkObserver(const LLUUID& uuid, + bool enforce_item_restrictions , + bool enforce_ordering , + nullary_func_t post_update_func) : + mUUID(uuid), + mEnforceItemRestrictions(enforce_item_restrictions), + mEnforceOrdering(enforce_ordering), + mPostUpdateFunc(post_update_func) + { + } + /* virtual */ void changed(U32 mask); + void postProcess(); +}; + +void LLBrokenLinkObserver::changed(U32 mask) +{ + if (mask & LLInventoryObserver::REBUILD) + { + // This observer should be executed after LLInventoryPanel::itemChanged(), + // but if it isn't, consider calling updateAppearanceFromCOF with a delay + const uuid_set_t& changed_item_ids = gInventory.getChangedIDs(); + for (uuid_set_t::const_iterator it = changed_item_ids.begin(); it != changed_item_ids.end(); ++it) + { + const LLUUID& id = *it; + if (id == mUUID) + { + // Might not be processed yet and it is not a + // good idea to update appearane here, postpone. + doOnIdleOneTime([this]() + { + postProcess(); + }); + + gInventory.removeObserver(this); + return; + } + } + } +} + +void LLBrokenLinkObserver::postProcess() +{ + LLViewerInventoryItem* item = gInventory.getItem(mUUID); + llassert(item && !item->getIsBrokenLink()); // the whole point was to get a correct link + if (item && item->getIsBrokenLink()) + { + LL_INFOS_ONCE("Avatar") << "Outfit link broken despite being regenerated" << LL_ENDL; + LL_DEBUGS("Avatar", "Inventory") << "Outfit link " << mUUID << " \"" << item->getName() << "\" is broken despite being regenerated" << LL_ENDL; + } + + LLAppearanceMgr::instance().updateAppearanceFromCOF( + mEnforceItemRestrictions , + mEnforceOrdering , + mPostUpdateFunc); + delete this; +} + struct LLFoundData { @@ -1780,12 +1846,18 @@ void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& ds { parent_id = gInventory.getRootFolderID(); } - LLUUID subfolder_id = gInventory.createNewCategory( parent_id, - LLFolderType::FT_NONE, - src_cat->getName()); - shallowCopyCategoryContents(src_id, subfolder_id, cb); + gInventory.createNewCategory( + parent_id, + LLFolderType::FT_NONE, + src_cat->getName(), + [src_id, cb](const LLUUID &new_id) + { + LLAppearanceMgr::getInstance()->shallowCopyCategoryContents(src_id, new_id, cb); - gInventory.notifyObservers(); + gInventory.notifyObservers(); + }, + src_cat->getThumbnailUUID() + ); } void LLAppearanceMgr::slamCategoryLinks(const LLUUID& src_id, const LLUUID& dst_id, @@ -2670,6 +2742,39 @@ void LLAppearanceMgr::updateAppearanceFromCOF(bool enforce_item_restrictions, LL_DEBUGS("Avatar") << self_av_string() << "starting" << LL_ENDL; + if (gInventory.hasPosiblyBrockenLinks()) + { + // Inventory has either broken links or links that + // haven't loaded yet. + // Check if LLAppearanceMgr needs to wait. + LLUUID current_outfit_id = getCOF(); + LLInventoryModel::item_array_t cof_items; + LLInventoryModel::cat_array_t cof_cats; + LLFindBrokenLinks is_brocken_link; + gInventory.collectDescendentsIf(current_outfit_id, + cof_cats, + cof_items, + LLInventoryModel::EXCLUDE_TRASH, + is_brocken_link); + + if (cof_items.size() > 0) + { + // Some links haven't loaded yet, but fetch isn't complete so + // links are likely fine and we will have to wait for them to + // load + if (LLInventoryModelBackgroundFetch::getInstance()->folderFetchActive()) + { + + LLBrokenLinkObserver* observer = new LLBrokenLinkObserver(cof_items.front()->getUUID(), + enforce_item_restrictions, + enforce_ordering, + post_update_func); + gInventory.addObserver(observer); + return; + } + } + } + if (enforce_item_restrictions) { // The point here is just to call @@ -3103,22 +3208,29 @@ void LLAppearanceMgr::wearCategoryFinal(LLUUID& cat_id, bool copy_items, bool ap { pid = gInventory.getRootFolderID(); } - - LLUUID new_cat_id = gInventory.createNewCategory( + + gInventory.createNewCategory( pid, LLFolderType::FT_NONE, - name); + name, + [cat_id, append](const LLUUID& new_cat_id) + { + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_id, cats, items); + // Create a CopyMgr that will copy items, manage its own destruction + new LLCallAfterInventoryCopyMgr( + *items, new_cat_id, std::string("wear_inventory_category_callback"), + boost::bind(&LLAppearanceMgr::wearInventoryCategoryOnAvatar, + LLAppearanceMgr::getInstance(), + gInventory.getCategory(new_cat_id), + append)); - // Create a CopyMgr that will copy items, manage its own destruction - new LLCallAfterInventoryCopyMgr( - *items, new_cat_id, std::string("wear_inventory_category_callback"), - boost::bind(&LLAppearanceMgr::wearInventoryCategoryOnAvatar, - LLAppearanceMgr::getInstance(), - gInventory.getCategory(new_cat_id), - append)); - - // BAP fixes a lag in display of created dir. - gInventory.notifyObservers(); + // BAP fixes a lag in display of created dir. + gInventory.notifyObservers(); + }, + cat->getThumbnailUUID() + ); } else { @@ -3608,7 +3720,7 @@ void LLAppearanceMgr::copyLibraryGestures() // Copy gestures LLUUID lib_gesture_cat_id = - gInventory.findLibraryCategoryUUIDForType(LLFolderType::FT_GESTURE,false); + gInventory.findLibraryCategoryUUIDForType(LLFolderType::FT_GESTURE); if (lib_gesture_cat_id.isNull()) { LL_WARNS() << "Unable to copy gestures, source category not found" << LL_ENDL; @@ -4132,7 +4244,7 @@ void LLAppearanceMgr::serverAppearanceUpdateCoro(LLCoreHttpUtil::HttpCoroutineAd if (cofVersion == LLViewerInventoryCategory::VERSION_UNKNOWN) { - LL_WARNS("AVatar") << "COF version is unknown... not requesting until COF version is known." << LL_ENDL; + LL_INFOS("AVatar") << "COF version is unknown... not requesting until COF version is known." << LL_ENDL; return; } else @@ -4503,26 +4615,15 @@ void LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name, boo // First, make a folder in the My Outfits directory. const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - if (AISAPI::isAvailable()) - { - // cap-based category creation was buggy until recently. use - // existence of AIS as an indicator the fix is present. Does - // not actually use AIS to create the category. - inventory_func_type func = boost::bind(&LLAppearanceMgr::onOutfitFolderCreated,this,_1,show_panel); - gInventory.createNewCategory( - parent_id, - LLFolderType::FT_OUTFIT, - new_folder_name, - func); - } - else - { - LLUUID folder_id = gInventory.createNewCategory( - parent_id, - LLFolderType::FT_OUTFIT, - new_folder_name); - onOutfitFolderCreated(folder_id, show_panel); - } + + gInventory.createNewCategory( + parent_id, + LLFolderType::FT_OUTFIT, + new_folder_name, + [show_panel](const LLUUID &new_cat_id) + { + LLAppearanceMgr::getInstance()->onOutfitFolderCreated(new_cat_id, show_panel); + }); } void LLAppearanceMgr::wearBaseOutfit() @@ -4908,6 +5009,73 @@ public: ~CallAfterCategoryFetchStage1() { } + /*virtual*/ void startFetch() + { + bool ais3 = AISAPI::isAvailable(); + for (uuid_vec_t::const_iterator it = mIDs.begin(); it != mIDs.end(); ++it) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(*it); + if (!cat) continue; + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // CHECK IT: isCategoryComplete() checks both version and descendant count but + // fetch() only works for Unknown version and doesn't care about descentants, + // as result fetch won't start and folder will potentially get stuck as + // incomplete in observer. + // Likely either both should use only version or both should check descendants. + cat->fetch(); //blindly fetch it without seeing if anything else is fetching it. + mIncomplete.push_back(*it); //Add to list of things being downloaded for this observer. + } + else if (!isCategoryComplete(cat)) + { + LL_DEBUGS("Inventory") << "Categoty " << *it << " incomplete despite having version" << LL_ENDL; + LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch(*it, true); + mIncomplete.push_back(*it); + } + else if (ais3) + { + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat->getUUID(), cats, items); + + if (items) + { + S32 complete_count = 0; + S32 incomplete_count = 0; + for (LLInventoryModel::item_array_t::const_iterator it = items->begin(); it < items->end(); ++it) + { + if (!(*it)->isFinished()) + { + incomplete_count++; + } + else + { + complete_count++; + } + } + // AIS can fetch couple items, but if there + // is more than a dozen it will be very slow + // it's faster to get whole folder in such case + if (incomplete_count > LLInventoryFetchItemsObserver::MAX_INDIVIDUAL_ITEM_REQUESTS + || (incomplete_count > 1 && complete_count == 0)) + { + LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch(*it, true); + mIncomplete.push_back(*it); + } + else + { + // let stage2 handle incomplete ones + mComplete.push_back(*it); + } + } + // else should have been handled by isCategoryComplete + } + else + { + mComplete.push_back(*it); + } + } + } virtual void done() { if (mComplete.size() <= 0) @@ -4924,13 +5092,11 @@ public: // What we do here is get the complete information on the // items in the requested category, and set up an observer // that will wait for that to happen. - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(mComplete.front(), - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH); - S32 count = item_array.size(); + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(mComplete.front(), cats, items); + + S32 count = items->size(); if(!count) { LL_WARNS() << "Nothing fetched in category " << mComplete.front() @@ -4942,11 +5108,13 @@ public: return; } - LL_INFOS() << "stage1 got " << item_array.size() << " items, passing to stage2 " << LL_ENDL; + LLViewerInventoryCategory* cat = gInventory.getCategory(mComplete.front()); + S32 version = cat ? cat->getVersion() : -2; + LL_INFOS() << "stage1, category " << mComplete.front() << " got " << count << " items, version " << version << " passing to stage2 " << LL_ENDL; uuid_vec_t ids; for(S32 i = 0; i < count; ++i) { - ids.push_back(item_array.at(i)->getUUID()); + ids.push_back(items->at(i)->getUUID()); } gInventory.removeObserver(this); @@ -4971,18 +5139,78 @@ protected: nullary_func_t mCallable; }; +void callAfterCOFFetch(nullary_func_t cb) +{ + LLUUID cat_id = LLAppearanceMgr::instance().getCOF(); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + + if (AISAPI::isAvailable()) + { + // Mark cof (update timer) so that background fetch won't request it + cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + // For reliability assume that we have no relevant cache, so + // fetch cof along with items cof's links point to. + AISAPI::FetchCOF([cb](const LLUUID& id) + { + cb(); + LLUUID cat_id = LLAppearanceMgr::instance().getCOF(); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } + }); + } + else + { + LL_INFOS() << "AIS API v3 not available, using callAfterCategoryFetch" << LL_ENDL; + // startup should have marked folder as fetching, remove that + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + callAfterCategoryFetch(cat_id, cb); + } +} + void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb) { - CallAfterCategoryFetchStage1 *stage1 = new CallAfterCategoryFetchStage1(cat_id, cb); - stage1->startFetch(); - if (stage1->isFinished()) - { - stage1->done(); - } - else - { - gInventory.addObserver(stage1); - } + CallAfterCategoryFetchStage1* stage1 = new CallAfterCategoryFetchStage1(cat_id, cb); + stage1->startFetch(); + if (stage1->isFinished()) + { + stage1->done(); + } + else + { + gInventory.addObserver(stage1); + } +} + +void callAfterCategoryLinksFetch(const LLUUID &cat_id, nullary_func_t cb) +{ + LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); + if (AISAPI::isAvailable()) + { + // Mark folder (update timer) so that background fetch won't request it + cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + // Assume that we have no relevant cache. Fetch folder, and items folder's links point to. + AISAPI::FetchCategoryLinks(cat_id, + [cb, cat_id](const LLUUID &id) + { + cb(); + LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } + }); + } + else + { + LL_WARNS() << "AIS API v3 not available, can't use AISAPI::FetchCOF" << LL_ENDL; + // startup should have marked folder as fetching, remove that + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + callAfterCategoryFetch(cat_id, cb); + } + } void add_wearable_type_counts(const uuid_vec_t& ids, diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 81d8e5b1ca..7ebcea5599 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -367,7 +367,9 @@ public: LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id,const std::string& name); // Invoke a given callable after category contents are fully fetched. +void callAfterCOFFetch(nullary_func_t cb); void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb); +void callAfterCategoryLinksFetch(const LLUUID &cat_id, nullary_func_t cb); // Wear all items in a uuid vector. void wear_multiple(const uuid_vec_t& ids, bool replace); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 65a3e07da6..57ee039418 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -230,7 +230,7 @@ #include "llcommandlineparser.h" #include "llfloatermemleak.h" #include "llfloaterreg.h" -#include "llfloatersimpleoutfitsnapshot.h" +#include "llfloatersimplesnapshot.h" #include "llfloatersnapshot.h" #include "llsidepanelinventory.h" #include "llatmosphere.h" @@ -1779,7 +1779,7 @@ bool LLAppViewer::doFrame() LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df Snapshot" ) pingMainloopTimeout("Main:Snapshot"); LLFloaterSnapshot::update(); // take snapshots - LLFloaterSimpleOutfitSnapshot::update(); + LLFloaterSimpleSnapshot::update(); gGLActive = FALSE; } diff --git a/indra/newview/llattachmentsmgr.cpp b/indra/newview/llattachmentsmgr.cpp index a9aa6ca958..9777a91dcf 100644 --- a/indra/newview/llattachmentsmgr.cpp +++ b/indra/newview/llattachmentsmgr.cpp @@ -305,6 +305,13 @@ void LLAttachmentsMgr::linkRecentlyArrivedAttachments() return; } + if (LLAppearanceMgr::instance().getCOFVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // Wait for cof to load + LL_DEBUGS_ONCE("Avatar") << "Received atachments, but cof isn't loaded yet, postponing processing" << LL_ENDL; + return; + } + LL_DEBUGS("Avatar") << "ATT checking COF linkability for " << mRecentlyArrivedAttachments.size() << " recently arrived items" << LL_ENDL; diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index c18430d270..61289c4e5d 100644 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -940,39 +940,55 @@ namespace action_give_inventory /** * Checks My Inventory visibility. */ + static bool is_give_inventory_acceptable_ids(const std::set inventory_selected_uuids) + { + if (inventory_selected_uuids.empty()) return false; // nothing selected + + bool acceptable = false; + std::set::const_iterator it = inventory_selected_uuids.begin(); + const std::set::const_iterator it_end = inventory_selected_uuids.end(); + for (; it != it_end; ++it) + { + LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); + // any category can be offered. + if (inv_cat) + { + acceptable = true; + continue; + } + + LLViewerInventoryItem* inv_item = gInventory.getItem(*it); + // check if inventory item can be given + if (LLGiveInventory::isInventoryGiveAcceptable(inv_item)) + { + acceptable = true; + continue; + } + + // there are neither item nor category in inventory + acceptable = false; + break; + } + return acceptable; + } static bool is_give_inventory_acceptable(LLInventoryPanel* panel = NULL) { // check selection in the panel - const std::set inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel); - if (inventory_selected_uuids.empty()) return false; // nothing selected + std::set inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel); + if (inventory_selected_uuids.empty()) + { + if(panel && panel->getRootFolder() && panel->getRootFolder()->isSingleFolderMode()) + { + inventory_selected_uuids.insert(panel->getRootFolderID()); + } + else + { + return false; // nothing selected + } + } - bool acceptable = false; - std::set::const_iterator it = inventory_selected_uuids.begin(); - const std::set::const_iterator it_end = inventory_selected_uuids.end(); - for (; it != it_end; ++it) - { - LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); - // any category can be offered. - if (inv_cat) - { - acceptable = true; - continue; - } - - LLViewerInventoryItem* inv_item = gInventory.getItem(*it); - // check if inventory item can be given - if (LLGiveInventory::isInventoryGiveAcceptable(inv_item)) - { - acceptable = true; - continue; - } - - // there are neither item nor category in inventory - acceptable = false; - break; - } - return acceptable; + return is_give_inventory_acceptable_ids(inventory_selected_uuids); } static void build_items_string(const std::set& inventory_selected_uuids , std::string& items_string) @@ -1100,70 +1116,89 @@ namespace action_give_inventory * @param avatar_names - avatar names request to be sent. * @param avatar_uuids - avatar names request to be sent. */ -// static void give_inventory(const uuid_vec_t& avatar_uuids, const std::vector avatar_names, LLInventoryPanel* panel = NULL) + + //static void give_inventory_ids(const uuid_vec_t& avatar_uuids, const std::vector avatar_names, const uuid_set_t inventory_selected_uuids) // [RLVa:KB] - @share - static void give_inventory(uuid_vec_t avatar_uuids, std::vector avatar_names, LLInventoryPanel* panel = NULL) + static void give_inventory_ids(uuid_vec_t avatar_uuids, std::vector avatar_names, const uuid_set_t inventory_selected_uuids) // [/RLVa:KB] - { - llassert(avatar_names.size() == avatar_uuids.size()); + { + llassert(avatar_names.size() == avatar_uuids.size()); // [RLVa:KB] - @share - if ( (RlvActions::isRlvEnabled()) && (RlvActions::hasBehaviour(RLV_BHVR_SHARE)) ) - { - for (int idxAvatar = avatar_uuids.size() - 1; idxAvatar >= 0; idxAvatar--) - { - if (!RlvActions::canGiveInventory(avatar_uuids[idxAvatar])) - { - RlvUtil::notifyBlocked(RlvStringKeys::Blocked::Share, LLSD().with("RECIPIENT", LLSLURL("agent", avatar_uuids[idxAvatar], "completename").getSLURLString())); + if ( (RlvActions::isRlvEnabled()) && (RlvActions::hasBehaviour(RLV_BHVR_SHARE)) ) + { + for (int idxAvatar = avatar_uuids.size() - 1; idxAvatar >= 0; idxAvatar--) + { + if (!RlvActions::canGiveInventory(avatar_uuids[idxAvatar])) + { + RlvUtil::notifyBlocked(RlvStringKeys::Blocked::Share, LLSD().with("RECIPIENT", LLSLURL("agent", avatar_uuids[idxAvatar], "completename").getSLURLString())); - avatar_uuids.erase(avatar_uuids.begin() + idxAvatar); - avatar_names.erase(avatar_names.begin() + idxAvatar); - } - } - } + avatar_uuids.erase(avatar_uuids.begin() + idxAvatar); + avatar_names.erase(avatar_names.begin() + idxAvatar); + } + } + } - if (avatar_uuids.empty()) - { - return; - } + if (avatar_uuids.empty()) + { + return; + } // [/RLVa:KB] - const std::set inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel); - if (inventory_selected_uuids.empty()) - { - return; - } + if (inventory_selected_uuids.empty()) + { + return; + } - std::string residents; - LLAvatarActions::buildResidentsString(avatar_names, residents, true); + std::string residents; + LLAvatarActions::buildResidentsString(avatar_names, residents, true); - std::string items; - build_items_string(inventory_selected_uuids, items); + std::string items; + build_items_string(inventory_selected_uuids, items); - int folders_count = 0; - std::set::const_iterator it = inventory_selected_uuids.begin(); + int folders_count = 0; + std::set::const_iterator it = inventory_selected_uuids.begin(); - //traverse through selected inventory items and count folders among them - for ( ; it != inventory_selected_uuids.end() && folders_count <=1 ; ++it) - { - LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); - if (NULL != inv_cat) - { - folders_count++; - } - } + //traverse through selected inventory items and count folders among them + for ( ; it != inventory_selected_uuids.end() && folders_count <=1 ; ++it) + { + LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); + if (NULL != inv_cat) + { + folders_count++; + } + } - // EXP-1599 - // In case of sharing multiple folders, make the confirmation - // dialog contain a warning that only one folder can be shared at a time. - std::string notification = (folders_count > 1) ? "ShareFolderConfirmation" : "ShareItemsConfirmation"; - LLSD substitutions; - substitutions["RESIDENTS"] = residents; - substitutions["ITEMS"] = items; - LLShareInfo::instance().mAvatarNames = avatar_names; - LLShareInfo::instance().mAvatarUuids = avatar_uuids; - LLNotificationsUtil::add(notification, substitutions, LLSD(), boost::bind(&give_inventory_cb, _1, _2, inventory_selected_uuids)); - } + // EXP-1599 + // In case of sharing multiple folders, make the confirmation + // dialog contain a warning that only one folder can be shared at a time. + std::string notification = (folders_count > 1) ? "ShareFolderConfirmation" : "ShareItemsConfirmation"; + LLSD substitutions; + substitutions["RESIDENTS"] = residents; + substitutions["ITEMS"] = items; + LLShareInfo::instance().mAvatarNames = avatar_names; + LLShareInfo::instance().mAvatarUuids = avatar_uuids; + LLNotificationsUtil::add(notification, substitutions, LLSD(), boost::bind(&give_inventory_cb, _1, _2, inventory_selected_uuids)); + } + + static void give_inventory(const uuid_vec_t& avatar_uuids, const std::vector avatar_names, LLInventoryPanel* panel = NULL) + { + llassert(avatar_names.size() == avatar_uuids.size()); + std::set inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel);; + + if (inventory_selected_uuids.empty()) + { + if(panel && panel->getRootFolder() && panel->getRootFolder()->isSingleFolderMode()) + { + inventory_selected_uuids.insert(panel->getRootFolderID()); + } + else + { + return; + } + } + give_inventory_ids(avatar_uuids, avatar_names, inventory_selected_uuids); + } } // static @@ -1284,6 +1319,28 @@ void LLAvatarActions::shareWithAvatars(LLView * panel) LLNotificationsUtil::add("ShareNotification"); } +//static +void LLAvatarActions::shareWithAvatars(const uuid_set_t inventory_selected_uuids, LLFloater* root_floater) +{ + using namespace action_give_inventory; + + LLFloaterAvatarPicker* picker = + LLFloaterAvatarPicker::show(boost::bind(give_inventory_ids, _1, _2, inventory_selected_uuids), TRUE, FALSE, FALSE, root_floater->getName()); + if (!picker) + { + return; + } + + picker->setOkBtnEnableCb(boost::bind(is_give_inventory_acceptable_ids, inventory_selected_uuids)); + picker->openFriendsTab(); + + if (root_floater) + { + root_floater->addDependentFloater(picker); + } + LLNotificationsUtil::add("ShareNotification"); +} + // static bool LLAvatarActions::canShareSelectedItems(LLInventoryPanel* inv_panel /* = NULL*/) { diff --git a/indra/newview/llavataractions.h b/indra/newview/llavataractions.h index 7af72027ce..7d730ddc27 100644 --- a/indra/newview/llavataractions.h +++ b/indra/newview/llavataractions.h @@ -139,6 +139,7 @@ public: * Share items with the picked avatars. */ static void shareWithAvatars(LLView * panel); + static void shareWithAvatars(const uuid_set_t inventory_selected_uuids, LLFloater* root_floater); /** * Block/unblock the avatar by id. diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index 3e4c37d6c2..f781030bf5 100644 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -114,6 +114,7 @@ public: virtual void previewItem( void ); virtual void selectItem(void) { } virtual void showProperties(void); + virtual void navigateToFolder(bool new_window = false, bool change_mode = false) {} // Methods used in sorting (see LLConversationSort::operator()) EConversationType const getType() const { return mConvType; } @@ -252,7 +253,7 @@ public: bool check(const LLFolderViewModelItem* item) { return true; } bool checkFolder(const LLFolderViewModelItem* folder) const { return true; } void setEmptyLookupMessage(const std::string& message) { } - std::string getEmptyLookupMessage() const { return mEmpty; } + std::string getEmptyLookupMessage(bool is_empty_folder = false) const { return mEmpty; } bool showAllResults() const { return true; } std::string::size_type getStringMatchOffset(LLFolderViewModelItem* item) const { return std::string::npos; } std::string::size_type getFilterStringSize() const { return 0; } diff --git a/indra/newview/llfloaterchangeitemthumbnail.cpp b/indra/newview/llfloaterchangeitemthumbnail.cpp new file mode 100644 index 0000000000..427de4dcaa --- /dev/null +++ b/indra/newview/llfloaterchangeitemthumbnail.cpp @@ -0,0 +1,957 @@ +/** + * @file llfloaterchangeitemthumbnail.cpp + * @brief LLFloaterChangeItemThumbnail class implementation + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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 "llfloaterchangeitemthumbnail.h" + +#include "llbutton.h" +#include "llclipboard.h" +#include "lliconctrl.h" +#include "llinventoryfunctions.h" +#include "llinventoryicon.h" +#include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "llfloaterreg.h" +#include "llfloatersimplesnapshot.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "lltextbox.h" +#include "lltexturectrl.h" +#include "llthumbnailctrl.h" +#include "llviewerfoldertype.h" +#include "llviewermenufile.h" +#include "llviewerobjectlist.h" +#include "llviewertexturelist.h" +#include "llwindow.h" +#include "lltrans.h" + + +class LLThumbnailImagePicker : public LLFilePickerThread +{ +public: + LLThumbnailImagePicker(const LLUUID &item_id); + LLThumbnailImagePicker(const LLUUID &item_id, const LLUUID &task_id); + ~LLThumbnailImagePicker(); + void notify(const std::vector& filenames) override; + +private: + LLUUID mInventoryId; + LLUUID mTaskId; +}; + +LLThumbnailImagePicker::LLThumbnailImagePicker(const LLUUID &item_id) + : LLFilePickerThread(LLFilePicker::FFLOAD_IMAGE) + , mInventoryId(item_id) +{ +} + +LLThumbnailImagePicker::LLThumbnailImagePicker(const LLUUID &item_id, const LLUUID &task_id) + : LLFilePickerThread(LLFilePicker::FFLOAD_IMAGE) + , mInventoryId(item_id) + , mTaskId(task_id) +{ +} + +LLThumbnailImagePicker::~LLThumbnailImagePicker() +{ +} + +void LLThumbnailImagePicker::notify(const std::vector& filenames) +{ + if (filenames.empty()) + { + return; + } + std::string file_path = filenames[0]; + if (file_path.empty()) + { + return; + } + + LLFloaterSimpleSnapshot::uploadThumbnail(file_path, mInventoryId, mTaskId); +} + +LLFloaterChangeItemThumbnail::LLFloaterChangeItemThumbnail(const LLSD& key) + : LLFloater(key) + , mObserverInitialized(false) + , mTooltipState(TOOLTIP_NONE) +{ +} + +LLFloaterChangeItemThumbnail::~LLFloaterChangeItemThumbnail() +{ + gInventory.removeObserver(this); + removeVOInventoryListener(); +} + +BOOL LLFloaterChangeItemThumbnail::postBuild() +{ + mItemNameText = getChild("item_name"); + mItemTypeIcon = getChild("item_type_icon"); + mThumbnailCtrl = getChild("item_thumbnail"); + mToolTipTextBox = getChild("tooltip_text"); + + LLSD tooltip_text; + mToolTipTextBox->setValue(tooltip_text); + + LLButton *upload_local = getChild("upload_local"); + upload_local->setClickedCallback(onUploadLocal, (void*)this); + upload_local->setMouseEnterCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseEnter, this, _1, _2, TOOLTIP_UPLOAD_LOCAL)); + upload_local->setMouseLeaveCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseLeave, this, _1, _2, TOOLTIP_UPLOAD_LOCAL)); + + LLButton *upload_snapshot = getChild("upload_snapshot"); + upload_snapshot->setClickedCallback(onUploadSnapshot, (void*)this); + upload_snapshot->setMouseEnterCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseEnter, this, _1, _2, TOOLTIP_UPLOAD_SNAPSHOT)); + upload_snapshot->setMouseLeaveCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseLeave, this, _1, _2, TOOLTIP_UPLOAD_SNAPSHOT)); + + LLButton *use_texture = getChild("use_texture"); + use_texture->setClickedCallback(onUseTexture, (void*)this); + use_texture->setMouseEnterCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseEnter, this, _1, _2, TOOLTIP_USE_TEXTURE)); + use_texture->setMouseLeaveCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseLeave, this, _1, _2, TOOLTIP_USE_TEXTURE)); + + mCopyToClipboardBtn = getChild("copy_to_clipboard"); + mCopyToClipboardBtn->setClickedCallback(onCopyToClipboard, (void*)this); + mCopyToClipboardBtn->setMouseEnterCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseEnter, this, _1, _2, TOOLTIP_COPY_TO_CLIPBOARD)); + mCopyToClipboardBtn->setMouseLeaveCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseLeave, this, _1, _2, TOOLTIP_COPY_TO_CLIPBOARD)); + + mPasteFromClipboardBtn = getChild("paste_from_clipboard"); + mPasteFromClipboardBtn->setClickedCallback(onPasteFromClipboard, (void*)this); + mPasteFromClipboardBtn->setMouseEnterCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseEnter, this, _1, _2, TOOLTIP_COPY_FROM_CLIPBOARD)); + mPasteFromClipboardBtn->setMouseLeaveCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseLeave, this, _1, _2, TOOLTIP_COPY_FROM_CLIPBOARD)); + + mRemoveImageBtn = getChild("remove_image"); + mRemoveImageBtn->setClickedCallback(onRemove, (void*)this); + mRemoveImageBtn->setMouseEnterCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseEnter, this, _1, _2, TOOLTIP_REMOVE)); + mRemoveImageBtn->setMouseLeaveCallback(boost::bind(&LLFloaterChangeItemThumbnail::onButtonMouseLeave, this, _1, _2, TOOLTIP_REMOVE)); + + return LLFloater::postBuild(); +} + +void LLFloaterChangeItemThumbnail::onOpen(const LLSD& key) +{ + if (!key.has("item_id") && !key.isUUID()) + { + closeFloater(); + } + + if (key.isUUID()) + { + mItemId = key.asUUID(); + } + else + { + mItemId = key["item_id"].asUUID(); + mTaskId = key["task_id"].asUUID(); + } + + refreshFromInventory(); +} + +void LLFloaterChangeItemThumbnail::onFocusReceived() +{ + mPasteFromClipboardBtn->setEnabled(LLClipboard::instance().hasContents()); +} + +void LLFloaterChangeItemThumbnail::onMouseEnter(S32 x, S32 y, MASK mask) +{ + mPasteFromClipboardBtn->setEnabled(LLClipboard::instance().hasContents()); +} + +BOOL LLFloaterChangeItemThumbnail::handleDragAndDrop( + S32 x, + S32 y, + MASK mask, + BOOL drop, + EDragAndDropType cargo_type, + void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg) +{ + if (cargo_type == DAD_TEXTURE) + { + LLInventoryItem *item = (LLInventoryItem *)cargo_data; + if (item->getAssetUUID().notNull()) + { + if (drop) + { + assignAndValidateAsset(item->getAssetUUID()); + } + + *accept = ACCEPT_YES_SINGLE; + } + else + { + *accept = ACCEPT_NO; + } + } + else + { + *accept = ACCEPT_NO; + } + + LL_DEBUGS("UserInput") << "dragAndDrop handled by LLFloaterChangeItemThumbnail " << getKey() << LL_ENDL; + + return TRUE; +} + +void LLFloaterChangeItemThumbnail::changed(U32 mask) +{ + //LLInventoryObserver + + if (mTaskId.notNull() || mItemId.isNull()) + { + // Task inventory or not set up yet + return; + } + + const std::set& mChangedItemIDs = gInventory.getChangedIDs(); + std::set::const_iterator it; + + for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) + { + // set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288) + if (*it == mItemId) + { + // if there's a change we're interested in. + if ((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0) + { + refreshFromInventory(); + } + } + } +} + +void LLFloaterChangeItemThumbnail::inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data) +{ + //LLVOInventoryListener + refreshFromInventory(); +} + +LLInventoryObject* LLFloaterChangeItemThumbnail::getInventoryObject() +{ + LLInventoryObject* obj = NULL; + if (mTaskId.isNull()) + { + // it is in agent inventory + if (!mObserverInitialized) + { + gInventory.addObserver(this); + mObserverInitialized = true; + } + + obj = gInventory.getObject(mItemId); + } + else + { + LLViewerObject* object = gObjectList.findObject(mTaskId); + if (object) + { + if (!mObserverInitialized) + { + registerVOInventoryListener(object, NULL); + mObserverInitialized = false; + } + + obj = object->getInventoryObject(mItemId); + } + } + return obj; +} + +void LLFloaterChangeItemThumbnail::refreshFromInventory() +{ + LLInventoryObject* obj = getInventoryObject(); + if (!obj) + { + closeFloater(); + } + + if (obj) + { + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + bool in_trash = gInventory.isObjectDescendentOf(obj->getUUID(), trash_id); + if (in_trash && obj->getUUID() != trash_id) + { + // Close properties when moving to trash + // Aren't supposed to view properties from trash + closeFloater(); + } + else + { + refreshFromObject(obj); + } + } + else + { + closeFloater(); + } +} + +class LLIsOutfitTextureType : public LLInventoryCollectFunctor +{ +public: + LLIsOutfitTextureType() {} + virtual ~LLIsOutfitTextureType() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + +bool LLIsOutfitTextureType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + return item && (item->getType() == LLAssetType::AT_TEXTURE); +} + +void LLFloaterChangeItemThumbnail::refreshFromObject(LLInventoryObject* obj) +{ + LLUIImagePtr icon_img; + LLUUID thumbnail_id = obj->getThumbnailUUID(); + + LLViewerInventoryItem* item = dynamic_cast(obj); + if (item) + { + setTitle(getString("title_item_thumbnail")); + + icon_img = LLInventoryIcon::getIcon(item->getType(), item->getInventoryType(), item->getFlags(), FALSE); + mRemoveImageBtn->setEnabled(thumbnail_id.notNull() && ((item->getActualType() != LLAssetType::AT_TEXTURE) || (item->getAssetUUID() != thumbnail_id))); + } + else + { + LLViewerInventoryCategory* cat = dynamic_cast(obj); + + if (cat) + { + setTitle(getString("title_folder_thumbnail")); + icon_img = LLUI::getUIImage(LLViewerFolderType::lookupIconName(cat->getPreferredType(), true)); + + if (thumbnail_id.isNull() && (cat->getPreferredType() == LLFolderType::FT_OUTFIT)) + { + // Legacy support, check if there is an image inside + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + // Not LLIsOfAssetType, because we allow links + LLIsOutfitTextureType f; + gInventory.getDirectDescendentsOf(mItemId, cats, items, f); + + if (1 == items.size()) + { + LLViewerInventoryItem* item = items.front(); + if (item && item->getIsLinkType()) + { + item = item->getLinkedItem(); + } + if (item) + { + thumbnail_id = item->getAssetUUID(); + if (thumbnail_id.notNull()) + { + // per SL-19188, set this image as a thumbnail + LL_INFOS() << "Setting image " << thumbnail_id + << " from outfit as a thumbnail for inventory object " << obj->getUUID() + << LL_ENDL; + assignAndValidateAsset(thumbnail_id, true); + } + } + } + } + + mRemoveImageBtn->setEnabled(thumbnail_id.notNull()); + } + } + mItemTypeIcon->setImage(icon_img); + mItemNameText->setValue(obj->getName()); + + mThumbnailCtrl->setValue(thumbnail_id); + + mCopyToClipboardBtn->setEnabled(thumbnail_id.notNull()); + mPasteFromClipboardBtn->setEnabled(LLClipboard::instance().hasContents()); + + // todo: some elements might not support setting thumbnails + // since they already have them + // It is unclear how system folders should function +} + +void LLFloaterChangeItemThumbnail::onUploadLocal(void *userdata) +{ + LLFloaterChangeItemThumbnail *self = (LLFloaterChangeItemThumbnail*)userdata; + + (new LLThumbnailImagePicker(self->mItemId, self->mTaskId))->getFile(); + + LLFloater* floaterp = self->mPickerHandle.get(); + if (floaterp) + { + floaterp->closeFloater(); + } + floaterp = self->mSnapshotHandle.get(); + if (floaterp) + { + floaterp->closeFloater(); + } +} + +void LLFloaterChangeItemThumbnail::onUploadSnapshot(void *userdata) +{ + LLFloaterChangeItemThumbnail *self = (LLFloaterChangeItemThumbnail*)userdata; + + LLFloater* floaterp = self->mSnapshotHandle.get(); + // Show the dialog + if (floaterp) + { + floaterp->openFloater(); + } + else + { + LLSD key; + key["item_id"] = self->mItemId; + key["task_id"] = self->mTaskId; + LLFloaterSimpleSnapshot* snapshot_floater = (LLFloaterSimpleSnapshot*)LLFloaterReg::showInstance("simple_snapshot", key, true); + if (snapshot_floater) + { + self->addDependentFloater(snapshot_floater); + self->mSnapshotHandle = snapshot_floater->getHandle(); + snapshot_floater->setOwner(self); + } + } + + floaterp = self->mPickerHandle.get(); + if (floaterp) + { + floaterp->closeFloater(); + } +} + +void LLFloaterChangeItemThumbnail::onUseTexture(void *userdata) +{ + LLFloaterChangeItemThumbnail *self = (LLFloaterChangeItemThumbnail*)userdata; + LLInventoryObject* obj = self->getInventoryObject(); + if (obj) + { + self->showTexturePicker(obj->getThumbnailUUID()); + } + + LLFloater* floaterp = self->mSnapshotHandle.get(); + if (floaterp) + { + floaterp->closeFloater(); + } +} + +void LLFloaterChangeItemThumbnail::onCopyToClipboard(void *userdata) +{ + LLFloaterChangeItemThumbnail *self = (LLFloaterChangeItemThumbnail*)userdata; + LLInventoryObject* obj = self->getInventoryObject(); + if (obj) + { + LLClipboard::instance().reset(); + LLClipboard::instance().addToClipboard(obj->getThumbnailUUID(), LLAssetType::AT_NONE); + self->mPasteFromClipboardBtn->setEnabled(true); + } +} + +void LLFloaterChangeItemThumbnail::onPasteFromClipboard(void *userdata) +{ + LLFloaterChangeItemThumbnail *self = (LLFloaterChangeItemThumbnail*)userdata; + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + if (objects.size() > 0) + { + LLUUID potential_uuid = objects[0]; + LLUUID asset_id; + + if (potential_uuid.notNull()) + { + LLViewerInventoryItem* item = gInventory.getItem(potential_uuid); + if (item) + { + // no point checking snapshot? + if (item->getType() == LLAssetType::AT_TEXTURE) + { + bool copy = item->getPermissions().allowCopyBy(gAgent.getID()); + bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); + + if (copy && xfer) + { + asset_id = item->getAssetUUID(); + } + else + { + LLNotificationsUtil::add("ThumbnailInsufficientPermissions"); + return; + } + } + } + else + { + // assume that this is a texture + asset_id = potential_uuid; + } + } + + LLInventoryObject* obj = self->getInventoryObject(); + if (obj && obj->getThumbnailUUID() == asset_id) + { + // nothing to do + return; + } + if (asset_id.notNull()) + { + self->assignAndValidateAsset(asset_id); + } + // else show 'buffer has no texture' warning? + } +} + +void LLFloaterChangeItemThumbnail::onRemove(void *userdata) +{ + LLFloaterChangeItemThumbnail *self = (LLFloaterChangeItemThumbnail*)userdata; + + LLSD payload; + payload["item_id"] = self->mItemId; + payload["object_id"] = self->mTaskId; + LLNotificationsUtil::add("DeleteThumbnail", LLSD(), payload, boost::bind(&LLFloaterChangeItemThumbnail::onRemovalConfirmation, _1, _2, self->getHandle())); +} + +// static +void LLFloaterChangeItemThumbnail::onRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle handle) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0 && !handle.isDead() && !handle.get()->isDead()) + { + LLFloaterChangeItemThumbnail* self = (LLFloaterChangeItemThumbnail*)handle.get(); + self->setThumbnailId(LLUUID::null); + } +} + +struct ImageLoadedData +{ + LLUUID mThumbnailId; + LLUUID mObjectId; + LLHandle mFloaterHandle; + bool mSilent; + // Keep image reference to prevent deletion on timeout + LLPointer mTexturep; +}; + +void LLFloaterChangeItemThumbnail::assignAndValidateAsset(const LLUUID &asset_id, bool silent) +{ + LLPointer texturep = LLViewerTextureManager::getFetchedTexture(asset_id); + if (texturep->isMissingAsset()) + { + LL_WARNS() << "Attempted to assign missing asset " << asset_id << LL_ENDL; + if (!silent) + { + LLNotificationsUtil::add("ThumbnailDimentionsLimit"); + } + } + else if (texturep->getFullWidth() == 0) + { + if (silent) + { + mExpectingAssetId = LLUUID::null; + } + else + { + // don't warn user multiple times if some textures took their time + mExpectingAssetId = asset_id; + } + ImageLoadedData *data = new ImageLoadedData(); + data->mObjectId = mItemId; + data->mThumbnailId = asset_id; + data->mFloaterHandle = getHandle(); + data->mSilent = silent; + data->mTexturep = texturep; + + texturep->setLoadedCallback(onImageDataLoaded, + MAX_DISCARD_LEVEL, // Don't need full image, just size data + FALSE, + FALSE, + (void*)data, + NULL, + FALSE); + } + else + { + if (validateAsset(asset_id)) + { + setThumbnailId(asset_id); + } + else if (!silent) + { + LLNotificationsUtil::add("ThumbnailDimentionsLimit"); + } + } +} +bool LLFloaterChangeItemThumbnail::validateAsset(const LLUUID &asset_id) +{ + if (asset_id.isNull()) + { + return false; + } + + LLPointer texturep = LLViewerTextureManager::findFetchedTexture(asset_id, TEX_LIST_STANDARD); + + if (!texturep) + { + return false; + } + + if (texturep->isMissingAsset()) + { + return false; + } + + if (texturep->getFullWidth() != texturep->getFullHeight()) + { + return false; + } + + if (texturep->getFullWidth() > LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MAX + || texturep->getFullHeight() > LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MAX) + { + return false; + } + + if (texturep->getFullWidth() < LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN + || texturep->getFullHeight() < LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN) + { + return false; + } + return true; +} + +//static +void LLFloaterChangeItemThumbnail::onImageDataLoaded( + BOOL success, + LLViewerFetchedTexture *src_vi, + LLImageRaw* src, + LLImageRaw* aux_src, + S32 discard_level, + BOOL final, + void* userdata) +{ + if (!userdata) return; + + if (!final && success) return; //not done yet + + ImageLoadedData* data = (ImageLoadedData*)userdata; + + if (success) + { + // Update the item, set it even if floater is dead + if (validateAsset(data->mThumbnailId)) + { + setThumbnailId(data->mThumbnailId, data->mObjectId); + } + else if (!data->mSilent) + { + // Should this only appear if floater is alive? + LLNotificationsUtil::add("ThumbnailDimentionsLimit"); + } + } + + // Update floater + if (!data->mSilent && !data->mFloaterHandle.isDead()) + { + LLFloaterChangeItemThumbnail* self = static_cast(data->mFloaterHandle.get()); + if (self && self->mExpectingAssetId == data->mThumbnailId) + { + self->mExpectingAssetId = LLUUID::null; + } + } + + delete data; +} + +//static +void LLFloaterChangeItemThumbnail::onFullImageLoaded( + BOOL success, + LLViewerFetchedTexture* src_vi, + LLImageRaw* src, + LLImageRaw* aux_src, + S32 discard_level, + BOOL final, + void* userdata) +{ + if (!userdata) return; + + if (!final && success) return; //not done yet + + ImageLoadedData* data = (ImageLoadedData*)userdata; + + if (success) + { + if (src_vi->getFullWidth() != src_vi->getFullHeight() + || src_vi->getFullWidth() < LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN) + { + if (!data->mSilent) + { + LLNotificationsUtil::add("ThumbnailDimentionsLimit"); + } + } + else if (src_vi->getFullWidth() > LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MAX) + { + LLFloaterSimpleSnapshot::uploadThumbnail(src, data->mObjectId, LLUUID::null); + } + else + { + setThumbnailId(data->mThumbnailId, data->mObjectId); + } + } + + delete data; +} + +void LLFloaterChangeItemThumbnail::showTexturePicker(const LLUUID &thumbnail_id) +{ + // show hourglass cursor when loading inventory window + getWindow()->setCursor(UI_CURSOR_WAIT); + + LLFloater* floaterp = mPickerHandle.get(); + // Show the dialog + if (floaterp) + { + floaterp->openFloater(); + } + else + { + floaterp = new LLFloaterTexturePicker( + this, + thumbnail_id, + thumbnail_id, + thumbnail_id, + FALSE, + TRUE, + LLTrans::getString("TexturePickerOutfitHeader"), // "SELECT PHOTO", // Localizable floater header + PERM_NONE, + PERM_NONE, + PERM_NONE, + FALSE, + NULL); + + mPickerHandle = floaterp->getHandle(); + + LLFloaterTexturePicker* texture_floaterp = dynamic_cast(floaterp); + if (texture_floaterp) + { + //texture_floaterp->setTextureSelectedCallback(); + //texture_floaterp->setOnUpdateImageStatsCallback(); + texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLPickerSource, const LLUUID&, const LLUUID&) + { + if (op == LLTextureCtrl::TEXTURE_SELECT) + { + onTexturePickerCommit(); + } + } + ); + + texture_floaterp->setLocalTextureEnabled(FALSE); + texture_floaterp->setBakeTextureEnabled(FALSE); + texture_floaterp->setCanApplyImmediately(false); + texture_floaterp->setCanApply(false, true, false /*Hide 'preview disabled'*/); + texture_floaterp->setMinDimentionsLimits(LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN); + + addDependentFloater(texture_floaterp); + } + + floaterp->openFloater(); + } + floaterp->setFocus(TRUE); +} + +void LLFloaterChangeItemThumbnail::onTexturePickerCommit() +{ + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mPickerHandle.get(); + + if (floaterp) + { + LLUUID asset_id = floaterp->getAssetID(); + + if (asset_id.isNull()) + { + setThumbnailId(asset_id); + return; + } + + LLInventoryObject* obj = getInventoryObject(); + if (obj && obj->getThumbnailUUID() == asset_id) + { + // nothing to do + return; + } + + LLPointer texturep = LLViewerTextureManager::findFetchedTexture(asset_id, TEX_LIST_STANDARD); + if (!texturep) + { + LL_WARNS() << "Image " << asset_id << " doesn't exist" << LL_ENDL; + return; + } + + if (texturep->isMissingAsset()) + { + LL_WARNS() << "Image " << asset_id << " is missing" << LL_ENDL; + return; + } + + if (texturep->getFullWidth() != texturep->getFullHeight()) + { + LLNotificationsUtil::add("ThumbnailDimentionsLimit"); + return; + } + + if (texturep->getFullWidth() < LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN + && texturep->getFullWidth() > 0) + { + LLNotificationsUtil::add("ThumbnailDimentionsLimit"); + return; + } + + if (texturep->getFullWidth() > LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MAX + || texturep->getFullWidth() == 0) + { + if (texturep->isFullyLoaded() + && (texturep->getCachedRawImageLevel() == 0 || texturep->getRawImageLevel() == 0) + && (texturep->isCachedRawImageReady() || texturep->isRawImageValid())) + { + if (texturep->isRawImageValid()) + { + LLFloaterSimpleSnapshot::uploadThumbnail(texturep->getRawImage(), mItemId, mTaskId); + } + else + { + LLFloaterSimpleSnapshot::uploadThumbnail(texturep->getCachedRawImage(), mItemId, mTaskId); + } + } + else + { + ImageLoadedData* data = new ImageLoadedData(); + data->mObjectId = mItemId; + data->mThumbnailId = asset_id; + data->mFloaterHandle = getHandle(); + data->mSilent = false; + data->mTexturep = texturep; + + texturep->setBoostLevel(LLGLTexture::BOOST_PREVIEW); + texturep->setMinDiscardLevel(0); + texturep->setLoadedCallback(onFullImageLoaded, + 0, // Need best quality + TRUE, + FALSE, + (void*)data, + NULL, + FALSE); + texturep->forceToSaveRawImage(0); + } + return; + } + + setThumbnailId(asset_id); + } +} + + +void LLFloaterChangeItemThumbnail::setThumbnailId(const LLUUID &new_thumbnail_id) +{ + LLInventoryObject* obj = getInventoryObject(); + if (!obj) + { + return; + } + + if (mTaskId.notNull()) + { + LL_ERRS() << "Not implemented yet" << LL_ENDL; + return; + } + + setThumbnailId(new_thumbnail_id, mItemId, obj); +} + +void LLFloaterChangeItemThumbnail::setThumbnailId(const LLUUID& new_thumbnail_id, const LLUUID& object_id) +{ + LLInventoryObject* obj = gInventory.getObject(object_id); + if (!obj) + { + return; + } + + setThumbnailId(new_thumbnail_id, object_id, obj); +} +void LLFloaterChangeItemThumbnail::setThumbnailId(const LLUUID& new_thumbnail_id, const LLUUID& object_id, LLInventoryObject* obj) +{ + if (obj->getThumbnailUUID() != new_thumbnail_id) + { + LLSD updates; + if (new_thumbnail_id.notNull()) + { + // At the moment server expects id as a string + updates["thumbnail"] = LLSD().with("asset_id", new_thumbnail_id.asString()); + } + else + { + // No thumbnail isntead of 'null id thumbnail' + updates["thumbnail"] = LLSD(); + } + LLViewerInventoryCategory* view_folder = dynamic_cast(obj); + if (view_folder) + { + update_inventory_category(object_id, updates, NULL); + } + LLViewerInventoryItem* view_item = dynamic_cast(obj); + if (view_item) + { + update_inventory_item(object_id, updates, NULL); + } + } +} + +void LLFloaterChangeItemThumbnail::onButtonMouseEnter(LLUICtrl* button, const LLSD& param, EToolTipState state) +{ + mTooltipState = state; + + std::string tooltip_text; + std::string tooltip_name = "tooltip_" + button->getName(); + if (hasString(tooltip_name)) + { + tooltip_text = getString(tooltip_name); + } + + mToolTipTextBox->setValue(tooltip_text); +} + +void LLFloaterChangeItemThumbnail::onButtonMouseLeave(LLUICtrl* button, const LLSD& param, EToolTipState state) +{ + if (mTooltipState == state) + { + mTooltipState = TOOLTIP_NONE; + LLSD tooltip_text; + mToolTipTextBox->setValue(tooltip_text); + } +} + diff --git a/indra/newview/llfloaterchangeitemthumbnail.h b/indra/newview/llfloaterchangeitemthumbnail.h new file mode 100644 index 0000000000..a91e9b8ee9 --- /dev/null +++ b/indra/newview/llfloaterchangeitemthumbnail.h @@ -0,0 +1,139 @@ +/** + * @file llfloaterchangeitemthumbnail.h + * @brief LLFloaterChangeItemThumbnail class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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_LLFLOATERCHANGEITEMTHUMBNAIL_H +#define LL_LLFLOATERCHANGEITEMTHUMBNAIL_H + +#include "llfloater.h" +#include "llinventoryobserver.h" +#include "llvoinventorylistener.h" + +class LLButton; +class LLIconCtrl; +class LLTextBox; +class LLThumbnailCtrl; +class LLUICtrl; +class LLViewerInventoryItem; +class LLViewerFetchedTexture; + +class LLFloaterChangeItemThumbnail : public LLFloater, public LLInventoryObserver, public LLVOInventoryListener +{ +public: + LLFloaterChangeItemThumbnail(const LLSD& key); + ~LLFloaterChangeItemThumbnail(); + + BOOL postBuild() override; + void onOpen(const LLSD& key) override; + void onFocusReceived() override; + void onMouseEnter(S32 x, S32 y, MASK mask) override; + + BOOL handleDragAndDrop( + S32 x, + S32 y, + MASK mask, + BOOL drop, + EDragAndDropType cargo_type, + void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg) override; + + void changed(U32 mask) override; + void inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data) override; + + static bool validateAsset(const LLUUID &asset_id); + +private: + + LLInventoryObject* getInventoryObject(); + void refreshFromInventory(); + void refreshFromObject(LLInventoryObject* obj); + + static void onUploadLocal(void*); + static void onUploadSnapshot(void*); + static void onUseTexture(void*); + static void onCopyToClipboard(void*); + static void onPasteFromClipboard(void*); + static void onRemove(void*); + static void onRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle handle); + + void assignAndValidateAsset(const LLUUID &asset_id, bool silent = false); + static void onImageDataLoaded(BOOL success, + LLViewerFetchedTexture *src_vi, + LLImageRaw* src, + LLImageRaw* aux_src, + S32 discard_level, + BOOL final, + void* userdata); + static void onFullImageLoaded(BOOL success, + LLViewerFetchedTexture* src_vi, + LLImageRaw* src, + LLImageRaw* aux_src, + S32 discard_level, + BOOL final, + void* userdata); + + void showTexturePicker(const LLUUID &thumbnail_id); + void onTexturePickerCommit(); + + void setThumbnailId(const LLUUID &new_thumbnail_id); + static void setThumbnailId(const LLUUID& new_thumbnail_id, const LLUUID& object_id); + static void setThumbnailId(const LLUUID& new_thumbnail_id, const LLUUID& object_id, LLInventoryObject* obj); + + enum EToolTipState + { + TOOLTIP_NONE, + TOOLTIP_UPLOAD_LOCAL, + TOOLTIP_UPLOAD_SNAPSHOT, + TOOLTIP_USE_TEXTURE, + TOOLTIP_COPY_TO_CLIPBOARD, + TOOLTIP_COPY_FROM_CLIPBOARD, + TOOLTIP_REMOVE, + }; + + void onButtonMouseEnter(LLUICtrl* button, const LLSD& param, EToolTipState state); + void onButtonMouseLeave(LLUICtrl* button, const LLSD& param, EToolTipState state); + + bool mObserverInitialized; + EToolTipState mTooltipState; + LLUUID mItemId; + LLUUID mTaskId; + LLUUID mExpectingAssetId; + + LLIconCtrl *mItemTypeIcon; + LLUICtrl *mItemNameText; + LLThumbnailCtrl *mThumbnailCtrl; + LLTextBox *mToolTipTextBox; + LLButton *mCopyToClipboardBtn; + LLButton *mPasteFromClipboardBtn; + LLButton *mRemoveImageBtn; + + LLHandle mPickerHandle; + LLHandle mSnapshotHandle; +}; +#endif // LL_LLFLOATERCHANGEITEMTHUMBNAIL_H diff --git a/indra/newview/llfloatereditenvironmentbase.cpp b/indra/newview/llfloatereditenvironmentbase.cpp index 2850951668..cd24d79b7f 100644 --- a/indra/newview/llfloatereditenvironmentbase.cpp +++ b/indra/newview/llfloatereditenvironmentbase.cpp @@ -260,7 +260,7 @@ void LLFloaterEditEnvironmentBase::onSaveAsCommit(const LLSD& notification, cons } else if (mInventoryItem) { - const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); LLUUID parent_id = mInventoryItem->getParentUUID(); if (marketplacelistings_id == parent_id || gInventory.isObjectDescendentOf(mInventoryItem->getUUID(), gInventory.getLibraryRootFolderID())) { diff --git a/indra/newview/llfloaterforgetuser.cpp b/indra/newview/llfloaterforgetuser.cpp index 4e955c7e02..b71fbb729c 100644 --- a/indra/newview/llfloaterforgetuser.cpp +++ b/indra/newview/llfloaterforgetuser.cpp @@ -164,6 +164,12 @@ bool LLFloaterForgetUser::onConfirmLogout(const LLSD& notification, const LLSD& if (option == 0) { // Remove creds + std::string grid_id = LLGridManager::getInstance()->getGridId(grid); + if (grid_id.empty()) + { + grid_id = grid; + } + gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid_id, LLStartUp::getUserId()); // doesn't write gSecAPIHandler->removeFromCredentialMap("login_list", grid, LLStartUp::getUserId()); LLPointer cred = gSecAPIHandler->loadCredential(grid); @@ -228,7 +234,13 @@ void LLFloaterForgetUser::processForgetUser() void LLFloaterForgetUser::forgetUser(const std::string &userid, const std::string &fav_id, const std::string &grid, bool delete_data) { // Remove creds - gSecAPIHandler->removeFromCredentialMap("login_list", grid, userid); + std::string grid_id = LLGridManager::getInstance()->getGridId(grid); + if (grid_id.empty()) + { + grid_id = grid; + } + gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid_id, userid); // doesn't write + gSecAPIHandler->removeFromCredentialMap("login_list", grid, userid); // write operation LLPointer cred = gSecAPIHandler->loadCredential(grid); if (cred.notNull() && cred->userID() == userid) diff --git a/indra/newview/llfloatergesture.cpp b/indra/newview/llfloatergesture.cpp index 114b937a44..0377912208 100644 --- a/indra/newview/llfloatergesture.cpp +++ b/indra/newview/llfloatergesture.cpp @@ -214,7 +214,7 @@ BOOL LLFloaterGesture::postBuild() getChildView("play_btn")->setVisible( true); getChildView("stop_btn")->setVisible( false); setDefaultBtn("play_btn"); - mGestureFolderID = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE, false); + mGestureFolderID = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); uuid_vec_t folders; folders.push_back(mGestureFolderID); diff --git a/indra/newview/llfloaterinventorysettings.cpp b/indra/newview/llfloaterinventorysettings.cpp new file mode 100644 index 0000000000..29d6e90a33 --- /dev/null +++ b/indra/newview/llfloaterinventorysettings.cpp @@ -0,0 +1,44 @@ +/** + * @file llfloaterinventorysettings.cpp + * @brief LLFloaterInventorySettings class implementation + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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 "llfloaterinventorysettings.h" + +LLFloaterInventorySettings::LLFloaterInventorySettings(const LLSD& key) + : LLFloater(key) +{ +} + +LLFloaterInventorySettings::~LLFloaterInventorySettings() +{} + +BOOL LLFloaterInventorySettings::postBuild() +{ + getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloater::closeFloater, this, false)); + return TRUE; +} + diff --git a/indra/newview/llfloaterinventorysettings.h b/indra/newview/llfloaterinventorysettings.h new file mode 100644 index 0000000000..50304276c7 --- /dev/null +++ b/indra/newview/llfloaterinventorysettings.h @@ -0,0 +1,45 @@ +/** + * @file llfloaterinventorysettings.h + * @brief LLFloaterInventorySettings class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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_LLFLOATERINVENTORYSETTINGS_H +#define LL_LLFLOATERINVENTORYSETTINGS_H + +#include "llfloater.h" + +class LLFloaterInventorySettings + : public LLFloater +{ + friend class LLFloaterReg; + +public: + virtual BOOL postBuild(); + +private: + LLFloaterInventorySettings(const LLSD& key); + ~LLFloaterInventorySettings(); +}; + +#endif diff --git a/indra/newview/llfloaterlinkreplace.cpp b/indra/newview/llfloaterlinkreplace.cpp index 16c91370f0..c0243ece37 100644 --- a/indra/newview/llfloaterlinkreplace.cpp +++ b/indra/newview/llfloaterlinkreplace.cpp @@ -381,8 +381,8 @@ BOOL LLFloaterLinkReplace::tick() void LLFloaterLinkReplace::processBatch(LLInventoryModel::item_array_t items) { const LLViewerInventoryItem* target_item = gInventory.getItem(mTargetUUID); - const LLUUID cof_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); - const LLUUID outfit_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); + const LLUUID cof_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID outfit_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) { diff --git a/indra/newview/llfloatermarketplacelistings.cpp b/indra/newview/llfloatermarketplacelistings.cpp index 86af9b1e5d..be54cc5f3c 100644 --- a/indra/newview/llfloatermarketplacelistings.cpp +++ b/indra/newview/llfloatermarketplacelistings.cpp @@ -41,8 +41,11 @@ #include "llnotificationmanager.h" #include "llnotificationsutil.h" #include "llsidepaneliteminfo.h" +#include "llsidepaneltaskinfo.h" +#include "lltabcontainer.h" #include "lltextbox.h" #include "lltrans.h" +#include "llviewerwindow.h" #include "fscommon.h" ///---------------------------------------------------------------------------- @@ -228,18 +231,31 @@ void LLPanelMarketplaceListings::onTabChange() void LLPanelMarketplaceListings::onAddButtonClicked() { - // Find active panel - LLInventoryPanel* panel = (LLInventoryPanel*)getChild("marketplace_filter_tabs")->getCurrentPanel(); - if (panel) - { - LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); - llassert(marketplacelistings_id.notNull()); - LLFolderType::EType preferred_type = LLFolderType::lookup("category"); - LLUUID category = gInventory.createNewCategory(marketplacelistings_id, preferred_type, LLStringUtil::null); - gInventory.notifyObservers(); - panel->setSelectionByID(category, TRUE); - panel->getRootFolder()->setNeedsAutoRename(TRUE); + LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + llassert(marketplacelistings_id.notNull()); + LLFolderType::EType preferred_type = LLFolderType::lookup("category"); + LLHandle handle = getHandle(); + gInventory.createNewCategory( + marketplacelistings_id, + preferred_type, + LLStringUtil::null, + [handle](const LLUUID &new_cat_id) + { + // Find active panel + LLPanel *marketplace_panel = handle.get(); + if (!marketplace_panel) + { + return; + } + LLInventoryPanel* panel = (LLInventoryPanel*)marketplace_panel->getChild("marketplace_filter_tabs")->getCurrentPanel(); + if (panel) + { + gInventory.notifyObservers(); + panel->setSelectionByID(new_cat_id, TRUE); + panel->getRootFolder()->setNeedsAutoRename(TRUE); + } } + ); } void LLPanelMarketplaceListings::onAuditButtonClicked() @@ -360,6 +376,7 @@ LLFloaterMarketplaceListings::LLFloaterMarketplaceListings(const LLSD& key) , mInventoryTitle(NULL) , mPanelListings(NULL) , mPanelListingsSet(false) +, mRootFolderCreating(false) { } @@ -432,7 +449,7 @@ void LLFloaterMarketplaceListings::fetchContents() { LLMarketplaceData::instance().setDataFetchedSignal(boost::bind(&LLFloaterMarketplaceListings::updateView, this)); LLMarketplaceData::instance().setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_LOADING); - LLInventoryModelBackgroundFetch::instance().start(mRootFolderId); + LLInventoryModelBackgroundFetch::instance().start(mRootFolderId, true); LLMarketplaceData::instance().getSLMListings(); } } @@ -445,15 +462,50 @@ void LLFloaterMarketplaceListings::setRootFolder() // If we are *not* a merchant or we have no market place connection established yet, do nothing return; } + if (!gInventory.isInventoryUsable()) + { + return; + } + LLFolderType::EType preferred_type = LLFolderType::FT_MARKETPLACE_LISTINGS; // We are a merchant. Get the Marketplace listings folder, create it if needs be. - LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, true); - if (marketplacelistings_id.isNull()) - { - // We should never get there unless the inventory fails badly - LL_ERRS("SLM") << "Inventory problem: failure to create the marketplace listings folder for a merchant!" << LL_ENDL; - return; - } + LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(preferred_type); + + if (marketplacelistings_id.isNull()) + { + if (!mRootFolderCreating) + { + mRootFolderCreating = true; + gInventory.createNewCategory( + gInventory.getRootFolderID(), + preferred_type, + LLStringUtil::null, + [](const LLUUID &new_cat_id) + { + LLFloaterMarketplaceListings* marketplace = LLFloaterReg::findTypedInstance("marketplace_listings"); + if (marketplace) + { + if (new_cat_id.notNull()) + { + // will call setRootFolder again + marketplace->updateView(); + } + // don't update in case of failure, createNewCategory can return + // immediately if cap is missing and will cause a loop + else + { + // unblock + marketplace->mRootFolderCreating = false; + LL_WARNS("SLM") << "Inventory warning: Failed to create marketplace listings folder for a merchant" << LL_ENDL; + } + } + } + ); + } + return; + } + + mRootFolderCreating = false; // No longer need to observe new category creation if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver)) @@ -541,6 +593,11 @@ void LLFloaterMarketplaceListings::updateView() { setRootFolder(); } + if (mRootFolderCreating) + { + // waiting for callback + return; + } // Update the bottom initializing status and progress dial if we are initializing or if we're a merchant and still loading if ((mkt_status <= MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING) || (is_merchant && (data_fetched <= MarketplaceFetchCodes::MARKET_FETCH_LOADING)) ) @@ -857,14 +914,17 @@ void LLFloaterMarketplaceValidation::onOpen(const LLSD& key) LLUUID cat_id(key.asUUID()); if (cat_id.isNull()) { - cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); } // Validates the folder if (cat_id.notNull()) { - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); - validate_marketplacelistings(cat, boost::bind(&LLFloaterMarketplaceValidation::appendMessage, this, _1, _2, _3), false); + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + cat_id, + NULL, + boost::bind(&LLFloaterMarketplaceValidation::appendMessage, this, _1, _2, _3), + false); } // Handle the listing folder being processed @@ -968,18 +1028,46 @@ LLFloaterItemProperties::~LLFloaterItemProperties() BOOL LLFloaterItemProperties::postBuild() { - // On the standalone properties floater, we have no need for a back button... - LLSidepanelItemInfo* panel = getChild("item_panel"); - LLButton* back_btn = panel->getChild("back_btn"); - back_btn->setVisible(FALSE); - return LLFloater::postBuild(); } void LLFloaterItemProperties::onOpen(const LLSD& key) { // Tell the panel which item it needs to visualize - LLSidepanelItemInfo* panel = getChild("item_panel"); - panel->setItemID(key["id"].asUUID()); + LLPanel* panel = findChild("sidepanel"); + + LLSidepanelItemInfo* item_panel = dynamic_cast(panel); + if (item_panel) + { + item_panel->setItemID(key["id"].asUUID()); + if (key.has("object")) + { + item_panel->setObjectID(key["object"].asUUID()); + } + item_panel->setParentFloater(this); + } + + LLSidepanelTaskInfo* task_panel = dynamic_cast(panel); + if (task_panel) + { + task_panel->setObjectSelection(LLSelectMgr::getInstance()->getSelection()); + } } +LLMultiItemProperties::LLMultiItemProperties(const LLSD& key) + : LLMultiFloater(LLSD()) +{ + // start with a small rect in the top-left corner ; will get resized + LLRect rect; + rect.setLeftTopAndSize(0, gViewerWindow->getWindowHeightScaled(), 350, 350); + setRect(rect); + LLFloater* last_floater = LLFloaterReg::getLastFloaterInGroup(key.asString()); + if (last_floater) + { + stackWith(*last_floater); + } + setTitle(LLTrans::getString("MultiPropertiesTitle")); + buildTabContainer(); + // Center multifloater on screen + center(); +} diff --git a/indra/newview/llfloatermarketplacelistings.h b/indra/newview/llfloatermarketplacelistings.h index f7a738822b..15a1d13743 100644 --- a/indra/newview/llfloatermarketplacelistings.h +++ b/indra/newview/llfloatermarketplacelistings.h @@ -33,6 +33,7 @@ #include "llinventorypanel.h" #include "llnotificationptr.h" #include "llmodaldialog.h" +#include "llmultifloater.h" #include "lltexteditor.h" class LLInventoryCategoriesObserver; @@ -143,6 +144,7 @@ private: LLTextBox * mInventoryTitle; LLUUID mRootFolderId; + bool mRootFolderCreating; LLPanelMarketplaceListings * mPanelListings; bool mPanelListingsSet; }; @@ -227,4 +229,10 @@ public: private: }; +class LLMultiItemProperties : public LLMultiFloater +{ +public: + LLMultiItemProperties(const LLSD& key); +}; + #endif // LL_LLFLOATERMARKETPLACELISTINGS_H diff --git a/indra/newview/llfloaternewfeaturenotification.cpp b/indra/newview/llfloaternewfeaturenotification.cpp new file mode 100644 index 0000000000..3a2035b9b9 --- /dev/null +++ b/indra/newview/llfloaternewfeaturenotification.cpp @@ -0,0 +1,76 @@ +/** + * @file llfloaternewfeaturenotification.cpp + * @brief LLFloaterNewFeatureNotification class implementation + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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 "llfloaternewfeaturenotification.h" + + +LLFloaterNewFeatureNotification::LLFloaterNewFeatureNotification(const LLSD& key) + : LLFloater(key) +{ +} + +LLFloaterNewFeatureNotification::~LLFloaterNewFeatureNotification() +{ +} + +BOOL LLFloaterNewFeatureNotification::postBuild() +{ + setCanDrag(FALSE); + getChild("close_btn")->setCommitCallback(boost::bind(&LLFloaterNewFeatureNotification::onCloseBtn, this)); + + const std::string title_txt = "title_txt"; + const std::string dsc_txt = "description_txt"; + std::string feature = "_" + getKey().asString(); + + getChild(title_txt)->setValue(getString(title_txt + feature)); + getChild(dsc_txt)->setValue(getString(dsc_txt + feature)); + + return TRUE; +} + +void LLFloaterNewFeatureNotification::onOpen(const LLSD& key) +{ + centerOnScreen(); +} + +void LLFloaterNewFeatureNotification::onCloseBtn() +{ + closeFloater(); +} + +void LLFloaterNewFeatureNotification::centerOnScreen() +{ + LLVector2 window_size = LLUI::getInstance()->getWindowSize(); + centerWithin(LLRect(0, 0, ll_round(window_size.mV[VX]), ll_round(window_size.mV[VY]))); + LLFloaterView* parent = dynamic_cast(getParent()); + if (parent) + { + parent->bringToFront(this); + } +} + diff --git a/indra/newview/llfloaternewfeaturenotification.h b/indra/newview/llfloaternewfeaturenotification.h new file mode 100644 index 0000000000..95501451dc --- /dev/null +++ b/indra/newview/llfloaternewfeaturenotification.h @@ -0,0 +1,49 @@ +/** + * @file llfloaternewfeaturenotification.h + * @brief LLFloaterNewFeatureNotification class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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_FLOATER_NEW_FEATURE_NOTOFICATION_H +#define LL_FLOATER_NEW_FEATURE_NOTOFICATION_H + +#include "llfloater.h" + +class LLFloaterNewFeatureNotification: + public LLFloater +{ + friend class LLFloaterReg; +public: + BOOL postBuild() override; + void onOpen(const LLSD& key) override; + +private: + LLFloaterNewFeatureNotification(const LLSD& key); + /*virtual*/ ~LLFloaterNewFeatureNotification(); + + void centerOnScreen(); + + void onCloseBtn(); +}; + +#endif diff --git a/indra/newview/llfloateropenobject.cpp b/indra/newview/llfloateropenobject.cpp index 980bdf530e..051f1c511b 100644 --- a/indra/newview/llfloateropenobject.cpp +++ b/indra/newview/llfloateropenobject.cpp @@ -181,34 +181,12 @@ void LLFloaterOpenObject::moveToInventory(bool wear, bool replace) } inventory_func_type func = boost::bind(LLFloaterOpenObject::callbackCreateInventoryCategory,_1,object_id,wear,replace); - LLUUID category_id = gInventory.createNewCategory(parent_category_id, - LLFolderType::FT_NONE, - name, - func); - - //If we get a null category ID, we are using a capability in createNewCategory and we will - //handle the following in the callbackCreateInventoryCategory routine. - if ( category_id.notNull() ) - { - LLCatAndWear* data = new LLCatAndWear; - data->mCatID = category_id; - data->mWear = wear; - data->mFolderResponded = false; - data->mReplace = replace; - - // Copy and/or move the items into the newly created folder. - // Ignore any "you're going to break this item" messages. - BOOL success = move_inv_category_world_to_agent(object_id, category_id, TRUE, - callbackMoveInventory, - (void*)data); - if (!success) - { - delete data; - data = NULL; - - LLNotificationsUtil::add("OpenObjectCannotCopy"); - } - } + // D567 copy thumbnail info + gInventory.createNewCategory( + parent_category_id, + LLFolderType::FT_NONE, + name, + func); } // static @@ -223,9 +201,14 @@ void LLFloaterOpenObject::callbackCreateInventoryCategory(const LLUUID& category // Copy and/or move the items into the newly created folder. // Ignore any "you're going to break this item" messages. - BOOL success = move_inv_category_world_to_agent(object_id, category_id, TRUE, - callbackMoveInventory, - (void*)wear_data); + BOOL success = move_inv_category_world_to_agent(object_id, + category_id, + TRUE, + [](S32 result, void* data, const LLMoveInv*) + { + callbackMoveInventory(result, data); + }, + (void*)wear_data); if (!success) { delete wear_data; diff --git a/indra/newview/llfloateroutfitphotopreview.cpp b/indra/newview/llfloateroutfitphotopreview.cpp deleted file mode 100644 index ade258aef7..0000000000 --- a/indra/newview/llfloateroutfitphotopreview.cpp +++ /dev/null @@ -1,288 +0,0 @@ -/** - * @file llfloateroutfitphotopreview.cpp - * @brief LLFloaterOutfitPhotoPreview class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llwindow.h" - -#include "llfloateroutfitphotopreview.h" - -#include "llagent.h" -#include "llappearancemgr.h" -#include "llbutton.h" -#include "llcombobox.h" -#include "llfilepicker.h" -#include "llfloaterreg.h" -#include "llimagetga.h" -#include "llimagepng.h" -#include "llinventory.h" -#include "llinventorymodel.h" -#include "llnotificationsutil.h" -#include "llresmgr.h" -#include "lltrans.h" -#include "lltextbox.h" -#include "lltextureview.h" -#include "llui.h" -#include "llviewerinventory.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" -#include "lllineeditor.h" - -const S32 MAX_OUTFIT_PHOTO_WIDTH = 256; -const S32 MAX_OUTFIT_PHOTO_HEIGHT = 256; - -const S32 CLIENT_RECT_VPAD = 4; - -LLFloaterOutfitPhotoPreview::LLFloaterOutfitPhotoPreview(const LLSD& key) - : LLPreview(key), - mUpdateDimensions(TRUE), - mImage(NULL), - mOutfitID(LLUUID()), - mImageOldBoostLevel(LLGLTexture::BOOST_NONE), - mExceedLimits(FALSE) -{ - updateImageID(); -} - -LLFloaterOutfitPhotoPreview::~LLFloaterOutfitPhotoPreview() -{ - LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ; - - if (mImage.notNull()) - { - mImage->setBoostLevel(mImageOldBoostLevel); - mImage = NULL; - } -} - -// virtual -BOOL LLFloaterOutfitPhotoPreview::postBuild() -{ - getChild("ok_btn")->setClickedCallback(boost::bind(&LLFloaterOutfitPhotoPreview::onOkBtn, this)); - getChild("cancel_btn")->setClickedCallback(boost::bind(&LLFloaterOutfitPhotoPreview::onCancelBtn, this)); - - return LLPreview::postBuild(); -} - -void LLFloaterOutfitPhotoPreview::draw() -{ - updateDimensions(); - - LLPreview::draw(); - - if (!isMinimized()) - { - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - const LLRect& border = mClientRect; - LLRect interior = mClientRect; - interior.stretch( -PREVIEW_BORDER_WIDTH ); - - // ...border - gl_rect_2d( border, LLColor4(0.f, 0.f, 0.f, 1.f)); - gl_rect_2d_checkerboard( interior ); - - if ( mImage.notNull() ) - { - // Draw the texture - gGL.diffuseColor3f( 1.f, 1.f, 1.f ); - gl_draw_scaled_image(interior.mLeft, - interior.mBottom, - interior.getWidth(), - interior.getHeight(), - mImage); - - // Pump the texture priority - F32 pixel_area = (F32)(interior.getWidth() * interior.getHeight() ); - mImage->addTextureStats( pixel_area ); - - S32 int_width = interior.getWidth(); - S32 int_height = interior.getHeight(); - mImage->setKnownDrawSize(int_width, int_height); - } - } - -} - -// virtual -void LLFloaterOutfitPhotoPreview::reshape(S32 width, S32 height, BOOL called_from_parent) -{ - LLPreview::reshape(width, height, called_from_parent); - - LLRect dim_rect(getChildView("dimensions")->getRect()); - - S32 horiz_pad = 2 * (LLPANEL_BORDER_WIDTH + PREVIEW_PAD) + PREVIEW_RESIZE_HANDLE_SIZE; - - S32 info_height = dim_rect.mTop + CLIENT_RECT_VPAD; - - LLRect client_rect(horiz_pad, getRect().getHeight(), getRect().getWidth() - horiz_pad, 0); - client_rect.mTop -= (PREVIEW_HEADER_SIZE + CLIENT_RECT_VPAD); - client_rect.mBottom += PREVIEW_BORDER + CLIENT_RECT_VPAD + info_height ; - - S32 client_width = client_rect.getWidth(); - S32 client_height = client_width; - - if(client_height > client_rect.getHeight()) - { - client_height = client_rect.getHeight(); - client_width = client_height; - } - mClientRect.setLeftTopAndSize(client_rect.getCenterX() - (client_width / 2), client_rect.getCenterY() + (client_height / 2), client_width, client_height); - -} - - -void LLFloaterOutfitPhotoPreview::updateDimensions() -{ - if (!mImage) - { - return; - } - if ((mImage->getFullWidth() * mImage->getFullHeight()) == 0) - { - return; - } - - if (mAssetStatus != PREVIEW_ASSET_LOADED) - { - mAssetStatus = PREVIEW_ASSET_LOADED; - mUpdateDimensions = TRUE; - } - - getChild("dimensions")->setTextArg("[WIDTH]", llformat("%d", mImage->getFullWidth())); - getChild("dimensions")->setTextArg("[HEIGHT]", llformat("%d", mImage->getFullHeight())); - - if ((mImage->getFullWidth() <= MAX_OUTFIT_PHOTO_WIDTH) && (mImage->getFullHeight() <= MAX_OUTFIT_PHOTO_HEIGHT)) - { - getChild("ok_btn")->setEnabled(TRUE); - mExceedLimits = FALSE; - } - else - { - mExceedLimits = TRUE; - LLStringUtil::format_map_t args; - args["MAX_WIDTH"] = llformat("%d", MAX_OUTFIT_PHOTO_WIDTH); - args["MAX_HEIGHT"] = llformat("%d", MAX_OUTFIT_PHOTO_HEIGHT); - std::string label = getString("exceed_limits", args); - getChild("notification")->setValue(label); - getChild("notification")->setColor(LLColor4::yellow); - getChild("ok_btn")->setEnabled(FALSE); - } - - if (mUpdateDimensions) - { - mUpdateDimensions = FALSE; - - reshape(getRect().getWidth(), getRect().getHeight()); - gFloaterView->adjustToFitScreen(this, FALSE); - } -} - -void LLFloaterOutfitPhotoPreview::loadAsset() -{ - if (mImage.notNull()) - { - mImage->setBoostLevel(mImageOldBoostLevel); - } - mImage = LLViewerTextureManager::getFetchedTexture(mImageID, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - mImageOldBoostLevel = mImage->getBoostLevel(); - mImage->setBoostLevel(LLGLTexture::BOOST_PREVIEW); - mImage->forceToSaveRawImage(0) ; - mAssetStatus = PREVIEW_ASSET_LOADING; - mUpdateDimensions = TRUE; - updateDimensions(); -} - -LLPreview::EAssetStatus LLFloaterOutfitPhotoPreview::getAssetStatus() -{ - if (mImage.notNull() && (mImage->getFullWidth() * mImage->getFullHeight() > 0)) - { - mAssetStatus = PREVIEW_ASSET_LOADED; - } - return mAssetStatus; -} - -void LLFloaterOutfitPhotoPreview::updateImageID() -{ - const LLViewerInventoryItem *item = static_cast(getItem()); - if(item) - { - mImageID = item->getAssetUUID(); - } - else - { - mImageID = mItemUUID; - } - -} - -/* virtual */ -void LLFloaterOutfitPhotoPreview::setObjectID(const LLUUID& object_id) -{ - mObjectUUID = object_id; - - const LLUUID old_image_id = mImageID; - - updateImageID(); - if (mImageID != old_image_id) - { - mAssetStatus = PREVIEW_ASSET_UNLOADED; - loadAsset(); - } - refreshFromItem(); -} - -void LLFloaterOutfitPhotoPreview::setOutfitID(const LLUUID& outfit_id) -{ - mOutfitID = outfit_id; - LLViewerInventoryCategory* outfit_folder = gInventory.getCategory(mOutfitID); - if(outfit_folder && !mExceedLimits) - { - getChild("notification")->setValue( getString("photo_confirmation")); - getChild("notification")->setTextArg("[OUTFIT]", outfit_folder->getName()); - getChild("notification")->setColor(LLColor4::white); - } - -} - -void LLFloaterOutfitPhotoPreview::onOkBtn() -{ - if(mOutfitID.notNull() && getItem()) - { - LLAppearanceMgr::instance().removeOutfitPhoto(mOutfitID); - LLPointer cb = NULL; - link_inventory_object(mOutfitID, LLConstPointer(getItem()), cb); - } - closeFloater(); -} - -void LLFloaterOutfitPhotoPreview::onCancelBtn() -{ - closeFloater(); -} diff --git a/indra/newview/llfloateroutfitphotopreview.h b/indra/newview/llfloateroutfitphotopreview.h deleted file mode 100644 index a1e7b58abe..0000000000 --- a/indra/newview/llfloateroutfitphotopreview.h +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @file llfloateroutfitphotopreview.h - * @brief LLFloaterOutfitPhotoPreview class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATEROUTFITPHOTOPREVIEW_H -#define LL_LLFLOATEROUTFITPHOTOPREVIEW_H - -#include "llpreview.h" -#include "llbutton.h" -#include "llframetimer.h" -#include "llviewertexture.h" - -class LLComboBox; -class LLImageRaw; - -class LLFloaterOutfitPhotoPreview : public LLPreview -{ -public: - LLFloaterOutfitPhotoPreview(const LLSD& key); - ~LLFloaterOutfitPhotoPreview(); - - virtual void draw(); - - virtual void loadAsset(); - virtual EAssetStatus getAssetStatus(); - - virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); - - /*virtual*/ void setObjectID(const LLUUID& object_id); - - void setOutfitID(const LLUUID& outfit_id); - void onOkBtn(); - void onCancelBtn(); - -protected: - void init(); - /* virtual */ BOOL postBuild(); - -private: - void updateImageID(); // set what image is being uploaded. - void updateDimensions(); - LLUUID mImageID; - LLUUID mOutfitID; - LLPointer mImage; - S32 mImageOldBoostLevel; - - // This is stored off in a member variable, because the save-as - // button and drag and drop functionality need to know. - BOOL mUpdateDimensions; - - BOOL mExceedLimits; - - LLLoadedCallbackEntry::source_callback_list_t mCallbackTextureList ; -}; -#endif // LL_LLFLOATEROUTFITPHOTOPREVIEW_H diff --git a/indra/newview/llfloatersidepanelcontainer.cpp b/indra/newview/llfloatersidepanelcontainer.cpp index 7175a929b8..e744eba5ea 100644 --- a/indra/newview/llfloatersidepanelcontainer.cpp +++ b/indra/newview/llfloatersidepanelcontainer.cpp @@ -97,6 +97,39 @@ void LLFloaterSidePanelContainer::closeFloater(bool app_quitting) } } +LLFloater* LLFloaterSidePanelContainer::getTopmostInventoryFloater() +{ + LLFloater* topmost_floater = NULL; + S32 z_min = S32_MAX; + + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + // Fix for sharing inventory when multiple inventory floaters are open: + // For the secondary floaters, we have registered those as + // "secondary_inventory" in LLFloaterReg, so we have to add those + // instances to the instance list! + //for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) + LLFloaterReg::const_instance_list_t& inst_list_secondary = LLFloaterReg::getFloaterList("secondary_inventory"); + LLFloaterReg::instance_list_t combined_list; + combined_list.insert(combined_list.end(), inst_list.begin(), inst_list.end()); + combined_list.insert(combined_list.end(), inst_list_secondary.begin(), inst_list_secondary.end()); + for (LLFloaterReg::instance_list_t::const_iterator iter = combined_list.begin(); iter != combined_list.end(); ++iter) + // + { + LLFloater* inventory_floater = (*iter); + + if (inventory_floater && inventory_floater->getVisible()) + { + S32 z_order = gFloaterView->getZOrder(inventory_floater); + if (z_order < z_min) + { + z_min = z_order; + topmost_floater = inventory_floater; + } + } + } + return topmost_floater; +} + LLPanel* LLFloaterSidePanelContainer::openChildPanel(const std::string& panel_name, const LLSD& params) { LLView* view = findChildView(panel_name, true); diff --git a/indra/newview/llfloatersidepanelcontainer.h b/indra/newview/llfloatersidepanelcontainer.h index e955214e5b..3069913313 100644 --- a/indra/newview/llfloatersidepanelcontainer.h +++ b/indra/newview/llfloatersidepanelcontainer.h @@ -57,6 +57,8 @@ public: LLPanel* openChildPanel(const std::string& panel_name, const LLSD& params); + static LLFloater* getTopmostInventoryFloater(); + // [RLVa:KB] - Checked: 2012-02-07 (RLVa-1.4.5) | Added: RLVa-1.4.5 static bool canShowPanel(const std::string& floater_name, const LLSD& key); static bool canShowPanel(const std::string& floater_name, const std::string& panel_name, const LLSD& key); diff --git a/indra/newview/llfloatersimpleoutfitsnapshot.cpp b/indra/newview/llfloatersimpleoutfitsnapshot.cpp deleted file mode 100644 index bab2efbbd5..0000000000 --- a/indra/newview/llfloatersimpleoutfitsnapshot.cpp +++ /dev/null @@ -1,333 +0,0 @@ -/** -* @file llfloatersimpleoutfitsnapshot.cpp -* @brief Snapshot preview window for saving as an outfit thumbnail in visual outfit gallery -* -* $LicenseInfo:firstyear=2022&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2022, 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 "llfloatersimpleoutfitsnapshot.h" - -#include "llfloaterreg.h" -#include "llimagefiltersmanager.h" -#include "llstatusbar.h" // can_afford_transaction() -#include "llnotificationsutil.h" -#include "llagentbenefits.h" -#include "llviewercontrol.h" - -LLSimpleOutfitSnapshotFloaterView* gSimpleOutfitSnapshotFloaterView = NULL; - -const S32 OUTFIT_SNAPSHOT_WIDTH = 256; -const S32 OUTFIT_SNAPSHOT_HEIGHT = 256; - -static LLDefaultChildRegistry::Register r("simple_snapshot_outfit_floater_view"); - -///---------------------------------------------------------------------------- -/// Class LLFloaterSimpleOutfitSnapshot::Impl -///---------------------------------------------------------------------------- - -LLSnapshotModel::ESnapshotFormat LLFloaterSimpleOutfitSnapshot::Impl::getImageFormat(LLFloaterSnapshotBase* floater) -{ - return LLSnapshotModel::SNAPSHOT_FORMAT_PNG; -} - -LLSnapshotModel::ESnapshotLayerType LLFloaterSimpleOutfitSnapshot::Impl::getLayerType(LLFloaterSnapshotBase* floater) -{ - return LLSnapshotModel::SNAPSHOT_TYPE_COLOR; -} - -void LLFloaterSimpleOutfitSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater) -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - updateResolution(floater); - if (previewp) - { - previewp->setSnapshotType(LLSnapshotModel::ESnapshotType::SNAPSHOT_TEXTURE); - previewp->setSnapshotFormat(LLSnapshotModel::ESnapshotFormat::SNAPSHOT_FORMAT_PNG); - previewp->setSnapshotBufferType(LLSnapshotModel::ESnapshotLayerType::SNAPSHOT_TYPE_COLOR); - } -} - -std::string LLFloaterSimpleOutfitSnapshot::Impl::getSnapshotPanelPrefix() -{ - return "panel_outfit_snapshot_"; -} - -void LLFloaterSimpleOutfitSnapshot::Impl::updateResolution(void* data) -{ - LLFloaterSimpleOutfitSnapshot *view = (LLFloaterSimpleOutfitSnapshot *)data; - - if (!view) - { - llassert(view); - return; - } - - S32 width = OUTFIT_SNAPSHOT_WIDTH; - S32 height = OUTFIT_SNAPSHOT_HEIGHT; - - LLSnapshotLivePreview* previewp = getPreviewView(); - if (previewp) - { - S32 original_width = 0, original_height = 0; - previewp->getSize(original_width, original_height); - - if (gSavedSettings.getBOOL("RenderHUDInSnapshot")) - { //clamp snapshot resolution to window size when showing UI HUD in snapshot - width = llmin(width, gViewerWindow->getWindowWidthRaw()); - height = llmin(height, gViewerWindow->getWindowHeightRaw()); - } - - llassert(width > 0 && height > 0); - - previewp->setSize(width, height); - - if (original_width != width || original_height != height) - { - // hide old preview as the aspect ratio could be wrong - checkAutoSnapshot(previewp, FALSE); - previewp->updateSnapshot(TRUE); - } - } -} - -void LLFloaterSimpleOutfitSnapshot::Impl::setStatus(EStatus status, bool ok, const std::string& msg) -{ - switch (status) - { - case STATUS_READY: - mFloater->setCtrlsEnabled(true); - break; - case STATUS_WORKING: - mFloater->setCtrlsEnabled(false); - break; - case STATUS_FINISHED: - mFloater->setCtrlsEnabled(true); - break; - } - - mStatus = status; -} - -///----------------------------------------------------------------re------------ -/// Class LLFloaterSimpleOutfitSnapshot -///---------------------------------------------------------------------------- - -LLFloaterSimpleOutfitSnapshot::LLFloaterSimpleOutfitSnapshot(const LLSD& key) - : LLFloaterSnapshotBase(key), - mOutfitGallery(NULL) -{ - impl = new Impl(this); -} - -LLFloaterSimpleOutfitSnapshot::~LLFloaterSimpleOutfitSnapshot() -{ -} - -BOOL LLFloaterSimpleOutfitSnapshot::postBuild() -{ - getChild("save_btn")->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost())); - - childSetAction("new_snapshot_btn", ImplBase::onClickNewSnapshot, this); - childSetAction("save_btn", boost::bind(&LLFloaterSimpleOutfitSnapshot::onSend, this)); - childSetAction("cancel_btn", boost::bind(&LLFloaterSimpleOutfitSnapshot::onCancel, this)); - - mThumbnailPlaceholder = getChild("thumbnail_placeholder"); - - // create preview window - LLRect full_screen_rect = getRootView()->getRect(); - LLSnapshotLivePreview::Params p; - p.rect(full_screen_rect); - LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); - LLView* parent_view = gSnapshotFloaterView->getParent(); - - parent_view->removeChild(gSnapshotFloaterView); - // make sure preview is below snapshot floater - parent_view->addChild(previewp); - parent_view->addChild(gSnapshotFloaterView); - - //move snapshot floater to special purpose snapshotfloaterview - gFloaterView->removeChild(this); - gSnapshotFloaterView->addChild(this); - - impl->mPreviewHandle = previewp->getHandle(); - previewp->setContainer(this); - impl->updateControls(this); - impl->setAdvanced(true); - impl->setSkipReshaping(true); - - previewp->mKeepAspectRatio = FALSE; - previewp->setThumbnailPlaceholderRect(getThumbnailPlaceholderRect()); - previewp->setAllowRenderUI(false); - - return TRUE; -} -const S32 PREVIEW_OFFSET_X = 12; -const S32 PREVIEW_OFFSET_Y = 70; - -void LLFloaterSimpleOutfitSnapshot::draw() -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - - if (previewp && (previewp->isSnapshotActive() || previewp->getThumbnailLock())) - { - // don't render snapshot window in snapshot, even if "show ui" is turned on - return; - } - - LLFloater::draw(); - - if (previewp && !isMinimized() && mThumbnailPlaceholder->getVisible()) - { - if(previewp->getThumbnailImage()) - { - bool working = impl->getStatus() == ImplBase::STATUS_WORKING; - const LLRect& thumbnail_rect = getThumbnailPlaceholderRect(); - const S32 thumbnail_w = previewp->getThumbnailWidth(); - const S32 thumbnail_h = previewp->getThumbnailHeight(); - - S32 offset_x = PREVIEW_OFFSET_X; - S32 offset_y = PREVIEW_OFFSET_Y; - - gGL.matrixMode(LLRender::MM_MODELVIEW); - // Apply floater transparency to the texture unless the floater is focused. - F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - LLColor4 color = working ? LLColor4::grey4 : LLColor4::white; - gl_draw_scaled_image(offset_x, offset_y, - thumbnail_w, thumbnail_h, - previewp->getThumbnailImage(), color % alpha); -#if LL_DARWIN - std::string alpha_color = getTransparencyType() == TT_ACTIVE ? "OutfitSnapshotMacMask" : "OutfitSnapshotMacMask2"; -#else - std::string alpha_color = getTransparencyType() == TT_ACTIVE ? "FloaterFocusBackgroundColor" : "DkGray"; -#endif - - previewp->drawPreviewRect(offset_x, offset_y, LLUIColorTable::instance().getColor(alpha_color)); - - gGL.pushUIMatrix(); - LLUI::translate((F32) thumbnail_rect.mLeft, (F32) thumbnail_rect.mBottom); - mThumbnailPlaceholder->draw(); - gGL.popUIMatrix(); - } - } - impl->updateLayout(this); -} - -void LLFloaterSimpleOutfitSnapshot::onOpen(const LLSD& key) -{ - LLSnapshotLivePreview* preview = getPreviewView(); - if (preview) - { - preview->updateSnapshot(TRUE); - } - focusFirstItem(FALSE); - gSnapshotFloaterView->setEnabled(TRUE); - gSnapshotFloaterView->setVisible(TRUE); - gSnapshotFloaterView->adjustToFitScreen(this, FALSE); - - impl->updateControls(this); - impl->setStatus(ImplBase::STATUS_READY); -} - -void LLFloaterSimpleOutfitSnapshot::onCancel() -{ - closeFloater(); -} - -void LLFloaterSimpleOutfitSnapshot::onSend() -{ - S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - if (can_afford_transaction(expected_upload_cost)) - { - saveTexture(); - postSave(); - } - else - { - LLSD args; - args["COST"] = llformat("%d", expected_upload_cost); - LLNotificationsUtil::add("ErrorPhotoCannotAfford", args); - inventorySaveFailed(); - } -} - -void LLFloaterSimpleOutfitSnapshot::postSave() -{ - impl->setStatus(ImplBase::STATUS_WORKING); -} - -// static -void LLFloaterSimpleOutfitSnapshot::update() -{ - LLFloaterSimpleOutfitSnapshot* inst = findInstance(); - if (inst != NULL) - { - inst->impl->updateLivePreview(); - } -} - - -// static -LLFloaterSimpleOutfitSnapshot* LLFloaterSimpleOutfitSnapshot::findInstance() -{ - return LLFloaterReg::findTypedInstance("simple_outfit_snapshot"); -} - -// static -LLFloaterSimpleOutfitSnapshot* LLFloaterSimpleOutfitSnapshot::getInstance() -{ - return LLFloaterReg::getTypedInstance("simple_outfit_snapshot"); -} - -void LLFloaterSimpleOutfitSnapshot::saveTexture() -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - if (!previewp) - { - llassert(previewp != NULL); - return; - } - - if (mOutfitGallery) - { - mOutfitGallery->onBeforeOutfitSnapshotSave(); - } - previewp->saveTexture(TRUE, getOutfitID().asString()); - if (mOutfitGallery) - { - mOutfitGallery->onAfterOutfitSnapshotSave(); - } - closeFloater(); -} - -///---------------------------------------------------------------------------- -/// Class LLSimpleOutfitSnapshotFloaterView -///---------------------------------------------------------------------------- - -LLSimpleOutfitSnapshotFloaterView::LLSimpleOutfitSnapshotFloaterView(const Params& p) : LLFloaterView(p) -{ -} - -LLSimpleOutfitSnapshotFloaterView::~LLSimpleOutfitSnapshotFloaterView() -{ -} diff --git a/indra/newview/llfloatersimplesnapshot.cpp b/indra/newview/llfloatersimplesnapshot.cpp new file mode 100644 index 0000000000..58604c5628 --- /dev/null +++ b/indra/newview/llfloatersimplesnapshot.cpp @@ -0,0 +1,499 @@ +/** +* @file llfloatersimplesnapshot.cpp +* @brief Snapshot preview window for saving as a thumbnail +* +* $LicenseInfo:firstyear=2022&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2022, 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 "llfloatersimplesnapshot.h" + +#include "llfloaterreg.h" +#include "llimagefiltersmanager.h" +#include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "llstatusbar.h" // can_afford_transaction() +#include "llnotificationsutil.h" +#include "llagent.h" +#include "llagentbenefits.h" +#include "llviewercontrol.h" +#include "llviewertexturelist.h" + + + +LLSimpleSnapshotFloaterView* gSimpleSnapshotFloaterView = NULL; + +const S32 LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MAX = 256; +const S32 LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN = 64; + +// Thumbnail posting coro + +static const std::string THUMBNAIL_UPLOAD_CAP = "InventoryThumbnailUpload"; + +void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, LLSD first_data) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("post_profile_image_coro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders; + + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + httpOpts->setFollowRedirects(true); + + LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; + return; + } + if (!result.has("uploader")) + { + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; + return; + } + std::string uploader_cap = result["uploader"].asString(); + if (uploader_cap.empty()) + { + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; + return; + } + + // Upload the image + + LLCore::HttpRequest::ptr_t uploaderhttpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t uploaderhttpHeaders(new LLCore::HttpHeaders); + LLCore::HttpOptions::ptr_t uploaderhttpOpts(new LLCore::HttpOptions); + S64 length; + + { + llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); + if (!instream.is_open()) + { + LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; + return; + } + length = instream.tellg(); + } + + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required! + uploaderhttpOpts->setFollowRedirects(true); + + result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); + + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + LL_DEBUGS("Thumbnail") << result << LL_ENDL; + + if (!status) + { + LL_WARNS("Thumbnail") << "Failed to upload image " << status.toString() << LL_ENDL; + return; + } + + if (result["state"].asString() != "complete") + { + if (result.has("message")) + { + LL_WARNS("Thumbnail") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL; + } + else + { + LL_WARNS("Thumbnail") << "Failed to upload image " << result << LL_ENDL; + } + return; + } + + if (first_data.has("category_id")) + { + LLUUID cat_id = first_data["category_id"].asUUID(); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + cat->setThumbnailUUID(result["new_asset"].asUUID()); + } + gInventory.addChangedMask(LLInventoryObserver::INTERNAL, cat_id); + } + if (first_data.has("item_id")) + { + LLUUID item_id = first_data["item_id"].asUUID(); + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (item) + { + item->setThumbnailUUID(result["new_asset"].asUUID()); + } + // Are we supposed to get BulkUpdateInventory? + gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); + } +} + +///---------------------------------------------------------------------------- +/// Class LLFloaterSimpleSnapshot::Impl +///---------------------------------------------------------------------------- + +LLSnapshotModel::ESnapshotFormat LLFloaterSimpleSnapshot::Impl::getImageFormat(LLFloaterSnapshotBase* floater) +{ + return LLSnapshotModel::SNAPSHOT_FORMAT_PNG; +} + +LLSnapshotModel::ESnapshotLayerType LLFloaterSimpleSnapshot::Impl::getLayerType(LLFloaterSnapshotBase* floater) +{ + return LLSnapshotModel::SNAPSHOT_TYPE_COLOR; +} + +void LLFloaterSimpleSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater) +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + updateResolution(floater); + if (previewp) + { + previewp->setSnapshotType(LLSnapshotModel::ESnapshotType::SNAPSHOT_TEXTURE); + previewp->setSnapshotFormat(LLSnapshotModel::ESnapshotFormat::SNAPSHOT_FORMAT_PNG); + previewp->setSnapshotBufferType(LLSnapshotModel::ESnapshotLayerType::SNAPSHOT_TYPE_COLOR); + } +} + +std::string LLFloaterSimpleSnapshot::Impl::getSnapshotPanelPrefix() +{ + return "panel_outfit_snapshot_"; +} + +void LLFloaterSimpleSnapshot::Impl::updateResolution(void* data) +{ + LLFloaterSimpleSnapshot *view = (LLFloaterSimpleSnapshot *)data; + + if (!view) + { + llassert(view); + return; + } + + S32 width = THUMBNAIL_SNAPSHOT_DIM_MAX; + S32 height = THUMBNAIL_SNAPSHOT_DIM_MAX; + + LLSnapshotLivePreview* previewp = getPreviewView(); + if (previewp) + { + S32 original_width = 0, original_height = 0; + previewp->getSize(original_width, original_height); + + if (gSavedSettings.getBOOL("RenderHUDInSnapshot")) + { //clamp snapshot resolution to window size when showing UI HUD in snapshot + width = llmin(width, gViewerWindow->getWindowWidthRaw()); + height = llmin(height, gViewerWindow->getWindowHeightRaw()); + } + + llassert(width > 0 && height > 0); + + previewp->setSize(width, height); + + if (original_width != width || original_height != height) + { + // hide old preview as the aspect ratio could be wrong + checkAutoSnapshot(previewp, FALSE); + previewp->updateSnapshot(TRUE); + } + } +} + +void LLFloaterSimpleSnapshot::Impl::setStatus(EStatus status, bool ok, const std::string& msg) +{ + switch (status) + { + case STATUS_READY: + mFloater->setCtrlsEnabled(true); + break; + case STATUS_WORKING: + mFloater->setCtrlsEnabled(false); + break; + case STATUS_FINISHED: + mFloater->setCtrlsEnabled(true); + break; + } + + mStatus = status; +} + +///----------------------------------------------------------------re------------ +/// Class LLFloaterSimpleSnapshot +///---------------------------------------------------------------------------- + +LLFloaterSimpleSnapshot::LLFloaterSimpleSnapshot(const LLSD& key) + : LLFloaterSnapshotBase(key) + , mOwner(NULL) + , mContextConeOpacity(0.f) +{ + impl = new Impl(this); +} + +LLFloaterSimpleSnapshot::~LLFloaterSimpleSnapshot() +{ +} + +BOOL LLFloaterSimpleSnapshot::postBuild() +{ + childSetAction("new_snapshot_btn", ImplBase::onClickNewSnapshot, this); + childSetAction("save_btn", boost::bind(&LLFloaterSimpleSnapshot::onSend, this)); + childSetAction("cancel_btn", boost::bind(&LLFloaterSimpleSnapshot::onCancel, this)); + + mThumbnailPlaceholder = getChild("thumbnail_placeholder"); + + // create preview window + LLRect full_screen_rect = getRootView()->getRect(); + LLSnapshotLivePreview::Params p; + p.rect(full_screen_rect); + LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); + + // Do not move LLFloaterSimpleSnapshot floater into gSnapshotFloaterView + // since it can be a dependednt floater and does not draw UI + + impl->mPreviewHandle = previewp->getHandle(); + previewp->setContainer(this); + impl->updateControls(this); + impl->setAdvanced(true); + impl->setSkipReshaping(true); + + previewp->mKeepAspectRatio = FALSE; + previewp->setThumbnailPlaceholderRect(getThumbnailPlaceholderRect()); + previewp->setAllowRenderUI(false); + previewp->setThumbnailSubsampled(TRUE); + + return TRUE; +} + +const S32 PREVIEW_OFFSET_Y = 70; + +void LLFloaterSimpleSnapshot::draw() +{ + if (mOwner) + { + static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); + drawConeToOwner(mContextConeOpacity, max_opacity, mOwner); + } + + LLSnapshotLivePreview* previewp = getPreviewView(); + + if (previewp && (previewp->isSnapshotActive() || previewp->getThumbnailLock())) + { + // don't render snapshot window in snapshot, even if "show ui" is turned on + return; + } + + LLFloater::draw(); + + if (previewp && !isMinimized() && mThumbnailPlaceholder->getVisible()) + { + if(previewp->getThumbnailImage()) + { + bool working = impl->getStatus() == ImplBase::STATUS_WORKING; + const S32 thumbnail_w = previewp->getThumbnailWidth(); + const S32 thumbnail_h = previewp->getThumbnailHeight(); + + LLRect local_rect = getLocalRect(); + S32 offset_x = (local_rect.getWidth() - thumbnail_w) / 2; + S32 offset_y = PREVIEW_OFFSET_Y; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + // Apply floater transparency to the texture unless the floater is focused. + F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + LLColor4 color = working ? LLColor4::grey4 : LLColor4::white; + gl_draw_scaled_image(offset_x, offset_y, + thumbnail_w, thumbnail_h, + previewp->getThumbnailImage(), color % alpha); + } + } + impl->updateLayout(this); +} + +void LLFloaterSimpleSnapshot::onOpen(const LLSD& key) +{ + LLSnapshotLivePreview* preview = getPreviewView(); + if (preview) + { + preview->updateSnapshot(TRUE); + } + focusFirstItem(FALSE); + gSnapshotFloaterView->setEnabled(TRUE); + gSnapshotFloaterView->setVisible(TRUE); + gSnapshotFloaterView->adjustToFitScreen(this, FALSE); + + impl->updateControls(this); + impl->setStatus(ImplBase::STATUS_READY); + + mInventoryId = key["item_id"].asUUID(); + mTaskId = key["task_id"].asUUID(); +} + +void LLFloaterSimpleSnapshot::onCancel() +{ + closeFloater(); +} + +void LLFloaterSimpleSnapshot::onSend() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + + std::string temp_file = gDirUtilp->getTempFilename(); + if (previewp->createUploadFile(temp_file, THUMBNAIL_SNAPSHOT_DIM_MAX, THUMBNAIL_SNAPSHOT_DIM_MIN)) + { + uploadImageUploadFile(temp_file, mInventoryId, mTaskId); + closeFloater(); + } + else + { + LLSD notif_args; + notif_args["REASON"] = LLImage::getLastError().c_str(); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + } +} + +void LLFloaterSimpleSnapshot::postSave() +{ + impl->setStatus(ImplBase::STATUS_WORKING); +} + +// static +void LLFloaterSimpleSnapshot::uploadThumbnail(const std::string &file_path, const LLUUID &inventory_id, const LLUUID &task_id) +{ + // generate a temp texture file for coroutine + std::string temp_file = gDirUtilp->getTempFilename(); + U32 codec = LLImageBase::getCodecFromExtension(gDirUtilp->getExtension(file_path)); + if (!LLViewerTextureList::createUploadFile(file_path, temp_file, codec, THUMBNAIL_SNAPSHOT_DIM_MAX, THUMBNAIL_SNAPSHOT_DIM_MIN, true)) + { + LLSD notif_args; + notif_args["REASON"] = LLImage::getLastError().c_str(); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + LL_WARNS("Thumbnail") << "Failed to upload thumbnail for " << inventory_id << " " << task_id << ", reason: " << notif_args["REASON"].asString() << LL_ENDL; + return; + } + uploadImageUploadFile(temp_file, inventory_id, task_id); +} + +// static +void LLFloaterSimpleSnapshot::uploadThumbnail(LLPointer raw_image, const LLUUID& inventory_id, const LLUUID& task_id) +{ + std::string temp_file = gDirUtilp->getTempFilename(); + if (!LLViewerTextureList::createUploadFile(raw_image, temp_file, THUMBNAIL_SNAPSHOT_DIM_MAX, THUMBNAIL_SNAPSHOT_DIM_MIN)) + { + LLSD notif_args; + notif_args["REASON"] = LLImage::getLastError().c_str(); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + LL_WARNS("Thumbnail") << "Failed to upload thumbnail for " << inventory_id << " " << task_id << ", reason: " << notif_args["REASON"].asString() << LL_ENDL; + return; + } + uploadImageUploadFile(temp_file, inventory_id, task_id); +} + +// static +void LLFloaterSimpleSnapshot::uploadImageUploadFile(const std::string &temp_file, const LLUUID &inventory_id, const LLUUID &task_id) +{ + LLSD data; + + if (task_id.notNull()) + { + data["item_id"] = inventory_id; + data["task_id"] = task_id; + } + else if (gInventory.getCategory(inventory_id)) + { + data["category_id"] = inventory_id; + } + else + { + data["item_id"] = inventory_id; + } + + std::string cap_url = gAgent.getRegionCapability(THUMBNAIL_UPLOAD_CAP); + if (cap_url.empty()) + { + LLSD args; + args["CAPABILITY"] = THUMBNAIL_UPLOAD_CAP; + LLNotificationsUtil::add("RegionCapabilityRequestError", args); + LL_WARNS("Thumbnail") << "Failed to upload profile image for item " << inventory_id << " " << task_id << ", no cap found" << LL_ENDL; + return; + } + + LLCoros::instance().launch("postAgentUserImageCoro", + boost::bind(post_thumbnail_image_coro, cap_url, temp_file, data)); +} + +void LLFloaterSimpleSnapshot::update() +{ + // initializes snapshots when needed + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("simple_snapshot"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); + iter != inst_list.end(); ++iter) + { + LLFloaterSimpleSnapshot* floater = dynamic_cast(*iter); + if (floater) + { + floater->impl->updateLivePreview(); + } + } +} + + +// static +LLFloaterSimpleSnapshot* LLFloaterSimpleSnapshot::findInstance(const LLSD &key) +{ + return LLFloaterReg::findTypedInstance("simple_snapshot", key); +} + +// static +LLFloaterSimpleSnapshot* LLFloaterSimpleSnapshot::getInstance(const LLSD &key) +{ + return LLFloaterReg::getTypedInstance("simple_snapshot", key); +} + +void LLFloaterSimpleSnapshot::saveTexture() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + if (!previewp) + { + llassert(previewp != NULL); + return; + } + + previewp->saveTexture(TRUE, getInventoryId().asString()); + closeFloater(); +} + +///---------------------------------------------------------------------------- +/// Class LLSimpleOutfitSnapshotFloaterView +///---------------------------------------------------------------------------- + +LLSimpleSnapshotFloaterView::LLSimpleSnapshotFloaterView(const Params& p) : LLFloaterView(p) +{ +} + +LLSimpleSnapshotFloaterView::~LLSimpleSnapshotFloaterView() +{ +} diff --git a/indra/newview/llfloatersimpleoutfitsnapshot.h b/indra/newview/llfloatersimplesnapshot.h similarity index 62% rename from indra/newview/llfloatersimpleoutfitsnapshot.h rename to indra/newview/llfloatersimplesnapshot.h index cc9a6c5d1e..91a81ee5c3 100644 --- a/indra/newview/llfloatersimpleoutfitsnapshot.h +++ b/indra/newview/llfloatersimplesnapshot.h @@ -1,6 +1,6 @@ /** -* @file llfloatersimpleoutfitsnapshot.h -* @brief Snapshot preview window for saving as an outfit thumbnail in visual outfit gallery +* @file llfloatersimplesnapshot.h +* @brief Snapshot preview window for saving as a thumbnail * * $LicenseInfo:firstyear=2022&license=viewerlgpl$ * Second Life Viewer Source Code @@ -24,26 +24,25 @@ * $/LicenseInfo$ */ -#ifndef LL_LLFLOATERSIMPLEOUTFITSNAPSHOT_H -#define LL_LLFLOATERSIMPLEOUTFITSNAPSHOT_H +#ifndef LL_LLFLOATERSIMPLESNAPSHOT_H +#define LL_LLFLOATERSIMPLESNAPSHOT_H #include "llfloater.h" #include "llfloatersnapshot.h" -#include "lloutfitgallery.h" #include "llsnapshotlivepreview.h" ///---------------------------------------------------------------------------- -/// Class LLFloaterSimpleOutfitSnapshot +/// Class LLFloaterSimpleSnapshot ///---------------------------------------------------------------------------- -class LLFloaterSimpleOutfitSnapshot : public LLFloaterSnapshotBase +class LLFloaterSimpleSnapshot : public LLFloaterSnapshotBase { - LOG_CLASS(LLFloaterSimpleOutfitSnapshot); + LOG_CLASS(LLFloaterSimpleSnapshot); public: - LLFloaterSimpleOutfitSnapshot(const LLSD& key); - ~LLFloaterSimpleOutfitSnapshot(); + LLFloaterSimpleSnapshot(const LLSD& key); + ~LLFloaterSimpleSnapshot(); BOOL postBuild(); void onOpen(const LLSD& key); @@ -51,36 +50,48 @@ public: static void update(); - static LLFloaterSimpleOutfitSnapshot* getInstance(); - static LLFloaterSimpleOutfitSnapshot* findInstance(); + static LLFloaterSimpleSnapshot* getInstance(const LLSD &key); + static LLFloaterSimpleSnapshot* findInstance(const LLSD &key); void saveTexture(); const LLRect& getThumbnailPlaceholderRect() { return mThumbnailPlaceholder->getRect(); } - void setOutfitID(LLUUID id) { mOutfitID = id; } - LLUUID getOutfitID() { return mOutfitID; } - void setGallery(LLOutfitGallery* gallery) { mOutfitGallery = gallery; } + void setInventoryId(const LLUUID &inventory_id) { mInventoryId = inventory_id; } + LLUUID getInventoryId() { return mInventoryId; } + void setTaskId(const LLUUID &task_id) { mTaskId = task_id; } + void setOwner(LLView *owner_view) { mOwner = owner_view; } void postSave(); + static void uploadThumbnail(const std::string &file_path, const LLUUID &inventory_id, const LLUUID &task_id); + static void uploadThumbnail(LLPointer raw_image, const LLUUID& inventory_id, const LLUUID& task_id); class Impl; friend class Impl; + static const S32 THUMBNAIL_SNAPSHOT_DIM_MAX; + static const S32 THUMBNAIL_SNAPSHOT_DIM_MIN; + private: void onSend(); void onCancel(); - LLUUID mOutfitID; - LLOutfitGallery* mOutfitGallery; + // uploads upload-ready file + static void uploadImageUploadFile(const std::string &temp_file, const LLUUID &inventory_id, const LLUUID &task_id); + + LLUUID mInventoryId; + LLUUID mTaskId; + + LLView* mOwner; + F32 mContextConeOpacity; }; ///---------------------------------------------------------------------------- -/// Class LLFloaterSimpleOutfitSnapshot::Impl +/// Class LLFloaterSimpleSnapshot::Impl ///---------------------------------------------------------------------------- -class LLFloaterSimpleOutfitSnapshot::Impl : public LLFloaterSnapshotBase::ImplBase +class LLFloaterSimpleSnapshot::Impl : public LLFloaterSnapshotBase::ImplBase { - LOG_CLASS(LLFloaterSimpleOutfitSnapshot::Impl); + LOG_CLASS(LLFloaterSimpleSnapshot::Impl); public: Impl(LLFloaterSnapshotBase* floater) : LLFloaterSnapshotBase::ImplBase(floater) @@ -108,7 +119,7 @@ private: /// Class LLSimpleOutfitSnapshotFloaterView ///---------------------------------------------------------------------------- -class LLSimpleOutfitSnapshotFloaterView : public LLFloaterView +class LLSimpleSnapshotFloaterView : public LLFloaterView { public: struct Params @@ -117,13 +128,13 @@ public: }; protected: - LLSimpleOutfitSnapshotFloaterView(const Params& p); + LLSimpleSnapshotFloaterView(const Params& p); friend class LLUICtrlFactory; public: - virtual ~LLSimpleOutfitSnapshotFloaterView(); + virtual ~LLSimpleSnapshotFloaterView(); }; -extern LLSimpleOutfitSnapshotFloaterView* gSimpleOutfitSnapshotFloaterView; +extern LLSimpleSnapshotFloaterView* gSimpleOutfitSnapshotFloaterView; -#endif // LL_LLFLOATERSIMPLEOUTFITSNAPSHOT_H +#endif // LL_LLFLOATERSIMPLESNAPSHOT_H diff --git a/indra/newview/llfolderviewmodelinventory.h b/indra/newview/llfolderviewmodelinventory.h index de28091c32..1649b2eed7 100644 --- a/indra/newview/llfolderviewmodelinventory.h +++ b/indra/newview/llfolderviewmodelinventory.h @@ -39,12 +39,14 @@ class LLFolderViewModelItemInventory public: LLFolderViewModelItemInventory(class LLFolderViewModelInventory& root_view_model); virtual const LLUUID& getUUID() const = 0; + virtual const LLUUID& getThumbnailUUID() const = 0; virtual time_t getCreationDate() const = 0; // UTC seconds virtual void setCreationDate(time_t creation_date_utc) = 0; virtual PermissionMask getPermissionMask() const = 0; virtual LLFolderType::EType getPreferredType() const = 0; virtual void showProperties(void) = 0; 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 isUpToDate() const = 0; virtual void addChild(LLFolderViewModelItem* child); diff --git a/indra/newview/llfriendcard.cpp b/indra/newview/llfriendcard.cpp index 38811ab250..fc899f67e5 100644 --- a/indra/newview/llfriendcard.cpp +++ b/indra/newview/llfriendcard.cpp @@ -478,14 +478,24 @@ void LLFriendCardsManager::ensureFriendsFolderExists() LL_WARNS() << "Failed to find \"" << cat_name << "\" category descendents in Category Tree." << LL_ENDL; } - friends_folder_ID = gInventory.createNewCategory(calling_cards_folder_ID, - LLFolderType::FT_CALLINGCARD, get_friend_folder_name()); - - gInventory.createNewCategory(friends_folder_ID, - LLFolderType::FT_CALLINGCARD, get_friend_all_subfolder_name()); - - // Now when we have all needed folders we can sync their contents with buddies list. - syncFriendsFolder(); + gInventory.createNewCategory( + calling_cards_folder_ID, + LLFolderType::FT_CALLINGCARD, + get_friend_folder_name(), + [](const LLUUID &new_category_id) + { + gInventory.createNewCategory( + new_category_id, + LLFolderType::FT_CALLINGCARD, + get_friend_all_subfolder_name(), + [](const LLUUID &new_category_id) + { + // Now when we have all needed folders we can sync their contents with buddies list. + LLFriendCardsManager::getInstance()->syncFriendsFolder(); + } + ); + } + ); } } @@ -510,11 +520,16 @@ void LLFriendCardsManager::ensureFriendsAllFolderExists() LL_WARNS() << "Failed to find \"" << cat_name << "\" category descendents in Category Tree." << LL_ENDL; } - friends_all_folder_ID = gInventory.createNewCategory(friends_folder_ID, - LLFolderType::FT_CALLINGCARD, get_friend_all_subfolder_name()); - - // Now when we have all needed folders we can sync their contents with buddies list. - syncFriendsFolder(); + gInventory.createNewCategory( + friends_folder_ID, + LLFolderType::FT_CALLINGCARD, + get_friend_all_subfolder_name(), + [](const LLUUID &new_cat_id) + { + // Now when we have all needed folders we can sync their contents with buddies list. + LLFriendCardsManager::getInstance()->syncFriendsFolder(); + } + ); } } diff --git a/indra/newview/llinspectobject.cpp b/indra/newview/llinspectobject.cpp index 4e295cb02d..9b1ee34fd8 100644 --- a/indra/newview/llinspectobject.cpp +++ b/indra/newview/llinspectobject.cpp @@ -696,9 +696,7 @@ void LLInspectObject::onClickOpen() void LLInspectObject::onClickMoreInfo() { - LLSD key; - key["task"] = "task"; - LLFloaterSidePanelContainer::showPanel("inventory", key); + LLFloaterReg::showInstance("task_properties"); closeFloater(); } diff --git a/indra/newview/llinspecttexture.cpp b/indra/newview/llinspecttexture.cpp new file mode 100644 index 0000000000..da4e3c0949 --- /dev/null +++ b/indra/newview/llinspecttexture.cpp @@ -0,0 +1,249 @@ +/** + * @file llinspecttexture.cpp + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llinspect.h" +#include "llinspecttexture.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewertexturelist.h" + + +// ============================================================================ +// Helper functions +// + +LLToolTip* LLInspectTextureUtil::createInventoryToolTip(LLToolTip::Params p) +{ + const LLSD& sdTooltip = p.create_params; + + if (sdTooltip.has("thumbnail_id") && sdTooltip["thumbnail_id"].asUUID().notNull()) + { + // go straight for thumbnail regardless of type + // TODO: make a tooltip factory? + return LLUICtrlFactory::create(p); + } + + LLInventoryType::EType eInvType = (sdTooltip.has("inv_type")) ? (LLInventoryType::EType)sdTooltip["inv_type"].asInteger() : LLInventoryType::IT_NONE; + switch (eInvType) + { + case LLInventoryType::IT_CATEGORY: + { + if (sdTooltip.has("item_id")) + { + const LLUUID idCategory = sdTooltip["item_id"].asUUID(); + LLViewerInventoryCategory* cat = gInventory.getCategory(idCategory); + if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT) + { + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + // Not LLIsOfAssetType, because we allow links + LLIsTextureType f; + gInventory.getDirectDescendentsOf(idCategory, cats, items, f); + + // Exactly one texture found => show the texture tooltip + if (1 == items.size()) + { + LLViewerInventoryItem* item = items.front(); + if (item && item->getIsLinkType()) + { + item = item->getLinkedItem(); + } + if (item) + { + // Note: LLFloaterChangeItemThumbnail will attempt to write this + // into folder's thumbnail id when opened + p.create_params.getValue()["thumbnail_id"] = item->getAssetUUID(); + return LLUICtrlFactory::create(p); + } + } + } + } + + // No or more than one texture found => show default tooltip + return LLUICtrlFactory::create(p); + } + default: + return LLUICtrlFactory::create(p); + } +} + +// ============================================================================ +// LLTexturePreviewView helper class +// + +class LLTexturePreviewView : public LLView +{ +public: + LLTexturePreviewView(const LLView::Params& p); + ~LLTexturePreviewView(); + +public: + void draw() override; + +public: + void setImageFromAssetId(const LLUUID& idAsset); + void setImageFromItemId(const LLUUID& idItem); + +protected: + LLPointer m_Image; + S32 mImageBoostLevel = LLGLTexture::BOOST_NONE; + std::string mLoadingText; +}; + + +LLTexturePreviewView::LLTexturePreviewView(const LLView::Params& p) + : LLView(p) +{ + mLoadingText = LLTrans::getString("texture_loading"); +} + +LLTexturePreviewView::~LLTexturePreviewView() +{ + if (m_Image) + { + m_Image->setBoostLevel(mImageBoostLevel); + m_Image = nullptr; + } +} + +void LLTexturePreviewView::draw() +{ + LLView::draw(); + + if (m_Image) + { + LLRect rctClient = getLocalRect(); + + if (4 == m_Image->getComponents()) + { + const LLColor4 color(.098f, .098f, .098f); + gl_rect_2d(rctClient, color, TRUE); + } + gl_draw_scaled_image(rctClient.mLeft, rctClient.mBottom, rctClient.getWidth(), rctClient.getHeight(), m_Image); + + bool isLoading = (!m_Image->isFullyLoaded()) && (m_Image->getDiscardLevel() > 0); + if (isLoading) + LLFontGL::getFontSansSerif()->renderUTF8(mLoadingText, 0, llfloor(rctClient.mLeft + 3), llfloor(rctClient.mTop - 25), LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); + m_Image->addTextureStats((isLoading) ? MAX_IMAGE_AREA : (F32)(rctClient.getWidth() * rctClient.getHeight())); + } +} + +void LLTexturePreviewView::setImageFromAssetId(const LLUUID& idAsset) +{ + m_Image = LLViewerTextureManager::getFetchedTexture(idAsset, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + if (m_Image) + { + mImageBoostLevel = m_Image->getBoostLevel(); + m_Image->setBoostLevel(LLGLTexture::BOOST_PREVIEW); + m_Image->forceToSaveRawImage(0); + if ( (!m_Image->isFullyLoaded()) && (!m_Image->hasFetcher()) ) + { + if (m_Image->isInFastCacheList()) + { + m_Image->loadFromFastCache(); + } + gTextureList.forceImmediateUpdate(m_Image); + } + } +} + +void LLTexturePreviewView::setImageFromItemId(const LLUUID& idItem) +{ + const LLViewerInventoryItem* pItem = gInventory.getItem(idItem); + setImageFromAssetId( (pItem) ? pItem->getAssetUUID() : LLUUID::null ); +} + +// ============================================================================ +// LLTextureToolTip class +// + +LLTextureToolTip::LLTextureToolTip(const LLToolTip::Params& p) + : LLToolTip(p) + , mPreviewView(nullptr) + , mPreviewSize(256) +{ + mMaxWidth = llmax(mMaxWidth, mPreviewSize); + + // Currently has to share params with LLToolTip, override values + setBackgroundColor(LLColor4::black); + setTransparentColor(LLColor4::black); + setBorderVisible(true); +} + +LLTextureToolTip::~LLTextureToolTip() +{ +} + +void LLTextureToolTip::initFromParams(const LLToolTip::Params& p) +{ + LLToolTip::initFromParams(p); + + // Create and add the preview control + LLView::Params p_preview; + p_preview.name = "texture_preview"; + LLRect rctPreview; + rctPreview.setOriginAndSize(mPadding, mTextBox->getRect().mTop, mPreviewSize, mPreviewSize); + p_preview.rect = rctPreview; + mPreviewView = LLUICtrlFactory::create(p_preview); + addChild(mPreviewView); + + // Parse the control params + const LLSD& sdTextureParams = p.create_params; + if (sdTextureParams.has("thumbnail_id")) + { + mPreviewView->setImageFromAssetId(sdTextureParams["thumbnail_id"].asUUID()); + } + else if (sdTextureParams.has("item_id")) + { + mPreviewView->setImageFromItemId(sdTextureParams["item_id"].asUUID()); + } + + // Currently has to share params with LLToolTip, override values manually + // Todo: provide from own params instead, may be like object inspector does it + LLViewBorder::Params border_params; + border_params.border_thickness(LLPANEL_BORDER_WIDTH); + border_params.highlight_light_color(LLColor4::white); + border_params.highlight_dark_color(LLColor4::white); + border_params.shadow_light_color(LLColor4::white); + border_params.shadow_dark_color(LLColor4::white); + addBorder(border_params); + setBorderVisible(true); + + setBackgroundColor(LLColor4::black); + setBackgroundVisible(true); + setBackgroundOpaque(true); + setBackgroundImage(nullptr); + setTransparentImage(nullptr); + + mTextBox->setColor(LLColor4::white); + + snapToChildren(); +} + +// ============================================================================ diff --git a/indra/newview/llinspecttexture.h b/indra/newview/llinspecttexture.h new file mode 100644 index 0000000000..ff0d80b825 --- /dev/null +++ b/indra/newview/llinspecttexture.h @@ -0,0 +1,49 @@ +/** + * @file llinspecttexture.h + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include "lltooltip.h" + +class LLTexturePreviewView; + +namespace LLInspectTextureUtil +{ + LLToolTip* createInventoryToolTip(LLToolTip::Params p); +} + +class LLTextureToolTip : public LLToolTip +{ +public: + LLTextureToolTip(const LLToolTip::Params& p); + ~LLTextureToolTip(); + +public: + void initFromParams(const LLToolTip::Params& p) override; + +protected: + LLTexturePreviewView* mPreviewView; + S32 mPreviewSize; +}; diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 3a1c7f65f3..3a51eeaa9d 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -41,7 +41,6 @@ #include "llfloateropenobject.h" #include "llfloaterreg.h" #include "llfloatermarketplacelistings.h" -#include "llfloateroutfitphotopreview.h" #include "llfloatersidepanelcontainer.h" #include "llsidepanelinventory.h" #include "llfloaterworldmap.h" @@ -109,30 +108,14 @@ void copy_slurl_to_clipboard_callback_inv(const std::string& slurl); -typedef std::pair two_uuids_t; -typedef std::list two_uuids_list_t; - const F32 SOUND_GAIN = 1.0f; const F32 FOLDER_LOADING_MESSAGE_DELAY = 0.5f; // Seconds to wait before showing the LOADING... text in folder views -struct LLMoveInv -{ - LLUUID mObjectID; - LLUUID mCategoryID; - two_uuids_list_t mMoveList; - void (*mCallback)(S32, void*); - void* mUserData; -}; - using namespace LLOldEvents; // Function declarations -bool move_task_inventory_callback(const LLSD& notification, const LLSD& response, boost::shared_ptr); bool confirm_attachment_rez(const LLSD& notification, const LLSD& response); void teleport_via_landmark(const LLUUID& asset_id); -static BOOL can_move_to_outfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit); -static bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit); -static BOOL can_move_to_landmarks(LLInventoryItem* inv_item); // Function left unused from FIRE-7219 //static bool check_category(LLInventoryModel* model, // const LLUUID& cat_id, @@ -162,6 +145,12 @@ bool isMarketplaceSendAction(const std::string& action) return ("send_to_marketplace" == action); } +bool isPanelActive(const std::string& panel_name) +{ + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); + return (active_panel && (active_panel->getName() == panel_name)); +} + // Used by LLFolderBridge as callback for directory fetching recursion class LLRightClickInventoryFetchDescendentsObserver : public LLInventoryFetchDescendentsObserver { @@ -197,6 +186,65 @@ public: } }; +class LLPasteIntoFolderCallback: public LLInventoryCallback +{ +public: + LLPasteIntoFolderCallback(LLHandle& handle) + : mInventoryPanel(handle) + { + } + ~LLPasteIntoFolderCallback() + { + processItems(); + } + + void fire(const LLUUID& inv_item) + { + mChangedIds.push_back(inv_item); + } + + void processItems() + { + LLInventoryPanel* panel = mInventoryPanel.get(); + bool has_elements = false; + for (LLUUID& inv_item : mChangedIds) + { + LLInventoryItem* item = gInventory.getItem(inv_item); + if (item && panel) + { + LLUUID root_id = panel->getRootFolderID(); + + if (inv_item == root_id) + { + return; + } + + LLFolderViewItem* item = panel->getItemByID(inv_item); + if (item) + { + if (!has_elements) + { + panel->clearSelection(); + panel->getRootFolder()->clearSelection(); + panel->getRootFolder()->requestArrange(); + panel->getRootFolder()->update(); + has_elements = true; + } + panel->getRootFolder()->changeSelection(item, TRUE); + } + } + } + + if (has_elements) + { + panel->getRootFolder()->scrollToShowSelection(); + } + } +private: + LLHandle mInventoryPanel; + std::vector mChangedIds; +}; + // +=================================================+ // | LLInvFVBridge | // +=================================================+ @@ -236,58 +284,17 @@ const std::string& LLInvFVBridge::getDisplayName() const std::string LLInvFVBridge::getSearchableDescription() const { - const LLInventoryModel* model = getInventoryModel(); - if (model) - { - const LLInventoryItem *item = model->getItem(mUUID); - if(item) - { - std::string desc = item->getDescription(); - LLStringUtil::toUpper(desc); - return desc; - } - } - return LLStringUtil::null; + return get_searchable_description(getInventoryModel(), mUUID); } std::string LLInvFVBridge::getSearchableCreatorName() const { - const LLInventoryModel* model = getInventoryModel(); - if (model) - { - const LLInventoryItem *item = model->getItem(mUUID); - if(item) - { - LLAvatarName av_name; - // Avoid null id requests entering name cache - // if (LLAvatarNameCache::get(item->getCreatorUUID(), &av_name)) - const auto& creatorId {item->getCreatorUUID()}; - if ( creatorId.notNull() && LLAvatarNameCache::get(creatorId, &av_name) ) - // - { - std::string username = av_name.getUserName(); - LLStringUtil::toUpper(username); - return username; - } - } - } - return LLStringUtil::null; + return get_searchable_creator_name(getInventoryModel(), mUUID); } std::string LLInvFVBridge::getSearchableUUIDString() const { - const LLInventoryModel* model = getInventoryModel(); - if (model) - { - const LLViewerInventoryItem *item = model->getItem(mUUID); - if(item /*&& (item->getIsFullPerm() || gAgent.isGodlikeWithoutAdminMenuFakery())*/) // Keep it FS-legacy style since we had it like this for ages - { - std::string uuid = item->getAssetUUID().asString(); - LLStringUtil::toUpper(uuid); - return uuid; - } - } - return LLStringUtil::null; + return get_searchable_UUID(getInventoryModel(), mUUID); } // Zi's extended inventory search @@ -373,7 +380,7 @@ BOOL LLInvFVBridge::cutToClipboard() const LLInventoryObject* obj = gInventory.getObject(mUUID); if (obj && isItemMovable() && isItemRemovable()) { - const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); const BOOL cut_from_marketplacelistings = gInventory.isObjectDescendentOf(mUUID, marketplacelistings_id); if (cut_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(mUUID) || @@ -456,6 +463,32 @@ void LLInvFVBridge::showProperties() } } +void LLInvFVBridge::navigateToFolder(bool new_window, bool change_mode) +{ + if(new_window) + { + mInventoryPanel.get()->openSingleViewInventory(mUUID); + } + else + { + if(change_mode) + { + LLInventoryPanel::setSFViewAndOpenFolder(mInventoryPanel.get(), mUUID); + } + else + { + LLInventorySingleFolderPanel* panel = dynamic_cast(mInventoryPanel.get()); + if (!panel || !getInventoryModel() || mUUID.isNull()) + { + return; + } + + panel->changeFolderRoot(mUUID); + } + + } +} + void LLInvFVBridge::removeBatch(std::vector& batch) { // Deactivate gestures when moving them into Trash @@ -849,33 +882,22 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, menuentry_vec_t &disabled_items, U32 flags) { const LLInventoryObject *obj = getInventoryObject(); + bool single_folder_root = (mRoot == NULL); if (obj) { -// [SL:KB] - Patch: Inventory-Links | Checked: 2010-04-12 (Catznip-2.0) items.push_back(std::string("Copy Separator")); - - items.push_back(std::string("Cut")); - if (!isItemMovable() || !isItemRemovable() || isLibraryItem()) - { - disabled_items.push_back(std::string("Cut")); - } - items.push_back(std::string("Copy")); +// [SL:KB] - Patch: Inventory-Links | Checked: 2010-04-12 (Catznip-2.0) if (!isItemCopyable() && !isItemLinkable()) +// [/SL:KB] + //if (!isItemCopyable()) { disabled_items.push_back(std::string("Copy")); } -// [/SL:KB] - //items.push_back(std::string("Copy Separator")); - //items.push_back(std::string("Copy")); - //if (!isItemCopyable()) - //{ - // disabled_items.push_back(std::string("Copy")); - //} - if (isAgentInventory()) + if (isAgentInventory() && !single_folder_root) { items.push_back(std::string("New folder from selected")); items.push_back(std::string("Subfolder Separator")); @@ -909,7 +931,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, items.push_back(std::string("Find Links")); } - if (!isInboxFolder()) + if (!isInboxFolder() && !single_folder_root) { items.push_back(std::string("Rename")); // Locked folder @@ -920,14 +942,20 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, disabled_items.push_back(std::string("Rename")); } } - + + items.push_back(std::string("thumbnail")); + if (isLibraryItem()) + { + disabled_items.push_back(std::string("thumbnail")); + } + + LLViewerInventoryItem *inv_item = gInventory.getItem(mUUID); if (show_asset_id) { items.push_back(std::string("Copy Asset UUID")); bool is_asset_knowable = false; - LLViewerInventoryItem* inv_item = gInventory.getItem(mUUID); if (inv_item) { is_asset_knowable = LLAssetType::lookupIsAssetIDKnowable(inv_item->getType()); @@ -939,13 +967,14 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, disabled_items.push_back(std::string("Copy Asset UUID")); } } -// [SL:KB] - Patch: Inventory-Links | Checked: 2010-04-12 (Catznip-2.0) - //items.push_back(std::string("Cut")); - //if (!isItemMovable() || !isItemRemovable()) - //{ - // disabled_items.push_back(std::string("Cut")); - //} -// [/SL:KB] + + if(!single_folder_root) + { + items.push_back(std::string("Cut")); + if (!isItemMovable() || !isItemRemovable()) + { + disabled_items.push_back(std::string("Cut")); + } if (canListOnMarketplace() && !isMarketplaceListingsFolder() && !isInboxFolder()) { @@ -962,6 +991,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, } } } + } } } @@ -998,21 +1028,15 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, items.push_back(std::string("Paste Separator")); - addDeleteContextMenuOptions(items, disabled_items); + if(!single_folder_root) + { + addDeleteContextMenuOptions(items, disabled_items); + } - // If multiple items are selected, disable properties (if it exists). - // Old, standalone properties floater - //if ((flags & FIRST_SELECTED_ITEM) == 0) - //{ - // disabled_items.push_back(std::string("Properties")); - //} - // - - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); // Don't offer "Show in Main View" for folders opened in separate inventory views // as there are no tabs to switch to - // if (active_panel && (active_panel->getName() != "All Items")) - if (active_panel && (active_panel->getName() != "All Items") && (active_panel->getName() != "inv_panel")) + //if (!isPanelActive("All Items") && !isPanelActive("comb_single_folder_inv")) + if (!isPanelActive("All Items") && !isPanelActive("comb_single_folder_inv") && !isPanelActive("inv_panel")) // { items.push_back(std::string("Show in Main Panel")); @@ -1122,7 +1146,7 @@ void LLInvFVBridge::addDeleteContextMenuOptions(menuentry_vec_t &items, items.push_back(std::string("Delete")); - if (!isItemRemovable()) + if (!isItemRemovable() || isPanelActive("Favorite Items")) { disabled_items.push_back(std::string("Delete")); } @@ -1371,6 +1395,16 @@ BOOL LLInvFVBridge::isLinkedObjectInTrash() const return FALSE; } +bool LLInvFVBridge::isItemInOutfits() const +{ + const LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + + const LLUUID my_outfits_cat = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + return isCOFFolder() || (my_outfits_cat == mUUID) || model->isObjectDescendentOf(mUUID, my_outfits_cat); +} + BOOL LLInvFVBridge::isLinkedObjectMissing() const { const LLInventoryObject *obj = getInventoryObject(); @@ -1451,7 +1485,7 @@ BOOL LLInvFVBridge::isLockedFolder(bool ignore_setting /*= false*/) const // *TODO : Suppress isInboxFolder() once Merchant Outbox is fully deprecated BOOL LLInvFVBridge::isInboxFolder() const { - const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX, false); + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); if (inbox_id.isNull()) { @@ -1463,7 +1497,7 @@ BOOL LLInvFVBridge::isInboxFolder() const BOOL LLInvFVBridge::isMarketplaceListingsFolder() const { - const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (folder_id.isNull()) { @@ -1763,7 +1797,7 @@ bool LLInvFVBridge::canListOnMarketplaceNow() const { std::string error_msg; LLInventoryModel* model = getInventoryModel(); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (marketplacelistings_id.notNull()) { LLViewerInventoryCategory * master_folder = model->getCategory(marketplacelistings_id); @@ -1861,6 +1895,12 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action) restoreItem(); return; } + else if ("thumbnail" == action) + { + LLSD data(mUUID); + LLFloaterReg::showInstance("change_item_thumbnail", data); + return; + } else if ("copy_uuid" == action) { // Single item only @@ -1915,7 +1955,7 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action) { LLInventoryItem* itemp = model->getItem(mUUID); if (!itemp) return; - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); // Note: For a single item, if it's not a copy, then it's a move move_item_to_marketplacelistings(itemp, marketplacelistings_id, ("copy_to_marketplace_listings" == action)); } @@ -2175,9 +2215,14 @@ std::string LLItemBridge::getLabelSuffix() const { // String table is loaded before login screen and inventory items are // loaded after login, so LLTrans should be ready. + // Keep it the old way please + //static std::string NO_COPY = LLTrans::getString("no_copy_lbl"); + //static std::string NO_MOD = LLTrans::getString("no_modify_lbl"); + //static std::string NO_XFER = LLTrans::getString("no_transfer_lbl"); static std::string NO_COPY = LLTrans::getString("no_copy"); static std::string NO_MOD = LLTrans::getString("no_modify"); static std::string NO_XFER = LLTrans::getString("no_transfer"); + // static std::string LINK = LLTrans::getString("link"); static std::string BROKEN_LINK = LLTrans::getString("broken_link"); std::string suffix; @@ -2198,17 +2243,20 @@ std::string LLItemBridge::getLabelSuffix() const BOOL copy = item->getPermissions().allowCopyBy(gAgent.getID()); if (!copy) { + //suffix += " "; // Keep it the old way please suffix += NO_COPY; } BOOL mod = item->getPermissions().allowModifyBy(gAgent.getID()); if (!mod) { - suffix += NO_MOD; + //suffix += suffix.empty() ? " " : ","; // Keep it the old way please + suffix += NO_MOD; } BOOL xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); if (!xfer) { + //suffix += suffix.empty() ? " " : ","; // Keep it the old way please suffix += NO_XFER; } } @@ -2413,6 +2461,21 @@ LLViewerInventoryItem* LLItemBridge::getItem() const return item; } +const LLUUID& LLItemBridge::getThumbnailUUID() const +{ + LLViewerInventoryItem* item = NULL; + LLInventoryModel* model = getInventoryModel(); + if(model) + { + item = (LLViewerInventoryItem*)model->getItem(mUUID); + } + if (item) + { + return item->getThumbnailUUID(); + } + return LLUUID::null; +} + BOOL LLItemBridge::isItemPermissive() const { LLViewerInventoryItem* item = getItem(); @@ -2510,12 +2573,32 @@ void LLFolderBridge::buildDisplayName() const std::string LLFolderBridge::getLabelSuffix() const { + static LLCachedControl xui_debug(gSavedSettings, "DebugShowXUINames", 0); + if (mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= FOLDER_LOADING_MESSAGE_DELAY) { return llformat(" (%s) ", LLTrans::getString("LoadingData").c_str()); } std::string suffix = ""; - if(mShowDescendantsCount) + if (xui_debug) + { + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(getUUID(), cats, items); + + LLViewerInventoryCategory* cat = gInventory.getCategory(getUUID()); + if (cat) + { + LLStringUtil::format_map_t args; + args["[FOLDER_COUNT]"] = llformat("%d", cats->size()); + args["[ITEMS_COUNT]"] = llformat("%d", items->size()); + args["[VERSION]"] = llformat("%d", cat->getVersion()); + args["[VIEWER_DESCENDANT_COUNT]"] = llformat("%d", cats->size() + items->size()); + args["[SERVER_DESCENDANT_COUNT]"] = llformat("%d", cat->getDescendentCount()); + suffix = " " + LLTrans::getString("InventoryFolderDebug", args); + } + } + else if(mShowDescendantsCount) { LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; @@ -2552,6 +2635,16 @@ LLFontGL::StyleFlags LLFolderBridge::getLabelStyle() const return LLFontGL::NORMAL; } +const LLUUID& LLFolderBridge::getThumbnailUUID() const +{ + LLViewerInventoryCategory* cat = getCategory(); + if (cat) + { + return cat->getThumbnailUUID(); + } + return LLUUID::null; +} + void LLFolderBridge::update() { // we know we have children but haven't fetched them (doesn't obey filter) @@ -2777,7 +2870,8 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, BOOL drop, std::string& tooltip_msg, BOOL is_link, - BOOL user_confirm) + BOOL user_confirm, + LLPointer cb) { LLInventoryModel* model = getInventoryModel(); @@ -2797,8 +2891,8 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, if (!filter) return false; const LLUUID &cat_id = inv_cat->getUUID(); - const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); const LLUUID from_folder_uuid = inv_cat->getParentUUID(); const BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); @@ -2816,11 +2910,11 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, if (is_agent_inventory) { - const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH, false); + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); // FIRE-1392: Allow dragging all asset types into Landmarks folder - //const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK, false); - const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); - const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND, false); + //const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); const BOOL move_is_into_trash = (mUUID == trash_id) || model->isObjectDescendentOf(mUUID, trash_id); const BOOL move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); @@ -3098,7 +3192,7 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, { // Category can contains objects, // create a new folder and populate it with links to original objects - dropToMyOutfits(inv_cat); + dropToMyOutfits(inv_cat, cb); } // if target is current outfit folder we use link else if (move_is_into_current_outfit && @@ -3108,14 +3202,16 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, // traverse category and add all contents to currently worn. BOOL append = true; LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, false, append); + if (cb) cb->fire(inv_cat->getUUID()); } else if (move_is_into_marketplacelistings) { move_folder_to_marketplacelistings(inv_cat, mUUID); + if (cb) cb->fire(inv_cat->getUUID()); } else { - if (model->isObjectDescendentOf(cat_id, model->findCategoryUUIDForType(LLFolderType::FT_INBOX, false))) + if (model->isObjectDescendentOf(cat_id, model->findCategoryUUIDForType(LLFolderType::FT_INBOX))) { set_dad_inbox_object(cat_id); } @@ -3127,6 +3223,7 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, (LLViewerInventoryCategory*)inv_cat, mUUID, move_is_into_trash); + if (cb) cb->fire(inv_cat->getUUID()); } if (move_is_from_marketplacelistings) { @@ -3145,14 +3242,20 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); if (version_folder_id.notNull()) { - LLViewerInventoryCategory* cat = gInventory.getCategory(version_folder_id); - if (!validate_marketplacelistings(cat,NULL)) + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [version_folder_id](bool result) { - LLMarketplaceData::instance().activateListing(version_folder_id,false); + if (!result) + { + LLMarketplaceData::instance().activateListing(version_folder_id, false); + } } + ); } // In all cases, update the listing we moved from so suffix are updated update_marketplace_category(from_folder_uuid); + if (cb) cb->fire(inv_cat->getUUID()); } } } @@ -3166,7 +3269,22 @@ BOOL LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, } else { - accept = move_inv_category_world_to_agent(cat_id, mUUID, drop, NULL, NULL, filter); + // Todo: fix me. moving from task inventory doesn't have a completion callback, + // yet making a copy creates new item id so this doesn't work right + std::function callback = [cb](S32, void*, const LLMoveInv* move_inv) mutable + { + two_uuids_list_t::const_iterator move_it; + for (move_it = move_inv->mMoveList.begin(); + move_it != move_inv->mMoveList.end(); + ++move_it) + { + if (cb) + { + cb->fire(move_it->second); + } + } + }; + accept = move_inv_category_world_to_agent(cat_id, mUUID, drop, callback, NULL, filter); } } else if (LLToolDragAndDrop::SOURCE_LIBRARY == source) @@ -3237,7 +3355,7 @@ void warn_move_inventory(LLViewerObject* object, boost::shared_ptr mo BOOL move_inv_category_world_to_agent(const LLUUID& object_id, const LLUUID& category_id, BOOL drop, - void (*callback)(S32, void*), + std::function callback, void* user_data, LLInventoryFilter* filter) { @@ -3540,6 +3658,12 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) return; } + else if ("thumbnail" == action) + { + LLSD data(mUUID); + LLFloaterReg::showInstance("change_item_thumbnail", data); + return; + } else if ("paste" == action) { pasteFromClipboard(); @@ -3621,18 +3745,26 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) if (depth_nesting_in_marketplace(mUUID) == 1) { LLUUID version_folder_id = LLMarketplaceData::instance().getVersionFolder(mUUID); - LLViewerInventoryCategory* cat = gInventory.getCategory(version_folder_id); mMessage = ""; - if (!validate_marketplacelistings(cat,boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3))) + + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [this](bool result) { - LLSD subs; - subs["[ERROR_CODE]"] = mMessage; - LLNotificationsUtil::add("MerchantListingFailed", subs); - } - else - { - LLMarketplaceData::instance().activateListing(mUUID,true); - } + // todo: might need to ensure bridge/mUUID exists or this will cause crashes + if (!result) + { + LLSD subs; + subs["[ERROR_CODE]"] = mMessage; + LLNotificationsUtil::add("MerchantListingFailed", subs); + } + else + { + LLMarketplaceData::instance().activateListing(mUUID, true); + } + }, + boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3) + ); } return; } @@ -3640,18 +3772,27 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) { if (depth_nesting_in_marketplace(mUUID) == 2) { - LLInventoryCategory* category = gInventory.getCategory(mUUID); mMessage = ""; - if (!validate_marketplacelistings(category,boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3),false,2)) + + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + mUUID, + [this](bool result) { - LLSD subs; - subs["[ERROR_CODE]"] = mMessage; - LLNotificationsUtil::add("MerchantFolderActivationFailed", subs); - } - else - { - LLMarketplaceData::instance().setVersionFolder(category->getParentUUID(), mUUID); - } + if (!result) + { + LLSD subs; + subs["[ERROR_CODE]"] = mMessage; + LLNotificationsUtil::add("MerchantFolderActivationFailed", subs); + } + else + { + LLInventoryCategory* category = gInventory.getCategory(mUUID); + LLMarketplaceData::instance().setVersionFolder(category->getParentUUID(), mUUID); + } + }, + boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3), + false, + 2); } return; } @@ -3674,29 +3815,44 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) } else if ("marketplace_create_listing" == action) { - LLViewerInventoryCategory* cat = gInventory.getCategory(mUUID); mMessage = ""; - bool validates = validate_marketplacelistings(cat,boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3),false); - if (!validates) + + // first run vithout fix_hierarchy, second run with fix_hierarchy + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + mUUID, + [this](bool result) { - mMessage = ""; - validates = validate_marketplacelistings(cat,boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3),true); - if (validates) + if (!result) { - LLNotificationsUtil::add("MerchantForceValidateListing"); + mMessage = ""; + + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + mUUID, + [this](bool result) + { + if (result) + { + LLNotificationsUtil::add("MerchantForceValidateListing"); + LLMarketplaceData::instance().createListing(mUUID); + } + else + { + LLSD subs; + subs["[ERROR_CODE]"] = mMessage; + LLNotificationsUtil::add("MerchantListingFailed", subs); + } + }, + boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3), + true); } - } + else + { + LLMarketplaceData::instance().createListing(mUUID); + } + }, + boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3), + false); - if (!validates) - { - LLSD subs; - subs["[ERROR_CODE]"] = mMessage; - LLNotificationsUtil::add("MerchantListingFailed", subs); - } - else - { - LLMarketplaceData::instance().createListing(mUUID); - } return; } else if ("marketplace_disassociate_listing" == action) @@ -3755,7 +3911,7 @@ void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) { LLInventoryCategory * cat = gInventory.getCategory(mUUID); if (!cat) return; - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); move_folder_to_marketplacelistings(cat, marketplacelistings_id, ("move_to_marketplace_listings" != action), (("copy_or_move_to_marketplace_listings" == action))); } // FIRE-29342: Protect folder option @@ -4059,7 +4215,7 @@ void LLFolderBridge::pasteFromClipboard() LLInventoryModel* model = getInventoryModel(); if (model && isClipboardPasteable()) { - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); const BOOL paste_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); BOOL cut_from_marketplacelistings = FALSE; @@ -4120,11 +4276,11 @@ void LLFolderBridge::perform_pasteFromClipboard() LLInventoryModel* model = getInventoryModel(); if (model && isClipboardPasteable()) { - const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); - const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE, false); - const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); - const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND, false); + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); const BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); const BOOL move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); @@ -4135,6 +4291,13 @@ void LLFolderBridge::perform_pasteFromClipboard() std::vector objects; LLClipboard::instance().pasteFromClipboard(objects); + + LLPointer cb = NULL; + LLInventoryPanel* panel = mInventoryPanel.get(); + if (panel->getRootFolder()->isSingleFolderMode() && panel->getRootFolderID() == mUUID) + { + cb = new LLPasteIntoFolderCallback(mInventoryPanel); + } LLViewerInventoryCategory * dest_folder = getCategory(); if (move_is_into_marketplacelistings) @@ -4225,7 +4388,7 @@ void LLFolderBridge::perform_pasteFromClipboard() if (move_is_into_outfit && item && can_move_to_outfit(item, move_is_into_current_outfit)) // { - dropToOutfit(item, move_is_into_current_outfit); + dropToOutfit(item, move_is_into_current_outfit, cb); } else if (/*move_is_into_my_outfits &&*/ LLAssetType::AT_CATEGORY == obj->getType()) // Unable to copy&paste into outfits anymore { @@ -4233,7 +4396,7 @@ void LLFolderBridge::perform_pasteFromClipboard() U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit"); if (cat && can_move_to_my_outfits(model, cat, max_items_to_wear)) { - dropToMyOutfits(cat); + dropToMyOutfits(cat, cb); } else { @@ -4249,7 +4412,7 @@ void LLFolderBridge::perform_pasteFromClipboard() { if (item && can_move_to_outfit(item, move_is_into_current_outfit)) { - dropToOutfit(item, move_is_into_current_outfit); + dropToOutfit(item, move_is_into_current_outfit, cb); } else { @@ -4268,11 +4431,12 @@ void LLFolderBridge::perform_pasteFromClipboard() { //changeItemParent() implicity calls dirtyFilter changeItemParent(model, viitem, parent_id, FALSE); + if (cb) cb->fire(item_id); } } else { - dropToFavorites(item); + dropToFavorites(item, cb); } } } @@ -4300,6 +4464,7 @@ void LLFolderBridge::perform_pasteFromClipboard() //changeCategoryParent() implicity calls dirtyFilter changeCategoryParent(model, vicat, parent_id, FALSE); } + if (cb) cb->fire(item_id); } } else @@ -4321,6 +4486,7 @@ void LLFolderBridge::perform_pasteFromClipboard() //changeItemParent() implicity calls dirtyFilter changeItemParent(model, viitem, parent_id, FALSE); } + if (cb) cb->fire(item_id); } } } @@ -4341,6 +4507,7 @@ void LLFolderBridge::perform_pasteFromClipboard() { copy_inventory_category(model, vicat, parent_id); } + if (cb) cb->fire(item_id); } } else @@ -4356,12 +4523,14 @@ void LLFolderBridge::perform_pasteFromClipboard() // Stop pasting into the marketplace as soon as we get an error break; } + if (cb) cb->fire(item_id); } // [SL:KB] - Patch: Inventory-Links | Checked: 2010-04-12 (Catznip-2.2.0a) | Added: Catznip-2.0.0a // else if (item->getIsLinkType()) // { -// link_inventory_object(parent_id, item_id, -// LLPointer(NULL)); +// link_inventory_object(parent_id, +// item_id, +// cb); // } // [/SL:KB] else @@ -4376,7 +4545,7 @@ void LLFolderBridge::perform_pasteFromClipboard() item->getUUID(), parent_id, std::string(), - LLPointer(NULL)); + cb); // [SL:KB] - Patch: Inventory-Links | Checked: 2010-04-12 (Catznip-2.2.0a) | Added: Catznip-2.0.0a } else if (LLAssetType::lookupIsLinkType(item->getActualType())) @@ -4385,7 +4554,7 @@ void LLFolderBridge::perform_pasteFromClipboard() obj_array.push_back(LLConstPointer(item)); link_inventory_array(parent_id, obj_array, - LLPointer(NULL)); + cb); } // [/SL:KB] } @@ -4404,9 +4573,9 @@ void LLFolderBridge::pasteLinkFromClipboard() LLInventoryModel* model = getInventoryModel(); if(model) { - const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); - const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); const BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); const BOOL move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); @@ -4423,6 +4592,14 @@ void LLFolderBridge::pasteLinkFromClipboard() std::vector objects; LLClipboard::instance().pasteFromClipboard(objects); + + LLPointer cb = NULL; + LLInventoryPanel* panel = mInventoryPanel.get(); + if (panel->getRootFolder()->isSingleFolderMode()) + { + cb = new LLPasteIntoFolderCallback(mInventoryPanel); + } + for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) @@ -4433,12 +4610,12 @@ void LLFolderBridge::pasteLinkFromClipboard() LLInventoryItem *item = model->getItem(object_id); if (item && can_move_to_outfit(item, move_is_into_current_outfit)) { - dropToOutfit(item, move_is_into_current_outfit); + dropToOutfit(item, move_is_into_current_outfit, cb); } } else if (LLConstPointer obj = model->getObject(object_id)) { - link_inventory_object(parent_id, obj, LLPointer(NULL)); + link_inventory_object(parent_id, obj, cb); } } // Change mode to paste for next paste @@ -4476,8 +4653,8 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); const LLUUID &favorites = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - const LLUUID &marketplace_listings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); - const LLUUID &outfits_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); + const LLUUID &marketplace_listings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &outfits_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); // FIRE-11628: Option to delete broken links from AO folder if (mUUID == AOEngine::instance().getAOFolder()) @@ -4511,12 +4688,6 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items } disabled_items.push_back(std::string("New Folder")); - disabled_items.push_back(std::string("New Script")); - disabled_items.push_back(std::string("New Note")); - disabled_items.push_back(std::string("New Settings")); - disabled_items.push_back(std::string("New Gesture")); - disabled_items.push_back(std::string("New Clothes")); - disabled_items.push_back(std::string("New Body Parts")); disabled_items.push_back(std::string("upload_def")); } if (favorites == mUUID) @@ -4539,11 +4710,6 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) { disabled_items.push_back(std::string("New Folder")); - disabled_items.push_back(std::string("New Script")); - disabled_items.push_back(std::string("New Note")); - disabled_items.push_back(std::string("New Gesture")); - disabled_items.push_back(std::string("New Clothes")); - disabled_items.push_back(std::string("New Body Parts")); disabled_items.push_back(std::string("upload_def")); } if (marketplace_listings_id == mUUID) @@ -4553,14 +4719,14 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items disabled_items.push_back(std::string("Cut")); disabled_items.push_back(std::string("Delete")); } + + if (isPanelActive("Favorite Items")) + { + disabled_items.push_back(std::string("Delete")); + } if(trash_id == mUUID) { - bool is_recent_panel = false; - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); - if (active_panel && (active_panel->getName() == "Recent Items")) - { - is_recent_panel = true; - } + bool is_recent_panel = isPanelActive("Recent Items"); // This is the trash. items.push_back(std::string("Empty Trash")); @@ -4579,6 +4745,8 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items { disabled_items.push_back(std::string("Empty Trash")); } + + items.push_back(std::string("thumbnail")); } else if(isItemInTrash()) { @@ -4610,19 +4778,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items if (!isMarketplaceListingsFolder() && !model->isObjectDescendentOf(mUUID, outfits_id)) // { - items.push_back(std::string("New Script")); - items.push_back(std::string("New Note")); - items.push_back(std::string("New Gesture")); - items.push_back(std::string("New Clothes")); - items.push_back(std::string("New Body Parts")); - items.push_back(std::string("New Settings")); items.push_back(std::string("upload_def")); - - if (!LLEnvironment::instance().isInventoryEnabled()) - { - disabled_items.push_back("New Settings"); - } - } } getClipboardEntries(false, items, disabled_items, flags); @@ -4634,6 +4790,7 @@ void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items if (cat && (cat->getPreferredType() == LLFolderType::FT_OUTFIT)) { items.push_back(std::string("Rename")); + items.push_back(std::string("thumbnail")); addDeleteContextMenuOptions(items, disabled_items); // EXT-4030: disallow deletion of currently worn outfit @@ -4656,6 +4813,7 @@ 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")); + addOpenFolderMenuOptions(flags, items); } //Added by aura to force inventory pull on right-click to display folder options correctly. 07-17-06 @@ -4808,9 +4966,12 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& if(!category) return; const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - if (trash_id == mUUID) return; - if (isItemInTrash()) return; - + if ((trash_id == mUUID) || isItemInTrash()) + { + addOpenFolderMenuOptions(flags, items); + return; + } + if (!isItemRemovable()) { disabled_items.push_back(std::string("Delete")); @@ -4826,7 +4987,7 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& // [/SL:KB] // Only enable calling-card related options for non-system folders. - if (!is_system_folder && is_agent_inventory) + if (!is_system_folder && is_agent_inventory && (mRoot != NULL)) { LLIsType is_callingcard(LLAssetType::AT_CALLINGCARD); if (mCallingCards || checkFolderForContentsOfType(model, is_callingcard)) @@ -4846,6 +5007,14 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& disabled_items.push_back(std::string("New folder from selected")); } + //skip the rest options in single-folder mode + if (mRoot == NULL) + { + return; + } + + addOpenFolderMenuOptions(flags, items); + #ifndef LL_RELEASE_FOR_DOWNLOAD if (LLFolderType::lookupIsProtectedType(type) && is_agent_inventory) { @@ -4878,37 +5047,33 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& // Patch: ReplaceWornItemsOnly items.push_back(std::string("Wear Items")); // + if (!LLAppearanceMgr::instance().getCanAddToCOF(mUUID)) + { + disabled_items.push_back(std::string("Add To Outfit")); + } } items.push_back(std::string("Replace Outfit")); + //if (!LLAppearanceMgr::instance().getCanReplaceCOF(mUUID)) +// [SL:KB] - Patch: Appearance-Misc | Checked: 2010-11-24 (Catznip-2.4) + if ( ((is_outfit) && (!LLAppearanceMgr::instance().getCanReplaceCOF(mUUID))) || + ((!is_outfit) && (gAgentWearables.isCOFChangeInProgress())) ) +// [/SL:KB] + { + disabled_items.push_back(std::string("Replace Outfit")); + } } if (is_agent_inventory) { items.push_back(std::string("Folder Wearables Separator")); + // Note: If user tries to unwear "My Inventory", it's going to deactivate everything including gestures + // Might be safer to disable this for "My Inventory" items.push_back(std::string("Remove From Outfit")); - if (!LLAppearanceMgr::getCanRemoveFromCOF(mUUID)) - { - disabled_items.push_back(std::string("Remove From Outfit")); - } - } - //if (!LLAppearanceMgr::instance().getCanReplaceCOF(mUUID)) -// [SL:KB] - Patch: Appearance-Misc | Checked: 2010-11-24 (Catznip-2.4) - if ( ((is_outfit) && (!LLAppearanceMgr::instance().getCanReplaceCOF(mUUID))) || - ((!is_outfit) && (gAgentWearables.isCOFChangeInProgress())) ) -// [/SL:KB] - { - disabled_items.push_back(std::string("Replace Outfit")); - } -// [RLVa:KB] - Checked: RLVa-2.0.3 - // Block "Replace Current Outfit" if the user can't wear the new folder - if ( (RlvActions::isRlvEnabled()) && (RlvFolderLocks::instance().isLockedFolder(mUUID, RLV_LOCK_ADD)) ) - { - disabled_items.push_back(std::string("Replace Outfit")); - } -// [/RLVa:KB] - if (!LLAppearanceMgr::instance().getCanAddToCOF(mUUID)) - { - disabled_items.push_back(std::string("Add To Outfit")); + if (type != LLFolderType::FT_ROOT_INVENTORY // Unless COF is empty, whih shouldn't be, warrantied to have worn items + && !LLAppearanceMgr::getCanRemoveFromCOF(mUUID)) // expensive from root! + { + disabled_items.push_back(std::string("Remove From Outfit")); + } } items.push_back(std::string("Outfit Separator")); @@ -4941,6 +5106,20 @@ void LLFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) menu.arrangeAndClear(); } +void LLFolderBridge::addOpenFolderMenuOptions(U32 flags, menuentry_vec_t& items) +{ + if ((flags & ITEM_IN_MULTI_SELECTION) == 0) + { + items.push_back(std::string("open_in_new_window")); + items.push_back(std::string("Open Folder Separator")); + items.push_back(std::string("Copy Separator")); + if(isPanelActive("comb_single_folder_inv")) + { + items.push_back(std::string("open_in_current_window")); + } + } +} + bool LLFolderBridge::hasChildren() const { LLInventoryModel* model = getInventoryModel(); @@ -4957,6 +5136,18 @@ BOOL LLFolderBridge::dragOrDrop(MASK mask, BOOL drop, { LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; + static LLPointer drop_cb = NULL; + LLInventoryPanel* panel = mInventoryPanel.get(); + LLToolDragAndDrop* drop_tool = LLToolDragAndDrop::getInstance(); + if (drop + && panel->getRootFolder()->isSingleFolderMode() + && panel->getRootFolderID() == mUUID + && drop_tool->getCargoIndex() == 0) + { + drop_cb = new LLPasteIntoFolderCallback(mInventoryPanel); + } + + //LL_INFOS() << "LLFolderBridge::dragOrDrop()" << LL_ENDL; BOOL accept = FALSE; switch(cargo_type) @@ -4974,7 +5165,7 @@ BOOL LLFolderBridge::dragOrDrop(MASK mask, BOOL drop, case DAD_GESTURE: case DAD_MESH: case DAD_SETTINGS: - accept = dragItemIntoFolder(inv_item, drop, tooltip_msg); + accept = dragItemIntoFolder(inv_item, drop, tooltip_msg, TRUE, drop_cb); break; case DAD_LINK: // DAD_LINK type might mean one of two asset types: AT_LINK or AT_LINK_FOLDER. @@ -4988,12 +5179,12 @@ BOOL LLFolderBridge::dragOrDrop(MASK mask, BOOL drop, LLInventoryCategory* linked_category = gInventory.getCategory(inv_item->getLinkedUUID()); if (linked_category) { - accept = dragCategoryIntoFolder((LLInventoryCategory*)linked_category, drop, tooltip_msg, TRUE); + accept = dragCategoryIntoFolder((LLInventoryCategory*)linked_category, drop, tooltip_msg, TRUE, TRUE, drop_cb); } } else { - accept = dragItemIntoFolder(inv_item, drop, tooltip_msg); + accept = dragItemIntoFolder(inv_item, drop, tooltip_msg, TRUE, drop_cb); } break; case DAD_CATEGORY: @@ -5003,7 +5194,7 @@ BOOL LLFolderBridge::dragOrDrop(MASK mask, BOOL drop, } else { - accept = dragCategoryIntoFolder((LLInventoryCategory*)cargo_data, drop, tooltip_msg); + accept = dragCategoryIntoFolder((LLInventoryCategory*)cargo_data, drop, tooltip_msg, FALSE, TRUE, drop_cb); } break; case DAD_ROOT_CATEGORY: @@ -5013,6 +5204,11 @@ BOOL LLFolderBridge::dragOrDrop(MASK mask, BOOL drop, LL_WARNS() << "Unhandled cargo type for drag&drop " << cargo_type << LL_ENDL; break; } + + if (!drop || drop_tool->getCargoIndex() + 1 == drop_tool->getCargoCount()) + { + drop_cb = NULL; + } return accept; } @@ -5340,143 +5536,56 @@ bool move_task_inventory_callback(const LLSD& notification, const LLSD& response if (move_inv->mCallback) { - move_inv->mCallback(option, move_inv->mUserData); + move_inv->mCallback(option, move_inv->mUserData, move_inv.get()); } move_inv.reset(); //since notification will persist return false; } -// Returns true if the item can be moved to Current Outfit or any outfit folder. -static BOOL can_move_to_outfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit) +void drop_to_favorites_cb(const LLUUID& id, LLPointer cb1, LLPointer cb2) { - // FIRE-8434/BUG-988 Viewer crashes when copying and pasting an empty outfit folder - if( !inv_item ) - return FALSE; - // - - LLInventoryType::EType inv_type = inv_item->getInventoryType(); - if ((inv_type != LLInventoryType::IT_WEARABLE) && - (inv_type != LLInventoryType::IT_GESTURE) && - (inv_type != LLInventoryType::IT_ATTACHMENT) && - (inv_type != LLInventoryType::IT_OBJECT) && - (inv_type != LLInventoryType::IT_SNAPSHOT) && - (inv_type != LLInventoryType::IT_TEXTURE)) - { - return FALSE; - } - - U32 flags = inv_item->getFlags(); - if(flags & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) - { - return FALSE; - } - - if((inv_type == LLInventoryType::IT_TEXTURE) || (inv_type == LLInventoryType::IT_SNAPSHOT)) - { - return !move_is_into_current_outfit; - } - - if (move_is_into_current_outfit && get_is_item_worn(inv_item->getUUID())) - { - return FALSE; - } - - return TRUE; + cb1->fire(id); + cb2->fire(id); } -// Returns true if folder's content can be moved to Current Outfit or any outfit folder. -static bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit) -{ - LLInventoryModel::cat_array_t *cats; - LLInventoryModel::item_array_t *items; - model->getDirectDescendentsOf(inv_cat->getUUID(), cats, items); - - if (items->size() > wear_limit) - { - return false; - } - - if (items->size() == 0) - { - // Nothing to move(create) - return false; - } - - if (cats->size() > 0) - { - // We do not allow subfolders in outfits of "My Outfits" yet - return false; - } - - LLInventoryModel::item_array_t::iterator iter = items->begin(); - LLInventoryModel::item_array_t::iterator end = items->end(); - - while (iter != end) - { - LLViewerInventoryItem *item = *iter; - if (!can_move_to_outfit(item, false)) - { - return false; - } - iter++; - } - - return true; -} - -// Returns TRUE if item is a landmark or a link to a landmark -// and can be moved to Favorites or Landmarks folder. -static BOOL can_move_to_landmarks(LLInventoryItem* inv_item) -{ - // Need to get the linked item to know its type because LLInventoryItem::getType() - // returns actual type AT_LINK for links, not the asset type of a linked item. - if (LLAssetType::AT_LINK == inv_item->getType()) - { - LLInventoryItem* linked_item = gInventory.getItem(inv_item->getLinkedUUID()); - if (linked_item) - { - return LLAssetType::AT_LANDMARK == linked_item->getType(); - } - } - - return LLAssetType::AT_LANDMARK == inv_item->getType(); -} - -void LLFolderBridge::dropToFavorites(LLInventoryItem* inv_item) +void LLFolderBridge::dropToFavorites(LLInventoryItem* inv_item, LLPointer cb) { // use callback to rearrange favorite landmarks after adding // to have new one placed before target (on which it was dropped). See EXT-4312. - LLPointer cb = new AddFavoriteLandmarkCallback(); + LLPointer cb_fav = new AddFavoriteLandmarkCallback(); LLInventoryPanel* panel = mInventoryPanel.get(); LLFolderViewItem* drag_over_item = panel ? panel->getRootFolder()->getDraggingOverItem() : NULL; LLFolderViewModelItemInventory* view_model = drag_over_item ? static_cast(drag_over_item->getViewModelItem()) : NULL; if (view_model) { - cb.get()->setTargetLandmarkId(view_model->getUUID()); + cb_fav.get()->setTargetLandmarkId(view_model->getUUID()); } + LLPointer callback = cb_fav; + if (cb) + { + callback = new LLBoostFuncInventoryCallback(boost::bind(drop_to_favorites_cb, _1, cb, cb_fav)); + } + copy_inventory_item( gAgent.getID(), inv_item->getPermissions().getOwner(), inv_item->getUUID(), mUUID, std::string(), - cb); + callback); } -void LLFolderBridge::dropToOutfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit) +void LLFolderBridge::dropToOutfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit, LLPointer cb) { if((inv_item->getInventoryType() == LLInventoryType::IT_TEXTURE) || (inv_item->getInventoryType() == LLInventoryType::IT_SNAPSHOT)) { - const LLUUID &my_outifts_id = getInventoryModel()->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); + const LLUUID &my_outifts_id = getInventoryModel()->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); if(mUUID != my_outifts_id) { - LLFloaterOutfitPhotoPreview* photo_preview = LLFloaterReg::showTypedInstance("outfit_photo_preview", inv_item->getUUID()); - if(photo_preview) - { - photo_preview->setOutfitID(mUUID); - } + // Legacy: prior to thumbnails images in outfits were used for outfit gallery. + LLNotificationsUtil::add("ThumbnailOutfitPhoto"); } return; } @@ -5493,21 +5602,22 @@ void LLFolderBridge::dropToOutfit(LLInventoryItem* inv_item, BOOL move_is_into_c } } -void LLFolderBridge::dropToMyOutfits(LLInventoryCategory* inv_cat) +void LLFolderBridge::dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer cb) { // make a folder in the My Outfits directory. const LLUUID dest_id = getInventoryModel()->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); // Note: creation will take time, so passing folder id to callback is slightly unreliable, // but so is collecting and passing descendants' ids - inventory_func_type func = boost::bind(&LLFolderBridge::outfitFolderCreatedCallback, this, inv_cat->getUUID(), _1); + inventory_func_type func = boost::bind(&LLFolderBridge::outfitFolderCreatedCallback, this, inv_cat->getUUID(), _1, cb); gInventory.createNewCategory(dest_id, LLFolderType::FT_OUTFIT, inv_cat->getName(), - func); + func, + inv_cat->getThumbnailUUID()); } -void LLFolderBridge::outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id) +void LLFolderBridge::outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id, LLPointer cb) { LLInventoryModel::cat_array_t* categories; LLInventoryModel::item_array_t* items; @@ -5538,7 +5648,6 @@ void LLFolderBridge::outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID ca if (!link_array.empty()) { - LLPointer cb = NULL; link_inventory_array(cat_dest_id, link_array, cb); } } @@ -5571,7 +5680,8 @@ void LLFolderBridge::callback_dropCategoryIntoFolder(const LLSD& notification, c BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg, - BOOL user_confirm) + BOOL user_confirm, + LLPointer cb) { LLInventoryModel* model = getInventoryModel(); @@ -5588,12 +5698,12 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, LLInventoryFilter* filter = getInventoryFilter(); if (!filter) return false; - const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, false); - const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE, false); + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); // FIRE-1392: Allow dragging all asset types into Landmarks folder - //const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK, false); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); - const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); + //const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); const LLUUID from_folder_uuid = inv_item->getParentUUID(); const BOOL move_is_into_current_outfit = (mUUID == current_outfit_id); @@ -5614,7 +5724,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, LLViewerObject* object = NULL; if(LLToolDragAndDrop::SOURCE_AGENT == source) { - const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH, false); + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); const BOOL move_is_into_trash = (mUUID == trash_id) || model->isObjectDescendentOf(mUUID, trash_id); const BOOL move_is_outof_current_outfit = LLAppearanceMgr::instance().getIsInCOF(inv_item->getUUID()); @@ -5779,26 +5889,27 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, // (copy the item) else if (move_is_into_favorites) { - dropToFavorites(inv_item); + dropToFavorites(inv_item, cb); } // CURRENT OUTFIT or OUTFIT folder // (link the item) else if (move_is_into_current_outfit || move_is_into_outfit) { - dropToOutfit(inv_item, move_is_into_current_outfit); + dropToOutfit(inv_item, move_is_into_current_outfit, cb); } // MARKETPLACE LISTINGS folder // Move the item else if (move_is_into_marketplacelistings) { move_item_to_marketplacelistings(inv_item, mUUID); + if (cb) cb->fire(inv_item->getUUID()); } // NORMAL or TRASH folder // (move the item, restamp if into trash) else { // set up observer to select item once drag and drop from inbox is complete - if (gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX, false))) + if (gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX))) { set_dad_inbox_object(inv_item->getUUID()); } @@ -5808,6 +5919,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, (LLViewerInventoryItem*)inv_item, mUUID, move_is_into_trash); + if (cb) cb->fire(inv_item->getUUID()); } if (move_is_from_marketplacelistings) @@ -5816,11 +5928,15 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); if (version_folder_id.notNull()) { - LLViewerInventoryCategory* cat = gInventory.getCategory(version_folder_id); - if (!validate_marketplacelistings(cat,NULL)) + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [version_folder_id](bool result) { - LLMarketplaceData::instance().activateListing(version_folder_id,false); - } + if (!result) + { + LLMarketplaceData::instance().activateListing(version_folder_id, false); + } + }); } } @@ -5893,11 +6009,16 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, if (accept && drop) { + LLUUID item_id = inv_item->getUUID(); boost::shared_ptr move_inv (new LLMoveInv()); move_inv->mObjectID = inv_item->getParentUUID(); - two_uuids_t item_pair(mUUID, inv_item->getUUID()); + two_uuids_t item_pair(mUUID, item_id); move_inv->mMoveList.push_back(item_pair); - move_inv->mCallback = NULL; + if (cb) + { + move_inv->mCallback = [item_id, cb](S32, void*, const LLMoveInv* move_inv) mutable + { cb->fire(item_id); }; + } move_inv->mUserData = NULL; if(is_move) { @@ -5994,13 +6115,13 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, // (copy the item) if (move_is_into_favorites) { - dropToFavorites(inv_item); + dropToFavorites(inv_item, cb); } // CURRENT OUTFIT or OUTFIT folder // (link the item) else if (move_is_into_current_outfit || move_is_into_outfit) { - dropToOutfit(inv_item, move_is_into_current_outfit); + dropToOutfit(inv_item, move_is_into_current_outfit, cb); } else { @@ -6010,7 +6131,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, inv_item->getUUID(), mUUID, std::string(), - LLPointer(NULL)); + cb); } } } @@ -6629,7 +6750,7 @@ std::string LLCallingCardBridge::getLabelSuffix() const if( item && LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID()) ) { // FIRE-17715: Make "online" suffix in calling card folder localizable - //return LLItemBridge::getLabelSuffix() + " (online)"; + //return LLItemBridge::getLabelSuffix() + " online"; return LLItemBridge::getLabelSuffix() + " " + LLTrans::getString("CallingCardOnlineLabelSuffix"); // } @@ -8397,16 +8518,28 @@ class LLObjectBridgeAction: public LLInvFVBridgeAction public: virtual void doIt() { - /* - LLFloaterReg::showInstance("properties", mUUID); - */ - LLInvFVBridgeAction::doIt(); + attachOrDetach(); } virtual ~LLObjectBridgeAction(){} protected: LLObjectBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} + void attachOrDetach(); }; +void LLObjectBridgeAction::attachOrDetach() +{ + if (get_is_item_worn(mUUID)) + { + LLAppearanceMgr::instance().removeItemFromAvatar(mUUID); + } + else + { + // Double-click add/replace option + //LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, false); // Don't replace if adding. + LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, !gSavedSettings.getBOOL("FSDoubleClickAddInventoryObjects")); // Don't replace if adding. + } +} + class LLLSLTextBridgeAction: public LLInvFVBridgeAction { friend class LLInvFVBridgeAction; @@ -8465,21 +8598,19 @@ void LLWearableBridgeAction::wearOnAvatar() LLViewerInventoryItem* item = getItem(); if(item) { - // FIRE-303: Double-click remove wearable - //LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, true); - bool is_bodypart = item->getType() == LLAssetType::AT_BODYPART; - if (get_is_item_worn(mUUID)) - { - if (!is_bodypart) - { - LLAppearanceMgr::instance().removeItemFromAvatar(mUUID); - } - } - else - { - LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, (is_bodypart || !gSavedSettings.getBOOL("FSDoubleClickAddInventoryClothing"))); - } - // + if (get_is_item_worn(mUUID)) + { + if(item->getType() != LLAssetType::AT_BODYPART) + { + LLAppearanceMgr::instance().removeItemFromAvatar(item->getUUID()); + } + } + else + { + // FIRE-303: Double-click remove wearable + //LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, true); + LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, (item->getType() == LLAssetType::AT_BODYPART || !gSavedSettings.getBOOL("FSDoubleClickAddInventoryClothing"))); + } } } diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index 8b9fbef569..106ac27011 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -28,7 +28,6 @@ #define LL_LLINVENTORYBRIDGE_H #include "llcallingcard.h" -#include "llfloaterproperties.h" #include "llfolderviewmodel.h" #include "llinventorymodel.h" #include "llinventoryobserver.h" @@ -47,9 +46,11 @@ class LLMenuGL; class LLCallingCardObserver; class LLViewerJointAttachment; class LLFolderView; +struct LLMoveInv; typedef std::vector menuentry_vec_t; - +typedef std::pair two_uuids_t; +typedef std::list two_uuids_list_t; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLInvFVBridge // @@ -84,6 +85,7 @@ public: // LLInvFVBridge functionality //-------------------------------------------------------------------- virtual const LLUUID& getUUID() const { return mUUID; } + virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null; } virtual void clearDisplayName() { mDisplayName.clear(); } virtual void restoreItem() {} virtual void restoreToWorld() {} @@ -108,6 +110,7 @@ public: virtual std::string getLabelSuffix() const { return LLStringUtil::null; } virtual void openItem() {} virtual void closeItem() {} + virtual void navigateToFolder(bool new_window = false, bool change_mode = false); virtual void showProperties(); virtual BOOL isItemRenameable() const { return TRUE; } virtual BOOL isMultiPreviewAllowed() { return TRUE; } @@ -115,6 +118,7 @@ public: virtual BOOL isItemRemovable() const; virtual BOOL isItemMovable() const; virtual BOOL isItemInTrash() const; + virtual bool isItemInOutfits() const; virtual BOOL isLink() const; virtual BOOL isLibraryItem() const; //virtual BOOL removeItem() = 0; @@ -122,7 +126,7 @@ public: virtual void move(LLFolderViewModelItem* new_parent_bridge) {} virtual bool isItemCopyable(bool can_copy_as_link = true) const { return false; } // [SL:KB] - Patch: Inventory-Links | Checked: 2013-09-19 (Catznip-3.6) - virtual bool isItemLinkable() const { return FALSE; } + virtual bool isItemLinkable() const { return false; } // [/SL:KB] virtual BOOL copyToClipboard() const; virtual BOOL cutToClipboard(); @@ -269,6 +273,7 @@ public: virtual LLUIImagePtr getIconOverlay() const; LLViewerInventoryItem* getItem() const; + virtual const LLUUID& getThumbnailUUID() const; protected: BOOL confirmRemoveItem(const LLSD& notification, const LLSD& response); @@ -293,8 +298,8 @@ public: mShowDescendantsCount(false) {} - BOOL dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg, BOOL user_confirm = TRUE); - BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, BOOL drop, std::string& tooltip_msg, BOOL is_link = FALSE, BOOL user_confirm = TRUE); + BOOL dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg, BOOL user_confirm = TRUE, LLPointer cb = NULL); + BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, BOOL drop, std::string& tooltip_msg, BOOL is_link = FALSE, BOOL user_confirm = TRUE, LLPointer cb = NULL); void callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item); void callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_category); @@ -314,6 +319,7 @@ public: static LLUIImagePtr getIcon(LLFolderType::EType preferred_type); virtual std::string getLabelSuffix() const; virtual LLFontGL::StyleFlags getLabelStyle() const; + virtual const LLUUID& getThumbnailUUID() const; void setShowDescendantsCount(bool show_count) {mShowDescendantsCount = show_count;} @@ -362,6 +368,7 @@ public: protected: 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); //-------------------------------------------------------------------- // Menu callbacks @@ -387,9 +394,9 @@ protected: void copyOutfitToClipboard(); void determineFolderType(); - void dropToFavorites(LLInventoryItem* inv_item); - void dropToOutfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit); - void dropToMyOutfits(LLInventoryCategory* inv_cat); + void dropToFavorites(LLInventoryItem* inv_item, LLPointer cb = NULL); + void dropToOutfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit, LLPointer cb = NULL); + void dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer cb = NULL); //-------------------------------------------------------------------- // Messy hacks for handling folder options @@ -399,7 +406,7 @@ public: static void staticFolderOptionsMenu(); protected: - void outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id); + void outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id, LLPointer cb); void callback_pasteFromClipboard(const LLSD& notification, const LLSD& response); void perform_pasteFromClipboard(); void gatherMessage(std::string& message, S32 depth, LLError::ELevel log_level); @@ -804,7 +811,7 @@ void rez_attachment(LLViewerInventoryItem* item, BOOL move_inv_category_world_to_agent(const LLUUID& object_id, const LLUUID& category_id, BOOL drop, - void (*callback)(S32, void*) = NULL, + std::function callback = NULL, void* user_data = NULL, LLInventoryFilter* filter = NULL); @@ -830,4 +837,16 @@ public: bool canWearSelected(const uuid_vec_t& item_ids) const; }; +struct LLMoveInv +{ + LLUUID mObjectID; + LLUUID mCategoryID; + two_uuids_list_t mMoveList; + std::function mCallback; + void* mUserData; +}; + +void warn_move_inventory(LLViewerObject* object, boost::shared_ptr move_inv); +bool move_task_inventory_callback(const LLSD& notification, const LLSD& response, boost::shared_ptr); + #endif // LL_LLINVENTORYBRIDGE_H diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index 843ab0650c..4cd6ab6b37 100644 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -71,6 +71,7 @@ LLInventoryFilter::FilterOps::FilterOps(const Params& p) mFilterTypes(p.types), mFilterUUID(p.uuid), mFilterLinks(p.links), + mFilterThumbnails(p.thumbnails), mCoalescedObjectsOnly(p.coalesced_objects_only), // FIRE-31369: Add inventory filter for coalesced objects mSearchVisibility(p.search_visibility) { @@ -90,7 +91,8 @@ LLInventoryFilter::LLInventoryFilter(const Params& p) mCurrentGeneration(0), mFirstRequiredGeneration(0), mFirstSuccessGeneration(0), - mSearchType(SEARCHTYPE_NAME) + mSearchType(SEARCHTYPE_NAME), + mSingleFolderMode(false) { // copy mFilterOps into mDefaultFilterOps markDefault(); @@ -176,6 +178,8 @@ bool LLInventoryFilter::check(const LLFolderViewModelItem* item) passed = passed && checkAgainstCreator(listener); passed = passed && checkAgainstSearchVisibility(listener); + passed = passed && checkAgainstFilterThumbnails(listener->getUUID()); + return passed; } @@ -213,17 +217,23 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const // when applying a filter, matching folders get their contents downloaded first // but make sure we are not interfering with pre-download if (isNotDefault() - && LLStartUp::getStartupState() > STATE_WEARABLES_WAIT) + && LLStartUp::getStartupState() > STATE_WEARABLES_WAIT + && !LLInventoryModelBackgroundFetch::instance().inventoryFetchInProgress()) { LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); - if (!cat || (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)) + if ((!cat && folder_id.notNull()) || (cat && cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)) { // At the moment background fetch only cares about VERSION_UNKNOWN, // so do not check isCategoryComplete that compares descendant count - LLInventoryModelBackgroundFetch::instance().start(folder_id); + LLInventoryModelBackgroundFetch::instance().start(folder_id, false); } } + if (!checkAgainstFilterThumbnails(folder_id)) + { + return false; + } + // Marketplace folder filtering const U32 filterTypes = mFilterOps.mFilterTypes; const U32 marketplace_filter = FILTERTYPE_MARKETPLACE_ACTIVE | FILTERTYPE_MARKETPLACE_INACTIVE | @@ -625,6 +635,19 @@ bool LLInventoryFilter::checkAgainstFilterLinks(const LLFolderViewModelItemInven return TRUE; } +bool LLInventoryFilter::checkAgainstFilterThumbnails(const LLUUID& object_id) const +{ + const LLInventoryObject *object = gInventory.getObject(object_id); + if (!object) return true; + + const bool is_thumbnail = object->getThumbnailUUID().notNull(); + if (is_thumbnail && (mFilterOps.mFilterThumbnails == FILTER_EXCLUDE_THUMBNAILS)) + return false; + if (!is_thumbnail && (mFilterOps.mFilterThumbnails == FILTER_ONLY_THUMBNAILS)) + return false; + return true; +} + bool LLInventoryFilter::checkAgainstCreator(const LLFolderViewModelItemInventory* listener) const { if (!listener) return TRUE; @@ -655,6 +678,9 @@ bool LLInventoryFilter::checkAgainstSearchVisibility(const LLFolderViewModelItem if (is_link && ((mFilterOps.mSearchVisibility & VISIBILITY_LINKS) == 0)) return FALSE; + if (listener->isItemInOutfits() && ((mFilterOps.mSearchVisibility & VISIBILITY_OUTFITS) == 0)) + return FALSE; + if (listener->isItemInTrash() && ((mFilterOps.mSearchVisibility & VISIBILITY_TRASH) == 0)) return FALSE; @@ -815,6 +841,32 @@ void LLInventoryFilter::setFilterSettingsTypes(U64 types) mFilterOps.mFilterTypes |= FILTERTYPE_SETTINGS; } +void LLInventoryFilter::setFilterThumbnails(U64 filter_thumbnails) +{ + if (mFilterOps.mFilterThumbnails != filter_thumbnails) + { + if (mFilterOps.mFilterThumbnails == FILTER_EXCLUDE_THUMBNAILS + && filter_thumbnails == FILTER_ONLY_THUMBNAILS) + { + setModified(FILTER_RESTART); + } + else if (mFilterOps.mFilterThumbnails == FILTER_ONLY_THUMBNAILS + && filter_thumbnails == FILTER_EXCLUDE_THUMBNAILS) + { + setModified(FILTER_RESTART); + } + else if (mFilterOps.mFilterThumbnails == FILTER_INCLUDE_THUMBNAILS) + { + setModified(FILTER_MORE_RESTRICTIVE); + } + else + { + setModified(FILTER_LESS_RESTRICTIVE); + } + } + mFilterOps.mFilterThumbnails = filter_thumbnails; +} + void LLInventoryFilter::setFilterEmptySystemFolders() { mFilterOps.mFilterTypes |= FILTERTYPE_EMPTYFOLDERS; @@ -873,6 +925,24 @@ void LLInventoryFilter::toggleSearchVisibilityLinks() } } +void LLInventoryFilter::toggleSearchVisibilityOutfits() +{ + bool hide_outfits = mFilterOps.mSearchVisibility & VISIBILITY_OUTFITS; + if (hide_outfits) + { + mFilterOps.mSearchVisibility &= ~VISIBILITY_OUTFITS; + } + else + { + mFilterOps.mSearchVisibility |= VISIBILITY_OUTFITS; + } + + if (hasFilterString()) + { + setModified(hide_outfits ? FILTER_MORE_RESTRICTIVE : FILTER_LESS_RESTRICTIVE); + } +} + void LLInventoryFilter::toggleSearchVisibilityTrash() { bool hide_trash = mFilterOps.mSearchVisibility & VISIBILITY_TRASH; @@ -1718,6 +1788,11 @@ U64 LLInventoryFilter::getSearchVisibilityTypes() const return mFilterOps.mSearchVisibility; } +U64 LLInventoryFilter::getFilterThumbnails() const +{ + return mFilterOps.mFilterThumbnails; +} + bool LLInventoryFilter::hasFilterString() const { return mFilterSubString.size() > 0; @@ -1795,9 +1870,9 @@ void LLInventoryFilter::setDefaultEmptyLookupMessage(const std::string& message) mDefaultEmptyLookupMessage = message; } -std::string LLInventoryFilter::getEmptyLookupMessage() const +std::string LLInventoryFilter::getEmptyLookupMessage(bool is_empty_folder) const { - if (isDefault() && !mDefaultEmptyLookupMessage.empty()) + if ((isDefault() || is_empty_folder) && !mDefaultEmptyLookupMessage.empty()) { return LLTrans::getString(mDefaultEmptyLookupMessage); } @@ -1820,7 +1895,7 @@ bool LLInventoryFilter::areDateLimitsSet() bool LLInventoryFilter::showAllResults() const { - return hasFilterString(); + return hasFilterString() && !mSingleFolderMode; } diff --git a/indra/newview/llinventoryfilter.h b/indra/newview/llinventoryfilter.h index f57ea4b004..b84a768025 100644 --- a/indra/newview/llinventoryfilter.h +++ b/indra/newview/llinventoryfilter.h @@ -75,6 +75,13 @@ public: FILTERLINK_ONLY_LINKS // only show links }; + enum EFilterThumbnail + { + FILTER_INCLUDE_THUMBNAILS, + FILTER_EXCLUDE_THUMBNAILS, + FILTER_ONLY_THUMBNAILS + }; + enum ESortOrderType { SO_NAME = 0, // Sort inventory by name @@ -105,7 +112,8 @@ public: VISIBILITY_NONE = 0, VISIBILITY_TRASH = 0x1 << 0, VISIBILITY_LIBRARY = 0x1 << 1, - VISIBILITY_LINKS = 0x1 << 2 + VISIBILITY_LINKS = 0x1 << 2, + VISIBILITY_OUTFITS = 0x1 << 3 }; struct FilterOps @@ -140,6 +148,7 @@ public: Optional show_folder_state; Optional permissions; Optional creator_type; + Optional thumbnails; Optional coalesced_objects_only; // FIRE-31369: Add inventory filter for coalesced objects Params() @@ -147,6 +156,7 @@ public: object_types("object_types", 0xffffFFFFffffFFFFULL), wearable_types("wearable_types", 0xffffFFFFffffFFFFULL), settings_types("settings_types", 0xffffFFFFffffFFFFULL), + thumbnails("thumbnails", FILTER_INCLUDE_THUMBNAILS), category_types("category_types", 0xffffFFFFffffFFFFULL), links("links", FILTERLINK_INCLUDE_LINKS), search_visibility("search_visibility", 0xFFFFFFFF), @@ -168,6 +178,7 @@ public: U64 mFilterObjectTypes, // For _OBJECT mFilterWearableTypes, mFilterSettingsTypes, // for _SETTINGS + mFilterThumbnails, mFilterLinks, mFilterCategoryTypes; // For _CATEGORY LLUUID mFilterUUID; // for UUID @@ -212,6 +223,7 @@ public: U64 getFilterWearableTypes() const; U64 getFilterSettingsTypes() const; U64 getSearchVisibilityTypes() const; + U64 getFilterThumbnails() const; bool isFilterObjectTypesWith(LLInventoryType::EType t) const; void setFilterObjectTypes(U64 types); @@ -227,6 +239,7 @@ public: void setFilterMarketplaceUnassociatedFolders(); void setFilterMarketplaceListingFolders(bool select_only_listing_folders); void setFilterNoMarketplaceFolder(); + void setFilterThumbnails(U64 filter_thumbnails); void updateFilterTypes(U64 types, U64& current_types); void setSearchType(ESearchType type); ESearchType getSearchType() { return mSearchType; } @@ -234,6 +247,7 @@ public: void toggleSearchVisibilityLinks(); void toggleSearchVisibilityTrash(); + void toggleSearchVisibilityOutfits(); void toggleSearchVisibilityLibrary(); void setSearchVisibilityTypes(U32 types); void setSearchVisibilityTypes(const Params& params); @@ -250,6 +264,8 @@ public: std::string::size_type getFilterSubStringLen(U32 index) const; // Multi-substring inventory search + void setSingleFolderMode(bool is_single_folder) { mSingleFolderMode = is_single_folder; } + void setFilterPermissions(PermissionMask perms); PermissionMask getFilterPermissions() const; @@ -299,7 +315,7 @@ public: void setEmptyLookupMessage(const std::string& message); void setDefaultEmptyLookupMessage(const std::string& message); - std::string getEmptyLookupMessage() const; + std::string getEmptyLookupMessage(bool is_empty_folder = false) const; // +-------------------------------------------------------------------+ // + Status @@ -343,6 +359,8 @@ public: LLInventoryFilter& operator =(const LLInventoryFilter& other); + bool checkAgainstFilterThumbnails(const LLUUID& object_id) const; + private: bool areDateLimitsSet(); bool checkAgainstFilterType(const class LLFolderViewModelItemInventory* listener) const; @@ -382,6 +400,8 @@ private: std::vector mFilterTokens; std::string mExactToken; + + bool mSingleFolderMode; }; #endif diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 8e4749fa5a..8bd64a6406 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -46,13 +46,16 @@ #include "llappearancemgr.h" #include "llappviewer.h" #include "llavataractions.h" +#include "llavatarnamecache.h" #include "llclipboard.h" #include "lldirpicker.h" #include "lldonotdisturbnotificationstorage.h" +#include "llfloatermarketplacelistings.h" #include "llfloatersidepanelcontainer.h" #include "llfocusmgr.h" #include "llfolderview.h" #include "llgesturemgr.h" +#include "llgiveinventory.h" #include "lliconctrl.h" #include "llimview.h" #include "llinventorybridge.h" @@ -99,10 +102,13 @@ #include "aoengine.h" #include "fsfloaterwearablefavorites.h" #include "fslslbridge.h" +#include "llfloatermarketplacelistings.h" +#include "llfloaterproperties.h" BOOL LLInventoryState::sWearNewClothing = FALSE; LLUUID LLInventoryState::sWearNewClothingTransactionID; std::list LLInventoryAction::sMarketplaceFolders; +//bool LLInventoryAction::sDeleteConfirmationDisplayed = false; // Undo delete item confirmation per-session annoyance // Helper function : callback to update a folder after inventory action happened in the background void update_folder_cb(const LLUUID& dest_folder) @@ -410,7 +416,7 @@ void update_all_marketplace_count(const LLUUID& cat_id) void update_all_marketplace_count() { // Get the marketplace root and launch the recursive exploration - const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (!marketplace_listings_uuid.isNull()) { update_all_marketplace_count(marketplace_listings_uuid); @@ -442,14 +448,36 @@ void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::s } void copy_inventory_category(LLInventoryModel* model, - LLViewerInventoryCategory* cat, - const LLUUID& parent_id, - const LLUUID& root_copy_id, - bool move_no_copy_items ) + LLViewerInventoryCategory* cat, + const LLUUID& parent_id, + const LLUUID& root_copy_id, + bool move_no_copy_items) +{ + // Create the initial folder + inventory_func_type func = [model, cat, root_copy_id, move_no_copy_items](const LLUUID& new_id) + { + copy_inventory_category_content(new_id, model, cat, root_copy_id, move_no_copy_items); + }; + gInventory.createNewCategory(parent_id, LLFolderType::FT_NONE, cat->getName(), func, cat->getThumbnailUUID()); +} + +void copy_inventory_category(LLInventoryModel* model, + LLViewerInventoryCategory* cat, + const LLUUID& parent_id, + const LLUUID& root_copy_id, + bool move_no_copy_items, + inventory_func_type callback) { // Create the initial folder - inventory_func_type func = boost::bind(©_inventory_category_content, _1, model, cat, root_copy_id, move_no_copy_items); - gInventory.createNewCategory(parent_id, LLFolderType::FT_NONE, cat->getName(), func); + inventory_func_type func = [model, cat, root_copy_id, move_no_copy_items, callback](const LLUUID &new_id) + { + copy_inventory_category_content(new_id, model, cat, root_copy_id, move_no_copy_items); + if (callback) + { + callback(new_id); + } + }; + gInventory.createNewCategory(parent_id, LLFolderType::FT_NONE, cat->getName(), func, cat->getThumbnailUUID()); } void copy_inventory_category_content(const LLUUID& new_cat_uuid, LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& root_copy_id, bool move_no_copy_items) @@ -574,11 +602,12 @@ BOOL get_is_item_worn(const LLUUID& id) const LLViewerInventoryItem* item = gInventory.getItem(id); if (!item) return FALSE; - + if (item->getIsLinkType() && !gInventory.getItem(item->getLinkedUUID())) { return FALSE; } + // Consider the item as worn if it has links in COF. // [SL:KB] - The code below causes problems across the board so it really just needs to go // if (LLAppearanceMgr::instance().isLinkedInCOF(id)) @@ -889,14 +918,21 @@ BOOL get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id) void show_task_item_profile(const LLUUID& item_uuid, const LLUUID& object_id) { // Optional legacy object properties - //LLFloaterSidePanelContainer::showPanel("inventory", LLSD().with("id", item_uuid).with("object", object_id)); + //LLSD params; + //params["id"] = item_uuid; + //params["object"] = object_id; + + //LLFloaterReg::showInstance("item_properties", params); if (gSavedSettings.getBOOL("FSUseLegacyObjectProperties")) { LLFloaterReg::showInstance("properties", LLSD().with("item_id", item_uuid).with("object_id", object_id)); } else { - LLFloaterSidePanelContainer::showPanel("inventory", LLSD().with("id", item_uuid).with("object", object_id)); + LLSD params; + params["id"] = item_uuid; + params["object"] = object_id; + LLFloaterReg::showInstance("item_properties", params); } // } @@ -905,14 +941,14 @@ void show_item_profile(const LLUUID& item_uuid) { LLUUID linked_uuid = gInventory.getLinkedItemID(item_uuid); // Optional legacy object properties - //LLFloaterSidePanelContainer::showPanel("inventory", LLSD().with("id", linked_uuid)); + //LLFloaterReg::showInstance("item_properties", LLSD().with("id", linked_uuid)); if (gSavedSettings.getBOOL("FSUseLegacyObjectProperties")) { LLFloaterReg::showInstance("properties", LLSD().with("item_id", linked_uuid)); } else { - LLFloaterSidePanelContainer::showPanel("inventory", LLSD().with("id", linked_uuid)); + LLFloaterReg::showInstance("item_properties", LLSD().with("id", linked_uuid)); } // } @@ -926,7 +962,22 @@ void show_item_original(const LLUUID& item_uuid) } // - LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); + static LLUICachedControl find_original_new_floater("FindOriginalOpenWindow", false); + + //show in a new single-folder window + if(find_original_new_floater) + { + const LLUUID& linked_item_uuid = gInventory.getLinkedItemID(item_uuid); + const LLInventoryObject *obj = gInventory.getObject(linked_item_uuid); + if (obj && obj->getParentUUID().notNull()) + { + LLPanelMainInventory::newFolderWindow(obj->getParentUUID(), linked_item_uuid); + } + } + //show in main Inventory + else + { + LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); if (!floater_inventory) { LL_WARNS() << "Could not find My Inventory floater" << LL_ENDL; @@ -935,20 +986,22 @@ void show_item_original(const LLUUID& item_uuid) LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); if (sidepanel_inventory) { - // FIRE-31037: "Recent" inventory filter gets reset when using "Show Original" - //LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); - //if (main_inventory) - //{ - // main_inventory->resetAllItemsFilters(); - //} - // + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + if(main_inventory->isSingleFolderMode()) + { + main_inventory->toggleViewMode(); + } + // FIRE-31037: "Recent" inventory filter gets reset when using "Show Original" + //main_inventory->resetAllItemsFilters(); + } reset_inventory_filter(); if (!LLFloaterReg::getTypedInstance("inventory")->isInVisibleChain()) { LLFloaterReg::toggleInstanceOrBringToFront("inventory"); } - sidepanel_inventory->showInventoryPanel(); const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); // Optional hiding of Received Items folder aka Inbox @@ -971,6 +1024,7 @@ void show_item_original(const LLUUID& item_uuid) } } } + } } @@ -996,22 +1050,6 @@ void open_marketplace_listings() LLFloaterReg::showInstance("marketplace_listings"); } -// Create a new folder in destFolderId with the same name as the item name and return the uuid of the new folder -// Note: this is used locally in various situation where we need to wrap an item into a special folder -LLUUID create_folder_for_item(LLInventoryItem* item, const LLUUID& destFolderId) -{ - llassert(item); - llassert(destFolderId.notNull()); - - LLUUID created_folder_id = gInventory.createNewCategory(destFolderId, LLFolderType::FT_NONE, item->getName()); - gInventory.notifyObservers(); - - // *TODO : Create different notifications for the various cases - LLNotificationsUtil::add("OutboxFolderCreated"); - - return created_folder_id; -} - ///---------------------------------------------------------------------------- // Marketplace functions // @@ -1026,7 +1064,7 @@ S32 depth_nesting_in_marketplace(LLUUID cur_uuid) // Todo: findCategoryUUIDForType is somewhat expensive with large // flat root folders yet we use depth_nesting_in_marketplace at // every turn, find a way to correctly cache this id. - const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (marketplace_listings_uuid.isNull()) { return -1; @@ -1498,6 +1536,7 @@ bool can_move_folder_to_marketplace(const LLInventoryCategory* root_folder, LLIn return accept; } +// Can happen asynhroneously!!! bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy) { // Get the marketplace listings depth of the destination folder, exit with error if not under marketplace @@ -1537,55 +1576,119 @@ bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_fol if (can_move_to_marketplace(inv_item, error_msg, true)) { // When moving an isolated item, we might need to create the folder structure to support it + + LLUUID item_id = inv_item->getUUID(); + std::function callback_create_stock = [copy, item_id](const LLUUID& new_cat_id) + { + if (new_cat_id.isNull()) + { + LL_WARNS() << "Failed to create category" << LL_ENDL; + LLSD subs; + subs["[ERROR_CODE]"] = + LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return; + } + + // Verify we can have this item in that destination category + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(new_cat_id); + LLViewerInventoryItem * viewer_inv_item = gInventory.getItem(item_id); + if (!dest_cat || !viewer_inv_item) + { + LL_WARNS() << "Move to marketplace: item or folder do not exist" << LL_ENDL; + + LLSD subs; + subs["[ERROR_CODE]"] = + LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return; + } + if (!dest_cat->acceptItem(viewer_inv_item)) + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + } + + if (copy) + { + // Copy the item + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(update_folder_cb, new_cat_id)); + copy_inventory_item( + gAgent.getID(), + viewer_inv_item->getPermissions().getOwner(), + viewer_inv_item->getUUID(), + new_cat_id, + std::string(), + cb); + } + else + { + // Reparent the item + gInventory.changeItemParent(viewer_inv_item, new_cat_id, true); + } + }; + + std::function callback_dest_create = [item_id, callback_create_stock](const LLUUID& new_cat_id) + { + if (new_cat_id.isNull()) + { + LL_WARNS() << "Failed to create category" << LL_ENDL; + LLSD subs; + subs["[ERROR_CODE]"] = + LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return; + } + + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(new_cat_id); + LLViewerInventoryItem * viewer_inv_item = gInventory.getItem(item_id); + if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()) && + (dest_cat->getPreferredType() != LLFolderType::FT_MARKETPLACE_STOCK)) + { + // We need to create a stock folder to move a no copy item + gInventory.createNewCategory(new_cat_id, LLFolderType::FT_MARKETPLACE_STOCK, viewer_inv_item->getName(), callback_create_stock); + } + else + { + callback_create_stock(new_cat_id); + } + }; + if (depth == 0) { // We need a listing folder - dest_folder = gInventory.createNewCategory(dest_folder, LLFolderType::FT_NONE, viewer_inv_item->getName()); - depth++; + gInventory.createNewCategory(dest_folder, + LLFolderType::FT_NONE, + viewer_inv_item->getName(), + [callback_dest_create](const LLUUID &new_cat_id) + { + if (new_cat_id.isNull()) + { + LL_WARNS() << "Failed to create listing folder for marketpace" << LL_ENDL; + return; + } + LLViewerInventoryCategory *dest_cat = gInventory.getCategory(new_cat_id); + if (!dest_cat) + { + LL_WARNS() << "Failed to find freshly created listing folder" << LL_ENDL; + return; + } + // version folder + gInventory.createNewCategory(new_cat_id, + LLFolderType::FT_NONE, + dest_cat->getName(), + callback_dest_create); + }); } - if (depth == 1) + else if (depth == 1) { // We need a version folder - dest_folder = gInventory.createNewCategory(dest_folder, LLFolderType::FT_NONE, viewer_inv_item->getName()); - depth++; - } - LLViewerInventoryCategory* dest_cat = gInventory.getCategory(dest_folder); - if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()) && - (dest_cat->getPreferredType() != LLFolderType::FT_MARKETPLACE_STOCK)) - { - // We need to create a stock folder to move a no copy item - dest_folder = gInventory.createNewCategory(dest_folder, LLFolderType::FT_MARKETPLACE_STOCK, viewer_inv_item->getName()); - dest_cat = gInventory.getCategory(dest_folder); - depth++; - } - - // Verify we can have this item in that destination category - if (!dest_cat->acceptItem(viewer_inv_item)) - { - LLSD subs; - subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); - LLNotificationsUtil::add("MerchantPasteFailed", subs); - return false; - } - - if (copy) - { - // Copy the item - LL_INFOS("SLM") << "Copy item '" << make_info(viewer_inv_item) << "' to '" << make_inventory_path(dest_folder) << "'" << LL_ENDL; - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(update_folder_cb, dest_folder)); - copy_inventory_item( - gAgent.getID(), - viewer_inv_item->getPermissions().getOwner(), - viewer_inv_item->getUUID(), - dest_folder, - std::string(), - cb); + gInventory.createNewCategory(dest_folder, LLFolderType::FT_NONE, viewer_inv_item->getName(), callback_dest_create); } else { - LL_INFOS("SLM") << "Move item '" << make_info(viewer_inv_item) << "' to '" << make_inventory_path(dest_folder) << "'" << LL_ENDL; - // Reparent the item - gInventory.changeItemParent(viewer_inv_item, dest_folder, true); + callback_dest_create(dest_folder); } } else @@ -1634,7 +1737,7 @@ bool move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUU // Reparent the folder gInventory.changeCategoryParent(viewer_inv_cat, dest_folder, false); // Check the destination folder recursively for no copy items and promote the including folders if any - validate_marketplacelistings(dest_cat); + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(dest_folder); } // Update the modified folders @@ -1659,32 +1762,23 @@ bool sort_alpha(const LLViewerInventoryCategory* cat1, const LLViewerInventoryCa return cat1->getName().compare(cat2->getName()) < 0; } -void dump_trace(std::string& message, S32 depth, LLError::ELevel log_level) -{ - LL_INFOS() << "validate_marketplacelistings : error = "<< log_level << ", depth = " << depth << ", message = " << message << LL_ENDL; -} - // Make all relevant business logic checks on the marketplace listings starting with the folder as argument. // This function does no deletion of listings but a mere audit and raises issues to the user (through the -// optional callback cb). It also returns a boolean, true if things validate, false if issues are raised. +// optional callback cb). // The only inventory changes that are done is to move and sort folders containing no-copy items to stock folders. -bool validate_marketplacelistings( +// @pending_callbacks - how many callbacks we are waiting for, must be inited before use +// @result - true if things validate, false if issues are raised, must be inited before use +typedef boost::function validation_result_callback_t; +void validate_marketplacelistings( LLInventoryCategory* cat, - validation_callback_t cb, + validation_result_callback_t cb_result, + LLMarketplaceValidator::validation_msg_callback_t cb_msg, bool fix_hierarchy, S32 depth, - bool notify_observers) + bool notify_observers, + S32 &pending_callbacks, + bool &result) { -#if 0 - // Used only for debug - if (!cb) - { - cb = boost::bind(&dump_trace, _1, _2, _3); - } -#endif - // Folder is valid unless issue is raised - bool result = true; - // Get the type and the depth of the folder LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *) (cat); const LLFolderType::EType folder_type = cat->getPreferredType(); @@ -1716,10 +1810,10 @@ bool validate_marketplacelistings( if (!can_move_folder_to_marketplace(cat, cat, cat, message, 0, fix_hierarchy)) { result = false; - if (cb) + if (cb_msg) { message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error") + " " + message; - cb(message,depth,LLError::LEVEL_ERROR); + cb_msg(message,depth,LLError::LEVEL_ERROR); } } } @@ -1729,26 +1823,46 @@ bool validate_marketplacelistings( { if (fix_hierarchy) { - if (cb) + if (cb_msg) { std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning") + " " + LLTrans::getString("Marketplace Validation Warning Stock"); - cb(message,depth,LLError::LEVEL_WARN); + cb_msg(message,depth,LLError::LEVEL_WARN); } + // Nest the stock folder one level deeper in a normal folder and restart from there + pending_callbacks++; LLUUID parent_uuid = cat->getParentUUID(); - LLUUID folder_uuid = gInventory.createNewCategory(parent_uuid, LLFolderType::FT_NONE, cat->getName()); - LLInventoryCategory* new_cat = gInventory.getCategory(folder_uuid); - gInventory.changeCategoryParent(viewer_cat, folder_uuid, false); - result &= validate_marketplacelistings(new_cat, cb, fix_hierarchy, depth + 1, notify_observers); - return result; + LLUUID cat_uuid = cat->getUUID(); + gInventory.createNewCategory(parent_uuid, + LLFolderType::FT_NONE, + cat->getName(), + [cat_uuid, cb_result, cb_msg, fix_hierarchy, depth](const LLUUID &new_cat_id) + { + if (new_cat_id.isNull()) + { + cb_result(0, false); + return; + } + LLInventoryCategory * move_cat = gInventory.getCategory(cat_uuid); + LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *)(move_cat); + LLInventoryCategory * new_cat = gInventory.getCategory(new_cat_id); + gInventory.changeCategoryParent(viewer_cat, new_cat_id, false); + S32 pending = 0; + bool result = true; + validate_marketplacelistings(new_cat, cb_result, cb_msg, fix_hierarchy, depth + 1, true, pending, result); + cb_result(pending, result); + } + ); + result = false; + return; } else { result = false; - if (cb) + if (cb_msg) { std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error") + " " + LLTrans::getString("Marketplace Validation Warning Stock"); - cb(message,depth,LLError::LEVEL_ERROR); + cb_msg(message,depth,LLError::LEVEL_ERROR); } } } @@ -1779,10 +1893,10 @@ bool validate_marketplacelistings( if (!can_move_to_marketplace(item, error_msg, false)) { has_bad_items = true; - if (cb && fix_hierarchy) + if (cb_msg && fix_hierarchy) { std::string message = indent + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error") + " " + error_msg; - cb(message,depth,LLError::LEVEL_ERROR); + cb_msg(message,depth,LLError::LEVEL_ERROR); } continue; } @@ -1813,35 +1927,35 @@ bool validate_marketplacelistings( if (depth == 2) { // If this is an empty version folder, warn only (listing won't be delivered by AIS, but only AIS should unlist) - if (cb) + if (cb_msg) { std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Empty Version"); - cb(message,depth,LLError::LEVEL_WARN); + cb_msg(message,depth,LLError::LEVEL_WARN); } } else if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth > 2)) { // If this is a legit but empty stock folder, warn only (listing must stay searchable when out of stock) - if (cb) + if (cb_msg) { std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Empty Stock"); - cb(message,depth,LLError::LEVEL_WARN); + cb_msg(message,depth,LLError::LEVEL_WARN); } } - else if (cb) + else if (cb_msg) { // We warn if there's nothing in a regular folder (may be it's an under construction listing) std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning Empty"); - cb(message,depth,LLError::LEVEL_WARN); + cb_msg(message,depth,LLError::LEVEL_WARN); } } else { // Done with that folder : Print out the folder name unless we already found an error here - if (cb && result && (depth >= 1)) + if (cb_msg && result && (depth >= 1)) { std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); - cb(message,depth,LLError::LEVEL_INFO); + cb_msg(message,depth,LLError::LEVEL_INFO); } } } @@ -1849,10 +1963,10 @@ bool validate_marketplacelistings( else if ((count == 1) && !has_bad_items && (((unique_key == default_key) && (depth > 1)) || ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth > 2) && (cat_array->size() == 0)))) { // Done with that folder : Print out the folder name unless we already found an error here - if (cb && result && (depth >= 1)) + if (cb_msg && result && (depth >= 1)) { std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); - cb(message,depth,LLError::LEVEL_INFO); + cb_msg(message,depth,LLError::LEVEL_INFO); } } else @@ -1874,11 +1988,12 @@ bool validate_marketplacelistings( while (items_vector_it != items_vector.end()) { // Create a new folder - LLUUID parent_uuid = (depth > 2 ? viewer_cat->getParentUUID() : viewer_cat->getUUID()); + const LLUUID parent_uuid = (depth > 2 ? viewer_cat->getParentUUID() : viewer_cat->getUUID()); + const LLUUID origin_uuid = viewer_cat->getUUID(); LLViewerInventoryItem* viewer_inv_item = gInventory.getItem(items_vector_it->second.back()); std::string folder_name = (depth >= 1 ? viewer_cat->getName() : viewer_inv_item->getName()); LLFolderType::EType new_folder_type = (items_vector_it->first == default_key ? LLFolderType::FT_NONE : LLFolderType::FT_MARKETPLACE_STOCK); - if (cb) + if (cb_msg) { std::string message = ""; if (new_folder_type == LLFolderType::FT_MARKETPLACE_STOCK) @@ -1889,30 +2004,71 @@ bool validate_marketplacelistings( { message = indent + folder_name + LLTrans::getString("Marketplace Validation Warning Create Version"); } - cb(message,depth,LLError::LEVEL_WARN); + cb_msg(message,depth,LLError::LEVEL_WARN); } - LLUUID folder_uuid = gInventory.createNewCategory(parent_uuid, new_folder_type, folder_name); - - // Move each item to the new folder - while (!items_vector_it->second.empty()) + + pending_callbacks++; + std::vector uuid_vector = items_vector_it->second; // needs to be a copy for lambda + gInventory.createNewCategory( + parent_uuid, + new_folder_type, + folder_name, + [uuid_vector, cb_result, cb_msg, depth, parent_uuid, origin_uuid, notify_observers](const LLUUID &new_category_id) { - LLViewerInventoryItem* viewer_inv_item = gInventory.getItem(items_vector_it->second.back()); - if (cb) + // Move each item to the new folder + std::vector::const_reverse_iterator iter = uuid_vector.rbegin(); + while (iter != uuid_vector.rend()) { - std::string message = indent + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Warning Move"); - cb(message,depth,LLError::LEVEL_WARN); + LLViewerInventoryItem* viewer_inv_item = gInventory.getItem(*iter); + if (cb_msg) + { + std::string indent; + for (int i = 1; i < depth; i++) + { + indent += " "; + } + std::string message = indent + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Warning Move"); + cb_msg(message, depth, LLError::LEVEL_WARN); + } + gInventory.changeItemParent(viewer_inv_item, new_category_id, true); + iter++; } - gInventory.changeItemParent(viewer_inv_item, folder_uuid, true); - items_vector_it->second.pop_back(); - } - - // Next type - update_marketplace_category(parent_uuid); - update_marketplace_category(folder_uuid); - if (notify_observers) - { - gInventory.notifyObservers(); + + if (origin_uuid != parent_uuid) + { + // We might have moved last item from a folder, check if it needs to be removed + LLViewerInventoryCategory* cat = gInventory.getCategory(origin_uuid); + if (cat->getDescendentCount() == 0) + { + // Remove previous folder if it ends up empty + if (cb_msg) + { + std::string indent; + for (int i = 1; i < depth; i++) + { + indent += " "; + } + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning Delete"); + cb_msg(message, depth, LLError::LEVEL_WARN); + } + gInventory.removeCategory(cat->getUUID()); + if (notify_observers) + { + gInventory.notifyObservers(); + } + } + } + + // Next type + update_marketplace_category(parent_uuid); + update_marketplace_category(new_category_id); + if (notify_observers) + { + gInventory.notifyObservers(); + } + cb_result(0, true); } + ); items_vector_it++; } } @@ -1926,11 +2082,11 @@ bool validate_marketplacelistings( { LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *) (*iter); gInventory.changeCategoryParent(viewer_cat, parent_uuid, false); - result &= validate_marketplacelistings(viewer_cat, cb, fix_hierarchy, depth, false); + validate_marketplacelistings(viewer_cat, cb_result, cb_msg, fix_hierarchy, depth, false, pending_callbacks, result); } } } - else if (cb) + else if (cb_msg) { // We are not fixing the hierarchy but reporting problems, report everything we can find // Print the folder name @@ -1941,20 +2097,20 @@ bool validate_marketplacelistings( // Report if a stock folder contains a mix of items result = false; std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Mixed Stock"); - cb(message,depth,LLError::LEVEL_ERROR); + cb_msg(message,depth,LLError::LEVEL_ERROR); } else if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (cat_array->size() != 0)) { // Report if a stock folder contains subfolders result = false; std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Subfolder In Stock"); - cb(message,depth,LLError::LEVEL_ERROR); + cb_msg(message,depth,LLError::LEVEL_ERROR); } else { // Simply print the folder name std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); - cb(message,depth,LLError::LEVEL_INFO); + cb_msg(message,depth,LLError::LEVEL_INFO); } } // Scan each item and report if there's a problem @@ -1969,21 +2125,21 @@ bool validate_marketplacelistings( // Report items that shouldn't be there to start with result = false; std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error") + " " + error_msg; - cb(message,depth,LLError::LEVEL_ERROR); + cb_msg(message,depth,LLError::LEVEL_ERROR); } else if ((!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) && (folder_type != LLFolderType::FT_MARKETPLACE_STOCK)) { // Report stock items that are misplaced result = false; std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error Stock Item"); - cb(message,depth,LLError::LEVEL_ERROR); + cb_msg(message,depth,LLError::LEVEL_ERROR); } else if (depth == 1) { // Report items not wrapped in version folder result = false; std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Warning Unwrapped Item"); - cb(message,depth,LLError::LEVEL_ERROR); + cb_msg(message,depth,LLError::LEVEL_ERROR); } } } @@ -1992,17 +2148,18 @@ bool validate_marketplacelistings( if (viewer_cat->getDescendentCount() == 0) { // Remove the current folder if it ends up empty - if (cb) + if (cb_msg) { std::string message = indent + viewer_cat->getName() + LLTrans::getString("Marketplace Validation Warning Delete"); - cb(message,depth,LLError::LEVEL_WARN); + cb_msg(message,depth,LLError::LEVEL_WARN); } gInventory.removeCategory(cat->getUUID()); if (notify_observers) { gInventory.notifyObservers(); } - return result && !has_bad_items; + result &=!has_bad_items; + return; } } @@ -2015,15 +2172,15 @@ bool validate_marketplacelistings( for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) { LLInventoryCategory* category = *iter; - result &= validate_marketplacelistings(category, cb, fix_hierarchy, depth + 1, false); + validate_marketplacelistings(category, cb_result, cb_msg, fix_hierarchy, depth + 1, false, pending_callbacks, result); } - + update_marketplace_category(cat->getUUID(), true, true); if (notify_observers) { gInventory.notifyObservers(); } - return result && !has_bad_items; + result &= !has_bad_items; } void change_item_parent(const LLUUID& item_id, const LLUUID& new_parent_id) @@ -2123,9 +2280,369 @@ void move_items_to_new_subfolder(const uuid_vec_t& selected_uuids, const std::st inventory_func_type func = boost::bind(&move_items_to_folder, _1, selected_uuids); gInventory.createNewCategory(first_item->getParentUUID(), LLFolderType::FT_NONE, folder_name, func); - } +// Returns true if the item can be moved to Current Outfit or any outfit folder. +bool can_move_to_outfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit) +{ + // FIRE-8434/BUG-988 Viewer crashes when copying and pasting an empty outfit folder + if (!inv_item) + return false; + // + + LLInventoryType::EType inv_type = inv_item->getInventoryType(); + if ((inv_type != LLInventoryType::IT_WEARABLE) && + (inv_type != LLInventoryType::IT_GESTURE) && + (inv_type != LLInventoryType::IT_ATTACHMENT) && + (inv_type != LLInventoryType::IT_OBJECT) && + (inv_type != LLInventoryType::IT_SNAPSHOT) && + (inv_type != LLInventoryType::IT_TEXTURE)) + { + return false; + } + + U32 flags = inv_item->getFlags(); + if(flags & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) + { + return false; + } + + if((inv_type == LLInventoryType::IT_TEXTURE) || (inv_type == LLInventoryType::IT_SNAPSHOT)) + { + return !move_is_into_current_outfit; + } + + if (move_is_into_current_outfit && get_is_item_worn(inv_item->getUUID())) + { + return false; + } + + return true; +} + +// Returns TRUE if item is a landmark or a link to a landmark +// and can be moved to Favorites or Landmarks folder. +bool can_move_to_landmarks(LLInventoryItem* inv_item) +{ + // Need to get the linked item to know its type because LLInventoryItem::getType() + // returns actual type AT_LINK for links, not the asset type of a linked item. + if (LLAssetType::AT_LINK == inv_item->getType()) + { + LLInventoryItem* linked_item = gInventory.getItem(inv_item->getLinkedUUID()); + if (linked_item) + { + return LLAssetType::AT_LANDMARK == linked_item->getType(); + } + } + + return LLAssetType::AT_LANDMARK == inv_item->getType(); +} + +// Returns true if folder's content can be moved to Current Outfit or any outfit folder. +bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit) +{ + LLInventoryModel::cat_array_t *cats; + LLInventoryModel::item_array_t *items; + model->getDirectDescendentsOf(inv_cat->getUUID(), cats, items); + + if (items->size() > wear_limit) + { + return false; + } + + if (items->size() == 0) + { + // Nothing to move(create) + return false; + } + + if (cats->size() > 0) + { + // We do not allow subfolders in outfits of "My Outfits" yet + return false; + } + + LLInventoryModel::item_array_t::iterator iter = items->begin(); + LLInventoryModel::item_array_t::iterator end = items->end(); + + while (iter != end) + { + LLViewerInventoryItem *item = *iter; + if (!can_move_to_outfit(item, false)) + { + return false; + } + iter++; + } + + return true; +} + +std::string get_localized_folder_name(LLUUID cat_uuid) +{ + std::string localized_root_name; + const LLViewerInventoryCategory* cat = gInventory.getCategory(cat_uuid); + if (cat) + { + LLFolderType::EType preferred_type = cat->getPreferredType(); + + // Translation of Accessories folder in Library inventory folder + bool accessories = false; + if(cat->getName() == "Accessories") + { + const LLUUID& parent_folder_id = cat->getParentUUID(); + accessories = (parent_folder_id == gInventory.getLibraryRootFolderID()); + } + + //"Accessories" inventory category has folder type FT_NONE. So, this folder + //can not be detected as protected with LLFolderType::lookupIsProtectedType + localized_root_name.assign(cat->getName()); + if (accessories || LLFolderType::lookupIsProtectedType(preferred_type)) + { + LLTrans::findString(localized_root_name, std::string("InvFolder ") + cat->getName(), LLSD()); + } + } + + return localized_root_name; +} + +void new_folder_window(const LLUUID& folder_id) +{ + LLPanelMainInventory::newFolderWindow(folder_id); +} + +void ungroup_folder_items(const LLUUID& folder_id) +{ + LLInventoryCategory* inv_cat = gInventory.getCategory(folder_id); + if (!inv_cat || LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) + { + return; + } + + // FIRE-32736: Add confirmation before ungrouping folder + LLSD args; + args["FOLDER_NAME"] = inv_cat->getName(); + LLNotificationsUtil::add("UngroupFolder", args, LLSD(), + [inv_cat](const LLSD& notification, const LLSD& response) + { + S32 opt = LLNotificationsUtil::getSelectedOption(notification, response); + if (opt == 1) + return; + // + + const LLUUID &new_cat_uuid = inv_cat->getParentUUID(); + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(inv_cat->getUUID(), cat_array, item_array); + LLInventoryModel::cat_array_t cats = *cat_array; + LLInventoryModel::item_array_t items = *item_array; + + for (LLInventoryModel::cat_array_t::const_iterator cat_iter = cats.begin(); cat_iter != cats.end(); ++cat_iter) + { + LLViewerInventoryCategory* cat = *cat_iter; + if (cat) + { + gInventory.changeCategoryParent(cat, new_cat_uuid, false); + } + } + for (LLInventoryModel::item_array_t::const_iterator item_iter = items.begin(); item_iter != items.end(); ++item_iter) + { + LLViewerInventoryItem* item = *item_iter; + if(item) + { + gInventory.changeItemParent(item, new_cat_uuid, false); + } + } + gInventory.removeCategory(inv_cat->getUUID()); + gInventory.notifyObservers(); + + }); // FIRE-32736: Add confirmation before ungrouping folder +} + +std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id) +{ + if (model) + { + const LLInventoryItem *item = model->getItem(item_id); + if(item) + { + std::string desc = item->getDescription(); + LLStringUtil::toUpper(desc); + return desc; + } + } + return LLStringUtil::null; +} + +std::string get_searchable_creator_name(LLInventoryModel* model, const LLUUID& item_id) +{ + if (model) + { + const LLInventoryItem *item = model->getItem(item_id); + if(item) + { + LLAvatarName av_name; + // Avoid null id requests entering name cache + //if (LLAvatarNameCache::get(item->getCreatorUUID(), &av_name)) + const auto& creatorId {item->getCreatorUUID()}; + if (creatorId.notNull() && LLAvatarNameCache::get(creatorId, &av_name)) + // + { + std::string username = av_name.getUserName(); + LLStringUtil::toUpper(username); + return username; + } + } + } + return LLStringUtil::null; +} + +std::string get_searchable_UUID(LLInventoryModel* model, const LLUUID& item_id) +{ + if (model) + { + const LLViewerInventoryItem *item = model->getItem(item_id); + if(item /*&& (item->getIsFullPerm() || gAgent.isGodlikeWithoutAdminMenuFakery())*/) // Keep it FS-legacy style since we had it like this for ages + { + std::string uuid = item->getAssetUUID().asString(); + LLStringUtil::toUpper(uuid); + return uuid; + } + } + return LLStringUtil::null; +} + +bool can_share_item(const LLUUID& item_id) +{ + bool can_share = false; + + if (gInventory.isObjectDescendentOf(item_id, gInventory.getRootFolderID())) + { + const LLViewerInventoryItem *item = gInventory.getItem(item_id); + if (item) + { + if (LLInventoryCollectFunctor::itemTransferCommonlyAllowed(item)) + { + can_share = LLGiveInventory::isInventoryGiveAcceptable(item); + } + } + else + { + can_share = (gInventory.getCategory(item_id) != NULL); + } + + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if ((item_id == trash_id) || gInventory.isObjectDescendentOf(item_id, trash_id)) + { + can_share = false; + } + } + + return can_share; +} +///---------------------------------------------------------------------------- +/// LLMarketplaceValidator implementations +///---------------------------------------------------------------------------- + + +LLMarketplaceValidator::LLMarketplaceValidator() + : mPendingCallbacks(0) + , mValidationInProgress(false) +{ +} + +LLMarketplaceValidator::~LLMarketplaceValidator() +{ +} + +void LLMarketplaceValidator::validateMarketplaceListings( + const LLUUID &category_id, + LLMarketplaceValidator::validation_done_callback_t cb_done, + LLMarketplaceValidator::validation_msg_callback_t cb_msg, + bool fix_hierarchy, + S32 depth) +{ + + mValidationQueue.emplace(category_id, cb_done, cb_msg, fix_hierarchy, depth); + if (!mValidationInProgress) + { + start(); + } +} + +void LLMarketplaceValidator::start() +{ + if (mValidationQueue.empty()) + { + mValidationInProgress = false; + return; + } + mValidationInProgress = true; + + const ValidationRequest &first = mValidationQueue.front(); + LLViewerInventoryCategory* cat = gInventory.getCategory(first.mCategoryId); + if (!cat) + { + LL_WARNS() << "Tried to validate a folder that doesn't exist" << LL_ENDL; + if (first.mCbDone) + { + first.mCbDone(false); + } + mValidationQueue.pop(); + start(); + return; + } + + validation_result_callback_t result_callback = [](S32 pending, bool result) + { + LLMarketplaceValidator* validator = LLMarketplaceValidator::getInstance(); + validator->mPendingCallbacks--; // we just got a callback + validator->mPendingCallbacks += pending; + validator->mPendingResult &= result; + if (validator->mPendingCallbacks <= 0) + { + llassert(validator->mPendingCallbacks == 0); // shouldn't be below 0 + const ValidationRequest &first = validator->mValidationQueue.front(); + if (first.mCbDone) + { + first.mCbDone(validator->mPendingResult); + } + validator->mValidationQueue.pop(); // done; + validator->start(); + } + }; + + mPendingResult = true; + mPendingCallbacks = 1; // do '1' in case something decides to callback immediately + + S32 pending_calbacks = 0; + bool result = true; + validate_marketplacelistings( + cat, + result_callback, + first.mCbMsg, + first.mFixHierarchy, + first.mDepth, + true, + pending_calbacks, + result); + + result_callback(pending_calbacks, result); +} + +LLMarketplaceValidator::ValidationRequest::ValidationRequest( + LLUUID category_id, + validation_done_callback_t cb_done, + validation_msg_callback_t cb_msg, + bool fix_hierarchy, + S32 depth) +: mCategoryId(category_id) +, mCbDone(cb_done) +, mCbMsg(cb_msg) +, mFixHierarchy(fix_hierarchy) +, mDepth(depth) +{} + ///---------------------------------------------------------------------------- /// LLInventoryCollectFunctor implementations ///---------------------------------------------------------------------------- @@ -2309,6 +2826,19 @@ bool LLFindCOFValidItems::operator()(LLInventoryCategory* cat, } } +bool LLFindBrokenLinks::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + // only for broken links getType will be a link + // otherwise it's supposed to have the type of an item + // it is linked too + if (item && LLAssetType::lookupIsLinkType(item->getType())) + { + return TRUE; + } + return FALSE; +} + bool LLFindWearables::operator()(LLInventoryCategory* cat, LLInventoryItem* item) { @@ -2374,6 +2904,11 @@ void LLFindWearablesOfType::setType(LLWearableType::EType type) mWearableType = type; } +bool LLIsTextureType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + return item && (item->getType() == LLAssetType::AT_TEXTURE); +} + bool LLFindNonRemovableObjects::operator()(LLInventoryCategory* cat, LLInventoryItem* item) { if (item) @@ -2670,9 +3205,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root if ("delete" == action) { - // Undo delete item confirmation per-session annoyance - //static bool sDisplayedAtSession = false; - const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); bool marketplacelistings_item = false; LLAllDescendentsPassedFilter f; for (std::set::iterator it = selected_items.begin(); (it != selected_items.end()) && (f.allDescendentsPassedFilter()); ++it) @@ -2696,17 +3229,16 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root else { // Undo delete item confirmation per-session annoyance - //if (!sDisplayedAtSession) // ask for the confirmation at least once per session + //if (!sDeleteConfirmationDisplayed) // ask for the confirmation at least once per session //{ // LLNotifications::instance().setIgnored("DeleteItems", false); - // sDisplayedAtSession = true; + // sDeleteConfirmationDisplayed = true; //} // LLSD args; // FIRE-31816: Include selection count when deleting more than one object from inventory //args["QUESTION"] = LLTrans::getString(root->getSelectedCount() > 1 ? "DeleteItems" : "DeleteItem"); - //args["QUESTION"] = LLTrans::getString(root->getSelectedCount() > 1 ? "DeleteItems" : "DeleteItem", args); LLLocale locale(""); std::string count_str{}; S32 selection_count = root->getSelectedCount(); @@ -2869,7 +3401,8 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root LLMultiPreview* multi_previewp = NULL; - LLMultiProperties* multi_propertiesp = NULL; + LLMultiItemProperties* multi_itempropertiesp = nullptr; + LLMultiProperties* multi_propertiesp = NULL; // Keep legacy properties floater if (("task_open" == action || "open" == action) && selected_items.size() > 1) { @@ -2903,10 +3436,23 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root } else if (("task_properties" == action || "properties" == action) && selected_items.size() > 1) { - multi_propertiesp = new LLMultiProperties(); - gFloaterView->addChild(multi_propertiesp); - - LLFloater::setFloaterHost(multi_propertiesp); + // Keep legacy properties floater + //multi_itempropertiesp = new LLMultiItemProperties("item_properties"); + //gFloaterView->addChild(multi_itempropertiesp); + //LLFloater::setFloaterHost(multi_itempropertiesp); + if (gSavedSettings.getBOOL("FSUseLegacyObjectProperties")) + { + multi_propertiesp = new LLMultiProperties(); + gFloaterView->addChild(multi_propertiesp); + LLFloater::setFloaterHost(multi_propertiesp); + } + else + { + multi_itempropertiesp = new LLMultiItemProperties("item_properties"); + gFloaterView->addChild(multi_itempropertiesp); + LLFloater::setFloaterHost(multi_itempropertiesp); + } + // } std::set selected_uuid_set = LLAvatarActions::getInventorySelectedUUIDs(); @@ -2916,7 +3462,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root if (action == "wear" || action == "wear_add") { const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - const LLUUID mp_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID mp_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); std::copy_if(selected_uuid_set.begin(), selected_uuid_set.end(), std::back_inserter(ids), @@ -3031,51 +3577,7 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root { if (ids.size() == 1) { - LLInventoryCategory* inv_cat = gInventory.getCategory(*ids.begin()); - if (!inv_cat || LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) - { - return; - } - - // FIRE-32736: Add confirmation before ungrouping folder - LLSD args; - args["FOLDER_NAME"] = inv_cat->getName(); - LLNotificationsUtil::add("UngroupFolder", args, LLSD(), - [inv_cat](const LLSD& notification, const LLSD& response) - { - S32 opt = LLNotificationsUtil::getSelectedOption(notification, response); - if (opt == 1) - return; - // - - const LLUUID &new_cat_uuid = inv_cat->getParentUUID(); - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(inv_cat->getUUID(), cat_array, item_array); - LLInventoryModel::cat_array_t cats = *cat_array; - LLInventoryModel::item_array_t items = *item_array; - - for (LLInventoryModel::cat_array_t::const_iterator cat_iter = cats.begin(); cat_iter != cats.end(); ++cat_iter) - { - LLViewerInventoryCategory* cat = *cat_iter; - if (cat) - { - gInventory.changeCategoryParent(cat, new_cat_uuid, false); - } - } - for (LLInventoryModel::item_array_t::const_iterator item_iter = items.begin(); item_iter != items.end(); ++item_iter) - { - LLViewerInventoryItem* item = *item_iter; - if(item) - { - gInventory.changeItemParent(item, new_cat_uuid, false); - } - } - gInventory.removeCategory(inv_cat->getUUID()); - gInventory.notifyObservers(); - - }); // FIRE-32736: Add confirmation before ungrouping folder - + ungroup_folder_items(*ids.begin()); } } // FIRE-22851: Show texture "Save as" file picker subsequently instead all at once @@ -3095,6 +3597,14 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root if(!bridge) continue; bridge->performAction(model, action); } + if(root->isSingleFolderMode() && selected_items.empty()) + { + LLInvFVBridge* bridge = (LLInvFVBridge*)root->getViewModelItem(); + if(bridge) + { + bridge->performAction(model, action); + } + } } // Update the marketplace listings that have been affected by the operation @@ -3105,10 +3615,16 @@ void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root { multi_previewp->openFloater(LLSD()); } + else if (multi_itempropertiesp) + { + multi_itempropertiesp->openFloater(LLSD()); + } + // Keep legacy properties floater else if (multi_propertiesp) { multi_propertiesp->openFloater(LLSD()); } + // } void LLInventoryAction::saveMultipleTextures(const std::vector& filenames, std::set selected_items, LLInventoryModel* model) @@ -3195,7 +3711,7 @@ void LLInventoryAction::buildMarketplaceFolders(LLFolderView* root) // target listing *and* the original listing. So we need to keep track of both. // Note: do not however put the marketplace listings root itself in this list or the whole marketplace data will be rebuilt. sMarketplaceFolders.clear(); - const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (marketplacelistings_id.isNull()) { return; diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index 006be790f3..db10b7288e 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -82,6 +82,7 @@ void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::s //void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name); void copy_inventory_category(LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& parent_id, const LLUUID& root_copy_id = LLUUID::null, bool move_no_copy_items = false); +void copy_inventory_category(LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& parent_id, const LLUUID& root_copy_id, bool move_no_copy_items, inventory_func_type callback); void copy_inventory_category_content(const LLUUID& new_cat_uuid, LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& root_copy_id, bool move_no_copy_items); @@ -98,13 +99,11 @@ std::string make_info(const LLInventoryObject* object); // Generates a string containing the path name and id of the object specified by id. std::string make_inventory_info(const LLUUID& id); -typedef boost::function validation_callback_t; - bool can_move_item_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryItem* inv_item, std::string& tooltip_msg, S32 bundle_size = 1, bool from_paste = false); bool can_move_folder_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryCategory* inv_cat, std::string& tooltip_msg, S32 bundle_size = 1, bool check_items = true, bool from_paste = false); bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy = false); bool move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, bool copy = false, bool move_no_copy_items = false); -bool validate_marketplacelistings(LLInventoryCategory* inv_cat, validation_callback_t cb = NULL, bool fix_hierarchy = true, S32 depth = -1, bool notify_observers = true); + S32 depth_nesting_in_marketplace(LLUUID cur_uuid); LLUUID nested_parent_id(LLUUID cur_uuid, S32 depth); S32 compute_stock_count(LLUUID cat_uuid, bool force_count = false); @@ -115,10 +114,65 @@ void move_items_to_folder(const LLUUID& new_cat_uuid, const uuid_vec_t& selected bool is_only_cats_selected(const uuid_vec_t& selected_uuids); bool is_only_items_selected(const uuid_vec_t& selected_uuids); +bool can_move_to_outfit(LLInventoryItem* inv_item, BOOL move_is_into_current_outfit); +bool can_move_to_landmarks(LLInventoryItem* inv_item); +bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit); +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); +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); +bool can_share_item(const LLUUID& item_id); + /** Miscellaneous global functions ** ** *******************************************************************************/ +class LLMarketplaceValidator: public LLSingleton +{ + LLSINGLETON(LLMarketplaceValidator); + ~LLMarketplaceValidator(); + LOG_CLASS(LLMarketplaceValidator); +public: + + typedef boost::function validation_msg_callback_t; + typedef boost::function validation_done_callback_t; + + void validateMarketplaceListings( + const LLUUID &category_id, + validation_done_callback_t cb_done = NULL, + validation_msg_callback_t cb_msg = NULL, + bool fix_hierarchy = true, + S32 depth = -1); + +private: + void start(); + + class ValidationRequest + { + public: + ValidationRequest( + LLUUID category_id, + validation_done_callback_t cb_done, + validation_msg_callback_t cb_msg, + bool fix_hierarchy, + S32 depth); + LLUUID mCategoryId; + validation_done_callback_t mCbDone; + validation_msg_callback_t mCbMsg; + bool mFixHierarchy; + S32 mDepth; + }; + + bool mValidationInProgress; + S32 mPendingCallbacks; + bool mPendingResult; + // todo: might be a good idea to memorize requests by id and + // filter out ones that got multiple validation requests + std::queue mValidationQueue; +}; + /******************************************************************************** ** ** ** INVENTORY COLLECTOR FUNCTIONS @@ -334,6 +388,20 @@ public: }; +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFindBrokenLinks +// +// Collects broken links +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFindBrokenLinks : public LLInventoryCollectFunctor +{ +public: + LLFindBrokenLinks() {} + virtual ~LLFindBrokenLinks() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLFindByMask //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -432,6 +500,15 @@ private: LLWearableType::EType mWearableType; }; +class LLIsTextureType : public LLInventoryCollectFunctor +{ +public: + LLIsTextureType() {} + virtual ~LLIsTextureType() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + /** Filter out wearables-links */ class LLFindActualWearablesOfType : public LLFindWearablesOfType { @@ -507,8 +584,8 @@ struct LLInventoryAction static void saveMultipleTextures(const std::vector& filenames, std::set selected_items, LLInventoryModel* model); - // Unused as of 24-08-2017 - //static const int sConfirmOnDeleteItemsNumber; + // Undo delete item confirmation per-session annoyance + //static bool sDeleteConfirmationDisplayed; private: static void buildMarketplaceFolders(LLFolderView* root); diff --git a/indra/newview/llinventorygallery.cpp b/indra/newview/llinventorygallery.cpp new file mode 100644 index 0000000000..6206d9b73d --- /dev/null +++ b/indra/newview/llinventorygallery.cpp @@ -0,0 +1,3830 @@ +/** + * @file llinventorygallery.cpp + * @brief LLInventoryGallery class implementation + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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 "llinventorygallery.h" +#include "llinventorygallerymenu.h" + +#include "llclipboard.h" +#include "llcommonutils.h" +#include "lliconctrl.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventoryicon.h" +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llthumbnailctrl.h" +#include "lltextbox.h" +#include "llviewerfoldertype.h" + +#include "llagent.h" +#include "llappearancemgr.h" +#include "llenvironment.h" +#include "llfriendcard.h" +#include "llgesturemgr.h" +#include "llmarketplacefunctions.h" +#include "llnotificationsutil.h" +#include "lloutfitobserver.h" +#include "lltrans.h" +#include "llviewerassettype.h" +#include "llviewermessage.h" +#include "llviewerobjectlist.h" +#include "llvoavatarself.h" + +static LLPanelInjector t_inventory_gallery("inventory_gallery"); + +const S32 GALLERY_ITEMS_PER_ROW_MIN = 2; + +// Helper dnd functions +BOOL dragCategoryIntoFolder(LLUUID dest_id, LLInventoryCategory* inv_cat, BOOL drop, std::string& tooltip_msg, BOOL is_link); +BOOL dragItemIntoFolder(LLUUID folder_id, LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg, BOOL user_confirm); +void dropToMyOutfits(LLInventoryCategory* inv_cat); + +class LLGalleryPanel: public LLPanel +{ +public: + + BOOL canFocusChildren() const override + { + // Tell Tab to not focus children + return FALSE; + } + +protected: + + LLGalleryPanel(const LLPanel::Params& params): LLPanel(params) + { + }; + + friend class LLUICtrlFactory; +}; + +//----------------------------- +// LLInventoryGallery +//----------------------------- + +LLInventoryGallery::LLInventoryGallery(const LLInventoryGallery::Params& p) + : LLPanel(), + mScrollPanel(NULL), + mGalleryPanel(NULL), + mLastRowPanel(NULL), + mGalleryCreated(false), + mRowCount(0), + mItemsAddedCount(0), + mRowPanelHeight(p.row_panel_height), + mVerticalGap(p.vertical_gap), + mHorizontalGap(p.horizontal_gap), + mItemWidth(p.item_width), + mItemHeight(p.item_height), + mItemHorizontalGap(p.item_horizontal_gap), + mItemsInRow(p.items_in_row), + mRowPanWidthFactor(p.row_panel_width_factor), + mGalleryWidthFactor(p.gallery_width_factor), + mIsInitialized(false), + mRootDirty(false), + mNeedsArrange(false), + mSearchType(LLInventoryFilter::SEARCHTYPE_NAME), + mSortOrder(LLInventoryFilter::SO_DATE) +{ + updateGalleryWidth(); + mFilter = new LLInventoryFilter(); + mCategoriesObserver = new LLInventoryCategoriesObserver(); + mThumbnailsObserver = new LLThumbnailsObserver(); + gInventory.addObserver(mThumbnailsObserver); + + mGestureObserver = new LLGalleryGestureObserver(this); + LLGestureMgr::instance().addObserver(mGestureObserver); + + mUsername = gAgentUsername; + LLStringUtil::toUpper(mUsername); +} + +LLInventoryGallery::Params::Params() + : row_panel_height("row_panel_height", 180), + vertical_gap("vertical_gap", 10), + horizontal_gap("horizontal_gap", 10), + item_width("item_width", 150), + item_height("item_height", 175), + item_horizontal_gap("item_horizontal_gap", 16), + items_in_row("items_in_row", GALLERY_ITEMS_PER_ROW_MIN), + row_panel_width_factor("row_panel_width_factor", 166), + gallery_width_factor("gallery_width_factor", 163) +{ + addSynonym(row_panel_height, "row_height"); +} + +const LLInventoryGallery::Params& LLInventoryGallery::getDefaultParams() +{ + return LLUICtrlFactory::getDefaultParams(); +} + +BOOL LLInventoryGallery::postBuild() +{ + mScrollPanel = getChild("gallery_scroll_panel"); + LLPanel::Params params = LLPanel::getDefaultParams(); + mGalleryPanel = LLUICtrlFactory::create(params); + mMessageTextBox = getChild("empty_txt"); + mInventoryGalleryMenu = new LLInventoryGalleryContextMenu(this); + mRootGalleryMenu = new LLInventoryGalleryContextMenu(this); + mRootGalleryMenu->setRootFolder(true); + return TRUE; +} + +LLInventoryGallery::~LLInventoryGallery() +{ + if (gEditMenuHandler == this) + { + gEditMenuHandler = NULL; + } + + delete mInventoryGalleryMenu; + delete mRootGalleryMenu; + delete mFilter; + + gIdleCallbacks.deleteFunction(onIdle, (void*)this); + + while (!mUnusedRowPanels.empty()) + { + LLPanel* panelp = mUnusedRowPanels.back(); + mUnusedRowPanels.pop_back(); + panelp->die(); + } + while (!mUnusedItemPanels.empty()) + { + LLPanel* panelp = mUnusedItemPanels.back(); + mUnusedItemPanels.pop_back(); + panelp->die(); + } + while (!mHiddenItems.empty()) + { + LLPanel* panelp = mHiddenItems.back(); + mHiddenItems.pop_back(); + panelp->die(); + } + + + if (gInventory.containsObserver(mCategoriesObserver)) + { + gInventory.removeObserver(mCategoriesObserver); + } + delete mCategoriesObserver; + + if (gInventory.containsObserver(mThumbnailsObserver)) + { + gInventory.removeObserver(mThumbnailsObserver); + } + delete mThumbnailsObserver; + + LLGestureMgr::instance().removeObserver(mGestureObserver); + delete mGestureObserver; +} + +void LLInventoryGallery::setRootFolder(const LLUUID cat_id) +{ + LLViewerInventoryCategory* category = gInventory.getCategory(cat_id); + if(!category || (mFolderID == cat_id)) + { + return; + } + if(mFolderID.notNull()) + { + mBackwardFolders.push_back(mFolderID); + } + + gIdleCallbacks.deleteFunction(onIdle, (void*)this); + + for (const LLUUID& id : mSelectedItemIDs) + { + if (mItemMap[id]) + { + mItemMap[id]->setSelected(FALSE); + } + } + + mFolderID = cat_id; + mItemsToSelect.clear(); + mSelectedItemIDs.clear(); + mItemBuildQuery.clear(); + mNeedsArrange = false; + dirtyRootFolder(); +} + +void LLInventoryGallery::dirtyRootFolder() +{ + if (getVisible()) + { + updateRootFolder(); + } + else + { + mRootDirty = true; + } +} + +void LLInventoryGallery::updateRootFolder() +{ + llassert(mFolderID.notNull()); + if (mIsInitialized && mFolderID.notNull()) + { + S32 count = mItemsAddedCount; + for (S32 i = count - 1; i >= 0; i--) + { + updateRemovedItem(mItems[i]->getUUID()); + } + S32 hidden_count = mHiddenItems.size(); + for (S32 i = hidden_count - 1; i >= 0; i--) + { + updateRemovedItem(mHiddenItems[i]->getUUID()); + } + mItemBuildQuery.clear(); + + if (gInventory.containsObserver(mCategoriesObserver)) + { + gInventory.removeObserver(mCategoriesObserver); + } + delete mCategoriesObserver; + + mCategoriesObserver = new LLInventoryCategoriesObserver(); + + if (gInventory.containsObserver(mThumbnailsObserver)) + { + gInventory.removeObserver(mThumbnailsObserver); + } + delete mThumbnailsObserver; + mThumbnailsObserver = new LLThumbnailsObserver(); + gInventory.addObserver(mThumbnailsObserver); + } + { + mRootChangedSignal(); + + gInventory.addObserver(mCategoriesObserver); + + // Start observing changes in selected category. + mCategoriesObserver->addCategory(mFolderID, + boost::bind(&LLInventoryGallery::refreshList, this, mFolderID)); + + LLViewerInventoryCategory* category = gInventory.getCategory(mFolderID); + //If not all items are fetched now + // the observer will refresh the list as soon as the new items + // arrive. + category->fetch(); + + //refreshList(cat_id); + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + + gInventory.getDirectDescendentsOf(mFolderID, cat_array, item_array); + + // Creating a vector of newly collected sub-categories UUIDs. + for (LLInventoryModel::cat_array_t::const_iterator iter = cat_array->begin(); + iter != cat_array->end(); + iter++) + { + mItemBuildQuery.insert((*iter)->getUUID()); + } + + for (LLInventoryModel::item_array_t::const_iterator iter = item_array->begin(); + iter != item_array->end(); + iter++) + { + mItemBuildQuery.insert((*iter)->getUUID()); + } + mIsInitialized = true; + mRootDirty = false; + + if (mScrollPanel) + { + mScrollPanel->goToTop(); + } + } + + LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLInventoryGallery::onCOFChanged, this)); + + if (!mGalleryCreated) + { + initGallery(); + } + + if (!mItemBuildQuery.empty()) + { + gIdleCallbacks.addFunction(onIdle, (void*)this); + } +} + +void LLInventoryGallery::initGallery() +{ + if (!mGalleryCreated) + { + uuid_vec_t cats; + getCurrentCategories(cats); + int n = cats.size(); + buildGalleryPanel(n); + mScrollPanel->addChild(mGalleryPanel); + for (int i = 0; i < n; i++) + { + addToGallery(mItemMap[cats[i]]); + } + reArrangeRows(); + mGalleryCreated = true; + } +} + +void LLInventoryGallery::draw() +{ + LLPanel::draw(); + if (mGalleryCreated) + { + if(!updateRowsIfNeeded()) + { + handleModifiedFilter(); + } + } +} + +void LLInventoryGallery::onVisibilityChange(BOOL new_visibility) +{ + if (new_visibility) + { + if (mRootDirty) + { + updateRootFolder(); + } + else if (mNeedsArrange) + { + gIdleCallbacks.addFunction(onIdle, (void*)this); + } + } + LLPanel::onVisibilityChange(new_visibility); +} + +bool LLInventoryGallery::updateRowsIfNeeded() +{ + S32 scroll_content_width = mScrollPanel ? mScrollPanel->getVisibleContentRect().getWidth() : getRect().getWidth(); + if(((scroll_content_width - mRowPanelWidth) > mItemWidth) + && mRowCount > 1) + { + reArrangeRows(1); + return true; + } + else if((mRowPanelWidth > (scroll_content_width + mItemHorizontalGap)) + && mItemsInRow > GALLERY_ITEMS_PER_ROW_MIN) + { + reArrangeRows(-1); + return true; + } + return false; +} + +bool compareGalleryItem(LLInventoryGalleryItem* item1, LLInventoryGalleryItem* item2, bool sort_by_date, bool sort_folders_by_name) +{ + if (item1->getSortGroup() != item2->getSortGroup()) + { + return (item1->getSortGroup() < item2->getSortGroup()); + } + + if(sort_folders_by_name && (item1->getSortGroup() != LLInventoryGalleryItem::SG_ITEM)) + { + std::string name1 = item1->getItemName(); + std::string name2 = item2->getItemName(); + + return (LLStringUtil::compareDict(name1, name2) < 0); + } + + if(((item1->isDefaultImage() && item2->isDefaultImage()) || (!item1->isDefaultImage() && !item2->isDefaultImage()))) + { + if(sort_by_date) + { + return item1->getCreationDate() > item2->getCreationDate(); + } + else + { + std::string name1 = item1->getItemName(); + std::string name2 = item2->getItemName(); + + return (LLStringUtil::compareDict(name1, name2) < 0); + } + } + else + { + return item2->isDefaultImage(); + } +} + +void LLInventoryGallery::reArrangeRows(S32 row_diff) +{ + std::vector buf_items = mItems; + for (std::vector::const_reverse_iterator it = buf_items.rbegin(); it != buf_items.rend(); ++it) + { + removeFromGalleryLast(*it, false); + } + for (std::vector::const_reverse_iterator it = mHiddenItems.rbegin(); it != mHiddenItems.rend(); ++it) + { + buf_items.push_back(*it); + } + mHiddenItems.clear(); + + mItemsInRow+= row_diff; + updateGalleryWidth(); + + bool sort_by_date = (mSortOrder & LLInventoryFilter::SO_DATE); + bool sort_folders_by_name = (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_NAME); + std::sort(buf_items.begin(), buf_items.end(), [sort_by_date, sort_folders_by_name](LLInventoryGalleryItem* item1, LLInventoryGalleryItem* item2) + { + return compareGalleryItem(item1, item2, sort_by_date, sort_folders_by_name); + }); + + for (std::vector::const_iterator it = buf_items.begin(); it != buf_items.end(); ++it) + { + (*it)->setHidden(false); + applyFilter(*it, mFilterSubString); + addToGallery(*it); + } + mFilter->clearModified(); + updateMessageVisibility(); +} + +void LLInventoryGallery::updateGalleryWidth() +{ + mRowPanelWidth = mRowPanWidthFactor * mItemsInRow - mItemHorizontalGap; + mGalleryWidth = mGalleryWidthFactor * mItemsInRow - mItemHorizontalGap; +} + +LLPanel* LLInventoryGallery::addLastRow() +{ + mRowCount++; + int row = 0; + int vgap = mVerticalGap * row; + LLPanel* result = buildRowPanel(0, row * mRowPanelHeight + vgap); + mGalleryPanel->addChild(result); + return result; +} + +void LLInventoryGallery::moveRowUp(int row) +{ + moveRow(row, mRowCount - 1 - row + 1); +} + +void LLInventoryGallery::moveRowDown(int row) +{ + moveRow(row, mRowCount - 1 - row - 1); +} + +void LLInventoryGallery::moveRow(int row, int pos) +{ + int vgap = mVerticalGap * pos; + moveRowPanel(mRowPanels[row], 0, pos * mRowPanelHeight + vgap); +} + +void LLInventoryGallery::removeLastRow() +{ + mRowCount--; + mGalleryPanel->removeChild(mLastRowPanel); + mUnusedRowPanels.push_back(mLastRowPanel); + mRowPanels.pop_back(); + if (mRowPanels.size() > 0) + { + // Just removed last row + mLastRowPanel = mRowPanels.back(); + } + else + { + mLastRowPanel = NULL; + } +} + +LLPanel* LLInventoryGallery::addToRow(LLPanel* row_stack, LLInventoryGalleryItem* item, int pos, int hgap) +{ + LLPanel* lpanel = buildItemPanel(pos * mItemWidth + hgap); + lpanel->addChild(item); + row_stack->addChild(lpanel); + mItemPanels.push_back(lpanel); + return lpanel; +} + +void LLInventoryGallery::addToGallery(LLInventoryGalleryItem* item) +{ + if(item->isHidden()) + { + mHiddenItems.push_back(item); + return; + } + mItemIndexMap[item] = mItemsAddedCount; + mIndexToItemMap[mItemsAddedCount] = item; + mItemsAddedCount++; + int n = mItemsAddedCount; + int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; + int n_prev = n - 1; + int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; + + bool add_row = row_count != row_count_prev; + int pos = 0; + if (add_row) + { + for (int i = 0; i < row_count_prev; i++) + { + moveRowUp(i); + } + mLastRowPanel = addLastRow(); + mRowPanels.push_back(mLastRowPanel); + } + pos = (n - 1) % mItemsInRow; + mItems.push_back(item); + addToRow(mLastRowPanel, item, pos, mHorizontalGap * pos); + reshapeGalleryPanel(row_count); +} + + +void LLInventoryGallery::removeFromGalleryLast(LLInventoryGalleryItem* item, bool needs_reshape) +{ + if(item->isHidden()) + { + mHiddenItems.pop_back(); + // Note: item still exists!!! + return; + } + int n_prev = mItemsAddedCount; + int n = mItemsAddedCount - 1; + int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; + int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; + mItemsAddedCount--; + mIndexToItemMap.erase(mItemsAddedCount); + + bool remove_row = row_count != row_count_prev; + removeFromLastRow(mItems[mItemsAddedCount]); + mItems.pop_back(); + if (remove_row) + { + for (int i = 0; i < row_count_prev - 1; i++) + { + moveRowDown(i); + } + removeLastRow(); + } + if (needs_reshape) + { + reshapeGalleryPanel(row_count); + } +} + + +void LLInventoryGallery::removeFromGalleryMiddle(LLInventoryGalleryItem* item) +{ + if(item->isHidden()) + { + mHiddenItems.erase(std::remove(mHiddenItems.begin(), mHiddenItems.end(), item), mHiddenItems.end()); + // item still exists and needs to be deleted or used!!! + return; + } + int n = mItemIndexMap[item]; + mItemIndexMap.erase(item); + mIndexToItemMap.erase(n); + std::vector saved; + for (int i = mItemsAddedCount - 1; i > n; i--) + { + saved.push_back(mItems[i]); + removeFromGalleryLast(mItems[i]); + } + removeFromGalleryLast(mItems[n]); + int saved_count = saved.size(); + for (int i = 0; i < saved_count; i++) + { + addToGallery(saved.back()); + saved.pop_back(); + } +} + +void LLInventoryGallery::removeFromLastRow(LLInventoryGalleryItem* item) +{ + mItemPanels.back()->removeChild(item); + mLastRowPanel->removeChild(mItemPanels.back()); + mUnusedItemPanels.push_back(mItemPanels.back()); + 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::Params giparams; + giparams.visible = true; + giparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); + giparams.rect(LLRect(0,mItemHeight, mItemWidth, 0)); + LLInventoryGalleryItem* gitem = LLUICtrlFactory::create(giparams); + gitem->setItemName(name); + gitem->setUUID(item_id); + gitem->setGallery(this); + gitem->setType(type, inventory_type, flags, is_link); + gitem->setThumbnail(thumbnail_id); + gitem->setWorn(is_worn); + gitem->setCreatorName(get_searchable_creator_name(&gInventory, item_id)); + gitem->setDescription(get_searchable_description(&gInventory, item_id)); + gitem->setAssetIDStr(get_searchable_UUID(&gInventory, item_id)); + gitem->setCreationDate(creation_date); + return gitem; +} + +void LLInventoryGallery::buildGalleryPanel(int row_count) +{ + LLPanel::Params params; + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); + params.visible = true; + params.use_bounding_rect = false; + mGalleryPanel = LLUICtrlFactory::create(params); + reshapeGalleryPanel(row_count); +} + +void LLInventoryGallery::reshapeGalleryPanel(int row_count) +{ + int bottom = 0; + int left = 0; + int height = row_count * (mRowPanelHeight + mVerticalGap); + LLRect rect = LLRect(left, bottom + height, left + mGalleryWidth, bottom); + mGalleryPanel->setRect(rect); + mGalleryPanel->reshape(mGalleryWidth, height); +} + +LLPanel* LLInventoryGallery::buildItemPanel(int left) +{ + int top = 0; + LLPanel* lpanel = NULL; + if(mUnusedItemPanels.empty()) + { + LLPanel::Params lpparams; + lpparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); + lpparams.visible = true; + lpparams.rect(LLRect(left, top + mItemHeight, left + mItemWidth + mItemHorizontalGap, top)); + lpparams.use_bounding_rect = false; + lpparams.focus_root = false; + //lpparams.tab_stop = false; + lpanel = LLUICtrlFactory::create(lpparams); + } + else + { + lpanel = mUnusedItemPanels.back(); + mUnusedItemPanels.pop_back(); + + LLRect rect = LLRect(left, top + mItemHeight, left + mItemWidth + mItemHorizontalGap, top); + lpanel->setShape(rect, false); + } + return lpanel; +} + +LLPanel* LLInventoryGallery::buildRowPanel(int left, int bottom) +{ + LLPanel* stack = NULL; + if(mUnusedRowPanels.empty()) + { + LLPanel::Params sparams; + sparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); + sparams.use_bounding_rect = false; + sparams.visible = true; + sparams.focus_root = false; + //sparams.tab_stop = false; + stack = LLUICtrlFactory::create(sparams); + } + else + { + stack = mUnusedRowPanels.back(); + mUnusedRowPanels.pop_back(); + } + moveRowPanel(stack, left, bottom); + return stack; +} + +void LLInventoryGallery::moveRowPanel(LLPanel* stack, int left, int bottom) +{ + LLRect rect = LLRect(left, bottom + mRowPanelHeight, left + mRowPanelWidth, bottom); + stack->setRect(rect); + stack->reshape(mRowPanelWidth, mRowPanelHeight); +} + +void LLInventoryGallery::setFilterSubString(const std::string& string) +{ + mFilterSubString = string; + mFilter->setFilterSubString(string); + + //reArrangeRows(); +} + +bool LLInventoryGallery::applyFilter(LLInventoryGalleryItem* item, const std::string& filter_substring) +{ + if(item) + { + bool visible = checkAgainstFilters(item, filter_substring); + item->setHidden(!visible); + return visible; + } + return false; +} + +bool LLInventoryGallery::checkAgainstFilters(LLInventoryGalleryItem* item, const std::string& filter_substring) +{ + if (!item) return false; + + if (item->isFolder() && (mFilter->getShowFolderState() == LLInventoryFilter::SHOW_ALL_FOLDERS)) + { + return true; + } + + if(item->isLink() && ((mFilter->getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LINKS) == 0) && !filter_substring.empty()) + { + return false; + } + + bool hidden = false; + + if(mFilter->getFilterCreatorType() == LLInventoryFilter::FILTERCREATOR_SELF) + { + hidden = (item->getCreatorName() == mUsername) || item->isFolder(); + } + else if(mFilter->getFilterCreatorType() == LLInventoryFilter::FILTERCREATOR_OTHERS) + { + hidden = (item->getCreatorName() != mUsername) || item->isFolder(); + } + if(hidden) + { + return false; + } + + if(!mFilter->checkAgainstFilterThumbnails(item->getUUID())) + { + return false; + } + + if(!checkAgainstFilterType(item->getUUID())) + { + return false; + } + + std::string desc; + switch(mSearchType) + { + case LLInventoryFilter::SEARCHTYPE_CREATOR: + desc = item->getCreatorName(); + break; + case LLInventoryFilter::SEARCHTYPE_DESCRIPTION: + desc = item->getDescription(); + break; + case LLInventoryFilter::SEARCHTYPE_UUID: + desc = item->getAssetIDStr(); + break; + case LLInventoryFilter::SEARCHTYPE_NAME: + default: + desc = item->getItemName() + item->getItemNameSuffix(); + break; + } + + LLStringUtil::toUpper(desc); + + std::string cur_filter = filter_substring; + LLStringUtil::toUpper(cur_filter); + + hidden = (std::string::npos == desc.find(cur_filter)); + return !hidden; +} + +void LLInventoryGallery::onIdle(void* userdata) +{ + LLInventoryGallery* self = (LLInventoryGallery*)userdata; + + if (!self->mIsInitialized || !self->mGalleryCreated) + { + self->mNeedsArrange = false; + return; + } + + bool visible = self->getVisible(); // In visible chain? + const F64 MAX_TIME_VISIBLE = 0.020f; + const F64 MAX_TIME_HIDDEN = 0.001f; // take it slow + const F64 max_time = visible ? MAX_TIME_VISIBLE : MAX_TIME_HIDDEN; + F64 curent_time = LLTimer::getTotalSeconds(); + const F64 end_time = curent_time + max_time; + + while (!self->mItemBuildQuery.empty() && end_time > curent_time) + { + uuid_set_t::iterator iter = self->mItemBuildQuery.begin(); + LLUUID item_id = *iter; + self->mNeedsArrange |= self->updateAddedItem(item_id); + self->mItemBuildQuery.erase(iter); + curent_time = LLTimer::getTotalSeconds(); + } + + if (self->mNeedsArrange && visible) + { + self->mNeedsArrange = false; + self->reArrangeRows(); + self->updateMessageVisibility(); + } + + if (!self->mItemsToSelect.empty() && !self->mNeedsArrange) + { + selection_deque selection_list(self->mItemsToSelect); + self->mItemsToSelect.clear(); + for (LLUUID & item_to_select : selection_list) + { + self->addItemSelection(item_to_select, true); + } + } + + if (self->mItemsToSelect.empty() && self->mItemBuildQuery.empty()) + { + gIdleCallbacks.deleteFunction(onIdle, (void*)self); + } +} + +void LLInventoryGallery::setSearchType(LLInventoryFilter::ESearchType type) +{ + if(mSearchType != type) + { + mSearchType = type; + if(!mFilterSubString.empty()) + { + reArrangeRows(); + } + } +} + +void LLInventoryGallery::getCurrentCategories(uuid_vec_t& vcur) +{ + for (gallery_item_map_t::const_iterator iter = mItemMap.begin(); + iter != mItemMap.end(); + iter++) + { + if ((*iter).second != NULL) + { + vcur.push_back((*iter).first); + } + } +} + +bool LLInventoryGallery::updateAddedItem(LLUUID item_id) +{ + LLInventoryObject* obj = gInventory.getObject(item_id); + if (!obj) + { + LL_WARNS("InventoryGallery") << "Failed to find item: " << item_id << LL_ENDL; + return false; + } + + std::string name = obj->getName(); + LLUUID thumbnail_id = obj->getThumbnailUUID();; + LLInventoryType::EType inventory_type(LLInventoryType::IT_CATEGORY); + U32 misc_flags = 0; + bool is_worn = false; + LLInventoryItem* inv_item = gInventory.getItem(item_id); + if (inv_item) + { + inventory_type = inv_item->getInventoryType(); + misc_flags = inv_item->getFlags(); + if (LLAssetType::AT_GESTURE == obj->getType()) + { + is_worn = LLGestureMgr::instance().isGestureActive(item_id); + } + else + { + is_worn = LLAppearanceMgr::instance().isLinkedInCOF(item_id); + } + } + else if (LLAssetType::AT_CATEGORY == obj->getType()) + { + name = get_localized_folder_name(item_id); + if(thumbnail_id.isNull()) + { + thumbnail_id = getOutfitImageID(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); + mItemMap.insert(LLInventoryGallery::gallery_item_map_t::value_type(item_id, item)); + if (mGalleryCreated) + { + res = applyFilter(item, mFilterSubString); + addToGallery(item); + } + + mThumbnailsObserver->addItem(item_id, + boost::bind(&LLInventoryGallery::updateItemThumbnail, this, item_id)); + return res; +} + +void LLInventoryGallery::updateRemovedItem(LLUUID item_id) +{ + gallery_item_map_t::iterator item_iter = mItemMap.find(item_id); + if (item_iter != mItemMap.end()) + { + mThumbnailsObserver->removeItem(item_id); + + LLInventoryGalleryItem* item = item_iter->second; + + deselectItem(item_id); + mItemMap.erase(item_iter); + removeFromGalleryMiddle(item); + + // kill removed item + if (item != NULL) + { + // Todo: instead of deleting, store somewhere to reuse later + item->die(); + } + } + + mItemBuildQuery.erase(item_id); +} + +void LLInventoryGallery::updateChangedItemName(LLUUID item_id, std::string name) +{ + gallery_item_map_t::iterator iter = mItemMap.find(item_id); + if (iter != mItemMap.end()) + { + LLInventoryGalleryItem* item = iter->second; + if (item) + { + item->setItemName(name); + } + } +} + +void LLInventoryGallery::updateWornItem(LLUUID item_id, bool is_worn) +{ + gallery_item_map_t::iterator iter = mItemMap.find(item_id); + if (iter != mItemMap.end()) + { + LLInventoryGalleryItem* item = iter->second; + if (item) + { + item->setWorn(is_worn); + } + } +} + +void LLInventoryGallery::updateItemThumbnail(LLUUID item_id) +{ + LLInventoryObject* obj = gInventory.getObject(item_id); + if (!obj) + { + return; + } + LLUUID thumbnail_id = obj->getThumbnailUUID(); + + if ((LLAssetType::AT_CATEGORY == obj->getType()) && thumbnail_id.isNull()) + { + thumbnail_id = getOutfitImageID(item_id); + } + + if (mItemMap[item_id]) + { + mItemMap[item_id]->setThumbnail(thumbnail_id); + + bool passes_filter = checkAgainstFilters(mItemMap[item_id], mFilterSubString); + if((mItemMap[item_id]->isHidden() && passes_filter) + || (!mItemMap[item_id]->isHidden() && !passes_filter)) + { + reArrangeRows(); + } + } +} + +BOOL LLInventoryGallery::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (mSelectedItemIDs.size() > 0) + { + setFocus(true); + } + mLastInteractedUUID = LLUUID::null; + + // Scroll is going to always return true + BOOL res = LLPanel::handleRightMouseDown(x, y, mask); + + if (mLastInteractedUUID.isNull()) // no child were hit + { + clearSelection(); + if (mInventoryGalleryMenu && mFolderID.notNull()) + { + uuid_vec_t selected_uuids; + selected_uuids.push_back(mFolderID); + mRootGalleryMenu->show(this, selected_uuids, x, y); + return TRUE; + } + } + return res; +} + + +BOOL LLInventoryGallery::handleKeyHere(KEY key, MASK mask) +{ + BOOL handled = FALSE; + switch (key) + { + case KEY_RETURN: + // Open selected items if enter key hit on the inventory panel + if (mask == MASK_NONE && mInventoryGalleryMenu && mSelectedItemIDs.size() == 1) + { + selection_deque::iterator iter = mSelectedItemIDs.begin(); + LLViewerInventoryCategory* category = gInventory.getCategory(*iter); + if (category) + { + setRootFolder(*iter); + handled = TRUE; + } + else + { + LLViewerInventoryItem* item = gInventory.getItem(*iter); + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(), *iter, &gInventory); + } + } + } + handled = TRUE; + break; + case KEY_DELETE: +#if LL_DARWIN + case KEY_BACKSPACE: +#endif + // Delete selected items if delete or backspace key hit on the inventory panel + // Note: on Mac laptop keyboards, backspace and delete are one and the same + if (canDeleteSelection()) + { + deleteSelection(); + } + handled = TRUE; + break; + + case KEY_F2: + mFilterSubString.clear(); + if (mInventoryGalleryMenu && mSelectedItemIDs.size() == 1) + { + mInventoryGalleryMenu->rename(mSelectedItemIDs.front()); + } + handled = TRUE; + break; + + case KEY_PAGE_UP: + mFilterSubString.clear(); + if (mScrollPanel) + { + mScrollPanel->pageUp(30); + } + handled = TRUE; + break; + + case KEY_PAGE_DOWN: + mFilterSubString.clear(); + if (mScrollPanel) + { + mScrollPanel->pageDown(30); + } + handled = TRUE; + break; + + case KEY_HOME: + mFilterSubString.clear(); + if (mScrollPanel) + { + mScrollPanel->goToTop(); + } + handled = TRUE; + break; + + case KEY_END: + mFilterSubString.clear(); + if (mScrollPanel) + { + mScrollPanel->goToBottom(); + } + handled = TRUE; + break; + + case KEY_LEFT: + moveLeft(mask); + handled = TRUE; + break; + + case KEY_RIGHT: + moveRight(mask); + handled = TRUE; + break; + + case KEY_UP: + moveUp(mask); + handled = TRUE; + break; + + case KEY_DOWN: + moveDown(mask); + handled = TRUE; + break; + + default: + break; + } + + if (handled) + { + mInventoryGalleryMenu->hide(); + } + + return handled; +} + +void LLInventoryGallery::moveUp(MASK mask) +{ + mFilterSubString.clear(); + + if (mInventoryGalleryMenu && mSelectedItemIDs.size() > 0 && mItemsAddedCount > 1) + { + LLInventoryGalleryItem* item = mItemMap[mLastInteractedUUID]; + if (item) + { + if (mask == MASK_NONE || mask == MASK_CONTROL) + { + S32 n = mItemIndexMap[item]; + n -= mItemsInRow; + if (n >= 0) + { + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + if (mask == MASK_CONTROL) + { + addItemSelection(item_id, true); + } + else + { + changeItemSelection(item_id, true); + } + item->setFocus(TRUE); + claimEditHandler(); + } + } + else if (mask == MASK_SHIFT) + { + S32 n = mItemIndexMap[item]; + S32 target = llmax(0, n - mItemsInRow); + if (target != n) + { + item = mIndexToItemMap[target]; + toggleSelectionRangeFromLast(item->getUUID()); + item->setFocus(TRUE); + claimEditHandler(); + } + } + } + } +} + +void LLInventoryGallery::moveDown(MASK mask) +{ + mFilterSubString.clear(); + + if (mInventoryGalleryMenu && mSelectedItemIDs.size() > 0 && mItemsAddedCount > 1) + { + LLInventoryGalleryItem* item = mItemMap[mLastInteractedUUID]; + if (item) + { + if (mask == MASK_NONE || mask == MASK_CONTROL) + { + S32 n = mItemIndexMap[item]; + n += mItemsInRow; + if (n < mItemsAddedCount) + { + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + if (mask == MASK_CONTROL) + { + addItemSelection(item_id, true); + } + else + { + changeItemSelection(item_id, true); + } + item->setFocus(TRUE); + claimEditHandler(); + } + } + else if (mask == MASK_SHIFT) + { + S32 n = mItemIndexMap[item]; + S32 target = llmin(mItemsAddedCount - 1, n + mItemsInRow); + if (target != n) + { + item = mIndexToItemMap[target]; + toggleSelectionRangeFromLast(item->getUUID()); + item->setFocus(TRUE); + claimEditHandler(); + } + } + } + } +} + +void LLInventoryGallery::moveLeft(MASK mask) +{ + mFilterSubString.clear(); + + if (mInventoryGalleryMenu && mSelectedItemIDs.size() > 0 && mItemsAddedCount > 1) + { + LLInventoryGalleryItem* item = mItemMap[mLastInteractedUUID]; + if (mask == MASK_SHIFT) + { + item = mItemMap[mLastInteractedUUID]; + } + if (item) + { + // Might be better to get item from panel + S32 n = mItemIndexMap[item]; + n--; + if (n < 0) + { + n = mItemsAddedCount - 1; + } + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + if (mask == MASK_CONTROL) + { + addItemSelection(item_id, true); + } + else if (mask == MASK_SHIFT) + { + if (item->isSelected()) + { + toggleItemSelection(mLastInteractedUUID, true); + } + else + { + toggleItemSelection(item_id, true); + } + mLastInteractedUUID = item_id; + } + else + { + changeItemSelection(item_id, true); + } + item->setFocus(TRUE); + claimEditHandler(); + } + } +} + +void LLInventoryGallery::moveRight(MASK mask) +{ + mFilterSubString.clear(); + + if (mInventoryGalleryMenu && mSelectedItemIDs.size() > 0 && mItemsAddedCount > 1) + { + LLInventoryGalleryItem* item = mItemMap[mLastInteractedUUID]; + if (item) + { + S32 n = mItemIndexMap[item]; + n++; + if (n == mItemsAddedCount) + { + n = 0; + } + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + if (mask == MASK_CONTROL) + { + addItemSelection(item_id, true); + } + else if (mask == MASK_SHIFT) + { + if (item->isSelected()) + { + toggleItemSelection(mLastInteractedUUID, true); + } + else + { + toggleItemSelection(item_id, true); + } + mLastInteractedUUID = item_id; + } + else + { + changeItemSelection(item_id, true); + } + item->setFocus(TRUE); + claimEditHandler(); + } + } +} + +void LLInventoryGallery::toggleSelectionRange(S32 start_idx, S32 end_idx) +{ + LLInventoryGalleryItem* item = NULL; + if (end_idx > start_idx) + { + for (S32 i = start_idx; i <= end_idx; i++) + { + item = mIndexToItemMap[i]; + LLUUID item_id = item->getUUID(); + toggleItemSelection(item_id, true); + } + } + else + { + for (S32 i = start_idx; i >= end_idx; i--) + { + item = mIndexToItemMap[i]; + LLUUID item_id = item->getUUID(); + toggleItemSelection(item_id, true); + } + } +} + +void LLInventoryGallery::toggleSelectionRangeFromLast(const LLUUID target) +{ + if (mLastInteractedUUID == target) + { + return; + } + LLInventoryGalleryItem* last_item = mItemMap[mLastInteractedUUID]; + LLInventoryGalleryItem* next_item = mItemMap[target]; + if (last_item && next_item) + { + S32 last_idx = mItemIndexMap[last_item]; + S32 next_idx = mItemIndexMap[next_item]; + if (next_item->isSelected()) + { + if (last_idx < next_idx) + { + toggleSelectionRange(last_idx, next_idx - 1); + } + else + { + toggleSelectionRange(last_idx, next_idx + 1); + } + } + else + { + if (last_idx < next_idx) + { + toggleSelectionRange(last_idx + 1, next_idx); + } + else + { + toggleSelectionRange(last_idx - 1, next_idx); + } + } + } + mLastInteractedUUID = next_item->getUUID(); +} + +void LLInventoryGallery::onFocusLost() +{ + // inventory no longer handles cut/copy/paste/delete + if (gEditMenuHandler == this) + { + gEditMenuHandler = NULL; + } + + LLPanel::onFocusLost(); + + for (const LLUUID& id : mSelectedItemIDs) + { + if (mItemMap[id]) + { + mItemMap[id]->setSelected(false); + } + } +} + +void LLInventoryGallery::onFocusReceived() +{ + // inventory now handles cut/copy/paste/delete + gEditMenuHandler = this; + + // Tab support, when tabbing into this view, select first item + if (mSelectedItemIDs.size() > 0) + { + LLInventoryGalleryItem* focus_item = NULL; + for (const LLUUID& id : mSelectedItemIDs) + { + if (mItemMap[id]) + { + focus_item = mItemMap[id]; + focus_item->setSelected(true); + } + } + if (focus_item) + { + focus_item->setFocus(TRUE); + } + } + else if (mIndexToItemMap.size() > 0 && mItemsToSelect.empty()) + { + // choose any items from visible rect + S32 vert_offset = mScrollPanel->getDocPosVertical(); + S32 panel_size = mVerticalGap + mRowPanelHeight; + S32 n = llclamp((S32)(vert_offset / panel_size) * mItemsInRow, 0, (S32)(mIndexToItemMap.size() - 1) ); + + LLInventoryGalleryItem* focus_item = mIndexToItemMap[n]; + changeItemSelection(focus_item->getUUID(), true); + focus_item->setFocus(TRUE); + } + + LLPanel::onFocusReceived(); +} + +void LLInventoryGallery::showContextMenu(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& item_id) +{ + if (mInventoryGalleryMenu && item_id.notNull()) + { + if (std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), item_id) == mSelectedItemIDs.end()) + { + changeItemSelection(item_id, false); + } + uuid_vec_t selected_uuids(mSelectedItemIDs.begin(), mSelectedItemIDs.end()); + mInventoryGalleryMenu->show(ctrl, selected_uuids, x, y); + } +} + +void LLInventoryGallery::changeItemSelection(const LLUUID& item_id, bool scroll_to_selection) +{ + for (const LLUUID& id : mSelectedItemIDs) + { + if (mItemMap[id]) + { + mItemMap[id]->setSelected(FALSE); + } + } + mSelectedItemIDs.clear(); + mItemsToSelect.clear(); + + if ((mItemMap.count(item_id) == 0) || mNeedsArrange) + { + mItemsToSelect.push_back(item_id); + return; + } + if (mSelectedItemIDs.size() == 1 + && std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), item_id) != mSelectedItemIDs.end()) + { + // Already selected + mLastInteractedUUID = item_id; + return; + } + + if (mItemMap[item_id]) + { + mItemMap[item_id]->setSelected(TRUE); + } + mSelectedItemIDs.push_back(item_id); + signalSelectionItemID(item_id); + mLastInteractedUUID = item_id; + + if (scroll_to_selection) + { + scrollToShowItem(item_id); + } +} + +void LLInventoryGallery::addItemSelection(const LLUUID& item_id, bool scroll_to_selection) +{ + if ((mItemMap.count(item_id) == 0) || mNeedsArrange) + { + mItemsToSelect.push_back(item_id); + return; + } + if (std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), item_id) != mSelectedItemIDs.end()) + { + // Already selected + mLastInteractedUUID = item_id; + return; + } + + if (mItemMap[item_id]) + { + mItemMap[item_id]->setSelected(TRUE); + } + mSelectedItemIDs.push_back(item_id); + signalSelectionItemID(item_id); + mLastInteractedUUID = item_id; + + if (scroll_to_selection) + { + scrollToShowItem(item_id); + } +} + +bool LLInventoryGallery::toggleItemSelection(const LLUUID& item_id, bool scroll_to_selection) +{ + bool result = false; + if ((mItemMap.count(item_id) == 0) || mNeedsArrange) + { + mItemsToSelect.push_back(item_id); + return result; + } + selection_deque::iterator found = std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), item_id); + if (found != mSelectedItemIDs.end()) + { + if (mItemMap[item_id]) + { + mItemMap[item_id]->setSelected(FALSE); + } + mSelectedItemIDs.erase(found); + result = false; + } + else + { + if (mItemMap[item_id]) + { + mItemMap[item_id]->setSelected(TRUE); + } + mSelectedItemIDs.push_back(item_id); + signalSelectionItemID(item_id); + result = true; + } + mLastInteractedUUID = item_id; + + if (scroll_to_selection) + { + scrollToShowItem(item_id); + } + return result; +} + +void LLInventoryGallery::scrollToShowItem(const LLUUID& item_id) +{ + LLInventoryGalleryItem* item = mItemMap[item_id]; + if(item) + { + const LLRect visible_content_rect = mScrollPanel->getVisibleContentRect(); + + LLRect item_rect; + item->localRectToOtherView(item->getLocalRect(), &item_rect, mScrollPanel); + LLRect overlap_rect(item_rect); + overlap_rect.intersectWith(visible_content_rect); + + //Scroll when the selected item is outside the visible area + if (overlap_rect.getHeight() + 5 < item->getRect().getHeight()) + { + LLRect content_rect = mScrollPanel->getContentWindowRect(); + LLRect constraint_rect; + constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); + + LLRect item_doc_rect; + item->localRectToOtherView(item->getLocalRect(), &item_doc_rect, mGalleryPanel); + + mScrollPanel->scrollToShowRect( item_doc_rect, constraint_rect ); + } + } +} + +LLInventoryGalleryItem* LLInventoryGallery::getFirstSelectedItem() +{ + if (mSelectedItemIDs.size() > 0) + { + selection_deque::iterator iter = mSelectedItemIDs.begin(); + return mItemMap[*iter]; + } + return NULL; +} + +void LLInventoryGallery::copy() +{ + if (!getVisible() || !getEnabled()) + { + return; + } + + LLClipboard::instance().reset(); + + for (const LLUUID& id : mSelectedItemIDs) + { + LLClipboard::instance().addToClipboard(id); + } + mFilterSubString.clear(); +} + +BOOL LLInventoryGallery::canCopy() const +{ + if (!getVisible() || !getEnabled() || mSelectedItemIDs.empty()) + { + return FALSE; + } + + for (const LLUUID& id : mSelectedItemIDs) + { + if (!isItemCopyable(id)) + { + return FALSE; + } + } + + return TRUE; +} + +void LLInventoryGallery::cut() +{ + if (!getVisible() || !getEnabled()) + { + return; + } + + // clear the inventory clipboard + LLClipboard::instance().reset(); + LLClipboard::instance().setCutMode(true); + for (const LLUUID& id : mSelectedItemIDs) + { + // todo: fade out selected item + LLClipboard::instance().addToClipboard(id); + } + + mFilterSubString.clear(); +} + +BOOL LLInventoryGallery::canCut() const +{ + if (!getVisible() || !getEnabled() || mSelectedItemIDs.empty()) + { + return FALSE; + } + + for (const LLUUID& id : mSelectedItemIDs) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(id); + if (cat) + { + if (!get_is_category_removable(&gInventory, id)) + { + return FALSE; + } + } + else if (!get_is_item_removable(&gInventory, id)) + { + return FALSE; + } + } + + return TRUE; +} + +void LLInventoryGallery::paste() +{ + if (!LLClipboard::instance().hasContents()) + { + return; + } + + const LLUUID& marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (mSelectedItemIDs.size() == 1 && gInventory.isObjectDescendentOf(*mSelectedItemIDs.begin(), marketplacelistings_id)) + { + return; + } + + bool is_cut_mode = LLClipboard::instance().isCutMode(); + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + + bool paste_into_root = mSelectedItemIDs.empty(); + for (LLUUID& dest : mSelectedItemIDs) + { + LLInventoryObject* obj = gInventory.getObject(dest); + if (!obj || (obj->getType() != LLAssetType::AT_CATEGORY)) + { + paste_into_root = true; + continue; + } + + paste(dest, objects, is_cut_mode, marketplacelistings_id); + is_cut_mode = false; + } + + if (paste_into_root) + { + for (const LLUUID& id : mSelectedItemIDs) + { + if (mItemMap[id]) + { + mItemMap[id]->setSelected(FALSE); + } + } + mSelectedItemIDs.clear(); + + paste(mFolderID, objects, is_cut_mode, marketplacelistings_id); + } + + LLClipboard::instance().setCutMode(false); +} + +void LLInventoryGallery::paste(const LLUUID& dest, + std::vector& objects, + bool is_cut_mode, + const LLUUID& marketplacelistings_id) +{ + LLHandle handle = getHandle(); + std::function on_copy_callback = NULL; + LLPointer cb = NULL; + if (dest == mFolderID) + { + on_copy_callback = [handle](const LLUUID& inv_item) + { + LLInventoryGallery* panel = (LLInventoryGallery*)handle.get(); + if (panel) + { + // Scroll to pasted item and highlight it + // Should it only highlight the last one? + panel->addItemSelection(inv_item, true); + } + }; + cb = new LLBoostFuncInventoryCallback(on_copy_callback); + } + + for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) + { + const LLUUID& item_id = (*iter); + if (gInventory.isObjectDescendentOf(item_id, marketplacelistings_id) && (LLMarketplaceData::instance().isInActiveFolder(item_id) || + LLMarketplaceData::instance().isListedAndActive(item_id))) + { + return; + } + LLViewerInventoryCategory* cat = gInventory.getCategory(item_id); + if (cat) + { + if (is_cut_mode) + { + gInventory.changeCategoryParent(cat, dest, false); + if (dest == mFolderID) + { + // Don't select immediately, wait for item to arrive + mItemsToSelect.push_back(item_id); + } + } + else + { + copy_inventory_category(&gInventory, cat, dest, LLUUID::null, false, on_copy_callback); + } + } + else + { + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (item) + { + if (is_cut_mode) + { + gInventory.changeItemParent(item, dest, false); + if (dest == mFolderID) + { + // Don't select immediately, wait for item to arrive + mItemsToSelect.push_back(item_id); + } + } + else + { + if (item->getIsLinkType()) + { + link_inventory_object(dest, item_id, cb); + } + else + { + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + dest, + std::string(), + cb); + } + } + } + } + } + + LLClipboard::instance().setCutMode(false); +} + +BOOL LLInventoryGallery::canPaste() const +{ + // Return FALSE on degenerated cases: empty clipboard, no inventory, no agent + if (!LLClipboard::instance().hasContents()) + { + return FALSE; + } + + // In cut mode, whatever is on the clipboard is always pastable + if (LLClipboard::instance().isCutMode()) + { + return TRUE; + } + + // In normal mode, we need to check each element of the clipboard to know if we can paste or not + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + S32 count = objects.size(); + for (S32 i = 0; i < count; i++) + { + const LLUUID& item_id = objects.at(i); + + // Each item must be copyable to be pastable + if (!isItemCopyable(item_id)) + { + return FALSE; + } + } + return TRUE; +} + +void LLInventoryGallery::onDelete(const LLSD& notification, const LLSD& response, const selection_deque selected_ids) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + for (const LLUUID& id : selected_ids) + { + LLInventoryObject* obj = gInventory.getObject(id); + if (!obj) + { + return; + } + if (obj->getType() == LLAssetType::AT_CATEGORY) + { + if (get_is_category_removable(&gInventory, id)) + { + gInventory.removeCategory(id); + } + } + else + { + if (get_is_item_removable(&gInventory, id)) + { + gInventory.removeItem(id); + } + } + } + } +} + +void LLInventoryGallery::deleteSelection() +{ + // Undo delete item confirmation per-session annoyance + //if (!LLInventoryAction::sDeleteConfirmationDisplayed) // ask for the confirmation at least once per session + //{ + // LLNotifications::instance().setIgnored("DeleteItems", false); + // LLInventoryAction::sDeleteConfirmationDisplayed = true; + //} + // + + LLSD args; + // FIRE-31816: Include selection count when deleting more than one object from inventory + //args["QUESTION"] = LLTrans::getString("DeleteItem"); + args["COUNT_TOTAL"] = 1; + args["QUESTION"] = LLTrans::getString("DeleteItem", args); + // + LLNotificationsUtil::add("DeleteItems", args, LLSD(), boost::bind(&LLInventoryGallery::onDelete, _1, _2, mSelectedItemIDs)); +} + +bool LLInventoryGallery::canDeleteSelection() +{ + if (mSelectedItemIDs.empty()) + { + return false; + } + + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (mFolderID == trash_id || gInventory.isObjectDescendentOf(mFolderID, trash_id)) + { + return false; + } + + for (const LLUUID& id : mSelectedItemIDs) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(id); + if (cat) + { + if (!get_is_category_removable(&gInventory, id)) + { + return false; + } + } + else if (!get_is_item_removable(&gInventory, id)) + { + return false; + } + } + + return true; +} + +void LLInventoryGallery::pasteAsLink() +{ + if (!LLClipboard::instance().hasContents()) + { + return; + } + + const LLUUID& current_outfit_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID& marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID& my_outifts_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + + bool paste_into_root = mSelectedItemIDs.empty(); + for (LLUUID& dest : mSelectedItemIDs) + { + LLInventoryObject* obj = gInventory.getObject(dest); + if (!obj || obj->getType() != LLAssetType::AT_CATEGORY) + { + paste_into_root = true; + continue; + } + + pasteAsLink(dest, objects, current_outfit_id, marketplacelistings_id, my_outifts_id); + } + + if (paste_into_root) + { + for (const LLUUID& id : mSelectedItemIDs) + { + if (mItemMap[id]) + { + mItemMap[id]->setSelected(FALSE); + } + } + mSelectedItemIDs.clear(); + + pasteAsLink(mFolderID, objects, current_outfit_id, marketplacelistings_id, my_outifts_id); + } + + LLClipboard::instance().setCutMode(false); +} + +void LLInventoryGallery::pasteAsLink(const LLUUID& dest, + std::vector& objects, + const LLUUID& current_outfit_id, + const LLUUID& marketplacelistings_id, + const LLUUID& my_outifts_id) +{ + const BOOL move_is_into_current_outfit = (dest == current_outfit_id); + const BOOL move_is_into_my_outfits = (dest == my_outifts_id) || gInventory.isObjectDescendentOf(dest, my_outifts_id); + const BOOL move_is_into_marketplacelistings = gInventory.isObjectDescendentOf(dest, marketplacelistings_id); + + if (move_is_into_marketplacelistings || move_is_into_current_outfit || move_is_into_my_outfits) + { + return; + } + + LLPointer cb = NULL; + if (dest == mFolderID) + { + LLHandle handle = getHandle(); + std::function on_link_callback = [handle](const LLUUID& inv_item) + { + LLInventoryGallery* panel = (LLInventoryGallery*)handle.get(); + if (panel) + { + // Scroll to pasted item and highlight it + // Should it only highlight the last one? + panel->addItemSelection(inv_item, true); + } + }; + cb = new LLBoostFuncInventoryCallback(on_link_callback); + } + + for (std::vector::const_iterator iter = objects.begin(); + iter != objects.end(); + ++iter) + { + const LLUUID& object_id = (*iter); + if (LLConstPointer link_obj = gInventory.getObject(object_id)) + { + link_inventory_object(dest, link_obj, cb); + } + } +} + +void LLInventoryGallery::claimEditHandler() +{ + gEditMenuHandler = this; +} + +void LLInventoryGallery::resetEditHandler() +{ + if (gEditMenuHandler == this) + { + gEditMenuHandler = NULL; + } +} + +bool LLInventoryGallery::isItemCopyable(const LLUUID & item_id) +{ + const LLInventoryCategory* cat = gInventory.getCategory(item_id); + if (cat) + { + // Folders are copyable if items in them are, recursively, copyable. + // Get the content of the folder + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(item_id, cat_array, item_array); + + // Check the items + LLInventoryModel::item_array_t item_array_copy = *item_array; + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + if (!isItemCopyable(item->getUUID())) + { + return false; + } + } + + // Check the folders + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLViewerInventoryCategory* category = *iter; + if (!isItemCopyable(category->getUUID())) + { + return false; + } + } + + return true; + } + + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (item) + { + // Can't copy worn objects. + // Worn objects are tied to their inworld conterparts + // Copy of modified worn object will return object with obsolete asset and inventory + if (get_is_item_worn(item_id)) + { + return false; + } + + static LLCachedControl inventory_linking(gSavedSettings, "InventoryLinking", true); + return (item->getIsLinkType() && inventory_linking) + || item->getPermissions().allowCopyBy(gAgent.getID()); + } + + return false; +} + +void LLInventoryGallery::updateMessageVisibility() +{ + + mMessageTextBox->setVisible(mItems.empty()); + if(mItems.empty()) + { + mMessageTextBox->setText(hasDescendents(mFolderID) ? LLTrans::getString("InventorySingleFolderEmpty") : LLTrans::getString("InventorySingleFolderNoMatches")); + } + + mScrollPanel->setVisible(!mItems.empty()); +} + +void LLInventoryGallery::refreshList(const LLUUID& category_id) +{ + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + + gInventory.getDirectDescendentsOf(category_id, cat_array, item_array); + uuid_vec_t vadded; + uuid_vec_t vremoved; + + // Create added and removed items vectors. + computeDifference(*cat_array, *item_array, vadded, vremoved); + + // Handle added tabs. + for (uuid_vec_t::const_iterator iter = vadded.begin(); + iter != vadded.end(); + ++iter) + { + const LLUUID cat_id = (*iter); + updateAddedItem(cat_id); + mNeedsArrange = true; + } + + // Handle removed tabs. + for (uuid_vec_t::const_iterator iter = vremoved.begin(); iter != vremoved.end(); ++iter) + { + const LLUUID cat_id = (*iter); + updateRemovedItem(cat_id); + } + + const LLInventoryModel::changed_items_t& changed_items = gInventory.getChangedIDs(); + for (LLInventoryModel::changed_items_t::const_iterator items_iter = changed_items.begin(); + items_iter != changed_items.end(); + ++items_iter) + { + LLInventoryObject* obj = gInventory.getObject(*items_iter); + if(!obj) + { + return; + } + + updateChangedItemName(*items_iter, obj->getName()); + mNeedsArrange = true; + } + + if(mNeedsArrange || !mItemsToSelect.empty()) + { + // Don't scroll to target/arrange immediately + // since more updates might be pending + gIdleCallbacks.addFunction(onIdle, (void*)this); + } + updateMessageVisibility(); +} + +void LLInventoryGallery::computeDifference( + const LLInventoryModel::cat_array_t vcats, + const LLInventoryModel::item_array_t vitems, + uuid_vec_t& vadded, + uuid_vec_t& vremoved) +{ + uuid_vec_t vnew; + // Creating a vector of newly collected UUIDs. + for (LLInventoryModel::cat_array_t::const_iterator iter = vcats.begin(); + iter != vcats.end(); + iter++) + { + vnew.push_back((*iter)->getUUID()); + } + for (LLInventoryModel::item_array_t::const_iterator iter = vitems.begin(); + iter != vitems.end(); + iter++) + { + vnew.push_back((*iter)->getUUID()); + } + + uuid_vec_t vcur; + getCurrentCategories(vcur); + std::copy(mItemBuildQuery.begin(), mItemBuildQuery.end(), std::back_inserter(vcur)); + + LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved); +} + +void LLInventoryGallery::onCOFChanged() +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + + gInventory.collectDescendents( + LLAppearanceMgr::instance().getCOF(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + + uuid_vec_t vnew; + uuid_vec_t vadded; + uuid_vec_t vremoved; + + for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin(); + iter != item_array.end(); + ++iter) + { + vnew.push_back((*iter)->getLinkedUUID()); + } + + // We need to update only items that were added or removed from COF. + LLCommonUtils::computeDifference(vnew, mCOFLinkedItems, vadded, vremoved); + + mCOFLinkedItems = vnew; + + for (uuid_vec_t::const_iterator iter = vadded.begin(); + iter != vadded.end(); + ++iter) + { + updateWornItem(*iter, true); + } + + for (uuid_vec_t::const_iterator iter = vremoved.begin(); iter != vremoved.end(); ++iter) + { + updateWornItem(*iter, false); + } +} + +void LLInventoryGallery::onGesturesChanged() +{ + uuid_vec_t vnew; + uuid_vec_t vadded; + uuid_vec_t vremoved; + + const LLGestureMgr::item_map_t& active_gestures = LLGestureMgr::instance().getActiveGestures(); + for (LLGestureMgr::item_map_t::const_iterator iter = active_gestures.begin(); + iter != active_gestures.end(); + ++iter) + { + vnew.push_back(iter->first); + } + + LLCommonUtils::computeDifference(vnew, mActiveGestures, vadded, vremoved); + + mActiveGestures = vnew; + + for (uuid_vec_t::const_iterator iter = vadded.begin(); + iter != vadded.end(); + ++iter) + { + updateWornItem(*iter, true); + } + + for (uuid_vec_t::const_iterator iter = vremoved.begin(); iter != vremoved.end(); ++iter) + { + updateWornItem(*iter, false); + } +} + +void LLInventoryGallery::deselectItem(const LLUUID& category_id) +{ + // Reset selection if the item is selected. + LLInventoryGalleryItem* item = mItemMap[category_id]; + if (item && item->isSelected()) + { + mItemMap[category_id]->setSelected(FALSE); + setFocus(true); + // Todo: support multiselect + // signalSelectionItemID(LLUUID::null); + } + + selection_deque::iterator found = std::find(mSelectedItemIDs.begin(), mSelectedItemIDs.end(), category_id); + if (found != mSelectedItemIDs.end()) + { + mSelectedItemIDs.erase(found); + } +} + +void LLInventoryGallery::clearSelection() +{ + for (const LLUUID& id: mSelectedItemIDs) + { + if (mItemMap[id]) + { + mItemMap[id]->setSelected(FALSE); + } + } + if (!mSelectedItemIDs.empty()) + { + mSelectedItemIDs.clear(); + // BUG: wrong, item can be null + signalSelectionItemID(LLUUID::null); + } +} + +void LLInventoryGallery::signalSelectionItemID(const LLUUID& category_id) +{ + mSelectionChangeSignal(category_id); +} + +boost::signals2::connection LLInventoryGallery::setSelectionChangeCallback(selection_change_callback_t cb) +{ + return mSelectionChangeSignal.connect(cb); +} + +LLUUID LLInventoryGallery::getFirstSelectedItemID() +{ + if (mSelectedItemIDs.size() > 0) + { + return *mSelectedItemIDs.begin(); + } + return LLUUID::null; +} + +LLUUID LLInventoryGallery::getOutfitImageID(LLUUID outfit_id) +{ + LLUUID thumbnail_id; + LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_id); + if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT) + { + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + // Not LLIsOfAssetType, because we allow links + LLIsTextureType f; + gInventory.getDirectDescendentsOf(outfit_id, cats, items, f); + + // Exactly one texture found => show the texture as thumbnail + if (1 == items.size()) + { + LLViewerInventoryItem* item = items.front(); + if (item && item->getIsLinkType()) + { + item = item->getLinkedItem(); + } + if (item) + { + thumbnail_id = item->getAssetUUID(); + } + } + } + return thumbnail_id; +} + +boost::signals2::connection LLInventoryGallery::setRootChangedCallback(callback_t cb) +{ + return mRootChangedSignal.connect(cb); +} + +void LLInventoryGallery::onForwardFolder() +{ + if(isForwardAvailable()) + { + mBackwardFolders.push_back(mFolderID); + mFolderID = mForwardFolders.back(); + mForwardFolders.pop_back(); + dirtyRootFolder(); + } +} + +void LLInventoryGallery::onBackwardFolder() +{ + if(isBackwardAvailable()) + { + mForwardFolders.push_back(mFolderID); + mFolderID = mBackwardFolders.back(); + mBackwardFolders.pop_back(); + dirtyRootFolder(); + } +} + +void LLInventoryGallery::clearNavigationHistory() +{ + mForwardFolders.clear(); + mBackwardFolders.clear(); +} + +bool LLInventoryGallery::isBackwardAvailable() +{ + return (!mBackwardFolders.empty() && (mFolderID != mBackwardFolders.back())); +} + +bool LLInventoryGallery::isForwardAvailable() +{ + return (!mForwardFolders.empty() && (mFolderID != mForwardFolders.back())); +} + +BOOL LLInventoryGallery::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, void* cargo_data, + EAcceptance* accept, std::string& tooltip_msg) +{ + // have children handle it first + BOOL handled = LLView::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, + accept, tooltip_msg); + + // when drop is not handled by child, it should be handled by the root folder . + if (!handled || (*accept == ACCEPT_NO)) + { + handled = baseHandleDragAndDrop(mFolderID, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + + return handled; +} + +void LLInventoryGallery::startDrag() +{ + std::vector types; + uuid_vec_t ids; + LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_AGENT; + for (LLUUID& selected_id : mSelectedItemIDs) + { + const LLInventoryItem* item = gInventory.getItem(selected_id); + if (item) + { + if (item->getPermissions().getOwner() == ALEXANDRIA_LINDEN_ID) + { + src = LLToolDragAndDrop::SOURCE_LIBRARY; + } + + EDragAndDropType type = LLViewerAssetType::lookupDragAndDropType(item->getType()); + types.push_back(type); + ids.push_back(selected_id); + } + + const LLViewerInventoryCategory* cat = gInventory.getCategory(selected_id); + if (cat && gInventory.isObjectDescendentOf(selected_id, gInventory.getRootFolderID()) + && !LLFolderType::lookupIsProtectedType((cat)->getPreferredType())) + { + if (cat->getOwnerID() == ALEXANDRIA_LINDEN_ID) + { + src = LLToolDragAndDrop::SOURCE_LIBRARY; + } + + EDragAndDropType type = LLViewerAssetType::lookupDragAndDropType(cat->getType()); + types.push_back(type); + ids.push_back(selected_id); + } + } + LLToolDragAndDrop::getInstance()->beginMultiDrag(types, ids, LLToolDragAndDrop::SOURCE_AGENT); +} + +bool LLInventoryGallery::areViewsInitialized() +{ + return mGalleryCreated && mItemBuildQuery.empty(); +} + +bool LLInventoryGallery::hasDescendents(const LLUUID& cat_id) +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_id, cats, items); + + return (cats->empty() && items->empty()); +} + +bool LLInventoryGallery::checkAgainstFilterType(const LLUUID& object_id) +{ + const LLInventoryObject *object = gInventory.getObject(object_id); + if(!object) return false; + + LLInventoryType::EType object_type = LLInventoryType::IT_CATEGORY; + LLInventoryItem* inv_item = gInventory.getItem(object_id); + if (inv_item) + { + object_type = inv_item->getInventoryType(); + } + const U32 filterTypes = mFilter->getFilterTypes(); + + if ((filterTypes & LLInventoryFilter::FILTERTYPE_OBJECT) && inv_item) + { + switch (object_type) + { + case LLInventoryType::IT_NONE: + // If it has no type, pass it, unless it's a link. + if (object && object->getIsLinkType()) + { + return false; + } + break; + case LLInventoryType::IT_UNKNOWN: + { + // Unknows are only shown when we show every type. + // Unknows are 255 and won't fit in 64 bits. + if (mFilter->getFilterObjectTypes() != 0xffffffffffffffffULL) + { + return false; + } + break; + } + default: + if ((1LL << object_type & mFilter->getFilterObjectTypes()) == U64(0)) + { + return false; + } + break; + } + } + + if (filterTypes & LLInventoryFilter::FILTERTYPE_DATE) + { + const U16 HOURS_TO_SECONDS = 3600; + time_t earliest = time_corrected() - mFilter->getHoursAgo() * HOURS_TO_SECONDS; + + if (mFilter->getMinDate() > time_min() && mFilter->getMinDate() < earliest) + { + earliest = mFilter->getMinDate(); + } + else if (!mFilter->getHoursAgo()) + { + earliest = 0; + } + + if (LLInventoryFilter::FILTERDATEDIRECTION_NEWER == mFilter->getDateSearchDirection() || mFilter->isSinceLogoff()) + { + if (object->getCreationDate() < earliest || + object->getCreationDate() > mFilter->getMaxDate()) + return false; + } + else + { + if (object->getCreationDate() > earliest || + object->getCreationDate() > mFilter->getMaxDate()) + return false; + } + } + return true; +} + +bool LLInventoryGallery::hasVisibleItems() +{ + return mItemsAddedCount > 0; +} + +void LLInventoryGallery::handleModifiedFilter() +{ + if(mFilter->isModified()) + { + reArrangeRows(); + } +} + +void LLInventoryGallery::setSortOrder(U32 order, bool update) +{ + bool dirty = (mSortOrder != order); + + mSortOrder = order; + if(update && dirty) + { + mNeedsArrange = true; + gIdleCallbacks.addFunction(onIdle, (void*)this); + } +} +//----------------------------- +// LLInventoryGalleryItem +//----------------------------- + +static LLDefaultChildRegistry::Register r("inventory_gallery_item"); + +LLInventoryGalleryItem::LLInventoryGalleryItem(const Params& p) + : LLPanel(p), + mSelected(false), + mDefaultImage(true), + mItemName(""), + mWornSuffix(""), + mPermSuffix(""), + mUUID(LLUUID()), + mIsFolder(true), + mIsLink(false), + mHidden(false), + mGallery(NULL), + mType(LLAssetType::AT_NONE), + mSortGroup(SG_ITEM), + mCutGeneration(0), + mSelectedForCut(false) +{ + buildFromFile("panel_inventory_gallery_item.xml"); +} + +LLInventoryGalleryItem::~LLInventoryGalleryItem() +{ +} + +BOOL LLInventoryGalleryItem::postBuild() +{ + mNameText = getChild("item_name"); + mTextBgPanel = getChild("text_bg_panel"); + + return TRUE; +} + +void LLInventoryGalleryItem::setType(LLAssetType::EType type, LLInventoryType::EType inventory_type, U32 flags, bool is_link) +{ + mType = type; + mIsFolder = (mType == LLAssetType::AT_CATEGORY); + mIsLink = is_link; + + std::string icon_name = LLInventoryIcon::getIconName(mType, inventory_type, flags); + if(mIsFolder) + { + mSortGroup = SG_NORMAL_FOLDER; + LLUUID folder_id = mUUID; + if(mIsLink) + { + LLInventoryObject* obj = gInventory.getObject(mUUID); + if (obj) + { + folder_id = obj->getLinkedUUID(); + } + } + LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); + if (cat) + { + LLFolderType::EType preferred_type = cat->getPreferredType(); + icon_name = LLViewerFolderType::lookupIconName(preferred_type); + + if (preferred_type == LLFolderType::FT_TRASH) + { + mSortGroup = SG_TRASH_FOLDER; + } + else if(LLFolderType::lookupIsProtectedType(cat->getPreferredType())) + { + mSortGroup = SG_SYSTEM_FOLDER; + } + } + } + else + { + const LLInventoryItem *item = gInventory.getItem(mUUID); + if(item && (LLAssetType::AT_CALLINGCARD != item->getType()) && !mIsLink) + { + std::string delim(" --"); + bool copy = item->getPermissions().allowCopyBy(gAgent.getID()); + if (!copy) + { + mPermSuffix += delim; + mPermSuffix += LLTrans::getString("no_copy_lbl"); + } + bool mod = item->getPermissions().allowModifyBy(gAgent.getID()); + if (!mod) + { + mPermSuffix += mPermSuffix.empty() ? delim : ","; + mPermSuffix += LLTrans::getString("no_modify_lbl"); + } + bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); + if (!xfer) + { + mPermSuffix += mPermSuffix.empty() ? delim : ","; + mPermSuffix += LLTrans::getString("no_transfer_lbl"); + } + } + } + + getChild("item_type")->setValue(icon_name); + getChild("link_overlay")->setVisible(is_link); +} + +void LLInventoryGalleryItem::setThumbnail(LLUUID id) +{ + mDefaultImage = id.isNull(); + if(mDefaultImage) + { + getChild("preview_thumbnail")->clearTexture(); + } + else + { + getChild("preview_thumbnail")->setValue(id); + } +} + +void LLInventoryGalleryItem::draw() +{ + if (isFadeItem()) + { + // Fade out to indicate it's being cut + LLViewDrawContext context(0.5f); + LLPanel::draw(); + } + else + { + LLPanel::draw(); + + // Draw border + LLUIColor border_color = LLUIColorTable::instance().getColor(mSelected ? "MenuItemHighlightBgColor" : "TextFgTentativeColor", LLColor4::white); + LLRect border = getChildView("preview_thumbnail")->getRect(); + border.mRight = border.mRight + 1; + border.mTop = border.mTop + 1; + gl_rect_2d(border, border_color.get(), FALSE); + } +} + +void LLInventoryGalleryItem::setItemName(std::string name) +{ + mItemName = name; + updateNameText(); +} + +void LLInventoryGalleryItem::setSelected(bool value) +{ + mSelected = value; + mTextBgPanel->setBackgroundVisible(value); + + if(mSelected) + { + LLViewerInventoryItem* item = gInventory.getItem(mUUID); + if(item && !item->isFinished()) + { + LLInventoryModelBackgroundFetch::instance().start(mUUID, false); + } + } +} + +BOOL LLInventoryGalleryItem::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // call changeItemSelection directly, before setFocus + // to avoid autoscroll from LLInventoryGallery::onFocusReceived() + if (mask == MASK_CONTROL) + { + mGallery->addItemSelection(mUUID, false); + } + else if (mask == MASK_SHIFT) + { + mGallery->toggleSelectionRangeFromLast(mUUID); + } + else + { + mGallery->changeItemSelection(mUUID, false); + } + setFocus(TRUE); + mGallery->claimEditHandler(); + + gFocusMgr.setMouseCapture(this); + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y ); + LLToolDragAndDrop::getInstance()->setDragStart(screen_x, screen_y); + return TRUE; +} + +BOOL LLInventoryGalleryItem::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (!isSelected()) + { + mGallery->changeItemSelection(mUUID, false); + } + else + { + // refresh last interacted + mGallery->addItemSelection(mUUID, false); + } + setFocus(TRUE); + mGallery->claimEditHandler(); + mGallery->showContextMenu(this, x, y, mUUID); + + LLUICtrl::handleRightMouseDown(x, y, mask); + return TRUE; +} + +BOOL LLInventoryGalleryItem::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if(hasMouseCapture()) + { + gFocusMgr.setMouseCapture(NULL); + return TRUE; + } + return LLPanel::handleMouseUp(x, y, mask); +} + +BOOL LLInventoryGalleryItem::handleHover(S32 x, S32 y, MASK mask) +{ + if(hasMouseCapture()) + { + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y ); + + if(LLToolDragAndDrop::getInstance()->isOverThreshold(screen_x, screen_y) && mGallery) + { + mGallery->startDrag(); + return LLToolDragAndDrop::getInstance()->handleHover(x, y, mask); + } + } + return LLUICtrl::handleHover(x,y,mask); +} + +BOOL LLInventoryGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (mIsFolder && mGallery) + { + // setRootFolder can destroy this item. + // Delay it until handleDoubleClick processing is complete + // or make gallery handle doubleclicks. + LLHandle handle = mGallery->getHandle(); + LLUUID navigate_to = mUUID; + doOnIdleOneTime([handle, navigate_to]() + { + LLInventoryGallery* gallery = (LLInventoryGallery*)handle.get(); + if (gallery) + { + gallery->setRootFolder(navigate_to); + } + }); + } + else + { + LLInvFVBridgeAction::doAction(mUUID, &gInventory); + } + + return TRUE; +} + +BOOL LLInventoryGalleryItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + if (!mIsFolder) + { + return FALSE; + } + return mGallery->baseHandleDragAndDrop(mUUID, drop, cargo_type, cargo_data, accept, tooltip_msg); +} + +BOOL LLInventoryGalleryItem::handleKeyHere(KEY key, MASK mask) +{ + if (!mGallery) + { + return FALSE; + } + + BOOL handled = FALSE; + switch (key) + { + case KEY_LEFT: + mGallery->moveLeft(mask); + handled = true; + break; + + case KEY_RIGHT: + mGallery->moveRight(mask); + handled = true; + break; + + case KEY_UP: + mGallery->moveUp(mask); + handled = true; + break; + + case KEY_DOWN: + mGallery->moveDown(mask); + handled = true; + break; + + default: + break; + } + return handled; +} + +void LLInventoryGalleryItem::onFocusLost() +{ + // inventory no longer handles cut/copy/paste/delete + mGallery->resetEditHandler(); + + LLPanel::onFocusLost(); +} + +void LLInventoryGalleryItem::onFocusReceived() +{ + // inventory now handles cut/copy/paste/delete + mGallery->claimEditHandler(); + + LLPanel::onFocusReceived(); +} + +void LLInventoryGalleryItem::setWorn(bool value) +{ + mWorn = value; + + if(mWorn) + { + mWornSuffix = (mType == LLAssetType::AT_GESTURE) ? LLTrans::getString("active") : LLTrans::getString("worn"); + } + else + { + mWornSuffix = ""; + } + + updateNameText(); +} + +LLFontGL* LLInventoryGalleryItem::getTextFont() +{ + if(mWorn) + { + return LLFontGL::getFontSansSerifSmallBold(); + } + return mIsLink ? LLFontGL::getFontSansSerifSmallItalic() : LLFontGL::getFontSansSerifSmall(); +} + +void LLInventoryGalleryItem::updateNameText() +{ + mNameText->setFont(getTextFont()); + mNameText->setText(mItemName + mPermSuffix + mWornSuffix); + mNameText->setToolTip(mItemName + mPermSuffix + mWornSuffix); + getChild("preview_thumbnail")->setToolTip(mItemName + mPermSuffix + mWornSuffix); +} + +bool LLInventoryGalleryItem::isFadeItem() +{ + LLClipboard& clipboard = LLClipboard::instance(); + if (mCutGeneration == clipboard.getGeneration()) + { + return mSelectedForCut; + } + + mCutGeneration = clipboard.getGeneration(); + mSelectedForCut = clipboard.isCutMode() && clipboard.isOnClipboard(mUUID); + return mSelectedForCut; +} + +//----------------------------- +// LLThumbnailsObserver +//----------------------------- + +void LLThumbnailsObserver::changed(U32 mask) +{ + std::vector deleted_ids; + for (item_map_t::iterator iter = mItemMap.begin(); + iter != mItemMap.end(); + ++iter) + { + const LLUUID& obj_id = (*iter).first; + LLItemData& data = (*iter).second; + + LLInventoryObject* obj = gInventory.getObject(obj_id); + if (!obj) + { + deleted_ids.push_back(obj_id); + continue; + } + + const LLUUID thumbnail_id = obj->getThumbnailUUID(); + if (data.mThumbnailID != thumbnail_id) + { + data.mThumbnailID = thumbnail_id; + data.mCallback(); + } + } + + // Remove deleted items from the list + for (std::vector::iterator deleted_id = deleted_ids.begin(); deleted_id != deleted_ids.end(); ++deleted_id) + { + removeItem(*deleted_id); + } +} + +bool LLThumbnailsObserver::addItem(const LLUUID& obj_id, callback_t cb) +{ + LLInventoryObject* obj = gInventory.getObject(obj_id); + if (obj) + { + mItemMap.insert(item_map_value_t(obj_id, LLItemData(obj_id, obj->getThumbnailUUID(), cb))); + return true; + } + return false; +} + +void LLThumbnailsObserver::removeItem(const LLUUID& obj_id) +{ + mItemMap.erase(obj_id); +} + +//----------------------------- +// Helper drag&drop functions +//----------------------------- + +BOOL LLInventoryGallery::baseHandleDragAndDrop(LLUUID dest_id, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; + + if (drop && LLToolDragAndDrop::getInstance()->getCargoIndex() == 0) + { + clearSelection(); + } + + BOOL accepted = FALSE; + switch(cargo_type) + { + case DAD_TEXTURE: + case DAD_SOUND: + case DAD_CALLINGCARD: + case DAD_LANDMARK: + case DAD_SCRIPT: + case DAD_CLOTHING: + case DAD_OBJECT: + case DAD_NOTECARD: + case DAD_BODYPART: + case DAD_ANIMATION: + case DAD_GESTURE: + case DAD_MESH: + case DAD_SETTINGS: + accepted = dragItemIntoFolder(dest_id, inv_item, drop, tooltip_msg, true); + if (accepted && drop) + { + // Don't select immediately, wait for item to arrive + mItemsToSelect.push_back(inv_item->getUUID()); + } + break; + case DAD_LINK: + // DAD_LINK type might mean one of two asset types: AT_LINK or AT_LINK_FOLDER. + // If we have an item of AT_LINK_FOLDER type we should process the linked + // category being dragged or dropped into folder. + if (inv_item && LLAssetType::AT_LINK_FOLDER == inv_item->getActualType()) + { + LLInventoryCategory* linked_category = gInventory.getCategory(inv_item->getLinkedUUID()); + if (linked_category) + { + accepted = dragCategoryIntoFolder(dest_id, (LLInventoryCategory*)linked_category, drop, tooltip_msg, TRUE); + } + } + else + { + accepted = dragItemIntoFolder(dest_id, inv_item, drop, tooltip_msg, TRUE); + } + if (accepted && drop && inv_item) + { + mItemsToSelect.push_back(inv_item->getUUID()); + } + break; + case DAD_CATEGORY: + if (LLFriendCardsManager::instance().isAnyFriendCategory(dest_id)) + { + accepted = FALSE; + } + else + { + LLInventoryCategory* cat_ptr = (LLInventoryCategory*)cargo_data; + accepted = dragCategoryIntoFolder(dest_id, cat_ptr, drop, tooltip_msg, FALSE); + if (accepted && drop) + { + mItemsToSelect.push_back(cat_ptr->getUUID()); + } + } + break; + case DAD_ROOT_CATEGORY: + case DAD_NONE: + break; + default: + LL_WARNS() << "Unhandled cargo type for drag&drop " << cargo_type << LL_ENDL; + break; + } + if (accepted) + { + *accept = ACCEPT_YES_MULTI; + } + else + { + *accept = ACCEPT_NO; + } + return accepted; +} + +// copy of LLFolderBridge::dragItemIntoFolder +BOOL dragItemIntoFolder(LLUUID folder_id, LLInventoryItem* inv_item, BOOL drop, std::string& tooltip_msg, BOOL user_confirm) +{ + LLViewerInventoryCategory * cat = gInventory.getCategory(folder_id); + if (!cat) + { + return FALSE; + } + LLInventoryModel* model = &gInventory; + + if (!model || !inv_item) return FALSE; + + // cannot drag into library + if((gInventory.getRootFolderID() != folder_id) && !model->isObjectDescendentOf(folder_id, gInventory.getRootFolderID())) + { + return FALSE; + } + if (!isAgentAvatarValid()) return FALSE; + + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + const BOOL move_is_into_current_outfit = (folder_id == current_outfit_id); + const BOOL move_is_into_favorites = (folder_id == favorites_id); + const BOOL move_is_into_my_outfits = (folder_id == my_outifts_id) || model->isObjectDescendentOf(folder_id, my_outifts_id); + const BOOL move_is_into_outfit = move_is_into_my_outfits || (cat && cat->getPreferredType()==LLFolderType::FT_OUTFIT); + const BOOL move_is_into_landmarks = (folder_id == landmarks_id) || model->isObjectDescendentOf(folder_id, landmarks_id); + const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(folder_id, marketplacelistings_id); + const BOOL move_is_from_marketplacelistings = model->isObjectDescendentOf(inv_item->getUUID(), marketplacelistings_id); + + LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); + BOOL accept = FALSE; + LLViewerObject* object = NULL; + if(LLToolDragAndDrop::SOURCE_AGENT == source) + { + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + + const BOOL move_is_into_trash = (folder_id == trash_id) || model->isObjectDescendentOf(folder_id, trash_id); + const BOOL move_is_outof_current_outfit = LLAppearanceMgr::instance().getIsInCOF(inv_item->getUUID()); + + //-------------------------------------------------------------------------------- + // Determine if item can be moved. + // + + BOOL is_movable = TRUE; + + switch (inv_item->getActualType()) + { + case LLAssetType::AT_CATEGORY: + is_movable = !LLFolderType::lookupIsProtectedType(((LLInventoryCategory*)inv_item)->getPreferredType()); + break; + default: + break; + } + // Can't explicitly drag things out of the COF. + if (move_is_outof_current_outfit) + { + is_movable = FALSE; + } + if (move_is_into_trash) + { + is_movable &= inv_item->getIsLinkType() || !get_is_item_worn(inv_item->getUUID()); + } + if (is_movable) + { + // Don't allow creating duplicates in the Calling Card/Friends + // subfolders, see bug EXT-1599. Check is item direct descendent + // of target folder and forbid item's movement if it so. + // Note: isItemDirectDescendentOfCategory checks if + // passed category is in the Calling Card/Friends folder + is_movable &= !LLFriendCardsManager::instance().isObjDirectDescendentOfCategory(inv_item, cat); + } + + // + //-------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------- + // Determine if item can be moved & dropped + // Note: if user_confirm is false, we already went through those accept logic test and can skip them + + accept = TRUE; + + if (user_confirm && !is_movable) + { + accept = FALSE; + } + else if (user_confirm && (folder_id == inv_item->getParentUUID()) && !move_is_into_favorites) + { + accept = FALSE; + } + else if (user_confirm && (move_is_into_current_outfit || move_is_into_outfit)) + { + accept = can_move_to_outfit(inv_item, move_is_into_current_outfit); + } + else if (user_confirm && (move_is_into_favorites || move_is_into_landmarks)) + { + accept = can_move_to_landmarks(inv_item); + } + else if (user_confirm && move_is_into_marketplacelistings) + { + //disable dropping in or out of marketplace for now + return FALSE; + + /*const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, folder_id); + LLViewerInventoryCategory * dest_folder = cat; + accept = can_move_item_to_marketplace(master_folder, dest_folder, inv_item, tooltip_msg, LLToolDragAndDrop::instance().getCargoCount() - LLToolDragAndDrop::instance().getCargoIndex());*/ + } + + // Check that the folder can accept this item based on folder/item type compatibility (e.g. stock folder compatibility) + if (user_confirm && accept) + { + LLViewerInventoryCategory * dest_folder = cat; + accept = dest_folder->acceptItem(inv_item); + } + + LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(FALSE); + + if (accept && drop) + { + if (inv_item->getType() == LLAssetType::AT_GESTURE + && LLGestureMgr::instance().isGestureActive(inv_item->getUUID()) && move_is_into_trash) + { + LLGestureMgr::instance().deactivateGesture(inv_item->getUUID()); + } + // If an item is being dragged between windows, unselect everything in the active window + // so that we don't follow the selection to its new location (which is very annoying). + // RN: a better solution would be to deselect automatically when an item is moved + // and then select any item that is dropped only in the panel that it is dropped in + if (active_panel) + { + active_panel->unSelectAll(); + } + // Dropping in or out of marketplace needs (sometimes) confirmation + if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) + { + //disable dropping in or out of marketplace for now + return FALSE; + } + + //-------------------------------------------------------------------------------- + // Destination folder logic + // + + // FAVORITES folder + // (copy the item) + else if (move_is_into_favorites) + { + copy_inventory_item( + gAgent.getID(), + inv_item->getPermissions().getOwner(), + inv_item->getUUID(), + folder_id, + std::string(), + LLPointer(NULL)); + } + // CURRENT OUTFIT or OUTFIT folder + // (link the item) + else if (move_is_into_current_outfit || move_is_into_outfit) + { + if (move_is_into_current_outfit) + { + LLAppearanceMgr::instance().wearItemOnAvatar(inv_item->getUUID(), true, true); + } + else + { + LLPointer cb = NULL; + link_inventory_object(folder_id, LLConstPointer(inv_item), cb); + } + } + // MARKETPLACE LISTINGS folder + // Move the item + else if (move_is_into_marketplacelistings) + { + //move_item_to_marketplacelistings(inv_item, mUUID); + return FALSE; + } + // NORMAL or TRASH folder + // (move the item, restamp if into trash) + else + { + // set up observer to select item once drag and drop from inbox is complete + if (gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX))) + { + set_dad_inbox_object(inv_item->getUUID()); + } + + gInventory.changeItemParent((LLViewerInventoryItem*)inv_item, folder_id, move_is_into_trash); + } + + if (move_is_from_marketplacelistings) + { + // If we move from an active (listed) listing, checks that it's still valid, if not, unlist + /*LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); + if (version_folder_id.notNull()) + { + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [version_folder_id](bool result) + { + if (!result) + { + LLMarketplaceData::instance().activateListing(version_folder_id, false); + } + }); + }*/ + return FALSE; + } + + // + //-------------------------------------------------------------------------------- + } + } + else if (LLToolDragAndDrop::SOURCE_WORLD == source) + { + // Make sure the object exists. If we allowed dragging from + // anonymous objects, it would be possible to bypass + // permissions. + object = gObjectList.findObject(inv_item->getParentUUID()); + if (!object) + { + LL_INFOS() << "Object not found for drop." << LL_ENDL; + return FALSE; + } + + // coming from a task. Need to figure out if the person can + // move/copy this item. + LLPermissions perm(inv_item->getPermissions()); + bool is_move = false; + if ((perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) + && perm.allowTransferTo(gAgent.getID()))) + // || gAgent.isGodlike()) + { + accept = TRUE; + } + else if(object->permYouOwner()) + { + // If the object cannot be copied, but the object the + // inventory is owned by the agent, then the item can be + // moved from the task to agent inventory. + is_move = true; + accept = TRUE; + } + + // Don't allow placing an original item into Current Outfit or an outfit folder + // because they must contain only links to wearable items. + if (move_is_into_current_outfit || move_is_into_outfit) + { + accept = FALSE; + } + // Don't allow to move a single item to Favorites or Landmarks + // if it is not a landmark or a link to a landmark. + else if ((move_is_into_favorites || move_is_into_landmarks) + && !can_move_to_landmarks(inv_item)) + { + accept = FALSE; + } + else if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + + if (accept && drop) + { + boost::shared_ptr move_inv (new LLMoveInv()); + move_inv->mObjectID = inv_item->getParentUUID(); + std::pair item_pair(folder_id, inv_item->getUUID()); + move_inv->mMoveList.push_back(item_pair); + move_inv->mCallback = NULL; + move_inv->mUserData = NULL; + if(is_move) + { + warn_move_inventory(object, move_inv); + } + else + { + // store dad inventory item to select added one later. See EXT-4347 + set_dad_inventory_item(inv_item, folder_id); + + LLNotification::Params params("MoveInventoryFromObject"); + params.functor.function(boost::bind(move_task_inventory_callback, _1, _2, move_inv)); + LLNotifications::instance().forceResponse(params, 0); + } + } + } + else if(LLToolDragAndDrop::SOURCE_NOTECARD == source) + { + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + else if ((inv_item->getActualType() == LLAssetType::AT_SETTINGS) && !LLEnvironment::instance().isInventoryEnabled()) + { + tooltip_msg = LLTrans::getString("NoEnvironmentSettings"); + accept = FALSE; + } + else + { + // Don't allow placing an original item from a notecard to Current Outfit or an outfit folder + // because they must contain only links to wearable items. + accept = !(move_is_into_current_outfit || move_is_into_outfit); + } + + if (accept && drop) + { + copy_inventory_from_notecard(folder_id, // Drop to the chosen destination folder + LLToolDragAndDrop::getInstance()->getObjectID(), + LLToolDragAndDrop::getInstance()->getSourceID(), + inv_item); + } + } + else if(LLToolDragAndDrop::SOURCE_LIBRARY == source) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)inv_item; + if(item && item->isFinished()) + { + accept = TRUE; + + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + else if (move_is_into_current_outfit || move_is_into_outfit) + { + accept = can_move_to_outfit(inv_item, move_is_into_current_outfit); + } + // Don't allow to move a single item to Favorites or Landmarks + // if it is not a landmark or a link to a landmark. + else if (move_is_into_favorites || move_is_into_landmarks) + { + accept = can_move_to_landmarks(inv_item); + } + + if (accept && drop) + { + // FAVORITES folder + // (copy the item) + if (move_is_into_favorites) + { + copy_inventory_item( + gAgent.getID(), + inv_item->getPermissions().getOwner(), + inv_item->getUUID(), + folder_id, + std::string(), + LLPointer(NULL)); + } + // CURRENT OUTFIT or OUTFIT folder + // (link the item) + else if (move_is_into_current_outfit || move_is_into_outfit) + { + if (move_is_into_current_outfit) + { + LLAppearanceMgr::instance().wearItemOnAvatar(inv_item->getUUID(), true, true); + } + else + { + LLPointer cb = NULL; + link_inventory_object(folder_id, LLConstPointer(inv_item), cb); + } + } + else + { + copy_inventory_item( + gAgent.getID(), + inv_item->getPermissions().getOwner(), + inv_item->getUUID(), + folder_id, + std::string(), + LLPointer(NULL)); + } + } + } + } + else + { + LL_WARNS() << "unhandled drag source" << LL_ENDL; + } + return accept; +} + +// copy of LLFolderBridge::dragCategoryIntoFolder +BOOL dragCategoryIntoFolder(LLUUID dest_id, LLInventoryCategory* inv_cat, + BOOL drop, std::string& tooltip_msg, BOOL is_link) +{ + BOOL user_confirm = TRUE; + LLInventoryModel* model = &gInventory; + LLViewerInventoryCategory * dest_cat = gInventory.getCategory(dest_id); + if (!dest_cat) + { + return FALSE; + } + + if (!inv_cat) return FALSE; // shouldn't happen, but in case item is incorrectly parented in which case inv_cat will be NULL + + if (!isAgentAvatarValid()) return FALSE; + // cannot drag into library + if((gInventory.getRootFolderID() != dest_id) && !model->isObjectDescendentOf(dest_id, gInventory.getRootFolderID())) + { + return FALSE; + } + + const LLUUID &cat_id = inv_cat->getUUID(); + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + //const LLUUID from_folder_uuid = inv_cat->getParentUUID(); + + const BOOL move_is_into_current_outfit = (dest_id == current_outfit_id); + const BOOL move_is_into_marketplacelistings = model->isObjectDescendentOf(dest_id, marketplacelistings_id); + const BOOL move_is_from_marketplacelistings = model->isObjectDescendentOf(cat_id, marketplacelistings_id); + + // check to make sure source is agent inventory, and is represented there. + LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); + const BOOL is_agent_inventory = (model->getCategory(cat_id) != NULL) + && (LLToolDragAndDrop::SOURCE_AGENT == source); + + BOOL accept = FALSE; + + if (is_agent_inventory) + { + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + + const BOOL move_is_into_trash = (dest_id == trash_id) || model->isObjectDescendentOf(dest_id, trash_id); + const BOOL move_is_into_my_outfits = (dest_id == my_outifts_id) || model->isObjectDescendentOf(dest_id, my_outifts_id); + const BOOL move_is_into_outfit = move_is_into_my_outfits || (dest_cat && dest_cat->getPreferredType()==LLFolderType::FT_OUTFIT); + const BOOL move_is_into_current_outfit = (dest_cat && dest_cat->getPreferredType()==LLFolderType::FT_CURRENT_OUTFIT); + const BOOL move_is_into_landmarks = (dest_id == landmarks_id) || model->isObjectDescendentOf(dest_id, landmarks_id); + const BOOL move_is_into_lost_and_found = model->isObjectDescendentOf(dest_id, lost_and_found_id); + + //-------------------------------------------------------------------------------- + // Determine if folder can be moved. + // + + BOOL is_movable = TRUE; + + if (is_movable && (marketplacelistings_id == cat_id)) + { + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipOutboxCannotMoveRoot"); + } + if (is_movable && move_is_from_marketplacelistings) + //&& LLMarketplaceData::instance().getActivationState(cat_id)) + { + // If the incoming folder is listed and active (and is therefore either the listing or the version folder), + // then moving is *not* allowed + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipOutboxDragActive"); + } + if (is_movable && (dest_id == cat_id)) + { + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipDragOntoSelf"); + } + if (is_movable && (model->isObjectDescendentOf(dest_id, cat_id))) + { + is_movable = FALSE; + tooltip_msg = LLTrans::getString("TooltipDragOntoOwnChild"); + } + if (is_movable && LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) + { + is_movable = FALSE; + // tooltip? + } + + U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit"); + if (is_movable && move_is_into_outfit) + { + if (dest_id == my_outifts_id) + { + if (source != LLToolDragAndDrop::SOURCE_AGENT || move_is_from_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutfitNotInInventory"); + is_movable = false; + } + else if (can_move_to_my_outfits(model, inv_cat, max_items_to_wear)) + { + is_movable = true; + } + else + { + tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit"); + is_movable = false; + } + } + else if(dest_cat && dest_cat->getPreferredType() == LLFolderType::FT_NONE) + { + is_movable = ((inv_cat->getPreferredType() == LLFolderType::FT_NONE) || (inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT)); + } + else + { + is_movable = false; + } + } + if(is_movable && move_is_into_current_outfit && is_link) + { + is_movable = FALSE; + } + if (is_movable && move_is_into_lost_and_found) + { + is_movable = FALSE; + } + if (is_movable && (dest_id == model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE))) + { + is_movable = FALSE; + // tooltip? + } + if (is_movable && (dest_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK)) + { + // One cannot move a folder into a stock folder + is_movable = FALSE; + // tooltip? + } + + LLInventoryModel::cat_array_t descendent_categories; + LLInventoryModel::item_array_t descendent_items; + if (is_movable) + { + model->collectDescendents(cat_id, descendent_categories, descendent_items, FALSE); + for (S32 i=0; i < descendent_categories.size(); ++i) + { + LLInventoryCategory* category = descendent_categories[i]; + if(LLFolderType::lookupIsProtectedType(category->getPreferredType())) + { + // Can't move "special folders" (e.g. Textures Folder). + is_movable = FALSE; + break; + } + } + } + if (is_movable + && move_is_into_current_outfit + && descendent_items.size() > max_items_to_wear) + { + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); + gInventory.collectDescendentsIf(cat_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + not_worn); + + if (items.size() > max_items_to_wear) + { + // Can't move 'large' folders into current outfit: MAINT-4086 + is_movable = FALSE; + LLStringUtil::format_map_t args; + args["AMOUNT"] = llformat("%d", max_items_to_wear); + tooltip_msg = LLTrans::getString("TooltipTooManyWearables",args); + } + } + if (is_movable && move_is_into_trash) + { + for (S32 i=0; i < descendent_items.size(); ++i) + { + LLInventoryItem* item = descendent_items[i]; + if (get_is_item_worn(item->getUUID())) + { + is_movable = FALSE; + break; // It's generally movable, but not into the trash. + } + } + } + if (is_movable && move_is_into_landmarks) + { + for (S32 i=0; i < descendent_items.size(); ++i) + { + LLViewerInventoryItem* item = descendent_items[i]; + + // Don't move anything except landmarks and categories into Landmarks folder. + // We use getType() instead of getActua;Type() to allow links to landmarks and folders. + if (LLAssetType::AT_LANDMARK != item->getType() && LLAssetType::AT_CATEGORY != item->getType()) + { + is_movable = FALSE; + break; // It's generally movable, but not into Landmarks. + } + } + } + + if (is_movable && move_is_into_marketplacelistings) + { + const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, dest_id); + LLViewerInventoryCategory * dest_folder = dest_cat; + S32 bundle_size = (drop ? 1 : LLToolDragAndDrop::instance().getCargoCount()); + is_movable = can_move_folder_to_marketplace(master_folder, dest_folder, inv_cat, tooltip_msg, bundle_size); + } + + // + //-------------------------------------------------------------------------------- + + accept = is_movable; + + if (accept && drop) + { + // Dropping in or out of marketplace needs (sometimes) confirmation + if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) + { + //disable dropping in or out of marketplace for now + return FALSE; + } + // Look for any gestures and deactivate them + if (move_is_into_trash) + { + for (S32 i=0; i < descendent_items.size(); i++) + { + LLInventoryItem* item = descendent_items[i]; + if (item->getType() == LLAssetType::AT_GESTURE + && LLGestureMgr::instance().isGestureActive(item->getUUID())) + { + LLGestureMgr::instance().deactivateGesture(item->getUUID()); + } + } + } + + if (dest_id == my_outifts_id) + { + // Category can contains objects, + // create a new folder and populate it with links to original objects + dropToMyOutfits(inv_cat); + } + // if target is current outfit folder we use link + else if (move_is_into_current_outfit && + (inv_cat->getPreferredType() == LLFolderType::FT_NONE || + inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT)) + { + // traverse category and add all contents to currently worn. + BOOL append = true; + LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, false, append); + } + else if (move_is_into_marketplacelistings) + { + //move_folder_to_marketplacelistings(inv_cat, dest_id); + } + else + { + if (model->isObjectDescendentOf(cat_id, model->findCategoryUUIDForType(LLFolderType::FT_INBOX))) + { + set_dad_inbox_object(cat_id); + } + + // Reparent the folder and restamp children if it's moving + // into trash. + gInventory.changeCategoryParent( + (LLViewerInventoryCategory*)inv_cat, + dest_id, + move_is_into_trash); + } + if (move_is_from_marketplacelistings) + { + //disable dropping in or out of marketplace for now + return FALSE; + + // If we are moving a folder at the listing folder level (i.e. its parent is the marketplace listings folder) + /*if (from_folder_uuid == marketplacelistings_id) + { + // Clear the folder from the marketplace in case it is a listing folder + if (LLMarketplaceData::instance().isListed(cat_id)) + { + LLMarketplaceData::instance().clearListing(cat_id); + } + } + else + { + // If we move from within an active (listed) listing, checks that it's still valid, if not, unlist + LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); + if (version_folder_id.notNull()) + { + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [version_folder_id](bool result) + { + if (!result) + { + LLMarketplaceData::instance().activateListing(version_folder_id, false); + } + } + ); + } + // In all cases, update the listing we moved from so suffix are updated + update_marketplace_category(from_folder_uuid); + }*/ + } + } + } + else if (LLToolDragAndDrop::SOURCE_WORLD == source) + { + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + else + { + accept = move_inv_category_world_to_agent(cat_id, dest_id, drop); + } + } + else if (LLToolDragAndDrop::SOURCE_LIBRARY == source) + { + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = FALSE; + } + else + { + // Accept folders that contain complete outfits. + accept = move_is_into_current_outfit && LLAppearanceMgr::instance().getCanMakeFolderIntoOutfit(cat_id); + } + + if (accept && drop) + { + LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, true, false); + } + } + + return accept; +} + +void outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id) +{ + LLInventoryModel::cat_array_t* categories; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_source_id, categories, items); + + LLInventoryObject::const_object_list_t link_array; + + + LLInventoryModel::item_array_t::iterator iter = items->begin(); + LLInventoryModel::item_array_t::iterator end = items->end(); + while (iter!=end) + { + const LLViewerInventoryItem* item = (*iter); + // By this point everything is supposed to be filtered, + // but there was a delay to create folder so something could have changed + LLInventoryType::EType inv_type = item->getInventoryType(); + if ((inv_type == LLInventoryType::IT_WEARABLE) || + (inv_type == LLInventoryType::IT_GESTURE) || + (inv_type == LLInventoryType::IT_ATTACHMENT) || + (inv_type == LLInventoryType::IT_OBJECT) || + (inv_type == LLInventoryType::IT_SNAPSHOT) || + (inv_type == LLInventoryType::IT_TEXTURE)) + { + link_array.push_back(LLConstPointer(item)); + } + iter++; + } + + if (!link_array.empty()) + { + LLPointer cb = NULL; + link_inventory_array(cat_dest_id, link_array, cb); + } +} + +void dropToMyOutfits(LLInventoryCategory* inv_cat) +{ + // make a folder in the My Outfits directory. + const LLUUID dest_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + // Note: creation will take time, so passing folder id to callback is slightly unreliable, + // but so is collecting and passing descendants' ids + inventory_func_type func = boost::bind(&outfitFolderCreatedCallback, inv_cat->getUUID(), _1); + gInventory.createNewCategory(dest_id, LLFolderType::FT_OUTFIT, inv_cat->getName(), func, inv_cat->getThumbnailUUID()); +} + diff --git a/indra/newview/llinventorygallery.h b/indra/newview/llinventorygallery.h new file mode 100644 index 0000000000..9b3f12701f --- /dev/null +++ b/indra/newview/llinventorygallery.h @@ -0,0 +1,419 @@ +/** + * @file llinventorygallery.h + * @brief LLInventoryGallery class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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_LLINVENTORYGALLERY_H +#define LL_LLINVENTORYGALLERY_H + +#include "llgesturemgr.h" +#include "lllistcontextmenu.h" +#include "llpanel.h" +#include "llinventoryfilter.h" +#include "llinventoryobserver.h" +#include "llinventorymodel.h" + +class LLInventoryCategoriesObserver; +class LLInventoryGalleryItem; +class LLScrollContainer; +class LLTextBox; +class LLThumbnailsObserver; +class LLGalleryGestureObserver; + +class LLInventoryGalleryContextMenu; + +typedef boost::function callback_t; + +class LLInventoryGallery : public LLPanel, public LLEditMenuHandler +{ +public: + + typedef boost::signals2::signal selection_change_signal_t; + typedef boost::function selection_change_callback_t; + typedef std::deque selection_deque; + + struct Params + : public LLInitParam::Block + { + Optional row_panel_height; + Optional row_panel_width_factor; + Optional gallery_width_factor; + Optional vertical_gap; + Optional horizontal_gap; + Optional item_width; + Optional item_height; + Optional item_horizontal_gap; + Optional items_in_row; + + Params(); + }; + + static const LLInventoryGallery::Params& getDefaultParams(); + + LLInventoryGallery(const LLInventoryGallery::Params& params = getDefaultParams()); + ~LLInventoryGallery(); + + BOOL postBuild() override; + void initGallery(); + void draw() override; + void onVisibilityChange(BOOL new_visibility) override; + BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, + void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) override; + void startDrag(); + BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) override; + BOOL handleKeyHere(KEY key, MASK mask) override; + void moveUp(MASK mask); + void moveDown(MASK mask); + void moveLeft(MASK mask); + void moveRight(MASK mask); + void toggleSelectionRange(S32 start_idx, S32 end_idx); + void toggleSelectionRangeFromLast(const LLUUID target); + + void onFocusLost() override; + void onFocusReceived() override; + + void setFilterSubString(const std::string& string); + std::string getFilterSubString() { return mFilterSubString; } + LLInventoryFilter& getFilter() const { return *mFilter; } + bool checkAgainstFilterType(const LLUUID& object_id); + + 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 updateItemThumbnail(LLUUID item_id); + void updateWornItem(LLUUID item_id, bool is_worn); + + void updateMessageVisibility(); + + void setRootFolder(const LLUUID cat_id); + void updateRootFolder(); + LLUUID getRootFolder() { return mFolderID; } + bool isRootDirty() { return mRootDirty; } + boost::signals2::connection setRootChangedCallback(callback_t cb); + void onForwardFolder(); + void onBackwardFolder(); + void clearNavigationHistory(); + bool isBackwardAvailable(); + bool isForwardAvailable(); + + void setNavBackwardList(std::list backward_list) { mBackwardFolders = backward_list; } + void setNavForwardList(std::list forward_list) { mForwardFolders = forward_list; } + std::list getNavBackwardList() { return mBackwardFolders; } + std::list getNavForwardList() { return mForwardFolders; } + + LLUUID getOutfitImageID(LLUUID outfit_id); + + void refreshList(const LLUUID& category_id); + void onCOFChanged(); + void onGesturesChanged(); + void computeDifference(const LLInventoryModel::cat_array_t vcats, const LLInventoryModel::item_array_t vitems, uuid_vec_t& vadded, uuid_vec_t& vremoved); + + void deselectItem(const LLUUID& category_id); + void clearSelection(); + void changeItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); + void addItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); + bool toggleItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); + void scrollToShowItem(const LLUUID& item_id); + void signalSelectionItemID(const LLUUID& category_id); + boost::signals2::connection setSelectionChangeCallback(selection_change_callback_t cb); + LLUUID getFirstSelectedItemID(); + + void setSearchType(LLInventoryFilter::ESearchType type); + LLInventoryFilter::ESearchType getSearchType() { return mSearchType; } + + bool areViewsInitialized(); + bool hasDescendents(const LLUUID& cat_id); + bool hasVisibleItems(); + void handleModifiedFilter(); + LLScrollContainer* getScrollableContainer() { return mScrollPanel; } + LLInventoryGalleryItem* getFirstSelectedItem(); + + // Copy & paste (LLEditMenuHandler) + void copy() override; + BOOL canCopy() const override; + + void cut() override; + BOOL canCut() const override; + + void paste() override; + BOOL canPaste() const override; + + // Copy & paste & delete + static void onDelete(const LLSD& notification, const LLSD& response, const selection_deque selected_ids); + void deleteSelection(); + bool canDeleteSelection(); + void pasteAsLink(); + + void setSortOrder(U32 order, bool update = false); + U32 getSortOrder() { return mSortOrder; }; + + void claimEditHandler(); + void resetEditHandler(); + static bool isItemCopyable(const LLUUID & item_id); + + BOOL baseHandleDragAndDrop(LLUUID dest_id, BOOL drop, EDragAndDropType cargo_type, + void* cargo_data, EAcceptance* accept, std::string& tooltip_msg); + + void showContextMenu(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& item_id); + +protected: + void paste(const LLUUID& dest, + std::vector& objects, + bool is_cut_mode, + const LLUUID& marketplacelistings_id); + void pasteAsLink(const LLUUID& dest, + std::vector& objects, + const LLUUID& current_outfit_id, + const LLUUID& marketplacelistings_id, + const LLUUID& my_outifts_id); + + bool applyFilter(LLInventoryGalleryItem* item, const std::string& filter_substring); + bool checkAgainstFilters(LLInventoryGalleryItem* item, const std::string& filter_substring); + static void onIdle(void* userdata); + void dirtyRootFolder(); + + LLInventoryCategoriesObserver* mCategoriesObserver; + LLThumbnailsObserver* mThumbnailsObserver; + LLGalleryGestureObserver* mGestureObserver; + LLInventoryObserver* mInventoryObserver; + selection_deque mSelectedItemIDs; + selection_deque mItemsToSelect; + LLUUID mLastInteractedUUID; + bool mIsInitialized; + bool mRootDirty; + + selection_change_signal_t mSelectionChangeSignal; + boost::signals2::signal mRootChangedSignal; + LLUUID mFolderID; + std::list mBackwardFolders; + std::list mForwardFolders; + +private: + void addToGallery(LLInventoryGalleryItem* item); + void removeFromGalleryLast(LLInventoryGalleryItem* item, bool needs_reshape = true); + void removeFromGalleryMiddle(LLInventoryGalleryItem* item); + LLPanel* addLastRow(); + void removeLastRow(); + void moveRowUp(int row); + void moveRowDown(int row); + void moveRow(int row, int pos); + LLPanel* addToRow(LLPanel* row_stack, LLInventoryGalleryItem* item, int pos, int hgap); + void removeFromLastRow(LLInventoryGalleryItem* item); + void reArrangeRows(S32 row_diff = 0); + 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); + + void buildGalleryPanel(int row_count); + void reshapeGalleryPanel(int row_count); + LLPanel* buildItemPanel(int left); + LLPanel* buildRowPanel(int left, int bottom); + void moveRowPanel(LLPanel* stack, int left, int bottom); + + std::vector mRowPanels; + std::vector mItemPanels; + std::vector mUnusedRowPanels; + std::vector mUnusedItemPanels; + std::vector mItems; + std::vector mHiddenItems; + LLScrollContainer* mScrollPanel; + LLPanel* mGalleryPanel; + LLPanel* mLastRowPanel; + LLTextBox* mMessageTextBox; + int mRowCount; + int mItemsAddedCount; + bool mGalleryCreated; + bool mNeedsArrange; + + /* Params */ + int mRowPanelHeight; + int mVerticalGap; + int mHorizontalGap; + int mItemWidth; + int mItemHeight; + int mItemHorizontalGap; + int mItemsInRow; + int mRowPanelWidth; + int mGalleryWidth; + int mRowPanWidthFactor; + int mGalleryWidthFactor; + + LLInventoryGalleryContextMenu* mInventoryGalleryMenu; + LLInventoryGalleryContextMenu* mRootGalleryMenu; + std::string mFilterSubString; + LLInventoryFilter* mFilter; + U32 mSortOrder; + + typedef std::map gallery_item_map_t; + gallery_item_map_t mItemMap; + uuid_vec_t mCOFLinkedItems; + uuid_vec_t mActiveGestures; + uuid_set_t mItemBuildQuery; + std::map mItemIndexMap; + std::map mIndexToItemMap; + + LLInventoryFilter::ESearchType mSearchType; + std::string mUsername; +}; + +class LLInventoryGalleryItem : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + {}; + + enum EInventorySortGroup + { + SG_SYSTEM_FOLDER, + SG_TRASH_FOLDER, + SG_NORMAL_FOLDER, + SG_ITEM + }; + + LLInventoryGalleryItem(const Params& p); + virtual ~LLInventoryGalleryItem(); + + BOOL postBuild(); + void draw(); + BOOL handleMouseDown(S32 x, S32 y, MASK mask); + BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); + BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + BOOL handleMouseUp(S32 x, S32 y, MASK mask); + BOOL handleHover(S32 x, S32 y, MASK mask); + BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + BOOL handleKeyHere(KEY key, MASK mask); + + void onFocusLost(); + void onFocusReceived(); + + LLFontGL* getTextFont(); + + void setItemName(std::string name); + bool isSelected() { return mSelected; } + void setSelected(bool value); + void setWorn(bool value); + void setUUID(LLUUID id) {mUUID = id;} + LLUUID getUUID() { return mUUID;} + + void setAssetIDStr(std::string asset_id) {mAssetIDStr = asset_id;} + std::string getAssetIDStr() { return mAssetIDStr;} + void setDescription(std::string desc) {mDesc = desc;} + std::string getDescription() { return mDesc;} + void setCreatorName(std::string name) {mCreatorName = name;} + std::string getCreatorName() { return mCreatorName;} + void setCreationDate(time_t date) {mCreationDate = date;} + time_t getCreationDate() { return mCreationDate;} + + std::string getItemName() {return mItemName;} + std::string getItemNameSuffix() {return mPermSuffix + mWornSuffix;} + bool isDefaultImage() {return mDefaultImage;} + + bool isHidden() {return mHidden;} + void setHidden(bool hidden) {mHidden = hidden;} + + void setType(LLAssetType::EType type, LLInventoryType::EType inventory_type, U32 flags, bool is_link); + LLAssetType::EType getAssetType() { return mType; } + void setThumbnail(LLUUID id); + void setGallery(LLInventoryGallery* gallery) { mGallery = gallery; } + bool isFolder() { return mIsFolder; } + bool isLink() { return mIsLink; } + EInventorySortGroup getSortGroup() { return mSortGroup; } + + void updateNameText(); + +private: + bool isFadeItem(); + + LLUUID mUUID; + LLTextBox* mNameText; + LLPanel* mTextBgPanel; + bool mSelected; + bool mWorn; + bool mDefaultImage; + bool mHidden; + bool mIsFolder; + bool mIsLink; + S32 mCutGeneration; + bool mSelectedForCut; + + std::string mAssetIDStr; + std::string mDesc; + std::string mCreatorName; + time_t mCreationDate; + + EInventorySortGroup mSortGroup; + LLAssetType::EType mType; + std::string mItemName; + std::string mWornSuffix; + std::string mPermSuffix; + LLInventoryGallery* mGallery; +}; + +class LLThumbnailsObserver : public LLInventoryObserver +{ +public: + LLThumbnailsObserver(){}; + + virtual void changed(U32 mask); + bool addItem(const LLUUID& obj_id, callback_t cb); + void removeItem(const LLUUID& obj_id); + +protected: + + struct LLItemData + { + LLItemData(const LLUUID& obj_id, const LLUUID& thumbnail_id, callback_t cb) + : mItemID(obj_id) + , mCallback(cb) + , mThumbnailID(thumbnail_id) + {} + + callback_t mCallback; + LLUUID mItemID; + LLUUID mThumbnailID; + }; + + typedef std::map item_map_t; + typedef item_map_t::value_type item_map_value_t; + item_map_t mItemMap; +}; + +class LLGalleryGestureObserver : public LLGestureManagerObserver +{ +public: + LLGalleryGestureObserver(LLInventoryGallery* gallery) : mGallery(gallery) {} + virtual ~LLGalleryGestureObserver() {} + virtual void changed() { mGallery->onGesturesChanged(); } + +private: + LLInventoryGallery* mGallery; +}; + +#endif diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp new file mode 100644 index 0000000000..0147642805 --- /dev/null +++ b/indra/newview/llinventorygallerymenu.cpp @@ -0,0 +1,729 @@ +/** + * @file llinventorygallerymenu.cpp + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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 "llinventorygallery.h" +#include "llinventorygallerymenu.h" + +#include "llagent.h" +#include "llappearancemgr.h" +#include "llavataractions.h" +#include "llclipboard.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloaterworldmap.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "lllandmarkactions.h" +#include "llmarketplacefunctions.h" +#include "llmenugl.h" +#include "llnotificationsutil.h" +#include "llpreviewtexture.h" +#include "lltrans.h" +#include "llviewerfoldertype.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" + +LLContextMenu* LLInventoryGalleryContextMenu::createMenu() +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add("Inventory.DoToSelected", boost::bind(&LLInventoryGalleryContextMenu::doToSelected, this, _2)); + registrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryGalleryContextMenu::fileUploadLocation, this, _2)); + registrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); + registrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); + + std::set uuids(mUUIDs.begin(), mUUIDs.end()); + registrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, uuids, gFloaterView->getParentFloater(mGallery))); + + enable_registrar.add("Inventory.CanSetUploadLocation", boost::bind(&LLInventoryGalleryContextMenu::canSetUploadLocation, this, _2)); + + LLContextMenu* menu = createFromFile("menu_gallery_inventory.xml"); + + updateMenuItemsVisibility(menu); + + return menu; +} + +void LLInventoryGalleryContextMenu::doToSelected(const LLSD& userdata) +{ + std::string action = userdata.asString(); + LLInventoryObject* obj = gInventory.getObject(mUUIDs.front()); + if(!obj) return; + + if ("open_selected_folder" == action) + { + mGallery->setRootFolder(mUUIDs.front()); + } + else if ("open_in_new_window" == action) + { + new_folder_window(mUUIDs.front()); + } + else if ("properties" == action) + { + show_item_profile(mUUIDs.front()); + } + else if ("restore" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(selected_id); + if (cat) + { + const LLUUID new_parent = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(cat->getType())); + // do not restamp children on restore + gInventory.changeCategoryParent(cat, new_parent, false); + } + else + { + LLViewerInventoryItem* item = gInventory.getItem(selected_id); + if (item) + { + bool is_snapshot = (item->getInventoryType() == LLInventoryType::IT_SNAPSHOT); + + const LLUUID new_parent = gInventory.findCategoryUUIDForType(is_snapshot ? LLFolderType::FT_SNAPSHOT_CATEGORY : LLFolderType::assetTypeToFolderType(item->getType())); + // do not restamp children on restore + gInventory.changeItemParent(item, new_parent, false); + } + } + } + } + else if ("copy_uuid" == action) + { + LLViewerInventoryItem* item = gInventory.getItem(mUUIDs.front()); + if(item) + { + LLUUID asset_id = item->getProtectedAssetUUID(); + std::string buffer; + asset_id.toString(buffer); + + gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(buffer)); + } + } + else if ("purge" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + remove_inventory_object(selected_id, NULL); + } + } + else if ("goto" == action) + { + show_item_original(mUUIDs.front()); + } + else if ("thumbnail" == action) + { + LLSD data(mUUIDs.front()); + LLFloaterReg::showInstance("change_item_thumbnail", data); + } + else if ("cut" == action) + { + if (mGallery->canCut()) + { + mGallery->cut(); + } + } + else if ("paste" == action) + { + if (mGallery->canPaste()) + { + mGallery->paste(); + } + } + else if ("delete" == action) + { + mGallery->deleteSelection(); + } + else if ("copy" == action) + { + if (mGallery->canCopy()) + { + mGallery->copy(); + } + } + else if ("paste_link" == action) + { + mGallery->pasteAsLink(); + } + else if ("rename" == action) + { + rename(mUUIDs.front()); + } + else if ("open" == action || "open_original" == action) + { + LLViewerInventoryItem* item = gInventory.getItem(mUUIDs.front()); + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(), mUUIDs.front(), &gInventory); + } + } + else if ("ungroup_folder_items" == action) + { + ungroup_folder_items(mUUIDs.front()); + } + else if ("take_off" == action || "detach" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLAppearanceMgr::instance().removeItemFromAvatar(selected_id); + } + } + else if ("wear_add" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLAppearanceMgr::instance().wearItemOnAvatar(selected_id, true, false); // Don't replace if adding. + } + } + else if ("wear" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLAppearanceMgr::instance().wearItemOnAvatar(selected_id, true, true); + } + } + else if ("activate" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLGestureMgr::instance().activateGesture(selected_id); + + LLViewerInventoryItem* item = gInventory.getItem(selected_id); + if (!item) return; + + gInventory.updateItem(item); + } + gInventory.notifyObservers(); + } + else if ("deactivate" == action) + { + for (LLUUID& selected_id : mUUIDs) + { + LLGestureMgr::instance().deactivateGesture(selected_id); + + LLViewerInventoryItem* item = gInventory.getItem(selected_id); + if (!item) return; + + gInventory.updateItem(item); + } + gInventory.notifyObservers(); + } + else if ("replace_links" == action) + { + LLFloaterReg::showInstance("linkreplace", LLSD(mUUIDs.front())); + } + else if ("copy_slurl" == action) + { + boost::function copy_slurl_cb = [](LLLandmark* landmark) + { + LLVector3d global_pos; + landmark->getGlobalPos(global_pos); + boost::function copy_slurl_to_clipboard_cb = [](const std::string& slurl) + { + gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); + LLSD args; + args["SLURL"] = slurl; + LLNotificationsUtil::add("CopySLURL", args); + }; +// GCC12 warning: maybe-uninitialized - probably bogus +#if defined(__GNUC__) && (__GNUC__ >= 12) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuninitialized" +#endif +// + LLLandmarkActions::getSLURLfromPosGlobal(global_pos, copy_slurl_to_clipboard_cb, true); +// GCC12 warning: maybe-uninitialized - probably bogus +#if defined(__GNUC__) && (__GNUC__ >= 12) +#pragma GCC diagnostic pop +#endif +// + + }; + LLLandmark* landmark = LLLandmarkActions::getLandmark(mUUIDs.front(), copy_slurl_cb); + if (landmark) + { + copy_slurl_cb(landmark); + } + } + else if ("about" == action) + { + LLSD key; + key["type"] = "landmark"; + key["id"] = mUUIDs.front(); + LLFloaterSidePanelContainer::showPanel("places", key); + } + else if ("show_on_map" == action) + { + boost::function show_on_map_cb = [](LLLandmark* landmark) + { + LLVector3d landmark_global_pos; + if (landmark->getGlobalPos(landmark_global_pos)) + { + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + if (!landmark_global_pos.isExactlyZero() && worldmap_instance) + { + worldmap_instance->trackLocation(landmark_global_pos); + LLFloaterReg::showInstance("world_map", "center"); + } + } + }; + LLLandmark* landmark = LLLandmarkActions::getLandmark(mUUIDs.front(), show_on_map_cb); + if(landmark) + { + show_on_map_cb(landmark); + } + } + else if ("save_as" == action) + { + LLPreviewTexture* preview_texture = LLFloaterReg::getTypedInstance("preview_texture", mUUIDs.front()); + if (preview_texture) + { + preview_texture->openToSave(); + preview_texture->saveAs(); + } + } +} + +void LLInventoryGalleryContextMenu::rename(const LLUUID& item_id) +{ + LLInventoryObject* obj = gInventory.getObject(item_id); + if (!obj) return; + + LLSD args; + args["NAME"] = obj->getName(); + + LLSD payload; + payload["id"] = mUUIDs.front(); + + LLNotificationsUtil::add("RenameItem", args, payload, boost::bind(onRename, _1, _2)); +} + +void LLInventoryGalleryContextMenu::onRename(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + std::string new_name = response["new_name"].asString(); + LLStringUtil::trim(new_name); + if (!new_name.empty()) + { + LLUUID id = notification["payload"]["id"].asUUID(); + + LLViewerInventoryCategory* cat = gInventory.getCategory(id); + if(cat && (cat->getName() != new_name)) + { + LLSD updates; + updates["name"] = new_name; + update_inventory_category(cat->getUUID(),updates, NULL); + return; + } + + LLViewerInventoryItem* item = gInventory.getItem(id); + if(item && (item->getName() != new_name)) + { + LLSD updates; + updates["name"] = new_name; + update_inventory_item(item->getUUID(),updates, NULL); + } + } +} + +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()); + } +} + +bool LLInventoryGalleryContextMenu::canSetUploadLocation(const LLSD& userdata) +{ + if (mUUIDs.size() != 1) + { + return false; + } + LLInventoryCategory* cat = gInventory.getCategory(mUUIDs.front()); + if (!cat) + { + return false; + } + return true; +} + +bool is_inbox_folder(LLUUID item_id) +{ + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); + + if (inbox_id.isNull()) + { + return false; + } + + return gInventory.isObjectDescendentOf(item_id, inbox_id); +} + +void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* menu) +{ + LLUUID selected_id = mUUIDs.front(); + LLInventoryObject* obj = gInventory.getObject(selected_id); + if (!obj) + { + return; + } + + std::vector items; + std::vector disabled_items; + + bool is_agent_inventory = gInventory.isObjectDescendentOf(selected_id, gInventory.getRootFolderID()); + bool is_link = obj->getIsLinkType(); + bool is_folder = (obj->getType() == LLAssetType::AT_CATEGORY); + bool is_cof = LLAppearanceMgr::instance().getIsInCOF(selected_id); + bool is_inbox = is_inbox_folder(selected_id); + bool is_trash = (selected_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH)); + bool is_in_trash = gInventory.isObjectDescendentOf(selected_id, gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH)); + bool is_lost_and_found = (selected_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); + bool is_outfits= (selected_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); + //bool is_favorites= (selected_id == gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE)); + + bool is_system_folder = false; + LLFolderType::EType folder_type(LLFolderType::FT_NONE); + bool has_children = false; + bool is_full_perm_item = false; + bool is_copyable = false; + LLViewerInventoryItem* selected_item = gInventory.getItem(selected_id); + + if(is_folder) + { + LLInventoryCategory* category = gInventory.getCategory(selected_id); + if (category) + { + folder_type = category->getPreferredType(); + is_system_folder = LLFolderType::lookupIsProtectedType(folder_type); + has_children = (gInventory.categoryHasChildren(selected_id) != LLInventoryModel::CHILDREN_NO); + } + } + else + { + if (selected_item) + { + is_full_perm_item = selected_item->getIsFullPerm(); + is_copyable = selected_item->getPermissions().allowCopyBy(gAgent.getID()); + } + } + + if(!is_link) + { + items.push_back(std::string("thumbnail")); + if (!is_agent_inventory || (is_in_trash && !is_trash)) + { + disabled_items.push_back(std::string("thumbnail")); + } + } + + if (is_folder) + { + if(!isRootFolder()) + { + items.push_back(std::string("Copy Separator")); + + items.push_back(std::string("open_in_current_window")); + items.push_back(std::string("open_in_new_window")); + items.push_back(std::string("Open Folder Separator")); + } + } + else + { + if (is_agent_inventory && (obj->getType() != LLAssetType::AT_LINK_FOLDER)) + { + items.push_back(std::string("Replace Links")); + } + if (obj->getType() == LLAssetType::AT_LANDMARK) + { + items.push_back(std::string("Landmark Separator")); + items.push_back(std::string("url_copy")); + items.push_back(std::string("About Landmark")); + items.push_back(std::string("show_on_map")); + } + } + + if(is_trash) + { + items.push_back(std::string("Empty Trash")); + + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(selected_id, cat_array, item_array); + if (0 == cat_array->size() && 0 == item_array->size()) + { + disabled_items.push_back(std::string("Empty Trash")); + } + } + else if(is_in_trash) + { + if (is_link) + { + items.push_back(std::string("Find Original")); + if (LLAssetType::lookupIsLinkType(obj->getType())) + { + disabled_items.push_back(std::string("Find Original")); + } + } + items.push_back(std::string("Purge Item")); + if (is_folder && !get_is_category_removable(&gInventory, selected_id)) + { + disabled_items.push_back(std::string("Purge Item")); + } + items.push_back(std::string("Restore Item")); + } + else + { + if(can_share_item(selected_id)) + { + items.push_back(std::string("Share")); + } + if (LLClipboard::instance().hasContents() && is_agent_inventory && !is_cof && !is_inbox_folder(selected_id)) + { + items.push_back(std::string("Paste")); + + static LLCachedControl inventory_linking(gSavedSettings, "InventoryLinking", true); + if (inventory_linking) + { + items.push_back(std::string("Paste As Link")); + } + } + if (is_folder && is_agent_inventory) + { + if (!is_cof && (folder_type != LLFolderType::FT_OUTFIT) && !is_outfits && !is_inbox_folder(selected_id)) + { + if (!gInventory.isObjectDescendentOf(selected_id, gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD)) && !isRootFolder()) + { + items.push_back(std::string("New Folder")); + } + items.push_back(std::string("upload_def")); + } + + if(is_outfits && !isRootFolder()) + { + items.push_back(std::string("New Outfit")); + } + + items.push_back(std::string("Subfolder Separator")); + if (!is_system_folder && !isRootFolder()) + { + if(has_children && (folder_type != LLFolderType::FT_OUTFIT)) + { + items.push_back(std::string("Ungroup folder items")); + } + items.push_back(std::string("Cut")); + items.push_back(std::string("Delete")); + if(!get_is_category_removable(&gInventory, selected_id)) + { + disabled_items.push_back(std::string("Delete")); + disabled_items.push_back(std::string("Cut")); + } + + if(!is_inbox) + { + items.push_back(std::string("Rename")); + } + } + if(!is_system_folder) + { + items.push_back(std::string("Copy")); + } + } + else if(!is_folder) + { + items.push_back(std::string("Properties")); + items.push_back(std::string("Copy Asset UUID")); + items.push_back(std::string("Copy Separator")); + + // Fix typo that causes gcc12 to error out + // bool is_asset_knowable = is_asset_knowable = LLAssetType::lookupIsAssetIDKnowable(obj->getType()); + bool is_asset_knowable = LLAssetType::lookupIsAssetIDKnowable(obj->getType()); + // + if ( !is_asset_knowable // disable menu item for Inventory items with unknown asset. EXT-5308 + || (! ( is_full_perm_item || gAgent.isGodlike()))) + { + disabled_items.push_back(std::string("Copy Asset UUID")); + } + if(is_agent_inventory) + { + items.push_back(std::string("Cut")); + if (!is_link || !is_cof || !get_is_item_worn(selected_id)) + { + items.push_back(std::string("Delete")); + } + if(!get_is_item_removable(&gInventory, selected_id)) + { + disabled_items.push_back(std::string("Delete")); + disabled_items.push_back(std::string("Cut")); + } + + if (selected_item && (selected_item->getInventoryType() != LLInventoryType::IT_CALLINGCARD) && !is_inbox && selected_item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())) + { + items.push_back(std::string("Rename")); + } + } + items.push_back(std::string("Copy")); + if (!is_copyable) + { + disabled_items.push_back(std::string("Copy")); + } + } + if((obj->getType() == LLAssetType::AT_SETTINGS) + || ((obj->getType() <= LLAssetType::AT_GESTURE) + && obj->getType() != LLAssetType::AT_OBJECT + && obj->getType() != LLAssetType::AT_CLOTHING + && obj->getType() != LLAssetType::AT_CATEGORY + && obj->getType() != LLAssetType::AT_LANDMARK + && obj->getType() != LLAssetType::AT_BODYPART)) + { + bool can_open = !LLAssetType::lookupIsLinkType(obj->getType()); + + if (can_open) + { + if (is_link) + items.push_back(std::string("Open Original")); + else + items.push_back(std::string("Open")); + } + else + { + disabled_items.push_back(std::string("Open")); + disabled_items.push_back(std::string("Open Original")); + } + + if(LLAssetType::AT_GESTURE == obj->getType()) + { + items.push_back(std::string("Gesture Separator")); + if(!LLGestureMgr::instance().isGestureActive(selected_id)) + { + items.push_back(std::string("Activate")); + } + else + { + items.push_back(std::string("Deactivate")); + } + } + } + else if(LLAssetType::AT_LANDMARK == obj->getType()) + { + items.push_back(std::string("Landmark Open")); + } + else if (obj->getType() == LLAssetType::AT_OBJECT || obj->getType() == LLAssetType::AT_CLOTHING || obj->getType() == LLAssetType::AT_BODYPART) + { + items.push_back(std::string("Wearable And Object Separator")); + if(obj->getType() == LLAssetType::AT_CLOTHING) + { + items.push_back(std::string("Take Off")); + } + if(get_is_item_worn(selected_id)) + { + if(obj->getType() == LLAssetType::AT_OBJECT) + { + items.push_back(std::string("Detach From Yourself")); + } + disabled_items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Wearable Add")); + } + else + { + if(obj->getType() == LLAssetType::AT_OBJECT) + { + items.push_back(std::string("Wearable Add")); + } + items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Take Off")); + } + + if (!gAgentAvatarp->canAttachMoreObjects() && (obj->getType() == LLAssetType::AT_OBJECT)) + { + disabled_items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Wearable Add")); + } + if (selected_item && (obj->getType() != LLAssetType::AT_OBJECT) && LLWearableType::getInstance()->getAllowMultiwear(selected_item->getWearableType())) + { + items.push_back(std::string("Wearable Add")); + if (!gAgentWearables.canAddWearable(selected_item->getWearableType())) + { + disabled_items.push_back(std::string("Wearable Add")); + } + } + } + if(obj->getType() == LLAssetType::AT_TEXTURE) + { + items.push_back(std::string("Save As")); + bool can_copy = selected_item && selected_item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); + if (!can_copy) + { + disabled_items.push_back(std::string("Save As")); + } + } + if (is_link) + { + items.push_back(std::string("Find Original")); + if (LLAssetType::lookupIsLinkType(obj->getType())) + { + disabled_items.push_back(std::string("Find Original")); + } + } + if (is_lost_and_found) + { + items.push_back(std::string("Empty Lost And Found")); + + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(selected_id, cat_array, item_array); + // Enable Empty menu item only when there is something to act upon. + if (0 == cat_array->size() && 0 == item_array->size()) + { + disabled_items.push_back(std::string("Empty Lost And Found")); + } + + disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("upload_def")); + } + } + + hide_context_entries(*menu, items, disabled_items); +} + diff --git a/indra/newview/llinventorygallerymenu.h b/indra/newview/llinventorygallerymenu.h new file mode 100644 index 0000000000..7c3545432b --- /dev/null +++ b/indra/newview/llinventorygallerymenu.h @@ -0,0 +1,62 @@ +/** + * @file llinventorygallerymenu.h + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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_LLINVENTORYGALLERYMENU_H +#define LL_LLINVENTORYGALLERYMENU_H + +#include "lllistcontextmenu.h" + +class LLInventoryGalleryContextMenu : public LLListContextMenu +{ +public: + LLInventoryGalleryContextMenu(LLInventoryGallery* gallery) + : LLListContextMenu(), + mGallery(gallery), + mRootFolder(false){} + /*virtual*/ LLContextMenu* createMenu(); + + bool isRootFolder() { return mRootFolder; } + void setRootFolder(bool is_root) { mRootFolder = is_root; } + void doToSelected(const LLSD& userdata); + void rename(const LLUUID& item_id); + +protected: + //virtual void buildContextMenu(class LLMenuGL& menu, U32 flags); + void updateMenuItemsVisibility(LLContextMenu* menu); + + void fileUploadLocation(const LLSD& userdata); + bool canSetUploadLocation(const LLSD& userdata); + + static void onRename(const LLSD& notification, const LLSD& response); + +private: + bool enableContextMenuItem(const LLSD& userdata); + bool checkContextMenuItem(const LLSD& userdata); + + LLInventoryGallery* mGallery; + bool mRootFolder; +}; + +#endif diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 999e95ca2b..0f1f207b87 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -37,9 +37,11 @@ #include "llappearancemgr.h" #include "llavatarnamecache.h" #include "llclipboard.h" +#include "lldispatcher.h" #include "llinventorypanel.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" +#include "llinventorymodelbackgroundfetch.h" #include "llinventoryobserver.h" #include "llinventorypanel.h" #include "llfloaterpreviewtrash.h" @@ -49,6 +51,7 @@ #include "llviewercontrol.h" #include "llviewernetwork.h" #include "llpreview.h" +#include "llviewergenericmessage.h" #include "llviewermessage.h" #include "llviewerfoldertype.h" #include "llviewerwindow.h" @@ -90,9 +93,11 @@ // 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 = 2; +const S32 LLInventoryModel::sCurrentInvCacheVersion = 3; BOOL LLInventoryModel::sFirstTimeInViewer2 = TRUE; +S32 LLInventoryModel::sPendingSystemFolders = 0; + ///---------------------------------------------------------------------------- /// Local function declarations, constants, enums, and typedefs ///---------------------------------------------------------------------------- @@ -149,6 +154,222 @@ bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item) return rv; } +struct InventoryCallbackInfo +{ + InventoryCallbackInfo(U32 callback, const LLUUID& inv_id) : + mCallback(callback), mInvID(inv_id) {} + U32 mCallback; + LLUUID mInvID; +}; + +///---------------------------------------------------------------------------- +/// Class LLDispatchClassifiedClickThrough +///---------------------------------------------------------------------------- + +class LLDispatchBulkUpdateInventory : public LLDispatchHandler +{ +public: + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) + { + LLSD message; + + // Expect single string parameter in the form of a notation serialized LLSD. + sparam_t::const_iterator it = strings.begin(); + if (it != strings.end()) { + const std::string& llsdRaw = *it++; + std::istringstream llsdData(llsdRaw); + if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length())) + { + LL_WARNS() << "LLDispatchBulkUpdateInventory: Attempted to read parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; + } + } + + LLInventoryModel::update_map_t update; + LLInventoryModel::cat_array_t folders; + LLInventoryModel::item_array_t items; + std::list cblist; + uuid_vec_t wearable_ids; + + LLSD item_data = message["item_data"]; + if (item_data.isArray()) + { + for (LLSD::array_iterator itd = item_data.beginArray(); itd != item_data.endArray(); ++itd) + { + const LLSD &item(*itd); + + // Agent id probably should be in the root of the message + LLUUID agent_id = item["agent_id"].asUUID(); + if (agent_id != gAgent.getID()) + { + LL_WARNS() << "Got a BulkUpdateInventory for the wrong agent." << LL_ENDL; + return false; + } + + LLPointer titem = new LLViewerInventoryItem; + titem->unpackMessage(item); + LL_DEBUGS("Inventory") << "unpacked item '" << titem->getName() << "' in " + << titem->getParentUUID() << LL_ENDL; + // callback id might be no longer supported + U32 callback_id = item["callback_id"].asInteger(); + + if (titem->getUUID().notNull()) + { + items.push_back(titem); + cblist.push_back(InventoryCallbackInfo(callback_id, titem->getUUID())); + if (titem->getInventoryType() == LLInventoryType::IT_WEARABLE) + { + wearable_ids.push_back(titem->getUUID()); + } + + // examine update for changes. + LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID()); + if (itemp) + { + if (titem->getParentUUID() == itemp->getParentUUID()) + { + update[titem->getParentUUID()]; + } + else + { + ++update[titem->getParentUUID()]; + --update[itemp->getParentUUID()]; + } + } + else + { + LLViewerInventoryCategory* folderp = gInventory.getCategory(titem->getParentUUID()); + if (folderp) + { + ++update[titem->getParentUUID()]; + } + } + } + else + { + cblist.push_back(InventoryCallbackInfo(callback_id, LLUUID::null)); + } + } + } + + LLSD folder_data = message["folder_data"]; + if (folder_data.isArray()) + { + for (LLSD::array_iterator itd = folder_data.beginArray(); itd != folder_data.endArray(); ++itd) + { + const LLSD &folder(*itd); + + LLPointer tfolder = new LLViewerInventoryCategory(gAgent.getID()); + tfolder->unpackMessage(folder); + + LL_DEBUGS("Inventory") << "unpacked folder '" << tfolder->getName() << "' (" + << tfolder->getUUID() << ") in " << tfolder->getParentUUID() + << LL_ENDL; + + // If the folder is a listing or a version folder, all we need to do is update the SLM data + int depth_folder = depth_nesting_in_marketplace(tfolder->getUUID()); + if ((depth_folder == 1) || (depth_folder == 2)) + { + // Trigger an SLM listing update + LLUUID listing_uuid = (depth_folder == 1 ? tfolder->getUUID() : tfolder->getParentUUID()); + S32 listing_id = LLMarketplaceData::instance().getListingID(listing_uuid); + LLMarketplaceData::instance().getListing(listing_id); + // In that case, there is no item to update so no callback -> we skip the rest of the update + } + else if (tfolder->getUUID().notNull()) + { + folders.push_back(tfolder); + LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); + if (folderp) + { + if (tfolder->getParentUUID() == folderp->getParentUUID()) + { + update[tfolder->getParentUUID()]; + } + else + { + ++update[tfolder->getParentUUID()]; + --update[folderp->getParentUUID()]; + } + } + else + { + // we could not find the folder, so it is probably + // new. However, we only want to attempt accounting + // for the parent if we can find the parent. + folderp = gInventory.getCategory(tfolder->getParentUUID()); + if (folderp) + { + ++update[tfolder->getParentUUID()]; + } + } + } + } + } + + gInventory.accountForUpdate(update); + + for (LLInventoryModel::cat_array_t::iterator cit = folders.begin(); cit != folders.end(); ++cit) + { + gInventory.updateCategory(*cit); + } + for (LLInventoryModel::item_array_t::iterator iit = items.begin(); iit != items.end(); ++iit) + { + gInventory.updateItem(*iit); + } + gInventory.notifyObservers(); + + /* + Transaction id not included? + + // The incoming inventory could span more than one BulkInventoryUpdate packet, + // so record the transaction ID for this purchase, then wear all clothing + // that comes in as part of that transaction ID. JC + if (LLInventoryState::sWearNewClothing) + { + LLInventoryState::sWearNewClothingTransactionID = tid; + LLInventoryState::sWearNewClothing = FALSE; + } + + if (tid.notNull() && tid == LLInventoryState::sWearNewClothingTransactionID) + { + count = wearable_ids.size(); + for (i = 0; i < count; ++i) + { + LLViewerInventoryItem* wearable_item; + wearable_item = gInventory.getItem(wearable_ids[i]); + LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); + } + } + */ + + if (LLInventoryState::sWearNewClothing && wearable_ids.size() > 0) + { + LLInventoryState::sWearNewClothing = FALSE; + + size_t count = wearable_ids.size(); + for (S32 i = 0; i < count; ++i) + { + LLViewerInventoryItem* wearable_item; + wearable_item = gInventory.getItem(wearable_ids[i]); + LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); + } + } + + std::list::iterator inv_it; + for (inv_it = cblist.begin(); inv_it != cblist.end(); ++inv_it) + { + InventoryCallbackInfo cbinfo = (*inv_it); + gInventoryCallbacks.fire(cbinfo.mCallback, cbinfo.mInvID); + } + return true; + } +}; +static LLDispatchBulkUpdateInventory sBulkUpdateInventory; + ///---------------------------------------------------------------------------- /// Class LLInventoryValidationInfo ///---------------------------------------------------------------------------- @@ -238,6 +459,7 @@ LLInventoryModel::LLInventoryModel() mIsNotifyObservers(FALSE), mModifyMask(LLInventoryObserver::ALL), mChangedItemIDs(), + mBulkFecthCallbackSlot(), mObservers(), mProtectedCategoriesChangedCallbackConnection(), // FIRE-29342: Protect folder option mHttpRequestFG(NULL), @@ -277,6 +499,11 @@ void LLInventoryModel::cleanupInventory() mObservers.erase(iter); delete observer; } + + if (mBulkFecthCallbackSlot.connected()) + { + mBulkFecthCallbackSlot.disconnect(); + } mObservers.clear(); // Run down HTTP transport @@ -519,6 +746,31 @@ void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id, items = get_ptr_in_map(mParentChildItemTree, cat_id); } +void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t& categories, item_array_t& items, LLInventoryCollectFunctor& f) const +{ + if (cat_array_t* categoriesp = get_ptr_in_map(mParentChildCategoryTree, cat_id)) + { + for (LLViewerInventoryCategory* pFolder : *categoriesp) + { + if (f(pFolder, nullptr)) + { + categories.push_back(pFolder); + } + } + } + + if (item_array_t* itemsp = get_ptr_in_map(mParentChildItemTree, cat_id)) + { + for (LLViewerInventoryItem* pItem : *itemsp) + { + if (f(nullptr, pItem)) + { + items.push_back(pItem); + } + } + } +} + LLInventoryModel::digest_t LLInventoryModel::hashDirectDescendentNames(const LLUUID& cat_id) const { LLInventoryModel::cat_array_t* cat_array; @@ -629,10 +881,77 @@ void LLInventoryModel::consolidateForType(const LLUUID& main_id, LLFolderType::E } } +void LLInventoryModel::ensureCategoryForTypeExists(LLFolderType::EType preferred_type) +{ + LLUUID rv = LLUUID::null; + LLUUID root_id = gInventory.getRootFolderID(); + if (LLFolderType::FT_ROOT_INVENTORY == preferred_type) + { + rv = root_id; + } + else if (root_id.notNull()) + { + cat_array_t* cats = NULL; + cats = get_ptr_in_map(mParentChildCategoryTree, root_id); + if (cats) + { + S32 count = cats->size(); + for (S32 i = 0; i < count; ++i) + { + LLViewerInventoryCategory* p_cat = cats->at(i); + if (p_cat && p_cat->getPreferredType() == preferred_type) + { + const LLUUID& folder_id = cats->at(i)->getUUID(); + if (rv.isNull() || folder_id < rv) + { + rv = folder_id; + } + } + } + } + } + + if (rv.isNull() && root_id.notNull()) + { + + if (isInventoryUsable()) + { + createNewCategory( + root_id, + preferred_type, + LLStringUtil::null, + [preferred_type](const LLUUID &new_cat_id) + { + if (new_cat_id.isNull()) + { + LL_WARNS("Inventory") + << "Failed to create folder of type " << preferred_type + << LL_ENDL; + } + else + { + LL_WARNS("Inventory") << "Created category: " << new_cat_id + << " for type: " << preferred_type << LL_ENDL; + sPendingSystemFolders--; + } + } + ); + } + else + { + LL_WARNS("Inventory") << "Can't create requested folder, type " << preferred_type + << " because inventory is not usable" << LL_ENDL; + } + } + else + { + sPendingSystemFolders--; + } +} + const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( LLFolderType::EType preferred_type, - bool create_folder, - const LLUUID& root_id) + const LLUUID& root_id) const { LLUUID rv = LLUUID::null; if(LLFolderType::FT_ROOT_INVENTORY == preferred_type) @@ -663,20 +982,15 @@ const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( if(rv.isNull() && root_id.notNull() - && create_folder && preferred_type != LLFolderType::FT_MARKETPLACE_LISTINGS && preferred_type != LLFolderType::FT_OUTBOX) { - - if (isInventoryUsable()) - { - return createNewCategory(root_id, preferred_type, LLStringUtil::null); - } - else - { - LL_WARNS("Inventory") << "Can't create requested folder, type " << preferred_type - << " because inventory is not usable" << LL_ENDL; - } + // if it does not exists, it should either be added + // to createCommonSystemCategories or server should + // have set it + llassert(!isInventoryUsable()); + LL_WARNS("Inventory") << "Tried to find folder, type " << preferred_type + << " but category does not exist" << LL_ENDL; } return rv; } @@ -685,12 +999,12 @@ const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( // specifies 'type' as what it defaults to containing. The category is // not necessarily only for that type. *NOTE: This will create a new // inventory category on the fly if one does not exist. -const LLUUID LLInventoryModel::findCategoryUUIDForType(LLFolderType::EType preferred_type, bool create_folder) +const LLUUID LLInventoryModel::findCategoryUUIDForType(LLFolderType::EType preferred_type) const { - return findCategoryUUIDForTypeInRoot(preferred_type, create_folder, gInventory.getRootFolderID()); + return findCategoryUUIDForTypeInRoot(preferred_type, gInventory.getRootFolderID()); } -const LLUUID LLInventoryModel::findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) +const LLUUID LLInventoryModel::findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) const { LLUUID cat_id; switch (preferred_type) @@ -721,14 +1035,14 @@ const LLUUID LLInventoryModel::findUserDefinedCategoryUUIDForType(LLFolderType:: if (cat_id.isNull() || !getCategory(cat_id)) { - cat_id = findCategoryUUIDForTypeInRoot(preferred_type, true, getRootFolderID()); + cat_id = findCategoryUUIDForTypeInRoot(preferred_type, getRootFolderID()); } return cat_id; } -const LLUUID LLInventoryModel::findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type, bool create_folder) +const LLUUID LLInventoryModel::findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type) const { - return findCategoryUUIDForTypeInRoot(preferred_type, create_folder, gInventory.getLibraryRootFolderID()); + return findCategoryUUIDForTypeInRoot(preferred_type, gInventory.getLibraryRootFolderID()); } LLUUID LLInventoryModel::findCategoryByName(std::string name) @@ -756,27 +1070,33 @@ LLUUID LLInventoryModel::findCategoryByName(std::string name) // Convenience function to create a new category. You could call // updateCategory() with a newly generated UUID category, but this // version will take care of details like what the name should be -// based on preferred type. Returns the UUID of the new category. -LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, +// based on preferred type. +void LLInventoryModel::createNewCategory(const LLUUID& parent_id, LLFolderType::EType preferred_type, const std::string& pname, - inventory_func_type callback) + inventory_func_type callback, + const LLUUID& thumbnail_id) { LL_DEBUGS(LOG_INV) << "Create '" << pname << "' in '" << make_inventory_path(parent_id) << "'" << LL_ENDL; - LLUUID id; if (!isInventoryUsable()) { LL_WARNS(LOG_INV) << "Inventory is not usable; can't create requested category of type " << preferred_type << LL_ENDL; - // FIXME failing but still returning an id? - return id; + if (callback) + { + callback(LLUUID::null); + } + return; } if(LLFolderType::lookup(preferred_type) == LLFolderType::badLookup()) { LL_DEBUGS(LOG_INV) << "Attempt to create undefined category." << LL_ENDL; - // FIXME failing but still returning an id? - return id; + if (callback) + { + callback(LLUUID::null); + } + return; } if (preferred_type != LLFolderType::FT_NONE) @@ -787,26 +1107,72 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, LL_WARNS(LOG_INV) << "Creating new system folder, type " << preferred_type << LL_ENDL; } - id.generate(); std::string name = pname; - if(!pname.empty()) - { - name.assign(pname); - } - else + if (pname.empty()) { name.assign(LLViewerFolderType::lookupNewCategoryName(preferred_type)); } - + + if (AISAPI::isAvailable()) + { + LLSD new_inventory = LLSD::emptyMap(); + new_inventory["categories"] = LLSD::emptyArray(); + LLViewerInventoryCategory cat(LLUUID::null, parent_id, preferred_type, name, gAgent.getID()); + cat.setThumbnailUUID(thumbnail_id); + LLSD cat_sd = cat.asAISCreateCatLLSD(); + new_inventory["categories"].append(cat_sd); + AISAPI::CreateInventory( + parent_id, + new_inventory, + [this, callback, parent_id, preferred_type, name] (const LLUUID& new_category) + { + if (new_category.isNull()) + { + if (callback && !callback.empty()) + { + callback(new_category); + } + return; + } + + // todo: not needed since AIS does the accounting? + LLViewerInventoryCategory* folderp = gInventory.getCategory(new_category); + if (!folderp) + { + // Add the category to the internal representation + LLPointer cat = new LLViewerInventoryCategory( + new_category, + parent_id, + preferred_type, + name, + gAgent.getID()); + + LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); + accountForUpdate(update); + + cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 + cat->setDescendentCount(0); + updateCategory(cat); + } + + if (callback && !callback.empty()) + { + callback(new_category); + } + }); + return; + } + LLViewerRegion* viewer_region = gAgent.getRegion(); std::string url; if ( viewer_region ) url = viewer_region->getCapability("CreateInventoryCategory"); - if (!url.empty() && callback) + if (!url.empty()) { //Let's use the new capability. - + LLUUID id; + id.generate(); LLSD request, body; body["folder_id"] = id; body["parent_id"] = parent_id; @@ -819,44 +1185,13 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, LL_DEBUGS(LOG_INV) << "Creating category via request: " << ll_pretty_print_sd(request) << LL_ENDL; LLCoros::instance().launch("LLInventoryModel::createNewCategoryCoro", boost::bind(&LLInventoryModel::createNewCategoryCoro, this, url, body, callback)); - - return LLUUID::null; + return; } - if (!gMessageSystem) - { - return LLUUID::null; - } - - // FIXME this UDP code path needs to be removed. Requires - // reworking many of the callers to use callbacks rather than - // assuming instant success. - - // Add the category to the internal representation - LLPointer cat = - new LLViewerInventoryCategory(id, parent_id, preferred_type, name, gAgent.getID()); - cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 - cat->setDescendentCount(0); - LLCategoryUpdate update(cat->getParentUUID(), 1); - accountForUpdate(update); - updateCategory(cat); - - LL_DEBUGS(LOG_INV) << "Creating category via UDP message CreateInventoryFolder, type " << preferred_type << LL_ENDL; - - // Create the category on the server. We do this to prevent people - // from munging their protected folders. - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("CreateInventoryFolder"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlock("FolderData"); - cat->packMessage(msg); - gAgent.sendReliableMessage(); - - LL_INFOS(LOG_INV) << "Created new category '" << make_inventory_path(id) << "'" << LL_ENDL; - // return the folder id of the newly created folder - return id; + if (callback) + { + callback(LLUUID::null); // Notify about failure + } } void LLInventoryModel::createNewCategoryCoro(std::string url, LLSD postData, inventory_func_type callback) @@ -880,12 +1215,20 @@ void LLInventoryModel::createNewCategoryCoro(std::string url, LLSD postData, inv if (!status) { LL_WARNS() << "HTTP failure attempting to create category." << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } if (!result.has("folder_id")) { LL_WARNS() << "Malformed response contents" << ll_pretty_print_sd(result) << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } return; } @@ -1451,7 +1794,7 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32 mask |= LLInventoryObserver::LABEL; } // Under marketplace, category labels are quite complex and need extra upate - const LLUUID marketplace_id = findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplace_id = findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (marketplace_id.notNull() && isObjectDescendentOf(cat->getUUID(), marketplace_id)) { mask |= LLInventoryObserver::LABEL; @@ -1544,7 +1887,6 @@ void LLInventoryModel::changeItemParent(LLViewerInventoryItem* item, << " from " << make_inventory_info(item->getParentUUID()) << " to " << make_inventory_info(new_parent_id) << LL_ENDL; - // Locked folder if ((isObjectDescendentOf(item->getUUID(), AOEngine::instance().getAOFolder()) && gSavedPerAccountSettings.getBOOL("LockAOFolders")) || @@ -1620,17 +1962,25 @@ void LLInventoryModel::changeCategoryParent(LLViewerInventoryCategory* cat, notifyObservers(); } -void LLInventoryModel::onAISUpdateReceived(const std::string& context, const LLSD& update) +void LLInventoryModel::rebuildBrockenLinks() { - LLTimer timer; - if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) - { - dump_sequential_xml(gAgentAvatarp->getFullname() + "_ais_update", update); - } + // make sure we aren't adding expensive Rebuild to anything else. + notifyObservers(); - AISUpdate ais_update(update); // parse update llsd into stuff to do. - ais_update.doUpdate(); // execute the updates in the appropriate order. - LL_INFOS(LOG_INV) << "elapsed: " << timer.getElapsedTimeF32() << LL_ENDL; + for (const broken_links_t::value_type &link_list : mPossiblyBrockenLinks) + { + for (const LLUUID& link_id : link_list.second) + { + addChangedMask(LLInventoryObserver::REBUILD , link_id); + } + } + for (const LLUUID& link_id : mLinksRebuildList) + { + addChangedMask(LLInventoryObserver::REBUILD , link_id); + } + mPossiblyBrockenLinks.clear(); + mLinksRebuildList.clear(); + notifyObservers(); } // Does not appear to be used currently. @@ -1936,6 +2286,20 @@ void LLInventoryModel::idleNotifyObservers() { // *FIX: Think I want this conditional or moved elsewhere... handleResponses(true); + + if (mLinksRebuildList.size() > 0) + { + if (mModifyMask != LLInventoryObserver::NONE || (mChangedItemIDs.size() != 0)) + { + notifyObservers(); + } + for (const LLUUID& link_id : mLinksRebuildList) + { + addChangedMask(LLInventoryObserver::REBUILD , link_id); + } + mLinksRebuildList.clear(); + notifyObservers(); + } if (mModifyMask == LLInventoryObserver::NONE && (mChangedItemIDs.size() == 0)) { @@ -2270,10 +2634,52 @@ void LLInventoryModel::addItem(LLViewerInventoryItem* item) // The item will show up as a broken link. if (item->getIsBrokenLink()) { - LL_INFOS(LOG_INV) << "Adding broken link [ name: " << item->getName() - << " itemID: " << item->getUUID() - << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; + if (item->getAssetUUID().notNull() + && LLInventoryModelBackgroundFetch::getInstance()->folderFetchActive()) + { + // Schedule this link for a recheck as inventory gets loaded + // Todo: expand to cover not just an initial fetch + mPossiblyBrockenLinks[item->getAssetUUID()].insert(item->getUUID()); + + // Do a blank rebuild of links once fetch is done + if (!mBulkFecthCallbackSlot.connected()) + { + // Links might take a while to update this way, and there + // might be a lot of them. A better option might be to check + // links periodically with final check on fetch completion. + mBulkFecthCallbackSlot = + LLInventoryModelBackgroundFetch::getInstance()->setFetchCompletionCallback( + [this]() + { + // rebuild is just in case, primary purpose is to wipe + // the list since we won't be getting anything 'new' + // see mLinksRebuildList + rebuildBrockenLinks(); + mBulkFecthCallbackSlot.disconnect(); + }); + } + LL_DEBUGS(LOG_INV) << "Scheduling a link to be rebuilt later [ name: " << item->getName() + << " itemID: " << item->getUUID() + << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; + + } + else + { + LL_INFOS(LOG_INV) << "Adding broken link [ name: " << item->getName() + << " itemID: " << item->getUUID() + << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; + } } + if (!mPossiblyBrockenLinks.empty()) + { + // check if we are waiting for this item + broken_links_t::iterator iter = mPossiblyBrockenLinks.find(item->getUUID()); + if (iter != mPossiblyBrockenLinks.end()) + { + mLinksRebuildList.insert(iter->second.begin() , iter->second.end()); + mPossiblyBrockenLinks.erase(iter); + } + } if (item->getIsLinkType()) { // Add back-link from linked-to UUID. @@ -2588,6 +2994,10 @@ bool LLInventoryModel::loadSkeleton( else { cached_ids.insert(tcat->getUUID()); + + // At the moment download does not provide a thumbnail + // uuid, use the one from cache + tcat->setThumbnailUUID(cat->getThumbnailUUID()); } } @@ -2881,7 +3291,7 @@ void LLInventoryModel::buildParentChildMap() } } - const BOOL COF_exists = (findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, FALSE) != LLUUID::null); + const BOOL COF_exists = (findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT) != LLUUID::null); sFirstTimeInViewer2 = !COF_exists || gAgent.isFirstLogin(); @@ -3080,6 +3490,11 @@ void LLInventoryModel::initHttpRequest() mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_LLSD_XML); mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_INVENTORY); } + + if (!gGenericDispatcher.isHandlerPresent("BulkUpdateInventory")) + { + gGenericDispatcher.addHandler("BulkUpdateInventory", &sBulkUpdateInventory); + } } void LLInventoryModel::handleResponses(bool foreground) @@ -3132,14 +3547,17 @@ LLCore::HttpHandle LLInventoryModel::requestPost(bool foreground, void LLInventoryModel::createCommonSystemCategories() { - gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH,true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE,true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD,true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS,true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK, true); // folder should exist before user tries to 'landmark this' - gInventory.findCategoryUUIDForType(LLFolderType::FT_SETTINGS, true); - gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX, true); + //amount of System Folder we should wait for + sPendingSystemFolders = 8; + + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_TRASH); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_FAVORITE); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CALLINGCARD); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MY_OUTFITS); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CURRENT_OUTFIT); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_LANDMARK); // folder should exist before user tries to 'landmark this' + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_SETTINGS); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_INBOX); } struct LLUUIDAndName @@ -3364,9 +3782,6 @@ void LLInventoryModel::registerCallbacks(LLMessageSystem* msg) msg->setHandlerFuncFast(_PREHASH_RemoveInventoryItem, processRemoveInventoryItem, NULL); - msg->setHandlerFuncFast(_PREHASH_UpdateInventoryFolder, - processUpdateInventoryFolder, - NULL); msg->setHandlerFuncFast(_PREHASH_RemoveInventoryFolder, processRemoveInventoryFolder, NULL); @@ -3400,6 +3815,10 @@ void LLInventoryModel::processUpdateCreateInventoryItem(LLMessageSystem* msg, vo msg->getU32Fast(_PREHASH_InventoryData, _PREHASH_CallbackID, callback_id); gInventoryCallbacks.fire(callback_id, item_id); + + // todo: instead of unpacking message fully, + // grab only an item_id, then fetch + LLInventoryModelBackgroundFetch::instance().scheduleItemFetch(item_id, true); } } @@ -3514,66 +3933,6 @@ void LLInventoryModel::processRemoveInventoryItem(LLMessageSystem* msg, void**) gInventory.notifyObservers(); } -// static -void LLInventoryModel::processUpdateInventoryFolder(LLMessageSystem* msg, - void**) -{ - LL_DEBUGS(LOG_INV) << "LLInventoryModel::processUpdateInventoryFolder()" << LL_ENDL; - LLUUID agent_id, folder_id, parent_id; - //char name[DB_INV_ITEM_NAME_BUF_SIZE]; - msg->getUUIDFast(_PREHASH_FolderData, _PREHASH_AgentID, agent_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS(LOG_INV) << "Got an UpdateInventoryFolder for the wrong agent." - << LL_ENDL; - return; - } - LLPointer lastfolder; // hack - cat_array_t folders; - update_map_t update; - S32 count = msg->getNumberOfBlocksFast(_PREHASH_FolderData); - for(S32 i = 0; i < count; ++i) - { - LLPointer tfolder = new LLViewerInventoryCategory(gAgent.getID()); - lastfolder = tfolder; - tfolder->unpackMessage(msg, _PREHASH_FolderData, i); - // make sure it's not a protected folder - tfolder->setPreferredType(LLFolderType::FT_NONE); - folders.push_back(tfolder); - // examine update for changes. - LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); - if(folderp) - { - if(tfolder->getParentUUID() == folderp->getParentUUID()) - { - update[tfolder->getParentUUID()]; - } - else - { - ++update[tfolder->getParentUUID()]; - --update[folderp->getParentUUID()]; - } - } - else - { - ++update[tfolder->getParentUUID()]; - } - } - gInventory.accountForUpdate(update); - for (cat_array_t::iterator it = folders.begin(); it != folders.end(); ++it) - { - gInventory.updateCategory(*it); - } - gInventory.notifyObservers(); - - // *HACK: Do the 'show' logic for a new item in the inventory. - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); - if (active_panel) - { - active_panel->setSelection(lastfolder->getUUID(), TAKE_FOCUS_NO); - } -} - // static void LLInventoryModel::removeInventoryFolder(LLUUID agent_id, LLMessageSystem* msg) @@ -3684,14 +4043,6 @@ void LLInventoryModel::processSaveAssetIntoInventory(LLMessageSystem* msg, } } -struct InventoryCallbackInfo -{ - InventoryCallbackInfo(U32 callback, const LLUUID& inv_id) : - mCallback(callback), mInvID(inv_id) {} - U32 mCallback; - LLUUID mInvID; -}; - // static void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) { @@ -3737,29 +4088,36 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); if(folderp) { - if(tfolder->getParentUUID() == folderp->getParentUUID()) - { + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + if (tfolder->getParentUUID() == folderp->getParentUUID()) + { // [RLVa:KB] - Checked: 2010-04-18 (RLVa-1.2.0e) | Added: RLVa-1.2.0e - // NOTE-RLVa: not sure if this is a hack or a bug-fix :o - // -> if we rename the folder on the first BulkUpdateInventory message subsequent messages will still contain - // the old folder name and gInventory.updateCategory() below will "undo" the folder name change but on the - // viewer-side *only* so the folder name actually becomes out of sync with what's on the inventory server - // -> so instead we keep the name of the existing folder and only do it for #RLV/~ in case this causes issues - // -> a better solution would be to only do the rename *after* the transaction completes but there doesn't seem - // to be any way to accomplish that either *sighs* - if ( (rlv_handler_t::isEnabled()) && (!folderp->getName().empty()) && (tfolder->getName() != folderp->getName()) && - ((tfolder->getName().find(RLV_PUTINV_PREFIX) == 0)) ) - { - tfolder->rename(folderp->getName()); - } + // NOTE-RLVa: not sure if this is a hack or a bug-fix :o + // -> if we rename the folder on the first BulkUpdateInventory message subsequent messages will still contain + // the old folder name and gInventory.updateCategory() below will "undo" the folder name change but on the + // viewer-side *only* so the folder name actually becomes out of sync with what's on the inventory server + // -> so instead we keep the name of the existing folder and only do it for #RLV/~ in case this causes issues + // -> a better solution would be to only do the rename *after* the transaction completes but there doesn't seem + // to be any way to accomplish that either *sighs* + if ( (rlv_handler_t::isEnabled()) && (!folderp->getName().empty()) && (tfolder->getName() != folderp->getName()) && + ((tfolder->getName().find(RLV_PUTINV_PREFIX) == 0)) ) + { + tfolder->rename(folderp->getName()); + } // [/RLVa:KB] - update[tfolder->getParentUUID()]; - } - else - { - ++update[tfolder->getParentUUID()]; - --update[folderp->getParentUUID()]; - } + update[tfolder->getParentUUID()]; + } + else + { + ++update[tfolder->getParentUUID()]; + --update[folderp->getParentUUID()]; + } + } + else + { + folderp->fetch(); + } } else { @@ -3769,7 +4127,14 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) folderp = gInventory.getCategory(tfolder->getParentUUID()); if(folderp) { - ++update[tfolder->getParentUUID()]; + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + ++update[tfolder->getParentUUID()]; + } + else + { + folderp->fetch(); + } } } } @@ -3815,7 +4180,14 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) LLViewerInventoryCategory* folderp = gInventory.getCategory(titem->getParentUUID()); if(folderp) { - ++update[titem->getParentUUID()]; + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + ++update[titem->getParentUUID()]; + } + else + { + folderp->fetch(); + } } } } @@ -3829,10 +4201,20 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) for (cat_array_t::iterator cit = folders.begin(); cit != folders.end(); ++cit) { gInventory.updateCategory(*cit); + + // Temporary workaround: just fetch the item using AIS to get missing fields. + // If this works fine we might want to extract ids only from the message + // then use AIS as a primary fetcher + LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch((*cit)->getUUID(), true /*force, since it has changes*/); } for (item_array_t::iterator iit = items.begin(); iit != items.end(); ++iit) { gInventory.updateItem(*iit); + + // Temporary workaround: just fetch the item using AIS to get missing fields. + // If this works fine we might want to extract ids only from the message + // then use AIS as a primary fetcher + LLInventoryModelBackgroundFetch::instance().scheduleItemFetch((*iit)->getUUID(), true); } // [SL:KB] - Patch: UI-Notifications | Checked: Catznip-6.5 gInventory.notifyObservers(tid); @@ -4922,7 +5304,6 @@ LLPointer LLInventoryModel::validate() const } else if (count_under_root > 1) { - // LL_WARNS("Inventory") << "Fatal inventory corruption: system folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; // FIRE-31634 [OPENSIM] Better inventory validation logging validation_info->mDuplicateRequiredSystemFolders.insert(folder_type); if (!is_automatic && folder_type != LLFolderType::FT_SETTINGS) { @@ -4938,6 +5319,8 @@ LLPointer LLInventoryModel::validate() const // outfits, trash and other non-automatic folders. validation_info->mFatalSystemDuplicate++; fatal_errs++; + // FIRE-31634 [OPENSIM] Better inventory validation logging + //LL_WARNS("Inventory") << "Fatal inventory corruption: system folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; } else { @@ -4947,6 +5330,8 @@ LLPointer LLInventoryModel::validate() const // Exception: FT_SETTINGS is not automatic, but only deserves a warning. validation_info->mWarnings["non_fatal_system_duplicate_under_root"]++; warning_count++; + // FIRE-31634 [OPENSIM] Better inventory validation logging + //LL_WARNS("Inventory") << "System folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; } } if (count_elsewhere > 0) diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index b826ae22e4..7f4b01be24 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -237,10 +237,14 @@ private: //-------------------------------------------------------------------- public: static BOOL getIsFirstTimeInViewer2(); + static bool isSysFoldersReady() { return (sPendingSystemFolders == 0); } + private: static BOOL sFirstTimeInViewer2; const static S32 sCurrentInvCacheVersion; // expected inventory cache version + static S32 sPendingSystemFolders; + /** Initialization/Setup ** ** *******************************************************************************/ @@ -266,6 +270,7 @@ public: void getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t*& categories, item_array_t*& items) const; + void getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t& categories, item_array_t& items, LLInventoryCollectFunctor& f) const; typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" // Compute a hash of direct descendant names (for detecting child name changes) @@ -332,24 +337,25 @@ public: // Find //-------------------------------------------------------------------- public: + + // Checks if category exists ("My Inventory" only), if it does not, creates it + void ensureCategoryForTypeExists(LLFolderType::EType preferred_type); + const LLUUID findCategoryUUIDForTypeInRoot( LLFolderType::EType preferred_type, - bool create_folder, - const LLUUID& root_id); + const LLUUID& root_id) const; // Returns the uuid of the category that specifies 'type' as what it // defaults to containing. The category is not necessarily only for that type. // NOTE: If create_folder is true, this will create a new inventory category // on the fly if one does not exist. *NOTE: if find_in_library is true it // will search in the user's library folder instead of "My Inventory" - const LLUUID findCategoryUUIDForType(LLFolderType::EType preferred_type, - bool create_folder = true); + const LLUUID findCategoryUUIDForType(LLFolderType::EType preferred_type) const; // will search in the user's library folder instead of "My Inventory" - const LLUUID findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type, - bool create_folder = true); + const LLUUID findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type) const; // Returns user specified category for uploads, returns default id if there are no // user specified one or it does not exist, creates default category if it is missing. - const LLUUID findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type); + const LLUUID findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) const; // Get whatever special folder this object is a child of, if any. const LLViewerInventoryCategory *getFirstNondefaultParent(const LLUUID& obj_id) const; @@ -455,13 +461,15 @@ public: const LLUUID& new_parent_id, BOOL restamp); + // Marks links from a "possibly" broken list for a rebuild + // clears the list + void rebuildBrockenLinks(); + bool hasPosiblyBrockenLinks() const { return mPossiblyBrockenLinks.size() > 0; } + //-------------------------------------------------------------------- // Delete //-------------------------------------------------------------------- public: - - // Update model after an AISv3 update received for any operation. - void onAISUpdateReceived(const std::string& context, const LLSD& update); // Update model after an item is confirmed as removed from // server. Works for categories or items. @@ -526,10 +534,11 @@ public: LLUUID findCategoryByName(std::string name); // Returns the UUID of the new category. If you want to use the default // name based on type, pass in a NULL to the 'name' parameter. - LLUUID createNewCategory(const LLUUID& parent_id, + void createNewCategory(const LLUUID& parent_id, LLFolderType::EType preferred_type, const std::string& name, - inventory_func_type callback = NULL); + inventory_func_type callback = NULL, + const LLUUID& thumbnail_id = LLUUID::null); protected: // Internal methods that add inventory and make sure that all of // the internal data structures are consistent. These methods @@ -632,6 +641,10 @@ private: U32 mModifyMaskBacklog; changed_items_t mChangedItemIDsBacklog; changed_items_t mAddedItemIDsBacklog; + typedef std::map broken_links_t; + broken_links_t mPossiblyBrockenLinks; // there can be multiple links per item + changed_items_t mLinksRebuildList; + boost::signals2::connection mBulkFecthCallbackSlot; // [SL:KB] - Patch: UI-Notifications | Checked: Catznip-6.5 LLUUID mTransactionId; @@ -724,7 +737,6 @@ public: static void processUpdateCreateInventoryItem(LLMessageSystem* msg, void**); static void removeInventoryItem(LLUUID agent_id, LLMessageSystem* msg, const char* msg_label); static void processRemoveInventoryItem(LLMessageSystem* msg, void**); - static void processUpdateInventoryFolder(LLMessageSystem* msg, void**); static void removeInventoryFolder(LLUUID agent_id, LLMessageSystem* msg); static void processRemoveInventoryFolder(LLMessageSystem* msg, void**); static void processRemoveInventoryObjects(LLMessageSystem* msg, void**); diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp index 5624737804..a27da8e041 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.cpp +++ b/indra/newview/llinventorymodelbackgroundfetch.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include "llinventorymodelbackgroundfetch.h" +#include "llaisapi.h" #include "llagent.h" #include "llappviewer.h" #include "llcallbacklist.h" @@ -190,12 +191,14 @@ const char * const LOG_INV("Inventory"); ///---------------------------------------------------------------------------- LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch(): - mBackgroundFetchActive(FALSE), + mBackgroundFetchActive(false), mFolderFetchActive(false), mFetchCount(0), - mAllFoldersFetched(FALSE), - mRecursiveInventoryFetchStarted(FALSE), - mRecursiveLibraryFetchStarted(FALSE), + mLastFetchCount(0), + mFetchFolderCount(0), + mAllRecursiveFoldersFetched(false), + mRecursiveInventoryFetchStarted(false), + mRecursiveLibraryFetchStarted(false), mMinTimeBetweenFetches(0.3f) {} @@ -204,7 +207,12 @@ LLInventoryModelBackgroundFetch::~LLInventoryModelBackgroundFetch() bool LLInventoryModelBackgroundFetch::isBulkFetchProcessingComplete() const { - return mFetchQueue.empty() && mFetchCount <= 0; + return mFetchFolderQueue.empty() && mFetchItemQueue.empty() && mFetchCount <= 0; +} + +bool LLInventoryModelBackgroundFetch::isFolderFetchProcessingComplete() const +{ + return mFetchFolderQueue.empty() && mFetchFolderCount <= 0; } bool LLInventoryModelBackgroundFetch::libraryFetchStarted() const @@ -239,7 +247,7 @@ bool LLInventoryModelBackgroundFetch::inventoryFetchInProgress() const bool LLInventoryModelBackgroundFetch::isEverythingFetched() const { - return mAllFoldersFetched; + return mAllRecursiveFoldersFetched; } BOOL LLInventoryModelBackgroundFetch::folderFetchActive() const @@ -247,17 +255,33 @@ BOOL LLInventoryModelBackgroundFetch::folderFetchActive() const return mFolderFetchActive; } -void LLInventoryModelBackgroundFetch::addRequestAtFront(const LLUUID & id, BOOL recursive, bool is_category) +void LLInventoryModelBackgroundFetch::addRequestAtFront(const LLUUID & id, bool recursive, bool is_category) { - mFetchQueue.push_front(FetchQueueInfo(id, recursive, is_category)); + EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; + if (is_category) + { + mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type, is_category)); + } + else + { + mFetchItemQueue.push_front(FetchQueueInfo(id, recursion_type, is_category)); + } } -void LLInventoryModelBackgroundFetch::addRequestAtBack(const LLUUID & id, BOOL recursive, bool is_category) +void LLInventoryModelBackgroundFetch::addRequestAtBack(const LLUUID & id, bool recursive, bool is_category) { - mFetchQueue.push_back(FetchQueueInfo(id, recursive, is_category)); + EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; + if (is_category) + { + mFetchFolderQueue.push_back(FetchQueueInfo(id, recursion_type, is_category)); + } + else + { + mFetchItemQueue.push_back(FetchQueueInfo(id, recursion_type, is_category)); + } } -void LLInventoryModelBackgroundFetch::start(const LLUUID& id, BOOL recursive) +void LLInventoryModelBackgroundFetch::start(const LLUUID& id, bool recursive) { LLViewerInventoryCategory * cat(gInventory.getCategory(id)); @@ -266,31 +290,53 @@ void LLInventoryModelBackgroundFetch::start(const LLUUID& id, BOOL recursive) // it's a folder, do a bulk fetch LL_DEBUGS(LOG_INV) << "Start fetching category: " << id << ", recursive: " << recursive << LL_ENDL; - mBackgroundFetchActive = TRUE; + mBackgroundFetchActive = true; mFolderFetchActive = true; + EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; if (id.isNull()) { if (! mRecursiveInventoryFetchStarted) { mRecursiveInventoryFetchStarted |= recursive; - mFetchQueue.push_back(FetchQueueInfo(gInventory.getRootFolderID(), recursive)); + if (recursive && AISAPI::isAvailable()) + { + // Not only root folder can be massive, but + // most system folders will be requested independently + // so request root folder and content separately + mFetchFolderQueue.push_front(FetchQueueInfo(gInventory.getRootFolderID(), FT_FOLDER_AND_CONTENT)); + } + else + { + mFetchFolderQueue.push_back(FetchQueueInfo(gInventory.getRootFolderID(), recursion_type)); + } gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); } if (! mRecursiveLibraryFetchStarted) { mRecursiveLibraryFetchStarted |= recursive; - mFetchQueue.push_back(FetchQueueInfo(gInventory.getLibraryRootFolderID(), recursive)); + mFetchFolderQueue.push_back(FetchQueueInfo(gInventory.getLibraryRootFolderID(), recursion_type)); gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); } } else { - // Specific folder requests go to front of queue. - if (mFetchQueue.empty() || mFetchQueue.front().mUUID != id) - { - mFetchQueue.push_front(FetchQueueInfo(id, recursive)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } + if (AISAPI::isAvailable()) + { + if (mFetchFolderQueue.empty() || mFetchFolderQueue.back().mUUID != id) + { + // On AIS make sure root goes to the top and follow up recursive + // fetches, not individual requests + mFetchFolderQueue.push_back(FetchQueueInfo(id, recursion_type)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + } + else if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != id) + { + // Specific folder requests go to front of queue. + mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + if (id == gInventory.getLibraryRootFolderID()) { mRecursiveLibraryFetchStarted |= recursive; @@ -303,21 +349,41 @@ void LLInventoryModelBackgroundFetch::start(const LLUUID& id, BOOL recursive) } else if (LLViewerInventoryItem * itemp = gInventory.getItem(id)) { - if (! itemp->mIsComplete && (mFetchQueue.empty() || mFetchQueue.front().mUUID != id)) + if (! itemp->mIsComplete) { - mBackgroundFetchActive = TRUE; - - mFetchQueue.push_front(FetchQueueInfo(id, false, false)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + scheduleItemFetch(id); } } } +void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id, bool forced) +{ + if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id) + { + mBackgroundFetchActive = true; + + // Specific folder requests go to front of queue. + mFetchFolderQueue.push_front(FetchQueueInfo(cat_id, forced ? FT_FORCED : FT_DEFAULT)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} + +void LLInventoryModelBackgroundFetch::scheduleItemFetch(const LLUUID& item_id, bool forced) +{ + if (mFetchItemQueue.empty() || mFetchItemQueue.front().mUUID != item_id) + { + mBackgroundFetchActive = true; + + mFetchItemQueue.push_front(FetchQueueInfo(item_id, forced ? FT_FORCED : FT_DEFAULT, false)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} + void LLInventoryModelBackgroundFetch::findLostItems() { - mBackgroundFetchActive = TRUE; - mFolderFetchActive = true; - mFetchQueue.push_back(FetchQueueInfo(LLUUID::null, TRUE)); + mBackgroundFetchActive = true; + mFolderFetchActive = true; + mFetchFolderQueue.push_back(FetchQueueInfo(LLUUID::null, FT_RECURSIVE)); gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); } @@ -326,15 +392,28 @@ void LLInventoryModelBackgroundFetch::setAllFoldersFetched() if (mRecursiveInventoryFetchStarted && mRecursiveLibraryFetchStarted) { - mAllFoldersFetched = TRUE; + mAllRecursiveFoldersFetched = true; //LL_INFOS(LOG_INV) << "All folders fetched, validating" << LL_ENDL; //gInventory.validate(); } + mFolderFetchActive = false; - mBackgroundFetchActive = false; + if (isBulkFetchProcessingComplete()) + { + mBackgroundFetchActive = false; + } + + // For now only informs about initial fetch being done + mFoldersFetchedSignal(); + LL_INFOS(LOG_INV) << "Inventory background fetch completed" << LL_ENDL; } +boost::signals2::connection LLInventoryModelBackgroundFetch::setFetchCompletionCallback(folders_fetched_callback_t cb) +{ + return mFoldersFetchedSignal.connect(cb); +} + void LLInventoryModelBackgroundFetch::backgroundFetchCB(void *) { LLInventoryModelBackgroundFetch::instance().backgroundFetch(); @@ -342,7 +421,7 @@ void LLInventoryModelBackgroundFetch::backgroundFetchCB(void *) void LLInventoryModelBackgroundFetch::backgroundFetch() { - if (mBackgroundFetchActive && gAgent.getRegion() && gAgent.getRegion()->capabilitiesReceived()) + if (mBackgroundFetchActive) { // If we'll be using the capability, we'll be sending batches and the background thing isn't as important. // Force HTTP features on SL @@ -350,17 +429,25 @@ void LLInventoryModelBackgroundFetch::backgroundFetch() if (use_http_inventory()) // { - bulkFetch(); + if (AISAPI::isAvailable()) + { + bulkFetchViaAis(); + } + else if (gAgent.getRegion() && gAgent.getRegion()->capabilitiesReceived()) + { + // If we'll be using the capability, we'll be sending batches and the background thing isn't as important. + bulkFetch(); + } return; } -#if 1 +#ifdef OPENSIM // Only OpenSim still might support old UDP fetch //-------------------------------------------------------------------------------- // DEPRECATED OLD CODE // // No more categories to fetch, stop fetch process. - if (mFetchQueue.empty()) + if (mFetchFolderQueue.empty()) { setAllFoldersFetched(); return; @@ -380,7 +467,7 @@ void LLInventoryModelBackgroundFetch::backgroundFetch() while(1) { - if (mFetchQueue.empty()) + if (mFetchFolderQueue.empty()) { break; } @@ -391,7 +478,7 @@ void LLInventoryModelBackgroundFetch::backgroundFetch() break; } - const FetchQueueInfo info = mFetchQueue.front(); + const FetchQueueInfo info = mFetchFolderQueue.front(); if (info.mIsCategory) { @@ -401,7 +488,7 @@ void LLInventoryModelBackgroundFetch::backgroundFetch() // Category has been deleted, remove from queue. if (!cat) { - mFetchQueue.pop_front(); + mFetchFolderQueue.pop_front(); continue; } @@ -427,7 +514,7 @@ void LLInventoryModelBackgroundFetch::backgroundFetch() else if (gInventory.isCategoryComplete(info.mUUID)) { // Finished with this category, remove from queue. - mFetchQueue.pop_front(); + mFetchFolderQueue.pop_front(); // Add all children to queue. LLInventoryModel::cat_array_t* categories; @@ -437,7 +524,7 @@ void LLInventoryModelBackgroundFetch::backgroundFetch() it != categories->end(); ++it) { - mFetchQueue.push_back(FetchQueueInfo((*it)->getUUID(),info.mRecursive)); + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(),info.mFetchType)); } // We received a response in less than the fast time. @@ -456,12 +543,12 @@ void LLInventoryModelBackgroundFetch::backgroundFetch() { // Received first packet, but our num descendants does not match db's num descendants // so try again later. - mFetchQueue.pop_front(); + mFetchFolderQueue.pop_front(); if (mNumFetchRetries++ < MAX_FETCH_RETRIES) { // push on back of queue - mFetchQueue.push_back(info); + mFetchFolderQueue.push_back(info); } mTimelyFetchPending = FALSE; mFetchTimer.reset(); @@ -475,7 +562,7 @@ void LLInventoryModelBackgroundFetch::backgroundFetch() { LLViewerInventoryItem* itemp = gInventory.getItem(info.mUUID); - mFetchQueue.pop_front(); + mFetchFolderQueue.pop_front(); if (!itemp) { continue; @@ -493,7 +580,7 @@ void LLInventoryModelBackgroundFetch::backgroundFetch() } else if (mFetchTimer.getElapsedTimeF32() > mMaxTimeBetweenFetches) { - mFetchQueue.push_back(info); + mFetchFolderQueue.push_back(info); mFetchTimer.reset(); mTimelyFetchPending = FALSE; } @@ -518,9 +605,476 @@ void LLInventoryModelBackgroundFetch::incrFetchCount(S32 fetching) mFetchCount = 0; } } +void LLInventoryModelBackgroundFetch::incrFetchFolderCount(S32 fetching) +{ + incrFetchCount(fetching); + mFetchFolderCount += fetching; + if (mFetchCount < 0) + { + LL_WARNS_ONCE(LOG_INV) << "Inventory fetch count fell below zero (0)." << LL_ENDL; + mFetchFolderCount = 0; + } +} + +void ais_simple_item_callback(const LLUUID& inv_id) +{ + LL_DEBUGS(LOG_INV , "AIS3") << "Response for " << inv_id << LL_ENDL; + LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); +} + +void LLInventoryModelBackgroundFetch::onAISContentCalback( + const LLUUID& request_id, + const uuid_vec_t& content_ids, + const LLUUID& response_id, + EFetchType fetch_type) +{ + // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis + incrFetchFolderCount(-1); + + uuid_vec_t::const_iterator folder_iter = content_ids.begin(); + uuid_vec_t::const_iterator folder_end = content_ids.end(); + while (folder_iter != folder_end) + { + std::list::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), *folder_iter); + if (found != mExpectedFolderIds.end()) + { + mExpectedFolderIds.erase(found); + } + + LLViewerInventoryCategory* cat(gInventory.getCategory(*folder_iter)); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } + if (response_id.isNull()) + { + // Failed to fetch, get it individually + mFetchFolderQueue.push_back(FetchQueueInfo(*folder_iter, FT_RECURSIVE)); + } + else + { + // push descendant back to verify they are fetched fully (ex: didn't encounter depth limit) + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(*folder_iter, categories, items); + if (categories) + { + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + + folder_iter++; + } + + if (!mFetchFolderQueue.empty()) + { + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} +void LLInventoryModelBackgroundFetch::onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type) +{ + // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis + incrFetchFolderCount(-1); + std::list::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), request_id); + if (found != mExpectedFolderIds.end()) + { + mExpectedFolderIds.erase(found); + } + else + { + // ais shouldn't respond twice + llassert(false); + LL_WARNS() << "Unexpected folder response for " << request_id << LL_ENDL; + } + + if (request_id.isNull()) + { + // orhans, no other actions needed + return; + } + + bool request_descendants = false; + if (response_id.isNull()) // Failure + { + LL_DEBUGS(LOG_INV , "AIS3") << "Failure response for folder " << request_id << LL_ENDL; + if (fetch_type == FT_RECURSIVE) + { + // A full recursive request failed. + // Try requesting folder and nested content separately + mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_FOLDER_AND_CONTENT)); + } + else if (fetch_type == FT_FOLDER_AND_CONTENT) + { + LL_WARNS() << "Failed to download folder: " << request_id << " Requesting known content separately" << LL_ENDL; + mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); + + // set folder's version to prevent viewer from trying to request folder indefinetely + LLViewerInventoryCategory* cat(gInventory.getCategory(request_id)); + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + cat->setVersion(0); + } + } + } + else + { + if (fetch_type == FT_RECURSIVE) + { + // Got the folder and content, now verify content + // Request content even for FT_RECURSIVE in case of changes, failures + // or if depth limit gets imlemented. + // This shouldn't redownload folders if they already have version + request_descendants = true; + LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << ". Requesting content" << LL_ENDL; + } + else if (fetch_type == FT_FOLDER_AND_CONTENT) + { + // readd folder for content request + mFetchFolderQueue.push_front(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); + } + else + { + LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << "." << LL_ENDL; + } + + } + + if (request_descendants) + { + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(request_id, categories, items); + if (categories) + { + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + + if (!mFetchFolderQueue.empty()) + { + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + + // done + LLViewerInventoryCategory * cat(gInventory.getCategory(request_id)); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } +} static LLTrace::BlockTimerStatHandle FTM_BULK_FETCH("Bulk Fetch"); +void LLInventoryModelBackgroundFetch::bulkFetchViaAis() +{ + LL_RECORD_BLOCK_TIME(FTM_BULK_FETCH); + //Background fetch is called from gIdleCallbacks in a loop until background fetch is stopped. + if (gDisconnected) + { + return; + } + + static LLCachedControl ais_pool(gSavedSettings, "PoolSizeAIS", 20); + // Don't have too many requests at once, AIS throttles + // Reserve one request for actions outside of fetch (like renames) + const U32 max_concurrent_fetches = llclamp(ais_pool - 1, 1, 50); + + if (mFetchCount >= max_concurrent_fetches) + { + return; + } + + // Don't loop for too long (in case of large, fully loaded inventory) + F64 curent_time = LLTimer::getTotalSeconds(); + const F64 max_time = LLStartUp::getStartupState() > STATE_WEARABLES_WAIT + ? 0.006f // 6 ms + : 1.f; + const F64 end_time = curent_time + max_time; + S32 last_fetch_count = mFetchCount; + + while (!mFetchFolderQueue.empty() && mFetchCount < max_concurrent_fetches && curent_time < end_time) + { + const FetchQueueInfo & fetch_info(mFetchFolderQueue.front()); + bulkFetchViaAis(fetch_info); + mFetchFolderQueue.pop_front(); + curent_time = LLTimer::getTotalSeconds(); + } + + // Ideally we shouldn't fetch items if recursive fetch isn't done, + // but there is a chance some request will start timeouting and recursive + // fetch will get stuck on a signle folder, don't block item fetch in such case + while (!mFetchItemQueue.empty() && mFetchCount < max_concurrent_fetches && curent_time < end_time) + { + const FetchQueueInfo& fetch_info(mFetchItemQueue.front()); + bulkFetchViaAis(fetch_info); + mFetchItemQueue.pop_front(); + curent_time = LLTimer::getTotalSeconds(); + } + + if (last_fetch_count != mFetchCount // if anything was added + || mLastFetchCount != mFetchCount) // if anything was substracted + { + LL_DEBUGS(LOG_INV , "AIS3") << "Total active fetches: " << mLastFetchCount << "->" << last_fetch_count << "->" << mFetchCount + << ", scheduled folder fetches: " << (S32)mFetchFolderQueue.size() + << ", scheduled item fetches: " << (S32)mFetchItemQueue.size() + << LL_ENDL; + mLastFetchCount = mFetchCount; + + if (!mExpectedFolderIds.empty()) + { + // A folder seem to be stack fetching on QA account, print oldest folder out + LL_DEBUGS(LOG_INV , "AIS3") << "Oldest expected folder: "; + std::list::const_iterator iter = mExpectedFolderIds.begin(); + LL_CONT << *iter; + if ((*iter).notNull()) + { + LLViewerInventoryCategory* cat(gInventory.getCategory(*iter)); + if (cat) + { + LL_CONT << " Folder name: " << cat->getName() << " Parent: " << cat->getParentUUID(); + } + else + { + LL_CONT << " This folder doesn't exist"; + } + } + else + { + LL_CONT << " Orphans request"; + } + LL_CONT << LL_ENDL; + } + } + + if (isFolderFetchProcessingComplete() && mFolderFetchActive) + { + setAllFoldersFetched(); + } + + if (isBulkFetchProcessingComplete()) + { + mBackgroundFetchActive = false; + } +} + +void LLInventoryModelBackgroundFetch::bulkFetchViaAis(const FetchQueueInfo& fetch_info) +{ + if (fetch_info.mIsCategory) + { + const LLUUID & cat_id(fetch_info.mUUID); + if (cat_id.isNull()) + { + incrFetchFolderCount(1); + mExpectedFolderIds.push_back(cat_id); + // Lost and found + // Should it actually be recursive? + AISAPI::FetchOrphans([](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISFolderCalback(LLUUID::null, + response_id, + FT_DEFAULT); + }); + } + else + { + LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); + if (cat) + { + if (fetch_info.mFetchType == FT_CONTENT_RECURSIVE) + { + // fetch content only, ignore cat itself + uuid_vec_t children; + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + + LLViewerInventoryCategory::EFetchType target_state = LLViewerInventoryCategory::FETCH_RECURSIVE; + bool content_done = true; + + // Top limit is 'as many as you can put into url' + static LLCachedControl ais_batch(gSavedSettings, "BatchSizeAIS3", 20); + S32 batch_limit = llclamp(ais_batch(), 1, 40); + + for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); + it != categories->end(); + ++it) + { + LLViewerInventoryCategory* child_cat = (*it); + if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion() + || child_cat->getFetching() >= target_state) + { + continue; + } + + if (child_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_LISTINGS) + { + // special case + content_done = false; + if (children.empty()) + { + // fetch marketplace alone + // Should it actually be fetched as FT_FOLDER_AND_CONTENT? + children.push_back(child_cat->getUUID()); + mExpectedFolderIds.push_back(child_cat->getUUID()); + child_cat->setFetching(target_state); + break; + } + else + { + // fetch marketplace alone next run + continue; + } + } + + children.push_back(child_cat->getUUID()); + mExpectedFolderIds.push_back(child_cat->getUUID()); + child_cat->setFetching(target_state); + + if (children.size() >= batch_limit) + { + content_done = false; + break; + } + } + + if (!children.empty()) + { + // increment before call in case of immediate callback + incrFetchFolderCount(1); + + EFetchType type = fetch_info.mFetchType; + LLUUID cat_id = cat->getUUID(); // need a copy for lambda + AISAPI::completion_t cb = [cat_id, children, type](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISContentCalback(cat_id, children, response_id, type); + }; + + AISAPI::ITEM_TYPE item_type = AISAPI::INVENTORY; + if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + { + item_type = AISAPI::LIBRARY; + } + + AISAPI::FetchCategorySubset(cat_id, children, item_type, true, cb, 0); + } + + if (content_done) + { + // This will have a bit of overlap with onAISContentCalback, + // but something else might have dowloaded folders, so verify + // every child that is complete has it's children done as well + for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); + it != categories->end(); + ++it) + { + LLViewerInventoryCategory* child_cat = (*it); + if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion()) + { + mFetchFolderQueue.push_back(FetchQueueInfo(child_cat->getUUID(), FT_RECURSIVE)); + } + } + } + else + { + // send it back to get the rest + mFetchFolderQueue.push_back(FetchQueueInfo(cat_id, FT_CONTENT_RECURSIVE)); + } + } + else if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion() + || fetch_info.mFetchType == FT_FORCED) + { + LLViewerInventoryCategory::EFetchType target_state = + fetch_info.mFetchType > FT_CONTENT_RECURSIVE + ? LLViewerInventoryCategory::FETCH_RECURSIVE + : LLViewerInventoryCategory::FETCH_NORMAL; + // start again if we did a non-recursive fetch before + // to get all children in a single request + if (cat->getFetching() < target_state) + { + // increment before call in case of immediate callback + incrFetchFolderCount(1); + cat->setFetching(target_state); + mExpectedFolderIds.push_back(cat_id); + + EFetchType type = fetch_info.mFetchType; + LLUUID cat_id = cat->getUUID(); + AISAPI::completion_t cb = [cat_id , type](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISFolderCalback(cat_id , response_id , type); + }; + + AISAPI::ITEM_TYPE item_type = AISAPI::INVENTORY; + if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + { + item_type = AISAPI::LIBRARY; + } + + AISAPI::FetchCategoryChildren(cat_id , item_type , type == FT_RECURSIVE , cb, 0); + } + } + else + { + // Already fetched, check if anything inside needs fetching + if (fetch_info.mFetchType == FT_RECURSIVE + || fetch_info.mFetchType == FT_FOLDER_AND_CONTENT) + { + LLInventoryModel::cat_array_t * categories(NULL); + LLInventoryModel::item_array_t * items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + // not push_front to not cause an infinite loop + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + } // else try to fetch folder either way? + } + } + else + { + LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); + + if (itemp) + { + if (!itemp->isFinished() || fetch_info.mFetchType == FT_FORCED) + { + mFetchCount++; + if (itemp->getPermissions().getOwner() == gAgent.getID()) + { + AISAPI::FetchItem(fetch_info.mUUID, AISAPI::INVENTORY, ais_simple_item_callback); + } + else + { + AISAPI::FetchItem(fetch_info.mUUID, AISAPI::LIBRARY, ais_simple_item_callback); + } + } + } + else // We don't know it, assume incomplete + { + // Assume agent's inventory, library wouldn't have gotten here + mFetchCount++; + AISAPI::FetchItem(fetch_info.mUUID, AISAPI::INVENTORY, ais_simple_item_callback); + } + } +} + // Bundle up a bunch of requests to send all at once. void LLInventoryModelBackgroundFetch::bulkFetch() { @@ -540,13 +1094,6 @@ void LLInventoryModelBackgroundFetch::bulkFetch() // inventory more quickly. static const U32 max_batch_size(10); static const S32 max_concurrent_fetches(12); // Outstanding requests, not connections - static const F32 new_min_time(0.05f); // *HACK: Clean this up when old code goes away entirely. - - mMinTimeBetweenFetches = new_min_time; - if (mMinTimeBetweenFetches < new_min_time) - { - mMinTimeBetweenFetches = new_min_time; // *HACK: See above. - } if (mFetchCount) { @@ -566,8 +1113,7 @@ void LLInventoryModelBackgroundFetch::bulkFetch() // } - if ((mFetchCount > max_concurrent_fetches) || - (mFetchTimer.getElapsedTimeF32() < mMinTimeBetweenFetches)) + if (mFetchCount > max_concurrent_fetches) { return; } @@ -587,98 +1133,108 @@ void LLInventoryModelBackgroundFetch::bulkFetch() LLSD item_request_body; LLSD item_request_body_lib; - while (! mFetchQueue.empty() + while (! mFetchFolderQueue.empty() && (item_count + folder_count) < max_batch_size) { - const FetchQueueInfo & fetch_info(mFetchQueue.front()); + const FetchQueueInfo & fetch_info(mFetchFolderQueue.front()); if (fetch_info.mIsCategory) { const LLUUID & cat_id(fetch_info.mUUID); - if (cat_id.isNull()) //DEV-17797 + if (cat_id.isNull()) //DEV-17797 Lost and found { LLSD folder_sd; folder_sd["folder_id"] = LLUUID::null.asString(); folder_sd["owner_id"] = gAgent.getID(); folder_sd["sort_order"] = LLSD::Integer(sort_order); - folder_sd["fetch_folders"] = LLSD::Boolean(FALSE); - folder_sd["fetch_items"] = LLSD::Boolean(TRUE); + folder_sd["fetch_folders"] = LLSD::Boolean(false); + folder_sd["fetch_items"] = LLSD::Boolean(true); folder_request_body["folders"].append(folder_sd); folder_count++; } else { - const LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); - - if (cat) - { - if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion() - && std::find(all_cats.begin(), all_cats.end(), cat_id) == all_cats.end()) - { - LLSD folder_sd; - folder_sd["folder_id"] = cat->getUUID(); - folder_sd["owner_id"] = cat->getOwnerID(); - folder_sd["sort_order"] = LLSD::Integer(sort_order); - folder_sd["fetch_folders"] = LLSD::Boolean(TRUE); //(LLSD::Boolean)sFullFetchStarted; - folder_sd["fetch_items"] = LLSD::Boolean(TRUE); + const LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); + if (cat) + { + if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion()) + { + if (std::find(all_cats.begin(), all_cats.end(), cat_id) == all_cats.end()) + { + LLSD folder_sd; + folder_sd["folder_id"] = cat->getUUID(); + folder_sd["owner_id"] = cat->getOwnerID(); + folder_sd["sort_order"] = LLSD::Integer(sort_order); + folder_sd["fetch_folders"] = LLSD::Boolean(TRUE); //(LLSD::Boolean)sFullFetchStarted; + folder_sd["fetch_items"] = LLSD::Boolean(TRUE); - // correct library owner for OpenSim (Rye) - // if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) - if (gInventory.getLibraryOwnerID() == cat->getOwnerID()) - // - { - folder_request_body_lib["folders"].append(folder_sd); - } - else - { - folder_request_body["folders"].append(folder_sd); - } - folder_count++; - } - - // May already have this folder, but append child folders to list. - if (fetch_info.mRecursive) - { - LLInventoryModel::cat_array_t * categories(NULL); - LLInventoryModel::item_array_t * items(NULL); - gInventory.getDirectDescendentsOf(cat->getUUID(), categories, items); - for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); - it != categories->end(); - ++it) - { - mFetchQueue.push_back(FetchQueueInfo((*it)->getUUID(), fetch_info.mRecursive)); - } - } - } + // correct library owner for OpenSim (Rye) + //if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + if (gInventory.getLibraryOwnerID() == cat->getOwnerID()) + // + { + folder_request_body_lib["folders"].append(folder_sd); + } + else + { + folder_request_body["folders"].append(folder_sd); + } + folder_count++; + } + } + else + { + // May already have this folder, but append child folders to list. + if (fetch_info.mFetchType >= FT_CONTENT_RECURSIVE) + { + LLInventoryModel::cat_array_t * categories(NULL); + LLInventoryModel::item_array_t * items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), fetch_info.mFetchType)); + } + } + } + } } - if (fetch_info.mRecursive) + if (fetch_info.mFetchType >= FT_CONTENT_RECURSIVE) { recursive_cats.push_back(cat_id); } all_cats.push_back(cat_id); } - else - { - LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); - if (itemp) - { - LLSD item_sd; - item_sd["owner_id"] = itemp->getPermissions().getOwner(); - item_sd["item_id"] = itemp->getUUID(); - if (itemp->getPermissions().getOwner() == gAgent.getID()) - { - item_request_body.append(item_sd); - } - else - { - item_request_body_lib.append(item_sd); - } - //itemp->fetchFromServer(); - item_count++; - } - } + mFetchFolderQueue.pop_front(); + } - mFetchQueue.pop_front(); + + while (!mFetchItemQueue.empty() + && (item_count + folder_count) < max_batch_size) + { + const FetchQueueInfo & fetch_info(mFetchItemQueue.front()); + + LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); + + if (itemp) + { + LLSD item_sd; + item_sd["owner_id"] = itemp->getPermissions().getOwner(); + item_sd["item_id"] = itemp->getUUID(); + if (itemp->getPermissions().getOwner() == gAgent.getID()) + { + item_request_body.append(item_sd); + } + else + { + item_request_body_lib.append(item_sd); + } + //itemp->fetchFromServer(); + item_count++; + } + + mFetchItemQueue.pop_front(); } // Issue HTTP POST requests to fetch folders and items @@ -749,14 +1305,22 @@ void LLInventoryModelBackgroundFetch::bulkFetch() bool LLInventoryModelBackgroundFetch::fetchQueueContainsNoDescendentsOf(const LLUUID & cat_id) const { - for (fetch_queue_t::const_iterator it = mFetchQueue.begin(); - it != mFetchQueue.end(); + for (fetch_queue_t::const_iterator it = mFetchFolderQueue.begin(); + it != mFetchFolderQueue.end(); ++it) { const LLUUID & fetch_id = (*it).mUUID; if (gInventory.isObjectDescendentOf(fetch_id, cat_id)) return false; } + for (fetch_queue_t::const_iterator it = mFetchItemQueue.begin(); + it != mFetchItemQueue.end(); + ++it) + { + const LLUUID & fetch_id = (*it).mUUID; + if (gInventory.isObjectDescendentOf(fetch_id, cat_id)) + return false; + } return true; } @@ -1015,10 +1579,10 @@ void BGFolderHttpHandler::processFailure(LLCore::HttpStatus status, LLCore::Http while (iter != end) { folders.append(*iter); - LLUUID fodler_id = iter->get("folder_id").asUUID(); - if (std::find(mRecursiveCatUUIDs.begin(), mRecursiveCatUUIDs.end(), fodler_id) != mRecursiveCatUUIDs.end()) + LLUUID folder_id = iter->get("folder_id").asUUID(); + if (std::find(mRecursiveCatUUIDs.begin(), mRecursiveCatUUIDs.end(), folder_id) != mRecursiveCatUUIDs.end()) { - recursive_cats.push_back(fodler_id); + recursive_cats.push_back(folder_id); } if (folders.size() == (S32)(size / 2)) { diff --git a/indra/newview/llinventorymodelbackgroundfetch.h b/indra/newview/llinventorymodelbackgroundfetch.h index efb6dc767d..bc159f4727 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.h +++ b/indra/newview/llinventorymodelbackgroundfetch.h @@ -47,9 +47,11 @@ class LLInventoryModelBackgroundFetch : public LLSingleton folders_fetched_callback_t; + boost::signals2::connection setFetchCompletionCallback(folders_fetched_callback_t cb); + + void addRequestAtFront(const LLUUID & id, bool recursive, bool is_category); + void addRequestAtBack(const LLUUID & id, bool recursive, bool is_category); protected: + + typedef enum { + FT_DEFAULT = 0, + FT_FORCED, // request non-recursively even if already loaded + FT_CONTENT_RECURSIVE, // request content recursively + FT_FOLDER_AND_CONTENT, // request folder, then content recursively + FT_RECURSIVE, // request everything recursively + } EFetchType; + struct FetchQueueInfo + { + FetchQueueInfo(const LLUUID& id, EFetchType recursive, bool is_category = true) + : mUUID(id), + mIsCategory(is_category), + mFetchType(recursive) + {} + + LLUUID mUUID; + bool mIsCategory; + EFetchType mFetchType; + }; + typedef std::deque fetch_queue_t; + + void onAISContentCalback(const LLUUID& request_id, const uuid_vec_t &content_ids, const LLUUID& response_id, EFetchType fetch_type); + void onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type); + void bulkFetchViaAis(); + void bulkFetchViaAis(const FetchQueueInfo& fetch_info); void bulkFetch(); void backgroundFetch(); @@ -80,37 +113,29 @@ protected: bool fetchQueueContainsNoDescendentsOf(const LLUUID& cat_id) const; private: - BOOL mRecursiveInventoryFetchStarted; - BOOL mRecursiveLibraryFetchStarted; - BOOL mAllFoldersFetched; + bool mRecursiveInventoryFetchStarted; + bool mRecursiveLibraryFetchStarted; + bool mAllRecursiveFoldersFetched; + typedef boost::signals2::signal folders_fetched_signal_t; + folders_fetched_signal_t mFoldersFetchedSignal; - BOOL mBackgroundFetchActive; + bool mBackgroundFetchActive; bool mFolderFetchActive; S32 mFetchCount; + S32 mLastFetchCount; // for debug + S32 mFetchFolderCount; LLFrameTimer mFetchTimer; F32 mMinTimeBetweenFetches; - + fetch_queue_t mFetchFolderQueue; + fetch_queue_t mFetchItemQueue; + std::list mExpectedFolderIds; // for debug, should this track time? // For legacy inventory BOOL mTimelyFetchPending; S32 mNumFetchRetries; F32 mMaxTimeBetweenFetches; // - struct FetchQueueInfo - { - FetchQueueInfo(const LLUUID& id, BOOL recursive, bool is_category = true) - : mUUID(id), - mIsCategory(is_category), - mRecursive(recursive) - {} - - LLUUID mUUID; - bool mIsCategory; - BOOL mRecursive; - }; - typedef std::deque fetch_queue_t; - fetch_queue_t mFetchQueue; }; #endif // LL_LLINVENTORYMODELBACKGROUNDFETCH_H diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index 897495a10e..6d5065d690 100644 --- a/indra/newview/llinventoryobserver.cpp +++ b/indra/newview/llinventoryobserver.cpp @@ -37,8 +37,10 @@ #include "llagent.h" #include "llagentwearables.h" +#include "llaisapi.h" #include "llfloater.h" #include "llfocusmgr.h" +#include "llinventorymodelbackgroundfetch.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" #include "llinventorymodel.h" @@ -58,6 +60,7 @@ #include "llviewernetwork.h" // [UDP-Msg] +const S32 LLInventoryFetchItemsObserver::MAX_INDIVIDUAL_ITEM_REQUESTS = 7; const F32 LLInventoryFetchItemsObserver::FETCH_TIMER_EXPIRY = 60.0f; @@ -151,7 +154,7 @@ LLInventoryFetchItemsObserver::LLInventoryFetchItemsObserver(const uuid_vec_t& i void LLInventoryFetchItemsObserver::changed(U32 mask) { - LL_DEBUGS() << this << " remaining incomplete " << mIncomplete.size() + LL_DEBUGS("InventoryFetch") << this << " remaining incomplete " << mIncomplete.size() << " complete " << mComplete.size() << " wait period " << mFetchingPeriod.getRemainingTimeF32() << LL_ENDL; @@ -160,6 +163,15 @@ void LLInventoryFetchItemsObserver::changed(U32 mask) // appropriate. if (!mIncomplete.empty()) { + if (!LLInventoryModelBackgroundFetch::getInstance()->isEverythingFetched()) + { + // Folders have a priority over items and they download items as well + // Wait untill initial folder fetch is done + LL_DEBUGS("InventoryFetch") << "Folder fetch in progress, resetting fetch timer" << LL_ENDL; + + mFetchingPeriod.reset(); + mFetchingPeriod.setTimerExpirySec(FETCH_TIMER_EXPIRY); + } // Have we exceeded max wait time? bool timeout_expired = mFetchingPeriod.hasExpired(); @@ -178,7 +190,7 @@ void LLInventoryFetchItemsObserver::changed(U32 mask) if (timeout_expired) { // Just concede that this item hasn't arrived in reasonable time and continue on. - LL_WARNS() << "Fetcher timed out when fetching inventory item UUID: " << item_id << LL_ENDL; + LL_WARNS("InventoryFetch") << "Fetcher timed out when fetching inventory item UUID: " << item_id << LL_ENDL; it = mIncomplete.erase(it); } else @@ -193,7 +205,7 @@ void LLInventoryFetchItemsObserver::changed(U32 mask) if (mIncomplete.empty()) { - LL_DEBUGS() << this << " done at remaining incomplete " + LL_DEBUGS("InventoryFetch") << this << " done at remaining incomplete " << mIncomplete.size() << " complete " << mComplete.size() << LL_ENDL; done(); } @@ -288,29 +300,21 @@ void fetch_items_from_llsd(const LLSD& items_llsd) void LLInventoryFetchItemsObserver::startFetch() { - LLUUID owner_id; + bool aisv3 = AISAPI::isAvailable(); + LLSD items_llsd; + + typedef std::map requests_by_folders_t; + requests_by_folders_t requests; for (uuid_vec_t::const_iterator it = mIDs.begin(); it < mIDs.end(); ++it) { - LLViewerInventoryItem* item = gInventory.getItem(*it); - if (item) - { - if (item->isFinished()) - { - // It's complete, so put it on the complete container. - mComplete.push_back(*it); - continue; - } - else - { - owner_id = item->getPermissions().getOwner(); - } - } - else - { - // assume it's agent inventory. - owner_id = gAgent.getID(); - } + LLViewerInventoryItem* item = gInventory.getItem(*it); + if (item && item->isFinished()) + { + // It's complete, so put it on the complete container. + mComplete.push_back(*it); + continue; + } // Ignore categories since they're not items. We // could also just add this to mComplete but not sure what the @@ -331,17 +335,98 @@ void LLInventoryFetchItemsObserver::startFetch() // pack this on the message. mIncomplete.push_back(*it); - // Prepare the data to fetch - LLSD item_entry; - item_entry["owner_id"] = owner_id; - item_entry["item_id"] = (*it); - items_llsd.append(item_entry); + if (aisv3) + { + if (item) + { + LLUUID parent_id = item->getParentUUID(); + requests[parent_id].push_back(*it); + } + else + { + // Can happen for gestures and calling cards if server notified us before they fetched + // Request by id without checking for an item. + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(*it); + } + } + else + { + // Prepare the data to fetch + LLSD item_entry; + if (item) + { + item_entry["owner_id"] = item->getPermissions().getOwner(); + } + else + { + // assume it's agent inventory. + item_entry["owner_id"] = gAgent.getID(); + } + item_entry["item_id"] = (*it); + items_llsd.append(item_entry); + } } mFetchingPeriod.reset(); mFetchingPeriod.setTimerExpirySec(FETCH_TIMER_EXPIRY); - fetch_items_from_llsd(items_llsd); + if (aisv3) + { + for (requests_by_folders_t::value_type &folder : requests) + { + if (folder.second.size() > MAX_INDIVIDUAL_ITEM_REQUESTS) + { + // requesting one by one will take a while + // do whole folder + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); + } + else + { + LLViewerInventoryCategory* cat = gInventory.getCategory(folder.first); + if (cat) + { + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // start fetching whole folder since it's not ready either way + cat->fetch(); + } + else if (cat->getViewerDescendentCount() <= folder.second.size() + || cat->getDescendentCount() <= folder.second.size()) + { + // Start fetching whole folder since we need all items + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); + + } + else + { + // get items one by one + for (LLUUID &item_id : folder.second) + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); + } + } + } + else + { + // Isn't supposed to happen? We should have all folders + // and if item exists, folder is supposed to exist as well. + llassert(false); + LL_WARNS("Inventory") << "Missing folder: " << folder.first << " fetching items individually" << LL_ENDL; + + // get items one by one + for (LLUUID &item_id : folder.second) + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); + } + } + } + } + } + else + { + fetch_items_from_llsd(items_llsd); + } + } LLInventoryFetchDescendentsObserver::LLInventoryFetchDescendentsObserver(const LLUUID& cat_id) : @@ -686,6 +771,13 @@ void LLInventoryCategoriesObserver::changed(U32 mask) } } + const LLUUID thumbnail_id = category->getThumbnailUUID(); + if (cat_data.mThumbnailId != thumbnail_id) + { + cat_data.mThumbnailId = thumbnail_id; + cat_changed = true; + } + // If anything has changed above, fire the callback. if (cat_changed) cat_data.mCallback(); @@ -703,6 +795,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; + LLUUID thumbnail_id; LLViewerInventoryCategory* category = gInventory.getCategory(cat_id); // If category could not be retrieved it might mean that @@ -714,6 +807,7 @@ bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t // Inventory category version is used to find out if some changes // to a category have been made. version = category->getVersion(); + thumbnail_id = category->getThumbnailUUID(); LLInventoryModel::cat_array_t* cats; LLInventoryModel::item_array_t* items; @@ -738,12 +832,12 @@ 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, cb, version, current_num_known_descendents,item_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))); } else { - mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, cb, version, current_num_known_descendents))); + mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents))); } } @@ -756,23 +850,25 @@ void LLInventoryCategoriesObserver::removeCategory(const LLUUID& cat_id) } LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, callback_t cb, S32 version, S32 num_descendents) + const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents) : mCatID(cat_id) , mCallback(cb) , mVersion(version) , mDescendentsCount(num_descendents) + , mThumbnailId(thumbnail_id) , mIsNameHashInitialized(false) { } LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash) + const LLUUID& cat_id, const LLUUID& thumbnail_id, 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) , mIsNameHashInitialized(true) , mItemNameHash(name_hash) { diff --git a/indra/newview/llinventoryobserver.h b/indra/newview/llinventoryobserver.h index 4af8102055..bec08d2cdf 100644 --- a/indra/newview/llinventoryobserver.h +++ b/indra/newview/llinventoryobserver.h @@ -104,6 +104,9 @@ public: /*virtual*/ void startFetch(); /*virtual*/ void changed(U32 mask); + + // For attempts to group requests if too many items are requested + static const S32 MAX_INDIVIDUAL_ITEM_REQUESTS; private: LLTimer mFetchingPeriod; @@ -125,7 +128,7 @@ public: LLInventoryFetchDescendentsObserver(const LLUUID& cat_id = LLUUID::null); LLInventoryFetchDescendentsObserver(const uuid_vec_t& cat_ids); - /*virtual*/ void startFetch(); + virtual void startFetch(); /*virtual*/ void changed(U32 mask); protected: BOOL isCategoryComplete(const LLViewerInventoryCategory* cat) const; @@ -273,14 +276,15 @@ protected: typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" struct LLCategoryData { - LLCategoryData(const LLUUID& cat_id, callback_t cb, S32 version, S32 num_descendents); - LLCategoryData(const LLUUID& cat_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash); + 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); callback_t mCallback; S32 mVersion; S32 mDescendentsCount; digest_t mItemNameHash; bool mIsNameHashInitialized; LLUUID mCatID; + LLUUID mThumbnailId; }; typedef std::map category_map_t; diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index cd2558929c..eda7db0be2 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -40,6 +40,7 @@ #include "llfolderviewitem.h" #include "llfloaterimcontainer.h" #include "llimview.h" +#include "llinspecttexture.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" #include "llinventorymodelbackgroundfetch.h" @@ -168,14 +169,16 @@ LLInventoryPanel::LLInventoryPanel(const LLInventoryPanel::Params& p) : mInvFVBridgeBuilder(NULL), mInventoryViewModel(p.name), mGroupedItemBridge(new LLFolderViewGroupedItemBridge), - mFocusSelection(false) + mFocusSelection(false), + mBuildChildrenViews(true), + mRootInited(false) { mInvFVBridgeBuilder = &INVENTORY_BRIDGE_BUILDER; if (!sColorSetInitialized) { // Make inventory selection color independent from menu color - //sDefaultColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + //sDefaultColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE); //sDefaultHighlightColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE); sDefaultColor = LLUIColorTable::instance().getColor("InventoryItemEnabledColor", DEFAULT_WHITE); sDefaultHighlightColor = LLUIColorTable::instance().getColor("InventoryItemHighlightFgColor", DEFAULT_WHITE); @@ -194,6 +197,7 @@ LLInventoryPanel::LLInventoryPanel(const LLInventoryPanel::Params& p) : mCommitCallbackRegistrar.add("Inventory.BeginIMSession", boost::bind(&LLInventoryPanel::beginIMSession, this)); mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryPanel::fileUploadLocation, this, _2)); + mCommitCallbackRegistrar.add("Inventory.OpenNewFolderWindow", boost::bind(&LLInventoryPanel::openSingleViewInventory, this, LLUUID())); mCommitCallbackRegistrar.add("Inventory.CustomAction", boost::bind(&LLInventoryPanel::onCustomAction, this, _2)); // Prevent warning "No callback found for: 'Inventory.CustomAction' in control: Find Links" } @@ -270,112 +274,11 @@ void LLInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) { // save off copy of params mParams = params; - // Clear up the root view - // Note: This needs to be done *before* we build the new folder view - LLUUID root_id = getRootFolderID(); - if (mFolderRoot.get()) - { - removeItemID(root_id); - mFolderRoot.get()->destroyView(); - } - mCommitCallbackRegistrar.pushScope(); // registered as a widget; need to push callback scope ourselves - { - // Determine the root folder in case specified, and - // build the views starting with that folder. - LLFolderView* folder_view = createFolderRoot(root_id); - mFolderRoot = folder_view->getHandle(); - - addItemID(root_id, mFolderRoot.get()); - } - mCommitCallbackRegistrar.popScope(); - mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); - mFolderRoot.get()->setEnableRegistrar(&mEnableCallbackRegistrar); - - // Scroller - LLRect scroller_view_rect = getRect(); - scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); - // Pull this magic number here so inventory scroll panel - // doesn't get cut off on the left side! - scroller_view_rect.mLeft += 2; - // - LLScrollContainer::Params scroller_params(mParams.scroll()); - scroller_params.rect(scroller_view_rect); - mScroller = LLUICtrlFactory::create(scroller_params); - addChild(mScroller); - mScroller->addChild(mFolderRoot.get()); - mFolderRoot.get()->setScrollContainer(mScroller); - mFolderRoot.get()->setFollowsAll(); - mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); + initFolderRoot(); - // Set up the callbacks from the inventory we're viewing, and then build everything. - mInventoryObserver = new LLInventoryPanelObserver(this); - mInventory->addObserver(mInventoryObserver); - - mCompletionObserver = new LLInvPanelComplObserver(boost::bind(&LLInventoryPanel::onItemsCompletion, this)); - mInventory->addObserver(mCompletionObserver); - - if (mBuildViewsOnInit && mViewsInitialized == VIEWS_UNINITIALIZED) - { - // Build view of inventory if we need default full hierarchy and inventory is ready, otherwise do in onIdle. - // Initializing views takes a while so always do it onIdle if viewer already loaded. - if (mInventory->isInventoryUsable() - && LLStartUp::getStartupState() <= STATE_WEARABLES_WAIT) - { - // Usually this happens on login, so we have less time constraits, but too long and we can cause a disconnect - const F64 max_time = 20.f; - initializeViews(max_time); - } - else - { - mViewsInitialized = VIEWS_INITIALIZING; - gIdleCallbacks.addFunction(onIdle, (void*)this); - } - } - - if (mSortOrderSetting != INHERIT_SORT_ORDER) - { - setSortOrder(gSavedSettings.getU32(mSortOrderSetting)); - } - else - { - setSortOrder(gSavedSettings.getU32(DEFAULT_SORT_ORDER)); - } - - // hide inbox - if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible")) - { - // Optional hiding of Received Items folder aka Inbox - //getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); - } - // hide marketplace listing box, unless we are a marketplace panel - if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible") && !mParams.use_marketplace_folders) - { - getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS)); - } - - // Optional hiding of Received Items folder aka Inbox - if (getName() != "Worn Items" && getName() != "inventory_inbox") - { - if (!gSavedSettings.getBOOL("FSShowInboxFolder")) - { - getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); - } - gSavedSettings.getControl("FSShowInboxFolder")->getSignal()->connect(boost::bind(&LLInventoryPanel::updateShowInboxFolder, this, _2)); - } - // Optional hiding of Received Items folder aka Inbox - - // set the filter for the empty folder if the debug setting is on - if (gSavedSettings.getBOOL("DebugHideEmptySystemFolders")) - { - getFilter().setFilterEmptySystemFolders(); - } - - // keep track of the clipboard state so that we avoid filtering too much - mClipboardState = LLClipboard::instance().getGeneration(); - - // Optional hiding of empty system folders - gSavedSettings.getControl("DebugHideEmptySystemFolders")->getSignal()->connect(boost::bind(&LLInventoryPanel::updateHideEmptySystemFolders, this, _2)); + // Optional hiding of empty system folders + gSavedSettings.getControl("DebugHideEmptySystemFolders")->getSignal()->connect(boost::bind(&LLInventoryPanel::updateHideEmptySystemFolders, this, _2)); // Initialize base class params. LLPanel::initFromParams(mParams); @@ -392,13 +295,134 @@ LLInventoryPanel::~LLInventoryPanel() clearFolderRoot(); } +void LLInventoryPanel::initFolderRoot() +{ + // Clear up the root view + // Note: This needs to be done *before* we build the new folder view + LLUUID root_id = getRootFolderID(); + if (mFolderRoot.get()) + { + removeItemID(root_id); + mFolderRoot.get()->destroyView(); + } + + mCommitCallbackRegistrar.pushScope(); // registered as a widget; need to push callback scope ourselves + { + // Determine the root folder in case specified, and + // build the views starting with that folder. + LLFolderView* folder_view = createFolderRoot(root_id); + mFolderRoot = folder_view->getHandle(); + mRootInited = true; + + addItemID(root_id, mFolderRoot.get()); + } + mCommitCallbackRegistrar.popScope(); + mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); + mFolderRoot.get()->setEnableRegistrar(&mEnableCallbackRegistrar); + + // Scroller + LLRect scroller_view_rect = getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(mParams.scroll()); + scroller_params.rect(scroller_view_rect); + mScroller = LLUICtrlFactory::create(scroller_params); + addChild(mScroller); + mScroller->addChild(mFolderRoot.get()); + mFolderRoot.get()->setScrollContainer(mScroller); + mFolderRoot.get()->setFollowsAll(); + mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); + + if (mSelectionCallback) + { + mFolderRoot.get()->setSelectCallback(mSelectionCallback); + } + + // Set up the callbacks from the inventory we're viewing, and then build everything. + mInventoryObserver = new LLInventoryPanelObserver(this); + mInventory->addObserver(mInventoryObserver); + + mCompletionObserver = new LLInvPanelComplObserver(boost::bind(&LLInventoryPanel::onItemsCompletion, this)); + mInventory->addObserver(mCompletionObserver); + + if (mBuildViewsOnInit) + { + initializeViewBuilding(); + } + + if (mSortOrderSetting != INHERIT_SORT_ORDER) + { + setSortOrder(gSavedSettings.getU32(mSortOrderSetting)); + } + else + { + setSortOrder(gSavedSettings.getU32(DEFAULT_SORT_ORDER)); + } + + // hide inbox + if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible")) + { + // Optional hiding of Received Items folder aka Inbox + //getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); + } + // hide marketplace listing box, unless we are a marketplace panel + if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible") && !mParams.use_marketplace_folders) + { + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS)); + } + + // Optional hiding of Received Items folder aka Inbox + if (getName() != "Worn Items" && getName() != "inventory_inbox") + { + if (!gSavedSettings.getBOOL("FSShowInboxFolder")) + { + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); + } + gSavedSettings.getControl("FSShowInboxFolder")->getSignal()->connect(boost::bind(&LLInventoryPanel::updateShowInboxFolder, this, _2)); + } + // Optional hiding of Received Items folder aka Inbox + + // set the filter for the empty folder if the debug setting is on + if (gSavedSettings.getBOOL("DebugHideEmptySystemFolders")) + { + getFilter().setFilterEmptySystemFolders(); + } + + // keep track of the clipboard state so that we avoid filtering too much + mClipboardState = LLClipboard::instance().getGeneration(); +} + +void LLInventoryPanel::initializeViewBuilding() +{ + if (mViewsInitialized == VIEWS_UNINITIALIZED) + { + LL_DEBUGS("Inventory") << "Setting views for " << getName() << " to initialize" << LL_ENDL; + // Build view of inventory if we need default full hierarchy and inventory is ready, otherwise do in onIdle. + // Initializing views takes a while so always do it onIdle if viewer already loaded. + if (mInventory->isInventoryUsable() + && LLStartUp::getStartupState() <= STATE_WEARABLES_WAIT) + { + // Usually this happens on login, so we have less time constraits, but too long and we can cause a disconnect + const F64 max_time = 20.f; + initializeViews(max_time); + } + else + { + mViewsInitialized = VIEWS_INITIALIZING; + gIdleCallbacks.addFunction(onIdle, (void*)this); + } + } +} + /*virtual*/ void LLInventoryPanel::onVisibilityChange(BOOL new_visibility) { if (new_visibility && mViewsInitialized == VIEWS_UNINITIALIZED) { - mViewsInitialized = VIEWS_INITIALIZING; - gIdleCallbacks.addFunction(onIdle, (void*)this); + // first call can be from tab initialization + if (gFloaterView->getParentFloater(this) != NULL) + { + initializeViewBuilding(); + } } LLPanel::onVisibilityChange(new_visibility); } @@ -815,7 +839,7 @@ LLUUID LLInventoryPanel::getRootFolderID() LLStringExplicit label(mParams.start_folder.name()); setLabel(label); - root_id = gInventory.findCategoryUUIDForType(preferred_type, false); + root_id = gInventory.findCategoryUUIDForType(preferred_type); if (root_id.isNull()) { LL_WARNS() << "Could not find folder of type " << preferred_type << LL_ENDL; @@ -950,6 +974,7 @@ void LLInventoryPanel::idle(void* user_data) void LLInventoryPanel::initializeViews(F64 max_time) { if (!gInventory.isInventoryUsable()) return; + if (!mRootInited) return; mViewsInitialized = VIEWS_BUILDING; @@ -977,7 +1002,10 @@ void LLInventoryPanel::initializeViews(F64 max_time) gIdleCallbacks.addFunction(idle, this); - openStartFolderOrMyInventory(); + if(mParams.open_first_folder) + { + openStartFolderOrMyInventory(); + } // Special case for new user login if (gAgent.isFirstLogin()) @@ -1009,8 +1037,8 @@ LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * br // params.tool_tip = params.name; // Don't bother with tooltips in inventory params.allow_drop = allow_drop; - params.font_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultColor)); - params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultHighlightColor)); + params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); + params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor); // Inventory specials params.for_inventory = true; @@ -1035,8 +1063,8 @@ LLFolderViewItem * LLInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge params.rect = LLRect (0, 0, 0, 0); // params.tool_tip = params.name; // Don't bother with tooltips in inventory - params.font_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultColor)); - params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : (bridge->isLink() ? sLinkColor : sDefaultHighlightColor)); + params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); + params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor); // Inventory specials params.for_inventory = true; @@ -1101,8 +1129,11 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, LLInventoryObject const* objectp, LLFolderViewItem *folder_view_item, LLFolderViewFolder *parent_folder, - const EBuildModes &mode) + const EBuildModes &mode, + S32 depth) { + depth++; + // Force the creation of an extra root level folder item if required by the inventory panel (default is "false") bool allow_drop = true; bool create_root = false; @@ -1132,7 +1163,7 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, if (objectp->getType() >= LLAssetType::AT_COUNT) { // Example: Happens when we add assets of new, not yet supported type to library - LL_DEBUGS() << "LLInventoryPanel::buildViewsTree called with unknown objectp->mType : " + LL_DEBUGS("Inventory") << "LLInventoryPanel::buildViewsTree called with unknown objectp->mType : " << ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID() << LL_ENDL; @@ -1202,7 +1233,8 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, } } - bool create_children = folder_view_item && objectp->getType() == LLAssetType::AT_CATEGORY; + bool create_children = folder_view_item && objectp->getType() == LLAssetType::AT_CATEGORY + && (mBuildChildrenViews || depth == 0); if (create_children) { @@ -1222,12 +1254,15 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, { create_children = false; // run it again for the sake of creating children - mBuildViewsQueue.push_back(id); + if (mBuildChildrenViews || depth == 0) + { + mBuildViewsQueue.push_back(id); + } } else { create_children = true; - folder_view_item->setChildrenInited(true); + folder_view_item->setChildrenInited(mBuildChildrenViews); } break; } @@ -1235,7 +1270,10 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, { create_children = false; // run it to create children, current caller is only interested in current view - mBuildViewsQueue.push_back(id); + if (mBuildChildrenViews || depth == 0) + { + mBuildViewsQueue.push_back(id); + } break; } case BUILD_ONE_FOLDER: @@ -1265,7 +1303,13 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, LLViewerInventoryItem::item_array_t* items; mInventory->lockDirectDescendentArrays(id, categories, items); + // Make sure panel won't lock in a loop over existing items if + // folder is enormous and at least some work gets done + const S32 MIN_ITEMS_PER_CALL = 500; + const S32 starting_item_count = mItemMap.size(); + LLFolderViewFolder *parentp = dynamic_cast(folder_view_item); + bool done = true; if(categories) { @@ -1283,11 +1327,28 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, // each time, especially since content is growing, we can just // iter over copy of mItemMap in some way LLFolderViewItem* view_itemp = getItemByID(cat->getUUID()); - buildViewsTree(cat->getUUID(), id, cat, view_itemp, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode)); + buildViewsTree(cat->getUUID(), id, cat, view_itemp, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode), depth); } else { - buildViewsTree(cat->getUUID(), id, cat, NULL, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode)); + buildViewsTree(cat->getUUID(), id, cat, NULL, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode), depth); + } + } + + if (!mBuildChildrenViews + && mode == BUILD_TIMELIMIT + && MIN_ITEMS_PER_CALL + starting_item_count < mItemMap.size()) + { + // Single folder view, check if we still have time + // + // Todo: make sure this causes no dupplciates, breaks nothing, + // especially filters and arrange + F64 curent_time = LLTimer::getTotalSeconds(); + if (mBuildViewsEndTime < curent_time) + { + mBuildViewsQueue.push_back(id); + done = false; + break; } } } @@ -1307,10 +1368,33 @@ LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, // each time, especially since content is growing, we can just // iter over copy of mItemMap in some way LLFolderViewItem* view_itemp = getItemByID(item->getUUID()); - buildViewsTree(item->getUUID(), id, item, view_itemp, parentp, mode); + buildViewsTree(item->getUUID(), id, item, view_itemp, parentp, mode, depth); + } + + if (!mBuildChildrenViews + && mode == BUILD_TIMELIMIT + && MIN_ITEMS_PER_CALL + starting_item_count < mItemMap.size()) + { + // Single folder view, check if we still have time + // + // Todo: make sure this causes no dupplciates, breaks nothing, + // especially filters and arrange + F64 curent_time = LLTimer::getTotalSeconds(); + if (mBuildViewsEndTime < curent_time) + { + mBuildViewsQueue.push_back(id); + done = false; + break; + } } } } + + if (!mBuildChildrenViews && done) + { + // flat list is done initializing folder + folder_view_item->setChildrenInited(true); + } mInventory->unlockDirectDescendentArrays(id); } @@ -1375,6 +1459,37 @@ BOOL LLInventoryPanel::handleHover(S32 x, S32 y, MASK mask) return TRUE; } +BOOL LLInventoryPanel::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (const LLFolderViewItem* hover_item_p = (!mFolderRoot.isDead()) ? mFolderRoot.get()->getHoveredItem() : nullptr) + { + if (const LLFolderViewModelItemInventory* vm_item_p = static_cast(hover_item_p->getViewModelItem())) + { + LLSD params; + params["inv_type"] = vm_item_p->getInventoryType(); + params["thumbnail_id"] = vm_item_p->getThumbnailUUID(); + params["item_id"] = vm_item_p->getUUID(); + + // tooltip should only show over folder, but screen + // rect includes items under folder as well + LLRect actionable_rect = hover_item_p->calcScreenRect(); + if (hover_item_p->isOpen() && hover_item_p->hasVisibleChildren()) + { + actionable_rect.mBottom = actionable_rect.mTop - hover_item_p->getItemHeight(); + } + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(hover_item_p->getToolTip()) + .sticky_rect(actionable_rect) + .delay_time(LLView::getTooltipTimeout()) + .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) + .create_params(params)); + return TRUE; + } + } + return LLPanel::handleToolTip(x, y, mask); +} + BOOL LLInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, @@ -1431,6 +1546,45 @@ void LLInventoryPanel::onFocusReceived() // inventory now handles cut/copy/paste/delete LLEditMenuHandler::gEditMenuHandler = mFolderRoot.get(); + // Tab support, when tabbing into this view, select first item + // (ideally needs to account for scroll) + bool select_first = mSelectThisID.isNull() && mFolderRoot.get() && mFolderRoot.get()->getSelectedCount() == 0; + + if (select_first) + { + LLFolderViewFolder::folders_t::const_iterator folders_it = mFolderRoot.get()->getFoldersBegin(); + LLFolderViewFolder::folders_t::const_iterator folders_end = mFolderRoot.get()->getFoldersEnd(); + + for (; folders_it != folders_end; ++folders_it) + { + const LLFolderViewFolder* folder_view = *folders_it; + if (folder_view->getVisible()) + { + const LLFolderViewModelItemInventory* modelp = static_cast(folder_view->getViewModelItem()); + setSelectionByID(modelp->getUUID(), TRUE); + select_first = false; + break; + } + } + } + + if (select_first) + { + LLFolderViewFolder::items_t::const_iterator items_it = mFolderRoot.get()->getItemsBegin(); + LLFolderViewFolder::items_t::const_iterator items_end = mFolderRoot.get()->getItemsEnd(); + + for (; items_it != items_end; ++items_it) + { + const LLFolderViewItem* item_view = *items_it; + if (item_view->getVisible()) + { + const LLFolderViewModelItemInventory* modelp = static_cast(item_view->getViewModelItem()); + setSelectionByID(modelp->getUUID(), TRUE); + break; + } + } + } + LLPanel::onFocusReceived(); } @@ -1487,6 +1641,7 @@ void LLInventoryPanel::setSelectCallback(const boost::functionsetSelectCallback(cb); } + mSelectionCallback = cb; } void LLInventoryPanel::clearSelection() @@ -1533,6 +1688,10 @@ void LLInventoryPanel::onSelectionChange(const std::deque& it { fv->startRenamingSelectedItem(); } + else + { + LL_DEBUGS("Inventory") << "Failed to start renemr, no items selected" << LL_ENDL; + } } std::set selected_items = mFolderRoot.get()->getSelectionList(); @@ -1771,6 +1930,11 @@ void LLInventoryPanel::fileUploadLocation(const LLSD& userdata) } } +void LLInventoryPanel::openSingleViewInventory(LLUUID folder_id) +{ + LLPanelMainInventory::newFolderWindow(folder_id.isNull() ? LLFolderBridge::sSelf.get()->getUUID() : folder_id); +} + void LLInventoryPanel::purgeSelectedItems() { if (!mFolderRoot.get()) return; @@ -1971,7 +2135,7 @@ LLInventoryPanel* LLInventoryPanel::getActiveInventoryPanel(BOOL auto_open) } //static -void LLInventoryPanel::openInventoryPanelAndSetSelection(BOOL auto_open, const LLUUID& obj_id, BOOL main_panel, BOOL take_keyboard_focus, BOOL reset_filter) +void LLInventoryPanel::openInventoryPanelAndSetSelection(BOOL auto_open, const LLUUID& obj_id, BOOL use_main_panel, BOOL take_keyboard_focus, BOOL reset_filter) { // Use correct inventory floater //LLSidepanelInventory* sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); @@ -1982,17 +2146,66 @@ void LLInventoryPanel::openInventoryPanelAndSetSelection(BOOL auto_open, const L bool show_inbox = gSavedSettings.getBOOL("FSShowInboxFolder"); // Optional hiding of Received Items folder aka Inbox // FIRE-22167: Make "Show in Main View" work properly - //if (!in_inbox && (main_panel || !sidepanel_inventory->getMainInventoryPanel()->isRecentItemsPanelSelected())) //if (main_panel && !in_inbox) + //if (!in_inbox && (use_main_panel || !sidepanel_inventory->getMainInventoryPanel()->isRecentItemsPanelSelected())) //if (main_panel && !in_inbox) //{ // sidepanel_inventory->selectAllItemsPanel(); //} // + + LLFloater* inventory_floater = LLFloaterSidePanelContainer::getTopmostInventoryFloater(); + if(!auto_open && inventory_floater && inventory_floater->getVisible()) + { + LLSidepanelInventory *inventory_panel = inventory_floater->findChild("main_panel"); + LLPanelMainInventory* main_panel = inventory_panel->getMainInventoryPanel(); + if(main_panel->isSingleFolderMode() && main_panel->isGalleryViewMode()) + { + LL_DEBUGS("Inventory") << "Opening gallery panel for item" << obj_id << LL_ENDL; + main_panel->setGallerySelection(obj_id); + return; + } + } + + // Use correct inventory floater + //LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + //if (main_inventory && main_inventory->isSingleFolderMode() + // && use_main_panel) + //{ + // const LLInventoryObject *obj = gInventory.getObject(obj_id); + // if (obj) + // { + // LL_DEBUGS("Inventory") << "Opening main inventory panel for item" << obj_id << LL_ENDL; + // main_inventory->setSingleFolderViewRoot(obj->getParentUUID(), false); + // main_inventory->setGallerySelection(obj_id); + // return; + // } + //} + if (!inventory_floater) + { + inventory_floater = LLFloaterReg::showInstance("inventory"); + } + if (use_main_panel && inventory_floater) + { + LLSidepanelInventory* inventory_panel = inventory_floater->findChild("main_panel"); + LLPanelMainInventory* main_inventory = inventory_panel->getMainInventoryPanel(); + if (main_inventory && main_inventory->isSingleFolderMode()) + { + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (obj) + { + main_inventory->setSingleFolderViewRoot(obj->getParentUUID(), false); + main_inventory->setGallerySelection(obj_id); + return; + } + } + } + // + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(auto_open); if (active_panel) { - LL_DEBUGS("Messaging") << "Highlighting" << obj_id << LL_ENDL; + LL_DEBUGS("Messaging", "Inventory") << "Highlighting" << obj_id << LL_ENDL; if (reset_filter) { @@ -2014,11 +2227,11 @@ void LLInventoryPanel::openInventoryPanelAndSetSelection(BOOL auto_open, const L inventory_panel->setSelection(obj_id, take_keyboard_focus); } } - else + else if (auto_open) { // FIRE-22167: Make "Show in Main View" work properly //LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); - if (main_panel) + if (use_main_panel) { active_panel->getParentByType()->selectFirstTab(); active_panel = getActiveInventoryPanel(FALSE); @@ -2031,9 +2244,33 @@ void LLInventoryPanel::openInventoryPanelAndSetSelection(BOOL auto_open, const L } active_panel->setSelection(obj_id, take_keyboard_focus); } + else + { + // Created items are going to receive proper focus from callbacks + active_panel->setSelection(obj_id, take_keyboard_focus); + } } } +void LLInventoryPanel::setSFViewAndOpenFolder(const LLInventoryPanel* panel, const LLUUID& folder_id) +{ + + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) + { + LLFloaterSidePanelContainer* inventory_floater = dynamic_cast(*iter); + LLSidepanelInventory* sidepanel_inventory = inventory_floater->findChild("main_panel"); + + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory && panel->hasAncestor(main_inventory) && !main_inventory->isSingleFolderMode()) + { + main_inventory->initSingleFolderRoot(folder_id); + main_inventory->toggleViewMode(); + main_inventory->setSingleFolderViewRoot(folder_id, false); + } + } +} + void LLInventoryPanel::addHideFolderType(LLFolderType::EType folder_type) { getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << folder_type)); @@ -2255,10 +2492,205 @@ LLInventoryRecentItemsPanel::LLInventoryRecentItemsPanel( const Params& params) mInvFVBridgeBuilder = &RECENT_ITEMS_BUILDER; } +static LLDefaultChildRegistry::Register t_single_folder_inventory_panel("single_folder_inventory_panel"); + +LLInventorySingleFolderPanel::LLInventorySingleFolderPanel(const Params& params) + : LLInventoryPanel(params) +{ + mBuildChildrenViews = false; + getFilter().setSingleFolderMode(true); + getFilter().setEmptyLookupMessage("InventorySingleFolderNoMatches"); + getFilter().setDefaultEmptyLookupMessage("InventorySingleFolderEmpty"); + + mCommitCallbackRegistrar.replace("Inventory.DoToSelected", boost::bind(&LLInventorySingleFolderPanel::doToSelected, this, _2)); + mCommitCallbackRegistrar.replace("Inventory.DoCreate", boost::bind(&LLInventorySingleFolderPanel::doCreate, this, _2)); + mCommitCallbackRegistrar.replace("Inventory.Share", boost::bind(&LLInventorySingleFolderPanel::doShare, this)); +} + +LLInventorySingleFolderPanel::~LLInventorySingleFolderPanel() +{ +} + +void LLInventorySingleFolderPanel::initFromParams(const Params& p) +{ + mFolderID = gInventory.getRootFolderID(); + + mParams = p; + LLPanel::initFromParams(mParams); +} + +void LLInventorySingleFolderPanel::initFolderRoot(const LLUUID& start_folder_id) +{ + if(mRootInited) return; + + mRootInited = true; + if(start_folder_id.notNull()) + { + mFolderID = start_folder_id; + } + + mParams.open_first_folder = false; + mParams.start_folder.id = mFolderID; + + LLInventoryPanel::initFolderRoot(); + mFolderRoot.get()->setSingleFolderMode(true); +} + +void LLInventorySingleFolderPanel::changeFolderRoot(const LLUUID& new_id) +{ + if (mFolderID != new_id) + { + if(mFolderID.notNull()) + { + mBackwardFolders.push_back(mFolderID); + } + mFolderID = new_id; + updateSingleFolderRoot(); + } +} + +void LLInventorySingleFolderPanel::onForwardFolder() +{ + if(isForwardAvailable()) + { + mBackwardFolders.push_back(mFolderID); + mFolderID = mForwardFolders.back(); + mForwardFolders.pop_back(); + updateSingleFolderRoot(); + } +} + +void LLInventorySingleFolderPanel::onBackwardFolder() +{ + if(isBackwardAvailable()) + { + mForwardFolders.push_back(mFolderID); + mFolderID = mBackwardFolders.back(); + mBackwardFolders.pop_back(); + updateSingleFolderRoot(); + } +} + +void LLInventorySingleFolderPanel::clearNavigationHistory() +{ + mForwardFolders.clear(); + mBackwardFolders.clear(); +} + +bool LLInventorySingleFolderPanel::isBackwardAvailable() +{ + return (!mBackwardFolders.empty() && (mFolderID != mBackwardFolders.back())); +} + +bool LLInventorySingleFolderPanel::isForwardAvailable() +{ + return (!mForwardFolders.empty() && (mFolderID != mForwardFolders.back())); +} + +boost::signals2::connection LLInventorySingleFolderPanel::setRootChangedCallback(root_changed_callback_t cb) +{ + return mRootChangedSignal.connect(cb); +} + +void LLInventorySingleFolderPanel::updateSingleFolderRoot() +{ + if (mFolderID != getRootFolderID()) + { + mRootChangedSignal(); + + LLUUID root_id = mFolderID; + if (mFolderRoot.get()) + { + mItemMap.clear(); + mFolderRoot.get()->destroyRoot(); + } + + mCommitCallbackRegistrar.pushScope(); + { + LLFolderView* folder_view = createFolderRoot(root_id); + folder_view->setChildrenInited(false); + mFolderRoot = folder_view->getHandle(); + mFolderRoot.get()->setSingleFolderMode(true); + addItemID(root_id, mFolderRoot.get()); + + LLRect scroller_view_rect = getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(mParams.scroll()); + scroller_params.rect(scroller_view_rect); + + if (mScroller) + { + removeChild(mScroller); + delete mScroller; + mScroller = NULL; + } + mScroller = LLUICtrlFactory::create(scroller_params); + addChild(mScroller); + mScroller->addChild(mFolderRoot.get()); + mFolderRoot.get()->setScrollContainer(mScroller); + mFolderRoot.get()->setFollowsAll(); + mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); + + if (!mSelectionCallback.empty()) + { + mFolderRoot.get()->setSelectCallback(mSelectionCallback); + } + } + mCommitCallbackRegistrar.popScope(); + mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); + + buildNewViews(mFolderID); + + LLFloater* root_floater = gFloaterView->getParentFloater(this); + if(root_floater) + { + root_floater->setFocus(true); + } + } +} + +bool LLInventorySingleFolderPanel::hasVisibleItems() +{ + return mFolderRoot.get()->hasVisibleChildren(); +} + +void LLInventorySingleFolderPanel::doCreate(const LLSD& userdata) +{ + std::string type_name = userdata.asString(); + LLUUID dest_id = LLFolderBridge::sSelf.get()->getUUID(); + if (("category" == type_name) || ("outfit" == type_name)) + { + changeFolderRoot(dest_id); + } + reset_inventory_filter(); + menu_create_inventory_item(this, dest_id, userdata); +} + +void LLInventorySingleFolderPanel::doToSelected(const LLSD& userdata) +{ + if (("open_in_current_window" == userdata.asString())) + { + changeFolderRoot(LLFolderBridge::sSelf.get()->getUUID()); + return; + } + LLInventoryPanel::doToSelected(userdata); +} + +void LLInventorySingleFolderPanel::doShare() +{ + LLAvatarActions::shareWithAvatars(this); +} /************************************************************************/ /* Asset Pre-Filtered Inventory Panel related class */ /************************************************************************/ +LLAssetFilteredInventoryPanel::LLAssetFilteredInventoryPanel(const Params& p) + : LLInventoryPanel(p) + , mAssetType(LLAssetType::AT_NONE) +{ +} + + void LLAssetFilteredInventoryPanel::initFromParams(const Params& p) { mAssetType = LLAssetType::lookup(p.filter_asset_type.getValue()); diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h index 4420883f84..31587abc5c 100644 --- a/indra/newview/llinventorypanel.h +++ b/indra/newview/llinventorypanel.h @@ -107,6 +107,7 @@ public: Optional folder_view; Optional folder; Optional item; + Optional open_first_folder; // All item and folder views will be initialized on init if true (default) // Will initialize on visibility change otherwise. @@ -126,6 +127,7 @@ public: show_root_folder("show_root_folder", false), allow_drop_on_root("allow_drop_on_root", true), use_marketplace_folders("use_marketplace_folders", false), + open_first_folder("open_first_folder", true), scroll("scroll"), accepts_drag_and_drop("accepts_drag_and_drop"), folder_view("folder_view"), @@ -159,22 +161,23 @@ public: LLFolderViewModelInventory& getRootViewModel() { return mInventoryViewModel; } // LLView methods - /*virtual*/ void onVisibilityChange(BOOL new_visibility); - void draw(); - /*virtual*/ BOOL handleKeyHere( KEY key, MASK mask ); - BOOL handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ void onVisibilityChange(BOOL new_visibility) override; + void draw() override; + /*virtual*/ BOOL handleKeyHere( KEY key, MASK mask ) override; + BOOL handleHover(S32 x, S32 y, MASK mask) override; /*virtual*/ BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, - std::string& tooltip_msg); + std::string& tooltip_msg) override; + BOOL handleToolTip(S32 x, S32 y, MASK mask) override; // LLUICtrl methods - /*virtual*/ void onFocusLost(); - /*virtual*/ void onFocusReceived(); + /*virtual*/ void onFocusLost() override; + /*virtual*/ void onFocusReceived() override; void onFolderOpening(const LLUUID &id); // LLBadgeHolder methods - bool addBadge(LLBadge * badge); + bool addBadge(LLBadge * badge) override; // Call this method to set the selection. void openAllFolders(); @@ -221,6 +224,7 @@ public: LLUUID getRootFolderID(); LLScrollContainer* getScrollableContainer() { return mScroller; } bool getAllowDropOnRoot() { return mParams.allow_drop_on_root; } + bool areViewsInitialized() { return mViewsInitialized == VIEWS_INITIALIZED && mFolderRoot.get() && !mFolderRoot.get()->needsArrange(); } void onSelectionChange(const std::deque &items, BOOL user_action); @@ -232,6 +236,7 @@ public: void doCreate(const LLSD& userdata); bool beginIMSession(); void fileUploadLocation(const LLSD& userdata); + void openSingleViewInventory(LLUUID folder_id = LLUUID()); void purgeSelectedItems(); bool attachObject(const LLSD& userdata); static void idle(void* user_data); @@ -257,10 +262,10 @@ public: static void openInventoryPanelAndSetSelection(BOOL auto_open, const LLUUID& obj_id, - BOOL main_panel = FALSE, + BOOL use_main_panel = FALSE, BOOL take_keyboard_focus = TAKE_FOCUS_YES, 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); LLFolderViewItem* getItemByID(const LLUUID& id); @@ -278,6 +283,10 @@ public: static void callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response, const std::vector inventory_selected); + void changeFolderRoot(const LLUUID& new_id) {}; + void initFolderRoot(); + void initializeViewBuilding(); + protected: void openStartFolderOrMyInventory(); // open the first level of inventory void onItemsCompletion(); // called when selected items are complete @@ -314,6 +323,9 @@ protected: */ const LLInventoryFolderViewModelBuilder* mInvFVBridgeBuilder; + bool mBuildChildrenViews; // build root and children + bool mRootInited; + //-------------------------------------------------------------------- // Sorting @@ -374,6 +386,8 @@ protected: virtual LLFolderView * createFolderRoot(LLUUID root_id ); virtual LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); 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 @@ -382,7 +396,8 @@ private: LLInventoryObject const* objectp, LLFolderViewItem *target_view, LLFolderViewFolder *parent_folder_view, - const EBuildModes &mode); + const EBuildModes &mode, + S32 depth = -1); typedef enum e_views_initialization_state { @@ -398,6 +413,55 @@ private: std::deque mBuildViewsQueue; }; + +class LLInventorySingleFolderPanel : public LLInventoryPanel +{ +public: + struct Params : public LLInitParam::Block + {}; + + void initFromParams(const Params& p); + bool isSelectionRemovable() { return false; } + + void initFolderRoot(const LLUUID& start_folder_id = LLUUID::null); + + void changeFolderRoot(const LLUUID& new_id); + void onForwardFolder(); + void onBackwardFolder(); + void clearNavigationHistory(); + LLUUID getSingleFolderRoot() { return mFolderID; } + + void doCreate(const LLSD& userdata); + void doToSelected(const LLSD& userdata); + void doShare(); + + bool isBackwardAvailable(); + bool isForwardAvailable(); + + bool hasVisibleItems(); + + void setNavBackwardList(std::list backward_list) { mBackwardFolders = backward_list; } + void setNavForwardList(std::list forward_list) { mForwardFolders = forward_list; } + std::list getNavBackwardList() { return mBackwardFolders; } + std::list getNavForwardList() { return mForwardFolders; } + + typedef boost::function root_changed_callback_t; + boost::signals2::connection setRootChangedCallback(root_changed_callback_t cb); + +protected: + LLInventorySingleFolderPanel(const Params& params); + ~LLInventorySingleFolderPanel(); + void updateSingleFolderRoot(); + + friend class LLUICtrlFactory; + + LLUUID mFolderID; + std::list mBackwardFolders; + std::list mForwardFolders; + + boost::signals2::signal mRootChangedSignal; +}; + /************************************************************************/ /* Asset Pre-Filtered Inventory Panel related class */ /* Exchanges filter's flexibility for speed of generation and */ @@ -417,7 +481,7 @@ public: void initFromParams(const Params& p); protected: - LLAssetFilteredInventoryPanel(const Params& p) : LLInventoryPanel(p) {} + LLAssetFilteredInventoryPanel(const Params& p); friend class LLUICtrlFactory; public: ~LLAssetFilteredInventoryPanel() {} diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index 417d11e179..b879ce88bc 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -87,6 +87,7 @@ LLLoginInstance::LLLoginInstance() : mLoginModule(new LLLogin()), mNotifications(NULL), mLoginState("offline"), + mSaveMFA(true), mAttemptComplete(false), mTransferRate(0.0f), mDispatcher("LLLoginInstance", "change") @@ -465,10 +466,7 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) gViewerWindow->setShowProgress(FALSE, FALSE); } - LLSD args(llsd::map( "MESSAGE", LLTrans::getString(response["message_id"]) )); - LLSD payload; - LLNotificationsUtil::add("PromptMFAToken", args, payload, - boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + showMFAChallange(LLTrans::getString(response["message_id"])); } else if( reason_response == "key" || reason_response == "presence" @@ -556,10 +554,7 @@ bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key) { // SL-18511 this TOS failure happened while we are in the middle of an MFA challenge/response. // the previously entered token is very likely expired, so prompt again - LLSD args(llsd::map( "MESSAGE", LLTrans::getString("LoginFailedAuthenticationMFARequired") )); - LLSD payload; - LLNotificationsUtil::add("PromptMFAToken", args, payload, - boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + showMFAChallange(LLTrans::getString("LoginFailedAuthenticationMFARequired")); } else { @@ -577,6 +572,22 @@ bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key) return true; } +void LLLoginInstance::showMFAChallange(const std::string& message) +{ + LLSD args(llsd::map("MESSAGE", message)); + LLSD payload; + if (gSavedSettings.getBOOL("RememberUser")) + { + LLNotificationsUtil::add("PromptMFATokenWithSave", args, payload, + boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + } + else + { + LLNotificationsUtil::add("PromptMFAToken", args, payload, + boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + } +} + bool LLLoginInstance::handleMFAChallenge(LLSD const & notif, LLSD const & response) { bool continue_clicked = response["continue"].asBoolean(); @@ -592,6 +603,7 @@ bool LLLoginInstance::handleMFAChallenge(LLSD const & notif, LLSD const & respon // Set the request data to true and retry login. mRequestData["params"]["token"] = token; + mSaveMFA = response.has("ignore") ? response["ignore"].asBoolean() : false; reconnect(); } else { LL_INFOS("LLLogin") << "PromptMFAToken: no token, attemptComplete" << LL_ENDL; diff --git a/indra/newview/lllogininstance.h b/indra/newview/lllogininstance.h index 100f21101f..9466c1e083 100644 --- a/indra/newview/lllogininstance.h +++ b/indra/newview/lllogininstance.h @@ -56,6 +56,7 @@ public: bool authSuccess() { return mAttemptComplete && mLoginState == "online"; } const std::string& getLoginState() { return mLoginState; } + bool saveMFA() const { return mSaveMFA; } LLSD getResponse(const std::string& key) { return getResponse()[key]; } LLSD getResponse(); // @@ -87,6 +88,7 @@ private: void syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response); bool handleTOSResponse(bool v, const std::string& key); + void showMFAChallange(const std::string& message); bool handleMFAChallenge(LLSD const & notif, LLSD const & response); void attemptComplete() { mAttemptComplete = true; } // In the future an event? @@ -98,6 +100,7 @@ private: LLSD mRequestData; LLSD mResponseData; bool mAttemptComplete; + bool mSaveMFA; F64 mTransferRate; std::string mSerialNumber; int mLastExecEvent; diff --git a/indra/newview/llmarketplacefunctions.cpp b/indra/newview/llmarketplacefunctions.cpp index 09fa993fc6..18a3423ae9 100644 --- a/indra/newview/llmarketplacefunctions.cpp +++ b/indra/newview/llmarketplacefunctions.cpp @@ -701,10 +701,9 @@ void LLMarketplaceInventoryObserver::onIdleProcessQueue(void *userdata) // If it's a folder known to the marketplace, let's check it's in proper shape if (LLMarketplaceData::instance().isListed(*id_it) || LLMarketplaceData::instance().isVersionFolder(*id_it)) { - LLInventoryCategory* cat = (LLInventoryCategory*)(obj); // can trigger notifyObservers // can cause more structural changes - validate_marketplacelistings(cat); + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(obj->getUUID()); } } else @@ -898,7 +897,7 @@ void LLMarketplaceData::setDataFetchedSignal(const status_updated_signal_t::slot // Get/Post/Put requests to the SLM Server using the SLM API void LLMarketplaceData::getSLMListings() { - const LLUUID marketplaceFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplaceFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); setUpdating(marketplaceFolderId, true); LLCoros::instance().launch("getSLMListings", @@ -1805,7 +1804,7 @@ bool LLMarketplaceData::isUpdating(const LLUUID& folder_id, S32 depth) } else { - const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); std::set::iterator it = mPendingUpdateSet.find(marketplace_listings_uuid); if (it != mPendingUpdateSet.end()) { @@ -1849,8 +1848,7 @@ void LLMarketplaceData::decrementValidationWaiting(const LLUUID& folder_id, S32 if (found->second <= 0) { mValidationWaitingList.erase(found); - LLInventoryCategory *cat = gInventory.getCategory(folder_id); - validate_marketplacelistings(cat); + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(folder_id); update_marketplace_category(folder_id); gInventory.notifyObservers(); } diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp index 14eebf40f3..de988555c5 100644 --- a/indra/newview/lloutfitgallery.cpp +++ b/indra/newview/lloutfitgallery.cpp @@ -36,12 +36,11 @@ #include "llaccordionctrltab.h" #include "llappearancemgr.h" -#include "llagentbenefits.h" #include "llerror.h" #include "llfilepicker.h" #include "llfloaterperms.h" #include "llfloaterreg.h" -#include "llfloatersimpleoutfitsnapshot.h" +#include "llfloatersimplesnapshot.h" #include "llimagedimensionsinfo.h" #include "llinventoryfunctions.h" #include "llinventorymodel.h" @@ -65,7 +64,6 @@ const S32 GALLERY_ITEMS_PER_ROW_MIN = 2; LLOutfitGallery::LLOutfitGallery(const LLOutfitGallery::Params& p) : LLOutfitListBase(), - mTexturesObserver(NULL), mOutfitsObserver(NULL), mScrollPanel(NULL), mGalleryPanel(NULL), @@ -125,7 +123,6 @@ void LLOutfitGallery::onOpen(const LLSD& info) LLOutfitListBase::onOpen(info); if (!mGalleryCreated) { - loadPhotos(); uuid_vec_t cats; getCurrentCategories(cats); int n = cats.size(); @@ -149,6 +146,264 @@ void LLOutfitGallery::draw() } } +BOOL LLOutfitGallery::handleKeyHere(KEY key, MASK mask) +{ + BOOL handled = FALSE; + switch (key) + { + case KEY_RETURN: + // Open selected items if enter key hit on the inventory panel + if (mask == MASK_NONE && mSelectedOutfitUUID.notNull()) + { + // Or should it wearSelectedOutfit? + getSelectedItem()->openOutfitsContent(); + } + handled = TRUE; + break; + case KEY_DELETE: +#if LL_DARWIN + case KEY_BACKSPACE: +#endif + // Delete selected items if delete or backspace key hit on the inventory panel + // Note: on Mac laptop keyboards, backspace and delete are one and the same + if (mSelectedOutfitUUID.notNull()) + { + onRemoveOutfit(mSelectedOutfitUUID); + } + handled = TRUE; + break; + + case KEY_F2: + LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID); + handled = TRUE; + break; + + case KEY_PAGE_UP: + if (mScrollPanel) + { + mScrollPanel->pageUp(30); + } + handled = TRUE; + break; + + case KEY_PAGE_DOWN: + if (mScrollPanel) + { + mScrollPanel->pageDown(30); + } + handled = TRUE; + break; + + case KEY_HOME: + if (mScrollPanel) + { + mScrollPanel->goToTop(); + } + handled = TRUE; + break; + + case KEY_END: + if (mScrollPanel) + { + mScrollPanel->goToBottom(); + } + handled = TRUE; + break; + + case KEY_LEFT: + moveLeft(); + handled = TRUE; + break; + + case KEY_RIGHT: + moveRight(); + handled = TRUE; + break; + + case KEY_UP: + moveUp(); + handled = TRUE; + break; + + case KEY_DOWN: + moveDown(); + handled = TRUE; + break; + + default: + break; + } + + if (handled) + { + mOutfitGalleryMenu->hide(); + } + + return handled; +} + +void LLOutfitGallery::moveUp() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + S32 n = mItemIndexMap[item]; + n -= mItemsInRow; + if (n >= 0) + { + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(TRUE); + + scrollToShowItem(mSelectedOutfitUUID); + } + } + } +} + +void LLOutfitGallery::moveDown() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + S32 n = mItemIndexMap[item]; + n += mItemsInRow; + if (n < mItemsAddedCount) + { + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(TRUE); + + scrollToShowItem(mSelectedOutfitUUID); + } + } + } +} + +void LLOutfitGallery::moveLeft() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + // Might be better to get item from panel + S32 n = mItemIndexMap[item]; + n--; + if (n < 0) + { + n = mItemsAddedCount - 1; + } + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(TRUE); + + scrollToShowItem(mSelectedOutfitUUID); + } + } +} + +void LLOutfitGallery::moveRight() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + S32 n = mItemIndexMap[item]; + n++; + if (n == mItemsAddedCount) + { + n = 0; + } + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(TRUE); + + scrollToShowItem(mSelectedOutfitUUID); + } + } +} + +void LLOutfitGallery::onFocusLost() +{ + LLOutfitListBase::onFocusLost(); + + if (mSelectedOutfitUUID.notNull()) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + item->setSelected(false); + } + } +} + +void LLOutfitGallery::onFocusReceived() +{ + LLOutfitListBase::onFocusReceived(); + + if (mSelectedOutfitUUID.notNull()) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + item->setSelected(true); + } + } +} + +void LLOutfitGallery::onRemoveOutfit(const LLUUID& outfit_cat_id) +{ + LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(onOutfitsRemovalConfirmation, _1, _2, outfit_cat_id)); +} + +void LLOutfitGallery::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + if (outfit_cat_id.notNull()) + { + gInventory.removeCategory(outfit_cat_id); + } +} + +void LLOutfitGallery::scrollToShowItem(const LLUUID& item_id) +{ + LLOutfitGalleryItem* item = mOutfitMap[item_id]; + if (item) + { + const LLRect visible_content_rect = mScrollPanel->getVisibleContentRect(); + + LLRect item_rect; + item->localRectToOtherView(item->getLocalRect(), &item_rect, mScrollPanel); + LLRect overlap_rect(item_rect); + overlap_rect.intersectWith(visible_content_rect); + + //Scroll when the selected item is outside the visible area + if (overlap_rect.getHeight() + 5 < item->getRect().getHeight()) + { + LLRect content_rect = mScrollPanel->getContentWindowRect(); + LLRect constraint_rect; + constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); + + LLRect item_doc_rect; + item->localRectToOtherView(item->getLocalRect(), &item_doc_rect, mGalleryPanel); + + mScrollPanel->scrollToShowRect(item_doc_rect, constraint_rect); + } + } +} + void LLOutfitGallery::updateRowsIfNeeded() { if(((getRect().getWidth() - mRowPanelWidth) > mItemWidth) && mRowCount > 1) @@ -269,8 +524,9 @@ void LLOutfitGallery::addToGallery(LLOutfitGalleryItem* item) mHiddenItems.push_back(item); return; } + mItemIndexMap[item] = mItemsAddedCount; + mIndexToItemMap[mItemsAddedCount] = item; mItemsAddedCount++; - mItemIndexMap[item] = mItemsAddedCount - 1; int n = mItemsAddedCount; int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; int n_prev = n - 1; @@ -306,6 +562,7 @@ void LLOutfitGallery::removeFromGalleryLast(LLOutfitGalleryItem* item) int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; mItemsAddedCount--; + mIndexToItemMap.erase(mItemsAddedCount); bool remove_row = row_count != row_count_prev; removeFromLastRow(mItems[mItemsAddedCount]); @@ -331,6 +588,7 @@ void LLOutfitGallery::removeFromGalleryMiddle(LLOutfitGalleryItem* item) } int n = mItemIndexMap[item]; mItemIndexMap.erase(item); + mIndexToItemMap.erase(n); std::vector saved; for (int i = mItemsAddedCount - 1; i > n; i--) { @@ -364,9 +622,15 @@ LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID gitem->setFollowsTop(); gitem->setOutfitName(name); gitem->setUUID(outfit_id); + gitem->setGallery(this); return gitem; } +LLOutfitGalleryItem* LLOutfitGallery::getSelectedItem() +{ + return mOutfitMap[mSelectedOutfitUUID]; +} + void LLOutfitGallery::buildGalleryPanel(int row_count) { LLPanel::Params params; @@ -440,12 +704,6 @@ void LLOutfitGallery::moveRowPanel(LLPanel* stack, int left, int bottom) LLOutfitGallery::~LLOutfitGallery() { delete mOutfitGalleryMenu; - - if (gInventory.containsObserver(mTexturesObserver)) - { - gInventory.removeObserver(mTexturesObserver); - } - delete mTexturesObserver; if (gInventory.containsObserver(mOutfitsObserver)) { @@ -617,6 +875,7 @@ void LLOutfitGallery::onChangeOutfitSelection(LLWearableItemsList* list, const L { mOutfitMap[category_id]->setSelected(TRUE); } + // mSelectedOutfitUUID will be set in LLOutfitListBase::ChangeOutfitSelection } void LLOutfitGallery::wearSelectedOutfit() @@ -668,7 +927,8 @@ static LLDefaultChildRegistry::Register r("outfit_gallery_i LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p) : LLPanel(p), - mTexturep(NULL), + mGallery(nullptr), + mTexturep(nullptr), mSelected(false), mWorn(false), mDefaultImage(true), @@ -743,10 +1003,13 @@ 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(mSelected ? "White" : (mWorn ? "OutfitGalleryItemWorn" : "White"), LLColor4::white); + LLUIColor text_color = LLUIColorTable::instance().getColor("White", LLColor4::white); mOutfitWornText->setReadOnlyColor(text_color.get()); mOutfitNameText->setReadOnlyColor(text_color.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 } void LLOutfitGalleryItem::setSelected(bool value) @@ -769,9 +1032,64 @@ BOOL LLOutfitGalleryItem::handleRightMouseDown(S32 x, S32 y, MASK mask) } BOOL LLOutfitGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + return openOutfitsContent() || LLPanel::handleDoubleClick(x, y, mask); +} + +BOOL LLOutfitGalleryItem::handleKeyHere(KEY key, MASK mask) +{ + if (!mGallery) + { + return FALSE; + } + + BOOL handled = FALSE; + switch (key) + { + case KEY_LEFT: + mGallery->moveLeft(); + handled = true; + break; + + case KEY_RIGHT: + mGallery->moveRight(); + handled = true; + break; + + case KEY_UP: + mGallery->moveUp(); + handled = true; + break; + + case KEY_DOWN: + mGallery->moveDown(); + handled = true; + break; + + default: + break; + } + return handled; +} + +void LLOutfitGalleryItem::onFocusLost() +{ + setSelected(false); + + LLPanel::onFocusLost(); +} + +void LLOutfitGalleryItem::onFocusReceived() +{ + setSelected(true); + + LLPanel::onFocusReceived(); +} + +bool LLOutfitGalleryItem::openOutfitsContent() { LLTabContainer* appearence_tabs = LLPanelOutfitsInventory::findInstance()->getChild("appearance_tabs"); - if (appearence_tabs && (mUUID != LLUUID())) + if (appearence_tabs && mUUID.notNull()) { appearence_tabs->selectTabByName("outfitslist_tab"); LLPanel* panel = appearence_tabs->getCurrentPanel(); @@ -784,12 +1102,11 @@ BOOL LLOutfitGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask) outfit_list->setSelectedOutfitByUUID(mUUID); LLAccordionCtrlTab* tab = accordion->getSelectedTab(); tab->showAndFocusHeader(); - return TRUE; + return true; } } } - - return LLPanel::handleDoubleClick(x, y, mask); + return false; } bool LLOutfitGalleryItem::setImageAssetId(LLUUID image_asset_id) @@ -835,68 +1152,22 @@ LLContextMenu* LLOutfitGalleryContextMenu::createMenu() boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id)); registrar.add("Outfit.Edit", boost::bind(editOutfit)); registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); - registrar.add("Outfit.Delete", boost::bind(&LLOutfitGalleryContextMenu::onRemoveOutfit, this, selected_id)); + registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id)); registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2)); - registrar.add("Outfit.UploadPhoto", boost::bind(&LLOutfitGalleryContextMenu::onUploadPhoto, this, selected_id)); - registrar.add("Outfit.SelectPhoto", boost::bind(&LLOutfitGalleryContextMenu::onSelectPhoto, this, selected_id)); - registrar.add("Outfit.TakeSnapshot", boost::bind(&LLOutfitGalleryContextMenu::onTakeSnapshot, this, selected_id)); - registrar.add("Outfit.RemovePhoto", boost::bind(&LLOutfitGalleryContextMenu::onRemovePhoto, this, selected_id)); + registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitGalleryContextMenu::onThumbnail, 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)); return createFromFile("menu_gallery_outfit_tab.xml"); } -void LLOutfitGalleryContextMenu::onUploadPhoto(const LLUUID& outfit_cat_id) +void LLOutfitGalleryContextMenu::onThumbnail(const LLUUID& outfit_cat_id) { LLOutfitGallery* gallery = dynamic_cast(mOutfitList); if (gallery && outfit_cat_id.notNull()) { - gallery->uploadPhoto(outfit_cat_id); - } -} - -void LLOutfitGalleryContextMenu::onSelectPhoto(const LLUUID& outfit_cat_id) -{ - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - if (gallery && outfit_cat_id.notNull()) - { - gallery->onSelectPhoto(outfit_cat_id); - } -} - -void LLOutfitGalleryContextMenu::onRemovePhoto(const LLUUID& outfit_cat_id) -{ - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - if (gallery && outfit_cat_id.notNull()) - { - gallery->checkRemovePhoto(outfit_cat_id); - gallery->refreshOutfit(outfit_cat_id); - } -} - -void LLOutfitGalleryContextMenu::onTakeSnapshot(const LLUUID& outfit_cat_id) -{ - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - if (gallery && outfit_cat_id.notNull()) - { - gallery->onTakeSnapshot(outfit_cat_id); - } -} - -void LLOutfitGalleryContextMenu::onRemoveOutfit(const LLUUID& outfit_cat_id) -{ - LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(&LLOutfitGalleryContextMenu::onOutfitsRemovalConfirmation, this, _1, _2, outfit_cat_id)); -} - -void LLOutfitGalleryContextMenu::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return; // canceled - - if (outfit_cat_id.notNull()) - { - gInventory.removeCategory(outfit_cat_id); + LLSD data(outfit_cat_id); + LLFloaterReg::showInstance("change_item_thumbnail", data); } } @@ -919,16 +1190,6 @@ bool LLOutfitGalleryContextMenu::onEnable(LLSD::String param) bool LLOutfitGalleryContextMenu::onVisible(LLSD::String param) { - mMenuHandle.get()->getChild("upload_photo")->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost())); - if ("remove_photo" == param) - { - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - LLUUID selected_id = mUUIDs.front(); - if (gallery && selected_id.notNull()) - { - return !gallery->hasDefaultImage(selected_id); - } - } return LLOutfitContextMenu::onVisible(param); } @@ -943,56 +1204,12 @@ void LLOutfitGalleryGearMenu::onUpdateItemsVisibility() bool have_selection = getSelectedOutfitID().notNull(); mMenu->setItemVisible("expand", FALSE); mMenu->setItemVisible("collapse", FALSE); - mMenu->setItemVisible("upload_photo", have_selection); - mMenu->setItemVisible("select_photo", have_selection); - mMenu->setItemVisible("take_snapshot", have_selection); - mMenu->setItemVisible("remove_photo", !hasDefaultImage()); + mMenu->setItemVisible("thumbnail", have_selection); mMenu->setItemVisible("sepatator3", TRUE); mMenu->setItemVisible("sort_folders_by_name", TRUE); LLOutfitListGearMenuBase::onUpdateItemsVisibility(); } -void LLOutfitGalleryGearMenu::onUploadFoto() -{ - LLUUID selected_outfit_id = getSelectedOutfitID(); - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - if (gallery && selected_outfit_id.notNull()) - { - gallery->uploadPhoto(selected_outfit_id); - } -} - -void LLOutfitGalleryGearMenu::onSelectPhoto() -{ - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - LLUUID selected_outfit_id = getSelectedOutfitID(); - if (gallery && !selected_outfit_id.isNull()) - { - gallery->onSelectPhoto(selected_outfit_id); - } -} - -void LLOutfitGalleryGearMenu::onRemovePhoto() -{ - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - LLUUID selected_outfit_id = getSelectedOutfitID(); - if (gallery && !selected_outfit_id.isNull()) - { - gallery->checkRemovePhoto(selected_outfit_id); - gallery->refreshOutfit(selected_outfit_id); - } -} - -void LLOutfitGalleryGearMenu::onTakeSnapshot() -{ - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - LLUUID selected_outfit_id = getSelectedOutfitID(); - if (gallery && !selected_outfit_id.isNull()) - { - gallery->onTakeSnapshot(selected_outfit_id); - } -} - void LLOutfitGalleryGearMenu::onChangeSortOrder() { bool sort_by_name = !gSavedSettings.getBOOL("OutfitGallerySortByName"); @@ -1019,104 +1236,82 @@ void LLOutfitGallery::onTextureSelectionChanged(LLInventoryItem* itemp) { } -void LLOutfitGallery::loadPhotos() -{ - //Iterate over inventory - mSnapshotFolderID = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); - LLViewerInventoryCategory* textures_category = gInventory.getCategory(mSnapshotFolderID); - if (!textures_category) - return; - if (mTexturesObserver == NULL) - { - mTexturesObserver = new LLInventoryCategoriesObserver(); - gInventory.addObserver(mTexturesObserver); - } - - // Start observing changes in "Textures" category. - mTexturesObserver->addCategory(mSnapshotFolderID, - boost::bind(&LLOutfitGallery::refreshTextures, this, mSnapshotFolderID)); - - textures_category->fetch(); -} - -void LLOutfitGallery::updateSnapshotFolderObserver() -{ - if(mSnapshotFolderID != gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE)) - { - if (gInventory.containsObserver(mTexturesObserver)) - { - gInventory.removeObserver(mTexturesObserver); - } - delete mTexturesObserver; - mTexturesObserver = NULL; - loadPhotos(); - } -} - void LLOutfitGallery::refreshOutfit(const LLUUID& category_id) { LLViewerInventoryCategory* category = gInventory.getCategory(category_id); if (category) { bool photo_loaded = false; - LLInventoryModel::cat_array_t sub_cat_array; - LLInventoryModel::item_array_t outfit_item_array; - // Collect all sub-categories of a given category. - gInventory.collectDescendents( - category->getUUID(), - sub_cat_array, - outfit_item_array, - LLInventoryModel::EXCLUDE_TRASH); - BOOST_FOREACH(LLViewerInventoryItem* outfit_item, outfit_item_array) + LLUUID asset_id = category->getThumbnailUUID(); + if (asset_id.isNull()) { - LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); - LLUUID asset_id, inv_id; - std::string item_name; - if (linked_item != NULL) + LLInventoryModel::cat_array_t sub_cat_array; + LLInventoryModel::item_array_t outfit_item_array; + // Collect all sub-categories of a given category. + gInventory.collectDescendents( + category->getUUID(), + sub_cat_array, + outfit_item_array, + LLInventoryModel::EXCLUDE_TRASH); + BOOST_FOREACH(LLViewerInventoryItem* outfit_item, outfit_item_array) { - if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) + LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); + LLUUID asset_id, inv_id; + std::string item_name; + if (linked_item != NULL) { - asset_id = linked_item->getAssetUUID(); - inv_id = linked_item->getUUID(); - item_name = linked_item->getName(); - } - } - else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) - { - asset_id = outfit_item->getAssetUUID(); - inv_id = outfit_item->getUUID(); - item_name = outfit_item->getName(); - } - if (asset_id.notNull()) - { - photo_loaded |= mOutfitMap[category_id]->setImageAssetId(asset_id); - // Rename links - if (!mOutfitRenamePending.isNull() && mOutfitRenamePending.asString() == item_name) - { - LLViewerInventoryCategory *outfit_cat = gInventory.getCategory(mOutfitRenamePending); - LLStringUtil::format_map_t photo_string_args; - photo_string_args["OUTFIT_NAME"] = outfit_cat->getName(); - std::string new_name = getString("outfit_photo_string", photo_string_args); - LLSD updates; - updates["name"] = new_name; - update_inventory_item(inv_id, updates, NULL); - mOutfitRenamePending.setNull(); - LLFloater* appearance_floater = LLFloaterReg::getInstance("appearance"); - if (appearance_floater) + if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) { - appearance_floater->setFocus(TRUE); + asset_id = linked_item->getAssetUUID(); + inv_id = linked_item->getUUID(); + item_name = linked_item->getName(); } } - if (item_name == LLAppearanceMgr::sExpectedTextureName) + else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) { - // Images with "appropriate" name take priority - break; + asset_id = outfit_item->getAssetUUID(); + inv_id = outfit_item->getUUID(); + item_name = outfit_item->getName(); + } + if (category->getThumbnailUUID().notNull()) + { + asset_id = category->getThumbnailUUID(); + } + if (asset_id.notNull()) + { + photo_loaded |= mOutfitMap[category_id]->setImageAssetId(asset_id); + // Rename links + if (!mOutfitRenamePending.isNull() && mOutfitRenamePending.asString() == item_name) + { + LLViewerInventoryCategory *outfit_cat = gInventory.getCategory(mOutfitRenamePending); + LLStringUtil::format_map_t photo_string_args; + photo_string_args["OUTFIT_NAME"] = outfit_cat->getName(); + std::string new_name = getString("outfit_photo_string", photo_string_args); + LLSD updates; + updates["name"] = new_name; + update_inventory_item(inv_id, updates, NULL); + mOutfitRenamePending.setNull(); + LLFloater* appearance_floater = LLFloaterReg::getInstance("appearance"); + if (appearance_floater) + { + appearance_floater->setFocus(TRUE); + } + } + if (item_name == LLAppearanceMgr::sExpectedTextureName) + { + // Images with "appropriate" name take priority + break; + } + } + if (!photo_loaded) + { + mOutfitMap[category_id]->setDefaultImage(); } } - if (!photo_loaded) - { - mOutfitMap[category_id]->setDefaultImage(); - } + } + else + { + mOutfitMap[category_id]->setImageAssetId(asset_id); } } @@ -1126,135 +1321,6 @@ void LLOutfitGallery::refreshOutfit(const LLUUID& category_id) } } -// Refresh linked textures from "textures" uploads folder -void LLOutfitGallery::refreshTextures(const LLUUID& category_id) -{ - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - - // Collect all sub-categories of a given category. - LLIsType is_texture(LLAssetType::AT_TEXTURE); - gInventory.collectDescendentsIf( - category_id, - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - is_texture); - - //Find texture which contain pending outfit ID string in name - LLViewerInventoryItem* photo_upload_item = NULL; - BOOST_FOREACH(LLViewerInventoryItem* item, item_array) - { - std::string name = item->getName(); - if (!mOutfitLinkPending.isNull() && name == mOutfitLinkPending.asString()) - { - photo_upload_item = item; - break; - } - } - - if (photo_upload_item != NULL) - { - LLUUID photo_item_id = photo_upload_item->getUUID(); - LLInventoryObject* upload_object = gInventory.getObject(photo_item_id); - if (!upload_object) - { - LL_WARNS() << "LLOutfitGallery::refreshTextures added_object is null!" << LL_ENDL; - } - else - { - linkPhotoToOutfit(photo_item_id, mOutfitLinkPending); - mOutfitRenamePending = mOutfitLinkPending; - mOutfitLinkPending.setNull(); - } - } -} - -void LLOutfitGallery::uploadPhoto(LLUUID outfit_id) -{ - outfit_map_t::iterator outfit_it = mOutfitMap.find(outfit_id); - if (outfit_it == mOutfitMap.end() || outfit_it->first.isNull()) - { - return; - } - (new LLFilePickerReplyThread(boost::bind(&LLOutfitGallery::uploadOutfitImage, this, _1, outfit_id), LLFilePicker::FFLOAD_IMAGE, false))->getFile(); -} - -void LLOutfitGallery::uploadOutfitImage(const std::vector& filenames, LLUUID outfit_id) -{ - std::string filename = filenames[0]; - LLLocalBitmap* unit = new LLLocalBitmap(filename); - if (unit->getValid()) - { - std::string exten = gDirUtilp->getExtension(filename); - U32 codec = LLImageBase::getCodecFromExtension(exten); - - LLImageDimensionsInfo image_info; - std::string image_load_error; - if (!image_info.load(filename, codec)) - { - image_load_error = image_info.getLastError(); - } - - S32 max_width = MAX_OUTFIT_PHOTO_WIDTH; - S32 max_height = MAX_OUTFIT_PHOTO_HEIGHT; - - if ((image_info.getWidth() > max_width) || (image_info.getHeight() > max_height)) - { - LLStringUtil::format_map_t args; - args["WIDTH"] = llformat("%d", max_width); - args["HEIGHT"] = llformat("%d", max_height); - - image_load_error = LLTrans::getString("outfit_photo_load_dimensions_error", args); - } - - if (!image_load_error.empty()) - { - LLSD subst; - subst["REASON"] = image_load_error; - LLNotificationsUtil::add("OutfitPhotoLoadError", subst); - return; - } - - S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - void *nruserdata = NULL; - nruserdata = (void *)&outfit_id; - - LLViewerInventoryCategory *outfit_cat = gInventory.getCategory(outfit_id); - if (!outfit_cat) return; - updateSnapshotFolderObserver(); - checkRemovePhoto(outfit_id); - std::string upload_pending_name = outfit_id.asString(); - std::string upload_pending_desc = ""; - upload_new_resource(filename, // file - upload_pending_name, - upload_pending_desc, - 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, - LLFloaterPerms::getNextOwnerPerms("Uploads"), - LLFloaterPerms::getGroupPerms("Uploads"), - LLFloaterPerms::getEveryonePerms("Uploads"), - upload_pending_name, LLAssetStorage::LLStoreAssetCallback(), expected_upload_cost, nruserdata, false); - mOutfitLinkPending = outfit_id; - } - delete unit; -} - -void LLOutfitGallery::linkPhotoToOutfit(LLUUID photo_id, LLUUID outfit_id) -{ - LLPointer cb = new LLUpdateGalleryOnPhotoLinked(); - link_inventory_object(outfit_id, photo_id, cb); -} - -bool LLOutfitGallery::checkRemovePhoto(LLUUID outfit_id) -{ - LLAppearanceMgr::instance().removeOutfitPhoto(outfit_id); - return true; -} - -void LLUpdateGalleryOnPhotoLinked::fire(const LLUUID& inv_item_id) -{ -} - LLUUID LLOutfitGallery::getPhotoAssetId(const LLUUID& outfit_id) { outfit_map_t::iterator outfit_it = mOutfitMap.find(outfit_id); @@ -1270,151 +1336,3 @@ LLUUID LLOutfitGallery::getDefaultPhoto() return LLUUID(); } -void LLOutfitGallery::onTexturePickerCommit(LLTextureCtrl::ETexturePickOp op, LLUUID id) -{ - LLUUID selected_outfit_id = getSelectedOutfitUUID(); - - if (selected_outfit_id.isNull()) - { - return; - } - - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - - if (floaterp && op == LLTextureCtrl::TEXTURE_SELECT) - { - LLUUID image_item_id; - if (id.notNull()) - { - image_item_id = id; - } - else - { - image_item_id = floaterp->findItemID(floaterp->getAssetID(), FALSE, TRUE); - if (image_item_id.isNull()) - { - LL_WARNS() << "id or image_item_id is NULL!" << LL_ENDL; - return; - } - } - - std::string image_load_error; - S32 max_width = MAX_OUTFIT_PHOTO_WIDTH; - S32 max_height = MAX_OUTFIT_PHOTO_HEIGHT; - if (mTextureSelected.isNull() || - mTextureSelected->getFullWidth() == 0 || - mTextureSelected->getFullHeight() == 0) - { - image_load_error = LLTrans::getString("outfit_photo_verify_dimensions_error"); - LL_WARNS() << "Cannot verify selected texture dimensions" << LL_ENDL; - return; - } - S32 width = mTextureSelected->getFullWidth(); - S32 height = mTextureSelected->getFullHeight(); - if ((width > max_width) || (height > max_height)) - { - LLStringUtil::format_map_t args; - args["WIDTH"] = llformat("%d", max_width); - args["HEIGHT"] = llformat("%d", max_height); - - image_load_error = LLTrans::getString("outfit_photo_select_dimensions_error", args); - } - - if (!image_load_error.empty()) - { - LLSD subst; - subst["REASON"] = image_load_error; - LLNotificationsUtil::add("OutfitPhotoLoadError", subst); - return; - } - - checkRemovePhoto(selected_outfit_id); - linkPhotoToOutfit(image_item_id, selected_outfit_id); - } -} - -void LLOutfitGallery::onSelectPhoto(LLUUID selected_outfit_id) -{ - if (selected_outfit_id.notNull()) - { - - // show hourglass cursor when loading inventory window - // because inventory construction is slooow - getWindow()->setCursor(UI_CURSOR_WAIT); - LLFloater* floaterp = mFloaterHandle.get(); - - // Show the dialog - if (floaterp) - { - floaterp->openFloater(); - } - else - { - floaterp = new LLFloaterTexturePicker( - this, - getPhotoAssetId(selected_outfit_id), - getPhotoAssetId(selected_outfit_id), - getPhotoAssetId(selected_outfit_id), - FALSE, - TRUE, - LLTrans::getString("TexturePickerOutfitHeader"), // "SELECT PHOTO", // Localizable floater header - PERM_NONE, - PERM_NONE, - PERM_NONE, - FALSE, - NULL); - - mFloaterHandle = floaterp->getHandle(); - mTextureSelected = NULL; - - LLFloaterTexturePicker* texture_floaterp = dynamic_cast(floaterp); - if (texture_floaterp) - { - texture_floaterp->setTextureSelectedCallback(boost::bind(&LLOutfitGallery::onTextureSelectionChanged, this, _1)); - texture_floaterp->setOnFloaterCommitCallback(boost::bind(&LLOutfitGallery::onTexturePickerCommit, this, _1, _2)); - texture_floaterp->setOnUpdateImageStatsCallback(boost::bind(&LLOutfitGallery::onTexturePickerUpdateImageStats, this, _1)); - texture_floaterp->setLocalTextureEnabled(FALSE); - texture_floaterp->setBakeTextureEnabled(FALSE); - texture_floaterp->setCanApply(false, true); - } - - floaterp->openFloater(); - } - floaterp->setFocus(TRUE); - } -} - -void LLOutfitGallery::onTakeSnapshot(LLUUID selected_outfit_id) -{ - LLFloaterReg::toggleInstanceOrBringToFront("simple_outfit_snapshot"); - LLFloaterSimpleOutfitSnapshot* snapshot_floater = LLFloaterSimpleOutfitSnapshot::getInstance(); - if (snapshot_floater) - { - snapshot_floater->setOutfitID(selected_outfit_id); - snapshot_floater->getInstance()->setGallery(this); - } -} - -void LLOutfitGallery::onBeforeOutfitSnapshotSave() -{ - LLUUID selected_outfit_id = getSelectedOutfitUUID(); - if (!selected_outfit_id.isNull()) - { - checkRemovePhoto(selected_outfit_id); - updateSnapshotFolderObserver(); - } -} - -void LLOutfitGallery::onAfterOutfitSnapshotSave() -{ - LLUUID selected_outfit_id = getSelectedOutfitUUID(); - if (!selected_outfit_id.isNull()) - { - mOutfitLinkPending = selected_outfit_id; - } -} - -void LLOutfitGallery::onTexturePickerUpdateImageStats(LLPointer texture) -{ - mTextureSelected = texture; -} diff --git a/indra/newview/lloutfitgallery.h b/indra/newview/lloutfitgallery.h index ce5c090134..9915752962 100644 --- a/indra/newview/lloutfitgallery.h +++ b/indra/newview/lloutfitgallery.h @@ -33,7 +33,6 @@ #include "lllayoutstack.h" #include "lloutfitslist.h" #include "llpanelappearancetab.h" -#include "lltexturectrl.h" #include "llviewertexture.h" #include @@ -44,15 +43,6 @@ class LLOutfitListGearMenuBase; class LLOutfitGalleryGearMenu; class LLOutfitGalleryContextMenu; -class LLUpdateGalleryOnPhotoLinked : public LLInventoryCallback -{ -public: - LLUpdateGalleryOnPhotoLinked(){} - virtual ~LLUpdateGalleryOnPhotoLinked(){} - /* virtual */ void fire(const LLUUID& inv_item_id); -private: -}; - class LLOutfitGallery : public LLOutfitListBase { public: @@ -83,10 +73,19 @@ public: /*virtual*/ BOOL postBuild(); /*virtual*/ void onOpen(const LLSD& info); - /*virtual*/ void draw(); - - void onSelectPhoto(LLUUID selected_outfit_id); - void onTakeSnapshot(LLUUID selected_outfit_id); + /*virtual*/ void draw(); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask); + void moveUp(); + void moveDown(); + void moveLeft(); + void moveRight(); + + /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived(); + + static void onRemoveOutfit(const LLUUID& outfit_cat_id); + static void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id); + void scrollToShowItem(const LLUUID& item_id); void wearSelectedOutfit(); @@ -106,14 +105,8 @@ public: void updateMessageVisibility(); bool hasDefaultImage(const LLUUID& outfit_cat_id); - void refreshTextures(const LLUUID& category_id); void refreshOutfit(const LLUUID& category_id); - void onTexturePickerCommit(LLTextureCtrl::ETexturePickOp op, LLUUID id); - void onTexturePickerUpdateImageStats(LLPointer texture); - void onBeforeOutfitSnapshotSave(); - void onAfterOutfitSnapshotSave(); - protected: /*virtual*/ void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id); /*virtual*/ void onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid); @@ -127,14 +120,8 @@ protected: void applyFilter(LLOutfitGalleryItem* item, const std::string& filter_substring); private: - void loadPhotos(); - void uploadPhoto(LLUUID outfit_id); - void uploadOutfitImage(const std::vector& filenames, LLUUID outfit_id); - void updateSnapshotFolderObserver(); LLUUID getPhotoAssetId(const LLUUID& outfit_id); LLUUID getDefaultPhoto(); - void linkPhotoToOutfit(LLUUID outfit_id, LLUUID photo_id); - bool checkRemovePhoto(LLUUID outfit_id); void addToGallery(LLOutfitGalleryItem* item); void removeFromGalleryLast(LLOutfitGalleryItem* item); void removeFromGalleryMiddle(LLOutfitGalleryItem* item); @@ -150,6 +137,7 @@ private: void updateGalleryWidth(); LLOutfitGalleryItem* buildGalleryItem(std::string name, LLUUID outfit_id); + LLOutfitGalleryItem* getSelectedItem(); void onTextureSelectionChanged(LLInventoryItem* itemp); @@ -190,17 +178,15 @@ private: LLListContextMenu* mOutfitGalleryMenu; - LLHandle mFloaterHandle; - typedef std::map outfit_map_t; typedef outfit_map_t::value_type outfit_map_value_t; outfit_map_t mOutfitMap; - typedef std::map item_num_map_t; + typedef std::map item_num_map_t; typedef item_num_map_t::value_type item_numb_map_value_t; item_num_map_t mItemIndexMap; + std::map mIndexToItemMap; - LLInventoryCategoriesObserver* mTexturesObserver; LLInventoryCategoriesObserver* mOutfitsObserver; }; class LLOutfitGalleryContextMenu : public LLOutfitContextMenu @@ -211,17 +197,13 @@ public: LLOutfitGalleryContextMenu(LLOutfitListBase* outfit_list) : LLOutfitContextMenu(outfit_list), mOutfitList(outfit_list){} + protected: /* virtual */ LLContextMenu* createMenu(); bool onEnable(LLSD::String param); bool onVisible(LLSD::String param); - void onUploadPhoto(const LLUUID& outfit_cat_id); - void onSelectPhoto(const LLUUID& outfit_cat_id); - void onRemovePhoto(const LLUUID& outfit_cat_id); - void onTakeSnapshot(const LLUUID& outfit_cat_id); + void onThumbnail(const LLUUID& outfit_cat_id); void onCreate(const LLSD& data); - void onRemoveOutfit(const LLUUID& outfit_cat_id); - void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id); private: LLOutfitListBase* mOutfitList; }; @@ -236,10 +218,6 @@ public: protected: /*virtual*/ void onUpdateItemsVisibility(); private: - /*virtual*/ void onUploadFoto(); - /*virtual*/ void onSelectPhoto(); - /*virtual*/ void onTakeSnapshot(); - /*virtual*/ void onRemovePhoto(); /*virtual*/ void onChangeSortOrder(); bool hasDefaultImage(); @@ -259,14 +237,21 @@ public: /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask); + /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived(); + bool openOutfitsContent(); + + void setGallery(LLOutfitGallery* gallery) { mGallery = gallery; } void setDefaultImage(); bool setImageAssetId(LLUUID asset_id); LLUUID getImageAssetId(); void setOutfitName(std::string name); void setOutfitWorn(bool value); void setSelected(bool value); - void setUUID(LLUUID outfit_id) {mUUID = outfit_id;} + void setUUID(const LLUUID &outfit_id) {mUUID = outfit_id;} + LLUUID getUUID() const { return mUUID; } std::string getItemName() {return mOutfitName;} bool isDefaultImage() {return mDefaultImage;} @@ -275,6 +260,7 @@ public: void setHidden(bool hidden) {mHidden = hidden;} private: + LLOutfitGallery* mGallery; LLPointer mTexturep; LLUUID mUUID; LLUUID mImageAssetId; diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index c0074fd2f9..08ed61aab2 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -35,7 +35,7 @@ #include "llaccordionctrltab.h" #include "llagentwearables.h" #include "llappearancemgr.h" -#include "llagentbenefits.h" +#include "llfloaterreg.h" #include "llfloatersidepanelcontainer.h" #include "llinventoryfunctions.h" #include "llinventorymodel.h" @@ -130,9 +130,8 @@ void LLOutfitsList::onOpen(const LLSD& info) { if (!mIsInitialized) { - const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); // Start observing changes in Current Outfit category. - mCategoriesObserver->addCategory(cof, boost::bind(&LLOutfitsList::onCOFChanged, this)); + LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLOutfitsList::onCOFChanged, this)); } LLOutfitListBase::onOpen(info); @@ -1255,10 +1254,7 @@ LLOutfitListGearMenuBase::LLOutfitListGearMenuBase(LLOutfitListBase* olist) registrar.add("Gear.WearAdd", boost::bind(&LLOutfitListGearMenuBase::onAdd, this)); - registrar.add("Gear.UploadPhoto", boost::bind(&LLOutfitListGearMenuBase::onUploadFoto, this)); - registrar.add("Gear.SelectPhoto", boost::bind(&LLOutfitListGearMenuBase::onSelectPhoto, this)); - registrar.add("Gear.TakeSnapshot", boost::bind(&LLOutfitListGearMenuBase::onTakeSnapshot, this)); - registrar.add("Gear.RemovePhoto", boost::bind(&LLOutfitListGearMenuBase::onRemovePhoto, this)); + registrar.add("Gear.Thumbnail", boost::bind(&LLOutfitListGearMenuBase::onThumbnail, this)); registrar.add("Gear.SortByName", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this)); enable_registrar.add("Gear.OnEnable", boost::bind(&LLOutfitListGearMenuBase::onEnable, this, _2)); @@ -1375,7 +1371,6 @@ bool LLOutfitListGearMenuBase::onEnable(LLSD::String param) bool LLOutfitListGearMenuBase::onVisible(LLSD::String param) { - getMenu()->getChild("upload_photo")->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost())); const LLUUID& selected_outfit_id = getSelectedOutfitID(); if (selected_outfit_id.isNull()) // no selection or invalid outfit selected { @@ -1394,24 +1389,11 @@ bool LLOutfitListGearMenuBase::onVisible(LLSD::String param) return true; } -void LLOutfitListGearMenuBase::onUploadFoto() +void LLOutfitListGearMenuBase::onThumbnail() { - -} - -void LLOutfitListGearMenuBase::onSelectPhoto() -{ - -} - -void LLOutfitListGearMenuBase::onTakeSnapshot() -{ - -} - -void LLOutfitListGearMenuBase::onRemovePhoto() -{ - + const LLUUID& selected_outfit_id = getSelectedOutfitID(); + LLSD data(selected_outfit_id); + LLFloaterReg::showInstance("change_item_thumbnail", data); } void LLOutfitListGearMenuBase::onChangeSortOrder() @@ -1431,10 +1413,7 @@ void LLOutfitListGearMenu::onUpdateItemsVisibility() if (!mMenu) return; mMenu->setItemVisible("expand", TRUE); mMenu->setItemVisible("collapse", TRUE); - mMenu->setItemVisible("upload_photo", FALSE); - mMenu->setItemVisible("select_photo", FALSE); - mMenu->setItemVisible("take_snapshot", FALSE); - mMenu->setItemVisible("remove_photo", FALSE); + mMenu->setItemVisible("thumbnail", FALSE); // Never visible? mMenu->setItemVisible("sepatator3", FALSE); mMenu->setItemVisible("sort_folders_by_name", FALSE); LLOutfitListGearMenuBase::onUpdateItemsVisibility(); diff --git a/indra/newview/lloutfitslist.h b/indra/newview/lloutfitslist.h index 290caf552a..b85ee81c78 100644 --- a/indra/newview/lloutfitslist.h +++ b/indra/newview/lloutfitslist.h @@ -170,10 +170,7 @@ public: protected: virtual void onUpdateItemsVisibility(); - virtual void onUploadFoto(); - virtual void onSelectPhoto(); - virtual void onTakeSnapshot(); - virtual void onRemovePhoto(); + virtual void onThumbnail(); virtual void onChangeSortOrder(); const LLUUID& getSelectedOutfitID(); diff --git a/indra/newview/llpanellandmarkinfo.cpp b/indra/newview/llpanellandmarkinfo.cpp index 834e664723..cc3c51dd83 100644 --- a/indra/newview/llpanellandmarkinfo.cpp +++ b/indra/newview/llpanellandmarkinfo.cpp @@ -111,9 +111,9 @@ void LLPanelLandmarkInfo::setInfoType(EInfoType type) } // Sets CREATE_LANDMARK infotype and creates landmark at desired folder -void LLPanelLandmarkInfo::setInfoAndCreateLandmark(const LLUUID& fodler_id) +void LLPanelLandmarkInfo::setInfoAndCreateLandmark(const LLUUID& folder_id) { - setInfoType(CREATE_LANDMARK, fodler_id); + setInfoType(CREATE_LANDMARK, folder_id); } void LLPanelLandmarkInfo::setInfoType(EInfoType type, const LLUUID &folder_id) diff --git a/indra/newview/llpanellandmarkinfo.h b/indra/newview/llpanellandmarkinfo.h index 46e2a1935b..8802ce066e 100644 --- a/indra/newview/llpanellandmarkinfo.h +++ b/indra/newview/llpanellandmarkinfo.h @@ -48,7 +48,7 @@ public: /*virtual*/ void setInfoType(EInfoType type); // Sets CREATE_LANDMARK infotype and creates landmark at desired folder - void setInfoAndCreateLandmark(const LLUUID& fodler_id); + void setInfoAndCreateLandmark(const LLUUID& folder_id); /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 49d9c73ddc..626d9cc3c0 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -1112,11 +1112,6 @@ void LLPanelLogin::onRememberPasswordCheck(void*) std::string grid(LLGridManager::getInstance()->getGridId()); std::string user_id(cred->userID()); - if (!remember_password) - { - gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid, user_id); - gSecAPIHandler->syncProtectedMap(); - } } } diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index 8dfbbe45c3..bbc93385a9 100644 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -37,8 +37,8 @@ #include "llfilepicker.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" +#include "llinventorygallery.h" #include "llinventorymodelbackgroundfetch.h" -#include "llinventorypanel.h" #include "llfiltereditor.h" #include "llfloatersidepanelcontainer.h" #include "llfloaterreg.h" @@ -53,12 +53,14 @@ #include "llspinctrl.h" #include "lltoggleablemenu.h" #include "lltooldraganddrop.h" +#include "lltrans.h" #include "llviewermenu.h" #include "llviewertexturelist.h" #include "llsidepanelinventory.h" #include "llfolderview.h" #include "llradiogroup.h" #include "llenvironment.h" +#include "llweb.h" // #include "lltrans.h" @@ -128,7 +130,14 @@ LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) mMenuVisibility(NULL), mMenuAddHandle(), mNeedUploadCost(true), - mSearchTypeCombo(NULL) // Properly initialize this + mMenuViewDefault(NULL), + mSingleFolderMode(false), + mForceShowInvLayout(false), + mViewMode(MODE_COMBINATION), + mListViewRootUpdatedConnection(), + mGalleryRootUpdatedConnection(), + mViewMenuButton(nullptr), // Keep better inventory layout + mSearchTypeCombo(NULL) // Properly initialize this { // Menu Callbacks (non contex menus) mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLPanelMainInventory::doToSelected, this, _2)); @@ -139,7 +148,6 @@ LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) mCommitCallbackRegistrar.add("Inventory.ShowFilters", boost::bind(&LLPanelMainInventory::toggleFindOptions, this)); mCommitCallbackRegistrar.add("Inventory.ResetFilters", boost::bind(&LLPanelMainInventory::resetFilters, this)); //mCommitCallbackRegistrar.add("Inventory.SetSortBy", boost::bind(&LLPanelMainInventory::setSortBy, this, _2)); // Sort By menu handlers - mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); mEnableCallbackRegistrar.add("Inventory.EnvironmentEnabled", [](LLUICtrl *, const LLSD &) { return LLPanelMainInventory::hasSettingsInventory(); }); @@ -174,6 +182,13 @@ LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) mEnableCallbackRegistrar.add("Inventory.CoalescedObjects.Check", boost::bind(&LLPanelMainInventory::isCoalescedObjectsChecked, this, _2)); // + // Register all callback handlers early + mCommitCallbackRegistrar.add("Inventory.GearDefault.Custom.Action", boost::bind(&LLPanelMainInventory::onCustomAction, this, _2)); + mEnableCallbackRegistrar.add("Inventory.GearDefault.Check", boost::bind(&LLPanelMainInventory::isActionChecked, this, _2)); + mEnableCallbackRegistrar.add("Inventory.GearDefault.Enable", boost::bind(&LLPanelMainInventory::isActionEnabled, this, _2)); + mEnableCallbackRegistrar.add("Inventory.GearDefault.Visible", boost::bind(&LLPanelMainInventory::isActionVisible, this, _2)); + // + mSavedFolderState = new LLSaveFolderState(); mSavedFolderState->setApply(FALSE); @@ -286,7 +301,7 @@ BOOL LLPanelMainInventory::postBuild() } // Now load the stored settings from disk, if available. std::string filterSaveName(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, FILTERS_FILENAME)); - LL_INFOS() << "LLPanelMainInventory::init: reading from " << filterSaveName << LL_ENDL; + LL_INFOS("Inventory") << "LLPanelMainInventory::init: reading from " << filterSaveName << LL_ENDL; llifstream file(filterSaveName.c_str()); LLSD savedFilterState; if (file.is_open()) @@ -348,6 +363,36 @@ BOOL LLPanelMainInventory::postBuild() mGearMenuButton = getChild("options_gear_btn"); mVisibilityMenuButton = getChild("options_visibility_btn"); + // Keep better inventory layout + //mViewMenuButton = getChild("view_btn"); + mViewMenuButton = findChild("view_btn"); + + mBackBtn = getChild("back_btn"); + mForwardBtn = getChild("forward_btn"); + mUpBtn = getChild("up_btn"); + mViewModeBtn = getChild("view_mode_btn"); + mNavigationBtnsPanel = getChild("nav_buttons"); + + mDefaultViewPanel = getChild("default_inventory_panel"); + mCombinationViewPanel = getChild("combination_view_inventory"); + mCombinationGalleryLayoutPanel = getChild("comb_gallery_layout"); + mCombinationListLayoutPanel = getChild("comb_inventory_layout"); + mCombinationLayoutStack = getChild("combination_view_stack"); + + mCombinationInventoryPanel = getChild("comb_single_folder_inv"); + LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); + comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_EXCLUDE_THUMBNAILS); + comb_inv_filter.markDefault(); + mCombinationInventoryPanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onCombinationInventorySelectionChanged, this, _1, _2)); + mListViewRootUpdatedConnection = mCombinationInventoryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::onCombinationRootChanged, this, false)); + + mCombinationGalleryPanel = getChild("comb_gallery_view_inv"); + mCombinationGalleryPanel->setSortOrder(mCombinationInventoryPanel->getSortOrder()); + LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); + comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_ONLY_THUMBNAILS); + comb_gallery_filter.markDefault(); + mGalleryRootUpdatedConnection = mCombinationGalleryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::onCombinationRootChanged, this, true)); + mCombinationGalleryPanel->setSelectionChangeCallback(boost::bind(&LLPanelMainInventory::onCombinationGallerySelectionChanged, this, _1)); initListCommandsHandlers(); const std::string texture_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost()); @@ -425,12 +470,21 @@ LLPanelMainInventory::~LLPanelMainInventory( void ) gInventory.removeObserver(this); delete mSavedFolderState; - auto menu = mMenuAddHandle.get(); - if(menu) - { - menu->die(); - mMenuAddHandle.markDead(); - } + auto menu = mMenuAddHandle.get(); + if(menu) + { + menu->die(); + mMenuAddHandle.markDead(); + } + + if (mListViewRootUpdatedConnection.connected()) + { + mListViewRootUpdatedConnection.disconnect(); + } + if (mGalleryRootUpdatedConnection.connected()) + { + mGalleryRootUpdatedConnection.disconnect(); + } } LLInventoryPanel* LLPanelMainInventory::getAllItemsPanel() @@ -487,6 +541,10 @@ BOOL LLPanelMainInventory::handleKeyHere(KEY key, MASK mask) { startSearch(); } + if(mSingleFolderMode && key == KEY_LEFT) + { + onBackFolderClicked(); + } } return LLPanel::handleKeyHere(key, mask); @@ -506,15 +564,78 @@ void LLPanelMainInventory::closeAllFolders() getPanel()->getRootFolder()->closeAllFolders(); } -void LLPanelMainInventory::newWindow() +S32 get_instance_num() { - static S32 instance_num = 0; - instance_num = (instance_num + 1) % S32_MAX; + static S32 instance_num = 0; + instance_num = (instance_num + 1) % S32_MAX; + + return instance_num; +} + +LLFloaterSidePanelContainer* LLPanelMainInventory::newWindow() +{ + S32 instance_num = get_instance_num(); if (!gAgentCamera.cameraMouselook()) { - LLFloaterReg::showTypedInstance("inventory", LLSD(instance_num)); + LLFloaterSidePanelContainer* floater = LLFloaterReg::showTypedInstance("inventory", LLSD(instance_num)); + LLSidepanelInventory* sidepanel_inventory = floater->findChild("main_panel"); + sidepanel_inventory->initInventoryViews(); + return floater; } + return NULL; +} + +//static +void LLPanelMainInventory::newFolderWindow(LLUUID folder_id, LLUUID item_to_select) +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) + { + LLFloaterSidePanelContainer* inventory_container = dynamic_cast(*iter++); + if (inventory_container) + { + LLSidepanelInventory* sidepanel_inventory = dynamic_cast(inventory_container->findChild("main_panel", true)); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory && main_inventory->isSingleFolderMode() + && (main_inventory->getCurrentSFVRoot() == folder_id)) + { + main_inventory->setFocus(true); + if(item_to_select.notNull()) + { + main_inventory->setGallerySelection(item_to_select); + } + return; + } + } + } + } + + S32 instance_num = get_instance_num(); + + LLFloaterSidePanelContainer* inventory_container = LLFloaterReg::showTypedInstance("inventory", LLSD(instance_num)); + if(inventory_container) + { + LLSidepanelInventory* sidepanel_inventory = dynamic_cast(inventory_container->findChild("main_panel", true)); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + main_inventory->initSingleFolderRoot(folder_id); + main_inventory->toggleViewMode(); + if(folder_id.notNull()) + { + if(item_to_select.notNull()) + { + main_inventory->setGallerySelection(item_to_select, true); + } + } + } + } + } } void LLPanelMainInventory::doCreate(const LLSD& userdata) @@ -523,15 +644,70 @@ void LLPanelMainInventory::doCreate(const LLSD& userdata) //reset_inventory_filter(); onFilterEdit(""); // - menu_create_inventory_item(getPanel(), NULL, userdata); + if(mSingleFolderMode) + { + if(isListViewMode() || isCombinationViewMode()) + { + LLFolderViewItem* current_folder = getActivePanel()->getRootFolder(); + if (current_folder) + { + if(isCombinationViewMode()) + { + mForceShowInvLayout = true; + } + + LLHandle handle = getHandle(); + std::function callback_created = [handle](const LLUUID& new_id) + { + gInventory.notifyObservers(); // not really needed, should have been already done + LLPanelMainInventory* panel = (LLPanelMainInventory*)handle.get(); + if (new_id.notNull() && panel) + { + // might need to refresh visibility, delay rename + panel->mCombInvUUIDNeedsRename = new_id; + + if (panel->isCombinationViewMode()) + { + panel->mForceShowInvLayout = true; + } + + LL_DEBUGS("Inventory") << "Done creating inventory: " << new_id << LL_ENDL; + } + }; + menu_create_inventory_item(NULL, getCurrentSFVRoot(), userdata, LLUUID::null, callback_created); + } + } + else + { + LLHandle handle = getHandle(); + std::function callback_created = [handle](const LLUUID &new_id) + { + gInventory.notifyObservers(); // not really needed, should have been already done + if (new_id.notNull()) + { + LLPanelMainInventory* panel = (LLPanelMainInventory*)handle.get(); + if (panel) + { + panel->setGallerySelection(new_id); + LL_DEBUGS("Inventory") << "Done creating inventory: " << new_id << LL_ENDL; + } + } + }; + menu_create_inventory_item(NULL, getCurrentSFVRoot(), userdata, LLUUID::null, callback_created); + } + } + else + { + menu_create_inventory_item(getPanel(), NULL, userdata); + } } void LLPanelMainInventory::resetFilters() { LLFloaterInventoryFinder *finder = getFinder(); // Properly reset all filters - //getActivePanel()->getFilter().resetDefault(); - LLInventoryFilter& filter = getActivePanel()->getFilter(); + //getCurrentFilter().resetDefault(); + LLInventoryFilter& filter = getCurrentFilter(); filter.resetDefault(); filter.setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); filter.setSearchType(LLInventoryFilter::SEARCHTYPE_NAME); @@ -560,56 +736,17 @@ void LLPanelMainInventory::resetAllItemsFilters() setFilterTextFromFilter(); } -void LLPanelMainInventory::onSelectSearchType() +void LLPanelMainInventory::findLinks(const LLUUID& item_id, const std::string& item_name) { - std::string new_type = mSearchTypeCombo->getValue(); - if (new_type == "search_by_name") - { - getActivePanel()->setSearchType(LLInventoryFilter::SEARCHTYPE_NAME); - } - if (new_type == "search_by_creator") - { - getActivePanel()->setSearchType(LLInventoryFilter::SEARCHTYPE_CREATOR); - } - if (new_type == "search_by_description") - { - getActivePanel()->setSearchType(LLInventoryFilter::SEARCHTYPE_DESCRIPTION); - } - if (new_type == "search_by_UUID") - { - getActivePanel()->setSearchType(LLInventoryFilter::SEARCHTYPE_UUID); - } + mFilterSubString = item_name; + + LLInventoryFilter &filter = mActivePanel->getFilter(); + filter.setFindAllLinksMode(item_name, item_id); + + mFilterEditor->setText(item_name); + mFilterEditor->setFocus(TRUE); } -void LLPanelMainInventory::updateSearchTypeCombo() -{ - // Check if combo box is actually there - if (!mSearchTypeCombo) - { - return; - } - // - - LLInventoryFilter::ESearchType search_type = getActivePanel()->getSearchType(); - switch(search_type) - { - case LLInventoryFilter::SEARCHTYPE_CREATOR: - mSearchTypeCombo->setValue("search_by_creator"); - break; - case LLInventoryFilter::SEARCHTYPE_DESCRIPTION: - mSearchTypeCombo->setValue("search_by_description"); - break; - case LLInventoryFilter::SEARCHTYPE_UUID: - mSearchTypeCombo->setValue("search_by_UUID"); - break; - case LLInventoryFilter::SEARCHTYPE_NAME: - default: - mSearchTypeCombo->setValue("search_by_name"); - break; - } -} - -// Sort By menu handlers void LLPanelMainInventory::setSortBy(const LLSD& userdata) { U32 sort_order_mask = getActivePanel()->getSortOrder(); @@ -644,6 +781,10 @@ void LLPanelMainInventory::setSortBy(const LLSD& userdata) sort_order_mask |= LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; } } + if(mSingleFolderMode && !isListViewMode()) + { + mCombinationGalleryPanel->setSortOrder(sort_order_mask, true); + } getActivePanel()->setSortOrder(sort_order_mask); // Recent items panel should save sort order @@ -659,6 +800,86 @@ void LLPanelMainInventory::setSortBy(const LLSD& userdata) // } +void LLPanelMainInventory::onSelectSearchType() +{ + std::string new_type = mSearchTypeCombo->getValue(); + if (new_type == "search_by_name") + { + setSearchType(LLInventoryFilter::SEARCHTYPE_NAME); + } + if (new_type == "search_by_creator") + { + setSearchType(LLInventoryFilter::SEARCHTYPE_CREATOR); + } + if (new_type == "search_by_description") + { + setSearchType(LLInventoryFilter::SEARCHTYPE_DESCRIPTION); + } + if (new_type == "search_by_UUID") + { + setSearchType(LLInventoryFilter::SEARCHTYPE_UUID); + } +} + +void LLPanelMainInventory::setSearchType(LLInventoryFilter::ESearchType type) +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + mCombinationGalleryPanel->setSearchType(type); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + mCombinationInventoryPanel->setSearchType(type); + mCombinationGalleryPanel->setSearchType(type); + } + else + { + getActivePanel()->setSearchType(type); + } +} + +void LLPanelMainInventory::updateSearchTypeCombo() +{ + // Check if combo box is actually there + if (!mSearchTypeCombo) + { + return; + } + // + + LLInventoryFilter::ESearchType search_type(LLInventoryFilter::SEARCHTYPE_NAME); + + if(mSingleFolderMode && isGalleryViewMode()) + { + search_type = mCombinationGalleryPanel->getSearchType(); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + search_type = mCombinationGalleryPanel->getSearchType(); + } + else + { + search_type = getActivePanel()->getSearchType(); + } + + switch(search_type) + { + case LLInventoryFilter::SEARCHTYPE_CREATOR: + mSearchTypeCombo->setValue("search_by_creator"); + break; + case LLInventoryFilter::SEARCHTYPE_DESCRIPTION: + mSearchTypeCombo->setValue("search_by_description"); + break; + case LLInventoryFilter::SEARCHTYPE_UUID: + mSearchTypeCombo->setValue("search_by_UUID"); + break; + case LLInventoryFilter::SEARCHTYPE_NAME: + default: + mSearchTypeCombo->setValue("search_by_name"); + break; + } +} + BOOL LLPanelMainInventory::isSortByChecked(const LLSD& userdata) { U32 sort_order_mask = getActivePanel()->getSortOrder(); @@ -720,7 +941,7 @@ void LLPanelMainInventory::onClearSearch() } // re-open folders that were initially open in case filter was active - if (mActivePanel && (mFilterSubString.size() || initially_active)) + if (mActivePanel && (mFilterSubString.size() || initially_active) && !mSingleFolderMode) { mSavedFolderState->setApply(TRUE); mActivePanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); @@ -731,7 +952,7 @@ void LLPanelMainInventory::onClearSearch() mFilterSubString = ""; // FIRE-22509: Only apply inbox filter on primary inventory window - //LLSidepanelInventory * sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + //LLSidepanelInventory * sidepanel_inventory = getParentSidepanelInventory(); //if (sidepanel_inventory) //{ // LLPanelMarketplaceInbox* inbox_panel = sidepanel_inventory->getChild("marketplace_inbox"); @@ -754,16 +975,32 @@ void LLPanelMainInventory::onClearSearch() void LLPanelMainInventory::onFilterEdit(const std::string& search_string ) { + if(mSingleFolderMode && isGalleryViewMode()) + { + mFilterSubString = search_string; + mCombinationGalleryPanel->setFilterSubString(mFilterSubString); + return; + } + if(mSingleFolderMode && isCombinationViewMode()) + { + mCombinationGalleryPanel->setFilterSubString(search_string); + } + if (search_string == "") { onClearSearch(); } + if (!mActivePanel) { return; } - LLInventoryModelBackgroundFetch::instance().start(); + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } mFilterSubString = search_string; // Separate search for inventory tabs from Satomi Ahn (FIRE-913 & FIRE-6862) @@ -805,7 +1042,7 @@ void LLPanelMainInventory::onFilterEdit(const std::string& search_string ) // Separate search for inventory tabs from Satomi Ahn (FIRE-913 & FIRE-6862) // FIRE-22509: Only apply inbox filter on primary inventory window - //LLSidepanelInventory * sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + //LLSidepanelInventory * sidepanel_inventory = getParentSidepanelInventory(); //if (sidepanel_inventory) //{ // LLPanelMarketplaceInbox* inbox_panel = sidepanel_inventory->getChild("marketplace_inbox"); @@ -971,7 +1208,7 @@ void LLPanelMainInventory::updateFilterDropdown(const LLInventoryFilter* filter) void LLPanelMainInventory::onFilterSelected() { // Find my index - mActivePanel = (LLInventoryPanel*)getChild("inventory filter tabs")->getCurrentPanel(); + setActivePanel(); if (!mActivePanel) { @@ -992,15 +1229,19 @@ void LLPanelMainInventory::onFilterSelected() setFilterSubString(mFilterSubString); } // Separate search for inventory tabs from Satomi Ahn (FIRE-913 & FIRE-6862) - LLInventoryFilter& filter = mActivePanel->getFilter(); + LLInventoryFilter& filter = getCurrentFilter(); LLFloaterInventoryFinder *finder = getFinder(); if (finder) { finder->changeFilter(&filter); + if (mSingleFolderMode) + { + finder->setTitle(getLocalizedRootName()); + } } - if (filter.isActive()) + if (filter.isActive() && !LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) { - // If our filter is active we may be the first thing requiring a fetch so we better start it here. + llassert(false); // this should have been done on startup LLInventoryModelBackgroundFetch::instance().start(); } updateFilterDropdown(&filter); // Filter dropdown @@ -1091,6 +1332,7 @@ void LLPanelMainInventory::draw() } LLPanel::draw(); updateItemcountText(); + updateCombinationVisibility(); } void LLPanelMainInventory::updateItemcountText() @@ -1149,6 +1391,21 @@ void LLPanelMainInventory::updateItemcountText() { text = getString("ItemcountUnknown", string_args); } + + if (mSingleFolderMode) + { + LLInventoryModel::cat_array_t *cats; + LLInventoryModel::item_array_t *items; + + gInventory.getDirectDescendentsOf(getCurrentSFVRoot(), cats, items); + + if (items && cats) + { + string_args["[ITEM_COUNT]"] = llformat("%d", items->size()); + string_args["[CATEGORY_COUNT]"] = llformat("%d", cats->size()); + text = getString("ItemcountCompleted", string_args); + } + } mCounterCtrl->setValue(text); //mCounterCtrl->setToolTip(text); // Include folders in inventory count @@ -1168,10 +1425,10 @@ void LLPanelMainInventory::onFocusReceived() void LLPanelMainInventory::setFilterTextFromFilter() { - //mFilterText = mActivePanel->getFilter().getFilterText(); // Filter dropdown + //mFilterText = getCurrentFilter().getFilterText(); // this method gets called by the filter subwindow (once every frame), so we update our combo box here - LLInventoryFilter &filter = mActivePanel->getFilter(); + LLInventoryFilter &filter = getCurrentFilter(); updateFilterDropdown(&filter); mFilterText = filter.getFilterText(); // Filter dropdown @@ -1189,8 +1446,17 @@ void LLPanelMainInventory::toggleFindOptions() LLFloater* parent_floater = gFloaterView->getParentFloater(this); if (parent_floater) parent_floater->addDependentFloater(mFinderHandle); - // start background fetch of folders - LLInventoryModelBackgroundFetch::instance().start(); + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } + + if (mSingleFolderMode) + { + finder->setTitle(getLocalizedRootName()); + } } else { @@ -1450,10 +1716,27 @@ void LLFloaterInventoryFinder::draw() filter &= ~(0x1 << LLInventoryType::IT_CATEGORY); } - // update the panel, panel will update the filter - mPanelMainInventory->getPanel()->setShowFolderState(getCheckShowEmpty() ? - LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - mPanelMainInventory->getPanel()->setFilterTypes(filter); + + bool is_sf_mode = mPanelMainInventory->isSingleFolderMode(); + if(is_sf_mode && mPanelMainInventory->isGalleryViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setShowFolderState(getCheckShowEmpty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setFilterObjectTypes(filter); + } + else + { + if(is_sf_mode && mPanelMainInventory->isCombinationViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setShowFolderState(getCheckShowEmpty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setFilterObjectTypes(filter); + } + // update the panel, panel will update the filter + mPanelMainInventory->getPanel()->setShowFolderState(getCheckShowEmpty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mPanelMainInventory->getPanel()->setFilterTypes(filter); + } if (getCheckSinceLogoff()) { @@ -1475,10 +1758,26 @@ void LLFloaterInventoryFinder::draw() } hours += days * 24; - mPanelMainInventory->getPanel()->setHoursAgo(hours); - mPanelMainInventory->getPanel()->setSinceLogoff(getCheckSinceLogoff()); + mPanelMainInventory->setFilterTextFromFilter(); - mPanelMainInventory->getPanel()->setDateSearchDirection(getDateSearchDirection()); + if(is_sf_mode && mPanelMainInventory->isGalleryViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setHoursAgo(hours); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateRangeLastLogoff(getCheckSinceLogoff()); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateSearchDirection(getDateSearchDirection()); + } + else + { + if(is_sf_mode && mPanelMainInventory->isCombinationViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setHoursAgo(hours); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateRangeLastLogoff(getCheckSinceLogoff()); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateSearchDirection(getDateSearchDirection()); + } + mPanelMainInventory->getPanel()->setHoursAgo(hours); + mPanelMainInventory->getPanel()->setSinceLogoff(getCheckSinceLogoff()); + mPanelMainInventory->getPanel()->setDateSearchDirection(getDateSearchDirection()); + } LLPanel::draw(); } @@ -1490,15 +1789,15 @@ void LLFloaterInventoryFinder::onCreatorSelfFilterCommit() if(show_creator_self && show_creator_other) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); } else if(show_creator_self) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); } else if(!show_creator_self || !show_creator_other) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); mCreatorOthers->set(TRUE); } } @@ -1510,15 +1809,15 @@ void LLFloaterInventoryFinder::onCreatorOtherFilterCommit() if(show_creator_self && show_creator_other) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); } else if(show_creator_other) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); } else if(!show_creator_other || !show_creator_self) { - mFilter->setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); mCreatorSelf->set(TRUE); } } @@ -1634,26 +1933,38 @@ void LLPanelMainInventory::showAllItemsPanel() void LLPanelMainInventory::initListCommandsHandlers() { - childSetAction("trash_btn", boost::bind(&LLPanelMainInventory::onTrashButtonClick, this)); + childSetAction("trash_btn", boost::bind(&LLPanelMainInventory::onTrashButtonClick, this)); // Keep better inventory layout childSetAction("add_btn", boost::bind(&LLPanelMainInventory::onAddButtonClick, this)); + childSetAction("view_mode_btn", boost::bind(&LLPanelMainInventory::onViewModeClick, this)); + childSetAction("up_btn", boost::bind(&LLPanelMainInventory::onUpFolderClicked, this)); + childSetAction("back_btn", boost::bind(&LLPanelMainInventory::onBackFolderClicked, this)); + childSetAction("forward_btn", boost::bind(&LLPanelMainInventory::onForwardFolderClicked, this)); + // Keep better inventory layout mTrashButton = getChild("trash_btn"); mTrashButton->setDragAndDropHandler(boost::bind(&LLPanelMainInventory::handleDragAndDropToTrash, this , _4 // BOOL drop , _5 // EDragAndDropType cargo_type , _7 // EAcceptance* accept )); + // - mCommitCallbackRegistrar.add("Inventory.GearDefault.Custom.Action", boost::bind(&LLPanelMainInventory::onCustomAction, this, _2)); - mEnableCallbackRegistrar.add("Inventory.GearDefault.Check", boost::bind(&LLPanelMainInventory::isActionChecked, this, _2)); - mEnableCallbackRegistrar.add("Inventory.GearDefault.Enable", boost::bind(&LLPanelMainInventory::isActionEnabled, this, _2)); + // Moved to constructor to register early + //mCommitCallbackRegistrar.add("Inventory.GearDefault.Custom.Action", boost::bind(&LLPanelMainInventory::onCustomAction, this, _2)); + //mEnableCallbackRegistrar.add("Inventory.GearDefault.Check", boost::bind(&LLPanelMainInventory::isActionChecked, this, _2)); + //mEnableCallbackRegistrar.add("Inventory.GearDefault.Enable", boost::bind(&LLPanelMainInventory::isActionEnabled, this, _2)); + //mEnableCallbackRegistrar.add("Inventory.GearDefault.Visible", boost::bind(&LLPanelMainInventory::isActionVisible, this, _2)); + // mMenuGearDefault = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_gear_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mGearMenuButton->setMenu(mMenuGearDefault, LLMenuButton::MP_TOP_LEFT, true); + mGearMenuButton->setMenu(mMenuGearDefault, LLMenuButton::MP_BOTTOM_LEFT, true); + mMenuViewDefault = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_view_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (mViewMenuButton) // Keep better inventory layout + mViewMenuButton->setMenu(mMenuViewDefault, LLMenuButton::MP_BOTTOM_LEFT, true); LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_add.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); mMenuAddHandle = menu->getHandle(); mMenuVisibility = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_search_visibility.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mVisibilityMenuButton->setMenu(mMenuVisibility, LLMenuButton::MP_BOTTOM_LEFT, true); + mVisibilityMenuButton->setMenu(mMenuVisibility, LLMenuButton::MP_BOTTOM_LEFT, true); // Update the trash button when selected item(s) get worn or taken off. LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLPanelMainInventory::updateListCommands, this)); @@ -1661,9 +1972,10 @@ void LLPanelMainInventory::initListCommandsHandlers() void LLPanelMainInventory::updateListCommands() { + // Keep better inventory layout bool trash_enabled = isActionEnabled("delete"); - mTrashButton->setEnabled(trash_enabled); + // } void LLPanelMainInventory::onAddButtonClick() @@ -1674,7 +1986,7 @@ void LLPanelMainInventory::onAddButtonClick() LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); if (menu) { - menu->getChild("New Folder")->setEnabled(!isRecentItemsPanelSelected()); + disableAddIfNeeded(); setUploadCostIfNeeded(); @@ -1682,6 +1994,218 @@ void LLPanelMainInventory::onAddButtonClick() } } +void LLPanelMainInventory::setActivePanel() +{ + // Todo: should cover gallery mode in some way + if(mSingleFolderMode && isListViewMode()) + { + mActivePanel = getChild("comb_single_folder_inv"); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + mActivePanel = getChild("comb_single_folder_inv"); + } + else + { + mActivePanel = (LLInventoryPanel*)getChild("inventory filter tabs")->getCurrentPanel(); + } + mViewModeBtn->setEnabled(mSingleFolderMode || (getAllItemsPanel() == getActivePanel())); +} + +void LLPanelMainInventory::initSingleFolderRoot(const LLUUID& start_folder_id) +{ + mCombinationInventoryPanel->initFolderRoot(start_folder_id); +} + +void LLPanelMainInventory::initInventoryViews() +{ + LLInventoryPanel* all_item = getChild(ALL_ITEMS); + all_item->initializeViewBuilding(); + LLInventoryPanel* recent_item = getChild(RECENT_ITEMS); + recent_item->initializeViewBuilding(); + LLInventoryPanel* worn_item = getChild(WORN_ITEMS); + worn_item->initializeViewBuilding(); +} + +void LLPanelMainInventory::toggleViewMode() +{ + if(mSingleFolderMode && isCombinationViewMode()) + { + mCombinationInventoryPanel->getRootFolder()->setForceArrange(false); + } + + mSingleFolderMode = !mSingleFolderMode; + mReshapeInvLayout = true; + + if (mCombinationGalleryPanel->getRootFolder().isNull()) + { + mCombinationGalleryPanel->setRootFolder(mCombinationInventoryPanel->getSingleFolderRoot()); + mCombinationGalleryPanel->updateRootFolder(); + } + + updatePanelVisibility(); + // Disable Expand/Collapse buttons in single folder mode + getChild("collapse_expand_buttons")->setVisible(!mSingleFolderMode); + + setActivePanel(); + updateTitle(); + onFilterSelected(); + + LLSidepanelInventory* sidepanel_inventory = getParentSidepanelInventory(); + if (sidepanel_inventory) + { + if(mSingleFolderMode) + { + sidepanel_inventory->hideInbox(); + } + else + { + sidepanel_inventory->toggleInbox(); + } + } +} + +void LLPanelMainInventory::onViewModeClick() +{ + LLUUID selected_folder; + LLUUID new_root_folder; + if(mSingleFolderMode) + { + selected_folder = getCurrentSFVRoot(); + } + else + { + LLFolderView* root = getActivePanel()->getRootFolder(); + std::set selection_set = root->getSelectionList(); + if (selection_set.size() == 1) + { + LLFolderViewItem* current_item = *selection_set.begin(); + if (current_item) + { + const LLUUID& id = static_cast(current_item->getViewModelItem())->getUUID(); + if(gInventory.getCategory(id) != NULL) + { + new_root_folder = id; + } + else + { + const LLViewerInventoryItem* selected_item = gInventory.getItem(id); + if (selected_item && selected_item->getParentUUID().notNull()) + { + new_root_folder = selected_item->getParentUUID(); + selected_folder = id; + } + } + } + } + mCombinationInventoryPanel->initFolderRoot(new_root_folder); + } + + toggleViewMode(); + + if (mSingleFolderMode && new_root_folder.notNull()) + { + setSingleFolderViewRoot(new_root_folder, true); + if(selected_folder.notNull() && isListViewMode()) + { + getActivePanel()->setSelection(selected_folder, TAKE_FOCUS_YES); + } + } + else + { + if(selected_folder.notNull()) + { + selectAllItemsPanel(); + getActivePanel()->setSelection(selected_folder, TAKE_FOCUS_YES); + } + } +} + +void LLPanelMainInventory::onUpFolderClicked() +{ + const LLViewerInventoryCategory* cat = gInventory.getCategory(getCurrentSFVRoot()); + if (cat) + { + if (cat->getParentUUID().notNull()) + { + if(isListViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cat->getParentUUID()); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->setRootFolder(cat->getParentUUID()); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cat->getParentUUID()); + } + } + } +} + +void LLPanelMainInventory::onBackFolderClicked() +{ + if(isListViewMode()) + { + mCombinationInventoryPanel->onBackwardFolder(); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->onBackwardFolder(); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->onBackwardFolder(); + } +} + +void LLPanelMainInventory::onForwardFolderClicked() +{ + if(isListViewMode()) + { + mCombinationInventoryPanel->onForwardFolder(); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->onForwardFolder(); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->onForwardFolder(); + } +} + +void LLPanelMainInventory::setSingleFolderViewRoot(const LLUUID& folder_id, bool clear_nav_history) +{ + if(isListViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(folder_id); + if(clear_nav_history) + { + mCombinationInventoryPanel->clearNavigationHistory(); + } + } + else if(isGalleryViewMode()) + { + mCombinationGalleryPanel->setRootFolder(folder_id); + if(clear_nav_history) + { + mCombinationGalleryPanel->clearNavigationHistory(); + } + } + else if(isCombinationViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(folder_id); + } + updateNavButtons(); +} + +LLUUID LLPanelMainInventory::getSingleFolderViewRoot() +{ + return mCombinationInventoryPanel->getSingleFolderRoot(); +} + void LLPanelMainInventory::showActionMenu(LLMenuGL* menu, std::string spawning_view_name) { if (menu) @@ -1691,16 +2215,17 @@ void LLPanelMainInventory::showActionMenu(LLMenuGL* menu, std::string spawning_v LLView* spawning_view = getChild (spawning_view_name); S32 menu_x, menu_y; //show menu in co-ordinates of panel - spawning_view->localPointToOtherView(0, spawning_view->getRect().getHeight(), &menu_x, &menu_y, this); - menu_y += menu->getRect().getHeight(); + spawning_view->localPointToOtherView(0, 0, &menu_x, &menu_y, this); LLMenuGL::showPopup(this, menu, menu_x, menu_y); } } +// Keep better inventory layout void LLPanelMainInventory::onTrashButtonClick() { onClipboardAction("delete"); } +// void LLPanelMainInventory::onClipboardAction(const LLSD& userdata) { @@ -1710,13 +2235,22 @@ void LLPanelMainInventory::onClipboardAction(const LLSD& userdata) void LLPanelMainInventory::saveTexture(const LLSD& userdata) { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) - { - return; - } - - const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); + LLUUID item_id; + if(mSingleFolderMode && isGalleryViewMode()) + { + item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); + if (item_id.isNull()) return; + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) + { + return; + } + item_id = static_cast(current_item->getViewModelItem())->getUUID(); + } + LLPreviewTexture* preview_texture = LLFloaterReg::showTypedInstance("preview_texture", LLSD(item_id), TAKE_FOCUS_YES); if (preview_texture) { @@ -1730,6 +2264,7 @@ void LLPanelMainInventory::onCustomAction(const LLSD& userdata) return; const std::string command_name = userdata.asString(); + if (command_name == "new_window") { newWindow(); @@ -1807,35 +2342,69 @@ void LLPanelMainInventory::onCustomAction(const LLSD& userdata) } if (command_name == "find_original") { + if(mSingleFolderMode && isGalleryViewMode()) + { + LLInventoryObject *obj = gInventory.getObject(mCombinationGalleryPanel->getFirstSelectedItemID()); + if (obj && obj->getIsLinkType()) + { + show_item_original(obj->getUUID()); + } + } + else + { LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); if (!current_item) { return; } static_cast(current_item->getViewModelItem())->performAction(getActivePanel()->getModel(), "goto"); + } } if (command_name == "find_links") { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) - { - return; - } - const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); - const std::string &item_name = current_item->getViewModelItem()->getName(); - mFilterSubString = item_name; - - LLInventoryFilter &filter = mActivePanel->getFilter(); - filter.setFindAllLinksMode(item_name, item_id); - - mFilterEditor->setText(item_name); - mFilterEditor->setFocus(TRUE); + if(mSingleFolderMode && isGalleryViewMode()) + { + LLFloaterSidePanelContainer* inventory_container = newWindow(); + if (inventory_container) + { + LLSidepanelInventory* sidepanel_inventory = dynamic_cast(inventory_container->findChild("main_panel", true)); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + LLInventoryObject *obj = gInventory.getObject(mCombinationGalleryPanel->getFirstSelectedItemID()); + if (obj) + { + main_inventory->findLinks(obj->getUUID(), obj->getName()); + } + } + } + } + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) + { + return; + } + const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); + const std::string &item_name = current_item->getViewModelItem()->getName(); + findLinks(item_id, item_name); + } } if (command_name == "replace_links") { - LLSD params; + LLSD params; + if(mSingleFolderMode && isGalleryViewMode()) + { + params = LLSD(mCombinationGalleryPanel->getFirstSelectedItemID()); + } + else + { LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); if (current_item) { @@ -1850,23 +2419,72 @@ void LLPanelMainInventory::onCustomAction(const LLSD& userdata) } } } + } LLFloaterReg::showInstance("linkreplace", params); } + if (command_name == "close_inv_windows") + { + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) + { + LLFloaterSidePanelContainer* iv = dynamic_cast(*iter++); + if (iv) + { + iv->closeFloater(); + } + } + LLFloaterReg::hideInstance("inventory_settings"); + } + + if (command_name == "toggle_search_outfits") + { + getCurrentFilter().toggleSearchVisibilityOutfits(); + } + if (command_name == "toggle_search_trash") { - mActivePanel->getFilter().toggleSearchVisibilityTrash(); + getCurrentFilter().toggleSearchVisibilityTrash(); } if (command_name == "toggle_search_library") { - mActivePanel->getFilter().toggleSearchVisibilityLibrary(); + getCurrentFilter().toggleSearchVisibilityLibrary(); } if (command_name == "include_links") { - mActivePanel->getFilter().toggleSearchVisibilityLinks(); - } + getCurrentFilter().toggleSearchVisibilityLinks(); + } + + if (command_name == "share") + { + if(mSingleFolderMode && isGalleryViewMode()) + { + std::set uuids{ mCombinationGalleryPanel->getFirstSelectedItemID()}; + LLAvatarActions::shareWithAvatars(uuids, gFloaterView->getParentFloater(this)); + } + else + { + LLAvatarActions::shareWithAvatars(this); + } + } + if (command_name == "shop") + { + LLWeb::loadURL(gSavedSettings.getString("MarketplaceURL")); + } + if (command_name == "list_view") + { + setViewMode(MODE_LIST); + } + if (command_name == "gallery_view") + { + setViewMode(MODE_GALLERY); + } + if (command_name == "combination_view") + { + setViewMode(MODE_COMBINATION); + } } void LLPanelMainInventory::onVisibilityChange( BOOL new_visibility ) @@ -1891,17 +2509,26 @@ void LLPanelMainInventory::onVisibilityChange( BOOL new_visibility ) bool LLPanelMainInventory::isSaveTextureEnabled(const LLSD& userdata) { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (current_item) - { - LLViewerInventoryItem *inv_item = dynamic_cast(static_cast(current_item->getViewModelItem())->getInventoryObject()); + LLViewerInventoryItem *inv_item = NULL; + if(mSingleFolderMode && isGalleryViewMode()) + { + inv_item = gInventory.getItem(mCombinationGalleryPanel->getFirstSelectedItemID()); + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (current_item) + { + inv_item = dynamic_cast(static_cast(current_item->getViewModelItem())->getInventoryObject()); + } + } if(inv_item) { bool can_save = inv_item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); - LLInventoryType::EType curr_type = static_cast(current_item->getViewModelItem())->getInventoryType(); + LLInventoryType::EType curr_type = inv_item->getInventoryType(); return can_save && (curr_type == LLInventoryType::IT_TEXTURE || curr_type == LLInventoryType::IT_SNAPSHOT); } - } + return false; } @@ -1934,9 +2561,16 @@ BOOL LLPanelMainInventory::isActionEnabled(const LLSD& userdata) } if (command_name == "find_original") { + LLUUID item_id; + if(mSingleFolderMode && isGalleryViewMode()) + { + item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); + } + else{ LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); if (!current_item) return FALSE; - const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); + item_id = static_cast(current_item->getViewModelItem())->getUUID(); + } const LLViewerInventoryItem *item = gInventory.getItem(item_id); if (item && item->getIsLinkType() && !item->getIsBrokenLink()) { @@ -1947,12 +2581,19 @@ BOOL LLPanelMainInventory::isActionEnabled(const LLSD& userdata) if (command_name == "find_links") { + LLUUID item_id; + if(mSingleFolderMode && isGalleryViewMode()) + { + item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); + } + else{ LLFolderView* root = getActivePanel()->getRootFolder(); std::set selection_set = root->getSelectionList(); if (selection_set.size() != 1) return FALSE; LLFolderViewItem* current_item = root->getCurSelectedItem(); if (!current_item) return FALSE; - const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); + item_id = static_cast(current_item->getViewModelItem())->getUUID(); + } const LLInventoryObject *obj = gInventory.getObject(item_id); if (obj && !obj->getIsLinkType() && LLAssetType::lookupCanLink(obj->getType())) { @@ -1976,10 +2617,16 @@ BOOL LLPanelMainInventory::isActionEnabled(const LLSD& userdata) if (command_name == "share") { + if(mSingleFolderMode && isGalleryViewMode()) + { + return can_share_item(mCombinationGalleryPanel->getFirstSelectedItemID()); + } + else{ LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); if (!current_item) return FALSE; LLSidepanelInventory* parent = LLFloaterSidePanelContainer::getPanel("inventory"); return parent ? parent->canShare() : FALSE; + } } if (command_name == "empty_trash") { @@ -1997,9 +2644,24 @@ BOOL LLPanelMainInventory::isActionEnabled(const LLSD& userdata) return TRUE; } +bool LLPanelMainInventory::isActionVisible(const LLSD& userdata) +{ + const std::string param_str = userdata.asString(); + if (param_str == "single_folder_view") + { + return mSingleFolderMode; + } + if (param_str == "multi_folder_view") + { + return !mSingleFolderMode; + } + + return true; +} + BOOL LLPanelMainInventory::isActionChecked(const LLSD& userdata) { - U32 sort_order_mask = getActivePanel()->getSortOrder(); + U32 sort_order_mask = (mSingleFolderMode && isGalleryViewMode()) ? mCombinationGalleryPanel->getSortOrder() : getActivePanel()->getSortOrder(); const std::string command_name = userdata.asString(); if (command_name == "sort_by_name") { @@ -2021,21 +2683,39 @@ BOOL LLPanelMainInventory::isActionChecked(const LLSD& userdata) return sort_order_mask & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; } + if (command_name == "toggle_search_outfits") + { + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_OUTFITS) != 0; + } + if (command_name == "toggle_search_trash") { - return (mActivePanel->getFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_TRASH) != 0; + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_TRASH) != 0; } if (command_name == "toggle_search_library") { - return (mActivePanel->getFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LIBRARY) != 0; + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LIBRARY) != 0; } if (command_name == "include_links") { - return (mActivePanel->getFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LINKS) != 0; + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LINKS) != 0; } + if (command_name == "list_view") + { + return isListViewMode(); + } + if (command_name == "gallery_view") + { + return isGalleryViewMode(); + } + if (command_name == "combination_view") + { + return isCombinationViewMode(); + } + if (command_name == "add_objects_on_double_click") { return gSavedSettings.getBOOL("FSDoubleClickAddInventoryObjects"); @@ -2221,6 +2901,7 @@ BOOL LLPanelMainInventory::isSearchTypeChecked(const LLSD& userdata) } // Extended Inventory Search +// Keep better inventory layout bool LLPanelMainInventory::handleDragAndDropToTrash(BOOL drop, EDragAndDropType cargo_type, EAcceptance* accept) { *accept = ACCEPT_NO; @@ -2234,6 +2915,7 @@ bool LLPanelMainInventory::handleDragAndDropToTrash(BOOL drop, EDragAndDropType } return true; } +// void LLPanelMainInventory::setUploadCostIfNeeded() { @@ -2249,10 +2931,410 @@ void LLPanelMainInventory::setUploadCostIfNeeded() } } +bool is_add_allowed(LLUUID folder_id) +{ + if(!gInventory.isObjectDescendentOf(folder_id, gInventory.getRootFolderID())) + { + return false; + } + + std::vector not_allowed_types; + not_allowed_types.push_back(LLFolderType::FT_LOST_AND_FOUND); + not_allowed_types.push_back(LLFolderType::FT_FAVORITE); + not_allowed_types.push_back(LLFolderType::FT_MARKETPLACE_LISTINGS); + not_allowed_types.push_back(LLFolderType::FT_TRASH); + not_allowed_types.push_back(LLFolderType::FT_CURRENT_OUTFIT); + not_allowed_types.push_back(LLFolderType::FT_INBOX); + + for (std::vector::const_iterator it = not_allowed_types.begin(); + it != not_allowed_types.end(); ++it) + { + if(gInventory.isObjectDescendentOf(folder_id, gInventory.findCategoryUUIDForType(*it))) + { + return false; + } + } + + LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); + if (cat && (cat->getPreferredType() == LLFolderType::FT_OUTFIT)) + { + return false; + } + return true; +} + +void LLPanelMainInventory::disableAddIfNeeded() +{ + LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); + if (menu) + { + bool enable = !mSingleFolderMode || is_add_allowed(getCurrentSFVRoot()); + + menu->getChild("New Folder")->setEnabled(enable && !isRecentItemsPanelSelected()); + menu->getChild("New Script")->setEnabled(enable); + menu->getChild("New Note")->setEnabled(enable); + menu->getChild("New Gesture")->setEnabled(enable); + menu->setItemEnabled("New Clothes", enable); + menu->setItemEnabled("New Body Parts", enable); + menu->setItemEnabled("New Settings", enable); + } +} + bool LLPanelMainInventory::hasSettingsInventory() { return LLEnvironment::instance().isInventoryEnabled(); } +void LLPanelMainInventory::updateTitle() +{ + LLFloater* inventory_floater = gFloaterView->getParentFloater(this); + if(inventory_floater) + { + if(mSingleFolderMode) + { + inventory_floater->setTitle(getLocalizedRootName()); + LLFloaterInventoryFinder *finder = getFinder(); + if (finder) + { + finder->setTitle(getLocalizedRootName()); + } + } + else + { + inventory_floater->setTitle(getString("inventory_title")); + } + } + updateNavButtons(); +} + +void LLPanelMainInventory::onCombinationRootChanged(bool gallery_clicked) +{ + if(gallery_clicked) + { + mCombinationInventoryPanel->changeFolderRoot(mCombinationGalleryPanel->getRootFolder()); + } + else + { + mCombinationGalleryPanel->setRootFolder(mCombinationInventoryPanel->getSingleFolderRoot()); + } + mForceShowInvLayout = false; + updateTitle(); + mReshapeInvLayout = true; +} + +void LLPanelMainInventory::onCombinationGallerySelectionChanged(const LLUUID& category_id) +{ +} + +void LLPanelMainInventory::onCombinationInventorySelectionChanged(const std::deque& items, BOOL user_action) +{ + onSelectionChange(mCombinationInventoryPanel, items, user_action); +} + +void LLPanelMainInventory::updatePanelVisibility() +{ + mDefaultViewPanel->setVisible(!mSingleFolderMode); + mCombinationViewPanel->setVisible(mSingleFolderMode); + mNavigationBtnsPanel->setVisible(mSingleFolderMode); + mViewModeBtn->setImageOverlay(mSingleFolderMode ? getString("default_mode_btn") : getString("single_folder_mode_btn")); + mViewModeBtn->setEnabled(mSingleFolderMode || (getAllItemsPanel() == getActivePanel())); + if (mSingleFolderMode) + { + if (isCombinationViewMode()) + { + LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); + comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_EXCLUDE_THUMBNAILS); + comb_inv_filter.markDefault(); + + LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); + comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_ONLY_THUMBNAILS); + comb_gallery_filter.markDefault(); + + // visibility will be controled by updateCombinationVisibility() + mCombinationGalleryLayoutPanel->setVisible(true); + mCombinationGalleryPanel->setVisible(true); + mCombinationListLayoutPanel->setVisible(true); + } + else + { + LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); + comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_INCLUDE_THUMBNAILS); + comb_inv_filter.markDefault(); + + LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); + comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_INCLUDE_THUMBNAILS); + comb_gallery_filter.markDefault(); + + mCombinationLayoutStack->setPanelSpacing(0); + mCombinationGalleryLayoutPanel->setVisible(mSingleFolderMode && isGalleryViewMode()); + mCombinationGalleryPanel->setVisible(mSingleFolderMode && isGalleryViewMode()); // to prevent or process updates + mCombinationListLayoutPanel->setVisible(mSingleFolderMode && isListViewMode()); + } + } + else + { + mCombinationGalleryLayoutPanel->setVisible(false); + mCombinationGalleryPanel->setVisible(false); // to prevent updates + mCombinationListLayoutPanel->setVisible(false); + } +} + +void LLPanelMainInventory::updateCombinationVisibility() +{ + if(mSingleFolderMode && isCombinationViewMode()) + { + bool is_gallery_empty = !mCombinationGalleryPanel->hasVisibleItems(); + bool show_inv_pane = mCombinationInventoryPanel->hasVisibleItems() || is_gallery_empty || mForceShowInvLayout; + + const S32 DRAG_HANDLE_PADDING = 12; // for drag handle to not overlap gallery when both inventories are visible + mCombinationLayoutStack->setPanelSpacing(show_inv_pane ? DRAG_HANDLE_PADDING : 0); + + mCombinationGalleryLayoutPanel->setVisible(!is_gallery_empty); + mCombinationListLayoutPanel->setVisible(show_inv_pane); + mCombinationInventoryPanel->getRootFolder()->setForceArrange(!show_inv_pane); + if(mCombinationInventoryPanel->hasVisibleItems()) + { + mForceShowInvLayout = false; + } + if(is_gallery_empty) + { + mCombinationGalleryPanel->handleModifiedFilter(); + } + + getActivePanel()->getRootFolder(); + + if (mReshapeInvLayout + && show_inv_pane + && (mCombinationGalleryPanel->hasVisibleItems() || mCombinationGalleryPanel->areViewsInitialized()) + && mCombinationInventoryPanel->areViewsInitialized()) + { + mReshapeInvLayout = false; + + // force drop previous shape (because panel doesn't decrease shape properly) + LLRect list_latout = mCombinationListLayoutPanel->getRect(); + list_latout.mTop = list_latout.mBottom; // min height is at 100, so it should snap to be bigger + mCombinationListLayoutPanel->setShape(list_latout, false); + + LLRect inv_inner_rect = mCombinationInventoryPanel->getScrollableContainer()->getScrolledViewRect(); + S32 inv_height = inv_inner_rect.getHeight() + + (mCombinationInventoryPanel->getScrollableContainer()->getBorderWidth() * 2) + + mCombinationInventoryPanel->getScrollableContainer()->getSize(); + LLRect inner_galery_rect = mCombinationGalleryPanel->getScrollableContainer()->getScrolledViewRect(); + S32 gallery_height = inner_galery_rect.getHeight() + + (mCombinationGalleryPanel->getScrollableContainer()->getBorderWidth() * 2) + + mCombinationGalleryPanel->getScrollableContainer()->getSize(); + LLRect layout_rect = mCombinationViewPanel->getRect(); + + // by default make it take 1/3 of the panel + S32 list_default_height = layout_rect.getHeight() / 3; + // Don't set height from gallery_default_height - needs to account for a resizer in such case + S32 gallery_default_height = layout_rect.getHeight() - list_default_height; + + if (inv_height > list_default_height + && gallery_height < gallery_default_height) + { + LLRect gallery_latout = mCombinationGalleryLayoutPanel->getRect(); + gallery_latout.mTop = gallery_latout.mBottom + gallery_height; + mCombinationGalleryLayoutPanel->setShape(gallery_latout, true /*tell stack to account for new shape*/); + } + else if (inv_height < list_default_height + && gallery_height > gallery_default_height) + { + LLRect list_latout = mCombinationListLayoutPanel->getRect(); + list_latout.mTop = list_latout.mBottom + inv_height; + mCombinationListLayoutPanel->setShape(list_latout, true /*tell stack to account for new shape*/); + } + else + { + LLRect list_latout = mCombinationListLayoutPanel->getRect(); + list_latout.mTop = list_latout.mBottom + list_default_height; + mCombinationListLayoutPanel->setShape(list_latout, true /*tell stack to account for new shape*/); + } + } + } + + if (mSingleFolderMode + && !isGalleryViewMode() + && mCombInvUUIDNeedsRename.notNull() + && mCombinationInventoryPanel->areViewsInitialized()) + { + mCombinationInventoryPanel->setSelectionByID(mCombInvUUIDNeedsRename, TRUE); + mCombinationInventoryPanel->getRootFolder()->scrollToShowSelection(); + mCombinationInventoryPanel->getRootFolder()->setNeedsAutoRename(TRUE); + mCombInvUUIDNeedsRename.setNull(); + } +} + +void LLPanelMainInventory::updateNavButtons() +{ + if(isListViewMode()) + { + mBackBtn->setEnabled(mCombinationInventoryPanel->isBackwardAvailable()); + mForwardBtn->setEnabled(mCombinationInventoryPanel->isForwardAvailable()); + } + if(isGalleryViewMode()) + { + mBackBtn->setEnabled(mCombinationGalleryPanel->isBackwardAvailable()); + mForwardBtn->setEnabled(mCombinationGalleryPanel->isForwardAvailable()); + } + if(isCombinationViewMode()) + { + mBackBtn->setEnabled(mCombinationInventoryPanel->isBackwardAvailable()); + mForwardBtn->setEnabled(mCombinationInventoryPanel->isForwardAvailable()); + } + + const LLViewerInventoryCategory* cat = gInventory.getCategory(getCurrentSFVRoot()); + bool up_enabled = (cat && cat->getParentUUID().notNull()); + mUpBtn->setEnabled(up_enabled); +} + +LLSidepanelInventory* LLPanelMainInventory::getParentSidepanelInventory() +{ + LLFloaterSidePanelContainer* inventory_container = dynamic_cast(gFloaterView->getParentFloater(this)); + if(inventory_container) + { + return dynamic_cast(inventory_container->findChild("main_panel", true)); + } + return NULL; +} + +void LLPanelMainInventory::setViewMode(EViewModeType mode) +{ + if(mode != mViewMode) + { + std::list forward_history; + std::list backward_history; + U32 sort_order = 0; + switch(mViewMode) + { + case MODE_LIST: + forward_history = mCombinationInventoryPanel->getNavForwardList(); + backward_history = mCombinationInventoryPanel->getNavBackwardList(); + sort_order = mCombinationInventoryPanel->getSortOrder(); + break; + case MODE_GALLERY: + forward_history = mCombinationGalleryPanel->getNavForwardList(); + backward_history = mCombinationGalleryPanel->getNavBackwardList(); + sort_order = mCombinationGalleryPanel->getSortOrder(); + break; + case MODE_COMBINATION: + forward_history = mCombinationInventoryPanel->getNavForwardList(); + backward_history = mCombinationInventoryPanel->getNavBackwardList(); + mCombinationInventoryPanel->getRootFolder()->setForceArrange(false); + sort_order = mCombinationInventoryPanel->getSortOrder(); + break; + } + + LLUUID cur_root = getCurrentSFVRoot(); + mViewMode = mode; + + updatePanelVisibility(); + + if(isListViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cur_root); + mCombinationInventoryPanel->setNavForwardList(forward_history); + mCombinationInventoryPanel->setNavBackwardList(backward_history); + mCombinationInventoryPanel->setSortOrder(sort_order); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->setRootFolder(cur_root); + mCombinationGalleryPanel->setNavForwardList(forward_history); + mCombinationGalleryPanel->setNavBackwardList(backward_history); + mCombinationGalleryPanel->setSortOrder(sort_order, true); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cur_root); + mCombinationGalleryPanel->setRootFolder(cur_root); + mCombinationInventoryPanel->setNavForwardList(forward_history); + mCombinationInventoryPanel->setNavBackwardList(backward_history); + mCombinationGalleryPanel->setNavForwardList(forward_history); + mCombinationGalleryPanel->setNavBackwardList(backward_history); + mCombinationInventoryPanel->setSortOrder(sort_order); + mCombinationGalleryPanel->setSortOrder(sort_order, true); + } + + updateNavButtons(); + + onFilterSelected(); + if((isListViewMode() && (mActivePanel->getFilterSubString() != mFilterSubString)) || + (isGalleryViewMode() && (mCombinationGalleryPanel->getFilterSubString() != mFilterSubString))) + { + onFilterEdit(mFilterSubString); + } + } +} + +std::string LLPanelMainInventory::getLocalizedRootName() +{ + return mSingleFolderMode ? get_localized_folder_name(getCurrentSFVRoot()) : ""; +} + +LLUUID LLPanelMainInventory::getCurrentSFVRoot() +{ + if(isListViewMode()) + { + return mCombinationInventoryPanel->getSingleFolderRoot(); + } + if(isGalleryViewMode()) + { + return mCombinationGalleryPanel->getRootFolder(); + } + if(isCombinationViewMode()) + { + return mCombinationInventoryPanel->getSingleFolderRoot(); + } + return LLUUID::null; +} + +LLInventoryFilter& LLPanelMainInventory::getCurrentFilter() +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + return mCombinationGalleryPanel->getFilter(); + } + else + { + return mActivePanel->getFilter(); + } +} + +void LLPanelMainInventory::setGallerySelection(const LLUUID& item_id, bool new_window) +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + mCombinationGalleryPanel->changeItemSelection(item_id, true); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + if(mCombinationGalleryPanel->getFilter().checkAgainstFilterThumbnails(item_id)) + { + mCombinationGalleryPanel->changeItemSelection(item_id, false); + scrollToGallerySelection(); + } + else + { + mCombinationInventoryPanel->setSelection(item_id, true); + scrollToInvPanelSelection(); + } + } + else if (mSingleFolderMode && isListViewMode()) + { + mCombinationInventoryPanel->setSelection(item_id, true); + } +} + +void LLPanelMainInventory::scrollToGallerySelection() +{ + mCombinationGalleryPanel->scrollToShowItem(mCombinationGalleryPanel->getFirstSelectedItemID()); +} + +void LLPanelMainInventory::scrollToInvPanelSelection() +{ + mCombinationInventoryPanel->getRootFolder()->scrollToShowSelection(); +} + // List Commands // //////////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/llpanelmaininventory.h b/indra/newview/llpanelmaininventory.h index 194335ec9c..ecbb531888 100644 --- a/indra/newview/llpanelmaininventory.h +++ b/indra/newview/llpanelmaininventory.h @@ -31,6 +31,7 @@ #include "llpanel.h" #include "llinventoryfilter.h" #include "llinventoryobserver.h" +#include "llinventorypanel.h" #include "lldndbutton.h" #include "llfolderview.h" @@ -38,14 +39,17 @@ class LLComboBox; class LLFolderViewItem; class LLInventoryPanel; +class LLInventoryGallery; class LLSaveFolderState; class LLFilterEditor; class LLTabContainer; class LLFloaterInventoryFinder; class LLMenuButton; class LLMenuGL; +class LLSidepanelInventory; class LLToggleableMenu; class LLFloater; +class LLFloaterSidePanelContainer; class LLComboBox; // Filter dropdown //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -65,6 +69,13 @@ public: BOOL postBuild(); + enum EViewModeType + { + MODE_LIST, + MODE_GALLERY, + MODE_COMBINATION + }; + virtual BOOL handleKeyHere(KEY key, MASK mask); // Inherited functionality @@ -87,6 +98,7 @@ public: // FIRE-19493: "Show Original" should open main inventory panel void showAllItemsPanel(); // + void setActivePanel(); bool isRecentItemsPanelSelected(); @@ -98,13 +110,40 @@ public: void setFocusFilterEditor(); - static void newWindow(); + static LLFloaterSidePanelContainer* newWindow(); + static void newFolderWindow(LLUUID folder_id = LLUUID(), LLUUID item_to_select = LLUUID()); void toggleFindOptions(); void resetFilters(); void resetAllItemsFilters(); + void findLinks(const LLUUID& item_id, const std::string& item_name); + + void onViewModeClick(); + void toggleViewMode(); + void initSingleFolderRoot(const LLUUID& start_folder_id = LLUUID::null); + void initInventoryViews(); + void onUpFolderClicked(); + void onBackFolderClicked(); + void onForwardFolderClicked(); + void setSingleFolderViewRoot(const LLUUID& folder_id, bool clear_nav_history = true); + void setGallerySelection(const LLUUID& item_id, bool new_window = false); + LLUUID getSingleFolderViewRoot(); + bool isSingleFolderMode() { return mSingleFolderMode; } + + void scrollToGallerySelection(); + void scrollToInvPanelSelection(); + + void setViewMode(EViewModeType mode); + bool isListViewMode() { return (mViewMode == MODE_LIST); } + bool isGalleryViewMode() { return (mViewMode == MODE_GALLERY); } + bool isCombinationViewMode() { return (mViewMode == MODE_COMBINATION); } + LLUUID getCurrentSFVRoot(); + std::string getLocalizedRootName(); + + LLInventoryFilter& getCurrentFilter(); + // Filter dropdown void onFilterTypeSelected(const std::string& filter_type_name); void updateFilterDropdown(const LLInventoryFilter* filter); @@ -149,6 +188,9 @@ protected: bool isSaveTextureEnabled(const LLSD& userdata); void updateItemcountText(); + void updatePanelVisibility(); + void updateCombinationVisibility(); + // Inventory Collapse and Expand Buttons void onCollapseButtonClicked(); void onExpandButtonClicked(); @@ -156,6 +198,9 @@ protected: void onFocusReceived(); void onSelectSearchType(); void updateSearchTypeCombo(); + void setSearchType(LLInventoryFilter::ESearchType type); + + LLSidepanelInventory* getParentSidepanelInventory(); private: LLFloaterInventoryFinder* getFinder(); @@ -176,12 +221,32 @@ private: std::string mCategoryCountString; LLComboBox* mSearchTypeCombo; + LLButton* mBackBtn; + LLButton* mForwardBtn; + LLButton* mUpBtn; + LLButton* mViewModeBtn; + LLLayoutPanel* mNavigationBtnsPanel; + + LLPanel* mDefaultViewPanel; + LLPanel* mCombinationViewPanel; + + bool mSingleFolderMode; + EViewModeType mViewMode; + + LLInventorySingleFolderPanel* mCombinationInventoryPanel; + LLInventoryGallery* mCombinationGalleryPanel; + LLPanel* mCombinationGalleryLayoutPanel; + LLLayoutPanel* mCombinationListLayoutPanel; + LLLayoutStack* mCombinationLayoutStack; + // Filter dropdown LLComboBox* mFilterComboBox; std::map mFilterMap; // contains name-to-number mapping for dropdown filter types U64 mFilterMask; // contains the cumulated bit filter for all dropdown filter types // Filter dropdown + boost::signals2::connection mListViewRootUpdatedConnection; + boost::signals2::connection mGalleryRootUpdatedConnection; ////////////////////////////////////////////////////////////////////////////////// // List Commands // @@ -190,11 +255,12 @@ protected: void updateListCommands(); void onAddButtonClick(); void showActionMenu(LLMenuGL* menu, std::string spawning_view_name); - void onTrashButtonClick(); + void onTrashButtonClick(); // Keep better inventory layout void onClipboardAction(const LLSD& userdata); BOOL isActionEnabled(const LLSD& command_name); BOOL isActionChecked(const LLSD& userdata); void onCustomAction(const LLSD& command_name); + bool isActionVisible(const LLSD& userdata); // FIRE-31369: Add inventory filter for coalesced objects void onCoalescedObjectsToggled(const LLSD& userdata); @@ -216,17 +282,27 @@ protected: void onSearchTypeChecked(const LLSD& userdata); // Extended Inventory Search + // Keep better inventory layout bool handleDragAndDropToTrash(BOOL drop, EDragAndDropType cargo_type, EAcceptance* accept); static bool hasSettingsInventory(); + void updateTitle(); + void updateNavButtons(); + + void onCombinationRootChanged(bool gallery_clicked); + void onCombinationGallerySelectionChanged(const LLUUID& category_id); + void onCombinationInventorySelectionChanged(const std::deque& items, BOOL user_action); /** * Set upload cost in "Upload" sub menu. */ void setUploadCostIfNeeded(); + void disableAddIfNeeded(); private: - LLDragAndDropButton* mTrashButton; + LLDragAndDropButton* mTrashButton; // Keep better inventory layout LLToggleableMenu* mMenuGearDefault; + LLToggleableMenu* mMenuViewDefault; LLToggleableMenu* mMenuVisibility; LLMenuButton* mGearMenuButton; + LLMenuButton* mViewMenuButton; LLMenuButton* mVisibilityMenuButton; LLHandle mMenuAddHandle; @@ -236,6 +312,10 @@ private: // Inventory Collapse and Expand Buttons bool mNeedUploadCost; + + bool mForceShowInvLayout; + bool mReshapeInvLayout; + LLUUID mCombInvUUIDNeedsRename; // List Commands // //////////////////////////////////////////////////////////////////////////////// }; diff --git a/indra/newview/llpanelmarketplaceinbox.cpp b/indra/newview/llpanelmarketplaceinbox.cpp index 53a0afeda6..e0eeeee11e 100644 --- a/indra/newview/llpanelmarketplaceinbox.cpp +++ b/indra/newview/llpanelmarketplaceinbox.cpp @@ -78,9 +78,6 @@ BOOL LLPanelMarketplaceInbox::postBuild() void LLPanelMarketplaceInbox::onSelectionChange(const std::deque& items, BOOL user_action) // { - LLSidepanelInventory* sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - - sidepanel_inventory->updateVerbs(); // FIRE-21948: Show element count in Received Items folder mInventoryPanel->onSelectionChange(items, user_action); diff --git a/indra/newview/llpanelmarketplaceinboxinventory.cpp b/indra/newview/llpanelmarketplaceinboxinventory.cpp index bdc38e0233..990e0abb69 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.cpp +++ b/indra/newview/llpanelmarketplaceinboxinventory.cpp @@ -64,10 +64,13 @@ LLInboxInventoryPanel::LLInboxInventoryPanel(const LLInboxInventoryPanel::Params : LLInventoryPanel(p) { LLInboxNewItemsStorage::getInstance()->load(); + LLInboxNewItemsStorage::getInstance()->addInboxPanel(this); } LLInboxInventoryPanel::~LLInboxInventoryPanel() -{} +{ + LLInboxNewItemsStorage::getInstance()->removeInboxPanel(this); +} void LLInboxInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) { @@ -128,6 +131,21 @@ LLFolderViewItem * LLInboxInventoryPanel::createFolderViewItem(LLInvFVBridge * b return LLUICtrlFactory::create(params); } +void LLInboxInventoryPanel::onRemoveItemFreshness(const LLUUID& item_id) +{ + LLInboxFolderViewFolder* inbox_folder_view = dynamic_cast(getFolderByID(item_id)); + if(inbox_folder_view) + { + inbox_folder_view->setFresh(false); + } + + LLInboxFolderViewItem* inbox_item_view = dynamic_cast(getItemByID(item_id)); + if(inbox_item_view) + { + inbox_item_view->setFresh(false); + } +} + // // LLInboxFolderViewFolder Implementation // @@ -364,4 +382,18 @@ void LLInboxNewItemsStorage::load() } } } + +void LLInboxNewItemsStorage::removeItem(const LLUUID& id) +{ + mNewItemsIDs.erase(id); + + //notify inbox panels + for (auto inbox : mInboxPanels) + { + if(inbox) + { + inbox->onRemoveItemFreshness(id); + } + } +} // eof diff --git a/indra/newview/llpanelmarketplaceinboxinventory.h b/indra/newview/llpanelmarketplaceinboxinventory.h index 3e508e801b..9eef5f209c 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.h +++ b/indra/newview/llpanelmarketplaceinboxinventory.h @@ -49,6 +49,8 @@ public: void initFromParams(const LLInventoryPanel::Params&); LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); LLFolderViewItem * createFolderViewItem(LLInvFVBridge * bridge); + + void onRemoveItemFreshness(const LLUUID& item_id); }; @@ -77,6 +79,7 @@ public: void deFreshify(); bool isFresh() const { return mFresh; } + void setFresh(bool is_fresh) { mFresh = is_fresh; } protected: bool mFresh; @@ -108,6 +111,7 @@ public: void deFreshify(); bool isFresh() const { return mFresh; } + void setFresh(bool is_fresh) { mFresh = is_fresh; } protected: bool mFresh; @@ -125,11 +129,16 @@ public: void load(); void addFreshItem(const LLUUID& id) { mNewItemsIDs.insert(id); } - void removeItem(const LLUUID& id) { mNewItemsIDs.erase(id); } + void removeItem(const LLUUID& id); bool isItemFresh(const LLUUID& id) { return (mNewItemsIDs.find(id) != mNewItemsIDs.end()); } + void addInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.insert(inbox); } + void removeInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.erase(inbox); } + private: std::set mNewItemsIDs; + + std::set mInboxPanels; }; #endif //LL_INBOXINVENTORYPANEL_H diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index 22e0b34a6f..747859d8e1 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -71,6 +71,7 @@ #include "rlvhandler.h" #include "rlvlocks.h" // [/RLVa:KB] +#include "llfloaterproperties.h" // Keep legacy properties floater const LLColor4U DEFAULT_WHITE(255, 255, 255); #include "tea.h" // @@ -122,6 +123,7 @@ public: virtual PermissionMask getPermissionMask() const { return PERM_NONE; } /*virtual*/ LLFolderType::EType getPreferredType() const { return LLFolderType::FT_NONE; } virtual const LLUUID& getUUID() const { return mUUID; } + virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null;} virtual time_t getCreationDate() const; virtual void setCreationDate(time_t creation_date_utc); @@ -130,6 +132,7 @@ public: virtual BOOL canOpenItem() const { return FALSE; } virtual void closeItem() {} virtual void selectItem() {} + virtual void navigateToFolder(bool new_window = false, bool change_mode = false) {} virtual BOOL isItemRenameable() const; virtual BOOL renameItem(const std::string& new_name); virtual BOOL isItemMovable() const; @@ -1574,6 +1577,7 @@ void LLPanelObjectInventory::inventoryChanged(LLViewerObject* object, mInventoryNeedsUpdate = TRUE; } + // Keep legacy properties floater // refresh any properties floaters that are hanging around. if(inventory) { @@ -1588,6 +1592,7 @@ void LLPanelObjectInventory::inventoryChanged(LLViewerObject* object, } } } + // } void LLPanelObjectInventory::updateInventory() diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index 58a642f53e..e5cb903b65 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -714,8 +714,12 @@ void LLPanelOutfitEdit::onFolderViewFilterCommitted(LLUICtrl* ctrl) LLOpenFoldersWithSelection opener; mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); - - LLInventoryModelBackgroundFetch::instance().start(); + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } } void LLPanelOutfitEdit::onListViewFilterCommitted(LLUICtrl* ctrl) @@ -752,8 +756,12 @@ void LLPanelOutfitEdit::onSearchEdit(const std::string& string) mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); } - - LLInventoryModelBackgroundFetch::instance().start(); + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } if (mInventoryItemsPanel->getFilterSubString().empty() && mSearchString.empty()) { diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp index 798cbc9923..daa87485fa 100644 --- a/indra/newview/llpaneloutfitsinventory.cpp +++ b/indra/newview/llpaneloutfitsinventory.cpp @@ -98,7 +98,7 @@ BOOL LLPanelOutfitsInventory::postBuild() // ( This is only necessary if we want to show a warning if a user deletes an item that has a // a link in an outfit, see "ConfirmItemDeleteHasLinks". ) - const LLUUID &outfits_cat = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS, false); + const LLUUID &outfits_cat = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); if (outfits_cat.notNull()) { LLInventoryModelBackgroundFetch::instance().start(outfits_cat); @@ -183,7 +183,11 @@ void LLPanelOutfitsInventory::onSearchEdit(const std::string& string) mActivePanel->setFilterSubString(LLStringUtil::null); } - LLInventoryModelBackgroundFetch::instance().start(); + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } if (mActivePanel->getFilterSubString().empty() && string.empty()) { diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 30f77c0e81..1793195050 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -71,6 +71,7 @@ #include "llpanelblockedlist.h" #include "llpanelprofileclassifieds.h" #include "llpanelprofilepicks.h" +#include "llthumbnailctrl.h" #include "lltrans.h" #include "llviewercontrol.h" #include "llviewermenu.h" //is_agent_mappable @@ -379,7 +380,7 @@ LLUUID post_profile_image(std::string cap_url, const LLSD &first_data, std::stri httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - LL_WARNS("AvatarProperties") << result << LL_ENDL; + LL_DEBUGS("AvatarProperties") << result << LL_ENDL; if (!status) { @@ -959,7 +960,7 @@ BOOL LLPanelProfileSecondLife::postBuild() mShowInSearchCheckbox = getChild("show_in_search"); // // Allow proper texture swatch handling - // mSecondLifePic = getChild("2nd_life_pic"); + // mSecondLifePic = getChild("2nd_life_pic"); mSecondLifePic = getChild("2nd_life_pic"); // mSecondLifePicLayout = getChild("image_panel"); @@ -2001,15 +2002,20 @@ void LLProfileImagePicker::notify(const std::vector& filenames) const S32 MAX_DIM = 256; if (!LLViewerTextureList::createUploadFile(file_path, temp_file, codec, MAX_DIM)) { - //todo: image not supported notification - LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)PROFILE_IMAGE_SL << ", failed to open image" << LL_ENDL; + LLSD notif_args; + notif_args["REASON"] = LLImage::getLastError().c_str(); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)mType << ", " << notif_args["REASON"].asString() << LL_ENDL; return; } std::string cap_url = gAgent.getRegionCapability(PROFILE_IMAGE_UPLOAD_CAP); if (cap_url.empty()) { - LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)PROFILE_IMAGE_SL << ", no cap found" << LL_ENDL; + LLSD args; + args["CAPABILITY"] = PROFILE_IMAGE_UPLOAD_CAP; + LLNotificationsUtil::add("RegionCapabilityRequestError", args); + LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)mType << ", no cap found" << LL_ENDL; return; } @@ -2500,30 +2506,16 @@ void LLPanelProfileSecondLife::onShowTexturePicker() mFloaterTexturePickerHandle = texture_floaterp->getHandle(); - texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLUUID id) + texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID&) { if (op == LLTextureCtrl::TEXTURE_SELECT) { - LLUUID image_asset_id; - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterTexturePickerHandle.get(); - if (floaterp) - { - if (id.notNull()) - { - image_asset_id = id; - } - else - { - image_asset_id = floaterp->getAssetID(); - } - } - - onCommitProfileImage(image_asset_id); + onCommitProfileImage(asset_id); } }); texture_floaterp->setLocalTextureEnabled(FALSE); texture_floaterp->setBakeTextureEnabled(FALSE); - texture_floaterp->setCanApply(false, true); + texture_floaterp->setCanApply(false, true, false); parent_floater->addDependentFloater(mFloaterTexturePickerHandle); @@ -2798,7 +2790,7 @@ BOOL LLPanelProfileFirstLife::postBuild() { mDescriptionEdit = getChild("fl_description_edit"); // Allow proper texture swatch handling - // mPicture = getChild("real_world_pic"); + // mPicture = getChild("real_world_pic"); mPicture = getChild("real_world_pic"); // @@ -2908,29 +2900,15 @@ void LLPanelProfileFirstLife::onChangePhoto() mFloaterTexturePickerHandle = texture_floaterp->getHandle(); - texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLUUID id) + texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID&) { if (op == LLTextureCtrl::TEXTURE_SELECT) { - LLUUID image_asset_id; - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterTexturePickerHandle.get(); - if (floaterp) - { - if (id.notNull()) - { - image_asset_id = id; - } - else - { - image_asset_id = floaterp->getAssetID(); - } - } - - onCommitPhoto(image_asset_id); + onCommitPhoto(asset_id); } }); texture_floaterp->setLocalTextureEnabled(FALSE); - texture_floaterp->setCanApply(false, true); + texture_floaterp->setCanApply(false, true, false); parent_floater->addDependentFloater(mFloaterTexturePickerHandle); diff --git a/indra/newview/llpanelprofile.h b/indra/newview/llpanelprofile.h index 82974b26d1..c0fda3dbb7 100644 --- a/indra/newview/llpanelprofile.h +++ b/indra/newview/llpanelprofile.h @@ -59,6 +59,7 @@ class LLTextBase; class LLMenuButton; class LLLineEditor; class LLTextEditor; +class LLThumbnailCtrl; class LLPanelProfileClassifieds; class LLPanelProfilePicks; class LLViewerFetchedTexture; @@ -222,7 +223,7 @@ private: LLCheckBoxCtrl* mShowInSearchCheckbox; // // Allow proper texture swatch handling - // LLIconCtrl* mSecondLifePic; + // LLThumbnailCtrl* mSecondLifePic; LLTextureCtrl* mSecondLifePic; // LLPanel* mSecondLifePicLayout; @@ -360,7 +361,7 @@ protected: LLTextEditor* mDescriptionEdit; // Allow proper texture swatch handling - // LLIconCtrl* mPicture; + // LLThumbnailCtrl* mPicture; LLTextureCtrl* mPicture; // LLButton* mUploadPhoto; diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp index 6edce546f2..04baa9b0f1 100644 --- a/indra/newview/llpanelwearing.cpp +++ b/indra/newview/llpanelwearing.cpp @@ -40,6 +40,7 @@ #include "llinventorymodel.h" #include "llinventoryobserver.h" #include "llmenubutton.h" +#include "lloutfitobserver.h" #include "llscrolllistctrl.h" #include "llviewermenu.h" #include "llviewerregion.h" @@ -253,8 +254,6 @@ LLPanelWearing::LLPanelWearing() , mIsInitialized(false) , mAttachmentsChangedConnection() { - mCategoriesObserver = new LLInventoryCategoriesObserver(); - mGearMenu = new LLWearingGearMenu(this); mContextMenu = new LLWearingContextMenu(); mAttachmentsMenu = new LLTempAttachmentsContextMenu(this); @@ -266,12 +265,6 @@ LLPanelWearing::~LLPanelWearing() delete mContextMenu; delete mAttachmentsMenu; - if (gInventory.containsObserver(mCategoriesObserver)) - { - gInventory.removeObserver(mCategoriesObserver); - } - delete mCategoriesObserver; - if (mAttachmentsChangedConnection.connected()) { mAttachmentsChangedConnection.disconnect(); @@ -321,10 +314,8 @@ void LLPanelWearing::onOpen(const LLSD& /*info*/) if (!category) return; - gInventory.addObserver(mCategoriesObserver); - // Start observing changes in Current Outfit category. - mCategoriesObserver->addCategory(cof, boost::bind(&LLWearableItemsList::updateList, mCOFItemsList, cof)); + LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLWearableItemsList::updateList, mCOFItemsList, cof)); // Fetch Current Outfit contents and refresh the list to display // initially fetched items. If not all items are fetched now diff --git a/indra/newview/llpanelwearing.h b/indra/newview/llpanelwearing.h index 18790ba726..d1d5d04fd5 100644 --- a/indra/newview/llpanelwearing.h +++ b/indra/newview/llpanelwearing.h @@ -97,7 +97,6 @@ private: // FIRE-22484: Double-click wear in outfits list void onDoubleClick(); - LLInventoryCategoriesObserver* mCategoriesObserver; LLWearableItemsList* mCOFItemsList; LLScrollListCtrl* mTempItemsList; LLWearingGearMenu* mGearMenu; diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index eeebf2e2fd..d1d5431654 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -58,7 +58,7 @@ #include "llviewerwindow.h" #include "lldrawable.h" #include "llfloaterinspect.h" -#include "llfloaterproperties.h" +#include "llfloaterproperties.h" // Keep legacy properties floater #include "llfloaterreporter.h" #include "llfloaterreg.h" #include "llfloatertools.h" @@ -7061,7 +7061,7 @@ void dialog_refresh_all() gMenuAttachmentOther->arrange(); } - LLFloaterProperties::dirtyAll(); + LLFloaterProperties::dirtyAll(); // Keep legacy properties floater LLFloaterInspect* inspect_instance = LLFloaterReg::findTypedInstance("inspect"); if(inspect_instance) diff --git a/indra/newview/llsettingsvo.cpp b/indra/newview/llsettingsvo.cpp index f5212a3026..eaa23d6a6c 100644 --- a/indra/newview/llsettingsvo.cpp +++ b/indra/newview/llsettingsvo.cpp @@ -95,6 +95,20 @@ namespace //========================================================================= +void LLSettingsVOBase::createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID& parent_id, std::function created_cb) +{ + inventory_result_fn cb = NULL; + + if (created_cb != NULL) + { + cb = [created_cb](LLUUID asset_id, LLUUID inventory_id, LLUUID object_id, LLSD results) + { + created_cb(inventory_id); + }; + } + createNewInventoryItem(stype, parent_id, cb); +} + void LLSettingsVOBase::createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID &parent_id, inventory_result_fn callback) { LLTransactionID tid; diff --git a/indra/newview/llsettingsvo.h b/indra/newview/llsettingsvo.h index 05ec0e9275..4f410ab7d9 100644 --- a/indra/newview/llsettingsvo.h +++ b/indra/newview/llsettingsvo.h @@ -49,6 +49,7 @@ public: typedef std::function inventory_download_fn; typedef std::function inventory_result_fn; + static void createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID& parent_id, std::function created_cb); static void createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID &parent_id, inventory_result_fn callback = inventory_result_fn()); static void createInventoryItem(const LLSettingsBase::ptr_t &settings, const LLUUID &parent_id, std::string settings_name, inventory_result_fn callback = inventory_result_fn()); static void createInventoryItem(const LLSettingsBase::ptr_t &settings, U32 next_owner_perm, const LLUUID &parent_id, std::string settings_name, inventory_result_fn callback = inventory_result_fn()); diff --git a/indra/newview/llsidepanelinventory.cpp b/indra/newview/llsidepanelinventory.cpp index 51ddbe1c41..d9abdfeb77 100644 --- a/indra/newview/llsidepanelinventory.cpp +++ b/indra/newview/llsidepanelinventory.cpp @@ -80,6 +80,8 @@ static const char * const INBOX_RELOAD_BUTTON_NAME = "reload_received_items_btn" static const char * const INVENTORY_LAYOUT_STACK_NAME = "inventory_layout_stack"; static const char * const MARKETPLACE_INBOX_PANEL = "marketplace_inbox"; +static bool sLoginCompleted = false; + bool LLSidepanelInventory::sInboxInitalized = false; // Inbox panel randomly shown on secondary inventory windows // @@ -124,11 +126,11 @@ private: LLSidepanelInventory::LLSidepanelInventory() : LLPanel() - , mItemPanel(NULL) , mPanelMainInventory(NULL) , mInboxEnabled(false) , mCategoriesObserver(NULL) , mInboxAddedObserver(NULL) + , mInboxLayoutPanel(NULL) { //buildFromFile( "panel_inventory.xml"); // Called from LLRegisterPanelClass::defaultPanelClassBuilder() } @@ -136,14 +138,12 @@ LLSidepanelInventory::LLSidepanelInventory() LLSidepanelInventory::~LLSidepanelInventory() { // FIRE-17603: Received Items button sometimes vanishing - //LLLayoutPanel* inbox_layout_panel = getChild(INBOX_LAYOUT_PANEL_NAME); - LLLayoutPanel* inbox_layout_panel = findChild(INBOX_LAYOUT_PANEL_NAME); - if (inbox_layout_panel) + if (mInboxLayoutPanel) { // // Save the InventoryMainPanelHeight in settings per account - gSavedPerAccountSettings.setS32("InventoryInboxHeight", inbox_layout_panel->getTargetDim()); + gSavedPerAccountSettings.setS32("InventoryInboxHeight", mInboxLayoutPanel->getTargetDim()); // FIRE-17603: Received Items button sometimes vanishing } // @@ -175,29 +175,11 @@ BOOL LLSidepanelInventory::postBuild() // UI elements from inventory panel { mInventoryPanel = getChild("sidepanel_inventory_panel"); - - mInfoBtn = mInventoryPanel->getChild("info_btn"); - mInfoBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onInfoButtonClicked, this)); - - mShareBtn = mInventoryPanel->getChild("share_btn"); - mShareBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onShareButtonClicked, this)); - - mShopBtn = mInventoryPanel->getChild("shop_btn"); - mShopBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onShopButtonClicked, this)); - - mWearBtn = mInventoryPanel->getChild("wear_btn"); - mWearBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onWearButtonClicked, this)); - - mPlayBtn = mInventoryPanel->getChild("play_btn"); - mPlayBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onPlayButtonClicked, this)); - - mTeleportBtn = mInventoryPanel->getChild("teleport_btn"); - mTeleportBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onTeleportButtonClicked, this)); mPanelMainInventory = mInventoryPanel->getChild("panel_main_inventory"); mPanelMainInventory->setSelectCallback(boost::bind(&LLSidepanelInventory::onSelectionChange, this, _1, _2)); - LLTabContainer* tabs = mPanelMainInventory->getChild("inventory filter tabs"); - tabs->setCommitCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); + //LLTabContainer* tabs = mPanelMainInventory->getChild("inventory filter tabs"); + //tabs->setCommitCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); // Add reload button to inventory inbox if (LLButton* reload_inbox_btn = getChild(INBOX_RELOAD_BUTTON_NAME) ; reload_inbox_btn) @@ -213,25 +195,7 @@ BOOL LLSidepanelInventory::postBuild() my_inventory_panel->addHideFolderType(LLFolderType::FT_FAVORITE); */ - LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); - } - - // UI elements from item panel - { - mItemPanel = getChild("sidepanel__item_panel"); - - LLButton* back_btn = mItemPanel->getChild("back_btn"); - back_btn->setClickedCallback(boost::bind(&LLSidepanelInventory::onBackButtonClicked, this)); - } - - // UI elements from task panel - { - mTaskPanel = findChild("sidepanel__task_panel"); - if (mTaskPanel) - { - LLButton* back_btn = mTaskPanel->getChild("back_btn"); - back_btn->setClickedCallback(boost::bind(&LLSidepanelInventory::onBackButtonClicked, this)); - } + //LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); } // Received items inbox setup @@ -249,17 +213,31 @@ BOOL LLSidepanelInventory::postBuild() inbox_button->setCommitCallback(boost::bind(&LLSidepanelInventory::onToggleInboxBtn, this)); - // Get the previous inbox state from "InventoryInboxToggleState" setting. - bool is_inbox_collapsed = !inbox_button->getToggleState(); + // For main Inventory floater: Get the previous inbox state from "InventoryInboxToggleState" setting. + // For additional Inventory floaters: Collapsed state is default. + bool is_inbox_collapsed = !inbox_button->getToggleState();// || sLoginCompleted; // Show inbox on main inventory window only // Restore the collapsed inbox panel state - LLLayoutPanel* inbox_panel = getChild(INBOX_LAYOUT_PANEL_NAME); - inv_stack->collapsePanel(inbox_panel, is_inbox_collapsed); - if (!is_inbox_collapsed) - { - inbox_panel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); - } + mInboxLayoutPanel = getChild(INBOX_LAYOUT_PANEL_NAME); + inv_stack->collapsePanel(mInboxLayoutPanel, is_inbox_collapsed); + if (!is_inbox_collapsed) + { + mInboxLayoutPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); + } + // Show inbox on main inventory window only + //if (sLoginCompleted) + //{ + // //save the state of Inbox panel only for main Inventory floater + // inbox_button->removeControlVariable(); + // inbox_button->setToggleState(false); + // updateInbox(); + //} + //else + //{ + // // Trigger callback for after login so we can setup to track inbox changes after initial inventory load + // LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLSidepanelInventory::updateInbox, this)); + //} // Set the inbox visible based on debug settings (final setting comes from http request below) // FIRE-17603: Received Items button sometimes vanishing //enableInbox(gSavedSettings.getBOOL("InventoryDisplayInbox")); @@ -269,6 +247,7 @@ BOOL LLSidepanelInventory::postBuild() // Trigger callback for after login so we can setup to track inbox changes after initial inventory load LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLSidepanelInventory::updateInbox, this)); + // // Optional hiding of Received Items folder aka Inbox gSavedSettings.getControl("FSShowInboxFolder")->getSignal()->connect(boost::bind(&LLSidepanelInventory::refreshInboxVisibility, this)); @@ -280,18 +259,27 @@ BOOL LLSidepanelInventory::postBuild() // Optional hiding of Received Items folder aka Inbox //gSavedSettings.getControl("InventoryDisplayInbox")->getCommitSignal()->connect(boost::bind(&handleInventoryDisplayInboxChanged)); - // Update the verbs buttons state. - updateVerbs(); + LLFloater *floater = dynamic_cast(getParent()); + if (floater && floater->getKey().isUndefined() && !sLoginCompleted) + { + // Prefill inventory for primary inventory floater + // Other floaters should fill on visibility change + // + // see get_instance_num(); + // Primary inventory floater will have undefined key + initInventoryViews(); + } return TRUE; } void LLSidepanelInventory::updateInbox() { + sLoginCompleted = true; // // Track inbox folder changes // - const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX, true); + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); // Set up observer to listen for creation of inbox if it doesn't exist if (inbox_id.isNull()) @@ -376,16 +364,42 @@ void LLSidepanelInventory::enableInbox(bool enabled) { mInboxEnabled = enabled; - LLLayoutPanel * inbox_layout_panel = getChild(INBOX_LAYOUT_PANEL_NAME); + if(!enabled || !mPanelMainInventory->isSingleFolderMode()) + { + toggleInbox(); + } +} + +void LLSidepanelInventory::hideInbox() +{ + if (mInboxLayoutPanel) // Inbox panel randomly shown on secondary inventory window + { + mInboxLayoutPanel->setVisible(false); + if (LLLayoutStack* inv_stack = findChild(INVENTORY_LAYOUT_STACK_NAME); inv_stack) + { + inv_stack->setPanelSpacing(0); + } + } +} + +void LLSidepanelInventory::toggleInbox() +{ // Optional hiding of Received Items folder aka Inbox - //inbox_layout_panel->setVisible(enabled); - inbox_layout_panel->setVisible(enabled && (!gSavedSettings.getBOOL("FSShowInboxFolder") || gSavedSettings.getBOOL("FSAlwaysShowInboxButton")) -// Show Received Items panel only in Second Life + //mInboxLayoutPanel->setVisible(mInboxEnabled); + if (mInboxLayoutPanel) + { + mInboxLayoutPanel->setVisible(mInboxEnabled && (!gSavedSettings.getBOOL("FSShowInboxFolder") || gSavedSettings.getBOOL("FSAlwaysShowInboxButton")) + // Show Received Items panel only in Second Life #ifdef OPENSIM - && LLGridManager::getInstance()->isInSecondLife() + && LLGridManager::getInstance()->isInSecondLife() #endif // OPENSIM - ); -// + ); + + if (LLLayoutStack* inv_stack = findChild(INVENTORY_LAYOUT_STACK_NAME); inv_stack) + { + inv_stack->setPanelSpacing(mInboxLayoutPanel->getVisible() ? gSavedSettings.getS32("UIResizeBarHeight") : 0); + } + } } // Optional hiding of Received Items folder aka Inbox @@ -422,25 +436,24 @@ void LLSidepanelInventory::onInboxChanged(const LLUUID& inbox_id) void LLSidepanelInventory::onToggleInboxBtn() { LLButton* inboxButton = getChild(INBOX_BUTTON_NAME); - LLLayoutPanel* inboxPanel = getChild(INBOX_LAYOUT_PANEL_NAME); LLLayoutStack* inv_stack = getChild(INVENTORY_LAYOUT_STACK_NAME); const bool inbox_expanded = inboxButton->getToggleState(); // Expand/collapse the indicated panel - inv_stack->collapsePanel(inboxPanel, !inbox_expanded); + inv_stack->collapsePanel(mInboxLayoutPanel, !inbox_expanded); if (inbox_expanded) { - inboxPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); - if (inboxPanel->isInVisibleChain()) + mInboxLayoutPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); + if (mInboxLayoutPanel->isInVisibleChain()) { gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); } } else { - gSavedPerAccountSettings.setS32("InventoryInboxHeight", inboxPanel->getTargetDim()); + gSavedPerAccountSettings.setS32("InventoryInboxHeight", mInboxLayoutPanel->getTargetDim()); } } @@ -464,6 +477,8 @@ void LLSidepanelInventory::onOpen(const LLSD& key) } #endif + gAgent.showLatestFeatureNotification("inventory"); + if(key.size() == 0) { // set focus on filter editor when side tray inventory shows up @@ -471,45 +486,6 @@ void LLSidepanelInventory::onOpen(const LLSD& key) filter_editor->setFocus(TRUE); return; } - - mItemPanel->reset(); - - if (key.has("id")) - { - mItemPanel->setItemID(key["id"].asUUID()); - if (key.has("object")) - { - mItemPanel->setObjectID(key["object"].asUUID()); - } - showItemInfoPanel(); - } - if (key.has("task")) - { - if (mTaskPanel) - mTaskPanel->setObjectSelection(LLSelectMgr::getInstance()->getSelection()); - showTaskInfoPanel(); - } -} - -void LLSidepanelInventory::onInfoButtonClicked() -{ - LLInventoryItem *item = getSelectedItem(); - if (item) - { - mItemPanel->reset(); - mItemPanel->setItemID(item->getUUID()); - showItemInfoPanel(); - } -} - -void LLSidepanelInventory::onShareButtonClicked() -{ - LLAvatarActions::shareWithAvatars(this); -} - -void LLSidepanelInventory::onShopButtonClicked() -{ - LLWeb::loadURL(gSavedSettings.getString("MarketplaceURL")); } void LLSidepanelInventory::performActionOnSelection(const std::string &action) @@ -531,47 +507,6 @@ void LLSidepanelInventory::performActionOnSelection(const std::string &action) static_cast(current_item->getViewModelItem())->performAction(mPanelMainInventory->getActivePanel()->getModel(), action); } -void LLSidepanelInventory::onWearButtonClicked() -{ - // Get selected items set. - const std::set selected_uuids_set = LLAvatarActions::getInventorySelectedUUIDs(); - if (selected_uuids_set.empty()) return; // nothing selected - - // Convert the set to a vector. - uuid_vec_t selected_uuids_vec; - for (std::set::const_iterator it = selected_uuids_set.begin(); it != selected_uuids_set.end(); ++it) - { - selected_uuids_vec.push_back(*it); - } - - // Wear all selected items. - wear_multiple(selected_uuids_vec, true); -} - -void LLSidepanelInventory::onPlayButtonClicked() -{ - const LLInventoryItem *item = getSelectedItem(); - if (!item) - { - return; - } - - switch(item->getInventoryType()) - { - case LLInventoryType::IT_GESTURE: - performActionOnSelection("play"); - break; - default: - performActionOnSelection("open"); - break; - } -} - -void LLSidepanelInventory::onTeleportButtonClicked() -{ - performActionOnSelection("teleport"); -} - void LLSidepanelInventory::onBackButtonClicked() { showInventoryPanel(); @@ -579,102 +514,17 @@ void LLSidepanelInventory::onBackButtonClicked() void LLSidepanelInventory::onSelectionChange(const std::deque &items, BOOL user_action) { - updateVerbs(); -} -void LLSidepanelInventory::showItemInfoPanel() -{ - mItemPanel->setVisible(TRUE); - if (mTaskPanel) - mTaskPanel->setVisible(FALSE); - mInventoryPanel->setVisible(FALSE); - - mItemPanel->dirty(); - mItemPanel->setIsEditing(FALSE); -} - -void LLSidepanelInventory::showTaskInfoPanel() -{ - mItemPanel->setVisible(FALSE); - mInventoryPanel->setVisible(FALSE); - - if (mTaskPanel) - { - mTaskPanel->setVisible(TRUE); - mTaskPanel->dirty(); - mTaskPanel->setIsEditing(FALSE); - } } void LLSidepanelInventory::showInventoryPanel() { - mItemPanel->setVisible(FALSE); - if (mTaskPanel) - mTaskPanel->setVisible(FALSE); mInventoryPanel->setVisible(TRUE); - updateVerbs(); } -void LLSidepanelInventory::updateVerbs() +void LLSidepanelInventory::initInventoryViews() { - mInfoBtn->setEnabled(FALSE); - mShareBtn->setEnabled(FALSE); - - mWearBtn->setVisible(FALSE); - mWearBtn->setEnabled(FALSE); - mPlayBtn->setVisible(FALSE); - mPlayBtn->setEnabled(FALSE); - mPlayBtn->setToolTip(std::string("")); - mTeleportBtn->setVisible(FALSE); - mTeleportBtn->setEnabled(FALSE); - mShopBtn->setVisible(TRUE); - - mShareBtn->setEnabled(canShare()); - - const LLInventoryItem *item = getSelectedItem(); - if (!item) - return; - - bool is_single_selection = getSelectedCount() == 1; - - mInfoBtn->setEnabled(is_single_selection); - - switch(item->getInventoryType()) - { - case LLInventoryType::IT_WEARABLE: - case LLInventoryType::IT_OBJECT: - case LLInventoryType::IT_ATTACHMENT: - mWearBtn->setVisible(TRUE); - mWearBtn->setEnabled(canWearSelected()); - mShopBtn->setVisible(FALSE); - break; - case LLInventoryType::IT_SOUND: - mPlayBtn->setVisible(TRUE); - mPlayBtn->setEnabled(TRUE); - mPlayBtn->setToolTip(LLTrans::getString("InventoryPlaySoundTooltip")); - mShopBtn->setVisible(FALSE); - break; - case LLInventoryType::IT_GESTURE: - mPlayBtn->setVisible(TRUE); - mPlayBtn->setEnabled(TRUE); - mPlayBtn->setToolTip(LLTrans::getString("InventoryPlayGestureTooltip")); - mShopBtn->setVisible(FALSE); - break; - case LLInventoryType::IT_ANIMATION: - mPlayBtn->setVisible(TRUE); - mPlayBtn->setEnabled(TRUE); - mPlayBtn->setEnabled(TRUE); - mPlayBtn->setToolTip(LLTrans::getString("InventoryPlayAnimationTooltip")); - mShopBtn->setVisible(FALSE); - break; - case LLInventoryType::IT_LANDMARK: - mTeleportBtn->setVisible(TRUE); - mTeleportBtn->setEnabled(TRUE); - mShopBtn->setVisible(FALSE); - break; - default: - break; - } + mPanelMainInventory->initInventoryViews(); } bool LLSidepanelInventory::canShare() @@ -797,12 +647,10 @@ void LLSidepanelInventory::clearSelections(bool clearMain, bool clearInbox) } } - if (clearInbox && mInboxEnabled && mInventoryPanelInbox.get()) + if (clearInbox && mInboxEnabled && !mInventoryPanelInbox.isDead()) { mInventoryPanelInbox.get()->getRootFolder()->clearSelection(); } - - updateVerbs(); } std::set LLSidepanelInventory::getInboxSelectionList() @@ -845,7 +693,7 @@ void LLSidepanelInventory::cleanup() // Add reload button to inventory inbox void LLSidepanelInventory::onReloadInboxClicked() { - const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX, true); + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); if (LLViewerInventoryCategory* cat = gInventory.getCategory(inbox_id); cat) { diff --git a/indra/newview/llsidepanelinventory.h b/indra/newview/llsidepanelinventory.h index bf6113267c..2214f3a052 100644 --- a/indra/newview/llsidepanelinventory.h +++ b/indra/newview/llsidepanelinventory.h @@ -66,9 +66,8 @@ public: void clearSelections(bool clearMain, bool clearInbox); std::set getInboxSelectionList(); - void showItemInfoPanel(); - void showTaskInfoPanel(); void showInventoryPanel(); + void initInventoryViews(); // checks can share selected item(s) bool canShare(); @@ -76,6 +75,8 @@ public: void onToggleInboxBtn(); void enableInbox(bool enabled); + void toggleInbox(); + void hideInbox(); // Optional hiding of Received Items folder aka Inbox void refreshInboxVisibility(); @@ -84,8 +85,6 @@ public: bool isInboxEnabled() const { return mInboxEnabled; } - void updateVerbs(); - static void cleanup(); protected: @@ -106,29 +105,16 @@ protected: private: LLPanel* mInventoryPanel; // Main inventory view LLHandle mInventoryPanelInbox; - LLSidepanelItemInfo* mItemPanel; // Individual item view - LLSidepanelTaskInfo* mTaskPanel; // Individual in-world object view LLPanelMainInventory* mPanelMainInventory; + LLLayoutPanel* mInboxLayoutPanel; + protected: - void onInfoButtonClicked(); - void onShareButtonClicked(); - void onShopButtonClicked(); - void onWearButtonClicked(); - void onPlayButtonClicked(); - void onTeleportButtonClicked(); void onReloadInboxClicked(); // Add reload button to inventory inbox public: void onBackButtonClicked(); private: - LLButton* mInfoBtn; - LLButton* mShareBtn; - LLButton* mWearBtn; - LLButton* mPlayBtn; - LLButton* mTeleportBtn; - LLButton* mShopBtn; - bool mInboxEnabled; LLInventoryCategoriesObserver* mCategoriesObserver; diff --git a/indra/newview/llsidepaneliteminfo.cpp b/indra/newview/llsidepaneliteminfo.cpp index 9f0d43fefa..100747445e 100644 --- a/indra/newview/llsidepaneliteminfo.cpp +++ b/indra/newview/llsidepaneliteminfo.cpp @@ -31,16 +31,23 @@ #include "llagent.h" #include "llavataractions.h" +#include "llavatarnamecache.h" #include "llbutton.h" +#include "llcallbacklist.h" #include "llcombobox.h" +#include "llfloater.h" #include "llfloaterreg.h" #include "llgroupactions.h" +#include "llgroupmgr.h" +#include "lliconctrl.h" #include "llinventorydefines.h" +#include "llinventoryicon.h" #include "llinventorymodel.h" #include "llinventoryobserver.h" #include "lllineeditor.h" #include "llradiogroup.h" #include "llslurl.h" +#include "lltexteditor.h" #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewerobjectlist.h" @@ -76,49 +83,6 @@ private: S32 mId; }; -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLItemPropertiesObserver -// -// Helper class to watch for changes to the item. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLItemPropertiesObserver : public LLInventoryObserver -{ -public: - LLItemPropertiesObserver(LLSidepanelItemInfo* floater) - : mFloater(floater) - { - gInventory.addObserver(this); - } - virtual ~LLItemPropertiesObserver() - { - gInventory.removeObserver(this); - } - virtual void changed(U32 mask); -private: - LLSidepanelItemInfo* mFloater; // Not a handle because LLSidepanelItemInfo is managing LLItemPropertiesObserver -}; - -void LLItemPropertiesObserver::changed(U32 mask) -{ - const std::set& mChangedItemIDs = gInventory.getChangedIDs(); - std::set::const_iterator it; - - const LLUUID& item_id = mFloater->getItemID(); - - for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) - { - // set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288) - if (*it == item_id) - { - // if there's a change we're interested in. - if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0) - { - mFloater->dirty(); - } - } - } -} - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLObjectInventoryObserver // @@ -162,36 +126,48 @@ static LLPanelInjector t_item_info("sidepanel_item_info"); // Default constructor LLSidepanelItemInfo::LLSidepanelItemInfo(const LLPanel::Params& p) - : LLSidepanelInventorySubpanel(p) + : LLPanel(p) , mItemID(LLUUID::null) , mObjectInventoryObserver(NULL) , mUpdatePendingId(-1) + , mIsDirty(false) /*Not ready*/ + , mParentFloater(NULL) { - mPropertiesObserver = new LLItemPropertiesObserver(this); + gInventory.addObserver(this); + gIdleCallbacks.addFunction(&LLSidepanelItemInfo::onIdle, (void*)this); } // Destroys the object LLSidepanelItemInfo::~LLSidepanelItemInfo() { - delete mPropertiesObserver; - mPropertiesObserver = NULL; + gInventory.removeObserver(this); + gIdleCallbacks.deleteFunction(&LLSidepanelItemInfo::onIdle, (void*)this); stopObjectInventoryObserver(); + + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } } // virtual BOOL LLSidepanelItemInfo::postBuild() { - LLSidepanelInventorySubpanel::postBuild(); - + mChangeThumbnailBtn = getChild("change_thumbnail_btn"); + mItemTypeIcon = getChild("item_type_icon"); + mLabelOwnerName = getChild("LabelOwnerName"); + mLabelCreatorName = getChild("LabelCreatorName"); + getChild("LabelItemName")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); getChild("LabelItemName")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitName,this)); - getChild("LabelItemDesc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); getChild("LabelItemDesc")->setCommitCallback(boost::bind(&LLSidepanelItemInfo:: onCommitDescription, this)); - // Creator information - getChild("BtnCreator")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onClickCreator,this)); - // owner information - getChild("BtnOwner")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onClickOwner,this)); + // Thumnail edition + mChangeThumbnailBtn->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onEditThumbnail, this)); // acquired date // owner permissions // Permissions debug text @@ -230,6 +206,12 @@ void LLSidepanelItemInfo::setItemID(const LLUUID& item_id) mItemID = item_id; mUpdatePendingId = -1; } + dirty(); +} + +void LLSidepanelItemInfo::setParentFloater(LLFloater* parent) +{ + mParentFloater = parent; } const LLUUID& LLSidepanelItemInfo::getObjectID() const @@ -253,12 +235,11 @@ void LLSidepanelItemInfo::onUpdateCallback(const LLUUID& item_id, S32 received_u void LLSidepanelItemInfo::reset() { - LLSidepanelInventorySubpanel::reset(); - mObjectID = LLUUID::null; mItemID = LLUUID::null; stopObjectInventoryObserver(); + dirty(); } void LLSidepanelItemInfo::refresh() @@ -266,60 +247,37 @@ void LLSidepanelItemInfo::refresh() LLViewerInventoryItem* item = findItem(); if(item) { - refreshFromItem(item); - updateVerbs(); + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + bool in_trash = (item->getUUID() == trash_id) || gInventory.isObjectDescendentOf(item->getUUID(), trash_id); + if (in_trash && mParentFloater) + { + // Close properties when moving to trash + // Aren't supposed to view properties from trash + mParentFloater->closeFloater(); + } + else + { + refreshFromItem(item); + } return; } - else - { - if (getIsEditing()) - { - setIsEditing(FALSE); - } - } - if (!getIsEditing()) - { - const std::string no_item_names[]={ - "LabelItemName", - "LabelItemDesc", - "LabelCreatorName", - "LabelOwnerName" - }; - - for(size_t t=0; tsetEnabled(false); - } - - setPropertiesFieldsEnabled(false); - - const std::string hide_names[]={ - "BaseMaskDebug", - "OwnerMaskDebug", - "GroupMaskDebug", - "EveryoneMaskDebug", - "NextMaskDebug" - }; - for(size_t t=0; tsetVisible(false); - } - } - - if (!item) - { - const std::string no_edit_mode_names[]={ - "BtnCreator", - "BtnOwner", - }; - for(size_t t=0; tsetEnabled(false); - } - } - - updateVerbs(); + if (mObjectID.notNull()) + { + LLViewerObject* object = gObjectList.findObject(mObjectID); + if (object) + { + // Object exists, but object's content is not nessesary + // loaded, so assume item exists as well + return; + } + } + + if (mParentFloater) + { + // if we failed to get item, it likely no longer exists + mParentFloater->closeFloater(); + } } void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) @@ -337,7 +295,7 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) } // do not enable the UI for incomplete items. - BOOL is_complete = item->isFinished(); + bool is_complete = item->isFinished(); const BOOL cannot_restrict_permissions = LLInventoryType::cannotRestrictPermissions(item->getInventoryType()); const BOOL is_calling_card = (item->getInventoryType() == LLInventoryType::IT_CALLINGCARD); const BOOL is_settings = (item->getInventoryType() == LLInventoryType::IT_SETTINGS); @@ -389,8 +347,22 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) getChild("LabelItemName")->setValue(item->getName()); getChildView("LabelItemDescTitle")->setEnabled(TRUE); getChildView("LabelItemDesc")->setEnabled(is_modifiable); - getChildView("IconLocked")->setVisible(!is_modifiable); getChild("LabelItemDesc")->setValue(item->getDescription()); + getChild("item_thumbnail")->setValue(item->getThumbnailUUID()); + + LLUIImagePtr icon_img = LLInventoryIcon::getIcon(item->getType(), item->getInventoryType(), item->getFlags(), FALSE); + mItemTypeIcon->setImage(icon_img); + + // Style for creator and owner links + LLStyle::Params style_params; + LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.color = link_color; + style_params.readonly_color = link_color; + style_params.is_link = true; // link will be added later + const LLFontGL* fontp = mLabelCreatorName->getFont(); + style_params.font.name = LLFontGL::nameFromFont(fontp); + style_params.font.size = LLFontGL::sizeFromFont(fontp); + style_params.font.style = "UNDERLINE"; ////////////////// // CREATOR NAME // @@ -401,9 +373,8 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) if (item->getCreatorUUID().notNull()) { LLUUID creator_id = item->getCreatorUUID(); -// std::string name = -// LLSLURL("agent", creator_id, "completename").getSLURLString(); -// getChildView("BtnCreator")->setEnabled(TRUE); +// std::string slurl = +// LLSLURL("agent", creator_id, "inspect").getSLURLString(); // [RLVa:KB] - Checked: RLVa-2.0.1 // If the object creator matches the object owner we need to anonymize the creator field as well bool fRlvCanShowCreator = true; @@ -412,19 +383,43 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) { fRlvCanShowCreator = false; } - std::string name = LLSLURL("agent", creator_id, (fRlvCanShowCreator) ? "completename" : "rlvanonym").getSLURLString(); - getChildView("BtnCreator")->setEnabled(fRlvCanShowCreator); + std::string slurl = LLSLURL("agent", creator_id, (fRlvCanShowCreator) ? "inspect" : "rlvanonym").getSLURLString(); +// [/RLVa:KB] + + style_params.link_href = slurl; + + LLAvatarName av_name; + if (LLAvatarNameCache::get(creator_id, &av_name)) + { +// updateCreatorName(creator_id, av_name, style_params); +// [RLVa:KB] - Checked: RLVa-2.0.1 + updateCreatorName(creator_id, av_name, style_params, !fRlvCanShowCreator); // [/RLVa:KB] + } + else + { + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } + mLabelCreatorName->setText(LLTrans::getString("None")); +// mCreatorCacheConnection = LLAvatarNameCache::get(creator_id, boost::bind(&LLSidepanelItemInfo::updateCreatorName, this, _1, _2, style_params)); +// [RLVa:KB] - Checked: RLVa-2.0.1 + mCreatorCacheConnection = LLAvatarNameCache::get(creator_id, boost::bind(&LLSidepanelItemInfo::updateCreatorName, this, _1, _2, style_params, !fRlvCanShowCreator)); +// [/RLVa:KB] + } + getChildView("LabelCreatorTitle")->setEnabled(TRUE); - getChildView("LabelCreatorName")->setEnabled(FALSE); - getChild("LabelCreatorName")->setValue(name); +// mLabelCreatorName->setEnabled(TRUE); +// [RLVa:KB] - Checked: RLVa-2.0.1 + mLabelCreatorName->setEnabled(fRlvCanShowCreator); +// [/RLVa:KB] } else { - getChildView("BtnCreator")->setEnabled(FALSE); getChildView("LabelCreatorTitle")->setEnabled(FALSE); - getChildView("LabelCreatorName")->setEnabled(FALSE); - getChild("LabelCreatorName")->setValue(getString("unknown_multiple")); + mLabelCreatorName->setEnabled(FALSE); + mLabelCreatorName->setValue(getString("unknown_multiple")); } //////////////// @@ -435,35 +430,73 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) // [RLVa:KB] - Checked: RVLa-2.0.1 bool fRlvCanShowOwner = true; // [/RLVa:KB] - std::string name; + std::string slurl; if (perm.isGroupOwned()) { - gCacheName->getGroupName(perm.getGroup(), name); + LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(perm.getGroup()); + + slurl = LLSLURL("group", perm.getGroup(), "inspect").getSLURLString(); + style_params.link_href = slurl; + if (group_data && group_data->isGroupPropertiesDataComplete()) + { + mLabelOwnerName->setText(group_data->mName, style_params); + } + else + { + // Triggers refresh + LLGroupMgr::getInstance()->sendGroupPropertiesRequest(perm.getGroup()); + + std::string name; + gCacheName->getGroupName(perm.getGroup(), name); + mLabelOwnerName->setText(name, style_params); + } } else { LLUUID owner_id = perm.getOwner(); -// name = LLSLURL("agent", owner_id, "completename").getSLURLString(); +// slurl = LLSLURL("agent", owner_id, "inspect").getSLURLString(); // [RLVa:KB] - Checked: RLVa-2.0.1 fRlvCanShowOwner = RlvActions::canShowName(RlvActions::SNC_DEFAULT, owner_id); - name = LLSLURL("agent", owner_id, (fRlvCanShowOwner) ? "completename" : "rlvanonym").getSLURLString(); + slurl = LLSLURL("agent", owner_id, (fRlvCanShowOwner) ? "inspect" : "rlvanonym").getSLURLString(); // [/RLVa:KB] - } -// getChildView("BtnOwner")->setEnabled(TRUE); + + style_params.link_href = slurl; + LLAvatarName av_name; + if (LLAvatarNameCache::get(owner_id, &av_name)) + { +// updateOwnerName(owner_id, av_name, style_params); // [RLVa:KB] - Checked: RLVa-2.0.1 - getChildView("BtnOwner")->setEnabled(fRlvCanShowOwner); + updateOwnerName(owner_id, av_name, style_params, !fRlvCanShowOwner); // [/RLVa:KB] + } + else + { + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + mLabelOwnerName->setText(LLTrans::getString("None")); +// mOwnerCacheConnection = LLAvatarNameCache::get(owner_id, boost::bind(&LLSidepanelItemInfo::updateOwnerName, this, _1, _2, style_params)); +// [RLVa:KB] - Checked: RLVa-2.0.1 + mOwnerCacheConnection = LLAvatarNameCache::get(owner_id, boost::bind(&LLSidepanelItemInfo::updateOwnerName, this, _1, _2, style_params, !fRlvCanShowOwner)); +// [/RLVa:KB] + } + } getChildView("LabelOwnerTitle")->setEnabled(TRUE); - getChildView("LabelOwnerName")->setEnabled(FALSE); - getChild("LabelOwnerName")->setValue(name); +// mLabelOwnerName->setEnabled(TRUE); +// [RLVa:KB] - Checked: RLVa-2.0.1 + mLabelOwnerName->setEnabled(fRlvCanShowOwner); +// [/RLVa:KB] } else { - getChildView("BtnOwner")->setEnabled(FALSE); getChildView("LabelOwnerTitle")->setEnabled(FALSE); - getChildView("LabelOwnerName")->setEnabled(FALSE); - getChild("LabelOwnerName")->setValue(getString("public")); + mLabelOwnerName->setEnabled(FALSE); + mLabelOwnerName->setValue(getString("public")); } + + // Not yet supported for task inventories + mChangeThumbnailBtn->setEnabled(mObjectID.isNull() && ALEXANDRIA_LINDEN_ID != perm.getOwner()); //////////// // ORIGIN // @@ -573,6 +606,8 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) if( gSavedSettings.getBOOL("DebugPermissions") ) { + childSetVisible("layout_debug_permissions", true); + BOOL slam_perm = FALSE; BOOL overwrite_group = FALSE; BOOL overwrite_everyone = FALSE; @@ -600,38 +635,29 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) perm_string = "B: "; perm_string += mask_to_string(base_mask, isOpenSim); // remove misleading X for export when not in OpenSim getChild("BaseMaskDebug")->setValue(perm_string); - getChildView("BaseMaskDebug")->setVisible(TRUE); perm_string = "O: "; perm_string += mask_to_string(owner_mask, isOpenSim); // remove misleading X for export when not in OpenSim getChild("OwnerMaskDebug")->setValue(perm_string); - getChildView("OwnerMaskDebug")->setVisible(TRUE); perm_string = "G"; perm_string += overwrite_group ? "*: " : ": "; perm_string += mask_to_string(group_mask, isOpenSim); // remove misleading X for export when not in OpenSim getChild("GroupMaskDebug")->setValue(perm_string); - getChildView("GroupMaskDebug")->setVisible(TRUE); perm_string = "E"; perm_string += overwrite_everyone ? "*: " : ": "; perm_string += mask_to_string(everyone_mask, isOpenSim); // remove misleading X for export when not in OpenSim getChild("EveryoneMaskDebug")->setValue(perm_string); - getChildView("EveryoneMaskDebug")->setVisible(TRUE); perm_string = "N"; perm_string += slam_perm ? "*: " : ": "; perm_string += mask_to_string(next_owner_mask, isOpenSim); // remove misleading X for export when not in OpenSim getChild("NextMaskDebug")->setValue(perm_string); - getChildView("NextMaskDebug")->setVisible(TRUE); } else { - getChildView("BaseMaskDebug")->setVisible(FALSE); - getChildView("OwnerMaskDebug")->setVisible(FALSE); - getChildView("GroupMaskDebug")->setVisible(FALSE); - getChildView("EveryoneMaskDebug")->setVisible(FALSE); - getChildView("NextMaskDebug")->setVisible(FALSE); + childSetVisible("layout_debug_permissions", false); } ///////////// @@ -766,6 +792,80 @@ void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) } } +//void LLSidepanelItemInfo::updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params) +// [RLVa:KB] - Checked: RLVa-2.0.1 +void LLSidepanelItemInfo::updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params, bool rlv_restricted) +// [/RLVa:KB] +{ + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } +// std::string name = creator_name.getCompleteName(); +// [RLVa:KB] - Checked: RLVa-2.0.1 + std::string name = rlv_restricted ? RlvStrings::getAnonym(creator_name) : creator_name.getCompleteName(); +// [/RLVa:KB] + mLabelCreatorName->setText(name, style_params); +} + +//void LLSidepanelItemInfo::updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params) +// [RLVa:KB] - Checked: RLVa-2.0.1 +void LLSidepanelItemInfo::updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params, bool rlv_restricted) +// [/RLVa:KB] +{ + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } +// std::string name = owner_name.getCompleteName(); +// [RLVa:KB] - Checked: RLVa-2.0.1 + std::string name = rlv_restricted ? RlvStrings::getAnonym(owner_name) : owner_name.getCompleteName(); +// [/RLVa:KB] + mLabelOwnerName->setText(name, style_params); +} + +void LLSidepanelItemInfo::changed(U32 mask) +{ + const LLUUID& item_id = getItemID(); + if (getObjectID().notNull() || item_id.isNull()) + { + // Task inventory or not set up yet + return; + } + + const std::set& mChangedItemIDs = gInventory.getChangedIDs(); + std::set::const_iterator it; + + for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) + { + // set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288) + if (*it == item_id) + { + // if there's a change we're interested in. + if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0) + { + dirty(); + } + } + } +} + +void LLSidepanelItemInfo::dirty() +{ + mIsDirty = true; +} + +// static +void LLSidepanelItemInfo::onIdle( void* user_data ) +{ + LLSidepanelItemInfo* self = reinterpret_cast(user_data); + + if( self->mIsDirty ) + { + self->refresh(); + self->mIsDirty = false; + } +} void LLSidepanelItemInfo::setAssociatedExperience( LLHandle hInfo, const LLSD& experience ) { @@ -903,7 +1003,7 @@ void LLSidepanelItemInfo::onCommitDescription() LLViewerInventoryItem* item = findItem(); if(!item) return; - LLLineEditor* labelItemDesc = getChild("LabelItemDesc"); + LLTextEditor* labelItemDesc = getChild("LabelItemDesc"); if(!labelItemDesc) { return; @@ -1016,7 +1116,14 @@ void LLSidepanelItemInfo::updatePermissions() } } -// static +void LLSidepanelItemInfo::onEditThumbnail() +{ + LLSD data; + data["task_id"] = mObjectID; + data["item_id"] = mItemID; + LLFloaterReg::showInstance("change_item_thumbnail", data); +} + void LLSidepanelItemInfo::onCommitSaleInfo(LLUICtrl* ctrl) { if (ctrl) diff --git a/indra/newview/llsidepaneliteminfo.h b/indra/newview/llsidepaneliteminfo.h index 5f29254182..290e6c0678 100644 --- a/indra/newview/llsidepaneliteminfo.h +++ b/indra/newview/llsidepaneliteminfo.h @@ -27,42 +27,59 @@ #ifndef LL_LLSIDEPANELITEMINFO_H #define LL_LLSIDEPANELITEMINFO_H -#include "llsidepanelinventorysubpanel.h" +#include "llinventoryobserver.h" +#include "llpanel.h" +#include "llstyle.h" //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLSidepanelItemInfo // Object properties for inventory side panel. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLAvatarName; class LLButton; +class LLFloater; +class LLIconCtrl; class LLViewerInventoryItem; class LLItemPropertiesObserver; class LLObjectInventoryObserver; class LLViewerObject; class LLPermissions; +class LLTextBox; -class LLSidepanelItemInfo : public LLSidepanelInventorySubpanel +class LLSidepanelItemInfo : public LLPanel, public LLInventoryObserver { public: LLSidepanelItemInfo(const LLPanel::Params& p = getDefaultParams()); virtual ~LLSidepanelItemInfo(); - /*virtual*/ BOOL postBuild(); + /*virtual*/ BOOL postBuild() override; /*virtual*/ void reset(); void setObjectID(const LLUUID& object_id); void setItemID(const LLUUID& item_id); - void setEditMode(BOOL edit); + void setParentFloater(LLFloater* parent); // For simplicity const LLUUID& getObjectID() const; const LLUUID& getItemID() const; // if received update and item id (from callback) matches internal ones, update UI void onUpdateCallback(const LLUUID& item_id, S32 received_update_id); + + void changed(U32 mask) override; + void dirty(); + + static void onIdle( void* user_data ); +// [RLVa:KB] - Checked: RLVa-2.0.1 +// void updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params); +// void updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params); + void updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params, bool rlv_restricted); + void updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params, bool rlv_restricted); +// [/RLVa:KB] protected: - /*virtual*/ void refresh(); - /*virtual*/ void save(); + void refresh() override; + void save(); LLViewerInventoryItem* findItem() const; LLViewerObject* findObject() const; @@ -75,14 +92,23 @@ private: void startObjectInventoryObserver(); void stopObjectInventoryObserver(); void setPropertiesFieldsEnabled(bool enabled); + + boost::signals2::connection mOwnerCacheConnection; + boost::signals2::connection mCreatorCacheConnection; LLUUID mItemID; // inventory UUID for the inventory item. LLUUID mObjectID; // in-world task UUID, or null if in agent inventory. - LLItemPropertiesObserver* mPropertiesObserver; // for syncing changes to item LLObjectInventoryObserver* mObjectInventoryObserver; // for syncing changes to items inside an object // We can send multiple properties updates simultaneously, make sure only last response counts and there won't be a race condition. S32 mUpdatePendingId; + bool mIsDirty; // item properties need to be updated + LLFloater* mParentFloater; + + LLUICtrl* mChangeThumbnailBtn; + LLIconCtrl* mItemTypeIcon; + LLTextBox* mLabelOwnerName; + LLTextBox* mLabelCreatorName; // // UI Elements @@ -94,6 +120,7 @@ protected: void onCommitDescription(); void onCommitPermissions(LLUICtrl* ctrl); void updatePermissions(); + void onEditThumbnail(); void onCommitSaleInfo(LLUICtrl* ctrl); void updateSaleInfo(); void onCommitChanges(LLPointer item); diff --git a/indra/newview/llsidepaneltaskinfo.cpp b/indra/newview/llsidepaneltaskinfo.cpp index 331abb9e98..94282607d2 100644 --- a/indra/newview/llsidepaneltaskinfo.cpp +++ b/indra/newview/llsidepaneltaskinfo.cpp @@ -42,6 +42,7 @@ #include "llresmgr.h" #include "lltextbox.h" #include "llbutton.h" +#include "llcallbacklist.h" #include "llcheckboxctrl.h" #include "llviewerobject.h" #include "llselectmgr.h" @@ -80,9 +81,11 @@ static LLPanelInjector t_task_info("sidepanel_task_info"); // Default constructor LLSidepanelTaskInfo::LLSidepanelTaskInfo() + : mVisibleDebugPermissions(true) // space was allocated by default { setMouseOpaque(FALSE); LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLSidepanelTaskInfo::refreshAll, this)); + gIdleCallbacks.addFunction(&LLSidepanelTaskInfo::onIdle, (void*)this); } @@ -90,13 +93,12 @@ LLSidepanelTaskInfo::~LLSidepanelTaskInfo() { if (sActivePanel == this) sActivePanel = NULL; + gIdleCallbacks.deleteFunction(&LLSidepanelTaskInfo::onIdle, (void*)this); } // virtual BOOL LLSidepanelTaskInfo::postBuild() { - LLSidepanelInventorySubpanel::postBuild(); - mOpenBtn = getChild("open_btn"); mOpenBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onOpenButtonClicked, this)); mPayBtn = getChild("pay_btn"); @@ -151,12 +153,12 @@ BOOL LLSidepanelTaskInfo::postBuild() mDALabelClickAction = getChildView("label click action"); mDAComboClickAction = getChild("clickaction"); mDAPathfindingAttributes = getChild("pathfinding_attributes_value"); - mDAB = getChildView("B:"); - mDAO = getChildView("O:"); - mDAG = getChildView("G:"); - mDAE = getChildView("E:"); - mDAN = getChildView("N:"); - mDAF = getChildView("F:"); + mDAB = getChild("B:"); + mDAO = getChild("O:"); + mDAG = getChild("G:"); + mDAE = getChild("E:"); + mDAN = getChild("N:"); + mDAF = getChild("F:"); return TRUE; } @@ -206,12 +208,22 @@ void LLSidepanelTaskInfo::disableAll() disablePermissions(); - mDAB->setVisible(FALSE); - mDAO->setVisible(FALSE); - mDAG->setVisible(FALSE); - mDAE->setVisible(FALSE); - mDAN->setVisible(FALSE); - mDAF->setVisible(FALSE); + if (mVisibleDebugPermissions) + { + mDAB->setVisible(FALSE); + mDAO->setVisible(FALSE); + mDAG->setVisible(FALSE); + mDAE->setVisible(FALSE); + mDAN->setVisible(FALSE); + mDAF->setVisible(FALSE); + + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLRect parent_rect = parent_floater->getRect(); + LLRect debug_rect = mDAB->getRect(); + // use double the debug rect for padding (since it isn't trivial to extract top_pad) + parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() - (debug_rect.getHeight() * 2)); + mVisibleDebugPermissions = false; + } mOpenBtn->setEnabled(FALSE); mPayBtn->setEnabled(FALSE); @@ -258,6 +270,8 @@ void LLSidepanelTaskInfo::disablePermissions() void LLSidepanelTaskInfo::refresh() { + mIsDirty = false; + LLButton* btn_deed_to_group = mDeedBtn; if (btn_deed_to_group) { @@ -637,31 +651,30 @@ void LLSidepanelTaskInfo::refresh() if (gSavedSettings.getBOOL("DebugPermissions") ) { // remove misleading X for export when not in OpenSim - bool isOpenSim {false}; + bool isOpenSim {false}; #ifdef OPENSIM - if( LLGridManager::instance().isInOpenSim() ) - { - isOpenSim = true; - } + if( LLGridManager::instance().isInOpenSim() ) + { + isOpenSim = true; + } #endif -// - if (valid_base_perms) - { - getChild("B:")->setValue("B: " + mask_to_string(base_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim - getChildView("B:")->setVisible( TRUE); - - getChild("O:")->setValue("O: " + mask_to_string(owner_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim - getChildView("O:")->setVisible( TRUE); - - getChild("G:")->setValue("G: " + mask_to_string(group_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim - getChildView("G:")->setVisible( TRUE); - - getChild("E:")->setValue("E: " + mask_to_string(everyone_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim - getChildView("E:")->setVisible( TRUE); - - getChild("N:")->setValue("N: " + mask_to_string(next_owner_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim - getChildView("N:")->setVisible( TRUE); - } + if (valid_base_perms) + { + mDAB->setValue("B: " + mask_to_string(base_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim + mDAB->setVisible( TRUE); + + mDAO->setValue("O: " + mask_to_string(owner_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim + mDAO->setVisible( TRUE); + + mDAG->setValue("G: " + mask_to_string(group_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim + mDAG->setVisible( TRUE); + + mDAE->setValue("E: " + mask_to_string(everyone_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim + mDAE->setVisible( TRUE); + + mDAN->setValue("N: " + mask_to_string(next_owner_mask_on, isOpenSim)); // remove misleading X for export when not in OpenSim + mDAN->setVisible( TRUE); + } U32 flag_mask = 0x0; if (objectp->permMove()) flag_mask |= PERM_MOVE; @@ -669,18 +682,35 @@ void LLSidepanelTaskInfo::refresh() if (objectp->permCopy()) flag_mask |= PERM_COPY; if (objectp->permTransfer()) flag_mask |= PERM_TRANSFER; - getChild("F:")->setValue("F:" + mask_to_string(flag_mask, isOpenSim)); // remove misleading X for export when not in OpenSim - getChildView("F:")->setVisible( TRUE); - } - else - { - getChildView("B:")->setVisible( FALSE); - getChildView("O:")->setVisible( FALSE); - getChildView("G:")->setVisible( FALSE); - getChildView("E:")->setVisible( FALSE); - getChildView("N:")->setVisible( FALSE); - getChildView("F:")->setVisible( FALSE); - } + mDAF->setValue("F:" + mask_to_string(flag_mask, isOpenSim)); // remove misleading X for export when not in OpenSim + mDAF->setVisible(TRUE); + + if (!mVisibleDebugPermissions) + { + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLRect parent_rect = parent_floater->getRect(); + LLRect debug_rect = mDAB->getRect(); + // use double the debug rect for padding (since it isn't trivial to extract top_pad) + parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() + (debug_rect.getHeight() * 2)); + mVisibleDebugPermissions = true; + } + } + else if (mVisibleDebugPermissions) + { + mDAB->setVisible(FALSE); + mDAO->setVisible(FALSE); + mDAG->setVisible(FALSE); + mDAE->setVisible(FALSE); + mDAN->setVisible(FALSE); + mDAF->setVisible(FALSE); + + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLRect parent_rect = parent_floater->getRect(); + LLRect debug_rect = mDAB->getRect(); + // use double the debug rect for padding (since it isn't trivial to extract top_pad) + parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() - (debug_rect.getHeight() * 2)); + mVisibleDebugPermissions = false; + } BOOL has_change_perm_ability = FALSE; BOOL has_change_sale_ability = FALSE; @@ -903,33 +933,6 @@ void LLSidepanelTaskInfo::refresh() getChildView("label click action")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); getChildView("clickaction")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); - if (!getIsEditing()) - { - const std::string no_item_names[] = - { - "Object Name", - "Object Description", - "button set group", - "checkbox share with group", - "button deed", - "checkbox allow everyone move", - "checkbox allow everyone copy", - "checkbox for sale", - "sale type", - "Edit Cost", - "checkbox next owner can modify", - "checkbox next owner can copy", - "checkbox next owner can transfer", - "clickaction", - "search_check", - "perm_modify", - //"Group Name", // Doesn't exist as of 2015-11-26 - }; - for (size_t t=0; tsetEnabled( FALSE); - } - } updateVerbs(); } @@ -1242,16 +1245,6 @@ void LLSidepanelTaskInfo::onCommitIncludeInSearch(LLUICtrl* ctrl, void* data) // virtual void LLSidepanelTaskInfo::updateVerbs() { - LLSidepanelInventorySubpanel::updateVerbs(); - - /* - mOpenBtn->setVisible(!getIsEditing()); - mPayBtn->setVisible(!getIsEditing()); - mBuyBtn->setVisible(!getIsEditing()); - //const LLViewerObject *obj = getFirstSelectedObject(); - //mEditBtn->setEnabled(obj && obj->permModify()); - */ - LLSafeHandle object_selection = LLSelectMgr::getInstance()->getSelection(); const BOOL any_selected = (object_selection->getNumNodes() > 0); @@ -1336,6 +1329,23 @@ LLSidepanelTaskInfo* LLSidepanelTaskInfo::getActivePanel() return sActivePanel; } +void LLSidepanelTaskInfo::dirty() +{ + mIsDirty = true; +} + +// static +void LLSidepanelTaskInfo::onIdle( void* user_data ) +{ + LLSidepanelTaskInfo* self = reinterpret_cast(user_data); + + if( self->mIsDirty ) + { + self->refresh(); + self->mIsDirty = false; + } +} + LLViewerObject* LLSidepanelTaskInfo::getObject() { if (!mObject->isDead()) diff --git a/indra/newview/llsidepaneltaskinfo.h b/indra/newview/llsidepaneltaskinfo.h index dc259cb22d..852d36293b 100644 --- a/indra/newview/llsidepaneltaskinfo.h +++ b/indra/newview/llsidepaneltaskinfo.h @@ -27,8 +27,8 @@ #ifndef LL_LLSIDEPANELTASKINFO_H #define LL_LLSIDEPANELTASKINFO_H -#include "llsidepanelinventorysubpanel.h" #include "lluuid.h" +#include "llpanel.h" #include "llselectmgr.h" //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -43,14 +43,14 @@ class LLNameBox; class LLViewerObject; class LLTextBase; -class LLSidepanelTaskInfo : public LLSidepanelInventorySubpanel +class LLSidepanelTaskInfo : public LLPanel { public: LLSidepanelTaskInfo(); virtual ~LLSidepanelTaskInfo(); - /*virtual*/ BOOL postBuild(); - /*virtual*/ void onVisibilityChange ( BOOL new_visibility ); + BOOL postBuild() override; + void onVisibilityChange ( BOOL new_visibility ) override; void setObjectSelection(LLObjectSelectionHandle selection); @@ -58,10 +58,12 @@ public: LLViewerObject* getFirstSelectedObject(); static LLSidepanelTaskInfo *getActivePanel(); + void dirty(); + static void onIdle( void* user_data ); protected: - /*virtual*/ void refresh(); // refresh all labels as needed - /*virtual*/ void save(); - /*virtual*/ void updateVerbs(); + void refresh() override; // refresh all labels as needed + void save(); + void updateVerbs(); void refreshAll(); // ignore current keyboard focus and update all fields @@ -103,6 +105,8 @@ private: LLUUID mCreatorID; LLUUID mOwnerID; LLUUID mLastOwnerID; + + bool mIsDirty; protected: void onOpenButtonClicked(); @@ -121,6 +125,10 @@ protected: private: LLPointer mObject; LLObjectSelectionHandle mObjectSelection; + + // mVisibleDebugPermissions doesn't nessesarily matche state + // of viewes and is primarily for floater resize + bool mVisibleDebugPermissions; static LLSidepanelTaskInfo* sActivePanel; private: @@ -148,12 +156,12 @@ private: LLView* mDALabelClickAction; LLComboBox* mDAComboClickAction; LLTextBase* mDAPathfindingAttributes; - LLView* mDAB; - LLView* mDAO; - LLView* mDAG; - LLView* mDAE; - LLView* mDAN; - LLView* mDAF; + LLUICtrl* mDAB; + LLUICtrl* mDAO; + LLUICtrl* mDAG; + LLUICtrl* mDAE; + LLUICtrl* mDAN; + LLUICtrl* mDAF; }; diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp index b0769e9d8e..42220006fb 100644 --- a/indra/newview/llsnapshotlivepreview.cpp +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -52,6 +52,7 @@ #include "llviewercontrol.h" #include "llviewermenufile.h" // upload_new_resource() #include "llviewerstats.h" +#include "llviewertexturelist.h" #include "llwindow.h" #include "llworld.h" #include @@ -986,6 +987,11 @@ LLPointer LLSnapshotLivePreview::getEncodedImage() return mPreviewImageEncoded; } +bool LLSnapshotLivePreview::createUploadFile(const std::string &out_filename, const S32 max_image_dimentions, const S32 min_image_dimentions) +{ + return LLViewerTextureList::createUploadFile(mPreviewImage, out_filename, max_image_dimentions, min_image_dimentions); +} + // We actually estimate the data size so that we do not require actual compression when showing the preview // Note : whenever formatted image is computed, mDataSize will be updated to reflect the true size void LLSnapshotLivePreview::estimateDataSize() diff --git a/indra/newview/llsnapshotlivepreview.h b/indra/newview/llsnapshotlivepreview.h index ab7ae266cd..058d368771 100644 --- a/indra/newview/llsnapshotlivepreview.h +++ b/indra/newview/llsnapshotlivepreview.h @@ -109,6 +109,7 @@ public: LLPointer getFormattedImage(); LLPointer getEncodedImage(); + bool createUploadFile(const std::string &out_file, const S32 max_image_dimentions, const S32 min_image_dimentions); /// Sets size of preview thumbnail image and the surrounding rect. void setThumbnailPlaceholderRect(const LLRect& rect) {mThumbnailPlaceholderRect = rect; } diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index a8616239ad..2cbc2b6a29 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -300,7 +300,7 @@ static bool mLoginStatePastUI = false; static bool mBenefitsSuccessfullyInit = false; const F32 STATE_AGENT_WAIT_TIMEOUT = 240; //seconds -const S32 MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN = 3; // Give region 3 chances +const S32 MAX_SEED_CAP_ATTEMPTS_BEFORE_ABORT = 4; // Give region 4 chances std::unique_ptr LLStartUp::sStateWatcher(new LLEventStream("StartupState")); std::unique_ptr LLStartUp::sListener(new LLStartupListener()); @@ -2077,11 +2077,18 @@ bool idle_startup() else { U32 num_retries = regionp->getNumSeedCapRetries(); - if (num_retries > MAX_SEED_CAP_ATTEMPTS_BEFORE_LOGIN) + if (num_retries > MAX_SEED_CAP_ATTEMPTS_BEFORE_ABORT) { - // Region will keep trying to get capabilities, - // but for now continue as if caps were granted - LLStartUp::setStartupState(STATE_SEED_CAP_GRANTED); + LL_WARNS("AppInit") << "Failed to get capabilities. Backing up to login screen!" << LL_ENDL; + if (gRememberPassword) + { + LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); + } + else + { + LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); + } + reset_login(); } else if (num_retries > 0) { @@ -2663,6 +2670,34 @@ bool idle_startup() LLInventoryModelBackgroundFetch::instance().start(); gInventory.createCommonSystemCategories(); + LLStartUp::setStartupState(STATE_INVENTORY_CALLBACKS ); + display_startup(); + + return FALSE; + } + + //--------------------------------------------------------------------- + // STATE_INVENTORY_CALLBACKS + //--------------------------------------------------------------------- + if (STATE_INVENTORY_CALLBACKS == LLStartUp::getStartupState()) + { + if (!LLInventoryModel::isSysFoldersReady()) + { + display_startup(); + return FALSE; + } + LLInventoryModelBackgroundFetch::instance().start(); + LLUUID cof_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + LLViewerInventoryCategory* cof = gInventory.getCategory(cof_id); + if (cof + && cof->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // Special case, dupplicate request prevention. + // Cof folder will be requested via FetchCOF + // in appearance manager, prevent recursive fetch + cof->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + } + // It's debatable whether this flag is a good idea - sets all // bits, and in general it isn't true that inventory @@ -2998,7 +3033,7 @@ bool idle_startup() gAgentWearables.sendDummyAgentWearablesUpdate(); } // [Legacy Bake] - callAfterCategoryFetch(LLAppearanceMgr::instance().getCOF(), set_flags_and_update_appearance); + callAfterCOFFetch(set_flags_and_update_appearance); } display_startup(); @@ -3707,7 +3742,7 @@ void LLStartUp::loadInitialOutfit( const std::string& outfit_folder_name, // Not going through the processAgentInitialWearables path, so need to set this here. LLAppearanceMgr::instance().setAttachmentInvLinkEnable(true); // Initiate creation of COF, since we're also bypassing that. - gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CURRENT_OUTFIT); ESex gender; if (gender_name == "male") @@ -3760,8 +3795,15 @@ void LLStartUp::loadInitialOutfit( const std::string& outfit_folder_name, bool do_append = false; LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); // Need to fetch cof contents before we can wear. - callAfterCategoryFetch(LLAppearanceMgr::instance().getCOF(), + if (do_copy) + { + callAfterCategoryFetch(LLAppearanceMgr::instance().getCOF(), boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); + } + else + { + callAfterCategoryLinksFetch(cat_id, boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); + } LL_DEBUGS() << "initial outfit category id: " << cat_id << LL_ENDL; } @@ -3819,6 +3861,7 @@ std::string LLStartUp::startupStateToString(EStartupState state) RTNENUM( STATE_AGENT_SEND ); RTNENUM( STATE_AGENT_WAIT ); RTNENUM( STATE_INVENTORY_SEND ); + RTNENUM(STATE_INVENTORY_CALLBACKS ); RTNENUM( STATE_MISC ); RTNENUM( STATE_PRECACHE ); RTNENUM( STATE_WEARABLES_WAIT ); @@ -3884,6 +3927,11 @@ void reset_login() LLFloaterReg::hideVisibleInstances(); LLStartUp::setStartupState( STATE_BROWSER_INIT ); + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->terminate(); + } + // Clear any verified certs and verify them again on next login // to ensure cert matches server instead of just getting reused LLPointer store = gSecAPIHandler->getCertificateStore(""); @@ -4639,7 +4687,7 @@ bool process_login_success_response(U32 &first_sim_size_x, U32 &first_sim_size_y std::string flag = login_flags["ever_logged_in"]; if(!flag.empty()) { - gAgent.setFirstLogin((flag == "N") ? TRUE : FALSE); + gAgent.setFirstLogin(flag == "N"); } /* Flag is currently ignored by the viewer. @@ -4761,7 +4809,7 @@ bool process_login_success_response(U32 &first_sim_size_x, U32 &first_sim_size_y std::string fake_initial_outfit_name = gSavedSettings.getString("FakeInitialOutfitName"); if (!fake_initial_outfit_name.empty()) { - gAgent.setFirstLogin(TRUE); + gAgent.setFirstLogin(true); sInitialOutfit = fake_initial_outfit_name; if (sInitialOutfitGender.empty()) { @@ -4796,9 +4844,10 @@ bool process_login_success_response(U32 &first_sim_size_x, U32 &first_sim_size_y LLViewerMedia::getInstance()->openIDSetup(openid_url, openid_token); } - // Only save mfa_hash for future logins if the user wants their info remembered. - if(response.has("mfa_hash") && gSavedSettings.getBOOL("RememberUser") && gSavedSettings.getBOOL("RememberPassword")) + if(response.has("mfa_hash") + && gSavedSettings.getBOOL("RememberUser") + && LLLoginInstance::getInstance()->saveMFA()) { std::string grid(LLGridManager::getInstance()->getGridId()); std::string user_id(gUserCredential->userID()); @@ -4806,6 +4855,13 @@ bool process_login_success_response(U32 &first_sim_size_x, U32 &first_sim_size_y // TODO(brad) - related to SL-17223 consider building a better interface that sync's automatically gSecAPIHandler->syncProtectedMap(); } + else if (!LLLoginInstance::getInstance()->saveMFA()) + { + std::string grid(LLGridManager::getInstance()->getGridId()); + std::string user_id(gUserCredential->userID()); + gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid, user_id); + gSecAPIHandler->syncProtectedMap(); + } // OpenSim legacy economy support #ifdef OPENSIM diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h index d31d3b61fe..4e2940c024 100644 --- a/indra/newview/llstartup.h +++ b/indra/newview/llstartup.h @@ -81,6 +81,7 @@ typedef enum { STATE_AGENT_SEND, // Connect to a region STATE_AGENT_WAIT, // Wait for region STATE_INVENTORY_SEND, // Do inventory transfer + STATE_INVENTORY_CALLBACKS, // Wait for missing system folders and register callbacks STATE_MISC, // Do more things (set bandwidth, start audio, save location, etc) STATE_PRECACHE, // Wait a bit for textures to download STATE_WEARABLES_WAIT, // Wait for clothing to download diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index ad041de2ea..9805052bb7 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -178,6 +178,9 @@ LLFloaterTexturePicker::LLFloaterTexturePicker( mSelectedItemPinned( FALSE ), mCanApply(true), mCanPreview(true), + mLimitsSet(false), + mMaxDim(S32_MAX), + mMinDim(0), mPreviewSettingChanged(false), mOnFloaterCommitCallback(NULL), mOnFloaterCloseCallback(NULL), @@ -318,19 +321,37 @@ void LLFloaterTexturePicker::stopUsingPipette() } } -void LLFloaterTexturePicker::updateImageStats() +bool LLFloaterTexturePicker::updateImageStats() { + bool result = true; if (mTexturep.notNull()) { //RN: have we received header data for this image? - if (mTexturep->getFullWidth() > 0 && mTexturep->getFullHeight() > 0) + S32 width = mTexturep->getFullWidth(); + S32 height = mTexturep->getFullHeight(); + if (width > 0 && height > 0) { - std::string formatted_dims = llformat("%d x %d", mTexturep->getFullWidth(),mTexturep->getFullHeight()); - mResolutionLabel->setTextArg("[DIMENSIONS]", formatted_dims); - if (mOnUpdateImageStatsCallback) - { - mOnUpdateImageStatsCallback(mTexturep); - } + if ((mLimitsSet && (width != height)) + || width < mMinDim + || width > mMaxDim + || height < mMinDim + || height > mMaxDim + ) + { + std::string formatted_dims = llformat("%dx%d", width, height); + mResolutionWarning->setTextArg("[TEXDIM]", formatted_dims); + result = false; + } + else + { + std::string formatted_dims = llformat("%d x %d", width, height); + mResolutionLabel->setTextArg("[DIMENSIONS]", formatted_dims); + } + + if (mOnUpdateImageStatsCallback) + { + mOnUpdateImageStatsCallback(mTexturep); + } } else { @@ -341,6 +362,23 @@ void LLFloaterTexturePicker::updateImageStats() { mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("")); } + mResolutionLabel->setVisible(result); + mResolutionWarning->setVisible(!result); + + // Hide buttons and pipete to make space for mResolutionWarning + // Hiding buttons is suboptimal, but at the moment limited to inventory thumbnails + // may be this should be an info/warning icon with a tooltip? + S32 index = mModeSelector->getValue().asInteger(); + if (index == 0) + { + mDefaultBtn->setVisible(result); + mNoneBtn->setVisible(result); + mBlankBtn->setVisible(result); + mPipetteBtn->setVisible(result); + // Special additions + mTransparentBtn->setVisible(result); + } + return result; } // virtual @@ -471,13 +509,29 @@ BOOL LLFloaterTexturePicker::postBuild() mTentativeLabel = getChild("Multiple"); mResolutionLabel = getChild("size_lbl"); + mResolutionWarning = getChild("over_limit_lbl"); - childSetAction("Default",LLFloaterTexturePicker::onBtnSetToDefault,this); - childSetAction("None", LLFloaterTexturePicker::onBtnNone,this); - childSetAction("Blank", LLFloaterTexturePicker::onBtnBlank,this); - childSetAction("Transparent", LLFloaterTexturePicker::onBtnTransparent,this); // FIRE-5082: "Transparent" button in Texture Panel + mDefaultBtn = getChild("Default"); + mNoneBtn = getChild("None"); + mBlankBtn = getChild("Blank"); + mPipetteBtn = getChild("Pipette"); + mSelectBtn = getChild("Select"); + mCancelBtn = getChild("Cancel"); + // Special additions + mTransparentBtn = getChild("Transparent"); + mUUIDBtn = getChild("TextureKeyApply"); + mUUIDEditor = getChild("TextureKey"); + mDefaultBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnSetToDefault,this)); + mNoneBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnNone, this)); + mBlankBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnBlank, this)); + mPipetteBtn->setCommitCallback(boost::bind(&LLFloaterTexturePicker::onBtnPipette, this)); + mSelectBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnSelect, this)); + mCancelBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnCancel, this)); + // Special additions + mTransparentBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnTransparent, this)); + mUUIDBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnApplyTexture, this)); childSetCommitCallback("show_folders_check", onShowFolders, this); getChildView("show_folders_check")->setVisible( FALSE); @@ -548,13 +602,6 @@ BOOL LLFloaterTexturePicker::postBuild() getChildView("show_folders_check")->setEnabled(FALSE); } - getChild("Pipette")->setCommitCallback( boost::bind(&LLFloaterTexturePicker::onBtnPipette, this)); - // UUID picker - childSetAction("TextureKeyApply", LLFloaterTexturePicker::onBtnApplyTexture,this); - // - childSetAction("Cancel", LLFloaterTexturePicker::onBtnCancel,this); - childSetAction("Select", LLFloaterTexturePicker::onBtnSelect,this); - // update permission filter once UI is fully initialized updateFilterPermMask(); mSavedFolderState.setApply(FALSE); @@ -574,13 +621,13 @@ void LLFloaterTexturePicker::draw() static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); drawConeToOwner(mContextConeOpacity, max_opacity, mOwner); - updateImageStats(); + bool valid_dims = updateImageStats(); // if we're inactive, gray out "apply immediate" checkbox getChildView("show_folders_check")->setEnabled(mActive && mCanApplyImmediately && !mNoCopyTextureSelected); - getChildView("Select")->setEnabled(mActive && mCanApply); - getChildView("Pipette")->setEnabled(mActive); - getChild("Pipette")->setValue(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()); + mSelectBtn->setEnabled(mActive && mCanApply && valid_dims); + mPipetteBtn->setEnabled(mActive); + mPipetteBtn->setValue(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()); //BOOL allow_copy = FALSE; if( mOwner ) @@ -614,11 +661,10 @@ void LLFloaterTexturePicker::draw() mTentativeLabel->setVisible( FALSE ); } - getChildView("Default")->setEnabled(mImageAssetID != mDefaultImageAssetID || mTentative); - getChildView("Blank")->setEnabled(mImageAssetID != mBlankImageAssetID || mTentative); - getChildView("Transparent")->setEnabled(mImageAssetID != mTransparentImageAssetID || mTentative); // FIRE-5082: "Transparent" button in Texture Panel - getChildView("None")->setEnabled(mAllowNoTexture && (!mImageAssetID.isNull() || mTentative)); - + mDefaultBtn->setEnabled(mImageAssetID != mDefaultImageAssetID || mTentative); + mBlankBtn->setEnabled(mImageAssetID != mBlankImageAssetID || mTentative); + mNoneBtn->setEnabled(mAllowNoTexture && (!mImageAssetID.isNull() || mTentative)); + mTransparentBtn->setEnabled(mImageAssetID != mTransparentImageAssetID || mTentative); // FIRE-5082: "Transparent" button in Texture Panel LLFloater::draw(); if( isMinimized() ) @@ -744,19 +790,78 @@ PermissionMask LLFloaterTexturePicker::getFilterPermMask() void LLFloaterTexturePicker::commitIfImmediateSet() { // FIRE-8298: Apply now checkbox has no effect - //if (!mNoCopyTextureSelected && mOnFloaterCommitCallback && mCanApply) - if (!mNoCopyTextureSelected && mOnFloaterCommitCallback && mCanApply && mCanPreview) + //if (!mNoCopyTextureSelected && mCanApply) + if (!mNoCopyTextureSelected && mCanApply && mCanPreview) // { - mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CHANGE, LLUUID::null); + commitCallback(LLTextureCtrl::TEXTURE_CHANGE); } } +void LLFloaterTexturePicker::commitCallback(LLTextureCtrl::ETexturePickOp op) +{ + if (!mOnFloaterCommitCallback) + { + return; + } + LLUUID asset_id = mImageAssetID; + LLUUID inventory_id; + LLPickerSource mode = (LLPickerSource)mModeSelector->getValue().asInteger(); + + switch (mode) + { + case PICKER_INVENTORY: + { + LLFolderView* root_folder = mInventoryPanel->getRootFolder(); + if (root_folder && root_folder->getCurSelectedItem()) + { + LLFolderViewItem* last_selected = root_folder->getCurSelectedItem(); + LLFolderViewModelItemInventory* inv_view = static_cast(last_selected->getViewModelItem()); + + LLInventoryItem* itemp = gInventory.getItem(inv_view->getUUID()); + if (itemp && itemp->getAssetUUID() == mImageAssetID) + { + inventory_id = inv_view->getUUID(); + } + else + { + mode = PICKER_UNKNOWN; // source of id unknown + } + } + else + { + mode = PICKER_UNKNOWN; // source of id unknown + } + break; + } + case PICKER_LOCAL: + { + if (!mLocalScrollCtrl->getAllSelected().empty()) + { + LLUUID temp_id = mLocalScrollCtrl->getFirstSelected()->getColumn(LOCAL_TRACKING_ID_COLUMN)->getValue().asUUID(); + asset_id = LLLocalBitmapMgr::getInstance()->getWorldID(temp_id); + } + else + { + asset_id = mImageAssetID; + mode = PICKER_UNKNOWN; // source of id unknown + } + break; + } + case PICKER_BAKE: + break; + default: + mode = PICKER_UNKNOWN; // source of id unknown + break; + } + + mOnFloaterCommitCallback(op, mode, asset_id, inventory_id); +} void LLFloaterTexturePicker::commitCancel() { if (!mNoCopyTextureSelected && mOnFloaterCommitCallback && mCanApply) { - mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, LLUUID::null); + mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, PICKER_UNKNOWN, mOriginalImageAssetID, LLUUID::null); } } @@ -830,7 +935,7 @@ void LLFloaterTexturePicker::onBtnCancel(void* userdata) self->setImageID( self->mOriginalImageAssetID ); if (self->mOnFloaterCommitCallback) { - self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, LLUUID::null); + self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, PICKER_UNKNOWN, self->mOriginalImageAssetID, LLUUID::null); } self->mViewModel->resetDirty(); self->closeFloater(); @@ -840,20 +945,7 @@ void LLFloaterTexturePicker::onBtnCancel(void* userdata) void LLFloaterTexturePicker::onBtnSelect(void* userdata) { LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - LLUUID local_id = LLUUID::null; - if (self->mOwner) - { - if (self->mLocalScrollCtrl->getVisible() && !self->mLocalScrollCtrl->getAllSelected().empty()) - { - LLUUID temp_id = self->mLocalScrollCtrl->getFirstSelected()->getColumn(LOCAL_TRACKING_ID_COLUMN)->getValue().asUUID(); - local_id = LLLocalBitmapMgr::getInstance()->getWorldID(temp_id); - } - } - - if (self->mOnFloaterCommitCallback) - { - self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_SELECT, local_id); - } + self->commitCallback(LLTextureCtrl::TEXTURE_SELECT); self->closeFloater(); } @@ -949,26 +1041,26 @@ void LLFloaterTexturePicker::onModeSelect(LLUICtrl* ctrl, void *userdata) //int index = self->mModeSelector->getValue().asInteger(); int index = self->mModeSelector->getSelectedIndex(); - self->getChild("Default")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild("Blank")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild("Transparent")->setVisible(index == 0 ? TRUE : FALSE); // FIRE-5082: "Transparent" button in Texture Panel - self->getChild("None")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild("Pipette")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild("inventory search editor")->setVisible(index == 0 ? TRUE : FALSE); - self->getChild("inventory panel")->setVisible(index == 0 ? TRUE : FALSE); + self->mDefaultBtn->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->mBlankBtn->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->mNoneBtn->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->mPipetteBtn->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->mTransparentBtn->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); // FIRE-5082: "Transparent" button in Texture Panel + self->getChild("inventory search editor")->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); + self->getChild("inventory panel")->setVisible(index == PICKER_INVENTORY ? TRUE : FALSE); /*self->getChild("show_folders_check")->setVisible(mode); no idea under which conditions the above is even shown, needs testing. */ - self->getChild("l_add_btn")->setVisible(index == 1 ? TRUE : FALSE); - self->getChild("l_rem_btn")->setVisible(index == 1 ? TRUE : FALSE); - self->getChild("l_upl_btn")->setVisible(index == 1 ? TRUE : FALSE); - self->getChild("l_name_list")->setVisible(index == 1 ? TRUE : FALSE); + self->getChild("l_add_btn")->setVisible(index == PICKER_LOCAL ? TRUE : FALSE); + self->getChild("l_rem_btn")->setVisible(index == PICKER_LOCAL ? TRUE : FALSE); + self->getChild("l_upl_btn")->setVisible(index == PICKER_LOCAL ? TRUE : FALSE); + self->getChild("l_name_list")->setVisible(index == PICKER_LOCAL ? TRUE : FALSE); - self->getChild("l_bake_use_texture_combo_box")->setVisible(index == 2 ? TRUE : FALSE); + self->getChild("l_bake_use_texture_combo_box")->setVisible(index == PICKER_BAKE ? TRUE : FALSE); self->getChild("hide_base_mesh_region")->setVisible(FALSE);// index == 2 ? TRUE : FALSE); - if (index == 2) + if (index == PICKER_BAKE) { self->stopUsingPipette(); @@ -1122,7 +1214,7 @@ void LLFloaterTexturePicker::onLocalScrollCommit(LLUICtrl* ctrl, void* userdata) { if (self->mOnFloaterCommitCallback) { - self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CHANGE, inworld_id); + self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CHANGE, PICKER_LOCAL, inworld_id, LLUUID::null); } } } @@ -1243,10 +1335,10 @@ void LLFloaterTexturePicker::updateFilterPermMask() //mInventoryPanel->setFilterPermMask( getFilterPermMask() ); Commented out due to no-copy texture loss. } -void LLFloaterTexturePicker::setCanApply(bool can_preview, bool can_apply) +void LLFloaterTexturePicker::setCanApply(bool can_preview, bool can_apply, bool inworld_image) { - getChildRef("Select").setEnabled(can_apply); - getChildRef("preview_disabled").setVisible(!can_preview); + mSelectBtn->setEnabled(can_apply); + getChildRef("preview_disabled").setVisible(!can_preview && inworld_image); getChildRef("apply_immediate_check").setVisible(can_preview); mCanApply = can_apply; @@ -1254,6 +1346,15 @@ void LLFloaterTexturePicker::setCanApply(bool can_preview, bool can_apply) mPreviewSettingChanged = true; } +void LLFloaterTexturePicker::setMinDimentionsLimits(S32 min_dim) +{ + mMinDim = min_dim; + mLimitsSet = true; + + std::string formatted_dims = llformat("%dx%d", mMinDim, mMinDim); + mResolutionWarning->setTextArg("[MINTEXDIM]", formatted_dims); +} + void LLFloaterTexturePicker::onFilterEdit(const std::string& search_string ) { std::string upper_case_search_string = search_string; @@ -1586,7 +1687,7 @@ void LLTextureCtrl::showPicker(BOOL take_focus) } if (texture_floaterp) { - texture_floaterp->setOnFloaterCommitCallback(boost::bind(&LLTextureCtrl::onFloaterCommit, this, _1, _2)); + texture_floaterp->setOnFloaterCommitCallback(boost::bind(&LLTextureCtrl::onFloaterCommit, this, _1, _2, _3, _4)); } if (texture_floaterp) { @@ -1659,8 +1760,11 @@ BOOL LLTextureCtrl::handleMouseDown(S32 x, S32 y, MASK mask) showPicker(FALSE); //grab textures first... LLInventoryModelBackgroundFetch::instance().start(gInventory.findCategoryUUIDForType(LLFolderType::FT_TEXTURE)); - //...then start full inventory fetch. - LLInventoryModelBackgroundFetch::instance().start(); + //...then start full inventory fetch (should have been done on startup, but just in case. + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + LLInventoryModelBackgroundFetch::instance().start(); + } } // Texture preview mode //else @@ -1711,7 +1815,7 @@ void LLTextureCtrl::onFloaterClose() mFloaterHandle.markDead(); } -void LLTextureCtrl::onFloaterCommit(ETexturePickOp op, LLUUID id) +void LLTextureCtrl::onFloaterCommit(ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID& inv_id) { LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); @@ -1725,22 +1829,29 @@ void LLTextureCtrl::onFloaterCommit(ETexturePickOp op, LLUUID id) else if (mCommitOnSelection || op == TEXTURE_SELECT) mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? - if(floaterp->isDirty() || id.notNull()) // mModelView->setDirty does not work. + if(floaterp->isDirty() || asset_id.notNull()) // mModelView->setDirty does not work. { setTentative( FALSE ); - if (id.notNull()) - { - mImageItemID = id; - mImageAssetID = id; - } - else - { - mImageItemID = floaterp->findItemID(floaterp->getAssetID(), FALSE); - LL_DEBUGS() << "mImageItemID: " << mImageItemID << LL_ENDL; - mImageAssetID = floaterp->getAssetID(); - LL_DEBUGS() << "mImageAssetID: " << mImageAssetID << LL_ENDL; - } + switch(source) + { + case PICKER_INVENTORY: + mImageItemID = inv_id; + mImageAssetID = asset_id; + break; + case PICKER_BAKE: + case PICKER_LOCAL: + mImageItemID = LLUUID::null; + mImageAssetID = asset_id; + break; + case PICKER_UNKNOWN: + default: + mImageItemID = floaterp->findItemID(asset_id, FALSE); + mImageAssetID = asset_id; + break; + } + + LL_DEBUGS() << "mImageAssetID: " << mImageAssetID << ", mImageItemID: " << mImageItemID << LL_ENDL; if (op == TEXTURE_SELECT && mOnSelectCallback) { diff --git a/indra/newview/lltexturectrl.h b/indra/newview/lltexturectrl.h index 7e20b74e45..cccb8db2d4 100644 --- a/indra/newview/lltexturectrl.h +++ b/indra/newview/lltexturectrl.h @@ -65,6 +65,14 @@ bool get_is_predefined_texture(LLUUID asset_id); LLUUID get_copy_free_item_by_asset_id(LLUUID image_id, bool no_trans_perm = false); bool get_can_copy_texture(LLUUID image_id); +enum LLPickerSource +{ + PICKER_INVENTORY, + PICKER_LOCAL, + PICKER_BAKE, + PICKER_UNKNOWN, // on cancel, default ids +}; + const LLUUID DEFAULT_BLANK_NORMAL_TEXTURE("5b53359e-59dd-d8a2-04c3-9e65134da47a"); const LLUUID DEFAULT_OBJECT_NORMAL_TEXTURE("85f28839-7a1c-b4e3-d71d-967792970a7b"); const LLUUID DEFAULT_OBJECT_SPECULAR_TEXTURE("87e0e8f7-8729-1ea8-cfc9-8915773009db"); @@ -199,7 +207,7 @@ public: void closeDependentFloater(); void onFloaterClose(); - void onFloaterCommit(ETexturePickOp op, LLUUID id); + void onFloaterCommit(ETexturePickOp op, LLPickerSource source, const LLUUID& local_id, const LLUUID& inv_id); // This call is returned when a drag is detected. Your callback // should return TRUE if the drag is acceptable. @@ -274,7 +282,7 @@ private: ////////////////////////////////////////////////////////////////////////////////////////// // LLFloaterTexturePicker -typedef boost::function floater_commit_callback; +typedef boost::function floater_commit_callback; typedef boost::function floater_close_callback; typedef boost::function set_image_asset_id_callback; typedef boost::function texture)> set_on_update_image_stats_callback; @@ -315,7 +323,7 @@ public: // New functions void setImageID(const LLUUID& image_asset_id, bool set_selection = true); - void updateImageStats(); + bool updateImageStats(); // true if within limits const LLUUID& getAssetID() { return mImageAssetID; } const LLUUID& findItemID(const LLUUID& asset_id, BOOL copyable_only, BOOL ignore_library = FALSE); void setCanApplyImmediately(BOOL b); @@ -329,11 +337,13 @@ public: void updateFilterPermMask(); void commitIfImmediateSet(); + void commitCallback(LLTextureCtrl::ETexturePickOp op); void commitCancel(); void onFilterEdit(const std::string& search_string); - void setCanApply(bool can_preview, bool can_apply); + void setCanApply(bool can_preview, bool can_apply, bool inworld_image = true); + void setMinDimentionsLimits(S32 min_dim); void setTextureSelectedCallback(const texture_selected_callback& cb) { mTextureSelectedCallback = cb; } void setOnFloaterCloseCallback(const floater_close_callback& cb) { mOnFloaterCloseCallback = cb; } void setOnFloaterCommitCallback(const floater_commit_callback& cb) { mOnFloaterCommitCallback = cb; } @@ -390,6 +400,7 @@ protected: LLTextBox* mTentativeLabel; LLTextBox* mResolutionLabel; + LLTextBox* mResolutionWarning; std::string mPendingName; BOOL mActive; @@ -409,11 +420,24 @@ protected: //LLComboBox* mModeSelector; LLRadioGroup* mModeSelector; LLScrollListCtrl* mLocalScrollCtrl; + LLButton* mDefaultBtn; + LLButton* mNoneBtn; + LLButton* mBlankBtn; + LLButton* mPipetteBtn; + LLButton* mSelectBtn; + LLButton* mCancelBtn; + // Special additions + LLLineEditor* mUUIDEditor{ nullptr }; + LLButton* mUUIDBtn{ nullptr }; + LLButton* mTransparentBtn{ nullptr }; private: bool mCanApply; bool mCanPreview; bool mPreviewSettingChanged; + bool mLimitsSet; + S32 mMaxDim; + S32 mMinDim; texture_selected_callback mTextureSelectedCallback; diff --git a/indra/newview/llthumbnailctrl.cpp b/indra/newview/llthumbnailctrl.cpp new file mode 100644 index 0000000000..04130fc724 --- /dev/null +++ b/indra/newview/llthumbnailctrl.cpp @@ -0,0 +1,239 @@ +/** + * @file llthumbnailctrl.cpp + * @brief LLThumbnailCtrl base class + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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 "llthumbnailctrl.h" + +#include "linden_common.h" +#include "llagent.h" +#include "lluictrlfactory.h" +#include "lluuid.h" +#include "lltrans.h" +#include "llviewborder.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llwindow.h" + +static LLDefaultChildRegistry::Register r("thumbnail"); + +LLThumbnailCtrl::Params::Params() +: border("border") +, border_color("border_color") +, fallback_image("fallback_image") +, image_name("image_name") +, border_visible("show_visible", false) +, interactable("interactable", false) +, show_loading("show_loading", true) +{} + +LLThumbnailCtrl::LLThumbnailCtrl(const LLThumbnailCtrl::Params& p) +: LLUICtrl(p) +, mBorderColor(p.border_color()) +, mBorderVisible(p.border_visible()) +, mFallbackImagep(p.fallback_image) +, mInteractable(p.interactable()) +, mShowLoadingPlaceholder(p.show_loading()) +, mPriority(LLGLTexture::BOOST_PREVIEW) +{ + mLoadingPlaceholderString = LLTrans::getString("texture_loading"); + + LLRect border_rect = getLocalRect(); + LLViewBorder::Params vbparams(p.border); + vbparams.name("border"); + vbparams.rect(border_rect); + mBorder = LLUICtrlFactory::create (vbparams); + addChild(mBorder); + + if (p.image_name.isProvided()) + { + setValue(p.image_name()); + } +} + +LLThumbnailCtrl::~LLThumbnailCtrl() +{ + mTexturep = nullptr; + mImagep = nullptr; + mFallbackImagep = nullptr; +} + + +void LLThumbnailCtrl::draw() +{ + LLRect draw_rect = getLocalRect(); + + if (mBorderVisible) + { + mBorder->setKeyboardFocusHighlight(hasFocus()); + + gl_rect_2d( draw_rect, mBorderColor.get(), FALSE ); + draw_rect.stretch( -1 ); + } + + // If we're in a focused floater, don't apply the floater's alpha to the texture. + const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + if( mTexturep ) + { + if( mTexturep->getComponents() == 4 ) + { + const LLColor4 color(.098f, .098f, .098f); + gl_rect_2d( draw_rect, color, TRUE); + } + + gl_draw_scaled_image( draw_rect.mLeft, draw_rect.mBottom, draw_rect.getWidth(), draw_rect.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); + + mTexturep->setKnownDrawSize(draw_rect.getWidth(), draw_rect.getHeight()); + } + else if( mImagep.notNull() ) + { + mImagep->draw(draw_rect, UI_VERTEX_COLOR % alpha ); + } + else if (mFallbackImagep.notNull()) + { + if (draw_rect.getWidth() > mFallbackImagep->getWidth() + && draw_rect.getHeight() > mFallbackImagep->getHeight()) + { + S32 img_width = mFallbackImagep->getWidth(); + S32 img_height = mFallbackImagep->getHeight(); + S32 rect_width = draw_rect.getWidth(); + S32 rect_height = draw_rect.getHeight(); + + LLRect fallback_rect; + fallback_rect.mLeft = draw_rect.mLeft + (rect_width - img_width) / 2; + fallback_rect.mRight = fallback_rect.mLeft + img_width; + fallback_rect.mBottom = draw_rect.mBottom + (rect_height - img_height) / 2; + fallback_rect.mTop = fallback_rect.mBottom + img_height; + + mFallbackImagep->draw(fallback_rect, UI_VERTEX_COLOR % alpha); + } + else + { + mFallbackImagep->draw(draw_rect, UI_VERTEX_COLOR % alpha); + } + } + else + { + gl_rect_2d( draw_rect, LLColor4::grey % alpha, TRUE ); + + // Draw X + gl_draw_x( draw_rect, LLColor4::black ); + } + + // Show "Loading..." string on the top left corner while this texture is loading. + // Using the discard level, do not show the string if the texture is almost but not + // fully loaded. + if (mTexturep.notNull() + && mShowLoadingPlaceholder + && !mTexturep->isFullyLoaded()) + { + U32 v_offset = 25; + LLFontGL* font = LLFontGL::getFontSansSerif(); + + // Don't show as loaded if the texture is almost fully loaded (i.e. discard1) unless god + if ((mTexturep->getDiscardLevel() > 1) || gAgent.isGodlike()) + { + font->renderUTF8( + mLoadingPlaceholderString, + 0, + llfloor(draw_rect.mLeft+3), + llfloor(draw_rect.mTop-v_offset), + LLColor4::white, + LLFontGL::LEFT, + LLFontGL::BASELINE, + LLFontGL::DROP_SHADOW); + } + } + + LLUICtrl::draw(); +} + +void LLThumbnailCtrl::clearTexture() +{ + mImageAssetID = LLUUID::null; + mTexturep = nullptr; + mImagep = nullptr; +} + +// virtual +// value might be a string or a UUID +void LLThumbnailCtrl::setValue(const LLSD& value) +{ + LLSD tvalue(value); + if (value.isString() && LLUUID::validate(value.asString())) + { + //RN: support UUIDs masquerading as strings + tvalue = LLSD(LLUUID(value.asString())); + } + + LLUICtrl::setValue(tvalue); + + mImageAssetID = LLUUID::null; + mTexturep = nullptr; + mImagep = nullptr; + + if (tvalue.isUUID()) + { + mImageAssetID = tvalue.asUUID(); + if (mImageAssetID.notNull()) + { + // Should it support baked textures? + mTexturep = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, MIPMAP_YES, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + + mTexturep->setBoostLevel(mPriority); + mTexturep->forceToSaveRawImage(0); + + S32 desired_draw_width = mTexturep->getWidth(); + S32 desired_draw_height = mTexturep->getHeight(); + + mTexturep->setKnownDrawSize(desired_draw_width, desired_draw_height); + } + } + else if (tvalue.isString()) + { + mImagep = LLUI::getUIImage(tvalue.asString(), LLGLTexture::BOOST_UI); + if (mImagep) + { + LLViewerFetchedTexture* texture = dynamic_cast(mImagep->getImage().get()); + if(texture) + { + mImageAssetID = texture->getID(); + } + } + } +} + +BOOL LLThumbnailCtrl::handleHover(S32 x, S32 y, MASK mask) +{ + if (mInteractable && getEnabled()) + { + getWindow()->setCursor(UI_CURSOR_HAND); + return TRUE; + } + return LLUICtrl::handleHover(x, y, mask); +} + + diff --git a/indra/newview/llthumbnailctrl.h b/indra/newview/llthumbnailctrl.h new file mode 100644 index 0000000000..686603b373 --- /dev/null +++ b/indra/newview/llthumbnailctrl.h @@ -0,0 +1,88 @@ +/** + * @file llthumbnailctrl.h + * @brief LLThumbnailCtrl base class + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023 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_LLTHUMBNAILCTRL_H +#define LL_LLTHUMBNAILCTRL_H + +#include "llui.h" +#include "lluictrl.h" +#include "llviewborder.h" // for params + +class LLUICtrlFactory; +class LLUUID; +class LLViewerFetchedTexture; + +// +// Classes +// + +// +class LLThumbnailCtrl +: public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Optional border; + Optional border_color; + Optional image_name; + Optional fallback_image; + Optional border_visible; + Optional interactable; + Optional show_loading; + + Params(); + }; +protected: + LLThumbnailCtrl(const Params&); + friend class LLUICtrlFactory; + +public: + virtual ~LLThumbnailCtrl(); + + virtual void draw() override; + + virtual void setValue(const LLSD& value ) override; + void clearTexture(); + + virtual BOOL handleHover(S32 x, S32 y, MASK mask) override; + +private: + S32 mPriority; + bool mBorderVisible; + bool mInteractable; + bool mShowLoadingPlaceholder; + std::string mLoadingPlaceholderString; + LLUUID mImageAssetID; + LLViewBorder* mBorder; + LLUIColor mBorderColor; + + LLPointer mTexturep; + LLPointer mImagep; + LLPointer mFallbackImagep; +}; + +#endif diff --git a/indra/newview/lltoastalertpanel.cpp b/indra/newview/lltoastalertpanel.cpp index 9116cb6684..ebeab5c1f3 100644 --- a/indra/newview/lltoastalertpanel.cpp +++ b/indra/newview/lltoastalertpanel.cpp @@ -285,6 +285,10 @@ LLToastAlertPanel::LLToastAlertPanel( LLNotificationPtr notification, bool modal if (!edit_text_name.empty()) { S32 y = VPAD + BTN_HEIGHT + VPAD/2; + if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) + { + y += EDITOR_HEIGHT; + } mLineEditor = LLUICtrlFactory::getInstance()->createFromFile("alert_line_editor.xml", this, LLPanel::child_registry_t::instance()); if (mLineEditor) @@ -543,6 +547,10 @@ void LLToastAlertPanel::onButtonPressed( const LLSD& data, S32 button ) { response[mLineEditor->getName()] = mLineEditor->getValue(); } + if (mNotification->getForm()->getIgnoreType() != LLNotificationForm::IGNORE_NO) + { + response["ignore"] = mNotification->isIgnored(); + } response[button_data->mButton->getName()] = true; // If we declared a URL and chose the URL option, go to the url diff --git a/indra/newview/lltoolbarview.cpp b/indra/newview/lltoolbarview.cpp index bbeab246b6..0222ca2e03 100644 --- a/indra/newview/lltoolbarview.cpp +++ b/indra/newview/lltoolbarview.cpp @@ -385,9 +385,9 @@ bool LLToolBarView::loadToolbars(bool force_default) } // SL-18581: Don't show the starter avatar toolbar button for NUX users - LLViewerInventoryCategory* my_outfits_cat = gInventory.getCategory(gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); if (gAgent.isFirstLogin()) { + LLViewerInventoryCategory* my_outfits_cat = gInventory.getCategory(gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); LL_WARNS() << "First login: checking for NUX user." << LL_ENDL; if (my_outfits_cat != NULL && my_outfits_cat->getDescendentCount() > 0) { diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp index 344843ce8e..f63b73f404 100644 --- a/indra/newview/lltooldraganddrop.cpp +++ b/indra/newview/lltooldraganddrop.cpp @@ -772,7 +772,7 @@ void LLToolDragAndDrop::dragOrDrop( S32 x, S32 y, MASK mask, BOOL drop, if (!handled) { // Disallow drag and drop to 3D from the marketplace - const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (marketplacelistings_id.notNull()) { for (S32 item_index = 0; item_index < (S32)mCargoIDs.size(); item_index++) @@ -1779,8 +1779,8 @@ EAcceptance LLToolDragAndDrop::dad3dRezAttachmentFromInv( return ACCEPT_NO; } - const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); - if(gInventory.isObjectDescendentOf(item->getUUID(), outbox_id)) + const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX); + if(outbox_id.notNull() && gInventory.isObjectDescendentOf(item->getUUID(), outbox_id)) { // Legacy return ACCEPT_NO; @@ -2253,8 +2253,8 @@ EAcceptance LLToolDragAndDrop::dad3dWearCategory( return ACCEPT_NO; } - const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX, false); - if(gInventory.isObjectDescendentOf(category->getUUID(), outbox_id)) + const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX); + if(outbox_id.notNull() && gInventory.isObjectDescendentOf(category->getUUID(), outbox_id)) { // Legacy return ACCEPT_NO; diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index ad56f4e5c9..ca5fa49ef6 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -56,6 +56,7 @@ #include "llfloaterbvhpreview.h" #include "llfloatercamera.h" #include "llfloatercamerapresets.h" +#include "llfloaterchangeitemthumbnail.h" #include "llfloaterchatvoicevolume.h" #include "llfloaterclassified.h" #include "llfloaterconversationlog.h" @@ -84,6 +85,7 @@ #include "llfloaterimagepreview.h" #include "llfloaterimsession.h" #include "llfloaterinspect.h" +#include "llfloaterinventorysettings.h" #include "llfloaterjoystick.h" #include "llfloaterlagmeter.h" #include "llfloaterland.h" @@ -98,12 +100,12 @@ #include "llfloatermyscripts.h" #include "llfloatermyenvironment.h" #include "llfloaternamedesc.h" +#include "llfloaternewfeaturenotification.h" #include "llfloaternotificationsconsole.h" #include "llfloaternotificationstabbed.h" #include "llfloaterobjectweights.h" #include "llfloateropenobject.h" -#include "llfloateroutfitphotopreview.h" -#include "llfloatersimpleoutfitsnapshot.h" +#include "llfloatersimplesnapshot.h" #include "llfloaterpathfindingcharacters.h" #include "llfloaterpathfindingconsole.h" #include "llfloaterpathfindinglinksets.h" @@ -117,7 +119,7 @@ #include "llfloaterpreferenceviewadvanced.h" #include "llfloaterpreviewtrash.h" #include "llfloaterprofile.h" -#include "llfloaterproperties.h" +#include "llfloaterproperties.h" // Keep legacy properties floater #include "llfloaterregiondebugconsole.h" #include "llfloaterregioninfo.h" #include "llfloaterregionrestarting.h" @@ -283,6 +285,7 @@ public: "avatar_picker", "camera", "camera_presets", + "change_item_thumbnail" "classified", "add_landmark", "delete_pref_preset", @@ -301,6 +304,7 @@ public: "message_critical", // Modal!!! Login specific. If this is in use elsewhere, better to create a non modal variant "message_tos", // Modal!!! Login specific. "mute_object_by_name", + "new_feature_notification", "publish_classified", "save_pref_preset", "save_camera_preset", @@ -396,6 +400,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("fs_camera_small", "floater_fs_camera_small.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("camera_presets", "floater_camera_presets.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("chat_voice", "floater_voice_chat_volume.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("change_item_thumbnail", "floater_change_item_thumbnail.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); // [FS communication UI] //LLFloaterReg::add("nearby_chat", "floater_im_session.xml", (LLFloaterBuildFunc)&LLFloaterIMNearbyChat::buildFloater); LLFloaterReg::add("fs_nearby_chat", "floater_fs_nearby_chat.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); @@ -446,9 +451,8 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("inventory", "floater_my_inventory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("inspect", "floater_inspect.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("item_properties", "floater_item_properties.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - -// LLFloaterReg::add("floater_settings_picker", "floater_settings_picker.xml" , (LLFloaterBuildFunc)&LLFloaterReg::build); - + LLFloaterReg::add("task_properties", "floater_task_properties.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("inventory_settings", "floater_inventory_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLInspectAvatarUtil::registerFloater(); LLInspectGroupUtil::registerFloater(); LLInspectObjectUtil::registerFloater(); @@ -476,6 +480,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("moveview", "floater_moveview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("mute_object_by_name", "floater_mute_object.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("mini_map", "floater_map.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("new_feature_notification", "floater_new_feature_notification.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("notifications_console", "floater_notifications_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); @@ -494,7 +499,6 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("object_weights", "floater_object_weights.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("openobject", "floater_openobject.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("outgoing_call", "floater_outgoing_call.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("outfit_photo_preview", "floater_outfit_photo_preview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterPayUtil::registerFloater(); LLFloaterReg::add("pathfinding_characters", "floater_pathfinding_characters.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); @@ -522,7 +526,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("preview_sound", "floater_preview_sound.xml", (LLFloaterBuildFunc)&LLFloaterReg::build/*, "preview"*/); LLFloaterReg::add("preview_texture", "floater_preview_texture.xml", (LLFloaterBuildFunc)&LLFloaterReg::build/*, "preview"*/); LLFloaterReg::add("preview_trash", "floater_preview_trash.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("properties", "floater_inventory_item_properties.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("properties", "floater_inventory_item_properties.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); // Keep legacy properties floater LLFloaterReg::add("publish_classified", "floater_publish_classified.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("save_pref_preset", "floater_save_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("save_camera_preset", "floater_save_camera_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); @@ -565,7 +569,7 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("scene_load_stats", "floater_scene_load_stats.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("stop_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("snapshot", "floater_snapshot.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("simple_outfit_snapshot", "floater_simple_outfit_snapshot.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("simple_snapshot", "floater_simple_snapshot.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); // Search floater is deferred to login now so we can tell what grid we're in. //LLFloaterReg::add("search", "floater_search.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("profile", "floater_profile.xml",(LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index bd986760ae..d1e329f99f 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -74,6 +74,7 @@ // [RLVa:KB] - Checked: 2014-11-02 (RLVa-1.4.11) #include "rlvcommon.h" // [/RLVa:KB] +#include "llviewernetwork.h" // do-nothing ops for use in callbacks. void no_op_inventory_func(const LLUUID&) {} @@ -320,7 +321,7 @@ LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& uuid, time_t creation_date_utc) : LLInventoryItem(uuid, parent_uuid, perm, asset_uuid, type, inv_type, name, desc, sale_info, flags, creation_date_utc), - mIsComplete(TRUE) + mIsComplete(true) { } @@ -329,7 +330,7 @@ LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& item_id, const std::string& name, LLInventoryType::EType inv_type) : LLInventoryItem(), - mIsComplete(FALSE) + mIsComplete(false) { mUUID = item_id; mParentUUID = parent_id; @@ -339,7 +340,7 @@ LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& item_id, LLViewerInventoryItem::LLViewerInventoryItem() : LLInventoryItem(), - mIsComplete(FALSE) + mIsComplete(false) { } @@ -356,7 +357,7 @@ LLViewerInventoryItem::LLViewerInventoryItem(const LLViewerInventoryItem* other) LLViewerInventoryItem::LLViewerInventoryItem(const LLInventoryItem *other) : LLInventoryItem(other), - mIsComplete(TRUE) + mIsComplete(true) { } @@ -459,48 +460,59 @@ void LLViewerInventoryItem::fetchFromServer(void) const { if(!mIsComplete) { - std::string url; + if (AISAPI::isAvailable()) // AIS v 3 + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(mUUID); + } + else + { + std::string url; - LLViewerRegion* region = gAgent.getRegion(); - // we have to check region. It can be null after region was destroyed. See EXT-245 - if (region) - { - if (gAgent.getID() != mPermissions.getOwner()) - { - url = region->getCapability("FetchLib2"); - } - else - { - url = region->getCapability("FetchInventory2"); - } - } - else - { - LL_WARNS(LOG_INV) << "Agent Region is absent" << LL_ENDL; - } + LLViewerRegion* region = gAgent.getRegion(); + // we have to check region. It can be null after region was destroyed. See EXT-245 + if (region) + { + if (gAgent.getID() != mPermissions.getOwner()) + { + url = region->getCapability("FetchLib2"); + } + else + { + url = region->getCapability("FetchInventory2"); + } + } + else + { + LL_WARNS(LOG_INV) << "Agent Region is absent" << LL_ENDL; + } - if (!url.empty()) - { - LLSD body; - body["agent_id"] = gAgent.getID(); - body["items"][0]["owner_id"] = mPermissions.getOwner(); - body["items"][0]["item_id"] = mUUID; + if (!url.empty()) + { + LLSD body; + body["agent_id"] = gAgent.getID(); + body["items"][0]["owner_id"] = mPermissions.getOwner(); + body["items"][0]["item_id"] = mUUID; - LLCore::HttpHandler::ptr_t handler(new LLInventoryModel::FetchItemHttpHandler(body)); - gInventory.requestPost(true, url, body, handler, "Inventory Item"); - } - else - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("FetchInventory"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("InventoryData"); - msg->addUUID("OwnerID", mPermissions.getOwner()); - msg->addUUID("ItemID", mUUID); - gAgent.sendReliableMessage(); - } + LLCore::HttpHandler::ptr_t handler(new LLInventoryModel::FetchItemHttpHandler(body)); + gInventory.requestPost(true, url, body, handler, "Inventory Item"); + } + // OpenSim compatibility +#ifdef OPENSIM + else if (LLGridManager::instance().isInOpenSim()) // no cap + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("FetchInventory"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("InventoryData"); + msg->addUUID("OwnerID", mPermissions.getOwner()); + msg->addUUID("ItemID", mUUID); + gAgent.sendReliableMessage(); + } +#endif + // + } } } @@ -595,7 +607,8 @@ LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& uuid, LLInventoryCategory(uuid, parent_uuid, pref, name), mOwnerID(owner_id), mVersion(LLViewerInventoryCategory::VERSION_UNKNOWN), - mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) + mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN), + mFetching(FETCH_NONE) { mDescendentsRequested.reset(); } @@ -603,7 +616,8 @@ LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& uuid, LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& owner_id) : mOwnerID(owner_id), mVersion(LLViewerInventoryCategory::VERSION_UNKNOWN), - mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) + mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN), + mFetching(FETCH_NONE) { mDescendentsRequested.reset(); } @@ -611,6 +625,7 @@ LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& owner_id) : LLViewerInventoryCategory::LLViewerInventoryCategory(const LLViewerInventoryCategory* other) { copyViewerCategory(other); + mFetching = FETCH_NONE; } LLViewerInventoryCategory::~LLViewerInventoryCategory() @@ -707,7 +722,6 @@ bool LLViewerInventoryCategory::fetch() mDescendentsRequested.reset(); mDescendentsRequested.setTimerExpirySec(FETCH_TIMER_EXPIRY); - std::string url; if (gAgent.getRegion()) { @@ -717,9 +731,9 @@ bool LLViewerInventoryCategory::fetch() { LL_WARNS(LOG_INV) << "agent region is null" << LL_ENDL; } - if (!url.empty()) //Capability found. Build up LLSD and use it. + if (!url.empty() || AISAPI::isAvailable()) { - LLInventoryModelBackgroundFetch::instance().start(mUUID, false); + LLInventoryModelBackgroundFetch::instance().start(mUUID, false); } // [UDP-Msg] else @@ -751,6 +765,43 @@ bool LLViewerInventoryCategory::fetch() return false; } +LLViewerInventoryCategory::EFetchType LLViewerInventoryCategory::getFetching() +{ + // if timer hasn't expired, request was scheduled, but not in progress + // if mFetching request was actually started + if (mDescendentsRequested.hasExpired()) + { + mFetching = FETCH_NONE; + } + return mFetching; +} + +void LLViewerInventoryCategory::setFetching(LLViewerInventoryCategory::EFetchType fetching) +{ + if (fetching > mFetching) // allow a switch from normal to recursive + { + if (mDescendentsRequested.hasExpired() || (mFetching == FETCH_NONE)) + { + mDescendentsRequested.reset(); + if (AISAPI::isAvailable()) + { + mDescendentsRequested.setTimerExpirySec(AISAPI::HTTP_TIMEOUT); + } + else + { + const F32 FETCH_TIMER_EXPIRY = 30.0f; + mDescendentsRequested.setTimerExpirySec(FETCH_TIMER_EXPIRY); + } + } + mFetching = fetching; + } + else if (fetching == FETCH_NONE) + { + mDescendentsRequested.stop(); + mFetching = fetching; + } +} + S32 LLViewerInventoryCategory::getViewerDescendentCount() const { LLInventoryModel::cat_array_t* cats; @@ -1102,13 +1153,18 @@ void create_notecard_cb(const LLUUID& inv_item) LLInventoryCallbackManager gInventoryCallbacks; -void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id, - const LLUUID& parent, const LLTransactionID& transaction_id, - const std::string& name, - const std::string& desc, LLAssetType::EType asset_type, - LLInventoryType::EType inv_type, U8 subtype, - U32 next_owner_perm, - LLPointer cb) +void create_inventory_item( + const LLUUID& agent_id, + const LLUUID& session_id, + const LLUUID& parent_id, + const LLTransactionID& transaction_id, + const std::string& name, + const std::string& desc, + LLAssetType::EType asset_type, + LLInventoryType::EType inv_type, + U8 subtype, + U32 next_owner_perm, + LLPointer cb) { //check if name is equal to one of special inventory items names //EXT-5839 @@ -1129,6 +1185,54 @@ void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id, } } +#ifdef USE_AIS_FOR_NC + // D567 18.03.2023 not yet implemented within AIS3 + if (AISAPI::isAvailable()) + { + LLSD new_inventory = LLSD::emptyMap(); + new_inventory["items"] = LLSD::emptyArray(); + + LLPermissions perms; + perms.init( + gAgentID, + gAgentID, + LLUUID::null, + LLUUID::null); + perms.initMasks( + PERM_ALL, + PERM_ALL, + PERM_NONE, + PERM_NONE, + next_owner_perm); + + LLUUID null_id; + LLPointer item = new LLViewerInventoryItem( + null_id, /*don't know yet*/ + parent_id, + perms, + null_id, /*don't know yet*/ + asset_type, + inv_type, + server_name, + desc, + LLSaleInfo(), + 0, + 0 /*don't know yet, whenever server creates it*/); + LLSD item_sd = item->asLLSD(); + new_inventory["items"].append(item_sd); + AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); + AISAPI::CreateInventory( + parent_id, + new_inventory, + cr); + return; + } + else + { + LL_WARNS() << "AIS v3 not available" << LL_ENDL; + } +#endif + LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_CreateInventoryItem); msg->nextBlock(_PREHASH_AgentData); @@ -1136,7 +1240,7 @@ void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id, msg->addUUIDFast(_PREHASH_SessionID, session_id); msg->nextBlock(_PREHASH_InventoryBlock); msg->addU32Fast(_PREHASH_CallbackID, gInventoryCallbacks.registerCB(cb)); - msg->addUUIDFast(_PREHASH_FolderID, parent); + msg->addUUIDFast(_PREHASH_FolderID, parent_id); msg->addUUIDFast(_PREHASH_TransactionID, transaction_id); msg->addU32Fast(_PREHASH_NextOwnerMask, next_owner_perm); msg->addS8Fast(_PREHASH_Type, (S8)asset_type); @@ -1498,25 +1602,25 @@ void update_inventory_category( LL_DEBUGS(LOG_INV) << "cat_id: [" << cat_id << "] name " << (obj ? obj->getName() : "(NOT FOUND)") << LL_ENDL; if(obj) { - if (LLFolderType::lookupIsProtectedType(obj->getPreferredType())) + if (LLFolderType::lookupIsProtectedType(obj->getPreferredType()) + && (updates.size() != 1 || !updates.has("thumbnail"))) { LLNotificationsUtil::add("CannotModifyProtectedCategories"); return; } - LLPointer new_cat = new LLViewerInventoryCategory(obj); - new_cat->fromLLSD(updates); // [UDP-Msg] if (AISAPI::isAvailable()) { // [UDP-Msg] - LLSD new_llsd = new_cat->asLLSD(); AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::UpdateCategory(cat_id, new_llsd, cr); + AISAPI::UpdateCategory(cat_id, updates, cr); // [UDP-Msg] } else { + LLPointer new_cat = new LLViewerInventoryCategory(obj); + new_cat->fromLLSD(updates); LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_UpdateInventoryFolder); msg->nextBlockFast(_PREHASH_AgentData); @@ -1586,7 +1690,9 @@ void remove_inventory_item( gInventory.onObjectDeletedFromServer(item_id); } } - else // no cap + // OpenSim compatibility +#ifdef OPENSIM + else if (LLGridManager::instance().isInOpenSim()) // no cap { LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RemoveInventoryItem); @@ -1605,6 +1711,12 @@ void remove_inventory_item( cb->fire(item_id); } } +#endif + // + else + { + LL_WARNS(LOG_INV) << "Tried to use inventory without AIS API" << LL_ENDL; + } } else { @@ -1771,7 +1883,9 @@ void purge_descendents_of(const LLUUID& id, LLPointer cb) AISAPI::completion_t cr = (cb) ? boost::bind(&doInventoryCb, cb, _1) : AISAPI::completion_t(); AISAPI::PurgeDescendents(id, cr); } - else // no cap + // OpenSim compatibility +#ifdef OPENSIM + else if (LLGridManager::instance().isInOpenSim()) // no cap { // Fast purge LL_DEBUGS(LOG_INV) << "purge_descendents_of fast case " << cat->getName() << LL_ENDL; @@ -1793,6 +1907,12 @@ void purge_descendents_of(const LLUUID& id, LLPointer cb) cb->fire(id); } } +#endif + // + else + { + LL_WARNS(LOG_INV) << "Tried to use inventory without AIS API" << LL_ENDL; + } } } @@ -1864,13 +1984,14 @@ void create_new_item(const std::string& name, const LLUUID& parent_id, LLAssetType::EType asset_type, LLInventoryType::EType inv_type, - U32 next_owner_perm) + U32 next_owner_perm, + std::function created_cb = NULL) { std::string desc; LLViewerAssetType::generateDescriptionFor(asset_type, desc); next_owner_perm = (next_owner_perm) ? next_owner_perm : PERM_MOVE | PERM_TRANSFER; - LLPointer cb = NULL; + LLPointer cb = NULL; switch (inv_type) { @@ -1894,9 +2015,17 @@ void create_new_item(const std::string& name, next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Notecards"); break; } - default: - break; - } + + default: + { + cb = new LLBoostFuncInventoryCallback(); + break; + } + } + if (created_cb != NULL) + { + cb->addOnFireFunc(created_cb); + } create_inventory_item(gAgent.getID(), gAgent.getSessionID(), @@ -2048,57 +2177,86 @@ const std::string NEW_GESTURE_NAME = "New Gesture"; // *TODO:Translate? (probabl // ! REFACTOR ! Really need to refactor this so that it's not a bunch of if-then statements... void menu_create_inventory_item(LLInventoryPanel* panel, LLFolderBridge *bridge, const LLSD& userdata, const LLUUID& default_parent_uuid) { - std::string type_name = userdata.asString(); - - if (("inbox" == type_name) || ("category" == type_name) || ("current" == type_name) || ("outfit" == type_name) || ("my_otfts" == type_name)) - { - LLFolderType::EType preferred_type = LLFolderType::lookup(type_name); + menu_create_inventory_item(panel, bridge ? bridge->getUUID() : LLUUID::null, userdata, default_parent_uuid); +} - LLUUID parent_id; - if (bridge) - { - parent_id = bridge->getUUID(); - } - else if (default_parent_uuid.notNull()) - { - parent_id = default_parent_uuid; - } - else - { - parent_id = gInventory.getRootFolderID(); - } +void menu_create_inventory_item(LLInventoryPanel* panel, LLUUID dest_id, const LLSD& userdata, const LLUUID& default_parent_uuid, std::function created_cb) +{ + std::string type_name = userdata.asString(); + + if (("inbox" == type_name) || ("category" == type_name) || ("current" == type_name) || ("outfit" == type_name) || ("my_otfts" == type_name)) + { + LLFolderType::EType preferred_type = LLFolderType::lookup(type_name); - LLUUID category = gInventory.createNewCategory(parent_id, preferred_type, LLStringUtil::null); - gInventory.notifyObservers(); - panel->setSelectionByID(category, TRUE); - } - else if ("lsl" == type_name) - { - const LLUUID parent_id = bridge ? bridge->getUUID() : gInventory.findCategoryUUIDForType(LLFolderType::FT_LSL_TEXT); - create_new_item(NEW_LSL_NAME, - parent_id, - LLAssetType::AT_LSL_TEXT, - LLInventoryType::IT_LSL, - PERM_MOVE | PERM_TRANSFER); // overridden in create_new_item - } - else if ("notecard" == type_name) - { - const LLUUID parent_id = bridge ? bridge->getUUID() : gInventory.findCategoryUUIDForType(LLFolderType::FT_NOTECARD); - create_new_item(NEW_NOTECARD_NAME, - parent_id, - LLAssetType::AT_NOTECARD, - LLInventoryType::IT_NOTECARD, - PERM_ALL); // overridden in create_new_item - } - else if ("gesture" == type_name) - { - const LLUUID parent_id = bridge ? bridge->getUUID() : gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); - create_new_item(NEW_GESTURE_NAME, - parent_id, - LLAssetType::AT_GESTURE, - LLInventoryType::IT_GESTURE, - PERM_ALL); // overridden in create_new_item - } + LLUUID parent_id; + if (dest_id.notNull()) + { + parent_id = dest_id; + } + else if (default_parent_uuid.notNull()) + { + parent_id = default_parent_uuid; + } + else + { + parent_id = gInventory.getRootFolderID(); + } + + std::function callback_cat_created = NULL; + if (panel) + { + LLHandle handle = panel->getHandle(); + callback_cat_created = [handle](const LLUUID& new_category_id) + { + gInventory.notifyObservers(); + LLInventoryPanel* panel = static_cast(handle.get()); + if (panel) + { + panel->setSelectionByID(new_category_id, TRUE); + } + LL_DEBUGS(LOG_INV) << "Done creating inventory: " << new_category_id << LL_ENDL; + }; + } + else if (created_cb != NULL) + { + callback_cat_created = created_cb; + } + gInventory.createNewCategory( + parent_id, + preferred_type, + LLStringUtil::null, + callback_cat_created); + } + else if ("lsl" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_LSL_TEXT); + create_new_item(NEW_LSL_NAME, + parent_id, + LLAssetType::AT_LSL_TEXT, + LLInventoryType::IT_LSL, + PERM_MOVE | PERM_TRANSFER, + created_cb); // overridden in create_new_item + } + else if ("notecard" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_NOTECARD); + create_new_item(NEW_NOTECARD_NAME, + parent_id, + LLAssetType::AT_NOTECARD, + LLInventoryType::IT_NOTECARD, + PERM_ALL, + created_cb); // overridden in create_new_item + } + else if ("gesture" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); + create_new_item(NEW_GESTURE_NAME, + parent_id, + LLAssetType::AT_GESTURE, + LLInventoryType::IT_GESTURE, + PERM_ALL, + created_cb); // overridden in create_new_item + } else if (("sky" == type_name) || ("water" == type_name) || ("daycycle" == type_name)) { LLSettingsType::type_e stype(LLSettingsType::ST_NONE); @@ -2121,25 +2279,28 @@ void menu_create_inventory_item(LLInventoryPanel* panel, LLFolderBridge *bridge, return; } - LLUUID parent_id = bridge ? bridge->getUUID() : gInventory.findCategoryUUIDForType(LLFolderType::FT_SETTINGS); + LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_SETTINGS); - LLSettingsVOBase::createNewInventoryItem(stype, parent_id); + LLSettingsVOBase::createNewInventoryItem(stype, parent_id, created_cb); + } + else + { + // Use for all clothing and body parts. Adding new wearable types requires updating LLWearableDictionary. + LLWearableType::EType wearable_type = LLWearableType::getInstance()->typeNameToType(type_name); + if (wearable_type >= LLWearableType::WT_SHAPE && wearable_type < LLWearableType::WT_COUNT) + { + const LLUUID parent_id = dest_id; + LLAgentWearables::createWearable(wearable_type, false, parent_id, created_cb); + } + else + { + LL_WARNS(LOG_INV) << "Can't create unrecognized type " << type_name << LL_ENDL; + } + } + if(panel) + { + panel->getRootFolder()->setNeedsAutoRename(TRUE); } - else - { - // Use for all clothing and body parts. Adding new wearable types requires updating LLWearableDictionary. - LLWearableType::EType wearable_type = LLWearableType::getInstance()->typeNameToType(type_name); - if (wearable_type >= LLWearableType::WT_SHAPE && wearable_type < LLWearableType::WT_COUNT) - { - const LLUUID parent_id = bridge ? bridge->getUUID() : LLUUID::null; - LLAgentWearables::createWearable(wearable_type, false, parent_id); - } - else - { - LL_WARNS(LOG_INV) << "Can't create unrecognized type " << type_name << LL_ENDL; - } - } - panel->getRootFolder()->setNeedsAutoRename(TRUE); } LLAssetType::EType LLViewerInventoryItem::getType() const @@ -2265,6 +2426,25 @@ const LLSaleInfo& LLViewerInventoryItem::getSaleInfo() const return LLInventoryItem::getSaleInfo(); } +const LLUUID& LLViewerInventoryItem::getThumbnailUUID() const +{ + if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_TEXTURE) + { + return mAssetUUID; + } + if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_LINK) + { + LLViewerInventoryItem *linked_item = gInventory.getItem(mAssetUUID); + return linked_item ? linked_item->getThumbnailUUID() : LLUUID::null; + } + if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_LINK_FOLDER) + { + LLViewerInventoryCategory *linked_cat = gInventory.getCategory(mAssetUUID); + return linked_cat ? linked_cat->getThumbnailUUID() : LLUUID::null; + } + return mThumbnailUUID; +} + LLInventoryType::EType LLViewerInventoryItem::getInventoryType() const { if (const LLViewerInventoryItem *linked_item = getLinkedItem()) diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h index 78f04f8baf..04410edf54 100644 --- a/indra/newview/llviewerinventory.h +++ b/indra/newview/llviewerinventory.h @@ -72,6 +72,7 @@ public: virtual const LLUUID& getCreatorUUID() const; virtual const std::string& getDescription() const; virtual const LLSaleInfo& getSaleInfo() const; + virtual const LLUUID& getThumbnailUUID() const; virtual LLInventoryType::EType getInventoryType() const; virtual bool isWearableType() const; virtual LLWearableType::EType getWearableType() const; @@ -134,8 +135,8 @@ public: virtual BOOL importLegacyStream(std::istream& input_stream); // new methods - BOOL isFinished() const { return mIsComplete; } - void setComplete(BOOL complete) { mIsComplete = complete; } + bool isFinished() const { return mIsComplete; } + void setComplete(bool complete) { mIsComplete = complete; } //void updateAssetOnServer() const; virtual void setTransactionID(const LLTransactionID& transaction_id); @@ -163,7 +164,7 @@ public: BOOL regenerateLink(); public: - BOOL mIsComplete; + bool mIsComplete; LLTransactionID mTransactionID; }; @@ -208,9 +209,18 @@ public: S32 getVersion() const; void setVersion(S32 version); - // Returns true if a fetch was issued. + // Returns true if a fetch was issued (not nessesary in progress). bool fetch(); + typedef enum { + FETCH_NONE = 0, + FETCH_NORMAL, + FETCH_RECURSIVE, + } EFetchType; + EFetchType getFetching(); + // marks as fetch being in progress or as done + void setFetching(EFetchType); + // used to help make caching more robust - for example, if // someone is getting 4 packets but logs out after 3. the viewer // may never know the cache is wrong. @@ -239,12 +249,14 @@ protected: LLUUID mOwnerID; S32 mVersion; S32 mDescendentCount; + EFetchType mFetching; LLFrameTimer mDescendentsRequested; }; class LLInventoryCallback : public LLRefCount { public: + virtual ~LLInventoryCallback() {} virtual void fire(const LLUUID& inv_item) = 0; }; @@ -286,17 +298,29 @@ class LLBoostFuncInventoryCallback: public LLInventoryCallback { public: - LLBoostFuncInventoryCallback(inventory_func_type fire_func = no_op_inventory_func, + LLBoostFuncInventoryCallback(inventory_func_type fire_func, nullary_func_type destroy_func = no_op): - mFireFunc(fire_func), mDestroyFunc(destroy_func) { + mFireFuncs.push_back(fire_func); } + LLBoostFuncInventoryCallback() + { + } + + void addOnFireFunc(inventory_func_type fire_func) + { + mFireFuncs.push_back(fire_func); + } + // virtual void fire(const LLUUID& item_id) { - mFireFunc(item_id); + for (inventory_func_type &func: mFireFuncs) + { + func(item_id); + } } // virtual @@ -307,7 +331,7 @@ public: private: - inventory_func_type mFireFunc; + std::list mFireFuncs; nullary_func_type mDestroyFunc; }; @@ -448,6 +472,8 @@ void menu_create_inventory_item(LLInventoryPanel* root, const LLSD& userdata, const LLUUID& default_parent_uuid = LLUUID::null); +void menu_create_inventory_item(LLInventoryPanel* panel, LLUUID dest_id, const LLSD& userdata, const LLUUID& default_parent_uuid = LLUUID::null, std::function folder_created_cb = NULL); + void slam_inventory_folder(const LLUUID& folder_id, const LLSD& contents, LLPointer cb); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 274192aecc..ea9d507653 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -482,14 +482,14 @@ void set_merchant_SLM_menu() LLCommand* command = LLCommandManager::instance().getCommand("marketplacelistings"); gToolBarView->enableCommand(command->id(), true); - const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); if (marketplacelistings_id.isNull()) { U32 mkt_status = LLMarketplaceData::instance().getSLMStatus(); bool is_merchant = (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MERCHANT) || (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT); if (is_merchant) { - gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, true); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MARKETPLACE_LISTINGS); LL_WARNS("SLM") << "Creating the marketplace listings folder for a merchant" << LL_ENDL; } } @@ -3744,9 +3744,7 @@ void handle_object_inspect() LLViewerObject* selected_objectp = selection->getFirstRootObject(); if (selected_objectp) { - LLSD key; - key["task"] = "task"; - LLFloaterSidePanelContainer::showPanel("inventory", key); + LLFloaterReg::showInstance("task_properties"); } /* diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 624b5df504..ba5071bac3 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -38,7 +38,7 @@ #include "llfloatermap.h" #include "llfloatermodelpreview.h" #include "llfloatersnapshot.h" -#include "llfloatersimpleoutfitsnapshot.h" +#include "llfloatersimplesnapshot.h" #include "llimage.h" #include "llimagebmp.h" #include "llimagepng.h" @@ -815,9 +815,7 @@ class LLFileEnableCloseAllWindows : public view_listener_t bool handleEvent(const LLSD& userdata) { LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); - LLFloaterSimpleOutfitSnapshot* floater_outfit_snapshot = LLFloaterSimpleOutfitSnapshot::findInstance(); - bool is_floaters_snapshot_opened = (floater_snapshot && floater_snapshot->isInVisibleChain()) - || (floater_outfit_snapshot && floater_outfit_snapshot->isInVisibleChain()); + bool is_floaters_snapshot_opened = (floater_snapshot && floater_snapshot->isInVisibleChain()); bool open_children = gFloaterView->allChildrenClosed() && !is_floaters_snapshot_opened; return !open_children && !LLNotificationsUI::LLToast::isAlertToastShown(); } @@ -832,9 +830,6 @@ class LLFileCloseAllWindows : public view_listener_t LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); if (floater_snapshot) floater_snapshot->closeFloater(app_quitting); - LLFloaterSimpleOutfitSnapshot* floater_outfit_snapshot = LLFloaterSimpleOutfitSnapshot::findInstance(); - if (floater_outfit_snapshot) - floater_outfit_snapshot->closeFloater(app_quitting); if (gMenuHolder) gMenuHolder->hideMenus(); return true; } diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 9fa8b41172..fca7af63b6 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -79,6 +79,7 @@ #include "llnotifications.h" #include "llnotificationsutil.h" #include "llpanelgrouplandmoney.h" +#include "llpanelmaininventory.h" #include "llrecentpeople.h" #include "llscriptfloater.h" #include "llscriptruntimeperms.h" @@ -1700,15 +1701,40 @@ void open_inventory_offer(const uuid_vec_t& objects, const std::string& from_nam } //////////////////////////////////////////////////////////////////////////////// + static LLUICachedControl find_original_new_floater("FindOriginalOpenWindow", false); + //show in a new single-folder window + if(find_original_new_floater && !from_name.empty()) + { + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (obj && obj->getParentUUID().notNull()) + { + if (obj->getActualType() == LLAssetType::AT_CATEGORY) + { + LLPanelMainInventory::newFolderWindow(obj_id); + } + else + { + LLPanelMainInventory::newFolderWindow(obj->getParentUUID(), obj_id); + } + } + } + else + { // Highlight item // Only show if either ShowInInventory is true OR it is an inventory // offer from an agent and the asset is not previewable const BOOL auto_open = gSavedSettings.getBOOL("ShowInInventory") || (from_agent_manual && !check_asset_previewable(asset_type)); //gSavedSettings.getBOOL("ShowInInventory") && // don't open if showininventory is false //!from_name.empty(); // don't open if it's not from anyone. - // Don't mess with open inventory panels when ShowInInventory is FALSE - if (auto_open) - LLInventoryPanel::openInventoryPanelAndSetSelection(auto_open, obj_id); + // Use correct inventory floater + //if (auto_open) + //{ + // LLFloaterReg::showInstance("inventory"); + //} + // + if (auto_open) // Don't mess with open inventory panels when ShowInInventory is FALSE + LLInventoryPanel::openInventoryPanelAndSetSelection(auto_open, obj_id, true); + } } } @@ -7593,42 +7619,47 @@ void container_inventory_arrived(LLViewerObject* object, { // create a new inventory category to put this in LLUUID cat_id; - cat_id = gInventory.createNewCategory(gInventory.getRootFolderID(), - LLFolderType::FT_NONE, - LLTrans::getString("AcquiredItems")); + gInventory.createNewCategory( + gInventory.getRootFolderID(), + LLFolderType::FT_NONE, + LLTrans::getString("AcquiredItems"), + [inventory](const LLUUID &new_cat_id) + { + LLInventoryObject::object_list_t::const_iterator it = inventory->begin(); + LLInventoryObject::object_list_t::const_iterator end = inventory->end(); + for (; it != end; ++it) + { + if ((*it)->getType() != LLAssetType::AT_CATEGORY) + { + LLInventoryObject* obj = (LLInventoryObject*)(*it); + LLInventoryItem* item = (LLInventoryItem*)(obj); + LLUUID item_id; + item_id.generate(); + time_t creation_date_utc = time_corrected(); + LLPointer new_item + = new LLViewerInventoryItem(item_id, + new_cat_id, + item->getPermissions(), + item->getAssetUUID(), + item->getType(), + item->getInventoryType(), + item->getName(), + item->getDescription(), + LLSaleInfo::DEFAULT, + item->getFlags(), + creation_date_utc); + new_item->updateServer(TRUE); + gInventory.updateItem(new_item); + } + } + gInventory.notifyObservers(); - LLInventoryObject::object_list_t::const_iterator it = inventory->begin(); - LLInventoryObject::object_list_t::const_iterator end = inventory->end(); - for ( ; it != end; ++it) - { - if ((*it)->getType() != LLAssetType::AT_CATEGORY) - { - LLInventoryObject* obj = (LLInventoryObject*)(*it); - LLInventoryItem* item = (LLInventoryItem*)(obj); - LLUUID item_id; - item_id.generate(); - time_t creation_date_utc = time_corrected(); - LLPointer new_item - = new LLViewerInventoryItem(item_id, - cat_id, - item->getPermissions(), - item->getAssetUUID(), - item->getType(), - item->getInventoryType(), - item->getName(), - item->getDescription(), - LLSaleInfo::DEFAULT, - item->getFlags(), - creation_date_utc); - new_item->updateServer(TRUE); - gInventory.updateItem(new_item); - } - } - gInventory.notifyObservers(); - if(active_panel) - { - active_panel->setSelection(cat_id, TAKE_FOCUS_NO); - } + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); + if (active_panel) + { + active_panel->setSelection(new_cat_id, TAKE_FOCUS_NO); + } + }); } else if (inventory->size() == 2) { diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index c8ac70b53b..aa8260f97a 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -63,7 +63,6 @@ #include "llcontrolavatar.h" #include "lldrawable.h" #include "llface.h" -#include "llfloaterproperties.h" #include "llfloatertools.h" #include "llfollowcam.h" #include "llhudtext.h" @@ -120,6 +119,7 @@ #include "llviewernetwork.h" #endif // [Legacy Bake] +#include "llfloaterproperties.h" // Keep legacy properties floater //#define DEBUG_UPDATE_TYPE @@ -3617,8 +3617,10 @@ void LLViewerObject::doInventoryCallback() void LLViewerObject::removeInventory(const LLUUID& item_id) { + // Keep legacy properties floater // close any associated floater properties LLFloaterReg::hideInstance("properties", item_id); + // LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RemoveTaskInventory); @@ -6211,7 +6213,7 @@ LLViewerObject::ExtraParameter* LLViewerObject::createNewParameterEntry(U16 para } default: { - LL_INFOS() << "Unknown param type." << LL_ENDL; + LL_INFOS_ONCE() << "Unknown param type: " << param_type << LL_ENDL; break; } }; diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index f8e90bd8db..9d838d3a06 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3262,6 +3262,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("InterestList"); + capabilityNames.append("InventoryThumbnailUpload"); capabilityNames.append("GetDisplayNames"); capabilityNames.append("GetExperiences"); capabilityNames.append("AgentExperiences"); diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp index 2d8fe3ae8a..c5c8ddc823 100644 --- a/indra/newview/llviewertexteditor.cpp +++ b/indra/newview/llviewertexteditor.cpp @@ -36,6 +36,7 @@ #include "llfloatersidepanelcontainer.h" #include "llfloaterworldmap.h" #include "llfocusmgr.h" +#include "llinspecttexture.h" #include "llinventorybridge.h" #include "llinventorydefines.h" #include "llinventorymodel.h" @@ -285,6 +286,21 @@ public: } virtual BOOL handleToolTip(S32 x, S32 y, MASK mask ) { + if (mItem->getThumbnailUUID().notNull()) + { + LLSD params; + params["inv_type"] = mItem->getInventoryType(); + params["thumbnail_id"] = mItem->getThumbnailUUID(); + params["asset_id"] = mItem->getAssetUUID(); + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(mToolTip) + .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) + .create_params(params)); + + return TRUE; + } + if (!mToolTip.empty()) { LLToolTipMgr::instance().show(mToolTip); diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 8af70d1d71..272b685ee1 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1430,11 +1430,52 @@ void LLViewerTextureList::decodeAllImages(F32 max_time) << LL_ENDL; } +bool LLViewerTextureList::createUploadFile(LLPointer raw_image, + const std::string& out_filename, + const S32 max_image_dimentions, + const S32 min_image_dimentions) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + + // make a copy, since convertToUploadFile scales raw image + LLPointer scale_image = new LLImageRaw( + raw_image->getData(), + raw_image->getWidth(), + raw_image->getHeight(), + raw_image->getComponents()); + + LLPointer compressedImage = LLViewerTextureList::convertToUploadFile(scale_image, max_image_dimentions); + if (compressedImage->getWidth() < min_image_dimentions || compressedImage->getHeight() < min_image_dimentions) + { + std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", + min_image_dimentions, + min_image_dimentions, + compressedImage->getWidth(), + compressedImage->getHeight()); + compressedImage->setLastError(reason); + return false; + } + if (compressedImage.isNull()) + { + compressedImage->setLastError("Couldn't convert the image to jpeg2000."); + LL_INFOS() << "Couldn't convert to j2c, file : " << out_filename << LL_ENDL; + return false; + } + if (!compressedImage->save(out_filename)) + { + compressedImage->setLastError("Couldn't create the jpeg2000 image for upload."); + LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; + return false; + } + return true; +} BOOL LLViewerTextureList::createUploadFile(const std::string& filename, const std::string& out_filename, const U8 codec, - const S32 max_image_dimentions) + const S32 max_image_dimentions, + const S32 min_image_dimentions, + bool force_square) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; // Load the image @@ -1462,8 +1503,18 @@ BOOL LLViewerTextureList::createUploadFile(const std::string& filename, image->setLastError("Image files with less than 3 or more than 4 components are not supported."); return FALSE; } + if (image->getWidth() < min_image_dimentions || image->getHeight() < min_image_dimentions) + { + std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", + min_image_dimentions, + min_image_dimentions, + image->getWidth(), + image->getHeight()); + image->setLastError(reason); + return FALSE; + } // Convert to j2c (JPEG2000) and save the file locally - LLPointer compressedImage = convertToUploadFile(raw_image, max_image_dimentions); + LLPointer compressedImage = convertToUploadFile(raw_image, max_image_dimentions, force_square); if (compressedImage.isNull()) { image->setLastError("Couldn't convert the image to jpeg2000."); @@ -1488,10 +1539,20 @@ BOOL LLViewerTextureList::createUploadFile(const std::string& filename, } // note: modifies the argument raw_image!!!! -LLPointer LLViewerTextureList::convertToUploadFile(LLPointer raw_image, const S32 max_image_dimentions) +LLPointer LLViewerTextureList::convertToUploadFile(LLPointer raw_image, const S32 max_image_dimentions, bool force_square) { LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - raw_image->biasedScaleToPowerOfTwo(max_image_dimentions); + if (force_square) + { + S32 biggest_side = llmax(raw_image->getWidth(), raw_image->getHeight()); + S32 square_size = raw_image->biasedDimToPowerOfTwo(biggest_side, max_image_dimentions); + + raw_image->scale(square_size, square_size); + } + else + { + raw_image->biasedScaleToPowerOfTwo(max_image_dimentions); + } LLPointer compressedImage = new LLImageJ2C(); if (gSavedSettings.getBOOL("LosslessJ2CUpload") && diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index 66aec3e8ca..3fccec633d 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -93,11 +93,19 @@ class LLViewerTextureList friend class LLLocalBitmap; public: + static bool createUploadFile(LLPointer raw_image, + const std::string& out_filename, + const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, + const S32 min_image_dimentions = 0); static BOOL createUploadFile(const std::string& filename, const std::string& out_filename, const U8 codec, - const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); - static LLPointer convertToUploadFile(LLPointer raw_image, const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, + const S32 min_image_dimentions = 0, + bool force_square = false); + static LLPointer convertToUploadFile(LLPointer raw_image, + const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, + bool force_square = false); static void processImageNotInDatabase( LLMessageSystem *msg, void **user_data ); // OpenSim compatibility static void receiveImageHeader(LLMessageSystem *msg, void **user_data); diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 69b2040994..fc8629fc29 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -10483,7 +10483,14 @@ void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys ) // RequestAgentUpdateAppearanceResponder::onRequestRequested() // assumes that cof version is only updated with server-bake // appearance messages. - LL_INFOS("Avatar") << "Processing appearance message version " << thisAppearanceVersion << LL_ENDL; + if (isSelf()) + { + LL_INFOS("Avatar") << "Processing appearance message version " << thisAppearanceVersion << LL_ENDL; + } + else + { + LL_INFOS("Avatar") << "Processing appearance message for " << getID() << ", version " << thisAppearanceVersion << LL_ENDL; + } // Note: // locally the COF is maintained via LLInventoryModel::accountForUpdate diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index b89e11de50..27752dddf8 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -5373,8 +5373,6 @@ void LLControlAVBridge::updateSpatialExtents() { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - LLControlAvatar* controlAvatar = getVObj()->getControlAvatar(); - LLSpatialGroup* root = (LLSpatialGroup*)mOctree->getListener(0); bool rootWasDirty = root->isDirty(); @@ -5385,10 +5383,11 @@ void LLControlAVBridge::updateSpatialExtents() // disappear when root goes off-screen" // // Expand extents to include Control Avatar placed outside of the bounds - // mDrawable crash reported by Aleric Inglewood - // if (controlAvatar && (rootWasDirty || controlAvatar->mPlaying)) - if (controlAvatar && controlAvatar->mDrawable.notNull() && (rootWasDirty || controlAvatar->mPlaying)) - // + LLControlAvatar* controlAvatar = getVObj() ? getVObj()->getControlAvatar() : NULL; + if (controlAvatar + && controlAvatar->mDrawable + && controlAvatar->mDrawable->getEntry() + && (rootWasDirty || controlAvatar->mPlaying)) { root->expandExtents(controlAvatar->mDrawable->getSpatialExtents(), *mDrawable->getXform()); } diff --git a/indra/newview/particleeditor.cpp b/indra/newview/particleeditor.cpp index d3507a3f7b..84d59d5713 100644 --- a/indra/newview/particleeditor.cpp +++ b/indra/newview/particleeditor.cpp @@ -538,9 +538,19 @@ void ParticleEditor::onInjectButtonClicked() // if no #Firestorm folder was found, create one if (categoryID.isNull()) { - categoryID = gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, ROOT_FIRESTORM_FOLDER); + gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, ROOT_FIRESTORM_FOLDER, [this](const LLUUID& new_cat_id) + { + createScriptInventoryItem(new_cat_id); + }); } + else + { + createScriptInventoryItem(categoryID); + } +} +void ParticleEditor::createScriptInventoryItem(LLUUID categoryID) +{ // if still no #Firestorm folder was found, try to find the default "Scripts" folder if (categoryID.isNull()) { diff --git a/indra/newview/particleeditor.h b/indra/newview/particleeditor.h index ecf75ca801..c8f69e9710 100644 --- a/indra/newview/particleeditor.h +++ b/indra/newview/particleeditor.h @@ -58,6 +58,7 @@ class ParticleEditor : public LLFloater void updateUI(); std::string createScript(); + void createScriptInventoryItem(LLUUID categoryID); void onParameterChange(); void onCopyButtonClicked(); diff --git a/indra/newview/quickprefs.cpp b/indra/newview/quickprefs.cpp index 8cd211e4ee..301f7baf0a 100644 --- a/indra/newview/quickprefs.cpp +++ b/indra/newview/quickprefs.cpp @@ -63,7 +63,7 @@ class FSSettingsCollector : public LLInventoryCollectFunctor public: FSSettingsCollector() { - mMarketplaceFolderUUID = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false); + mMarketplaceFolderUUID = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); } virtual ~FSSettingsCollector() {} diff --git a/indra/newview/rlvinventory.cpp b/indra/newview/rlvinventory.cpp index 01915a7dd9..f8362411b9 100644 --- a/indra/newview/rlvinventory.cpp +++ b/indra/newview/rlvinventory.cpp @@ -479,12 +479,7 @@ void RlvRenameOnWearObserver::doneIdle() { // "No modify" item with a non-renameable parent: create a new folder named and move the item into it inventory_func_type f = boost::bind(RlvRenameOnWearObserver::onCategoryCreate, _1, pItem->getUUID()); - LLUUID idFolder = gInventory.createNewCategory(pFolder->getUUID(), LLFolderType::FT_NONE, strFolderName, f); - if (idFolder.notNull()) - { - // Not using the new 'CreateInventoryCategory' cap so manually invoke the callback - RlvRenameOnWearObserver::onCategoryCreate(idFolder, pItem->getUUID()); - } + gInventory.createNewCategory(pFolder->getUUID(), LLFolderType::FT_NONE, strFolderName, f); } } } @@ -532,9 +527,7 @@ bool RlvGiveToRLVOffer::createDestinationFolder(const std::string& strPath) else { inventory_func_type f = boost::bind(RlvGiveToRLVOffer::onCategoryCreateCallback, _1, this); - const LLUUID idTemp = gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, RLV_ROOT_FOLDER, f); - if (idTemp.notNull()) - onCategoryCreateCallback(idTemp, this); + gInventory.createNewCategory(gInventory.getRootFolderID(), LLFolderType::FT_NONE, RLV_ROOT_FOLDER, f); } return true; } @@ -567,9 +560,7 @@ void RlvGiveToRLVOffer::onCategoryCreateCallback(LLUUID idFolder, RlvGiveToRLVOf { LLInventoryObject::correctInventoryName(strFolder); inventory_func_type f = boost::bind(RlvGiveToRLVOffer::onCategoryCreateCallback, _1, pInstance); - const LLUUID idTemp = gInventory.createNewCategory(idFolder, LLFolderType::FT_NONE, strFolder, f); - if (idTemp.notNull()) - onCategoryCreateCallback(idTemp, pInstance); + gInventory.createNewCategory(idFolder, LLFolderType::FT_NONE, strFolder, f); return; } } diff --git a/indra/newview/skins/ansastorm/xui/de/panel_main_inventory.xml b/indra/newview/skins/ansastorm/xui/de/panel_main_inventory.xml index 295ee640df..bb75ea8238 100644 --- a/indra/newview/skins/ansastorm/xui/de/panel_main_inventory.xml +++ b/indra/newview/skins/ansastorm/xui/de/panel_main_inventory.xml @@ -9,6 +9,7 @@ [ITEM_COUNT] Elemente abgerufen [FILTER] + Inventar @@ -16,11 +17,15 @@ + + + + - + @@ -77,33 +82,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + +