diff --git a/autobuild.xml b/autobuild.xml
index b135c99810..6fa46093e1 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -5,7 +5,7 @@
SDL2
- InventoryInboxToggleState
-
- Comment
- Stores the open/closed state of inventory Received items panel
- Persist
- 1
- Type
- Boolean
- Value
- 0
InventoryLinking
@@ -7692,6 +7681,17 @@
Backup
0
+ LastUIFeatureVersion
+
+ Comment
+ UI Feature Version number for tracking feature notification between viewer builds
+ Persist
+ 1
+ Type
+ LLSD
+ Value
+
+
LastFindPanel
Comment
@@ -18963,6 +18963,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
@@ -18970,7 +18981,7 @@ Change of this parameter will affect the layout of buttons in notification toast
Type
U32
Value
- 1
+ 20
PoolSizeUpload
@@ -23515,6 +23526,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
@@ -23548,6 +23570,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
@@ -25688,7 +25733,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 8ed497199e..2f165fff6d 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -231,7 +231,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"
@@ -1787,7 +1787,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 e823bc3756..ac9e92a226 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; }
@@ -253,7 +254,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 2537041dcf..38477be80c 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 2fe08b39f5..7436146005 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,29 +108,13 @@
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;
-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,
@@ -161,6 +144,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
{
@@ -196,6 +185,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 |
// +=================================================+
@@ -235,58 +283,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
@@ -372,7 +379,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) ||
@@ -455,6 +462,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
@@ -848,33 +881,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"));
@@ -908,7 +930,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
@@ -919,14 +941,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());
@@ -938,13 +966,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())
{
@@ -961,6 +990,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id,
}
}
}
+ }
}
}
@@ -997,21 +1027,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"));
@@ -1121,7 +1145,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"));
}
@@ -1370,6 +1394,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();
@@ -1450,7 +1484,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())
{
@@ -1462,7 +1496,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())
{
@@ -1762,7 +1796,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);
@@ -1860,6 +1894,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
@@ -1914,7 +1954,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));
}
@@ -2174,9 +2214,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;
@@ -2197,17 +2242,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;
}
}
@@ -2412,6 +2460,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,13 +2573,32 @@ void LLFolderBridge::buildDisplayName() const
std::string LLFolderBridge::getLabelSuffix() const
{
static LLCachedControl folder_loading_message_delay(gSavedSettings, "FolderLoadingMessageWaitTime", 0.5f);
+ 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;
@@ -2553,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)
@@ -2778,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();
@@ -2798,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);
@@ -2817,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);
@@ -3099,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 &&
@@ -3109,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);
}
@@ -3128,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)
{
@@ -3146,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());
}
}
}
@@ -3167,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)
@@ -3238,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)
{
@@ -3541,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();
@@ -3622,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;
}
@@ -3641,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;
}
@@ -3675,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)
@@ -3756,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
@@ -4060,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;
@@ -4121,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);
@@ -4136,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)
@@ -4226,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
{
@@ -4234,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
{
@@ -4250,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
{
@@ -4269,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);
}
}
}
@@ -4301,6 +4464,7 @@ void LLFolderBridge::perform_pasteFromClipboard()
//changeCategoryParent() implicity calls dirtyFilter
changeCategoryParent(model, vicat, parent_id, FALSE);
}
+ if (cb) cb->fire(item_id);
}
}
else
@@ -4322,6 +4486,7 @@ void LLFolderBridge::perform_pasteFromClipboard()
//changeItemParent() implicity calls dirtyFilter
changeItemParent(model, viitem, parent_id, FALSE);
}
+ if (cb) cb->fire(item_id);
}
}
}
@@ -4342,6 +4507,7 @@ void LLFolderBridge::perform_pasteFromClipboard()
{
copy_inventory_category(model, vicat, parent_id);
}
+ if (cb) cb->fire(item_id);
}
}
else
@@ -4357,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
@@ -4377,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()))
@@ -4386,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]
}
@@ -4405,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);
@@ -4424,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)
@@ -4434,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
@@ -4477,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())
@@ -4512,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)
@@ -4540,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)
@@ -4554,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"));
@@ -4580,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())
{
@@ -4611,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);
@@ -4635,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
@@ -4657,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
@@ -4809,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"));
@@ -4827,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))
@@ -4847,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)
{
@@ -4879,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"));
@@ -4942,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();
@@ -4958,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)
@@ -4975,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.
@@ -4989,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:
@@ -5004,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:
@@ -5014,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;
}
@@ -5343,143 +5538,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;
}
@@ -5496,21 +5604,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;
@@ -5541,7 +5650,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);
}
}
@@ -5574,7 +5682,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();
@@ -5591,12 +5700,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);
@@ -5617,7 +5726,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());
@@ -5782,26 +5891,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());
}
@@ -5811,6 +5921,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)
@@ -5819,11 +5930,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);
+ }
+ });
}
}
@@ -5896,11 +6011,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)
{
@@ -5997,13 +6117,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
{
@@ -6013,7 +6133,7 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item,
inv_item->getUUID(),
mUUID,
std::string(),
- LLPointer(NULL));
+ cb);
}
}
}
@@ -6632,7 +6752,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");
//
}
@@ -8400,16 +8520,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;
@@ -8468,21 +8600,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 a68c49dddb..1120d6eba3 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,7 +2280,44 @@ 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;
}
std::string get_category_path(LLUUID cat_id)
@@ -2144,6 +2338,329 @@ std::string get_category_path(LLUUID cat_id)
return localized_cat_name;
}
}
+// 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
///----------------------------------------------------------------------------
@@ -2327,6 +2844,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)
{
@@ -2392,6 +2922,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)
@@ -2688,9 +3223,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)
@@ -2714,17 +3247,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();
@@ -2887,7 +3419,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)
{
@@ -2921,10 +3454,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();
@@ -2934,7 +3480,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),
@@ -3049,51 +3595,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
@@ -3113,6 +3615,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
@@ -3123,10 +3633,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)
@@ -3213,7 +3729,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 a0ca7133f8..e9bacaf5b0 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);
@@ -116,10 +115,65 @@ bool is_only_cats_selected(const uuid_vec_t& selected_uuids);
bool is_only_items_selected(const uuid_vec_t& selected_uuids);
std::string get_category_path(LLUUID cat_id);
+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
@@ -335,6 +389,20 @@ public:
};
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLFindBrokenLinks
+//
+// Collects broken links
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+class LLFindBrokenLinks : public LLInventoryCollectFunctor
+{
+public:
+ LLFindBrokenLinks() {}
+ virtual ~LLFindBrokenLinks() {}
+ virtual bool operator()(LLInventoryCategory* cat,
+ LLInventoryItem* item);
+};
+
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLFindByMask
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -433,6 +501,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
{
@@ -508,8 +585,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